19

背景

我正在尝试创建一个简单的应用程序来真正理解 DDD+TDD+etc 的整个堆栈。我的目标是在运行时动态注入 DAL 存储库类。这使我的域和应用程序服务层可测试。我现在计划使用“穷人的 DI”来完成这个......所以我会在启动附近的一个简单的控制台应用程序中做到这一点:

    // 可怜人的 DI,在运行时注入 DAL 存储库类
    var productRepository = new SimpleOrder.Repository.ProductRespository();
    var customerRepository = new SimpleOrder.Repository.CustomerRepository();
    var orderRepository = new SimpleOrder.Repository.OrderRepository();

    // 在应用服务层将构造函数注入到这个类中,
    // SimpleOrder.ApplicationFacade
    OrderEntry oe = new OrderEntry(customerRepository, orderRepository, productRepository);

为了完成这种依赖注入,我创建了三个存储库接口:

-- ICustomerRepository
-- IOrderRepository
-- IProductRespository

一个典型的实现:

    命名空间 SimpleOrder.Domain.Interfaces
    {
        公共接口 ICustomerRepository
        {
            客户 GetCustomerById(int customerId);
            无效 SaveCustomer(客户客户);
        }
    }

** 请注意,SaveCustomer 引用了域层中定义的 Customer 模型类。这是其他存储库的典型特征。

但是我不确定它们应该在哪个项目/层中实现。我在一个解决方案中有 5 个项目:

  1. SimpleOrder.ConsoleClient (presentation) -- 我想从这里注入域的具体实现作为应用程序

  2. SimpleOrder.ApplicationFacade(应用程序服务) ——在域中编排低级方法的大块高级、粗粒度方法

  3. SimpleOrder.Contracts—— 用于表示和应用程序服务之间通信的 DTO 类

  4. SimpleOrder.Domain(domain/bll) ——域模型类Customer、Order、OrderItem、Product

  5. SimpleOrder.Repository (dal) -- 实现存储库接口

这是我看到的选项:

选项 1:在 SimpleOrder.Contracts 中定义存储库接口 ...

PRO:这是我认为他们应该属于的地方,因为我创建它是为了在各个关注点/层之间共享合同。例如,此处定义了 DTO。

缺点:但是每个接口的方法签名都引用了域模型类。
这意味着我必须添加对 SimpleOrder.Domain 的引用,但是当在另一个项目中引用 SimpleOrder.Contracts 时,它必须随身携带 SimpleOrder.Domain。这感觉不对。

选项 2:与上述相同的场景,但我还在 SimpleOrder.Contracts 中为每个域模型类定义接口,以便我可以打破存储库接口与实际模型类的耦合。

例子:

    命名空间 SimpleOrder.Domain.Interfaces
    {
        公共接口 ICustomerRepository
        {
            ICustomer** GetCustomerById(int customerId);
            无效 SaveCustomer(ICustomer 客户);
        }

        公共接口 ICustomer
        {
            int 客户 ID { 获取;放; }
            字符串名称 { 获取;放; }
            System.Collections.Generic.List Orders { get; }
        }
    }

影响:每个领域模型类都必须实现他的相关接口。IE,

    公共类客户:SimpleOrder.Domain.Interfaces.ICustomer
    {
        公共客户()
        {
            _orders = 新列表();
        }

        公共 int CustomerId { 获取;放; }
        公共字符串名称 { 获取;放; }

        私有列表_orders;
        公共虚拟列表订单{
            得到{返回_orders; }
        }
    }

PRO:修复选项 1 的问题。

缺点:这会增加项目中文件的数量(以及感知的复杂性),因为每个域类现在都有一个关联的接口。

选项 3:在 SimpleOrder.Domain 中定义存储库接口

影响:为了在运行时从 SimpleOrder.ConsoleClient 将具体的存储库类注入应用程序服务层(SimpleOrder.ApplicationFacade 项目),SimpleOder.ConsoleClient 还需要对 SimpleOrder.Domain 的引用。

PRO:这也解决了选项 1

CON:我试图避免直接从表示层引用领域层,因为现在表示层可以对领域层了解太多。当我将来用 WPF 或 ASP.NET MVC 应用程序替换控制台应用程序时,我会冒第二个和后续表示层实现尝试调用模型而不是应用程序服务层中的方法的风险。(但是我确实在选项 4 中考虑了这一点。)

选项 4:将接口放在 SimpleOrder.Domain 中,然后从 SimpleOrder.ConsoleClient 引用 SimpleOrder.Domain。

PRO:修复了上述所有问题。

CON:这感觉不对,因为当我应该提供对 SimpleOrder.ApplicationFacade 中更高级别的块状方法的访问时,我将提供从表示层直接访问域层中较低级别方法的访问。

问题 我已经尝试了每一个,但已经选择了选项 4,但是这让我对它产生了不好的印象。有更好的选择吗?我在正确的轨道上吗?

4

2 回答 2

16

根据我对您问题的理解,我同意选项 4 是最好的。存储库接口应在所有域对象旁边的域层中声明。所述接口的实现应该是基础设施层的一部分——将你的领域层连接到世界的层。看看Hexagonal Architecture以了解其中的一些动机。

为了解决选项 4 的缺点,您不应将控制台应用程序视为仅仅是表示层。它还具有其他职责,例如作为应用程序的主机和 DI 术语中的组合根。控制台应用程序可能有一个表示组件,它只与应用程序服务通信。您还可以将应用程序服务封装在使用 ASP.NET WebAPI 实现的开放主机服务后面。然后表示层将仅引用此服务,并对底层域层隐藏。

于 2012-12-12T18:45:07.273 回答
0

我同意eulerfx。我唯一要补充的是它感觉不对,因为传统的 N 层架构会让你让一切都依赖于数据库并使用抽象来打破这些依赖关系。

您应该查看洋葱架构,这是您在选项 4 中组织依赖项的方式的一个示例。我个人认为这是可用的最具可扩展性的架构模型。如果您将此架构与 DDD 实践相结合,您会以最少的工作量获得最大的灵活性。

附带说明一下,我见过的许多实现将存储库和域服务的合同分解为自己的项目。不过,这纯粹是一个组织决定。将它们包含在您的域模型项目中是完全有效的。

于 2012-12-18T18:20:04.780 回答