Design patterns DDD-域模型、服务和存储库之间的依赖关系

Design patterns DDD-域模型、服务和存储库之间的依赖关系,design-patterns,architecture,domain-driven-design,Design Patterns,Architecture,Domain Driven Design,只是想知道其他人是如何分层他们的架构的。假设我的图层如下所示: 域层 --产品 --ProductService(imp是否应该进入该层? --IPProductService --IPProductRepository 基础结构层 --ProductRepository(我的域中IPProductRepository的Imp) 现在,当创建新产品时,我需要通过调用ProductService.GetNextProductId()方法来分配产品id 因为服务依赖于存储库,所以我使用IPProdu

只是想知道其他人是如何分层他们的架构的。假设我的图层如下所示:

域层
--产品
--ProductService(imp是否应该进入该层?
--IPProductService
--IPProductRepository

基础结构层
--ProductRepository(我的域中IPProductRepository的Imp)

现在,当创建新产品时,我需要通过调用ProductService.GetNextProductId()方法来分配产品id

因为服务依赖于存储库,所以我使用IPProductRepository接口设置了ProductService ctor,该接口可以在以后注入。大概是这样的:

    public class ProductService : IProductService
    {
        private IProductRepository _repository;

        public ProductService(IProductRepository repository)
        {
            _repository = repository;
        }

        public long GetNextProductId()
        {
            return _repository.GetNextProductId();
        }
    }
我的问题是,当我在产品类中使用服务时,我在实例化一个新的产品服务类时引用了ctor中的存储库。在DDD中,有这样的参考是一个很大的禁忌。我甚至不确定是否正确设置了我的产品域类以调用该服务,请有人建议:

public class Product : Entity
    {
        private ProductService _svc;
        private IProductRepository _repository;

        public Product(string name, Address address) //It doesnt seem right to put parm for IProductRepository in the ctor?
            : base(_svc.GetNextProductId) // This is where i pass the id
        {
            // where to create an instance of IProductRepository?
        }
    }
如何优雅地解决这个设计问题?我愿意听取经验丰富的DDD'ER的建议

编辑:

谢谢你的评论。我还怀疑是否应该从产品类调用服务。我还没有使用工厂模式,因为对象的构造仍然很简单。我觉得它还不能作为一种工厂方法

我很困惑……如果我的产品类需要来自服务的一些其他数据,比如GetSystemDateTime()(我知道,这是一个糟糕的示例,但试图演示一个非db调用),那么将ProductId放在一边,这个服务方法将在何处调用


DDD中的服务是逻辑转储,其中的逻辑不是域对象的自然逻辑,对吗?那么它是如何粘合在一起的呢?

您的域模型不应该引用ProductService或IPProductRepository。如果您创建一个新产品,它必须通过工厂创建-工厂可以使用ProductService获取产品id


事实上,我会使用适当的接口包装ProductService,例如IPProductIDGeneratorService,这样您就可以使用IoC容器将其注入工厂。

如果我正确理解您的问题,您可以声明您的产品类正在调用ProductService类。不应该。您应该在负责创建和配置产品的factory类中执行此操作。调用此方法的位置也可能取决于何时发布ProductId:我们可能会遇到类似的情况,即我们需要从遗留会计系统中获取项目的编号。我推迟获取数字,直到项目被持久化,这样我们就不会浪费任何数字或有差距。如果您处于类似的情况,您可能希望在存储库保存方法中而不是在创建对象时发出ProductId

顺便问一下,您真的认为您将拥有不止一个ProductService或ProductRepository吗?如果不是的话,我就不会为接口费心了

编辑以添加:


我建议从小事做起,从两个简单的类开始,产品和产品服务。ProductServices将执行所有服务,包括factory和repository,因为您可以将它们视为专门服务。

最后一点,DDD中的服务是一个放置我所说的“笨拙”逻辑的地方。如果您有某种类型的逻辑或工作流依赖于其他实体,那么这种逻辑通常不“适合”域对象本身。示例:如果我的业务对象上有一个方法来执行某种类型的验证,那么服务类可能会执行此方法(仍然将与实体相关的实际验证逻辑保留在其类中)

我经常提到的另一个非常好的例子是资金转移方法。您不需要将account对象从一个对象传输到另一个对象,而是需要一个接受“to”帐户和“from”帐户的服务。然后在服务中,您将调用“从”帐户上的取款方法和“到”帐户上的存款方法。如果您试图将其放入帐户实体本身,则会感到尴尬


一个伟大的播客,深入谈论这个话题可以找到。David Laribee现在很好地解释了DDD的“如何”和“为什么”。

为什么在内存中创建产品时需要产品id?通常,在存储库中创建产品时会设置产品id

请看下面的代码:

var id1=_repository.GetNextProductId(); var id2=_repository.GetNextProductId()

它会返回两个不同的产品ID吗

如果答案是肯定的,那么它是安全的(但仍然很尴尬);
如果答案是否定的,那么你将有一个巨大的问题

与Marcelo一致,您应该首先确定产品ID是否真的是一个域模型概念。如果业务用户从不使用或不知道产品ID,通常通过名称、编号、SKU或由名称+维度组成的自然产品ID来引用产品,那么域模型应该知道这一点

也就是说,假设产品ID是数据库中的一个自动编号字段,下面是我构建DDD项目的方式:

项目业务(领域模型)

没有引用,因此不依赖任何内容

public class Product : Entity
{
    private Product(string name, Address address)
    {
        //set values.
    }

    //Factory method, even for simple ctor is used for encapsulation so we don't have 
    //to publically expose the constructor.  What if we needed more than just a couple           
    //of value objects?
    public static CreateNewProduct(string name, Address address) 
    {
        return new Product(name, address);
    }

    public static GetAddress(string address, string city, string state, string zip) { }
}

public interface IProductRepository : IEnumerable<Product>
{
    void Add(Product product);
    //The following methods are extraneous, but included for completion sake.
    int IndexOf(Product product);
    Product this[int index] { get; set; }
}
如有必要,您可以:


Project.Services(在自身和表示层之间使用DTO的应用程序服务层)

以下是我将如何组织您的问题。我相信这也是DDD推荐的方法

public class ProductService : IProductService // Application Service class, used by outside components like UI, WCF, HTTP Services, other Bounded Contexts, etc.
{
    private readonly IProductRepository _prodRepository;
    private readonly IStoreRepository _storeRepository;

    public ProductService(IProductRepository prodRepository, IStoreRepository storeRepository) // Injected dependencies DI
    {
        if(prodRepository == null) throw new NullArgumentException("Prod Repo is required."); // guard
        if(storeRepository == null) throw new NullArgumentException("Store Repo is required."); // guard

        _prodRepository = prodRepository;
        _storeRepository = storeRepository;
    }

    public void AddProductToStore(string name, Address address, StoreId storeId) //An exposed API method related to Product that is a part of your Application Service. Address and StoreId are value objects.
    {
        Store store = _storeRepository.GetBy(storeId);
        IProductIdGenerator productIdGenerator = new ProductIdGenerator(_prodRepository);
        Product product = Product.MakeNew(name, address, productIdGenerator);
    }

    ... // Rest of API
}

public class Product : Entity
{
    public static MakeNew(string name, Address address, IProductIdGenerator productIdGenerator) // Factory to make construction behaviour more explicit
    {
        return new Product(name, address, productIdGenerator);
    }

    protected Product(string name, Address address, IProductIdGenerator productIdGenerator)
        : base(productIdGenerator.GetNextProductId())
    {
        Name = name;
        Address = address;
    }

    ... // Rest of Product methods, properties and fields
}

public class ProductIdGenerator : IProductIdGenerator
{
    private IProductRepository _repository;

    public ProductIdGenerator(IProductRepository repository)
    {
        _repository = repository;
    }

    public long GetNextProductId()
    {
        return _repository.GetNextProductId();
    }
}

public interface IProductIdGenerator
{
    long GetNextProductId();
}
基本上,ProductService是应用程序服务的一部分,即需要使用域或跨越域边界的所有内容的入口和出口点。它负责将每个用例委托给适当的人员
public class Application
{
    IProductRepository repository = new SqlProductRepository(SqlConnectionString);

    protected void Save_Click(object sender, EventArgs e)
    {
        Product newProduct = Product.CreateNewProduct(name, Product.GetAddress(address,city,state,zip));
        repository.Add(newProduct);
    }
}    
public class ProductService : IProductService // Application Service class, used by outside components like UI, WCF, HTTP Services, other Bounded Contexts, etc.
{
    private readonly IProductRepository _prodRepository;
    private readonly IStoreRepository _storeRepository;

    public ProductService(IProductRepository prodRepository, IStoreRepository storeRepository) // Injected dependencies DI
    {
        if(prodRepository == null) throw new NullArgumentException("Prod Repo is required."); // guard
        if(storeRepository == null) throw new NullArgumentException("Store Repo is required."); // guard

        _prodRepository = prodRepository;
        _storeRepository = storeRepository;
    }

    public void AddProductToStore(string name, Address address, StoreId storeId) //An exposed API method related to Product that is a part of your Application Service. Address and StoreId are value objects.
    {
        Store store = _storeRepository.GetBy(storeId);
        IProductIdGenerator productIdGenerator = new ProductIdGenerator(_prodRepository);
        Product product = Product.MakeNew(name, address, productIdGenerator);
    }

    ... // Rest of API
}

public class Product : Entity
{
    public static MakeNew(string name, Address address, IProductIdGenerator productIdGenerator) // Factory to make construction behaviour more explicit
    {
        return new Product(name, address, productIdGenerator);
    }

    protected Product(string name, Address address, IProductIdGenerator productIdGenerator)
        : base(productIdGenerator.GetNextProductId())
    {
        Name = name;
        Address = address;
    }

    ... // Rest of Product methods, properties and fields
}

public class ProductIdGenerator : IProductIdGenerator
{
    private IProductRepository _repository;

    public ProductIdGenerator(IProductRepository repository)
    {
        _repository = repository;
    }

    public long GetNextProductId()
    {
        return _repository.GetNextProductId();
    }
}

public interface IProductIdGenerator
{
    long GetNextProductId();
}