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

DDD中使用DTO时领域层如何定义响应结构(Laravel示例)

领域驱动设计(DDD)下Laravel认证模块的DTO与响应结构最佳实践

1. 响应结构的定义位置

响应结构(即DTO)必须定义在应用层,核心逻辑:

  • 领域层仅负责封装核心业务规则与领域模型,不关心对外输出的格式或数据结构,只返回领域实体、值对象或领域服务的业务结果。
  • 应用层作为领域层与外部的协调者,知晓用例的业务意图和对外暴露的数据需求,因此由它定义输入输出DTO是职责范围内的事。
  • 表现层(如Controller)仅负责接收请求、调用应用层用例、将DTO转换为HTTP响应,不参与响应结构的定义。

2. DTO与领域层的同步方案

通过**映射器(Mapper)**实现DTO与领域模型的解耦,具体做法:

  • 在应用层单独编写Mapper类,专门负责将领域实体/值对象转换为响应DTO。
  • 当领域层修改实体字段时,仅需更新Mapper中的映射逻辑,无需修改DTO结构(除非业务需求要求响应字段变更)。
  • 绝对避免在领域实体中添加toDTO()这类方法,这会导致领域层依赖应用层,违反依赖倒置原则。

3. 领域层是否应处理令牌、格式化时间等内容?

绝对不应该,这些属于领域层之外的关注点:

  • 令牌生成是基础设施层的职责(如Laravel Passport/JWT实现),属于技术细节,与核心业务规则无关。
  • 多国时间格式化属于表现层/应用层的展示需求,领域层只处理与业务相关的时间逻辑(如用户创建时间、过期时间),不负责对外展示的格式化。
  • 领域层应保持纯净,只关注“用户是否通过认证”这类核心业务问题,将技术细节和展示逻辑交给外层处理。

Laravel代码示例(清洁架构+DDD)

领域层:核心业务逻辑

// app/Domain/Entities/User.php
namespace App\Domain\Entities;

class User
{
    public function __construct(
        public readonly string $id,
        public readonly string $name,
        public readonly string $email,
        private readonly string $passwordHash
    ) {}

    public function verifyPassword(string $plainPassword): bool
    {
        return password_verify($plainPassword, $this->passwordHash);
    }
}

// app/Domain/Services/AuthService.php
namespace App\Domain\Services;

use App\Domain\Entities\User;
use App\Domain\Repositories\UserRepositoryInterface;

class AuthService
{
    public function __construct(private UserRepositoryInterface $userRepo) {}

    public function authenticate(string $email, string $password): ?User
    {
        $user = $this->userRepo->findByEmail($email);
        if (!$user || !$user->verifyPassword($password)) {
            return null;
        }
        return $user;
    }
}

应用层:用例与DTO

// app/Application/Auth/DTOs/LoginResponseDTO.php
namespace App\Application\Auth\DTOs;

use Spatie\DataTransferObject\DataTransferObject;

class LoginResponseDTO extends DataTransferObject
{
    public string $user_id;
    public string $name;
    public string $email;
    public string $access_token;
    public array $localized_times;
}

// app/Application/Auth/Mappers/UserResponseMapper.php
namespace App\Application\Auth\Mappers;

use App\Domain\Entities\User;
use App\Application\Auth\DTOs\LoginResponseDTO;

class UserResponseMapper
{
    public static function map(User $user, string $accessToken, array $localizedTimes): LoginResponseDTO
    {
        return new LoginResponseDTO([
            'user_id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'access_token' => $accessToken,
            'localized_times' => $localizedTimes,
        ]);
    }
}

// app/Application/Auth/UseCases/LoginUseCase.php
namespace App\Application\Auth\UseCases;

use App\Domain\Services\AuthService;
use App\Application\Auth\Mappers\UserResponseMapper;
use App\Application\Auth\DTOs\LoginResponseDTO;
use App\Infrastructure\Auth\TokenGeneratorInterface;
use App\Infrastructure\Localization\TimeFormatterInterface;

class LoginUseCase
{
    public function __construct(
        private AuthService $authService,
        private TokenGeneratorInterface $tokenGenerator,
        private TimeFormatterInterface $timeFormatter
    ) {}

    public function execute(string $email, string $password): ?LoginResponseDTO
    {
        $user = $this->authService->authenticate($email, $password);
        if (!$user) return null;

        $accessToken = $this->tokenGenerator->generateForUser($user);
        $localizedTimes = $this->timeFormatter->formatCurrentTime(['en', 'zh']);

        return UserResponseMapper::map($user, $accessToken, $localizedTimes);
    }
}

基础设施层:技术实现

// app/Infrastructure/Auth/PassportTokenGenerator.php
namespace App\Infrastructure\Auth;

use App\Domain\Entities\User;
use Laravel\Passport\TokenRepository;
use Laravel\Passport\ClientRepository;

class PassportTokenGenerator implements TokenGeneratorInterface
{
    public function __construct(
        private TokenRepository $tokenRepo,
        private ClientRepository $clientRepo
    ) {}

    public function generateForUser(User $user): string
    {
        $client = $this->clientRepo->find(1);
        $token = $this->tokenRepo->create(
            $user->id,
            $client->id,
            '',
            true
        );
        return $token->accessToken;
    }
}

// app/Infrastructure/Localization/TimeFormatter.php
namespace App\Infrastructure\Localization;

use Carbon\Carbon;

class TimeFormatter implements TimeFormatterInterface
{
    public function formatCurrentTime(array $locales): array
    {
        $times = [];
        foreach ($locales as $locale) {
            Carbon::setLocale($locale);
            $times[$locale] = Carbon::now()->toDateTimeString();
        }
        return $times;
    }
}

表现层:HTTP控制器

// app/Http/Controllers/Auth/LoginController.php
namespace App\Http\Controllers\Auth;

use App\Application\Auth\UseCases\LoginUseCase;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class LoginController extends Controller
{
    public function __construct(private LoginUseCase $loginUseCase) {}

    public function __invoke(Request $request): JsonResponse
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required'
        ]);

        $responseDto = $this->loginUseCase->execute(
            $request->email,
            $request->password
        );

        if (!$responseDto) {
            return response()->json(['message' => 'Invalid credentials'], 401);
        }

        return response()->json($responseDto->toArray());
    }
}

内容的提问来源于stack exchange,提问作者Mutaz Nayef

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 16:12:31