分层职责
清晰的分层职责是架构可维护性的关键
目录
架构分层概览
┌─────────────────────────────────────────────────────────┐
│ Controller Layer │
│ 薄控制器 - 输入验证和输出格式化 │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Service Layer │
│ 用例编排 - 事务管理 - 流程控制 │
└────────┬────────────────────────────────────────────────┘
│
┌────────▼───────────────┐ ┌─────────────────────┐
│ Domain Layer │ │ Contract Layer │
│ 业务逻辑 - 纯 PHP │◄───────│ 接口定义 │
└────────────────────────┘ └──────────┬──────────┘
▲ ▲
│ │
┌────────┴────────────────────────────────────┴──────────┐
│ Infrastructure Layer │
│ 外部依赖 - 数据库 - 第三方服务 - 缓存 │
└─────────────────────────────────────────────────────────┘Controller 层
职责
唯一职责:HTTP 请求入口和响应出口
- 接收 HTTP 请求
- 验证输入参数
- 调用 Service 层
- 格式化响应输出
- 不包含业务逻辑
特点
- 薄控制器 (Thin Controller)
- 只做输入输出 (Input/Output Only)
- 不直接访问数据库
- 不包含业务规则
代码示例
php
<?php
declare(strict_types=1);
namespace app\controller\api\v1;
use app\service\order\CreateOrderService;
use app\service\order\CancelOrderService;
use app\service\order\GetOrderService;
use support\Request;
use support\Response;
final class OrderController
{
public function __construct(
private readonly CreateOrderService $createOrderService,
private readonly CancelOrderService $cancelOrderService,
private readonly GetOrderService $getOrderService
) {
}
public function create(Request $request): Response
{
// 1. 验证输入
$validated = $this->validate($request, [
'items' => 'required|array|min:1',
'shipping_address' => 'required|array',
]);
// 2. 调用服务层
$order = $this->createOrderService->handle(
userId: $request->user()->id,
items: $validated['items'],
shippingAddress: $validated['shipping_address']
);
// 3. 格式化响应
return json([
'success' => true,
'data' => [
'id' => $order->id(),
'total' => $order->totalAmount()->toDollars(),
'status' => $order->status()->value,
],
], 201);
}
public function show(Request $request, int $id): Response
{
$order = $this->getOrderService->handle($id);
return json([
'success' => true,
'data' => $order->toArray(),
]);
}
public function cancel(Request $request, int $id): Response
{
$this->cancelOrderService->handle($id, $request->user()->id);
return json([
'success' => true,
'message' => 'Order cancelled successfully',
]);
}
}DO
- 验证输入参数
- 调用 Service 层方法
- 格式化 JSON/HTML 响应
- 处理 HTTP 状态码
- 异常转换为 HTTP 响应
DON'T
- 不要包含业务逻辑
- 不要直接访问 Model
- 不要直接访问数据库
- 不要调用外部 API
- 不要包含复杂计算
Service 层
职责
核心职责:用例编排和事务管理
- 编排业务流程
- 管理事务边界
- 调用 Domain 层
- 调用 Infrastructure 层
- 不包含业务规则(规则在 Domain 层)
特点
- 用例驱动 (Use Case Driven)
- 事务管理 (Transaction Management)
- 流程编排 (Orchestration)
- 依赖接口 (Depend on Interfaces)
代码示例
php
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\UserRepositoryInterface;
use app\contract\repository\ProductRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\contract\gateway\NotificationGatewayInterface;
use app\domain\order\entity\Order;
use app\domain\order\exception\InvalidOrderException;
use support\Db;
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly UserRepositoryInterface $userRepository,
private readonly ProductRepositoryInterface $productRepository,
private readonly PaymentGatewayInterface $paymentGateway,
private readonly NotificationGatewayInterface $notificationGateway
) {
}
public function handle(int $userId, array $items, array $shippingAddress): Order
{
// 事务管理
return Db::transaction(function () use ($userId, $items, $shippingAddress) {
// 1. 获取用户(调用仓储)
$user = $this->userRepository->findById($userId);
if ($user === null) {
throw new InvalidOrderException('User not found');
}
// 2. 验证库存(调用仓储)
foreach ($items as $item) {
$product = $this->productRepository->findById($item['product_id']);
if ($product === null || $product->stock() < $item['quantity']) {
throw new InvalidOrderException('Insufficient inventory');
}
}
// 3. 创建订单实体(调用领域层)
$order = Order::create($userId, $items, $shippingAddress);
$order->calculateTotal();
// 4. 扣减库存(调用仓储)
foreach ($items as $item) {
$product = $this->productRepository->findById($item['product_id']);
$product->decreaseStock($item['quantity']);
$this->productRepository->save($product);
}
// 5. 持久化订单(调用仓储)
$this->orderRepository->save($order);
// 6. 创建支付(调用外部服务)
$this->paymentGateway->createPaymentIntent($order);
// 7. 发送通知(调用外部服务)
$this->notificationGateway->sendOrderConfirmation($user, $order);
return $order;
});
}
}事务管理示例
php
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\domain\order\entity\Order;
use support\Db;
final class CancelOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository
) {
}
public function handle(int $orderId, int $userId): void
{
Db::transaction(function () use ($orderId, $userId) {
$order = $this->orderRepository->findById($orderId);
// 业务规则验证(Domain 层负责)
$order->cancel();
// 持久化
$this->orderRepository->save($order);
// 触发领域事件
foreach ($order->releaseEvents() as $event) {
event($event);
}
});
}
}DO
- 管理事务边界
- 编排多个 Domain 对象
- 调用多个 Repository
- 调用外部服务
- 触发领域事件
DON'T
- 不要包含业务规则(放 Domain 层)
- 不要直接访问数据库(通过 Repository)
- 不要依赖具体实现(依赖接口)
- 不要包含复杂计算(放 Domain 层)
Domain 层
职责
核心职责:业务逻辑和业务规则
- 实现业务规则
- 领域模型
- 业务计算
- 状态转换
- 纯 PHP,不依赖框架
特点
- 纯业务逻辑 (Pure Business Logic)
- 框架无关 (Framework Agnostic)
- 可独立测试 (Independently Testable)
- 不访问数据库 (No Database Access)
Entity 示例
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\event\OrderCreated;
use app\domain\order\event\OrderCancelled;
use app\domain\order\exception\InvalidOrderException;
final class Order
{
private array $domainEvents = [];
private function __construct(
private readonly int $id,
private readonly int $userId,
private array $items,
private Money $totalAmount,
private OrderStatus $status,
private readonly \DateTimeImmutable $createdAt
) {
}
public static function create(int $userId, array $items, array $shippingAddress): self
{
// 业务规则:订单必须至少有一个商品
if (empty($items)) {
throw new InvalidOrderException('Order must have at least one item');
}
// 业务规则:每个商品数量必须大于 0
foreach ($items as $item) {
if ($item['quantity'] <= 0) {
throw new InvalidOrderException('Item quantity must be greater than 0');
}
}
$order = new self(
id: 0,
userId: $userId,
items: $items,
totalAmount: Money::zero(),
status: OrderStatus::Pending,
createdAt: new \DateTimeImmutable()
);
$order->recordEvent(new OrderCreated($order));
return $order;
}
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 cancel(): void
{
// 业务规则:只有待支付和已支付的订单可以取消
if (!$this->status->canBeCancelled()) {
throw new InvalidOrderException(
"Order cannot be cancelled in status: {$this->status->value}"
);
}
// 状态转换
$this->status = OrderStatus::Cancelled;
$this->recordEvent(new OrderCancelled($this));
}
public function ship(): void
{
// 业务规则:只有已支付的订单可以发货
if ($this->status !== OrderStatus::Paid) {
throw new InvalidOrderException('Only paid orders can be shipped');
}
$this->status = OrderStatus::Shipped;
}
// Getters
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;
}
private function recordEvent(object $event): void
{
$this->domainEvents[] = $event;
}
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}Enum 示例(OrderStatus)
enum/ - 固定选项集合,如状态、类型、方法 vo/ - 需要验证、运算或多属性的值对象
php
<?php
declare(strict_types=1);
namespace app\domain\order\enum;
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function isPending(): bool
{
return $this === self::Pending;
}
public function canBeCancelled(): bool
{
return in_array($this, [self::Pending, self::Paid]);
}
public function canBeShipped(): bool
{
return $this === self::Paid;
}
}Value Object 示例(Money)
php
<?php
declare(strict_types=1);
namespace app\domain\order\vo;
final class Money
{
private function __construct(
private readonly int $cents
) {
if ($cents < 0) {
throw new \InvalidArgumentException('Money cannot be negative');
}
}
public static function fromCents(int $cents): self
{
return new self($cents);
}
public static function fromDollars(float $dollars): self
{
return new self((int) round($dollars * 100));
}
public static function zero(): self
{
return new self(0);
}
public function add(self $other): self
{
return new self($this->cents + $other->cents);
}
public function subtract(self $other): self
{
return new self($this->cents - $other->cents);
}
public function multiply(int $factor): self
{
return new self($this->cents * $factor);
}
public function toCents(): int
{
return $this->cents;
}
public function toDollars(): float
{
return $this->cents / 100;
}
public function equals(self $other): bool
{
return $this->cents === $other->cents;
}
public function greaterThan(self $other): bool
{
return $this->cents > $other->cents;
}
}DO
- 实现业务规则
- 业务计算和验证
- 状态转换逻辑
- 触发领域事件
- 使用枚举和值对象
DON'T
- 不要依赖框架类
- 不要访问数据库
- 不要调用外部 API
- 不要依赖 Infrastructure
- 不要使用静态方法访问全局状态
Infrastructure 层
职责
核心职责:外部依赖的具体实现
- 实现 Repository 接口
- 实现 Gateway 接口
- 数据库访问
- 缓存操作
- 第三方服务集成
特点
- 实现接口 (Implement Interfaces)
- 依赖外部系统 (Depend on External Systems)
- 数据转换 (Data Transformation)
- 技术细节 (Technical Details)
Repository 实现示例
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);
if ($model === null) {
return null;
}
return $this->toDomain($model);
}
public function findByUserId(int $userId): array
{
$models = OrderModel::where('user_id', $userId)->get();
return $models->map(fn ($model) => $this->toDomain($model))->all();
}
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();
// 触发领域事件
foreach ($order->releaseEvents() as $event) {
event($event);
}
}
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,
items: json_decode($model->items, true),
totalAmount: Money::fromDollars($model->total_amount),
status: OrderStatus::from($model->status),
createdAt: new \DateTimeImmutable($model->created_at)
);
}
}Gateway 实现示例
php
<?php
declare(strict_types=1);
namespace app\infrastructure\gateway\payment;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;
final class StripePaymentGateway implements PaymentGatewayInterface
{
public function __construct(
private readonly string $apiKey
) {
}
public function createPaymentIntent(Order $order): string
{
// 调用 Stripe API
$stripe = new \Stripe\StripeClient($this->apiKey);
$paymentIntent = $stripe->paymentIntents->create([
'amount' => $order->totalAmount()->toCents(),
'currency' => 'usd',
'metadata' => [
'order_id' => $order->id(),
],
]);
return $paymentIntent->id;
}
public function charge(Order $order, string $paymentMethodId): bool
{
$stripe = new \Stripe\StripeClient($this->apiKey);
try {
$stripe->paymentIntents->confirm($paymentMethodId);
return true;
} catch (\Stripe\Exception\CardException $e) {
return false;
}
}
}DO
- 实现 Contract 接口
- 访问数据库
- 调用第三方 API
- 数据格式转换
- 缓存操作
DON'T
- 不要包含业务逻辑
- 不要直接被 Controller 调用
- 不要暴露技术细节给 Domain
Contract 层
职责
核心职责:定义接口契约
- 定义 Repository 接口
- 定义 Gateway 接口
- 定义 Service 接口
- 只有接口,没有实现
特点
- 纯接口 (Pure Interfaces)
- 定义契约 (Define Contracts)
- 依赖倒置 (Dependency Inversion)
Repository 接口示例
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 findByUserId(int $userId): array;
public function save(Order $order): void;
public function delete(Order $order): void;
public function nextIdentity(): int;
}Gateway 接口示例
php
<?php
declare(strict_types=1);
namespace app\contract\gateway;
use app\domain\order\entity\Order;
interface PaymentGatewayInterface
{
public function createPaymentIntent(Order $order): string;
public function charge(Order $order, string $paymentMethodId): bool;
public function refund(Order $order): bool;
}DO
- 定义清晰的接口方法
- 使用类型提示
- 返回 Domain 对象
- 文档注释
DON'T
- 不要包含实现代码
- 不要依赖具体类
- 不要暴露技术细节
Support 层
职责
核心职责:通用工具和辅助功能
- 辅助函数
- 可复用 Trait
- 自定义异常
- 通用工具类
特点
- 真正通用 (Truly Generic)
- 无业务逻辑 (No Business Logic)
- 可复用 (Reusable)
辅助函数示例
php
<?php
declare(strict_types=1);
namespace app\support\helper;
function array_get(array $array, string $key, mixed $default = null): mixed
{
return $array[$key] ?? $default;
}
function money_format(int $cents): string
{
return '$' . number_format($cents / 100, 2);
}Trait 示例
php
<?php
declare(strict_types=1);
namespace app\support\trait;
trait HasTimestamps
{
private \DateTimeImmutable $createdAt;
private \DateTimeImmutable $updatedAt;
public function createdAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function updatedAt(): \DateTimeImmutable
{
return $this->updatedAt;
}
protected function initializeTimestamps(): void
{
$now = new \DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
}
protected function touch(): void
{
$this->updatedAt = new \DateTimeImmutable();
}
}自定义异常示例
php
<?php
declare(strict_types=1);
namespace app\support\exception;
class BusinessException extends \Exception
{
public function __construct(
string $message,
private readonly array $context = []
) {
parent::__construct($message);
}
public function context(): array
{
return $this->context;
}
}DO
- 真正通用的工具
- 无业务逻辑的辅助函数
- 可复用的 Trait
- 基础异常类
DON'T
- 不要当垃圾桶
- 不要包含业务逻辑
- 不要依赖特定模块
分层交互示例
完整流程:创建订单
php
// 1. Controller 层 - 接收请求
namespace app\controller\api\v1;
final class OrderController
{
public function create(Request $request): Response
{
$order = $this->createOrderService->handle(...);
return json(['data' => $order]);
}
}
// 2. Service 层 - 编排流程
namespace app\service\order;
final class CreateOrderService
{
public function handle(int $userId, array $items, array $shippingAddress): Order
{
return Db::transaction(function () use ($userId, $items, $shippingAddress) {
$order = Order::create($userId, $items, $shippingAddress);
$order->calculateTotal();
$this->orderRepository->save($order);
return $order;
});
}
}
// 3. Domain 层 - 业务逻辑
namespace app\domain\order\entity;
final class Order
{
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(...);
}
public function calculateTotal(): void
{
$this->totalAmount = array_reduce(...);
}
}
// 4. Contract 层 - 接口定义
namespace app\contract\repository;
interface OrderRepositoryInterface
{
public function save(Order $order): void;
}
// 5. Infrastructure 层 - 持久化
namespace app\infrastructure\repository\eloquent;
final class EloquentOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
$model = OrderModel::findOrNew($order->id());
$model->user_id = $order->userId();
$model->save();
}
}最佳实践总结
Controller 层
- 保持薄控制器
- 只做输入输出
- 不包含业务逻辑
Service 层
- 管理事务边界
- 编排业务流程
- 依赖接口而非实现
Domain 层
- 纯业务逻辑
- 框架无关
- 可独立测试
- 使用 Enum 表示固定状态,Value Object 表示复杂值
Infrastructure 层
- 实现接口
- 处理技术细节
- 数据转换
Contract 层
- 定义清晰接口
- 依赖倒置原则
Support 层
- 真正通用的工具
- 不包含业务逻辑