krak/effects

在领域层内安全地管理副作用。

v0.3.1 2021-02-05 16:07 UTC

This package is auto-updated.

Last update: 2024-09-06 00:10:07 UTC


README

效果库是一组小型实用工具,帮助启用代码中的副作用,使用PHP生成器来转移所有权。

这有助于领域驱动设计和维护纯净的领域模型。

使用方法

<?php

use function Krak\Effects\{handleEffects, expect};

// Domain Entity
final class ShoppingCart
{
    public function checkOut(CheckOutShoppingCart $checkOutShoppingCart) {
        // ... build up captueCharge command
        $capturedCharge = expect(CapturedCharge::class, yield new CaptureCharge(/* args */));
    }
}

// Domain Commands/Effects
final class CheckOutShoppingCart {}
final class CaptureCharge {}
final class CapturedCharge {}

// Application Command Handler
final class HandleCheckOutShoppingCart
{
    public function __invoke(CheckOutShoppingCart $checkOutShoppingCart): void {
        $shoppingCart = $this->shoppingCarts->get($checkOutShoppingCart->shoppingCart());
        handleEffects($shoppingCart->checkOut($checkOutShoppingCart), [
            CaptureCharge::class => function(CaptureCharge $captureCharge) {
                return $this->paymentGateway->capture($captureCharge); // returns a CapturedCharge instance
            }
        ]);
    }
}

工作原理

这是通过利用PHP生成器允许将值发送回产生结果的事实来工作的。`handleEffects` 函数只是简单地迭代领域方法,拉取所有命令,将它们传递给命令处理映射,然后将响应发送回领域方法。

期望函数只是一个安全辅助工具,提供类型自动完成,并在出现映射错误时断言期望的类,以便使调试更加愉快。技术上这不是必需的,所以如果你不关心psalm和PHPStorm的自动完成帮助,那么请随意使用`yield`关键字而不使用`expect`函数。

使用yield from嵌套效果

如果你需要一个方法引发几个效果,那么可能有必要有专门的方法来管理和引发这些效果。

你可以使用`yield from`语句从子方法引发效果。以下是一个例子

final class Product
{
    public function checkout() {
        yield from $this->raiseEffects();
    }
    
    private function raiseEffects() {
        $result = yield new Effect1();
    }
}

Prewk\Result集成

如果你正在处理更复杂的领域方法/服务,将代码的各个部分结构化成独立的函数,这些函数返回结果并可以短路操作(就像使用正常的Result类一样),这可能是有帮助的。

让我们看看如何使用MapEffectResults类来实现这一点。

use Prewk\Result;
use Krak\Effects\Bridge\Result\MapEffectResults;
use function Krak\Effects\expect;

final class Product
{
    public function syncInventory() {
        expect(Result::class, yield from MapEffectResults::map(
            $this->fetchInventoryFromERP(),
            $this->fetchPricingRules(),
            $this->pushInventoryToThirdParty()
        ))->mapErr(function() {
            // set some error state maybe.
        })->map(function() {
            // set some success state maybe.
        });
    }
    
    public function fetchInventoryFromERP(){
        return function() {
            return expect(Result::class, yield new FetchInventoryFromERP($this->productId));
        };
    }
    
    public function fetchPricingRules(){
        return function(InventoryFromERP $inventoryFromERP) {
            return expect(Result::class, yield new FetchPricingRulesForProduct($this->productId))
                ->map(function(PricingRules $pricingRules) use ($inventoryFromERP) {
                    return [$inventoryFromERP, $pricingRules];
                });
        };
    }
    
    public function pushInventoryToThirdParty() {
        return function(array $tup) {
            [$inventoryFromERP, $pricingRules] = $tup;
            // calculate final inventory using special logic
            return expect(Result::class, yield new PushInventoryToThirdParty($finalInventory));
        };
    }
}

// in some application service
\Krak\Effects\handleEffects($product->syncInventory(), []); // with handlers accordingly

安装

使用composer在krak/effects中安装

灵感

这种设计受到了Elm语言设计的影响,即在保持纯应用代码的同时,将副作用留给运行时管理。

以下是一些关于领域模型纯净性和副作用的其它有帮助的资源