나는 DDD를 공부하고 있으며 현재 실제 코드에 개념을 적용하는 방법을 찾기 위해 고심하고 있습니다. 나는 N-tier에 대해 약 10 년의 경험을 가지고 있기 때문에, 어려움을 겪고있는 이유는 나의 정신 모델이 그 디자인과 너무 연관되어 있기 때문일 가능성이 높습니다.
Asp.NET 웹 응용 프로그램을 만들었으며 웹 모니터링 응용 프로그램이라는 간단한 도메인부터 시작합니다. 요구 사항 :
- 사용자는 모니터링 할 새 웹앱을 등록 할 수 있어야합니다. 웹 앱의 이름은 알기 쉽고 URL을 가리 킵니다.
- 웹 앱은 주기적으로 상태 (온라인 / 오프라인)를 폴링합니다.
- 웹앱은 주기적으로 현재 버전을 폴링합니다 (웹앱에는 특정 마크 업으로 시스템 버전을 선언하는 파일 인 “/version.html”이있을 것으로 예상됩니다).
내 의심은 주로 책임 분담, 각 사물 (유효성, 비즈니스 규칙 등)에 적합한 장소를 찾는 것에 관한 것입니다. 아래에서는 코드를 작성하고 질문과 고려 사항이 포함 된 주석을 추가했습니다.
비판하고 조언하십시오 . 미리 감사드립니다!
도메인 모델
모든 비즈니스 규칙을 캡슐화하도록 모델링되었습니다.
// 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().
}
}
그리고 Create 및 Deletes를 처리하는 DomainService 클래스는 집계 자체의 문제가 아니라고 생각합니다.
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;
}
}
응용 프로그램 계층
아래 클래스는 WebMonitoring 도메인을위한 외부 인터페이스 (웹 인터페이스, 나머지 API 등)를 제공합니다. 현재로서는 셸일 뿐이며 적절한 서비스로 호출을 리디렉션하지만 앞으로 더 많은 로직을 조정하기 위해 앞으로도 계속 확장 될 것입니다 (도메인 모델을 통해 항상 달성 됨).
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);
}
}
문제 종결
여기와 다른 질문 에 대한 답변을 수집 한 후 다른 이유로 열었지만 궁극적 으로이 질문 과 같은 시점에 도달하면 이보다 깨끗하고 더 나은 해결책을 찾았습니다.
답변
귀하의 WebApp
골재 에 대한 조언이 길어지면 , repository
여기 를 당기는 것이 올바른 접근법이 아니라는 것에 전적으로 동의합니다 . 내 경험상 총계는 자신의 상태에 따라 행동이 옳은지 아닌지를 ‘결정’할 것입니다. 따라서 상태가 아닌 경우 다른 서비스에서 벗어날 수 있습니다. 그러한 검사가 필요한 경우 일반적으로 집계를 호출하는 서비스 (예 :)로 이동합니다 WebAppService
.
또한 여러 응용 프로그램이 동시에 집계를 호출하려는 유스 케이스에 도달 할 수 있습니다. 이런 일이 발생하면 오랜 시간이 걸리는 이와 같은 아웃 바운드 통화를 수행하는 동안 다른 용도로 집계를 차단하고 있습니다. 결국 집계 처리 속도가 느려지고 바람직하지 않은 것으로 생각됩니다.
따라서 약간의 유효성 검사를 이동하면 집계가 상당히 얇아지는 것처럼 보일 수도 있지만,으로 이동하는 것이 좋습니다 WebAppService
.
또한 WebAppRegistered
이벤트 게시를 집계로 옮기는 것이 좋습니다 . 집계는 생성되는 사람이므로 생성 프로세스가 성공하면 해당 지식을 세계에 게시하는 것이 좋습니다.
이것이 @Levidad를 도울 수 있기를 바랍니다!