当前位置: 首页 > 后端技术 > PHP

PHP事件溯源

时间:2023-03-29 14:59:25 PHP

本文转载自【WhyDecoupling】:https://codedecoupled.com/php...EventSourcing是领域驱动设计(DomainDrivenDesign)设计思想中的架构模式之一。领域驱动设计是一种面向业务的建模方法。它帮助开发人员构建更贴近业务的模型。在传统应用中,我们将状态存储在数据库中,当状态发生变化时,我们立即更新数据库中相应的状态值。事件溯源采用完全不同的模型。它的核心是事件,所有的状态都来自于事件。我们通过播放事件来获取应用程序的状态,因此称为事件溯源。在本文中,我们将使用事件溯源模式编写一个简化的购物车来分解事件溯源的几个重要组件概念。我们还将使用Spatie的事件源库来避免重新发明轮子。在我们的例子中,用户可以添加、删除和查看购物车的内容,它有两个业务逻辑:购物车不能添加超过3个产品。当用户添加第四个产品时,系统会自动发送提醒邮件。要求和免责声明本文使用Laravel框架。本文使用特定版本spatie/laravel-event-sourcing:4.9.0来避免不同版本之间的语法问题。本文不是分步教程。你必须有一定的Laravel基础才能看懂本文。请避免文字崇拜,专注于架构模式的构成。这篇文章的重点是解释事件溯源的核心思想,而事件溯源在这个库中的实现并不是唯一的解决方案。领域事件(DomainEvent)事件追踪中的事件称为领域事件。与传统的事务事件不同,它们具有以下特点:与业务密切相关,因此其名称中往往包含业务术语,不应与数据库挂钩。例如,如果将产品添加到购物车,则相应的领域事??件应该是ProductAddedToCart,而不是CartUpdated。它指的是发生的事情,所以它必须是过去时,例如ProductAddedToCart而不是ProductAddToCart。域事件只能追加,不能删除或更改。如果需要删除,我们需要使用带有删除效果的领域事件,比如ProductRemovedFromCart。基于以上信息,我们构建了三个领域事件:公共诠释$金额;公共函数__construct(int$productId,int$amount){$this->productId=$productId;$this->金额=$金额;}}ProductRemovedFromCart:productId=$productId;}}CartCapacityExceeded:currentProducts=$currentProducts;}}EventsProductAddedToCart和ProductRemovedFromCart分别代表产品加入购物车和从购物车中移除,事件CartCapacityExceeded表示购物车中的商品超出标准。这是我们前面提到的业务逻辑之一。聚合在领域驱动设计中,聚合是指一组密切相关的类,它们自己形成一个边界。边界外的对象只能通过聚合根(AggregateRoot)与这个聚合进行交互,聚合根是聚合中的一个特殊类。我们可以把聚合想象成一个家庭账本,对这个账本的任何操作都必须经过户主(聚合根)。聚合有几个特点:保证核心业务的不变性。也就是说,我们在做聚合校验,对违反业务逻辑的操作抛出异常。它是产生域事件的地方。域事件在聚合根中生成。也就是说,我们可以在领域事件中完成业务需求。它是自包含的,边界清晰,即聚合中的方法只能通过聚合根调用。聚合是服务业务逻辑的主要和最直接的部分,我们用它来可视化我们的业务模型。总而言之,让我们构建一个CartAggregateRoot聚合根:}}CartAggregateRoot有addItem和removeItem两个方法,分别代表添加和删除商品。另外,我们还需要添加一些属性来记录购物车的内容:publicfunctionaddItem(int$productId,int$amount){}publicfunctionremoveItem(int$productId){}}privatearray$products;会记录购物车里的商品,那么什么时候可以给它赋值呢?在事件溯源中,这是在事件发生之后,所以我们首先需要发布领域事件:publicfunctionaddItem(int$productId,int$amount){$this->recordThat(newProductAddedToCart($productId,$amount));}publicfunctionremoveItem(int$productId){$this->recordThat(newProductRemovedFromCart($productId));}}在调用addItem和removeItem事件,我们分别发布ProductAddedToCart和ProductRemovedFromCart事件,同时我们通过applymagic方法给$products赋值:recordThat(newProductAddedToCart($productId,$amount));}publicfunctionremoveItem($productId){$this->recordThat(newProductRemovedFromCart($productId));}publicfunctionapplyProductAddedToCart(ProductAddedToCart$event){$this->products[]=$event->productId;}publicfunctionapplyProductRemovedFromCart(ProductRemoved$From){$this->products[]=array_filter($this->products,function($productId)use($event){return$productId!==$event->productId;});}}apply*是Spatie事件溯源库自带的神奇方法。当我们使用recordThat发布事件时,apply*会自动被调用。它确保状态更改发生在事件发布之后。现在CartAggregateRoot已经通过事件获得了需要的状态,我们现在可以添加第一个业务逻辑:购物车最多可以添加3个产品。修改CartAggregateRoot::addItem,当用户添加第四个商品时,释放相关领域事件CartCapacityExceeded:publicfunctionaddItem(int$productId,int$amount){if(count($this->products)>=3){$this->recordThat(newCartCapacityExceeded($this->products));返回;}$this->recordThat(newProductAddedToCart($productId,$amount));}现在聚合根工作了,虽然代码很简单,但是基于模拟业务建立的模型非常直观。添加项目时,我们调用:CartAggregateRoot::retrieve(Uuid::uuid4())->addItem(1,100);添加项目时,我们调用:CartAggregateRoot::retrieve($uuid)->removeItem(1);ProjectorUI界面是应用不可或缺的一部分,比如向用户显示购物车的内容,重放聚合根可能会出现性能问题。此时我们就可以使用投影仪(Projector)了。投影仪实时监控领域事件,通过它我们可以创建服务于UI的数据库表。projector的特点是可以reshape,当我们在代码中发现影响UI数据的bug时,我们可以reshape这个projector构建的form。让我们写一个为用户服务的CartProjector:$projection->product_id=$event->productId;$projection->saveOrFail();}publicfunctiononProductRemovedFromCart(ProductRemovedFromCart$event){ProjectionCart::where('product_id',$event->productId)->delete();}}ProjectorCartProjector将根据监听的事件添加或删除表单projection_carts。ProjectionCart是一个常见的Laravel模型,我们只是用它来操作数据库。当我们的UI需要显示购物车的内容时,我们从projection_carts中读取数据,类似于读写分离。ReactorReactor就像一个投影仪,实时监控领域事件。不同之处在于Reactor无法重塑。它的目的是执行有副作用的操作,所以它不能被重塑。我们用它来实现我们的第二个业务逻辑:当用户添加第4个产品时,系统会自动发送一封提醒邮件。发送(newCartWarning());}}反应机WarningReactor将监听CartCapacityExceeded事件,我们将使用LaravelMailable发送警报邮件。总结到目前为止,我们已经简要介绍了事件可追溯性的几个组成部分。软件的初衷是用我们熟悉的编程语言来解决复杂的业务问题。为了解决真正的业务问题,大师们发明了面向对象编程(OOP),让我们可以避免写面条代码,构建最真实的模型。但不知为何,ORM的出现使得大部分开发者的模型停留在数据库层面。模型不应该是对数据库表的封装,而是对业务的封装。面向对象编程给我们的是更准确地建模业务对象的能力。数据库的设计和数据的操作不是软件的核心,而是业务。在软件设计之初,我们应该忘掉数据库设计,专注于业务。本文转载自【WhyDecoupled】:https://codedecoupled.com/php...,如果你也对TDD、DDD和简洁代码感兴趣,请关注公众号【WhyDecoupled】,一起探讨一起软件开发的方式。