背景
我正在尝试创建一个简单的应用程序来真正理解整个DDD + TDD +等堆栈。我的目标是在运行时动态注入DAL存储库类。这让我的 域和应用程序服务层可测试。我打算用“穷人的DI”来完成 现在这个...所以我会在启动附近的一个简单的控制台应用程序中执行此操作:
// Poor man's DI, injecting DAL repository classes at runtime var productRepository = new SimpleOrder.Repository.ProductRespository(); var customerRepository = new SimpleOrder.Repository.CustomerRepository(); var orderRepository = new SimpleOrder.Repository.OrderRepository(); // Constructor injection into this class in the Application Services layer, // SimpleOrder.ApplicationFacade OrderEntry oe = new OrderEntry(customerRepository, orderRepository, productRepository);
为了完成这种依赖注入,我创建了三个存储库接口:
-- ICustomerRepository -- IOrderRepository -- IProductRespository
典型的实施:
namespace SimpleOrder.Domain.Interfaces { public interface ICustomerRepository { Customer GetCustomerById(int customerId); void SaveCustomer(Customer customer); } }
**请注意,SaveCustomer引用了域层中定义的Customer模型类。这是其他存储库的典型特征。
但是我不确定应该在哪个项目/层中实施。我在解决方案中有5个项目:
SimpleOrder.ConsoleClient(演示文稿) - 我想从这里注入域的具体实现作为应用程序
SimpleOrder.ApplicationFacade(应用服务) - 在域中编排较低级别方法的较高级别,粗粒度方法
SimpleOrder.Contracts - 用于表示和应用程序服务之间通信的DTO类
SimpleOrder.Domain(domain / bll) - 域模型类Customer,Order,OrderItem,Product
SimpleOrder.Repository(dal) - 实现存储库接口
我认为这是我的选择:
选项1:在SimpleOrder.Contracts中定义存储库接口......
PRO:这是我认为他们应该属于的地方,因为我创建了这个以分享各种关注/层之间的合同。例如,DTO在这里定义。
CON:但是每个接口的方法签名都引用了Domain模型类 这意味着我必须添加对SimpleOrder.Domain的引用,但是当时 SimpleOrder.Contracts在另一个项目中引用,它必须携带 SimpleOrder.Domain一起骑行。这感觉不对。
选项2:与上述情况相同,但我也为每个域模型定义了接口 SimpleOrder.Contracts中的类,因此我可以打破存储库接口与实际模型类的耦合。
示例:
namespace SimpleOrder.Domain.Interfaces { public interface ICustomerRepository { ICustomer** GetCustomerById(int customerId); void SaveCustomer(ICustomer customer); } public interface ICustomer { int CustomerId { get; set; } string Name { get; set; } System.Collections.Generic.List Orders { get; } } }
影响:每个域模型类都必须实现其相关的接口。即,
public class Customer : SimpleOrder.Domain.Interfaces.ICustomer { public Customer() { _orders = new List(); } public int CustomerId { get; set; } public string Name { get; set; } private List _orders; public virtual List Orders { get { return _orders; } } }
PRO:修复了选项1的问题。
CON:这会爆炸项目中的文件数量(以及感知到的复杂性),因为 每个域类现在都有一个关联的接口。
选项3:在SimpleOrder.Domain中定义存储库接口
影响:为了在运行时从SimpleOrder.ConsoleClient将具体的存储库类注入应用程序服务层(SimpleOrder.ApplicationFacade项目),SimpleOder.ConsoleClient还需要对SimpleOrder.Domain的引用。
PRO:此ALSO解决了选项1
CON:我试图避免直接从表示层引用域层,因为现在表示层可以对域层了解太多。当我将来使用WPF或ASP.NET MVC应用程序替换控制台应用程序时,我会冒第二次和后续表示层实现的风险,试图在模型中调用方法而不是Application Services层。 (但我在选项4中考虑过这一点。)
选项4:将接口放在SimpleOrder.Domain中,然后从SimpleOrder.ConsoleClient引用SimpleOrder.Domain。
PRO:解决了上述所有问题。
CON:这感觉不对,因为我将从表示层提供访问权限 当我仅提供时,直接到域层中的低级方法 访问SimpleOrder.ApplicationFacade中的更高级别的chunky方法。
问题 我已经尝试了其中的每一个,但已经确定了选项4,但是我的嘴里却留下了不好的味道。有更好的选择吗?我在这里走在正确的轨道上吗?
答案 0 :(得分:10)
根据我对你的问题的理解,我同意选项4是最好的。应在所有域对象旁边的域层中声明存储库接口。所述接口的实现应该是基础设施层的一部分 - 将您的域层连接到世界的层。看看Hexagonal Architecture,看看其中的一些动机。
要解决选项4的con,您不应该将控制台应用视为仅仅是表示层。它还有其他职责,例如作为应用程序的主机和DI中的composition root。可能存在控制台应用程序的表示组件,其仅与应用程序服务通信。您还可以将应用程序服务封装在使用ASP.NET WebAPI实现的open host service之后。然后,表示层将仅引用此服务并从底层域层隐藏。
答案 1 :(得分:0)
我同意eulerfx。我唯一要补充的是它感觉不对,因为传统的N层架构会让你依赖数据库并使用抽象来打破这些依赖。
您应该查看Onion Architecture,这是您在选项4中组织依赖项的一种示例。我个人认为这是可用的最具扩展性的体系结构模型。如果您将此架构与DDD实践相结合,那么您将以最少的努力获得最大的灵活性。
另外,我看到的许多实现都将存储库和域服务的合同分解为自己的项目。这纯粹是一个组织决定。将它们放在您的域模型项目中是完全有效的。