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

Bob*_*bor 12 domain-driven-design interface ddd-repositories layer n-tier-architecture

背景

我正在尝试创建一个简单的应用程序来真正理解整个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(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在这里定义.

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

选项2:与上面相同的场景,但我也为SimpleOrder.Contracts中的每个Domain模型类定义接口,因此我可以打破存储库接口与实际模型类的耦合.

例:


    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应用程序替换控制台应用程序时,我冒险尝试在模型中调用方法而不是应用程序服务层,从而冒第二次和后续的表示层实现.(但我在选项4中考虑过这一点.)

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

PRO:解决了上述所有问题.

CON:这感觉不对,因为当我提供对SimpleOrder.ApplicationFacade中更高级别的粗略方法的访问时,我将提供从表层直接访问域层中的低级方法的访问权限.

问题 我已尝试了其中的每一个,但已经确定了选项4,但是在我的嘴里却留下了不好的味道.有更好的选择吗?我在这里走在正确的轨道上吗?

eul*_*rfx 10

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

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