Ben*_*n J 5 c# architecture oop domain-driven-design onion-architecture
我正在将一个"大球泥"(BBOM)式系统迁移到基于域驱动设计思想的系统.
在重构的各种迭代之后,域聚合/实体当前使用内部状态对象建模,如本文中Vaughn Vernon所描述的,例如:https://vaughnvernon.co/?p = 889#comment-1896
所以基本上,实体可能看起来像这样:
public class Customer
{
private readonly CustomerState state;
public Customer(CustomerState state)
{
this.state = state;
}
public Customer()
{
this.state = new CustomerState();
}
public string CustomerName => this.state.CustomerName;
[...]
}
Run Code Online (Sandbox Code Playgroud)
截至今天,该系统中的状态对象始终是来自当前使用的应用程序的专有数据访问框架的数据库表包装器,其类似于活动记录模式.因此,所有状态对象都从数据访问框架的基类部分继承.目前,无法将POCO用作状态对象,实体框架或其中任何一种.
该应用程序目前使用经典的层架构,其中基础结构(包括提到的表包装器/状态对象)位于底部,然后是域.域知道基础结构,并且使用基础结构在域中实现存储库.正如您在上面所看到的,大多数实体都包含一个公共构造函数,用于在域内方便地创建新实例,内部只是创建一个新的状态对象(因为域知道它).
现在,我们希望进一步发展这个并逐渐转变架构,从而产生更多的"洋葱"式架构.在该体系结构中,域只包含存储库接口,实际的实现将由位于其上的基础结构层提供.在这种情况下,域无法再知道实际的状态对象/数据库表包装器.
解决这个问题的一个想法是让状态对象实现域定义的接口,这实际上似乎是一个很好的解决方案.它在技术上也是可行的,因为即使状态对象必须从特殊的数据访问基类继承,它们也可以自由地实现接口.所以上面的例子将改为:
public class Customer
{
private readonly ICustomerState state;
public Customer(ICustomerState state)
{
this.state = state;
}
public Customer()
{
this.state= <<<-- what to do here??;
}
[...]
}
Run Code Online (Sandbox Code Playgroud)
因此,当存储库(现在在基础结构中实现)实例化一个新的Customer时,它可以轻松地传入实现ICustomerState的数据库包装器对象.到现在为止还挺好
但是,在域中创建新实体时,不再可能创建内部状态对象,因为我们不再知道它的实际实现.
有几种可能的解决方案,但它们似乎都没有吸引力:
StateFactory.Instance.Create<TState>()方法来创建内部状态对象.然后,基础设施负责为其注册适当的实施.类似的方法是以某种方式获得DI容器并从那里解决工厂.我个人并不喜欢这种服务定位器方法,但在这种特殊情况下可能是可以接受的.有没有更好的选择,我错过了?
小智 1
领域驱动设计并不适合大泥球。尝试在大型系统中应用 DDD 并不像面向对象设计那样有效。尝试从共同协作的对象的角度进行思考,隐藏数据的复杂性,并开始从方法/行为的角度进行思考,以通过行为来操纵对象内部。
\n为了实现洋葱结构,我建议遵循以下规则:
以下是我如何使用对象设计业务规则,以便具有可读性和可维护性:
\n\npublic interface IProductBacklog\n{\n KeyValuePair<bool, int> TryAddProductBacklogItem(string description);\n\n bool ExistProductBacklogItem(string description);\n\n bool ExistProductBacklogItem(int backlogItemId);\n\n bool TryDeleteProductBacklogItem(int backlogItemId);\n}\n\npublic sealed class AddProductBacklogItemBusinessRule\n{\n private readonly IProductBacklog productBacklog;\n\n public AddProductBacklogItemBusinessRule(IProductBacklog productBacklog)\n {\n this.productBacklog = productBacklog ?? throw new ArgumentNullException(nameof(productBacklog));\n }\n\n public int Execute(string productBacklogItemDescription)\n {\n if (productBacklog.ExistProductBacklogItem(productBacklogItemDescription))\n throw new InvalidOperationException("Duplicate");\n KeyValuePair<bool, int> result = productBacklog.TryAddProductBacklogItem(productBacklogItemDescription);\n if (!result.Key)\n throw new InvalidOperationException("Error adding productBacklogItem");\n return result.Value;\n }\n}\n\npublic sealed class DeleteProductBacklogItemBusinessRule\n{\n private readonly IProductBacklog productBacklog;\n\n public DeleteProductBacklogItemBusinessRule(IProductBacklog productBacklog)\n {\n this.productBacklog = productBacklog ?? throw new ArgumentNullException(nameof(productBacklog));\n }\n\n public void Execute(int productBacklogItemId)\n {\n if (productBacklog.ExistProductBacklogItem(productBacklogItemId))\n throw new InvalidOperationException("Not exists");\n if(!productBacklog.TryDeleteProductBacklogItem(productBacklogItemId))\n throw new InvalidOperationException("Error deleting productBacklogItem");\n }\n}\n\npublic sealed class SqlProductBacklog : IProductBacklog\n{\n //High performance, not loading unnesesary data\n public bool ExistProductBacklogItem(string description)\n {\n //Sql implementation\n throw new NotImplementedException();\n }\n\n public bool ExistProductBacklogItem(int backlogItemId)\n {\n //Sql implementation\n throw new NotImplementedException();\n }\n\n public KeyValuePair<bool, int> TryAddProductBacklogItem(string description)\n {\n //Sql implementation\n throw new NotImplementedException();\n }\n\n public bool TryDeleteProductBacklogItem(int backlogItemId)\n {\n //Sql implementation\n throw new NotImplementedException();\n }\n}\n\npublic sealed class EntityFrameworkProductBacklog : IProductBacklog\n{\n //Use EF here\n public bool ExistProductBacklogItem(string description)\n {\n //EF implementation\n throw new NotImplementedException();\n }\n\n public bool ExistProductBacklogItem(int backlogItemId)\n {\n //EF implementation\n throw new NotImplementedException();\n }\n\n public KeyValuePair<bool, int> TryAddProductBacklogItem(string description)\n {\n //EF implementation\n throw new NotImplementedException();\n }\n\n public bool TryDeleteProductBacklogItem(int backlogItemId)\n {\n //EF implementation\n throw new NotImplementedException();\n }\n}\n\npublic class ControllerClientCode\n{\n private readonly IProductBacklog productBacklog;\n\n //Inject from Services, IoC, etc to unit test\n public ControllerClientCode(IProductBacklog productBacklog)\n {\n this.productBacklog = productBacklog;\n }\n\n public void AddProductBacklogItem(string description)\n {\n var businessRule = new AddProductBacklogItemBusinessRule(productBacklog);\n var generatedId = businessRule.Execute(description);\n //Do something with the generated backlog item id\n }\n\n public void DeletePRoductBacklogItem(int productBacklogId)\n {\n var businessRule = new DeleteProductBacklogItemBusinessRule(productBacklog);\n businessRule.Execute(productBacklogId);\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n