AIGC在金融场景是如何落地的? 了解详情
写点什么

如何设计一款大型 Laravel 应用程序的架构(上)?

  • 2020-06-02
  • 本文字数:3351 字

    阅读完需:约 11 分钟

如何设计一款大型Laravel应用程序的架构(上)?


本文最初发布于 stitcher 博客,经原作者授权由 InfoQ 中文站编译并分享。


首先来看场景概况。这是我们曾经做过的一个比较大的项目。一旦项目完成,它将为数以十万计的用户提供服务,处理大量财务交易,并且需要即时创建独立的租户专属安装。该项目的一个关键需求是需要轻松地报告和追踪产品订购流程的历史记录,这一流程也是业务的核心。


除了这个面向前端的客户流程外,还会有一个复杂的管理面板来管理产品。这里几乎完全不需要报告或追踪管理活动的历史记录。主要目标是提供一个易于使用的产品管理系统。


希望你能理解我故意使用模糊术语的做法,因为这显然不是一个开源项目。不过,我认为“产品管理”和“订单”的概念足以让你了解我们所做的是怎样的设计决策了。


我们首先来讨论这个系统的一种设计方法,这种方法出自我之前写过的《超越CRUD的Laravel》文章系列。


在这样的系统中可能会有两个域组:Product 和 Order,以及两个同时使用这些域的应用程序:AdminApplicationCustomerApplication


简化版本如下所示:



在之前的项目中,我们成功应用了这一架构,所以这次也可以直接使用它了事。但它也有一些缺点,特别是对于这个新项目而言,缺陷很突出:我们必须牢记,报告和历史记录跟踪是订购流程的关键之处。我们希望能在代码中体现这一点,而不是简单地加个副效果就行。


例如:我们可以使用活动日志包来跟踪订单过程中出现的“历史消息”。我们还能在订单和历史记录表上编写自定义查询以生成报告。


但是,这些解决方案只能成为核心业务的简单的副效果,否则就没法正常工作。可是我们的情况下并没有这个条件。因此,Freek 和我的任务就是为这个项目设计一个方案,让报告和历史记录跟踪成为应用程序中易于维护和易于使用的核心部分。


很自然地,我们将目光投向了事件溯源,这是一种可以满足上述要求的优秀而灵活的解决方案。但没有什么是免费的午餐:事件溯源需要编写很多额外代码才能完成其他方法下很简单的事情。在有些地方,你过去只需要简单的 CRUD 操作处理数据库中的数据即可,可现在你不得不操心事件调度,还要用 projector 和 reactor 处理事件,同时还要一直关注版本控制。


很明显,事件溯源系统能解决许多问题;但即使在一些没有任何收益的地方,它也会带来很多开销。


这就是我的意思:如果我们决定事件溯源 Orders 模块(它依赖于 Products 模块中的数据),那么我们还需要事件溯源后者,否则的话我们可能会撞上无效状态。如果 Products 没有事件溯源,并且已被删除,则我们将无法重建 Orders 状态,因为它缺少信息。


因此,要么我们事件溯源一切内容,要么就设法解决这个矛盾。

需要事件溯源一切内容吗?

过去,在一些业余项目中使用事件溯源的经历让我们痛苦地意识到,我们不应该低估它所增加的复杂性。此外,Greg Young 表示事件溯源整个系统往往不是一个好主意——他对人们关于事件溯源的误解有完整的论述,值得一看!


很显然,我们不想事件溯源整个应用程序。这样做根本没有任何意义。唯一的选择是找到一种将有状态系统与事件溯源系统结合在一起的方法,但可惜在这个主题上我们发现没什么资源可用。


不管怎样,我们还是进行了一些费工费力的研究,并设法找到了问题的答案。但这个答案不是来自事件溯源社区,而是来自一种完善的 DDD 实践:限界上下文。


如果我们希望 Products 模块成为一个独立的,有状态的系统,则必须清晰地尊重 Products 和 Orders 之间的界限。我们不能把这两个模块视为一个单体应用程序,而必须将它们视为两个单独的上下文——也就是单独的服务,两者之间通信时必须确保 Order 上下文永远不会进入无效状态。


如果构建的 Order 上下文不直接依赖于 Product 上下文,那么这个 Product 上下文是怎么构建的就无关紧要了。


在与 Freek 讨论时,我提到:将 Products 视为可通过一个 REST API 访问的独立服务。当 API 下线或改动了自己的数据结构时,我们如何保证事件溯源应用程序仍然可以正常工作呢。


显然,我们实际上并不会构建在服务之间通信的 API,因为它们将位于同一服务器上的同一代码库中。但在设计系统时有这样的思考还是很好的。


边界是下面这个样子,其中每个服务都有自己的内部设计。



如果你读过了我的《超越 CRUD 的 Laravel》系列文章,那么肯定已经熟悉了 Product 上下文的工作机制。那边就没什么新东西要讲了。不过 Order 上下文可以多讲一些背景信息。

事件溯源一部分内容

下面我们看一下事件溯源的部分内容。我假设你之所以会阅读这篇文章,至少是因为你对事件溯源感兴趣,所以我不会详细解释所有内容。


OrderAggregateRoot将跟踪在这一上下文中发生的所有事件,并将成为与应用程序对话的入口点。它还将调度事件,事件被存储并传播到所有 reactor 和 projector。


Reactor 将处理副效果,而这些副效果将永远不会重放,并且 projector 将进行投影。在我们的项目中,这些都是简单的 Laravel 模型。尽管只能从 projector 内部写入这些模型,但可以从其他任何上下文中读取它们。



我们在这里做出的一个设计决策是不拆分读写模型,因为现在我们使用了口头和书面约定,要求这些模型只能通过它们的 projector 写入。这种投影模型的一个例子就是一个 Order。


要记住的一条最重要规则是,只能从 Order 存储的事件中重建 Order 上下文的整个状态。


那么我们如何从其他上下文中提取数据呢?当与 Product 相关的上下文中发生某些事情时,如何通知 Order 上下文?可以肯定的是:与 Products 有关的所有信息将需要作为事件存储在 Order 上下文中;因为在这一上下文中,事件是唯一的真实来源。


为了做到这一点,我们引入第三种事件监听器。我们已经有了 projectors 和 reactors;现在我们添加订阅者(subscribers)的概念。允许这些订阅者侦听来自其他上下文的事件,并在其当前上下文中进行相应的处理。看起来,它们几乎总是将外部事件转换为内部存储事件。



从事件存储在 Order 上下文中的那一刻起,我们就可以放心地忘记对 Product 上下文的任何依赖。


有些读者可能认为我们会通过在这两个上下文之间复制事件来复制数据。当然,我们将基于 Product 的created时间存储特定于 Orders 的事件,因此的确会复制一些数据。但是,这样做带来的好处比你想象的更多。


首先:Product 上下文不需要知道其他哪些上下文将使用它的数据。它不必考虑事件版本控制,因为它的事件永远都不会被存储。这样我们就可以在处理 Product 上下文时将它视为正常的有状态应用程序,无需加入事件溯源,也就避免其复杂性。


第二:会被事件溯源的不只是 Order 上下文,而且所有这些上下文都能单独侦听 Product 上下文中触发的相关事件。


第三:我们不必存储原始 Product 事件的完整副本,因为每个上下文都可以挑选和存储与自己用例相关的数据。

数据迁移问题呢?

一个新问题出现了。


假设这套系统已经投入生产一年之久,我们决定添加一个新的事件溯源上下文;它还需要有关 Product 上下文的信息。由于上面列出的原因,原始的 Product 事件没有被存储下来——那么我们如何为新的上下文建立初始状态?


答案是这样的:在部署时,我们必须读取所有产品数据,并根据现有产品将相关事件发送到新添加的上下文中。这种一次性迁移的麻烦是额外的成本,但它让我们可以自由地处理 Product 上下文,而不必担心外部环境。对于这个项目,这是值得付出的代价。

最终整合

最后,通过使用只读模型,我们就能使用从所有上下文收集的应用程序数据。到目前为止,我们的约定依旧要求这些模型是只读的;而将来可能会改变这个约定。



从应用程序到 Product 上下文,就像普通的有状态应用程序一样通信即可。应用程序和事件溯源的上下文(例如 Orders)之间是通过其聚合根通信的。


下面是最终成品的概述。这张图中还缺少一些箭头,但是上下文和应用程序之间与它们内部的相关流程画的应该足够清楚了。



解决我们问题的关键来自 DDD 的限界上下文思想。它们描述了我们代码库中的严格界限,我们不能随意跨越这些界限。当然这增加了一层复杂性,但它还让我们能够自由地以期望的方式构建每个上下文,而不必操心支持其他上下文的问题。


最后一个难题是仅依靠事件作为上下文之间的交流手段。它又增加了一层复杂性,但同时也是一种解耦和增加灵活性的方式。


第二部分则是讲述我们如何在一个 Laravel 项目中编写具体的代码。


英文原文:


Combining event sourcing and stateful systems


2020-06-02 14:271712
用户头像
王强 技术是文明进步的力量

发布了 752 篇内容, 共 335.4 次阅读, 收获喜欢 1677 次。

关注

评论

发布
暂无评论
发现更多内容

e家帮系统开发有哪些功能?

ALVIS

本夕生活小程序开发|快速搭建

ALVIS

商家入驻平台开发多少钱??

ALVIS

短短29天,应对高峰100W+访问,看浙大如何交出满分答卷

TakinTalks稳定性社区

哒哒家政小程序开发多少钱?

ALVIS

美牙视界软件开发讲解

(王经理)专业app小程序开发

美健日纪系统开发怎么做??

ALVIS

小羽佳家政小程序开发

ALVIS

美健日纪小程序开发定制

ALVIS

皇家家政小程序开发怎么做?

ALVIS

女巫面具APP开发是什么??

ALVIS

微商新零售小程序怎么开发??

ALVIS

绿袋环保小程序开发怎么样?

ALVIS

关键字transient的认识

阿志

序列化 java关键字

红色壹佰拼团小程序开发

(王经理)专业app小程序开发

[架构实战营]模块六作业

xyu

#架构实战营

劳务派遣公司管理小程序开发

(王经理)专业app小程序开发

微商新零售模式开发怎么做???

ALVIS

浪潮云说丨浪潮云IBP数据工场,打造行业数据基座

浪潮云

云计算

纷多多拼团小程序开发

ALVIS

织信Informat低代码平台,赋能企业数字化飞速升级,提效300%!

优秀

低代码 低代码平台

爱购团购软件开发

(王经理)专业app小程序开发

课程管理APP开发难吗?

ALVIS

益题库小程序找谁开发??

ALVIS

新零售商城怎么开发??

ALVIS

DockerHub再现百万下载量黑产镜像,小心你的容器被挖矿

腾讯安全云鼎实验室

容器 云原生 镜像

茶酒交易小程序开发难吗??

ALVIS

绿色篮子APP怎么开发??

ALVIS

图书馆预约小程序开发多少钱?

ALVIS

sql task6

橙橙橙橙汁丶

题库答题软件开发

(王经理)专业app小程序开发

  • 扫码添加小助手
    领取最新资料包
如何设计一款大型Laravel应用程序的架构(上)?_架构_Brent_InfoQ精选文章