Skip to content

依赖方向规则

清晰的依赖方向是架构稳定性的基础


目录


依赖方向图

┌─────────────────────────────────────────────────────────┐
│                      Controller Layer                    │
│                   (HTTP Input/Output)                    │
└────────────────────────┬────────────────────────────────┘
                         │ depends on

┌─────────────────────────────────────────────────────────┐
│                      Service Layer                       │
│                  (Use Case Orchestration)                │
└────────┬────────────────────────────────────────────────┘
         │ depends on

┌────────────────────────┐        ┌─────────────────────┐
│    Domain Layer        │←───────│  Contract Layer     │
│  (Business Logic)      │ impl   │  (Interfaces)       │
└────────────────────────┘        └──────────┬──────────┘
         ↑                                    ↑
         │ depends on                         │ implements
         │                                    │
┌────────────────────────────────────────────┴────────────┐
│                 Infrastructure Layer                     │
│          (Repository, Gateway, External Services)        │
└──────────────────────────────────────────────────────────┘

允许的依赖

Controller → Service

允许:Controller 可以依赖 Service 层

php
<?php

declare(strict_types=1);

namespace app\controller\api\v1;

use app\service\order\CreateOrderService;
use support\Request;
use support\Response;

final class OrderController
{
    public function __construct(
        private readonly CreateOrderService $createOrderService
    ) {
    }

    public function create(Request $request): Response
    {
        $order = $this->createOrderService->handle(
            userId: $request->user()->id,
            items: $request->post('items'),
            shippingAddress: $request->post('shipping_address')
        );

        return json(['data' => $order]);
    }
}

Service → Domain + Contract

允许:Service 可以依赖 Domain 实体和 Contract 接口

php
<?php

declare(strict_types=1);

namespace app\service\order;

use app\contract\repository\OrderRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;
use support\Db;

final class CreateOrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly PaymentGatewayInterface $paymentGateway
    ) {
    }

    public function handle(int $userId, array $items, array $shippingAddress): Order
    {
        return Db::transaction(function () use ($userId, $items, $shippingAddress) {
            $order = Order::create($userId, $items, $shippingAddress);
            $this->orderRepository->save($order);
            $this->paymentGateway->createPaymentIntent($order);
            return $order;
        });
    }
}

Infrastructure → Contract + Domain

允许:Infrastructure 实现 Contract 接口,可以依赖 Domain 实体

php
<?php

declare(strict_types=1);

namespace app\infrastructure\repository\eloquent;

use app\contract\repository\OrderRepositoryInterface;
use app\domain\order\entity\Order;
use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use app\model\eloquent\Order as OrderModel;

final class EloquentOrderRepository implements OrderRepositoryInterface
{
    public function findById(int $id): ?Order
    {
        $model = OrderModel::find($id);
        return $model ? $this->toDomain($model) : null;
    }

    public function save(Order $order): void
    {
        $model = OrderModel::findOrNew($order->id());
        $model->user_id = $order->userId();
        $model->total_amount = $order->totalAmount()->toDollars();
        $model->status = $order->status()->value;
        $model->save();
    }

    private function toDomain(OrderModel $model): Order
    {
        return Order::reconstitute(
            id: $model->id,
            userId: $model->user_id,
            totalAmount: Money::fromDollars($model->total_amount),
            status: OrderStatus::from($model->status),
            createdAt: new \DateTimeImmutable($model->created_at)
        );
    }
}

Domain → Domain (Same Context)

允许:同一限界上下文内的 Domain 对象可以相互依赖

php
<?php

declare(strict_types=1);

namespace app\domain\order\entity;

use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;

final class Order
{
    private function __construct(
        private readonly int $id,
        private Money $totalAmount,
        private OrderStatus $status
    ) {
    }

    public function calculateTotal(array $items): void
    {
        $total = array_reduce(
            $items,
            fn (Money $carry, array $item) => $carry->add(
                Money::fromCents($item['price'] * $item['quantity'])
            ),
            Money::zero()
        );

        $this->totalAmount = $total;
    }
}

禁止的依赖

Domain → Framework

禁止:Domain 层不能依赖框架类

php
<?php

// 错误示例 - Domain 依赖框架
namespace app\domain\order\entity;

use support\Db;  // 不能依赖 Webman 框架
use support\Redis;  // 不能依赖框架

final class Order
{
    public function save(): void
    {
        // Domain 不应该直接访问数据库
        Db::table('orders')->insert([...]);
    }
}
php
<?php

// 正确示例 - 通过 Repository 接口
namespace app\domain\order\entity;

final class Order
{
    // Domain 只包含业务逻辑,不负责持久化
    public function calculateTotal(): void
    {
        // 纯业务逻辑
    }
}

// 持久化由 Service 层调用 Repository 完成
namespace app\service\order;

use app\contract\repository\OrderRepositoryInterface;

final class CreateOrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository
    ) {
    }

    public function handle(): void
    {
        $order = Order::create(...);
        $this->orderRepository->save($order);  // 通过接口持久化
    }
}

Domain → Model

禁止:Domain 层不能依赖 ORM Model

php
<?php

// 错误示例
namespace app\domain\order\entity;

use app\model\eloquent\Order as OrderModel;  // 不能依赖 Model

final class Order
{
    public function toModel(): OrderModel
    {
        // Domain 不应该知道 ORM 的存在
        return new OrderModel([...]);
    }
}
php
<?php

// 正确示例 - 由 Repository 负责转换
namespace app\infrastructure\repository\eloquent;

use app\domain\order\entity\Order;
use app\model\eloquent\Order as OrderModel;

final class EloquentOrderRepository
{
    public function save(Order $order): void
    {
        // Repository 负责 Domain 到 Model 的转换
        $model = OrderModel::findOrNew($order->id());
        $model->user_id = $order->userId();
        $model->save();
    }
}

Domain → Infrastructure

禁止:Domain 层不能依赖 Infrastructure 实现

php
<?php

// 错误示例
namespace app\domain\order\entity;

use app\infrastructure\gateway\payment\StripePaymentGateway;

final class Order
{
    public function pay(): void
    {
        // Domain 不应该依赖具体实现
        $gateway = new StripePaymentGateway();
        $gateway->charge($this->totalAmount);
    }
}
php
<?php

// 正确示例 - 通过 Service 层调用
namespace app\service\order;

use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;

final class PayOrderService
{
    public function __construct(
        private readonly PaymentGatewayInterface $paymentGateway
    ) {
    }

    public function handle(Order $order): void
    {
        // Service 层调用外部服务
        $this->paymentGateway->charge($order->totalAmount());
    }
}

Controller → Model

禁止:Controller 不能直接访问 Model

php
<?php

// 错误示例
namespace app\controller\api\v1;

use app\model\eloquent\Order;
use support\Request;

final class OrderController
{
    public function index(Request $request): array
    {
        // Controller 不应该直接查询数据库
        return Order::where('user_id', $request->user()->id)->get();
    }
}
php
<?php

// 正确示例 - 通过 Service 层
namespace app\controller\api\v1;

use app\service\order\GetUserOrdersService;
use support\Request;

final class OrderController
{
    public function __construct(
        private readonly GetUserOrdersService $getUserOrdersService
    ) {
    }

    public function index(Request $request): array
    {
        // 通过 Service 层获取数据
        return $this->getUserOrdersService->handle($request->user()->id);
    }
}

Controller → Infrastructure

禁止:Controller 不能直接依赖 Infrastructure

php
<?php

// 错误示例
namespace app\controller\api\v1;

use app\infrastructure\repository\eloquent\EloquentOrderRepository;

final class OrderController
{
    public function __construct(
        private readonly EloquentOrderRepository $orderRepository
    ) {
    }
}
php
<?php

// 正确示例 - 依赖 Service
namespace app\controller\api\v1;

use app\service\order\CreateOrderService;

final class OrderController
{
    public function __construct(
        private readonly CreateOrderService $createOrderService
    ) {
    }
}

执行策略

1. PHPStan 规则

创建 phpstan.neon 配置文件:

neon
parameters:
    level: 8
    paths:
        - app

    # 禁止 Domain 层依赖框架
    ignoreErrors:
        -
            message: '#app\\domain\\.*# cannot depend on #support\\.*#'
            path: app/domain
        -
            message: '#app\\domain\\.*# cannot depend on #app\\model\\.*#'
            path: app/domain
        -
            message: '#app\\domain\\.*# cannot depend on #app\\infrastructure\\.*#'
            path: app/domain

    # 禁止 Controller 直接依赖 Model
    ignoreErrors:
        -
            message: '#app\\controller\\.*# cannot depend on #app\\model\\.*#'
            path: app/controller
        -
            message: '#app\\controller\\.*# cannot depend on #app\\infrastructure\\.*#'
            path: app/controller

运行检查:

bash
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse

2. Rector 自动重构

创建 rector.php 配置:

php
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/app',
    ])
    ->withRules([
        // 自动检测并报告违反依赖规则的代码
    ]);

3. Code Review Checklist

在 Pull Request 时检查:

  • [ ] Domain 层是否依赖了框架类?
  • [ ] Domain 层是否依赖了 Model?
  • [ ] Controller 是否直接访问 Model?
  • [ ] Controller 是否包含业务逻辑?
  • [ ] Service 层是否正确使用了接口?
  • [ ] Infrastructure 是否实现了 Contract 接口?

4. 单元测试验证

Domain 层应该可以独立测试,不需要启动框架:

php
<?php

declare(strict_types=1);

namespace tests\Unit\Domain\Order;

use app\domain\order\entity\Order;
use app\domain\order\values\Money;

test('order calculates total correctly', function () {
    $order = Order::create(
        userId: 1,
        items: [
            ['price' => 1000, 'quantity' => 2],
            ['price' => 500, 'quantity' => 1],
        ],
        shippingAddress: []
    );

    $order->calculateTotal();

    expect($order->totalAmount())->toEqual(Money::fromCents(2500));
});

代码示例

完整示例:订单创建流程

1. Controller 层

php
<?php

declare(strict_types=1);

namespace app\controller\api\v1;

use app\service\order\CreateOrderService;
use support\Request;
use support\Response;

final class OrderController
{
    public function __construct(
        private readonly CreateOrderService $createOrderService
    ) {
    }

    public function create(Request $request): Response
    {
        $validated = $this->validate($request, [
            'items' => 'required|array',
            'shipping_address' => 'required|array',
        ]);

        $order = $this->createOrderService->handle(
            userId: $request->user()->id,
            items: $validated['items'],
            shippingAddress: $validated['shipping_address']
        );

        return json(['data' => $order->toArray()]);
    }
}

2. Service 层

php
<?php

declare(strict_types=1);

namespace app\service\order;

use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\UserRepositoryInterface;
use app\domain\order\entity\Order;
use support\Db;

final class CreateOrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly UserRepositoryInterface $userRepository
    ) {
    }

    public function handle(int $userId, array $items, array $shippingAddress): Order
    {
        return Db::transaction(function () use ($userId, $items, $shippingAddress) {
            $user = $this->userRepository->findById($userId);

            $order = Order::create($userId, $items, $shippingAddress);
            $order->calculateTotal();
            $order->validateInventory();

            $this->orderRepository->save($order);

            return $order;
        });
    }
}

3. Domain 层

php
<?php

declare(strict_types=1);

namespace app\domain\order\entity;

use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use app\domain\order\exception\InvalidOrderException;

final class Order
{
    private function __construct(
        private readonly int $id,
        private readonly int $userId,
        private array $items,
        private Money $totalAmount,
        private OrderStatus $status
    ) {
    }

    public static function create(int $userId, array $items, array $shippingAddress): self
    {
        if (empty($items)) {
            throw new InvalidOrderException('Order must have at least one item');
        }

        return new self(
            id: 0,
            userId: $userId,
            items: $items,
            totalAmount: Money::zero(),
            status: OrderStatus::Pending
        );
    }

    public function calculateTotal(): void
    {
        $total = array_reduce(
            $this->items,
            fn (Money $carry, array $item) => $carry->add(
                Money::fromCents($item['price'] * $item['quantity'])
            ),
            Money::zero()
        );

        $this->totalAmount = $total;
    }

    public function validateInventory(): void
    {
        // 业务规则验证
    }

    public function id(): int
    {
        return $this->id;
    }

    public function userId(): int
    {
        return $this->userId;
    }

    public function totalAmount(): Money
    {
        return $this->totalAmount;
    }

    public function status(): OrderStatus
    {
        return $this->status;
    }
}

4. Contract 层

php
<?php

declare(strict_types=1);

namespace app\contract\repository;

use app\domain\order\entity\Order;

interface OrderRepositoryInterface
{
    public function findById(int $id): ?Order;

    public function save(Order $order): void;

    public function delete(Order $order): void;
}

5. Infrastructure 层

php
<?php

declare(strict_types=1);

namespace app\infrastructure\repository\eloquent;

use app\contract\repository\OrderRepositoryInterface;
use app\domain\order\entity\Order;
use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use app\model\eloquent\Order as OrderModel;

final class EloquentOrderRepository implements OrderRepositoryInterface
{
    public function findById(int $id): ?Order
    {
        $model = OrderModel::find($id);
        return $model ? $this->toDomain($model) : null;
    }

    public function save(Order $order): void
    {
        $model = OrderModel::findOrNew($order->id());
        $model->user_id = $order->userId();
        $model->total_amount = $order->totalAmount()->toDollars();
        $model->status = $order->status()->value;
        $model->save();
    }

    public function delete(Order $order): void
    {
        OrderModel::destroy($order->id());
    }

    private function toDomain(OrderModel $model): Order
    {
        return Order::reconstitute(
            id: $model->id,
            userId: $model->user_id,
            totalAmount: Money::fromDollars($model->total_amount),
            status: OrderStatus::from($model->status),
            createdAt: new \DateTimeImmutable($model->created_at)
        );
    }
}

依赖注入配置

config/container.php 中配置依赖注入:

php
<?php

declare(strict_types=1);

use app\contract\repository\OrderRepositoryInterface;
use app\infrastructure\repository\eloquent\EloquentOrderRepository;

return [
    // 绑定接口到实现
    OrderRepositoryInterface::class => DI\autowire(EloquentOrderRepository::class),
];

最佳实践

DO

  1. Controller 只依赖 Service
  2. Service 依赖 Contract 接口,不依赖具体实现
  3. Domain 保持纯净,不依赖框架
  4. Infrastructure 实现 Contract 接口
  5. 使用依赖注入容器管理依赖

DON'T

  1. 不要在 Domain 层使用框架类
  2. 不要在 Controller 直接访问 Model
  3. 不要在 Service 层依赖具体的 Infrastructure 实现
  4. 不要跨层级调用(如 Controller → Infrastructure)
  5. 不要循环依赖

相关文档