이벤트 소싱은 도메인 중심 디자인 사고의 아키텍처 패턴 중 하나입니다. 도메인 중심 설계는 비즈니스 중심 모델링 접근 방식입니다. 이는 개발자가 비즈니스에 더 가까운 모델을 구축하는 데 도움이 됩니다. 오늘은 PHP의 이벤트 소싱에 대해 이야기하겠습니다.
이벤트 소싱은 도메인 중심 디자인 디자인 철학의 아키텍처 패턴 중 하나입니다. 도메인 중심 설계는 비즈니스 중심 모델링 접근 방식입니다. 이는 개발자가 비즈니스에 더 가까운 모델을 구축하는 데 도움이 됩니다.
기존 애플리케이션에서는 상태가 변경되면 데이터베이스에서 해당 상태 값을 즉시 업데이트합니다. 이벤트 소싱은 완전히 다른 모델을 채택하며, 그 핵심은 이벤트이며, 모든 상태는 이벤트를 재생하여 애플리케이션의 상태를 가져오므로 이를 이벤트 소싱이라고 합니다.
이 기사에서는 이벤트 소싱 모델을 사용하여 간단한 장바구니를 작성하여 이벤트 소싱의 몇 가지 중요한 개념을 분석하겠습니다. 또한 바퀴를 재발명하는 것을 피하기 위해 Spatie의 이벤트 소싱 라이브러리를 사용할 것입니다.
우리의 경우 사용자는 장바구니의 내용을 추가, 삭제 및 볼 수 있으며 두 가지 비즈니스 논리가 있습니다.
장바구니에는 3개 이상의 제품을 추가할 수 없습니다. 사용자가 4번째 제품을 추가하면 시스템에서 자동으로 알림 이메일을 보냅니다.
요구 사항 및 면책 조항
이 문서에서는 Laravel 프레임워크를 사용합니다. 이 문서에서는 특정 버전 spatie/laravel-event-sourcing:4.9.0을 사용하여 서로 다른 버전 간의 구문 문제를 방지합니다. 이 글은 단계별 튜토리얼이 아닙니다. 이 글을 이해하려면 Laravel에 대한 특정 기본 지식이 있어야 합니다. 장황한 말을 피하고 아키텍처 패턴의 구조에 집중하세요. 이 기사의 초점은 이벤트 소싱의 핵심 아이디어를 자세히 설명하는 것입니다. 이 라이브러리에서 이벤트 소싱을 구현하는 것이 유일한 솔루션은 아닙니다.
도메인 이벤트
이벤트 소싱의 이벤트를 도메인 이벤트라고 합니다. 기존의 거래 이벤트와는 달리 다음과 같은 특징이 있습니다.
비즈니스와 밀접한 관련이 있으므로 네이밍이 비즈니스 용어와 관련되어 있는 경우가 많습니다. 데이터베이스에 연결되었습니다. 예를 들어 장바구니에 제품을 추가할 때 해당 도메인 이벤트는 CartUpdated가 아니라 ProductAddedToCart여야 합니다. 발생한 일을 나타내므로 ProductAddToCart 대신 ProductAddedToCart와 같이 과거형이어야 합니다. 도메인 이벤트는 추가만 가능하며 삭제하거나 변경할 수 없습니다. 삭제해야 하는 경우 ProductRemovedFromCart와 같은 삭제 효과가 있는 도메인 이벤트를 사용해야 합니다.
위 정보를 기반으로 세 가지 필드 이벤트를 구성합니다.
ProductAddedToCart:
<?php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class ProductAddedToCart extends ShouldBeStored { public int $productId; public int $amount; public function __construct(int $productId, int $amount) { $this->productId = $productId; $this->amount = $amount; } }
ProductRemovedFromCart:
<?php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class ProductRemovedFromCart extends ShouldBeStored { public int $productId; public function __construct(int $productId) { $this->productId = $productId; } }
CartCapacityExceeded:
<?php use Spatie\EventSourcing\StoredEvents\ShouldBeStored; class CartCapacityExceeded extends ShouldBeStored { public array $currentProducts; public function __construct(array $currentProducts) { $this->currentProducts = $currentProducts; } }
이벤트 ProductAddedToCart 및 ProductRemovedFromCart는 각각 추가된 제품을 나타냅니다. 장바구니가 장바구니에서 제거되었습니다. CartCapacityExceeded 이벤트는 장바구니에 있는 항목이 앞서 언급한 비즈니스 논리 중 하나인 제한을 초과했음을 나타냅니다.
Aggregate
도메인 중심 설계에서 Aggregate는 밀접하게 관련된 클래스 그룹을 의미하며 경계 외부의 개체는 Aggregate Root(집합 루트)를 통해서만 액세스할 수 있습니다. 이 집계와 상호 작용하는 경우 집계 루트는 집계의 특수 클래스입니다. 집계를 가족 가계부로 상상할 수 있습니다. 이 가계부에 대한 모든 작업은 세대주(집계 루트)를 거쳐야 합니다.
Aggregation에는 다음과 같은 특징이 있습니다.
핵심 비즈니스의 불변성을 보장합니다. 즉, 집계로 검증을 수행하고 비즈니스 로직을 위반하는 작업에 대해 예외를 발생시킵니다. 도메인 이벤트가 발생하는 곳입니다. 도메인 이벤트는 집계 루트에서 생성됩니다. 즉, 도메인 이벤트에서 비즈니스 요구 사항을 완료할 수 있습니다. 이는 독립적이며 명확한 경계를 가지고 있습니다. 즉, 집계의 메서드는 집계 루트를 통해서만 호출할 수 있습니다.
Aggregation은 비즈니스 로직을 제공하는 주요하고 가장 직접적인 부분으로 비즈니스를 직관적으로 모델링하는 데 사용됩니다.
요약하자면 CartAggregateRoot 집계 루트를 구축해 보겠습니다.
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { public function addItem(int $productId, int $amount) { } public function removeItem(int $productId) { } }
CartAggregateRoot에는 각각 제품 추가 및 제거를 나타내는 addItem 및 RemoveItem 두 가지 메서드가 있습니다.
또한 장바구니의 내용을 기록하려면 몇 가지 속성을 추가해야 합니다.
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { private array $products; public function addItem(int $productId, int $amount) { } public function removeItem(int $productId) { } }
private array $products는 장바구니에 있는 제품을 기록하므로 언제 값을 할당할 수 있나요? 이벤트 소싱에서는 이벤트가 발생한 후이므로 먼저 도메인 이벤트를 게시해야 합니다.
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { private array $products; public function addItem(int $productId, int $amount) { $this->recordThat( new ProductAddedToCart($productId, $amount) ); } public function removeItem(int $productId) { $this->recordThat( new ProductRemovedFromCart($productId) ); } }
addItem 및 RemoveItem 이벤트를 호출할 때 각각 ProductAddedToCart 및 ProductRemovedFromCart 이벤트를 게시하는 동시에 적용을 전달합니다. $ 제품 할당에 대한 매직 메소드:
<?php use Spatie\EventSourcing\AggregateRoots\AggregateRoot; class CartAggregateRoot extends AggregateRoot { private array $products; public function addItem(int $productId, int $amount) { $this->recordThat( new ProductAddedToCart($productId, $amount) ); } public function removeItem(int $productId) { $this->recordThat( new ProductRemovedFromCart($productId) ); } public function applyProductAddedToCart(ProductAddedToCart $event) { $this->products[] = $event->productId; } public function applyProductRemovedFromCart(ProductRemovedFromCart $event) { $this->products[] = array_filter($this->products, function ($productId) use ($event) { return $productId !== $event->productId; }); } }
apply*는 Spatie의 이벤트 소싱 라이브러리와 함께 제공되는 매직 메소드입니다. RecordThat을 사용하여 이벤트를 게시하면 이벤트 이후에 상태 변경이 자동으로 호출됩니다. 출판됩니다.
现在 CartAggregateRoot 已通过事件获取了需要的状态,现在我们可以加入第一条业务逻辑:购物车不可添加超过 3 种产品。
修改 CartAggregateRoot::addItem,当用户添加第 4 种产品时,发布相关领域事件 CartCapacityExceeded:
public function addItem(int $productId, int $amount) { if (count($this->products) >= 3) { $this->recordThat( new CartCapacityExceeded($this->products) ); return; } $this->recordThat( new ProductAddedToCart($productId, $amount) ); }
现在我们已经完成了聚合根工作,虽然代码很简单,但是根据模拟业务而建立的模型非常直观。
加入商品时,我们调用:
CartAggregateRoot::retrieve(Uuid::uuid4())->addItem(1, 100);
加入商品时,我们调用:
CartAggregateRoot::retrieve($uuid)->removeItem(1);
放映机(Projector)
UI 界面是应用中不可缺少的部分,比如向用户展示购物车中的内容,通过重播聚合根或许会有性能问题。此时我们可以使用放映机(Projector)。
放映机实时监控领域事件,我们通过它可以建立服务于 UI 的数据库表。放映机的特点是它可以重塑,当我们发现代码中的 bug 影响到 UI 数据时,我们可以重塑此放映机建立的表单。
让我们写一个服务于用户的放映机 CartProjector:
<?php use Spatie\EventSourcing\EventHandlers\Projectors\Projector; class CartProjector extends Projector { public function onProductAddedToCart(ProductAddedToCart $event) { $projection = new ProjectionCart(); $projection->product_id = $event->productId; $projection->saveOrFail(); } public function onProductRemovedFromCart(ProductRemovedFromCart $event) { ProjectionCart::where('product_id', $event->productId)->delete(); } }
放映机 CartProjector
会根据监听的事件来增加或者删除表单 projection_carts,ProjectionCart 是一个普通的 Laravel 模型,我们仅使用它来操作数据库。
当我们的 UI 需要展示购物车中的内容时,我们从 projection_carts 读取数据,这和读写分离有异曲同工之妙。
反应机(Reactor)
反应机(Reactor)和放映机一样,实时监控领域事件。不同的是反应机不可以重塑,它的用途是用来执行带有副作用的操作,所以它不可以重塑。
我们使用它来实现我们的第二个业务逻辑:当用户添加第 4 个产品时,系统将自动发出一个预警邮件。
<?php use Spatie\EventSourcing\EventHandlers\Reactors\Reactor; class WarningReactor extends Reactor { public function onCartCapacityExceeded(CartCapacityExceeded $event) { Mail::to('admin@corporation.com')->send(new CartWarning()); } }
反应机 WarningReactor
会监听到事件 CartCapacityExceeded, 我们就会使用 Laravel Mailable 发送一封警报邮件。
总结
至此我们简单的介绍了事件溯源的几个组成部分。软件的初衷是运用我们熟悉的编程语言来解决复杂的业务问题。为了解决现实中的业务问题,大神们发明了面向对象编程(OOP),于是我们可以避免写出面条代码,可以建立最贴近现实的模型。但是由于某种原因, ORM 的出现让大多数开发者的模型停留在了数据库层面,模型不应该是对数据库表的封装,而是对业务的封装。面向对象编程赋予我们的是对业务对象更精确的建模能力。数据库的设计,数据的操作并不是软件关注的核心,业务才是。
在软件设计之初,我们应该忘记数据库设计,将注意力放到业务上面。
推荐学习:php视频教程
위 내용은 PHP의 이벤트 소싱에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!