Chào mừng bạn đến với thế giới của Domain-Driven Design (DDD), một chủ đề tuy không mới nhưng vẫn còn khá xa lạ với nhiều lập trình viên Việt Nam. Bài viết này sẽ cung cấp một cái nhìn tổng quan và dễ hiểu về DDD, dựa trên những nghiên cứu và tìm tòi cá nhân, đồng thời mong muốn nhận được sự đóng góp từ cộng đồng để hoàn thiện hơn nữa kiến thức về lĩnh vực này.
Domain-driven design (DDD) is an approach to developing software for complex needs by deeply connecting the implementation to an evolving model of the core business concepts.
Hiểu một cách đơn giản, DDD là phương pháp phát triển phần mềm cho các bài toán phức tạp, tập trung vào việc kết nối chặt chẽ giữa việc triển khai phần mềm và mô hình nghiệp vụ cốt lõi. Nền tảng của DDD bao gồm:
- Ưu tiên nghiệp vụ chính (core domain) và logic nghiệp vụ (domain logic): DDD đặt trọng tâm vào việc hiểu rõ và giải quyết các vấn đề nghiệp vụ quan trọng nhất.
- Mô hình hóa làm trung tâm: DDD sử dụng mô hình nghiệp vụ làm nền tảng cho việc thiết kế và phát triển phần mềm.
- Sự hợp tác giữa lập trình viên và chuyên gia nghiệp vụ (domain expert): DDD khuyến khích sự hợp tác chặt chẽ giữa các bên liên quan để đảm bảo phần mềm đáp ứng đúng nhu cầu nghiệp vụ.
Theo quan điểm cá nhân, DDD không phải là một design pattern mà là một tư tưởng, một cách tiếp cận để giải quyết các bài toán phức tạp. DDD tập trung vào việc phân tích và xây dựng phần mềm dựa trên các domain (nghiệp vụ) khác nhau. Việc này xuất phát từ thực tế rằng khách hàng (domain expert) là người nắm rõ nhất về nghiệp vụ của họ. Vì vậy, việc xây dựng phần mềm dựa trên domain sẽ giúp đảm bảo rằng phần mềm đáp ứng đúng yêu cầu của khách hàng.
DDD hướng đến việc thiết kế phần mềm sao cho không chỉ lập trình viên mà ngay cả khách hàng (những người không có kiến thức về kỹ thuật) cũng có thể hiểu được trọng tâm của vấn đề. Triển khai DDD đòi hỏi kỹ năng và quy trình tiếp cận có hệ thống. Khó khăn lớn nhất là lập trình viên thường thiếu kiến thức về domain và phải xây dựng hệ thống dựa trên khái niệm này.
Mục Lục
Ngôn Ngữ Chung (Ubiquitous Language)
Ngôn ngữ chung giữa domain expert và developer là yếu tố then chốt. Việc sử dụng chung ngôn ngữ giúp tránh mọi hiểu lầm, sai sót trong quá trình xây dựng và phát triển ứng dụng. Phần lớn các lỗi đều xuất phát từ việc khách hàng diễn đạt một cách, còn developer lại hiểu theo một cách khác. Nhiều domain có các thuật ngữ riêng, xa lạ với developer, gây ra nhầm lẫn khi thảo luận về ứng dụng. Ngôn ngữ chung giúp giảm thiểu những nhầm lẫn này.
Việc phản ánh các thuật ngữ, khái niệm của domain vào source code thông qua việc đặt tên package, class, method, properties… là hoàn toàn khả thi. Và tất nhiên, cần phản ánh vào mọi feature để đảm bảo khách hàng cũng có thể hiểu được chúng là gì.
Tóm lại, Ngôn ngữ chung (UL):
- Là ngôn ngữ được sử dụng chung giữa kỹ thuật và nghiệp vụ.
- Được thể hiện trong code.
- Được thể hiện trong tất cả tính năng của hệ thống.
Ngữ Cảnh Giới Hạn (Bounded Context)
Trong DDD, ý tưởng chính là chia hệ thống phức tạp dựa trên các domain của nó. Tuy nhiên, đôi khi một số domain lại chồng chéo lên nhau, hoặc đối với các đối tượng khác nhau thì domain tương ứng cũng khác nhau. Ví dụ, nghiệp vụ xuất hóa đơn có thể có cách xử lý khác nhau đối với từng đối tượng. Điều này dẫn đến sự phức tạp trong logic.
Để giải quyết vấn đề này, cần chia hệ thống thành các hệ thống con phục vụ cho những đối tượng nhất định, mỗi hệ thống con có domain tương ứng. Nói cách khác, chia hệ thống dựa trên những ngữ cảnh cụ thể, giới hạn từng đối tượng, từng domain (Bounded Context). Với ý tưởng “chia để trị”, DDD rất phù hợp cho việc áp dụng microservice. Việc chia thành các ngữ cảnh cụ thể tương đương với việc tách các xử lý logic và tách biệt về cơ sở dữ liệu. Mức độ giới hạn của các ngữ cảnh hoàn toàn phụ thuộc vào nhu cầu thực tế.
Các ngữ cảnh được chia nhỏ đều dựa trên một domain lớn, có nghĩa là chúng có liên quan đến nhau. Tuy nhiên, chúng cần được tách biệt và không phụ thuộc lẫn nhau. Xu hướng thiết kế hiện đại là đảo ngược sự phụ thuộc, và DDD cũng vậy. Thay vì phụ thuộc lẫn nhau, chúng ta sẽ tạo ra một layer trung gian (Anti-Corruption Layer) ở giữa hai ngữ cảnh và cho chúng phụ thuộc vào layer này. Điều này hữu ích khi thay đổi hoặc tái cấu trúc các ngữ cảnh, vì chỉ cần sửa đổi layer trung gian mà không ảnh hưởng đến các ngữ cảnh.
Alt: Sơ đồ Bounded Context minh họa sự tương tác và phân chia trách nhiệm giữa các ngữ cảnh khác nhau trong một hệ thống DDD, với lớp Anti-Corruption Layer bảo vệ sự toàn vẹn dữ liệu.
Tóm lại, Bounded Context:
- Là domain độc lập.
- Được kết nối với nhau thông qua Anti-Corruption Layer.
- Độc lập về database.
- Phù hợp để áp dụng cho microservice.
Các Thành Phần Cơ Bản (Basic Elements)
Thực Thể (Entity)
Trong DDD, mô hình hóa các domain là yếu tố quan trọng để cả developer lẫn domain expert đều hiểu rõ. Để mô hình hóa, thành phần không thể thiếu là các entity (thực thể). Tất cả các domain đều phải có đối tượng cụ thể. Entity trong DDD khác biệt ở chỗ nó phải được định danh (có ID) và định danh phải bất biến, duy nhất trong toàn bộ hệ thống. Việc phân biệt các thực thể là rất quan trọng, đặc biệt khi chia hệ thống dựa theo các domain, có thể dẫn đến việc đối tượng trong nhiều domain thực chất là một đối tượng. Việc gắn ID sẽ giúp xác định đối tượng có là một hay không.
Entity cần chứa logic của riêng nó để có thể sử dụng trên nhiều domain, đảm bảo tính nhất quán của hệ thống và giảm bớt những xử lý dư thừa. Entity cũng cần có life cycle (creation and deletion) trong chính bản thân nó.
Ví dụ, thẻ Visa là một entity. Nó có thể được dùng trong nhiều domain khác nhau như thanh toán online, chuyển tiền qua tài khoản khác… Mã thẻ (số tài khoản) là duy nhất, không được phép trùng với bất kì thẻ nào khác và không thể thay đổi. Thẻ có thông tin về ngày khả dụng, ngày hết hạn, tương ứng với life cycle của nó.
Tóm lại, entity trong DDD là một đối tượng:
- Có định danh bất biến và duy nhất.
- Chứa life cycle: creation và deletion.
- Nên chứa các logic của riêng nó thay vì thiết kế theo anemic model.
Đối Tượng Giá Trị (Value Type)
Không phải đối tượng nào cũng cần định danh. Đối tượng không có định danh được gọi là value type (hay value object). Những đối tượng này thường dùng để lưu giá trị và không cần phân biệt với nhau, chỉ đơn giản là lưu giá trị. Ví dụ, voucher giảm giá, người ta chỉ quan tâm đến giá trị giảm giá được in trên voucher.
Value type đóng vai trò lưu trữ các giá trị của setting, kết hợp với entity để giúp entity phân biệt nhau trong từng domain. Value type có tính bất biến: một khi được tạo ra thì nó không thể thay đổi trong vòng đời của nó.
Điều cần lưu ý khi sử dụng value type là chúng sẽ như nhau khi có giá trị giống nhau, không có sự khác biệt. Vì vậy, hãy chắc chắn rằng tất cả các value type đều bình đẳng với nhau vì không có sự khác biệt khi các thuộc tính đều có cùng giá trị.
Tóm lại, value type là object:
- Không có định danh.
- Nếu các thuộc tính đều có cùng giá trị thì là các object như nhau.
Tập Hợp (Aggregates)
Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it’s useful to treat the order (together with its line items) as a single aggregate. – Martin Fowler
Một Aggregate là một nhóm các đối tượng có thể được xem như là một đơn vị thống nhất. Để dễ hình dung, hãy nghĩ về hình ảnh chùm nho. Một chùm nho có nhiều trái nho được nối với nhau trên một cuống nho và nối với thân cây nho thông qua gốc của cuống nho đó.
Một aggregate bao gồm các entity. Các entity trong nội bộ aggregate có thể tự do tham chiếu đến nhau, tuy nhiên muốn tham chiếu đến đối tượng nằm ở aggregate khác thì phải thông qua gốc của aggregate (aggregate root). Điều này giúp giảm bớt sự phụ thuộc giữa các entity trong hệ thống. Thay vì chúng phải kết nối lẫn nhau, chúng chỉ cần liên kết thông qua các aggregate root.
Trong một aggregate, phải đảm bảo có đầy đủ các logic liên quan đến tất cả entity chứa trong nó để các entity có thể giao tiếp với nhau. Những aggregate khác muốn tác động đến các entity này chỉ cần sử dụng các logic đó mà thôi, không nhất thiết phải tạo thêm logic chỉ đích danh chính entity đó.
Đúc kết lại, aggregate là:
- Một tập hợp các thực thể.
- Các xử lý đều thông qua root entity nên có thể xem aggregate là một đơn vị thống nhất. Lưu ý: aggregate phải chứa các xử lý logic liên quan đến tất cả các entity.
Domain Services
Service trong DDD khác biệt với service trong anemic model. Với anemic modal, service được xây dựng dựa trên các entity, là nơi xử lý các đối tượng đó. DDD chú trọng vào domain, và service là các chức năng trong domain đó. Service sẽ cung cấp các chức năng cho domain. Việc này sẽ đưa service phụ thuộc vào module cấp cao hơn là domain.
Domain service là nơi chứa các logic quá phức tạp mà trong phạm vi entity không thể làm được. Service cũng là nơi chứa các logic làm việc trên nhiều aggregate.
Vậy, domain service là:
- Nơi chứa các chức năng của domain.
- Chứa các logic phức tạp mà không thể chứa trong phạm vi entity hoặc những logic làm việc với nhiều aggregate.
Alt: Minh họa Domain Services trong DDD, cho thấy sự tương tác giữa Domain Services, Entities, và Aggregates để thực hiện các nghiệp vụ phức tạp.
Microservice đang là một xu hướng lập trình chủ chốt cho mọi ứng dụng lớn và DDD chính là khởi đầu cho việc triển khai microservice. Bài viết này hi vọng mang đến cho bạn cái nhìn sơ bộ về DDD và các thành phần cốt lõi của nó. Vì chỉ là cốt lõi nên chắc chắn còn nhiều thành phần khác chưa được đề cập tới. Hy vọng nhận được nhiều góp ý, trao đổi từ các bạn để có thể nắm rõ hơn về DDD.
Cảm ơn bạn đã dành thời gian đọc bài viết này. Chúc bạn một ngày tốt lành!
