You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

整洁架构/DDD:PHP应用中复杂值对象的最佳实践探讨

整洁架构下PHP复杂值对象(如Money)的最佳实践

针对你提到的四种方案的痛点,推荐**「领域接口定义+第三方库轻量适配」**的方案,既能复用成熟库的实现,又能保持领域层的独立性,同时避免过度设计。

核心思路

整洁架构的核心要求是领域层不依赖任何外部实现,但完全自行实现复杂值对象会重复造轮子且容易遗漏边界场景(比如货币不匹配处理、精度计算)。通过「领域定义行为契约,基础设施层适配第三方库」的方式,能在两者间找到平衡:

  • 领域层只定义业务需要的行为接口,不涉及具体实现
  • 基础设施层用第三方库实现该接口,仅做转换逻辑,不添加额外业务规则
  • 依赖注入保证领域层只依赖接口,而非第三方库

具体实现步骤(以Money为例)

1. 领域层定义Money值对象接口

只保留业务必需的方法,避免过度抽象:

namespace App\Domain\ValueObject;

interface Money
{
    // 从金额和货币创建实例
    public static function fromAmount(float $amount, string $currency): self;
    // 金额加法
    public function add(Money $other): self;
    // 金额减法
    public function subtract(Money $other): self;
    // 金额乘法
    public function multiply(float $factor): self;
    // 获取金额
    public function getAmount(): float;
    // 获取货币代码
    public function getCurrency(): string;
    // 相等性判断
    public function equals(Money $other): bool;
}

2. 基础设施层实现适配器

封装第三方库(如brick/money),仅做转换:

namespace App\Infrastructure\ValueObject;

use App\Domain\ValueObject\Money as DomainMoney;
use Brick\Money\Money as BrickMoney;

class BrickMoneyAdapter implements DomainMoney
{
    private BrickMoney $money;

    private function __construct(BrickMoney $money)
    {
        $this->money = $money;
    }

    public static function fromAmount(float $amount, string $currency): self
    {
        return new self(BrickMoney::of($amount, $currency));
    }

    public function add(DomainMoney $other): self
    {
        if (!$other instanceof self) {
            throw new \InvalidArgumentException('仅支持同类型Money实例相加');
        }
        return new self($this->money->plus($other->money));
    }

    public function subtract(DomainMoney $other): self
    {
        if (!$other instanceof self) {
            throw new \InvalidArgumentException('仅支持同类型Money实例相减');
        }
        return new self($this->money->minus($other->money));
    }

    public function multiply(float $factor): self
    {
        return new self($this->money->multipliedBy($factor));
    }

    public function getAmount(): float
    {
        return $this->money->getAmount()->toFloat();
    }

    public function getCurrency(): string
    {
        return $this->money->getCurrency()->getCode();
    }

    public function equals(DomainMoney $other): bool
    {
        if (!$other instanceof self) {
            return false;
        }
        return $this->money->isEqualTo($other->money);
    }
}

3. 领域层依赖接口而非具体实现

在领域服务或实体中,直接使用领域接口:

namespace App\Domain\Service;

use App\Domain\ValueObject\Money;

class OrderTotalCalculator
{
    public function calculate(Money $subtotal, Money $tax): Money
    {
        return $subtotal->add($tax);
    }
}

方案优势对比

  • 避免重复造轮子:复用第三方库成熟的边界处理(如货币不匹配抛出异常、高精度计算)
  • 不污染领域层:领域层仅含接口,无任何第三方依赖,符合整洁架构的依赖规则
  • 无冗余抽象:接口仅定义业务必需方法,适配器逻辑极简,避免过度设计
  • 保持充血模型:值对象的行为和数据绑定,不会变成贫血模型导致业务规则分散

通用扩展(适用于其他复杂值对象)

对于其他具备复杂行为的值对象(如DateRange、Email),可遵循相同逻辑:

  1. 在领域层定义仅包含业务必需行为的接口
  2. 在基础设施层用成熟库或自定义实现适配该接口
  3. 领域层通过依赖注入使用接口,隔离外部实现细节

内容的提问来源于stack exchange,提问作者Yurii Orlyk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 23:37:27