Questions tagged «cqrs»

命令查询职责隔离设计模式

2
为什么用Handle()分隔类CommandHandler而不是Command本身的处理方法
我有一部分使用S#arp Architecture实施的CQRS模式,如下所示: public class MyCommand { public CustomerId { get; set; } // some other fields } public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult> { Handle(MyCommand command) { // some code for saving Customer entity return CommandResult.Success; } } 我想知道为什么不只Command包含数据和处理方法的类?这是一种可测试性的好处,您需要与命令属性分开测试命令处理逻辑吗?还是某些频繁的业务需求,您需要由不同的实现来处理一个命令ICommandHandler<MyCommand, CommandResult>?

3
域驱动设计中的域对象是否应该仅是写操作?
我已经阅读了近两年的关于域驱动设计的文章,并且一直在谨慎地将一些概念引入我的日常工作中,或者至少计划了如何在域驱动设计中定期完成工作的计划。 我开始得出一个结论,尤其是在阅读了有关事件源和命令查询责任隔离(CQRS)的更多信息后,域对象可能仅用于写目的。更清楚地说,在我阅读的许多文档中,人们似乎巧妙地暗示了域对象负责进行以域为中心的操作/计算,验证,然后在那里主要是为持久化提供了一条途径存储库实现中提供的基础结构。尽管我喜欢这样的事实,因为它可以大大简化领域模型,因为它消除了公开状态的责任。 如果确实确实将域对象主要用作只写对象,那么这对我提出了一些问题,希望有人可以回答。 如何对具有setter的对象或修改对象状态但不提供向外公共接口以从诸如C#中的属性获取器读取状态的方法执行单元测试?仅仅为了使该对象可测试就可以公开状态吗? 如何向用户显示在域中完成的计算或运算的结果,而不必持久存储它们,然后将结果从持久存储中拉出到域的上下文之外?仅出于显示结果的目的而公开状态是否可以? 根据经验,唯一的属性获取器(获取访问器)应该是在域中也可写的属性吗?还是换句话说,只读属性应该唯一避免,因为它们仅出于读取目的存在,因此在实际的域模型中不发挥必要的作用? 相关资料: TDD,DDD和封装

2
CQRS +事件源:(是否正确)命令通常是点对点传递的,而域事件是通过pub / sub传递的?
我基本上是想围绕CQRS的概念和相关概念。 尽管CQRS不一定包含消息传递和事件源,但它似乎是一个很好的组合(从结合了这些概念的许多示例/博客中可以看出) 给定一个用于某种情况的状态更改的用例(例如,更新有关SO的问题),您是否认为以下流程是正确的(如最佳实践中那样)? 系统发出一个汇总的UpdateQuestionCommand,可以将其分成几个较小的命令:以Question Aggregate Root为目标的UpdateQuestion和以User Aggregate Root为目标的UpdateUserAction(对点进行计数)。这些是使用点对点消息传递异步发送的。 聚合根起作用,如果一切顺利,则分别引发事件QuestionUpdated和UserActionUpdated,它们包含外包给事件存储的状态。要保留yadayada,只是为了完整起见,此处并不是重点。 这些事件也被放在发布/订阅队列中进行广播。任何订阅者(其中可能有一个或多个创建阅读视图的投影仪)都可以自由订阅这些事件。 一个普遍的问题:最佳实践是命令之间进行点对点通信(即:接收方已知),而广播事件(即:接收方未知)吗? 假设以上所述,允许通过pub / sub而不是点对点广播命令的优点/缺点是什么? 例如:在使用Saga广播广播Commands时可能会出现问题,因为Saga在某个聚合根之一出现故障的情况下需要扮演的调解角色受到了阻碍,因为saga不知道哪个聚合根开始参与。 另一方面,我看到允许广播命令时的优势(灵活性)。

2
没有DDD和没有(或带有?)ES的CQRS-什么是写模型,什么是读模型?
据我了解,CQRS背后的主要思想是拥有2个不同的数据模型来处理命令和查询。这些被称为“写入模型”和“读取模型”。 让我们考虑一个Twitter应用程序克隆的示例。以下是命令: 用户可以自己注册。CreateUserCommand(string username)发出UserCreatedEvent 用户可以关注其他用户。FollowUserCommand(int userAId, int userBId)发出UserFollowedEvent 用户可以创建帖子。CreatePostCommand(int userId, string text)发出PostCreatedEvent 虽然我在上面使用了“事件”一词,但我并不是说“事件源”事件。我的意思是触发读取模型更新的信号。我没有活动存储,到目前为止,我想专注于CQRS本身。 这是查询: 用户需要查看其帖子列表。 GetPostsQuery(int userId) 用户需要查看其关注者列表。 GetFollowersQuery(int userId) 用户需要查看其后的用户列表。 GetFollowedUsersQuery(int userId) 用户需要查看“朋友供稿”-他们所有朋友活动的日志(“您的朋友约翰刚刚创建了一个新帖子”)。 GetFriedFeedRecordsQuery(int userId) 要处理,CreateUserCommand我需要知道这样的用户是否已经存在。因此,在这一点上,我知道我的写模型应该具有所有用户的列表。 要处理,FollowUserCommand我需要知道userA是否已经跟随userB。在这一点上,我希望我的写模型具有所有用户关注用户连接的列表。 最后,要处理CreatePostCommand我不需要其他任何东西,因为我没有诸如之类的命令UpdatePostCommand。如果有这些内容,则需要确保该帖子存在,因此我需要所有帖子的列表。但是因为我没有此要求,所以不需要跟踪所有帖子。 问题1:以我使用的方式使用术语“写模型”实际上正确吗?还是ES的“写模型”总是代表“事件存储”?如果是这样,我需要处理命令的数据和我需要处理查询的数据之间是否存在任何分隔? 要处理GetPostsQuery,我需要所有帖子的列表。这意味着我的阅读模型应具有所有帖子的列表。我将通过收听来维护此模型PostCreatedEvent。 要同时处理GetFollowersQuery和GetFollowedUsersQuery,我需要一个用户之间所有连接的列表。为了维持这种模式,我要听UserFollowedEvent。这是第2个问题:如果我在这里使用写模型的连接列表,实际上可以吗?还是我应该更好地创建一个单独的读取模型,因为将来可能需要比写入模型更多的细节? 最后,要处理,GetFriendFeedRecordsQuery我需要: 听 UserFollowedEvent 听 PostCreatedEvent 了解哪些用户关注其他用户 如果用户A跟随用户B并且用户B开始跟随用户C,则应显示以下记录: 对于用户A:“您的朋友用户B刚刚开始关注用户C” 对于用户B:“您刚刚开始关注用户C” 对于用户C:“用户B现在正在关注您” 这里的问题3:我应该用什么模式来获取连接列表?我应该使用写模型吗?我应该使用读取模型- GetFollowersQuery/ GetFollowedUsersQuery吗?还是应该让GetFriendFeedRecordsQuery模型本身处理UserFollowedEvent并维护自己的所有连接列表?
11 cqrs  read-model 

1
如何在CQRS + Event Sourcing架构中处理Add / Create *命令
我想使用CQRS模式和Event Sourcing来实现我的第一个应用程序。我想知道如何正确处理聚合根的创建。假设有人发送CreateItem命令。应该如何处理?事件ItemCreated应该存储在哪里?作为新项目的第一事件?还是我应该具有某种ItemList实体来聚合所有项目,并且其事件列表仅由ItemCreated事件组成? 乌迪·达汉( Udi Dahan)建议不要创建聚合根,而应始终使用某种获取方法。但是我如何获取新的东西,当然还没有分配任何ID。我理解背后的想法,并且认为一个新对象是其状态由零个事件组成的对象是很合理的。但是我应该如何使用呢?我的存储库中应该有一个与众不同的方法吗?getNewItem()还是让我的get(id)方法接受Optional<ItemId>? 编辑:经过一段时间的挖掘,我发现使用actor对上述模式进行了非常有趣的实现。作者不是创建聚合,而是从某种具有新创建的UUID的存储库中检索聚合。这种方法的缺点是他允许出现临时的不一致状态。我也想知道如何delete使用这种方法来实现方法。只需将Deleted事件添加到聚合的事件列表中?


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
我必须妥协:DRY还是Command-Query-Separation?
我最近正在重构既是命令又是查询方法的方法。 将其分为一个命令方法和一个查询方法后,我发现代码中现在有多个地方可以调用命令,然后从查询中获取值,这似乎违反了DRY原理。 但是,如果我要将通用代码包装到一个方法中,则该方法既是命令又是查询。这可以接受吗?

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

4
CQRS + ES中的对象应该在哪里进行完全初始化:在构造函数中,还是在应用第一个事件时?
在OOP社区中似乎已经达成广泛的共识,即类构造函数不应将对象部分或什至完全未初始化。 我所说的“初始化”是什么意思?粗略地讲,原子过程将新创建的对象带入其所有类不变式都成立的状态。它应该是发生在对象上的第一件事(每个对象只能运行一次),并且任何内容都不应该拥有未初始化的对象。(因此,经常建议在类构造函数中执行对象初始化。出于同样的原因,Initialize方法常常被皱眉,因为这些方法打破了原子性并使得可以持有和使用尚未使用的对象成为可能。处于定义良好的状态。) 问题:当CQRS与事件源(CQRS + ES)结合使用时,对象的所有状态变化都被捕获在一系列有序的事件(事件流)中,我想知道对象何时真正达到完全初始化的状态:在类构造函数的末尾,还是在将第一个事件应用于对象之后? 注意:我避免使用“聚合根”一词。如果愿意,在阅读“对象”时将其替换。 讨论示例:假定每个对象由某个不透明Id值唯一标识(请考虑GUID)。可以使用相同的Id值在事件存储中标识表示该对象状态变化的事件流:(不用担心正确的事件顺序。) interface IEventStore { IEnumerable<IEvent> GetEventsOfObject(Id objectId); } 进一步假设有两种对象类型Customer和ShoppingCart。让我们关注ShoppingCart:创建时,购物车为空,必须与一个客户完全关联。最后一点是类不变式:ShoppingCart与a无关的对象Customer处于无效状态。 在传统的OOP中,可以在构造函数中对此建模: partial class ShoppingCart { public Id Id { get; private set; } public Customer Customer { get; private set; } public ShoppingCart(Id id, Customer customer) { this.Id = id; this.Customer = customer; } } …
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.