Làm thế nào để áp dụng một số khái niệm về DDD vào mã thực tế? Câu hỏi cụ thể bên trong


9

Tôi đã nghiên cứu DDD và tôi hiện đang đấu tranh để tìm cách áp dụng các khái niệm trong mã thực tế. Tôi có khoảng 10 năm kinh nghiệm với N-tier, vì vậy rất có thể lý do tôi gặp khó khăn là mô hình tinh thần của tôi quá gắn liền với thiết kế đó.

Tôi đã tạo một Ứng dụng web Asp.NET và tôi bắt đầu với một miền đơn giản: ứng dụng giám sát web. Yêu cầu:

  • Người dùng phải có thể đăng ký Ứng dụng web mới để theo dõi. Ứng dụng web có tên thân thiện và trỏ đến một URL;
  • Ứng dụng web sẽ thăm dò định kỳ cho một trạng thái (trực tuyến / ngoại tuyến);
  • Ứng dụng web sẽ thăm dò định kỳ cho phiên bản hiện tại của nó (ứng dụng web dự kiến ​​sẽ có "/version.html", đây là một tệp khai báo phiên bản hệ thống của nó trong một đánh dấu cụ thể).

Nghi ngờ của tôi quan tâm chủ yếu là phân chia trách nhiệm, tìm vị trí thích hợp cho từng điều (xác nhận, quy tắc kinh doanh, vv). Dưới đây, tôi đã viết một số mã và thêm ý kiến ​​với các câu hỏi và cân nhắc.

Hãy phê bình và khuyên bảo . Cảm ơn trước!


MÔ HÌNH TÊN

Mô hình hóa để đóng gói tất cả các quy tắc kinh doanh.

// 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().
    }
}

Và một lớp DomainService để xử lý các Tạo và Xóa, mà tôi tin rằng không phải là mối quan tâm của chính Tập hợp.

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;
    }
}

LỚP ỨNG DỤNG

Lớp bên dưới cung cấp giao diện cho miền WebMonitoring với thế giới bên ngoài (giao diện web, phần còn lại, v.v.). Tại thời điểm này, nó chỉ là một vỏ bọc, chuyển hướng các cuộc gọi đến các dịch vụ phù hợp, nhưng nó sẽ phát triển trong tương lai để phối hợp nhiều logic hơn (luôn luôn được thực hiện thông qua các mô hình miền).

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);
    }
}

Kết thúc vấn đề

Sau khi thu thập các câu trả lời ở đây và trong câu hỏi khác , mà tôi đã mở ra vì một lý do khác nhưng tối hậu thư đã đến cùng một điểm với câu hỏi này, tôi đã đưa ra giải pháp sạch hơn và tốt hơn này:

Đề xuất giải pháp trong Github Gist


Tôi đã đọc rất nhiều, nhưng tôi không tìm thấy những ví dụ thực tế như vậy, ngoại trừ những người áp dụng CQRS và các mô hình và thực tiễn trực giao khác, nhưng tôi đang tìm kiếm điều đơn giản này ngay bây giờ.
Levidad

1
Câu hỏi này có thể phù hợp hơn với codereview.stackexchange.com
VoiceOfUnreason

2
Bản thân tôi cũng thích bạn với nhiều thời gian dành cho các ứng dụng n-tier. Tôi chỉ biết về DDD từ sách, diễn đàn, v.v., vì vậy tôi sẽ chỉ đăng một bình luận. Có hai loại xác nhận: xác thực đầu vào và xác thực quy tắc kinh doanh. Xác thực đầu vào đi vào lớp Ứng dụng và Xác thực tên miền đi trong Lớp Miền. WebApp trông giống như một Thực thể chứ không phải là một aggreagate và WebAppService trông giống một dịch vụ ứng dụng hơn là một Dịch vụ Miền. Ngoài ra tổng hợp của bạn tham chiếu Container là một mối quan tâm cơ sở hạ tầng. Nó cũng trông giống như một công cụ định vị dịch vụ.
Adrian Iftode

1
Có, bởi vì nó không mô hình một mối quan hệ. Các tập hợp đang mô hình hóa các mối quan hệ giữa các đối tượng miền. WebApp chỉ có dữ liệu thô và một số hành vi và có thể xử lý ví dụ với bất biến sau: không cập nhật các phiên bản như crazy tức là chuyển sang phiên bản 3 khi phiên bản hiện tại là 1.
Adrian Iftode

1
Miễn là ValueObject có một phương thức thực hiện sự bình đẳng giữa các thể hiện, tôi nghĩ là ổn. Trong kịch bản của bạn, bạn có thể tạo một đối tượng giá trị Phiên bản. Kiểm tra phiên bản ngữ nghĩa, bạn sẽ nhận được rất nhiều ý tưởng về cách bạn có thể mô hình hóa đối tượng giá trị này, bao gồm cả bất biến và hành vi. WebApp không nên nói chuyện với một kho lưu trữ, thực sự tôi tin rằng không có bất kỳ tài liệu tham khảo nào từ dự án của bạn có chứa nội dung miền đến bất kỳ thứ gì khác liên quan đến cơ sở hạ tầng (kho lưu trữ, đơn vị công việc) trực tiếp hoặc gián tiếp (thông qua giao diện).
Adrian Iftode

Câu trả lời:


1

Một dòng dài lời khuyên về WebApptổng hợp của bạn , tôi hoàn toàn đồng ý rằng kéo vào repositorykhông phải là phương pháp đúng đắn ở đây. Theo kinh nghiệm của tôi, Uẩn sẽ đưa ra 'quyết định' cho dù một hành động có ổn hay không dựa trên trạng thái của chính nó. Do đó, không phải trên tiểu bang nó có thể kéo từ các dịch vụ khác. Nếu bạn cần một tấm séc như vậy, thông thường tôi sẽ chuyển nó sang dịch vụ gọi tổng hợp (trong ví dụ của bạn là WebAppService).

Ngoài ra, bạn có thể sử dụng trường hợp sử dụng mà một số ứng dụng muốn gọi đồng thời tổng hợp của bạn. Nếu điều này xảy ra, trong khi bạn đang thực hiện các cuộc gọi đi như thế này có thể mất nhiều thời gian, thì bạn sẽ chặn tổng hợp của bạn cho các cách sử dụng khác. Điều này cuối cùng sẽ làm chậm quá trình xử lý tổng hợp, điều mà tôi nghĩ cũng không mong muốn.

Vì vậy, mặc dù có vẻ như tổng hợp của bạn trở nên khá mỏng nếu bạn di chuyển một chút xác nhận đó, tôi nghĩ rằng tốt hơn là chuyển nó sang WebAppService.

Tôi cũng đề nghị chuyển việc xuất bản WebAppRegisteredsự kiện vào tổng hợp của bạn. Tổng hợp là anh chàng được tạo ra, vì vậy nếu quá trình sáng tạo của nó thành công, sẽ rất hợp lý khi để nó xuất bản kiến ​​thức đó ra thế giới.

Hy vọng điều này sẽ giúp bạn ra @Levidad!


Xin chào Steven, cảm ơn bạn đã đóng góp. Tôi đã mở một câu hỏi khác ở đây rằng ultimatelly có cùng quan điểm với câu hỏi này và cuối cùng tôi đã nghĩ ra một nỗ lực Giải pháp sạch hơn cho vấn đề này. Bạn có vui lòng xem và chia sẻ suy nghĩ của bạn? Tôi nghĩ rằng nó đi theo hướng gợi ý của bạn ở trên.
Levidad

Chắc chắn điều Levidad, tôi sẽ có một cái nhìn!
Steven

1
Tôi vừa kiểm tra cả hai câu trả lời, từ 'Tiếng nói bất hợp lý' và 'Erik Eidt'. Cả hai đều dọc theo những gì tôi sẽ nhận xét về câu hỏi bạn đã có, vì vậy tôi thực sự không thể thêm giá trị ở đó. Và, để trả lời câu hỏi của bạn: Cách bạn WebAppAR được thiết lập trong 'Giải pháp sạch hơn' mà bạn chia sẻ thực sự dọc theo những gì tôi sẽ xem là một cách tiếp cận tốt cho Tổng hợp. Hy vọng điều này sẽ giúp bạn ra Levidad!
Steven
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.