在分层体系结构中为存储库定义接口的位置?

时间:2012-12-12 18:25:51

标签: interface domain-driven-design layer n-tier-architecture ddd-repositories

背景

我正在尝试创建一个简单的应用程序来真正理解整个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个项目:

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

  2. SimpleOrder.ApplicationFacade(应用服务) - 在域中编排较低级别方法的较高级别,粗粒度方法

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

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

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

  6. 我认为这是我的选择:

    选项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,但是我的嘴里却留下了不好的味道。有更好的选择吗?我在这里走在正确的轨道上吗?

2 个答案:

答案 0 :(得分:10)

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

要解决选项4的con,您不应该将控制台应用视为仅仅是表示层。它还有其他职责,例如作为应用程序的主机和DI中的composition root。可能存在控制台应用程序的表示组件,其仅与应用程序服务通信。您还可以将应用程序服务封装在使用ASP.NET WebAPI实现的open host service之后。然后,表示层将仅引用此服务并从底层域层隐藏。

答案 1 :(得分:0)

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

您应该查看Onion Architecture,这是您在选项4中组织依赖项的一种示例。我个人认为这是可用的最具扩展性的体系结构模型。如果您将此架构与DDD实践相结合,那么您将以最少的努力获得最大的灵活性。

另外,我看到的许多实现都将存储库和域服务的合同分解为自己的项目。这纯粹是一个组织决定。将它们放在您的域模型项目中是完全有效的。