CQRS之旅——旅程8(后记:经验教训)

旅程8:后记:经验教训

咱们的地图有多好?咱们走了多远?咱们学到了什么?咱们迷路了吗?

“这片土地可能对那些愿意冒险的人有益。”亨利.哈德逊
复制代码

这一章总结了咱们旅程中的发现。它强调了咱们在这个过程当中所学到的最重要的经验教训,提出了若是咱们用新知识开始这段旅程,咱们将以不一样的方式作的一些事情,并指出了Contoso会议管理系统的一些将来道路。git

你应该记住,这个总结反映的是咱们的具体旅程,并不是全部这些发现都适用于你本身的CQRS旅行。例如,咱们的目标之一是探索如何在部署到Microsoft Azure并在利用云的可伸缩性和可靠性的应用程序中实现CQRS模式。对于咱们的项目,这意味着使用消息传递来支持多个角色类型和实例之间的通讯。您的项目可能不须要多个角色实例,或者没有部署到云中,所以可能不须要如此普遍地(或者根本不须要)使用消息传递。github

咱们但愿这些发现可以被证实是有用的,特别是当您刚刚开始使用CQRS和事件源时。web

咱们学到了什么

本节描述了咱们学到的主要经验教训。它们没有以任何特定的顺序呈现。数据库

性能问题

在咱们的旅程开始时,咱们对CQRS模式的一个概念是,经过分离应用程序的读和写方面,咱们能够优化每一个方面的性能。CQRS社区的许多人都认同这一观点,例如:windows

“CQRS告诉我,我能够分别优化读和写,并且我没必要老是手动的反规范化到平面表中。”浏览器

  • Kelly Sommers - CQRS顾问

这在咱们的实践过程当中获得了证明,当咱们确实须要解决性能问题时,这种分离使咱们受益不浅。性能优化

在旅程的最后阶段,测试揭示了应用程序中的一组性能问题。当咱们研究它们时,发现它们与咱们实现CQRS模式的方式关系不大,而与咱们使用基础设施的方式关系更大。发现这些问题的根源是困难的,因为应用程序中有如此多的活动部件,得到正确的跟踪和用于分析的正确数据是一项挑战。一旦咱们肯定了瓶颈,修复它们就相对容易了,这主要是由于CQRS模式使您可以清楚地分离系统的不一样元素,好比读和写。尽管实现CQRS模式所致使的关注点分离会使识别问题变得更加困难,可是一旦您识别出一个问题,不只更容易修复它,并且更容易防止它的重现。解耦的体系结构使得编写重现问题的单元测试更加简单。架构

咱们在处理系统中的性能问题时遇到的挑战更多地是因为咱们的系统是一个分布式的、基于消息的系统,而不是由于它实现了CQRS模式。异步

第7章,“添加弹性和优化性能”提供了关于咱们处理系统中性能问题的方法的更多信息,并对咱们想要进行但没有时间实现的额外更改提出了一些建议。分布式

实现消息驱动系统远非易事

咱们这个项目的基础设施是在旅程中根据须要开发它。咱们没有预料(也没有预先警告)须要多少时间和精力来建立应用程序所需的健壮基础设施。咱们在许多开发任务上花费的时间至少是最初计划的两倍,由于咱们持续发现与基础设施相关的额外需求。特别是,咱们从一开始就了解到拥有健壮的事件存储是相当重要的。咱们从经验中获得的另外一个关键思想是,消息总线上的全部I/O都应该是异步的。

Jana(软件架构师)发言:
虽然咱们的事件存储还不是生产环境完备的,可是若是您决定实现本身的事件存储,那么当前的实现很好地指示了应该处理的问题类型。

尽管咱们的应用程序并不大,但它向咱们清楚地说明了end-to-end跟踪的重要性,以及帮助咱们理解系统中全部消息流的工具的价值。第4章“扩展和加强订单和注册限界上下文”描述了测试在帮助咱们理解系统方面的价值,并讨论了由咱们的顾问之一Josh Elster建立的消息传递中间语言(messaging intermediate language, MIL)。

Gary(CQRS专家)发言:
若是咱们有一个用于消息传递的标准符号,就能够帮助咱们与领域专家和核心团队以外的人员沟通一些问题,这也会有所帮助。

总之,咱们一路上遇到的许多问题都与CQRS模式没有特定的关系,而是与咱们解决方案的分布式、消息驱动特性更相关。

Jana(软件架构师)发言:
咱们发现,使用不一样的Topic来传输由不一样聚合发布的事件,经过这样来划分服务总线有助于实现可伸缩性。有关更多信息,请参见第7章“ 添加弹性和优化性能”。另外,请参阅这些博客文章:“ Microsoft Azure Storage Abstractions and their Scalability Targets”和“ Best Practices for Performance Improvements Using Service Bus Brokered Messaging”。

使用云带来的挑战

虽然云提供了不少好处,好比可靠的、可伸缩的、现成的服务,您只需单击几下鼠标就可使用这些服务,可是云环境也带来了一些挑战:

  • 您可能没法在任何您想要的地方使用事务,由于云的分布式特性使得ACID(原子性、一致性、隔离性、持久性)事务在许多场景中不切实际。所以,您须要了解如何使用最终的一致性。例如,请参见第5章“准备发布V1版本”,以及第7章“添加弹性和优化性能”中减小UI延迟的部分章节。
  • 您可能须要从新检查关于如何将应用程序组织到不一样层的假设。例如,参见第7章“添加弹性和优化性能”中关于进程内同步命令的讨论。
  • 您不只必须考虑浏览器或内部环境与云之间的延迟,还必须考虑在云中运行的系统的不一样部分之间的延迟。
  • 您必须考虑到瞬时错误,并了解不一样的云服务可能如何实现节流。若是您的应用程序使用几个可能被节流的云服务,那么您必须协调应用程序如何处理不一样服务在不一样时间进行节流。

Markus(软件开发人员)发言:
咱们发现,代码中只有一个总线抽象,这掩盖了这样一个事实,即有些消息是在本地进程内处理的,有些消息是在不一样的角色实例中处理的。要查看这是如何实现的,请查看 ICommandBus接口以及 CommandBusSynchronousCommandBusDecorator类。第七章“ 增长弹性和优化性能”包括了对 SynchronousCommandBusDecorator类的讨论。

备注:咱们的Visual Studio解决方案中的多个构建配置是为部分解决这个问题而设计的,也帮助人们下载和使用代码来快速入门。
复制代码

CQRS是不一样的

在咱们的旅程开始时,有人警告咱们,尽管CQRS模式看起来很简单,但实际上它要求您在考虑项目的许多方面时进行重大的转变。咱们在旅途中的经历再次证实了这一点。您必须准备抛弃许多假设和预先设想的想法,在开始充分理解从模式中得到的好处以前,您可能须要先在几个限界上下文中实现CQRS模式。

这方面的一个例子是最终一致性的概念。若是您来自关系数据库背景,而且已经习惯了事务的ACID属性,那么在系统的全部级别上接受最终的一致性并理解其含义是一个很大的步骤。第5章“准备发布V1版本”和第7章“添加弹性和优化性能”都讨论了系统不一样领域的最终一致性。

除了与您可能熟悉的不一样以外,尚未一种正确的方法来实现CQRS模式。因为咱们对模式和方法的不熟悉,咱们在功能块上作了更多错误的开始,而且对所需的时间估计不好。随着咱们对这种方法愈来愈熟悉,咱们但愿可以更快地肯定如何在特定状况下实现模式,并提升咱们估算的准确性。

Markus(软件开发人员)发言:
CQRS模式在概念上很简单,而细节才决定成败。

咱们花了一些时间来理解CQRS方法及其含义的另外一种状况是在限界上下文之间的集成期间。第5章“准备发布V1版本”详细讨论了团队如何处理会议管理与订单和注册上下文之间的集成问题。这部分旅程揭示了一些额外的复杂性,当您使用事件做为集成机制时,这些复杂性与限界上下文之间的耦合级别有关。咱们的假设是,事件应该只包含关于聚合或限界上下文中变化的信息,但事实证实这种假设是没有帮助的,事件能够包含对一个或多个订阅者有用的附加信息,并有助于减小订阅者必须执行的工做量。

CQRS模式为如何划分系统引入了额外的思考。您不只须要考虑如何将系统划分为层,还须要考虑如何将系统划分为限界上下文,其中一些上下文将包含CQRS模式的实现。在旅程的最后阶段,咱们修改了关于层的一些假设,将一些处理从最初完成处理的工做者角色引入到web角色中。在第7章“增长弹性和优化性能”中讨论了如何在进程中发送和处理命令。应该根据领域模型将系统划分为限界上下文,每一个限界上下文都有本身的领域模型和通用语言。一旦肯定了限界上下文,就能够肯定在哪些限界上下文中实现CQRS模式。这将影响如何以及在何处须要实现这些隔离限界上下文之间的集成。第二章“[分解领域]”介绍了咱们对Contoso会议管理系统的所做的决策。

Gary(CQRS专家)发言:
单个进程(部署中的角色实例)能够承载多个限界上下文。在此场景中,您不须要为限界上下文使用服务总线来彼此通讯。

实现CQRS模式比实现传统的(建立、读取、更新、删除)CRUD风格的系统更复杂。对于这个项目,第一次学习CQRS和建立分布式、异步消息传递基础设施的开销也很大。咱们在此过程当中的经验清楚地向咱们证明了为何CQRS模式不是顶级体系结构。您必须确保实现基于CQRS的限界上下文相关的成本是值得的,一般,您将在高竞争、高协做的领域中看到CQRS模式的好处。

Gary(CQRS专家)发言:
分析业务需求、构建有用的模型、维护模型、用代码表示它以及使用CQRS模式实现它都须要时间和金钱。若是这是您第一次实现CQRS模式,那么您还须要对基础设施元素(如消息总线和事件存储)进行开销投资。

事件源和事务日志

对于事件源和事务日志是否等同于同一件事,咱们进行了一些讨论:它们都建立了所发生事情的记录,而且都容许您经过重播历史数据来从新建立系统的状态。结论是,事件的显著特征是除了记录所发生的事实以外,还能捕获意图。有关咱们所说的意图的更多细节,请参阅参考指南中的第4章“深刻CQRS和ES”。

涉及到领域专家的

实现CQRS模式鼓励领域专家的参与。该模式使您可以将写端上的领域和读端上的报告需求分离出来,并将它们与基础设施关注点分离开来。这种分离使领域专家更容易参与系统中他的专业知识最有价值的方面。使用领域驱动的设计概念,如限界上下文和通用语言,也有助于集中团队的注意力,并促进与领域专家的清晰沟通。

咱们的验收测试证实是一种有效的方法,可让领域专家参与进来并获取他的知识。第4章“扩展和加强订单和注册有界上下文”详细描述了这种测试方法。

Jana(软件架构师)发言:
做为一个反作用,这些验收测试还有助于咱们处理伪生产版本的快速发布,由于它们使咱们可以在UI级别运行一组完整的测试,以验证除单元测试和集成测试以外的系统行为。

除了帮助团队定义系统的功能需求以外,领域专家还应该参与评估一致性、可用性、持久性和成本之间的权衡。例如,领域专家应该帮助肯定何时手动流程是可接受的,以及在系统的不一样区域中须要什么级别的一致性。

Gary(CQRS专家)发言:
开发人员倾向于将全部内容都锁定到事务中,以确保彻底的一致性,但有时并不值得这样作。

什么时候使用CQRS

如今咱们已经完成了咱们的旅程,咱们如今能够建议您应该评估的一些标准,以肯定是否应该考虑在应用程序中的一个或多个限界上下文中实现CQRS模式。您能正面回答的问题越多,就越有可能将CQRS模式应用到给定的限界上下文中,从而使您的解决方案受益:

  • 限界上下文是否实现了业务功能的一个领域,这个领域是您的市场中的一个关键区别点?
  • 限界上下文本质上是否与可能在运行时具备高争用级别的元素协做?换句话说,多个用户是否会为了访问相同的资源而竞争?
  • 限界上下文是否可能经历不断变化的业务规则?
  • 您是否已经具有了健壮的、可伸缩的消息传递和持久性基础设施?
  • 可伸缩性是这个限界上下文面临的挑战之一吗?
  • 限界上下文中的业务逻辑复杂吗?
  • 您清楚CQRS模式将给这个限界上下文带来的好处吗?

Gary(CQRS专家)发言:
这些都是经验法则,不是硬性规定。

若是咱们从新开始,会有什么不一样?

本节是咱们反思咱们的旅程的结果,以及肯定了一些咱们想以不一样方式去作的事情和一些咱们但愿追求的其余机会。若是在咱们掌握了如今咱们所了解的CQRS和ES知识以后重来一次的话。

从消息传递和持久性的坚实基础设施开始

咱们将从一个可靠的消息传递和持久性基础设施开始。咱们采起的方法是从简单的先开始,并根据须要创建基础设施,这意味着咱们在旅程中积累了技术债务。咱们还发现,采用这种方法意味着在某些状况下,咱们对基础设施的选择影响了咱们实现领域的方式。

Jana(软件架构师)发言:
从旅行的角度来看,若是咱们从一个坚实的基础设施开始,咱们将有时间处理领域中一些更复杂的部分,好比等待列表(Wating-list)。

从一个可靠的基础设施开始也能使咱们更早地开始性能测试。咱们还将进一步研究其余人如何在基于CQRS的系统上进行性能测试,并在其余系统上寻找性能基准,好比Jonathan Oliver的EventStore

咱们采起这种方法的缘由之一是咱们从顾问那里获得的建议:“不要担忧基础设施。”

更多地利用基础设施的能力

从一个坚实的基础设施开始也将容许咱们更多地利用基础设施的能力。例如,当咱们发布一个事件时,咱们使用消息发起者的ID做为会话ID在Azure服务总线传递,但从系统处理事件的部分来看,这并不老是最好的使用会话ID的方式。

做为其中的一部分,咱们还将研究基础设施如何支持其余最终一致性的特殊状况,如时间一致性、单调一致性、“read my writes”和自我一致性。

咱们想探讨的另外一个想法是使用基础设施来支持版本之间的迁移。咱们能够考虑使用基于消息的流程或实时通讯流程来协调把新版本上线,而不是针对每一个版本以特定的方式处理迁移。

采用更系统的方法来实现过程管理器

咱们在旅程的早期就开始实现咱们的过程管理器,而且仍然在强化它,并确保它的行为在旅程的最后阶段是幂等的。一样,从为流程管理人员提供一些坚实的基础设施支持开始,使他们更有弹性,这将对咱们有所帮助。可是,若是咱们要从新开始,咱们也会等到过程的后期再实现流程管理器,而不是直接开始。

在旅程的第一阶段,咱们开始实现RegistrationProcessManager类。第3章“订单和注册限界上下文”描述了初始实现。在旅程的每一个后续阶段,咱们都对流程管理器进行了更改。

以不一样的方式划分应用程序

在项目开始时,咱们会更仔细地考虑系统的分层。咱们发现咱们的划分的方式是把应用程序分到web角色和工做者角色中,这在第4章“扩展和加强订单和注册限界上下文“中进行了描述。但这不是最优的,在旅程的最后阶段,在第7章“增长弹性和优化性能”中,做为性能优化的一部分,咱们对架构作了一些重大改变。

例如,在旅程的最后阶段,做为从新组织的一部分,咱们在web应用程序中引入了同步命令处理,同时引入了已存在的异步命令处理。

以不一样的方式组织开发团队

咱们学习CQRS模式的方法是迭代开发、回顾、讨论,而后重构。可是,咱们能够经过让几个开发人员在相同的特性上独立工做,而后比较结果,从而学到更多。这可能揭示了更普遍的解决方案和方法。

评估领域域和限界上下文是否适合使用CQRS模式

咱们但愿从一组更清晰的启发开始(如本章前面概述的启发),以肯定特定的限界上下文是否会受益于CQRS模式。若是咱们关注领域中更复杂的地方,好比等待列表(Wating-list),而不是订单、注册和支付的限界上下文,咱们可能会学到更多。

性能计划

咱们将在旅程的早期处理性能问题。咱们尤为要:

  • 提早设定明确的性能目标。
  • 在过程当中更早地运行性能测试。
  • 使用更大更实际的负载。

咱们没有作任何性能测试,直到旅程的最后阶段。有关咱们发现的问题以及如何解决这些问题的详细讨论,请参见第7章“添加弹性和优化性能”。

在旅程的最后阶段,咱们在服务总线上引入了一些分区,以提升事件的吞吐量。此分区是基于事件的发布者完成的,所以由同一个聚合类型发布的事件将发布到同一个Topic。咱们但愿把当前使用一个Topic的扩展到使用多个Topic,可能会基于消息中OrderID的hash进行分区(这种方法一般称为分片)。这将为应用程序提供更大的扩展。

以不一样的方式思考UI

咱们认为UI与读写模型交互的方式,以及它处理最终一致性的方式都很好,而且知足了业务需求。特别是,UI检查预订是否可能成功并相应地修改其行为的方式,以及UI容许用户在等待更新读模型时继续输入数据的方式。有关当前解决方案如何工做的更多细节,请参见第7章“添加弹性和优化性能”中的“优化UI”一节。

咱们想研究除非绝对须要,其余避免在UI中等待的方法,好比使用浏览器推送技术。在某些地方,当前系统中的UI仍然须要等待针对读模型的异步更新。

探索事件源的一些额外好处

咱们发如今旅程的第三阶段,第5章“准备发布V1版本”中,修改订单和注册限界上下文来使用事件有助于简化这个限界上下文的实现,一部分是由于它已经使用了大量的事件。

在当前的旅程中,咱们没有机会进一步探索灵活性的承诺,以及从事件源中挖掘过去事件以得到新的业务看法的能力。可是,咱们确实确保系统保存了全部事件的副本(不只仅是那些重建聚合状态所需的副本)和命令,以便在未来启用这些类型的场景。

Gary(CQRS专家)发言:
一样有趣的是,经过事件源或其余技术(如数据库事务日志或SQL Server的StreamInsight特性)来挖掘过去的事件流以获取新的业务洞察是否更容易实现?

探索关于限界上下文集成的相关问题

在咱们的V3版本中,全部限界上下文都由同一个核心开发团队实现。咱们但愿研究在实践中,由不一样开发团队实现的限界上下文与现有系统集成起来有多容易。

这是您为学习经验作出贡献的一个很好的机会:继续实现另外一个限界上下文(请参阅产品backlog中的优秀用户故事),将它集成到Contoso会议管理系统中,并在旅程的另外一章中描述您的经验。

相关文章
相关标签/搜索