Bazı DDD kavramları gerçek koda nasıl uygulanır? İçindeki belirli sorular


9

DDD okuyorum ve şu anda kavramları gerçek kodda uygulamak için bir yol bulmak için mücadele ediyorum. N katmanında yaklaşık 10 yıllık tecrübem var, bu yüzden mücadele etmemin nedeni zihinsel modelimin bu tasarıma çok bağlı olması.

Bir Asp.NET Web Uygulaması oluşturdum ve basit bir etki alanı ile başlıyorum: bir web izleme uygulaması. Gereksinimler:

  • Kullanıcının izlemek için yeni bir Web Uygulaması kaydedebilmesi gerekir. Web uygulamasının kolay bir adı vardır ve bir URL'yi gösterir;
  • Web uygulaması düzenli olarak bir durum için (çevrimiçi / çevrimdışı) anket yapar;
  • Web uygulaması mevcut sürümü için periyodik olarak yoklayacaktır (web uygulamasının, sistem sürümünü belirli bir biçimlendirmede bildiren bir dosya olan "/version.html" olması bekleniyor).

Şüphelerim esas olarak sorumlulukların bölünmesi, her şey için uygun yeri bulma (doğrulama, iş kuralı, vb.) İle ilgilidir. Aşağıda, bazı kodlar yazdım ve sorular ve düşüncelerle yorumlar ekledim.

Lütfen eleştirin ve tavsiye edin . Şimdiden teşekkürler!


ALAN MODELİ

Tüm iş kurallarını kapsayacak şekilde modellenmiştir.

// Encapsulates logic for creating and validating Url's.
// Based on "Unbreakable Domain Models", YouTube talk from Mathias Verraes
// See https://youtu.be/ZJ63ltuwMaE
public class Url: ValueObject
{
    private System.Uri _uri;

    public string Url => _uri.ToString();

    public Url(string url)
    {
        _uri = new Uri(url, UriKind.Absolute); // Fails for a malformed URL.
    }
}

// Base class for all Aggregates (root or not).
public abstract class Aggregate
{
    public Guid Id { get; protected set; } = Guid.NewGuid();
    public DateTime CreatedAt { get; protected set; } = DateTime.UtcNow;
}

public class WebApp: Aggregate
{
    public string Name { get; private set; }
    public Url Url { get; private set; }
    public string Version { get; private set; }
    public DateTime? VersionLatestCheck { get; private set; }
    public bool IsAlive { get; private set; }
    public DateTime? IsAliveLatestCheck { get; private set; }

    public WebApp(Guid id, string name, Url url)
    {
        if (/* some business validation fails */)
            throw new InvalidWebAppException(); // Custom exception.

        Id = id;
        Name = name;
        Url = url;
    }

    public void UpdateVersion()
    {
        // Delegates the plumbing of HTTP requests and markup-parsing to infrastructure.
        var versionChecker = Container.Get<IVersionChecker>();
        var version = versionChecker.GetCurrentVersion(this.Url);

        if (version != this.Version)
        {
            var evt = new WebAppVersionUpdated(
                this.Id, 
                this.Name, 
                this.Version /* old version */, 
                version /* new version */);
            this.Version = version;
            this.VersionLatestCheck = DateTime.UtcNow;

            // Now this eems very, very wrong!
            var repository = Container.Get<IWebAppRepository>();
            var updateResult = repository.Update(this);
            if (!updateResult.OK) throw new Exception(updateResult.Errors.ToString());

            _eventDispatcher.Publish(evt);
        }

        /*
         * I feel that the aggregate should be responsible for checking and updating its
         * version, but it seems very wrong to access a Global Container and create the
         * necessary instances this way. Dependency injection should occur via the
         * constructor, and making the aggregate depend on infrastructure also seems wrong.
         * 
         * But if I move such methods to WebAppService, I'm making the aggregate
         * anaemic; It will become just a simple bag of getters and setters.
         *
         * Please advise.
         */
    }

    public void UpdateIsAlive()
    {
        // Code very similar to UpdateVersion().
    }
}

Ve Oluşturma ve Silme işlemlerini gerçekleştirecek bir DomainService sınıfı, Toplama'nın kendisinin endişesi olmadığına inanıyorum.

public class WebAppService
{
    private readonly IWebAppRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IEventDispatcher _eventDispatcher;

    public WebAppService(
        IWebAppRepository repository, 
        IUnitOfWork unitOfWork, 
        IEventDispatcher eventDispatcher
    ) {
        _repository = repository;
        _unitOfWork = unitOfWork;
        _eventDispatcher = eventDispatcher;
    }

    public OperationResult RegisterWebApp(NewWebAppDto newWebApp)
    {
        var webApp = new WebApp(newWebApp);

        var addResult = _repository.Add(webApp);
        if (!addResult.OK) return addResult.Errors;

        var commitResult = _unitOfWork.Commit();
        if (!commitResult.OK) return commitResult.Errors;

        _eventDispatcher.Publish(new WebAppRegistered(webApp.Id, webApp.Name, webApp.Url);
        return OperationResult.Success;
    }

    public OperationResult RemoveWebApp(Guid webAppId)
    {
        var removeResult = _repository.Remove(webAppId);
        if (!removeResult) return removeResult.Errors;

        _eventDispatcher.Publish(new WebAppRemoved(webAppId);
        return OperationResult.Success;
    }
}

UYGULAMA KATMANI

Aşağıdaki sınıf, dış dünyaya WebMonitoring etki alanı için bir arabirim sağlar (web arabirimleri, geri kalan api'ler, vb.). Şu anda sadece uygun kabuk çağrılarını yönlendiren bir kabuk, ancak gelecekte daha fazla mantık düzenlemek için büyüyecek (her zaman etki alanı modelleri aracılığıyla gerçekleştirilir).

public class WebMonitoringAppService
{
    private readonly IWebAppQueries _webAppQueries;
    private readonly WebAppService _webAppService;

    /*
     * I'm not exactly reaching for CQRS here, but I like the idea of having a
     * separate class for handling queries right from the beginning, since it will
     * help me fine-tune them as needed, and always keep a clean separation between
     * crud-like queries (needed for domain business rules) and the ones for serving
     * the outside-world.
     */

    public WebMonitoringAppService(
        IWebAppQueries webAppQueries, 
        WebAppService webAppService
    ) {
        _webAppQueries = webAppQueries;
        _webAppService = webAppService;
    }

    public WebAppDetailsDto GetDetails(Guid webAppId)
    {
        return _webAppQueries.GetDetails(webAppId);
    }

    public List<WebAppDetailsDto> ListWebApps()
    {
        return _webAppQueries.ListWebApps(webAppId);
    }

    public OperationResult RegisterWebApp(NewWebAppDto newWebApp)
    {
        return _webAppService.RegisterWebApp(newWebApp);
    }

    public OperationResult RemoveWebApp(Guid webAppId)
    {
        return _webAppService.RemoveWebApp(newWebApp);
    }
}

Konuları Kapatma

Burada ve farklı bir nedenden dolayı açtığım ama sonuçta bununla aynı noktaya geldiğim diğer soruda cevapları topladıktan sonra , bu daha temiz ve daha iyi bir çözüm buldum:

Github Gist'te çözüm önerisi


Çok okuyorum, ama CQRS ve diğer ortogonal kalıpları ve uygulamaları uygulayanlar dışında böyle pratik örnekler bulamadım, ama şu anda bu basit şeyi arıyorum.
Levidad

1
Bu soru codereview.stackexchange.com için daha uygun olabilir
VoiceOfUnreason

2
N-katmanlı uygulamalarla geçirdiğiniz çok zamanla kendimden hoşlanıyorum. DDD'yi sadece kitaplardan, forumlardan vb. Biliyorum, bu yüzden sadece bir yorum göndereceğim. İki tür doğrulama vardır: giriş doğrulaması ve iş kuralları doğrulaması. Giriş doğrulaması Uygulama katmanına ve Etki Alanı doğrulaması Etki Alanı Katmanına gider. WebApp, bir toplayıcı değil, bir Varlık gibi görünür ve WebAppService, bir DomainService'ten çok bir uygulama hizmeti gibi görünür. Ayrıca, topluluğunuz altyapı ile ilgili bir konu olan Konteynere referansta bulunmaktadır. Aynı zamanda bir servis bulucu gibi görünüyor.
Adrian Iftode

1
Evet, çünkü bir ilişkiyi modellemiyor. Toplamalar, etki alanı nesneleri arasındaki ilişkileri modelliyor. WebApp sadece ham veri ve bazı davranışlara sahiptir ve örneğin aşağıdaki değişmezle başa çıkabilir: çılgın gibi sürümleri güncellemek uygun değildir, yani mevcut sürüm 1 olduğunda sürüm 3'e adım atmak
Adrian Iftode 2:18 '

1
ValueObject örnekleri arasında eşitliği uygulayan bir yöntem olduğu sürece, bence sorun yok. Senaryonuzda bir Sürüm değeri nesnesi oluşturabilirsiniz. Anlamsal sürüm oluşturmayı kontrol edin, değişmezler ve davranışlar da dahil olmak üzere bu değer nesnesini nasıl modelleyebileceğiniz hakkında birçok fikir edineceksiniz. WebApp bir havuzla konuşmamalıdır, aslında projenizden, doğrudan veya dolaylı olarak (arayüzler aracılığıyla) altyapı ile ilgili başka herhangi bir şeye (depolar, iş birimi) ilişkin herhangi bir referansa sahip olmamanın güvenli olduğuna inanıyorum.
Adrian Iftode

Yanıtlar:


1

Agregatınızla ilgili uzun bir tavsiye çizgisi olduğu sürece, WebAppbu konuyu repositoryçekmenin burada doğru bir yaklaşım olmadığını tamamen kabul ediyorum . Deneyimlerime göre, Agrega, bir eylemin kendi durumuna göre olup olmadığına dair 'karar' verecektir. Bu nedenle, diğer hizmetlerden faydalanabileceğini belirtmez. Eğer böyle bir kontrole ihtiyaç duyarsanız, genellikle bunu toplamı çağıran hizmete taşırdım (örneğin örneğinizde WebAppService).

Ayrıca, birkaç uygulamanın aynı anda toplamınızı çağırmak istediği kullanım durumuna da inebilirsiniz. Böyle bir şey olursa, bunun gibi uzun süren aramalar yaparken toplamınızı diğer kullanımlar için engelliyorsunuz demektir. Bu sonuçta toplama işlemlerini yavaşlatacaktır, bence bu da arzu edilmez.

Bu nedenle, bu doğrulama işlemini biraz taşırsanız toplamınızın oldukça ince görünmesine rağmen, onu taşımak için daha iyi olduğunu düşünüyorum WebAppService.

Ayrıca WebAppRegisteredetkinliğin yayınlanmasını topluluğunuza taşımanızı da öneririm . Agrega yaratılan kişidir, bu yüzden eğer yaratma süreci başarılı olursa, bu bilgiyi dünyaya yayınlamasına izin vermek mantıklıdır.

Umarım bu size yardımcı olur @Levidad!


Merhaba Steven, katkılarınız için teşekkürler. Burada, ultimatelly'nin bu sorunun aynı noktasına ulaştığı başka bir soru açtım ve sonunda bu sorun için daha Temiz bir Çözüm girişimi buldum . Lütfen bir göz atın ve düşüncelerinizi paylaşır mısınız? Bence yukarıdaki önerileriniz doğrultusunda ilerliyor.
Levidad

Elbette Levidad, bir göz atacağım!
Steven

1
'Her iki cevabı da' Mantıksız Ses 've' Erik Eidt'ten kontrol ettim. Her ikisi de, orada var olduğunuz soru hakkında yorum yapacağım satırlar boyunca, bu yüzden gerçekten değer katamam. Ve sorunuzu cevaplamak için: WebAppAR'nizin paylaştığınız 'Temiz Çözüm'de kurulduğu yol gerçekten de bir Agrega için iyi bir yaklaşım olarak göreceğim şeyler boyunca. Bu Levidad size yardımcı umuyoruz!
Steven
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.