Questions tagged «domain-driven-design»

域驱动设计(DDD)是一种通过将实现与不断发展的模型连接来开发满足复杂需求的软件的方法。

6
实体方法调用上的DDD注入服务
问题的简短格式 在DDD和OOP的最佳实践中,是否可以在实体方法调用上注入服务? 长格式示例 假设我们在DDD中有一个经典的Order-LineItems案例,其中有一个称为Order的域实体,它也充当聚合根,并且该实体不仅由其Value Objects组成,而且还包含Line Item的集合实体。 假设我们希望在应用程序中使用流利的语法,以便我们可以做类似的事情(请注意第2行中的语法,在此称为getLineItems方法): $order = $orderService->getOrderByID($orderID); foreach($order->getLineItems($orderService) as $lineItem) { ... } 我们不想将任何LineItemRepository注入OrderEntity,因为这违反了我能想到的几个原则。但是,语法的流畅性是我们真正想要的,因为它易于阅读和维护以及测试。 考虑下面的代码,指出该方法getLineItems中OrderEntity: interface IOrderService { public function getOrderByID($orderID) : OrderEntity; public function getLineItems(OrderEntity $orderEntity) : LineItemCollection; } class OrderService implements IOrderService { private $orderRepository; private $lineItemRepository; public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) { $this->orderRepository …

5
如何对待汇总之间引用的验证?
我在汇总之间的引用方面有些挣扎。让我们假设该聚合Car引用了该聚合Driver。此参考将通过具有进行建模Car.driverId。 现在我的问题是,我应该走多远的时间才能验证在中创建Car集合CarFactory。我应该相信传递的对象DriverId是现有对象, Driver还是应该检查该不变量? 对于检查,我看到两种可能性: 我可以更改汽车制造厂的签名以接受完整的驾驶员实体。然后,工厂将仅从该实体中选择ID,并以此为单位构建汽车。这里不变量被隐式检查。 我可以DriverRepository在CarFactory和显式调用中引用driverRepository.exists(driverId)。 但是现在我想知道是否有太多的不变性检查?我可以想象那些聚合可能生活在单独的有界上下文中,现在我将依赖于DriverBC的DriverRepository或Driver实体污染汽车BC。 而且,如果我与领域专家交谈,他们将永远不会质疑此类引用的有效性。我感觉到我的领域模型受到无关的关注。但是话又说回来,在某个时候,用户输入应该得到验证。

3
DDD-贫血域模型是反模式吗?我们应该使用富域模型吗?[关闭]
已关闭。这个问题是基于观点的。它当前不接受答案。 想改善这个问题吗?更新问题,以便通过编辑此帖子以事实和引用的形式回答。 2年前关闭。 Enes 和Fowler很久以前就批评了贫血症领域模型,因为它显然违反了面向对象的原理等。DDD社区显然与此声明保持一致。 但是,近年来,一直有不同意见的人声称它根本不是反模式,并且它是遵循SOLID原则的一个示例。 我已经使用Spring Framework工作了很多年。每个公司中的每个项目都始终使用服务于贫乏模型(JPA实体)的存储库,其中包含业务逻辑的服务层。此外,大多数样本,甚至包括Spring员工的官方样本,都展示了这种工作方式。 我的问题是:贫血领域模型是否仍被视为反模式?我们所有人都在做关于DDD的事情吗?您是否不认为拥有Rich Domain模型违反了SOLID原则?

2
如何设计总体边界?
我想编写一个类似电子商务的应用程序。 您知道,在类似的应用程序中,产品可能具有不同的属性和功能。为了模拟这种机会,我创建了以下域模型实体: 类别 -这类似于“电子产品>计算机”,即产品类型。分类包含属性列表(List <属性>)。 属性 -包含名称,度量单位,数据类型的独立实体。例如“名称”,“重量”,“屏幕尺寸”。同一属性可以具有不同的产品。 产品 -仅包含名称和与属性有关的值列表。值是一个仅包含值字段和属性的字段ID的对象。 我最初决定在此方案中使Category像单个聚合一样,因为例如当我添加新产品时,我需要知道与当前类别有关的所有数据,包括与当前类别有关的属性(category.AddNewProduct(product))。但是,当我只需要添加不属于任何类别的新属性时该怎么办。例如,我不能执行此category.AddNewProperty(property),因为它明确表示我们将属性添加到特定类别。 好的,下一步我决定将Property划分为单独的聚合,但是它将是一个包含简单实体的列表。 当然,我可以创建诸如PropertyAggregate之类的内容以保留在属性和业务规则列表中,但是当我添加产品时,我需要在类别内具有属于该类别的属性的整个列表,以检查不变量。但是我也意识到,将链接内的链接保留在其他聚合上是一种不好的做法。 设计此业务案例有哪些选择?

2
如何在CQRS中创建新的聚合根?
我们应该如何在cqrs体系结构中创建新的聚合根?在此示例中,我想创建新的聚合根AR2,其中包含对第一个AR1的引用。 我正在使用AR1方法作为起点来创建AR2。到目前为止,我看到的选择很少: 在AR1的内部方法中,createAr2RootOpt1我可以new AR2()使用具有访问存储库权限的域服务立即将该对象调用并将其保存到db。 我可以在第一个聚合根发出事件,例如。SholdCreateAR2Event然后有无状态的传奇对此做出反应并发出命令CreateAR2Command,该命令随后被处理并实际创建AR2并发出AR2CreatedEvent。如果使用事件源SholdCreateAR2Event,则不会将其保留在事件存储中,因为它不会影响第一个聚合根的状态。(或者我们还是应该将其保存在事件存储中?) class AR1{ Integer id; DomainService ds; //OPTION 1 void createAr2RootOpt1(){ AR2 ar2 = new AR2(); ds.saveToRepo(ar2); } //OPTION 2 void createAr2RootOpt2(){ publishEvent(new SholdCreateAR2Event()); //we don't need this event. Shoud it still be preserved in event store? } } class AR2{ Integer id; Integer ar1Id; void …

3
事件源,一个事件,两个聚合的状态已更改
我正在尝试学习DDD和相关主题的方法。我想到了一个简单的有界上下文来实现“银行”的想法:有帐户,可以在它们之间存入,提取和转移资金。保留更改历史也很重要。 我确定了Account实体,并且事件来源可以很好地跟踪其中的更改。其他实体或值对象与该问题无关,因此我不会提及它们。 考虑存款和取款时-这相对简单,因为只修改了一个汇总。 转移时有所不同-必须通过一个MoneyTransferred事件来修改两个聚合。DDD不赞成在一个事务中修改多个聚合。另一方面,事件源的规则是将事件应用于实体并基于它们修改状态。如果事件可以简单地存储在数据库中,那就没有问题。但是,为了防止同时修改事件源实体,我们必须对每个聚合的事件流实施某种版本控制(以保持其事务界限)。版本控制带来了另一个问题-我无法使用简单的结构来存储事件并读回它们以将其应用于聚合。 我的问题是-如何将这三个原则结合在一起:“一个交易合计一笔交易”,“事件->总交易变更”和“防止并发修改”?

2
有没有一种优雅的方法来检查域对象属性上的唯一约束,而又不将业务逻辑移入服务层?
我已经适应领域驱动设计大约8年了,即使经过了这些年,仍然有一件事困扰着我。那就是针对域对象检查数据存储中的唯一记录。 2013年9月,马丁·福勒(Martin Fowler)提到了TellDon'tAsk原则,如果可能的话,应将其应用于所有领域对象,然后应返回一条消息,说明操作如何进行(在面向对象的设计中,这通常是通过异常来完成的,操作失败)。 我的项目通常分为许多部分,其中两个部分是“域”(包含业务规则,没有其他内容,该域完全不考虑持久性)和“服务”。知道用于CRUD数据的存储库层的服务。 由于属于对象的属性的唯一性是域/业务规则,因此对于域模块来说应该很长,因此该规则正是它应该在的位置。 为了能够检查记录的唯一性,您需要查询当前数据集(通常是数据库),以查明是否存在另一个假设的记录Name。 考虑到域层对于持久性是无知的,并且不知道如何检索数据,而只知道如何对它们进行操作,因此它无法真正地访问存储库本身。 我一直在适应的设计如下所示: class ProductRepository { // throws Repository.RecordNotFoundException public Product GetBySKU(string sku); } class ProductCrudService { private ProductRepository pr; public ProductCrudService(ProductRepository repository) { pr = repository; } public void SaveProduct(Domain.Product product) { try { pr.GetBySKU(product.SKU); throw Service.ProductWithSKUAlreadyExistsException("msg"); } catch (Repository.RecordNotFoundException e) { // suppress/log …

4
查找表:它们是否在域模型中泄漏?
您正在建立一个跟踪公司的系统。这些公司有联系人。这些联系人通常都是专家,只回答某些类型的问题,例如帐单/付款,销售,订购和客户支持。 使用域驱动设计和洋葱架构,我使用以下类型对它进行了建模: 公司 有联络人 联系 有联系方式 ContactType(枚举) CompanyRepository(接口) EFCompanyRepository(在外部程序集中定义,使用EntityFramework,实现CompanyRepository) 对于如何为该应用程序建模数据库,我们的团队意见分歧。 A面:精益DDDers: 定义哪些ContactTypes对一个联系人有效是Domain的工作。向数据库中添加表以验证是否未保存未知的ContactType是域泄漏的迹象。它将逻辑传播得太远了。 将静态表添加到数据库和相应的代码是浪费的。在此应用程序中,数据库解决了一个问题:保留事物并将其还给我。编写一个额外的表和相应的CRUD代码很浪费。 更改持久性策略应尽可能容易。更改业务规则的可能性更大。如果我决定SQL Server花费太多,则不需要重新构建放在架构中的所有验证。 B面:传统主义者[这可能不是一个好名字。DBCentrists?]: 在数据库中存储没有读取代码就没有意义的数据是一个坏主意。报表和其他使用者必须自己重复值列表。 按需加载数据库类型字典的代码并不多。不用担心 如果更改的源是代码而不是数据,那么当更改时,我将不得不部署位而不是简单的SQL脚本。 哪一方都不对或错,但从长远来看,其中一方可能会更有效率,计算初始开发,错误等的开发时间。这是哪一方-还是有更好的折衷办法?其他编写这种样式的代码的团队做什么?

2
我应该使用域对象中的存储库还是将域对象推回到服务层?
我来自交易脚本世界,现在开始研究DDD。我不确定将DDD设计与数据库持久性集成在一起的正确方法。这就是我所拥有的: 一个名为OrganisationService的服务类,其接口包含用于检索和保存Organization域对象实例的方法。组织是一个聚合根,并具有与之相关的其他数据:成员和许可证。在OrganisationService中使用EF6数据库优先DBContext来检索OrganisationDB实体以及相关的MemberDB和LicenseDB实体。当由OrganisationService检索并加载到Organization域对象中时,所有这些都转换为与其等效的域对象类。该对象看起来像这样: public class Organisation { public IList<Member> Members { get; set; } public IList<License> Licenses { get; set; } } 我没有在OrganisationService中使用存储库模式...我将EF本身用作存储库,因为现在EF6似乎已使存储库成为多余。 在设计的这一点上,Organization域对象是贫乏的:它看起来像EF POCO Organization类。OrganisationService类看起来很像存储库! 现在,我需要开始添加逻辑。该逻辑包括管理组织的许可证和成员。现在,在交易脚本时代,我将向OrganisationService添加方法来处理这些操作,并调用存储库以与DB进行交互,但是对于DDD,我相信此逻辑应封装在Organization域对象本身内... 这是我不确定应该做什么的地方:作为逻辑的一部分,我需要将这些数据持久化回数据库。这是否意味着我应该在Organization域对象中使用DbContext来做到这一点?在域对象内使用存储库/ EF是否不好?如果是这样,那么这种持久性在哪里? public class Organisation { public IList<Member> Members { get; set; } public IList<License> Licenses { get; set; } public void AddLicensesToOrganisation(IList<License> licensesToAdd) …

2
当我们将计算与副作用分开时,我们将“询问世界”的代码放在哪里?
根据命令查询分离原则,以及使用Clojure演示的“ 数据中的思考”和“ DDD”,应将副作用(修改世界)与计算和决策分开,以便更容易理解和测试这两个部分。 这就留下了一个悬而未决的问题:我们应该把“问世界”放在边界的哪个位置?一方面,从外部系统(例如数据库,扩展服务的API等)请求数据不是参照透明的,因此不应与纯计算和决策代码放在一起。另一方面,将它们从计算部分中分离出来并作为参数传递是有问题的,甚至是不可能的,因为因为我们可能事先不知道可能需要请求哪些数据。

2
在哪里验证依赖于数据库内容的域模型规则?
我正在使用允许管理员定义包含字段的表单的系统。然后,使用定义的表格将数据输入系统。有时,表单是由用户通过GUI填写的,有时,表单是根据另一个系统报告的值来填写的。 对于每个字段,管理员可以定义一个验证规则,以限制该字段的允许值。验证规则可以是“在字段中输入的值必须为True或False”到“在字段中输入的值必须存在于数据库表B的A列中”的任何内容。管理员可以随时更改字段的验证规则。 在这种情况下,您认为最适合验证每个字段正确填写的位置是什么?我目前有两种主要方法: 选项1:在域模型中验证 每个字段对象将包含管理员指定的验证规则。Field对象还将引用IValidator。尝试设置字段的值时,字段会将给定的值和验证规则传递给IValidator。如果给定的值无效,则将在另一个系统的GUI /接口中引发ValidationException并进行适当处理。 优点: 强大的保护功能,防止字段被意外分配违反验证规则的值 缺点: 数据访问层需要能够绕过验证并构造违反当前验证规则的字段。尽管管理员更改了字段的验证规则,但我们仍然需要能够基于旧数据构造Field对象,例如,当渲染多年前填充的表单时。每当我们存储字段时,都可以通过存储当前的验证规则来解决此问题。 在这种设计中,Field模型通过IValidator间接链接到数据访问层/存储库。服务/存储库的领域模型的注入似乎被普遍令人难以接受的。 选项2:在服务中验证 尝试确保所有尝试设置字段值的尝试都通过可确保验证规则成立的服务。如果违反了验证规则,则抛出ValidationException。 当然,当创建以前一直保存在数据库中的字段对象时,数据访问层将不会使用服务。 优点: 不违反“不要将服务/存储库注入您的域模型”的思想。 保留字段时,无需保留当前的验证规则。该服务可以简单地查询该字段的当前验证规则;查看历史记录数据时,字段的值不会更改。 缺点: 不能保证应该使用服务设置字段值的所有逻辑实际上都这样做。我认为这是一个主要缺点。似乎要做的就是有人写“ thisField.setValue(thatField.getValue())”,并且可能会违反thisField的验证规则,而没有人明智。当数据访问层将要保留字段时,可以通过确保字段的值与验证规则匹配来缓解这种情况。 我目前更喜欢选项#1,而不是选项#2,主要是因为我将其视为业务逻辑,并认为选项2带来了将不良数据引入系统的更大风险。您更喜欢哪个选项,或者是否有比上述两个选项更适合这种情况的设计? 编辑(验证的复杂性) 目前出现的验证案例相对简单。字段值必须是例如数字,日期,带时间的日期,或者是数据库列中的现有值。但是,我怀疑复杂性会随着时间逐渐增加。例如,验证解决方案的构建必须考虑国际化-诸如Date之类的内容可能会以特定于语言环境的语法输入。 我现在决定继续使用选项#1,尝试注意不要为域模型分配太多职责。那些面临类似情况的人可能还想查看相关问题分层体系结构中的验证和授权以及数据输入验证-在哪里?多少?。

3
带有ORM的DDD业务逻辑应该去哪里?
我过去使用过MDA(模型驱动的体系结构)工具,我们通过UML进行建模,这会生成业务实体(我们的域模型)和ORM(映射等)。 该域上的许多业务代码和服务都是该模型的一部分,并且我们的存储库正在返回业务实体(因此,不可能切换到另一个ORM(不是我们想要的))。 但是,现在我正在开始一个项目,我想从DDD角度进行思考。 到目前为止,感觉好像我将业务逻辑放入域模型中,并通过存储库与ORM(无论选择哪种)一起使用。但是,如果我想继续在应用程序的ORM部分中使用MDA工具,则此处创建的模型将非常贫乏(即不包含任何业务逻辑)。同样,如果我将实体框架(.net)或NHibernate用于我的ORM,它也将是贫血模型。我不确定如果我刚刚使用NHibernate,您会将业务逻辑放在哪里。 我是否以这种方式正确思考,换句话说,使用DDD使用域中的所有业务逻辑,而仅使用ORM通过存储库进行持久化?

2
命令处理程序和DDD
我有一个ASP.NET MVC应用程序,该应用程序使用查询服务来获取数据,并使用命令服务来发送命令。我的问题是关于命令部分的。 如果有请求进入,则命令服务将使用命令调度程序,该命令调度程序会将命令路由到其指定的命令处理程序。该命令处理程序首先验证命令,如果一切都可以接受,它将执行命令。 具体示例:AddCommentToArticleCommandHandler接收一个AddCommentToArticleCommand,该命令具有ArticleId,CommentText和EmailAddress。 第一; 必须进行验证,例如:-检查文章是否存在-检查文章是否未关闭-检查注释文本是否在20到500个字符之间,以及是否填写了电子邮件地址-检查电子邮件地址是否具有正确的格式。 我想知道将验证放在哪里? 1 /在命令处理程序本身中。但是,它不能在其他命令处理程序中重用。 2 /在域实体中。但是,由于域实体不了解存储库或服务,因此它无法执行所需的验证(无法检查文章是否存在)。但是另一方面,如果实体不包含逻辑,则它将变成一个简单的数据容器,不遵循DDD原理。 3 /命令处理程序使用验证器,以便可以在其他命令处理程序中重用验证。 4 /其他机制? 我正在寻找此特定示例的责任链,以及哪些对象(实体,存储库,...)在其中起作用。 从命令处理程序到存储库,您是否有实现此想法的想法?

4
领域驱动设计中的重构
已关闭。这个问题需要细节或说明。它当前不接受答案。 想改善这个问题吗?添加细节并通过编辑此帖子来澄清问题。 6年前关闭。 我刚刚开始从事一个项目,我们正在使用领域驱动的设计(由Eric Evans在“ 领域驱动的设计:解决软件核心中的复杂性”中定义。我相信我们的项目肯定是该设计的候选人就像埃文斯在书中描述的那样。 我在不断重构的想法中挣扎。 我知道在任何项目中重构都是必不可少的,随着软件的改变,重构将不可避免地发生。但是,根据我的经验,重构是在开发团队的需求发生变化时发生的,而不是对领域变化的理解(Evans称之为“重构以获得更大的洞察力”)。我最关心的是对领域模型的理解方面的突破。我知道进行小的更改,但是如果需要对模型进行较大的更改怎么办? 在说服更清晰的域模型之后,应该说服自己(和其他人)重构的有效方法是什么?毕竟,为了改善代码的组织或性能而进行的重构可能与无处不在的语言代码的表达方式完全不同。有时似乎没有足够的时间进行重构。 幸运的是,SCRUM使它可以进行重构。SCRUM的迭代性质使其易于构建一小块并进行更改。但是随着时间的流逝,该片段会变大,如果您在该片段太大而又难以更改时又取得突破,该怎么办? 是否有人从事采用领域驱动设计的项目?如果是这样,那么对此有所了解将是很棒的。我特别想听听一些成功的故事,因为DDD似乎很难解决。 谢谢!

4
域驱动设计和跨域交互
我是DDD的相对新手,但是我正在阅读任何东西,只要我能动手实践并提炼出我的知识即可。 我遇到了这个DDD问题,答案之一吸引了我。 DDD有界的上下文和域? 在一个答案中,张贴者给出了一个电子商务系统的示例,其中产品至少在两个域中: 1)产品目录2)库存管理 好的,这一切都有道理,即在您的电子商务前端中,您对显示产品信息感兴趣,而对库存管理不感兴趣。 但。您可能要在网页上显示库存水平,或者要显示库存的版本号(假设库存是书籍,杂志等)。此信息来自库存域。 那么,您将如何处理呢?你会 a)加载产品域和库存域聚合?b)您是否会在“产品”域实体上保留一些属性,以用于库存数量和库存版本,然后在“库存”实体更新时使用“域事件”来更新这些属性? 最后一个问题。我知道我们注定要忘记/忽略域的持久性,而只需考虑域。但是,请仔细考虑一下,在上面的示例中,我们最终可能会有2个DB表用于产品目录和产品库存。现在,我们在这些产品中使用相同的标识符吗?或者,我们可以对数据使用1个表行还是1个表行,并简单地将相关数据映射到聚合属性上?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.