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: