0
0
Lập trình
Admin Team
Admin Teamtechmely

Từ Mớ Bòng Bông đến Bản Giao Hưởng: Quản Lý Ứng Dụng PHP Với DDD và CQRS

Đăng vào 7 tháng trước

• 13 phút đọc

Từ Mớ Bòng Bông đến Bản Giao Hưởng: Quản Lý Ứng Dụng PHP Với DDD và CQRS

Giới thiệu

Bạn đã từng nhìn vào một codebase và nghĩ rằng "Tôi cần một bản đồ, một la bàn, và có thể là một nhà trị liệu"? Chúng ta đều đã trải qua khoảnh khắc đó. Khi mà UserController của bạn phát triển lên đến 500 dòng, xử lý mọi thứ từ xác thực đến gửi email chúc mừng sinh nhật, và khiến bạn phải đặt câu hỏi về lựa chọn nghề nghiệp của mình.

Vậy nếu tôi nói với bạn rằng có một cách để viết mã sạch sẽ, có tổ chức, mà tương lai bạn sẽ thực sự cảm ơn bạn hiện tại? Hãy đến với Thiết kế Hướng miền (DDD) và Phân tách Trách nhiệm Lệnh và Truy vấn (CQRS) – không chỉ là những chữ viết tắt sang trọng, mà là vũ khí bí mật của bạn chống lại hỗn loạn.

Kiến Trúc Lớn: Bốn Lớp Thiền

Hãy nghĩ về ứng dụng của chúng ta như một ngôi nhà được tổ chức tốt. Mỗi tầng có mục đích riêng, và không ai làm giặt ở nhà bếp (hy vọng là vậy).

Lớp 1: Miền – Nơi Logic Kinh Doanh Sống Đời Tốt Nhất

Đây là linh hồn của ứng dụng của bạn. Logic kinh doanh hoàn toàn, không bị ô nhiễm, không quan tâm bạn đang sử dụng Laravel, Symfony, hay viết mã bằng bồ câu đưa thư.

php Copy
class User {
    private function __construct(
        private UserId $id,
        private UserName $name,
        private Email $email,
        private HashedPassword $password,
        private UserRole $role,
        private \DateTimeInterface $createdAt,
        private ?\DateTimeInterface $updatedAt = null
    ) {}

    public static function create(
        UserName $name,
        Email $email,
        HashedPassword $password,
        UserRole $role
    ): self {
        return new self(
            UserId::generate(),
            $name,
            $email,
            $password,
            $role,
            new \DateTimeImmutable()
        );
    }
}

Bạn có nhận thấy điều gì đẹp đẽ ở đây không? User này không biết gì về cơ sở dữ liệu, yêu cầu HTTP, hay Laravel. Nó chỉ là một người dùng, làm những gì người dùng cần làm. Ngón tay cái lên.

Đối Tượng Giá Trị: Những Người Hùng Vô Danh

Nhớ lần cuối bạn đã vô tình truyền một địa chỉ email khi bạn muốn truyền tên không? Đối tượng giá trị sẽ cứu bạn khỏi những phiên gỡ lỗi lúc 2 giờ sáng:

php Copy
readonly class Password {
    public function __construct(private string $value) {
        if (strlen($value) < 8) {
            throw new \InvalidArgumentException('Mật khẩu phải ít nhất 8 ký tự');
        }
    }

    public function hash(): HashedPassword {
        return new HashedPassword(password_hash($this->value, PASSWORD_DEFAULT));
    }
}

Bây giờ bạn không thể tạo một mật khẩu không hợp lệ. Mã sẽ không biên dịch được. Giống như có một người bảo vệ cho các kiểu dữ liệu của bạn.

Lớp 2: Ứng Dụng – Người Dàn Dựng Chính

Lớp này giống như một nhạc trưởng, đảm bảo mọi nhạc cụ chơi đúng thời điểm. Nó dàn dựng các kịch bản kinh doanh thông qua Lệnh và Bộ xử lý.

Lệnh: Nói Bằng Ngôn Ngữ Kinh Doanh

php Copy
readonly class RegisterUserCommand {
    public function __construct(
        public UserName $name,
        public Email $email,
        public Password $password,
        public UserRole $role
    ) {}
}

Nhìn đấy! Không có mảng, không có tham số chuỗi ngẫu nhiên. Chỉ có ý định thuần túy, đầy diễn đạt. Khi bạn thấy RegisterUserCommand, bạn biết chính xác điều gì sắp xảy ra.

Bộ Xử Lý: Nơi Phép Màu Xảy Ra

php Copy
class RegisterUserHandler {
    public function handle(RegisterUserCommand $command): User {
        // Quy tắc kinh doanh: Không có email trùng lặp
        if ($this->userRepository->existsByEmail($command->email)) {
            throw new EmailAlreadyExistsException();
        }

        // Tạo người dùng (logic miền)
        $user = User::create(
            $command->name,
            $command->email,
            $command->password->hash(),
            $command->role
        );

        // Lưu lại
        $this->userRepository->save($user);

        // Thông báo cho thế giới về điều đã xảy ra
        $this->eventDispatcher->dispatch(
            new UserRegistered($user, new \DateTimeImmutable())
        );

        return $user;
    }
}

Bộ xử lý này đọc như một câu chuyện: "Kiểm tra xem email có tồn tại không, tạo người dùng, lưu người dùng, thông báo tin vui." Không có đoán mò, không có bất ngờ.

Lớp 3: Cơ Sở Hạ Tầng – Bộ Phận Làm Việc Bẩn

Đây là nơi chúng ta dính bẩn với cơ sở dữ liệu, APIs, và tất cả những điều rắc rối thực tế. Nhưng điểm đáng chú ý là – nó hoàn toàn có thể thay thế.

php Copy
class EloquentUserRepository implements UserRepositoryInterface {
    public function save(User $user): void {
        // Dịch đối tượng miền thành hàng cơ sở dữ liệu
        $eloquentUser = EloquentUser::updateOrCreate(
            ['id' => $user->id()->value()],
            [
                'name' => $user->name()->value(),
                'email' => $user->email()->value(),
                'password' => $user->password()->value(),
            ]
        );

        $eloquentUser->assignRole($user->role()->value);
    }

    private function toDomainEntity(EloquentUser $eloquentUser): User {
        // Dịch hàng cơ sở dữ liệu trở lại thành đối tượng miền
        return new User(
            new UserId($eloquentUser->id),
            new UserName($eloquentUser->name),
            new Email($eloquentUser->email),
            new HashedPassword($eloquentUser->password),
            UserRole::from($eloquentUser->roles->first()?->name ?? 'user'),
            $eloquentUser->created_at,
            $eloquentUser->updated_at
        );
    }
}

Bạn thấy điều gì xảy ra không? Chúng ta đang dịch giữa các đối tượng miền thuần túy và các mô hình Eloquent. Ngày mai, nếu bạn quyết định chuyển sang MongoDB, bạn chỉ cần viết một kho lưu trữ mới. Logic kinh doanh của bạn vẫn không bị ảnh hưởng.

Lớp 4: Trình Bày – Người Làm Thỏa Mãn Khách Hàng

Lớp này nói HTTP, xử lý JSON, và đối phó với tất cả những drama cụ thể của web:

php Copy
class AuthController extends Controller {
    public function register(RegisterUserRequest $request): JsonResponse {
        try {
            $data = $request->validated();

            // Xây dựng lệnh từ dữ liệu HTTP
            $command = new RegisterUserCommand(
                new UserName($data['name']),
                new Email($data['email']),
                new Password($data['password']),
                UserRole::from($data['role'])
            );

            // Thực thi logic kinh doanh
            $user = $this->registerHandler->handle($command);
            $token = $this->tokenService->generate($user);

            // Trả về phản hồi HTTP
            return response()->json([
                'success' => true,
                'token' => $token,
                'user' => [
                    'id' => $user->id()->value(),
                    'name' => $user->name()->value(),
                    'email' => $user->email()->value(),
                    'role' => $user->role()->value
                ]
            ], 201);

        } catch (EmailAlreadyExistsException $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 422);
        }
    }
}

Bộ điều khiển giờ đây gọn gàng, tập trung, và trung thực về nhiệm vụ của nó: dịch giữa HTTP và ứng dụng của bạn.

Tại Sao Kiến Trúc Này Sẽ Thay Đổi Cuộc Đời Bạn

1. Kiểm Thử Trở Nên Gần Như Vui (Gần Như)

Nhớ những lần vật lộn để kiểm thử các bộ điều khiển nói chuyện trực tiếp với cơ sở dữ liệu? Những ngày đó đã qua:

php Copy
// Giả lập kho lưu trữ, kiểm thử logic kinh doanh
$mockRepository = $this->createMock(UserRepositoryInterface::class);
$mockRepository->expects($this->once())->method('existsByEmail')->willReturn(false);

$handler = new RegisterUserHandler($mockRepository, $eventDispatcher);
$user = $handler->handle($command);

$this->assertEquals('John Doe', $user->name()->value());

Sự hạnh phúc tuyệt đối. Không cần thiết lập cơ sở dữ liệu, không cần giả lập HTTP. Chỉ cần kiểm thử logic thuần túy.

2. Độc Lập Về Framework (Cuối Cùng!)

Logic kinh doanh của bạn không quan tâm nếu Laravel trở nên lỗi thời hoặc nếu bạn cần chuyển sang Symfony. Lớp miền là độc lập với framework:

php Copy
// Điều này hoạt động bất kể lựa chọn framework của bạn
$user = User::create($name, $email, $password, $role);

3. An Toàn Kiểu Dữ Liệu Thực Sự Giúp Bạn

Với các Đối Tượng Giá Trị, điều này thực sự sẽ không biên dịch được:

php Copy
// Lỗi biên dịch: Không thể truyền chuỗi nơi yêu cầu Email
$user = User::create("John", "not-an-email-object", $password, $role);

Trình soạn thảo của bạn phát hiện lỗi trước khi người dùng của bạn làm. Cách mạng, phải không?

4. Xử Lý Lỗi Có Ý Nghĩa

Những ngày của các thông điệp ngoại lệ khó hiểu đã qua:

php Copy
try {
    $user = $handler->handle($command);
} catch (EmailAlreadyExistsException $e) {
    // Rõ ràng điều gì đã sai
} catch (UserNotFoundException $e) {
    // Cái này cũng vậy
}

Không còn phải điều tra để tìm hiểu lý do tại sao điều gì đó lại thất bại.

5. Sự Kiện: Mạng Xã Hội Của Mã Nguồn Của Bạn

Các thành phần có thể phản ứng với những gì xảy ra mà không bị liên kết chặt chẽ:

php Copy
class UserRegistered {
    public function __construct(
        private User $user,
        private \DateTimeInterface $occurredAt
    ) {}
}

Muốn gửi email chúc mừng? Lắng nghe UserRegistered. Cần phân tích dữ liệu? Sự kiện tương tự. Mã đăng ký của bạn không cần biết hoặc quan tâm đến các tính năng này.

Nói Thật: Khi Nào Nên Sử Dụng Kiến Trúc Này

Nên Sử Dụng Khi:

  • Logic kinh doanh của bạn phức tạp
  • Bạn có một đội hơn 3 lập trình viên
  • Dự án sẽ tồn tại lâu hơn 6 tháng
  • Bạn đánh giá sự tỉnh táo và giấc ngủ của mình

Không Nên Sử Dụng Khi:

  • Xây dựng một ứng dụng CRUD đơn giản
  • Làm việc trong dự án hackathon cuối tuần
  • Logic kinh doanh của bạn vừa đủ trong một tệp
  • Bạn thích chịu đựng (chỉ đùa thôi!)

Bắt Đầu Mà Không Mất Trí

Bắt Đầu Nhỏ

Đừng viết lại mọi thứ cùng một lúc. Chọn một tính năng, thực hiện nó với kiến trúc này, và xem cảm giác như thế nào. Tôi đảm bảo bạn sẽ muốn làm nhiều hơn.

Đầu Tư Vào Kiểm Thử Sớm

Kiến trúc này làm cho việc kiểm thử trở nên dễ dàng hơn rất nhiều. Viết các bài kiểm thử khi bạn đi – bạn của tương lai sẽ gửi thiệp cảm ơn.

Tài Liệu Ranh Giới Của Bạn

Biết mã nào thuộc về đâu. Vẽ sơ đồ nếu cần thiết. Các đồng đội của bạn sẽ đánh giá cao sự rõ ràng.

Chấp Nhận Đường Cong Học Tập

Vâng, ban đầu cảm giác như là nhiều việc hơn. Nhưng nhớ rằng: bạn không chỉ đang viết mã cho hôm nay. Bạn đang viết mã cho lập trình viên tiếp theo phải bảo trì nó (có thể là bạn trong 6 tháng).

Kết Luận

Điều này không chỉ liên quan đến việc viết mã "doanh nghiệp" hay ấn tượng với đồng nghiệp của bạn (mặc dù điều đó sẽ xảy ra). Nó liên quan đến việc tôn trọng nghề của bạn. Nó liên quan đến việc xây dựng phần mềm có thể phát triển, có thể hiểu, mà không cần những cuộc thám hiểm khảo cổ để chỉnh sửa.

Chắc chắn, bạn có thể tiếp tục nhồi nhét mọi thứ vào các bộ điều khiển và hy vọng điều tốt nhất. Nhưng niềm vui ở đâu trong đó? Niềm tự hào về nghề thủ công ở đâu?

Hãy thực hiện bước nhảy. Tương lai của bạn – và đội của bạn – sẽ cảm ơn bạn vì điều đó.

Bây giờ hãy tiến lên và kiến trúc một cái gì đó đẹp đẽ.

Câu Hỏi Thường Gặp (FAQ)

DDD và CQRS là gì?

DDD (Domain-Driven Design) là phương pháp thiết kế phần mềm tập trung vào logic kinh doanh, trong khi CQRS (Command Query Responsibility Segregation) phân tách các thao tác ghi và đọc.

Khi nào nên sử dụng DDD và CQRS?

Sử dụng khi bạn có logic kinh doanh phức tạp, nhiều lập trình viên, và dự án dài hạn.

Có những công cụ nào hỗ trợ DDD và CQRS không?

Có nhiều công cụ như Symfony, Laravel, và các thư viện hỗ trợ CQRS có sẵn.

Làm thế nào để bắt đầu với DDD và CQRS?

Bắt đầu với một tính năng nhỏ, thực hiện nó theo cách DDD và CQRS, và mở rộng dần theo thời gian.

Các lỗi thường gặp khi sử dụng DDD và CQRS?

Một số lỗi thường gặp bao gồm không phân tách rõ ràng giữa các lớp, không đầu tư vào kiểm thử, và không tài liệu hóa.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào