Questions tagged «domain-driven-design»

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

4
我们应该在哪里验证领域模型
我仍在寻找域模型验证的最佳实践。将验证放入域模型的构造函数中好吗?我的域模型验证示例如下: public class Order { private readonly List<OrderLine> _lineItems; public virtual Customer Customer { get; private set; } public virtual DateTime OrderDate { get; private set; } public virtual decimal OrderTotal { get; private set; } public Order (Customer customer) { if (customer == null) throw new ArgumentException("Customer name must …

7
RESTful API是否倾向于鼓励贫血领域模型?
我正在一个项目中,我们试图将域驱动设计和REST都应用于面向服务的体系结构。我们不必担心100%符合REST。最好说我们正在尝试构建面向资源的HTTP API(Richardson REST成熟度模型的第2级)。但是,我们试图远离RPC样式的HTTP请求的使用,即,我们尝试根据RFC2616实现我们的HTTP动词,而不是使用POSTdo来实现IsPostalAddressValid(...)。 但是,对此的强调似乎是以我们尝试应用域驱动设计为代价的。只有GET,POST,PUT,DELETE和其他一些很少使用的方法,我们倾向于建立眉头服务和眉头服务往往有贫血的域模型。 POST:接收数据,对其进行验证,然后将其转储到数据中。GET:检索数据,然后将其返回。那里没有真正的业务逻辑。我们还在服务之间使用消息(事件),在我看来,大多数业务逻辑最终都围绕该消息构建。 REST和DDD是否处于某种程度的紧张状态?(或者我在这里误解了什么?我们是否可能在做其他错误?)是否有可能在面向服务的体系结构中构建强大的域模型,同时避免RPC样式的HTTP调用?

1
为什么数据库作为队列那么糟糕?[关闭]
我刚刚读了这篇文章,我很困惑。 假设有1个webapp和1个不同的应用程序充当“工作者”,它们共享同一个数据库。 哦,我说“分享” ..但是这篇文章警告了什么?: 第四,在应用程序(或服务)之间共享数据库是一件坏事。在其中放置无定形共享状态太诱人了,在您不知道它的情况下,您将拥有一个巨大的耦合怪物。 =>不同意。在某些情况下,不同的应用程序仍然是同一单元的一部分,因此,在这种情况下,“耦合问题”的概念毫无意义。 让我们继续:Web应用程序处理客户端HTTP请求,并可能随时更新某些聚合(DDD术语),从而生成相应的域事件。 工作人员的目标是通过处理所需的作业来处理那些域事件。 重点是: 事件数据应如何传递给工作人员? 正如阅读的文章所提倡的那样,第一个解决方案是使用RabbitMQ,它是一款出色的面向消息的中间件。 工作流程将很简单: 每当Web dyno生成事件时,它都会通过RabbitMQ发布该事件,从而为工作人员提供帮助。 缺点是,如果不处理潜在的发送失败或硬件问题,则无法保证提交汇总更新和事件的发布之间的即时一致性。这是另一个主要问题。 示例:发布事件可能没有成功进行汇总更新...导致事件代表域模型的错误表示。 您可能会争辩说存在全局XA(两阶段提交),但这不是适合所有数据库或中间件的解决方案。 那么,什么是确保这种即时一致性的好的解决方案?: IMO,将事件存储在数据库中,并且与聚合更新在同一本地事务中。 将创建一个简单的异步调度程序,并负责从数据库查询当前未发布的事件,并将其发送到RabbitMQ,后者再填充工作程序。 但是,为什么还要在webapp端并需要一个额外的调度程序:为什么在这种情况下需要RabbitMQ? 通过这种解决方案,在逻辑上似乎可以不需要RabbitMQ,尤其是因为数据库是共享的。 确实,无论如何,我们都看到即时一致性涉及从数据库进行轮询。 因此,为什么工人不直接对这次投票负责? 因此,我想知道为什么网络上有那么多文章在推广面向消息的中间件时几乎没有批评数据库排队。 文章摘录: 简单,使用正确的工具完成工作:这种情况正在引起消息传递系统的注意。它解决了上述所有问题;不再需要轮询,高效的消息传递,无需从队列中清除已完成的消息以及没有共享状态。 和即时的一致性,被忽略了吗? 综上所述,实际上无论情况如何,无论是否共享数据库,我们都需要数据库轮询。 我错过了一些批评观念吗? 谢谢

10
使用GUID作为主键
我通常在数据库中使用自动增量ID作为主键。我正在尝试学习使用GUID的好处。我已经阅读了这篇文章:https : //betterexplained.com/articles/the-quick-guide-to-guids/ 我意识到这些GUID用于在应用程序级别识别对象。它们是否也作为主键存储在数据库级别。例如,假设我有以下课程: public class Person { public GUID ID; public string Name; .. //Person Methods follow } 假设我想在内存中创建一个新人员,然后将其插入数据库。我可以这样做吗? Person p1 = new Person(); p1.ID=GUID.NewGUID(); PersonRepository.Insert(p1); 假设我有一个包含数百万行的数据库,其中GUID作为主键。这将永远是独一无二的吗?我什至正确理解了GUID吗? 我之前读过这篇文章:http : //enterprisecraftsmanship.com/2014/11/15/cqs-with-database-generation-ids/。它似乎使我感到困惑,因为它似乎建议在GUID和整数之间作为主键使用快乐的介质。 编辑11/06/18 我已经相信Guid比int更适合我的要求。这些天来,我使用CQRS的次数更多,而GUID则更适合。 我确实注意到一些开发人员在域模型中将GUID建模为字符串,例如:https : //github.com/dotnet-architecture/eShopOnContainers/blob/dev/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/ Buyer.cs-在这种情况下:IdentityGuid是一个建模为字符串的GUID。除了此处说明的内容外,是否还有其他理由:在分布式系统中使用自定义值对象或Guid作为实体标识符?。将GUID建模为字符串是“正常的”还是应该在模型和数据库中将其建模为GUID?

3
应用程序或域服务中的DDD存储库
这些天,我正在学习DDD,并且对如何使用DDD管理存储库有一些疑问。 实际上,我遇到了两个可能性: 第一 我读过的管理服务的第一种方法是在应用程序服务中注入存储库和域模型。 这样,在一种应用程序服务方法中,我们称为域服务方法(检查业务规则),如果条件良好,则在特殊方法上调用存储库以从数据库中持久化/检索实体。 一个简单的方法可以是: class ApplicationService{ constructor(domainService, repository){ this.domainService = domainService this.repository = repository } postAction(data){ if(this.domainService.validateRules(data)){ this.repository.persist(new Entity(data.name, data.surname)) } // ... } } 第二个 第二种可能性是将存储库注入domainService内,并且仅通过域服务使用存储库: class ApplicationService{ constructor(domainService){ this.domainService = domainService } postAction(data){ if(this.domainService.persist(data)){ console.log('all is good') } // ... } } class DomainService{ constructor(repository){ this.repository …


2
如果在业务逻辑更改时失败,则单元测试是否被认为是脆弱的?
请参见下面的代码;它会测试以查看具有性别的女性是否有资格接受要约1: [Fact] public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale() { var personId = Guid.NewGuid(); var gender = "F"; var person = new Person(personId, gender); var id = Guid.NewGuid(); var offer1 = new Offer1(id,"Offer1"); Assert.False(offer1.IsEligible(person)); } 此单元测试成功。但是,如果将来向女性提供“ Offer1”,它将失败。 可以接受的说法是-如果围绕报价1的业务逻辑发生了变化,那么单元测试就必须发生变化。请注意,在某些情况下(对于某些商品),业务逻辑会在数据库中更改,如下所示: update Offers set Gender='M' where offer=1; 在某些情况下,在域模型中如下所示: if (Gender=Gender.Male) { //do something } 另请注意,在某些情况下,背后的域逻辑会定期更改,而在某些情况下则不会。

1
如何选择使用域事件还是让应用程序层协调一切
我正在迈入域驱动设计的第一步,购买了蓝皮书和其他所有书籍,我发现自己看到三种实现特定解决方案的方法。作为记录:我没有使用CQRS或事件源。 假设有一个用户请求进入了应用程序服务层。出于某种原因,该请求的业务逻辑分为实体上的方法和域服务上的方法。我应该如何去调用那些方法? 到目前为止,我收集的选项有: 让应用程序服务调用这两种方法 使用方法注入/双重调度将域服务注入到实体中,让实体来做,然后让它调用域服务的方法(或者反之,让域服务在实体上调用方法) 在实体方法中引发一个域事件,该事件的处理程序调用域服务。(我正在谈论的域事件是:http : //www.udidahan.com/2009/06/14/domain-events-salvation/) 我认为这些都是可行的,但我无法在它们之间进行选择。我已经考虑了很长时间了,直到现在我再也看不到这三者之间的语义差异了。您知道什么时候使用什么准则吗?

2
DDD-聚合根的存储库是否处理保存的聚合?
我正在对现有应用程序的未开发模块使用类似DDD的方法;由于架构原因,它不是100%DDD,但我正在尝试使用一些DDD概念。我有一个界上下文(我认为这是适当的期限-我仍然在学习DDD)由两个实体:Conversation和Message。对话是根源,因为没有对话就不存在消息,并且系统中的所有消息都是对话的一部分。 我有一个ConversationRepository类(尽管它实际上更像是一个网关,但我使用术语“存储库”)可以在数据库中找到会话。当找到会话时,它还会(通过工厂)为该会话创建消息列表(作为属性公开)。这似乎是正确的处理方式,因为似乎不需要完整的MessageRepository类,因为只有在检索到对话时才存在该类。 但是,在保存消息时,由于它是Message的聚合根,这是ConversationRepository的责任吗?我的意思是,我是否应该在ConversationRepository上有一个名为AddMessageMessage 的方法,该方法将Message作为参数并将其保存到数据库中?还是应该有一个单独的存储库来查找/保存消息?逻辑上似乎是每个实体一个存储库,但是我也听说过“每个上下文一个存储库”。

4
在DDD中,是验证应用程序逻辑还是域逻辑?
假设我们正在使用DDD对表单进行建模;表格可能具有某种与之相关的业务规则-如果您不是学生,可能需要指定收入,如果您表示已婚,则需要列出孩子。如果您指定了一个国家,那么它应该有一个有效的国家。 这种验证是否存在于域或应用程序层中?我正在考虑的其他一些问题: 某些框架(例如Laravel)提供了验证规则,可以在请求到达控制器之前验证输入。如果在该级别进行验证,是否会破坏DDD? 对于确定国家/地区是否有效的情况,通常我只会查询世界上所有国家/地区的数据库表。但是,在DDD中,这可能(根据我的理解)是在域层上完成的。是否允许域层访问数据库,还是必须使用非SQL搜索来确定有效的国家/地区? 是否有必要在应用程序和域层都验证输入?

3
实际上,什么是领域驱动开发?[关闭]
已关闭。这个问题需要更加集中。它当前不接受答案。 想改善这个问题吗?更新问题,使其仅通过编辑此帖子来关注一个问题。 3年前关闭。 我从该地区的开发商那里听说了域驱动开发。他大声疾呼,就像改变需求的灵丹妙药。 我读了维基。还是不太清楚。实际上,“ 3D”是什么?现在UML类图表已过时真的令人惊讶吗?

5
REST API如何适合基于命令/操作的域?
在本文中,作者声称 有时,需要公开API中固有的非RESTful操作。 然后 如果一个API有太多的动作,则表明它是使用RPC观点设计的,而不是使用RESTful原理设计的,或者所讨论的API自然更适合RPC类型模型。 这也反映了我在其他地方阅读和听到的内容。 但是,我觉得这很令人困惑,我希望对此事有更好的了解。 示例I:通过REST接口关闭VM 我认为,有两种根本不同的方法来对虚拟机关闭建模。每种方式可能会有一些变化,但现在让我们集中讨论最基本的差异。 1.修补资源的状态属性 PATCH /api/virtualmachines/42 Content-Type:application/json { "state": "shutting down" } (或者,PUT在子资源上/api/virtualmachines/42/state。) VM将在后台关闭,并且在稍后的某个时间点(取决于关闭的成功与否)将成功,否则状态可能不会通过“关闭电源”在内部进行更新。 2.在资源的actions属性上进行PUT或POST PUT /api/virtualmachines/42/actions Content-Type:application/json { "type": "shutdown" } 结果与第一个示例完全相同。该状态将立即更新为“关闭”,并可能最终更新为“关闭电源”。 两种设计都是RESTful的吗? 哪个设计更好? 示例II:CQRS 如果我们有一个CQRS域,其中包含许多这样的“操作”(又称命令),它们可能潜在地导致多个聚合的更新,或者无法映射到具体资源和子资源上的CRUD操作? 我们是否应该在可能的情况下尝试建模尽可能多的命令,具体取决于在具体资源上创建或更新的具体资源(遵循示例I的第一种方法),其余部分使用“动作端点”? 还是应该将所有命令映射到动作端点(如示例I的第二种方法)? 我们应该在哪里划界线?设计何时变得不那么RESTful? CQRS模型是否更适合RPC之类的API? 据我了解,根据上面引用的文字。 从我的许多问题中可以看出,我对该主题有些困惑。您能帮我更好地了解它吗?

2
DDD聚合序列化的最佳做法
根据DDD,域逻辑不应被序列化,对象关系映射等技术问题所污染。 那么,如何在不通过getter和setter公开公开状态的情况下序列化或映射聚合状态?我已经看到了很多示例,例如存储库实现,但是实际上所有示例都依赖于实体上的公共访问器和用于映射的值对象。 我们可以使用反射来避免公共访问器,但是IMO这些域对象仍将隐式依赖于序列化问题。例如,在不调整序列化/映射配置的情况下,您无法重命名或删除私有字段。因此,您必须考虑序列化,而应将重点放在域逻辑上。 那么,要遵循的最佳妥协是什么?与公共访问器一起生活,但要避免将它们用于映射代码以外的其他用途?还是我只是想念一些明显的东西? 我对序列化DDD域对象(由实体和值对象组成的聚合)的状态特别感兴趣。这与无状态服务在简单数据容器对象上运行的常规脚本或事务脚本场景中的序列化无关。

5
在“无人问津”的世界中进行单元测试
我不认为自己是DDD专家,但作为解决方案架构师,请尽可能尝试应用最佳实践。我知道围绕DDD中的(公共)二传手“样式”的赞成和反对有很多讨论,我可以看到论点的两面。我的问题是,我在一个拥有各种技能,知识和经验的团队中工作,这意味着我不能相信每个开发人员都会以“正确”的方式做事。例如,如果我们设计域对象以便通过一种方法来执行对对象内部状态的更改,但提供公共属性设置器,则有人将不可避免地设置该属性而不是调用该方法。使用此示例: public class MyClass { public Boolean IsPublished { get { return PublishDate != null; } } public DateTime? PublishDate { get; set; } public void Publish() { if (IsPublished) throw new InvalidOperationException("Already published."); PublishDate = DateTime.Today; Raise(new PublishedEvent()); } } 我的解决方案是将属性设置器设为私有,这是可能的,因为我们用来为对象水合的ORM使用反射,因此它能够访问私有设置器。但是,这在尝试编写单元测试时会出现问题。例如,当我想编写一个单元测试来验证我们不能重新发布的要求时,我需要指出该对象已经发布。我当然可以通过两次调用Publish来做到这一点,但是我的测试假设第一次调用正确实现了Publish。好像有点臭。 让我们使用以下代码使场景更真实一些: public class Document { public Document(String title) …

2
如何准确地验证CQRS命令并将其转换为域对象?
我一直在修改穷人的CQRS 1,因为我喜欢它的灵活性,可以在一个数据存储中存储细粒度的数据,这为分析提供了极大的可能性,从而增加了商业价值,并且在需要时还提供了另一种包含非规范化数据的读取以提高性能。 。 但是不幸的是,从一开始,我就一直在为应该在这种架构中放置业务逻辑的问题而苦苦挣扎。 据我了解,命令是传达意图的手段,它本身与域没有关系。它们基本上是数据(哑巴,如果需要的话)传输对象。这是为了使命令可以在不同技术之间轻松转移。对于成功完成的事件的响应,同样适用于事件。 在典型的DDD应用程序中,业务逻辑驻留在实体,值对象,集合根中,它们既包含数据又包含行为。但是命令不是域对象,因此命令不应仅限于数据的域表示形式,因为这会对它们造成很大的压力。 因此,真正的问题是:逻辑到底在哪里? 我发现我在尝试构建一个非常复杂的集合(该集合为它的值的组合设置一些规则)时往往最容易遇到这种斗争。另外,在对域对象建模时,我喜欢遵循快速失败范例,知道对象何时到达处于有效状态的方法。 假设聚合Car使用两个组件: Transmission, Engine。 两个Transmission和Engine值对象被表示为超级类型和具有根据子类型,Automatic和Manual传输,或Petrol与Electric分别引擎。 在此域中,完全依靠成功创建的Transmission,,Automatic或Manual或任意一种类型来生存Engine。但是Car聚合引入了一些新规则,这些规则仅在Transmission和Engine对象在同一上下文中使用时才适用。即: 当汽车使用Electric发动机时,唯一允许的变速箱类型是Automatic。 当汽车使用Petrol发动机时,可能有两种类型的发动机Transmission。 我可以在创建命令的级别上捕获这种违反组件组合的行为,但是正如我之前所说,据我所知,不应这样做,因为命令将包含业务逻辑,而业务逻辑应限于域层。 一种选择是将这种业务逻辑验证移至命令验证器本身,但这似乎也不对。感觉就像我将解构该命令,检查使用getter检索的属性,并在验证器中比较它们并检查结果。这让我尖叫得像是违反了得墨meter耳的法律。 放弃提到的验证选项,因为它似乎不可行,似乎应该使用该命令并从中构造聚合。但是这种逻辑应该在哪里存在?是否应该在负责处理具体命令的命令处理程序中?还是应该在命令验证器中(我也不喜欢这种方法)? 我当前正在使用命令,并在负责的命令处理程序中从中创建一个聚合。但是,当我执行此操作时,如果我有一个命令验证器,它将根本不包含任何内容,因为如果该CreateCar命令存在,则它将包含我知道在单独的情况下有效的组件,但集合可能表示不同。 让我们想象一个混合了不同验证过程的场景-使用CreateUser命令创建一个新用户。 该命令包含Id将要创建的一个用户及其Email。 系统为用户的电子邮件地址规定以下规则: 必须是唯一的 不能为空, 最多包含100个字符(数据库列的最大长度)。 在这种情况下,即使有一个唯一的电子邮件是一条业务规则,但对其进行汇总检查几乎没有任何意义,因为我需要将系统中的全部当前电子邮件加载到内存中,并在命令中检查该电子邮件(Eeeek!某物,某物,性能)。因此,我将此检查移至命令验证程序,该程序将UserRepository作为依赖项,并使用存储库来检查是否存在使用命令中存在电子邮件的用户。 当涉及到这一点时,突然将其他两个电子邮件规则也放入命令验证器中是有意义的。但是我觉得规则应该确实存在于User聚合中,并且命令验证程序应该只检查唯一性,如果验证成功,我应该继续在中创建User聚合CreateUserCommandHandler并将其传递到要保存的存储库中。 我之所以这样,是因为存储库的save方法很可能会接受一个聚合,该聚合可确保一旦传递了聚合,所有不变量都将得到满足。当逻辑(例如,非空性)仅出现在命令验证本身中时,另一位程序员可以完全跳过此验证,并直接UserRepository使用User对象调用对象中的save方法,这可能导致致命的数据库错误,因为电子邮件可能包含太久了。 您个人如何处理这些复杂的验证和转换?我大多对自己的解决方案感到满意,但是我觉得我需要肯定的是,我对自己的选择并不满意,但我的想法和方法并不完全愚蠢。我完全接受完全不同的方法。如果您有自己尝试过的东西并且为您做得很好,我很乐意看到您的解决方案。 1作为负责创建RESTful系统的PHP开发人员,我对CQRS的解释与标准的异步命令处理方法略有不同,例如有时由于需要同步处理命令而从命令返回结果。

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.