+2

Sai lầm dễ gặp phải khi chuyển từ MVC pattern sang clean architecture

Nên đọc về Clean Architecture và Domain-Driven Design trước khi đọc blog này

Hôm bữa, một người bạn của mình ngỏ ý muốn học thêm về DDD và Clean Architecture để tham gia vào các dự án của mình. Sau khi kiểm tra cơ bản về cách triển khai Clean Architecture mà bạn mình lượm lặt được trên các blog trên Internet, có thể nhận thấy ngay rằng những blog này được viết bởi những người mới chuyển từ các kiến trúc cổ điển sang Clean Architecture, chưa có kinh nghiệm, chưa nắm rõ lý thuyết mà đã viết blog. Điều này dẫn đến những sai lầm khiến việc sử dụng kiến trúc này kém hiệu quả hơn.

1. Entity trong domain

Đây là lỗi hay gặp nhất, phổ biến số một vì MVC pattern tồn tại quá lâu đã gọi tầng dữ liệu là model. Tuy nhiên, khi nằm trong Clean Architecture, domain layer không được phép phụ thuộc vào bất cứ thứ gì bên ngoài, và dữ liệu thực tế chính là một "yếu tố" bên ngoài.

Nhiều lập trình viên mắc sai lầm khi để Entity trong domain layer chứa các phụ thuộc vào ORM hoặc cơ sở dữ liệu cụ thể, chẳng hạn như sử dụng các decorator của một ORM như TypeORM hoặc Sequelize ngay trong Entity. Điều này vi phạm nguyên tắc của Clean Architecture, bởi vì domain layer phải hoàn toàn độc lập với tầng cơ sở dữ liệu.

Cách khắc phục:

  • Nên nhớ, domain layer chỉ chứa domain model và domain service. Domain model phải trông như model trong các bước thiết kế và phân tích, nó chỉ có nhiệm vụ mô tả hình dạng của object, không được chứa thêm các thông tin cụ thể liên quan đến database hay logic. Domain service là những action mà các model tương tác với nhau và đặc biệt, không được phụ thuộc vào bất kỳ thứ gì ở các layer trên (Application và Infrastructure).
  • Domain model phải được định nghĩa mà không sử dụng các công nghệ bên ngoài, ví dụ như các ORM-specific decorators (@Column, @Entity, v.v.).
  • Thao tác với data source là thao tác với hệ thống ngoài – các entity cũng là một phần của hệ thống ngoài. Vì vậy, khi triển khai entity và repository (các service tương tác với data source), chỉ có interface của chúng được cài đặt dưới dạng adapter trong domain.
  • Các entity được khai báo dưới dạng adapter trong domain layer phải kế thừa domain model.
  • Các thuộc tính cụ thể của entity và cài đặt của repository phải được đặt trong Infrastructure.
  • Nếu một use case cần sử dụng data source, nó phải phụ thuộc vào adapter trong domain, và dependency injector sẽ có nhiệm vụ truyền cài đặt cụ thể từ Infrastructure vào use case.

2. Business logic nằm trong controller

Trong nhiều hệ thống sử dụng MVC truyền thống, business logic thường được đặt trong controller, hoặc ít nhất controller có quyền truy cập trực tiếp vào dữ liệu. Tuy nhiên, trong Clean Architecture, controller chỉ là một thành phần trong Infrastructure layer, chịu trách nhiệm tiếp nhận yêu cầu từ bên ngoài. Layer chứa logic của ứng dụng là Application. Nhiều developer khi chuyển từ MVC sang Clean Architecture vẫn có thói quen đặt toàn bộ logic trong controller, có thể vì tính tiện lợi hoặc chưa hiểu rõ sự tách biệt của các tầng.

Cách khắc phục: Phải nắm rõ rằng Infrastructure layer chỉ chịu trách nhiệm tương tác với môi trường bên ngoài chứ không chứa business logic cụ thể. Vì vậy, controller như một phần của Infrastructure sẽ chỉ chứa những đoạn code cung cấp tính năng tiếp nhận request, ví dụ:

  • Middleware, nhưng chỉ nên xử lý các nhiệm vụ như authentication, logging hoặc lọc request; nếu có business logic, cần inject Use Case từ Application Layer để xử lý chính.
  • Routing, gọi đúng Use Case từ Application Layer để xử lý.
  • Handle exception từ Use Case, v.v.

3. Coi view/presentation như một layer cụ thể

Không, view hay presentation vẫn chỉ là một thành phần trong Infrastructure, và thông thường Clean Architecture được ứng dụng với microservice API nên ít khi có cả view.

4. Không phân biệt được Application Use Case và Domain Service

Đây cũng là một sai lầm của mình khi mới bắt đầu, và trong hầu hết các tutorial/blog trên mạng cũng sẽ vắng bóng khái niệm "Domain Service". Về cơ bản, khi ta cài đặt một business logic cần tới domain knowledge, nếu như trừu tượng hóa đủ tốt, logic này sẽ chỉ cần quan tâm tới tương tác giữa các domain model.

Ví dụ: Khi tính phí vận chuyển cho một đơn hàng, logic sẽ chỉ cần tương tác với các domain model như Order, Policy, Voucher. Như vậy, hoàn toàn có thể cài đặt một domain service CalculateShippingFee trong domain mà không vi phạm nguyên tắc của Clean Architecture. Tuy nhiên, thông thường mọi người sẽ cài đặt nó như một Use Case trong Application layer, hoặc tệ hơn là trong Controller ở Infrastructure layer.

Cách khắc phục: Nắm rõ nguyên tắc cơ bản: nếu một action chỉ tương tác giữa các domain model, hãy coi nó là domain service.

Lợi ích của việc làm đúng nguyên tắc này sẽ toả sáng nhất khi áp dụng vào domain-driven design. Khi domain service tách rời hẳn khỏi các layer khác, domain knowledge sẽ dễ dàng được implement hơn, tránh lỗi và đúng ý domain expert hơn.

5. Sai luồng phụ thuộc

Trong Clean Architecture, luồng phụ thuộc luôn là một chiều từ ngoài vào trong:

  • Application phụ thuộc vào Domain.
  • Infrastructure phụ thuộc vào Domain hoặc Application.
  • Không có chiều ngược lại.

Nhiều developer khi mới chuyển từ các kiến trúc cổ điển sang có thói quen phụ thuộc cứng (phụ thuộc vào implementation, instance). Khi làm theo nguyên tắc của Clean Architecture, họ có thể cảm thấy khó hiểu và vi phạm, ví dụ: Use Case phụ thuộc vào Repository trong Infrastructure. Tuy nhiên, làm như vậy sẽ gây ra phụ thuộc vòng (circular dependency) và phụ thuộc cứng, làm mất tính linh hoạt của kiến trúc và vi phạm những nguyên tắc cơ bản của lập trình khi phải làm việc với nhiều module.

Cách khắc phục:

  • Nếu một lớp bên trong cần gọi một lớp bên ngoài, hãy định nghĩa interface trong lớp bên trong và để lớp bên ngoài implement nó.
  • Sử dụng dependency injection để inject implementation vào thay vì phụ thuộc trực tiếp.
  • Sử dụng shared token hoặc identifier ở lớp bên trong để quản lý việc inject phụ thuộc.

Lời kết

Clean Architecture, giống như bất kỳ kiến trúc nào hướng tới việc tối ưu hóa chất lượng code, có thể khó hiểu và dài dòng khi mới tiếp cận. Tuy nhiên, nếu tuân thủ tốt các nguyên tắc, bạn sẽ đạt được một codebase sạch, linh hoạt và dễ bảo trì, giúp giảm thiểu nợ kỹ thuật và tăng khả năng mở rộng của hệ thống. Và đặc biệt mình nhìn thấy được tiềm năng của low-code trong Clean Architecture, nếu như được thiết kế tốt, generation engine có thể sinh ra hầu hết mọi thứ, Infrastructure Layer chủ yếu là các công việc lặp lại, với sự hỗ trợ của generative AI thì việc sinh ra Application Layer bằng native language là khả thi, khi đó chỉ cần domain expert fine tune Domain Service là có thể ra một hệ thống hoàn chỉnh, hiệu năng cao, code sạch - thứ mà low-code trước đây không thể làm được.

Dù quá trình chuyển đổi từ MVC sang Clean Architecture có thể gặp nhiều thách thức, nhưng khi đã làm chủ được mô hình này, bạn sẽ thấy mọi thứ trở nên rõ ràng hơn, và quan trọng nhất – code của bạn sẽ "đẹp" hơn bao giờ hết.

Bài viết được hỗ trợ bởi ChatGPT.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí