
在领域驱动设计和事件溯源的背景下,聚合根(aggregate root)是业务不变性规则(invariants)的守护者。它确保任何状态变更都符合预设的业务逻辑。然而,在实际应用中,尤其当聚合根需要响应外部数据源的更新时,如何高效且不重复地处理这些不变性规则,常常成为一个难题。
考虑以下一个 ProductAggregateRoot 的 changePrice 方法:
class ProductAggregateRoot
{
private $price;
private $availability;
// ... 构造函数和状态恢复方法 ...
public function changePrice(ChangeProductPrice $command): self
{
// 不变性规则1: 产品不可用时不能改变价格
if ($this->availability->equals(Availability::UNAVAILABLE())) {
throw CannotChangePriceException::unavailableProduct();
}
// 不变性规则2: 价格未发生变化时无需改变
if ($this->price->equals($command->newPrice)) {
throw CannotChangePriceException::priceHasntChanged();
}
$this->recordThat(
new ProductPriceChanged($this->price, $command->newPrice)
);
return $this;
}
// ... 其他方法 ...
}当我们需要从外部源同步产品数据,可能同时更新价格和可用性时,直接调用上述方法会面临挑战。例如,在一个领域服务中,为了避免重复检查或处理异常,可能会出现如下“尝试-捕获”模式:
class ProductSynchronizationService
{
public function synchronizeProduct(ProductId $productId, ExternalProductState $state): void
{
$aggregate = $this->productRepository->get($productId);
try {
$aggregate->changePrice(new ChangeProductPrice(
$productId,
$state->getPrice()
));
} catch (CannotChangePriceException $ex) {
// 忽略或记录异常,感觉“不自然”
}
try {
// 假设也有一个 changeAvailability 方法
$aggregate->changeAvailability(new ChangeProductAvailability(
$productId,
$state->getAvailability()
));
} catch (CannotChangeAvailabilityException $ex) {
// 同样处理,感觉“不自然”
}
$this->productRepository->save($aggregate);
}
}这种模式虽然能工作,但显得笨拙且不够优雅。它强制调用者预知并处理聚合根内部的细节,并且在某些情况下,如价格未变时抛出异常,可能并非业务的真实意图。
解决上述问题的一个核心思路是,将相关的操作封装到一个更具业务意图的命令中。当从外部源同步数据时,我们通常希望一次性更新产品的多个属性,而不是分别处理。因此,可以设计一个反映这种整体性操作的命令和聚合根方法。
例如,我们可以创建一个 UpdateProductDetailsFromExternalSource 命令,并相应地在聚合根中实现一个方法来处理它:
// 定义一个更具业务意图的命令
class UpdateProductDetailsFromExternalSource
{
public ProductId $productId;
public Money $newPrice;
public Availability $newAvailability;
public function __construct(ProductId $productId, Money $newPrice, Availability $newAvailability)
{
$this->productId = $productId;
$this->newPrice = $newPrice;
$this->newAvailability = $newAvailability;
}
}
class ProductAggregateRoot
{
private $price;
private $availability;
// ...
public function updateDetailsFromExternalSource(UpdateProductDetailsFromExternalSource $command): self
{
$priceChanged = !$this->price->equals($command->newPrice);
$availabilityChanged = !$this->availability->equals($command->newAvailability);
// 在这里进行更宏观的不变性检查
// 例如:如果产品不可用,但外部源要求将其设置为可用,则允许
// 如果外部源要求将价格设置为某个值,即使当前不可用,也可能允许,
// 但如果只是更新价格,且产品不可用,则可能抛出异常。
// 这里的逻辑需要根据具体的业务规则来定。
if ($priceChanged) {
// 可以在这里添加针对价格更新的特定不变性,例如:
// if ($command->newPrice->isNegative()) { throw InvalidPriceException(); }
$this->recordThat(new ProductPriceChanged($this->price, $command->newPrice));
}
if ($availabilityChanged) {
$this->recordThat(new ProductAvailabilityChanged($this->availability, $command->newAvailability));
}
// 如果没有任何变化,则不发布任何事件,也不抛出异常
// 这符合幂等性原则,避免了不必要的异常捕获
return $this;
}
// ...
}通过这种方式,领域服务可以更简洁地调用聚合根:
class ProductSynchronizationService
{
public function synchronizeProduct(ProductId $productId, ExternalProductState $state): void
{
$aggregate = $this->productRepository->get($productId);
$aggregate->updateDetailsFromExternalSource(new UpdateProductDetailsFromExternalSource(
$productId,
$state->getPrice(),
$state->getAvailability()
));
$this->productRepository->save($aggregate);
}
}这种方法的好处在于:
在聚合根中,将“尝试将状态设置为当前值”视为错误,往往会给调用者带来不必要的负担。命令的本质是表达一种“期望”或“意图”,即希望聚合根达到某个状态。如果聚合根已经处于该状态,那么满足这个期望的最佳方式是不做任何改变,而不是抛出异常。这符合幂等性原则,即多次执行相同操作产生相同结果(或不改变状态)。
我们可以修改 changePrice 方法,使其在价格未实际改变时,不抛出异常,而是直接返回聚合根实例:
class ProductAggregateRoot
{
private $price;
private $availability;
// ...
public function changePrice(ChangeProductPrice $command): self
{
// 不变性规则1: 产品不可用时不能改变价格
// 这是一个硬性业务规则,若违反则抛出异常
if ($this->availability->equals(Availability::UNAVAILABLE())) {
throw CannotChangePriceException::unavailableProduct();
}
// 策略二应用:如果价格未发生变化,则不抛出异常,直接返回
if ($this->price->equals($command->newPrice)) {
return $this; // 视为幂等操作,不发布事件
}
$this->recordThat(
new ProductPriceChanged($this->price, $command->newPrice)
);
return $this;
}
// ...
}通过这种调整,调用者无需预先检查当前价格,也无需捕获“价格未变”的异常。这使得客户端代码更加简洁和健壮。
在事件溯源和聚合根的实践中,优雅地处理业务不变性规则是构建健壮系统的关键。通过以下策略,我们可以避免冗余检查和不必要的异常处理:
遵循这些原则,可以使我们的领域模型更清晰、更易于维护,并更好地表达业务逻辑。
以上就是事件溯源与聚合根:高效处理业务不变性规则的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号