Xàm xí về Domain Driven Design - Part 1: Quá trình giác ngộ

- 12 mins

Markdowm Image

Có bao giờ bạn làm việc trong một Enterprise System thực sự lớn, nơi mà bạn phãi trăn trở với hàng tá vấn đề, từ việc đáp ứng một lượng Business Logic khổng cho đến việc design tổ chức sao cho ứng dụng của bạn chạy ngon lành dưới sức ép của high concurrency context. Hơn thế nữa, business luôn luôn thay đổi cùng với sự chồng chéo rối rắm khiến bạn stress, hiệu xuất làm việc của bạn đi xuống, làm thế nào để lường trước và tránh tình trạng đó xãy ra?

Có bao giờ bạn làm việc trong một codebase mà cứ hể thêm code vào là thấy một nùi phức tạp lòi ra? Hơn nữa mỗi một thay đổi bạn không thể biết nó sẻ đụng chạm đến những thứ nào khác hay không, làm sao để tránh tạo ra một code base kiểu đó?

Thử đứng dưới góc độ của một người quản lí, làm sao chúng ta control việc phát triển của một ứng dụng phức tạp một cách mượt mà trơn tru. Nơi mà sự phức tạp cứ luôn luôn tăng, luôn luôn ập đến? Điều đó cũng kéo theo quá nhiều rủi ro, vậy chúng ta phãi quản lí, đối phó với những rủi ro đó như thế nào?

Làm sao chúng ta chia một hệ thống lớn thành nhiều phần và giao chúng cho các team khác nhau cùng phối hợp phát triển và tích hợp chúng lại một cách mượt mà, nhịp nhàng và hiệu quả?

Mình rất may mắn khi có trải nghiệm, cái nhìn về những vấn đề trên khi tham gia vào một dự án khá lớn với vài chục modules, phãi đáp ứng được lượng lớn logic dưới sức ép của concurrency và big data context. Trong mỗi module là rất nhiều sự phức tạp đến từ nhiều concerns khác nhau, điều đó phần nào giúp mình nếm trãi được sự đắng cay của complexity. Bên cạnh đó cũng có một số sai lầm đến trong cách apply kiến trúc Event Driven(Reactive System) mà nếu có nền tảng về DDD thì có thể đã thực sự apply tốt hơn. Domain Driven Design chính xác là bài thuốc đã phần nào giúp mình thoát khỏi những nổi ám ảnh đó. Bảo vệ hệ thống khỏi sự phức tạp cũng như giúp tạo nền tảng để xây dựng Reactive System đáp ứng được các yêu cầu về large scalable, high concurrency cũng như tổ chức các component một cách declarative hơn. Nó là bài thuốc gần như là tốt nhất dành cho các Enterprise System phức tạp.

Với hơn 15 năm phát triển kể từ khi được giới thiệu từ năm 2003, DDD bây giờ không chỉ là một phương pháp, cách tiếp cận trong thiết kế phần mềm nữa. DDD thực sự cần thiết trong mọi ngóc ngách của quá trình phát triển phần mềm. Từ khâu tiếp nhận requirement, thảo luận làm rõ domain knowledge đến khâu decouple, design cho đến tổ chức quản lí phối hợp các đội nhóm, quản lí risk và planning. DDD thực sự powerful đối với dân chúng ta.

Trong chuỗi bài xàm xí này mình sẻ cố gắn tập trung ghi lại kiến thức bản thân về Domain Driven Design, bạn đọc nếu có thấy bất cứ sai lầm thiếu sót nào mong rằng sẻ comment ở dưới để mình cũng cố thêm.

Mở đầu cho chuổi xàm xí này mình sẻ điểm nhanh trãi nghiệm bản thân qua các dự án với các mẫu kiến trúc.

1. MVC Architecture:

Markdowm Image

Khi bắt đầu đi làm, mẫu thiết kế đầu tiên mình được tiếp cận đó là MVC. Nhìn chung với ứng dụng nhỏ đơn giản thì mô hình này khá ổn.

Code được tổ chức riêng biệt thành các thành phần Model - View - Controller tách bạch rõ ràng, bước đầu tương đối dễ quản lí và phát triển dự án. Code base khá đơn giản, đáp ứng yêu cầu về tốc độ phát triển. Người mới có thể nhanh chóng nắm được bộ source.

Tuy nhiên, với đội ngủ có chuyên môn chưa cao, chưa thật sự cứng với các mô hình, principles như SOLID chẳng hạn thì thật khó để quản lí một cách lâu dài. Với đội ngủ chuyên môn chưa cao thì làm việc với MVC sẻ mãi là MVC, không có khả năng ứng phó với sự phức tạp và refactor cho hợp lí. Vì logic quá tập trung đóng gói trong Data model lẫn Business Model, các model của chúng ta có xu hướng phình to và rất khó có thể chia nhỏ cả bề ngang lẫn bề dọc. MVC thật sự là không còn khả dụng với các ứng dụng vừa và lớn.

2. Database Centric Approach với mô hình 3-Layers và Transaction Script

Markdowm Image

Đa số các dự án mình tham gia trước đó đều dựa trên Database Centric Approach với mô hình 3-Layers và Transaction Script.

Với Database-Centric Approach, database luôn là trung tâm của ứng dụng, mọi thứ sẻ phãi phụ thuộc vào database. Với approach này business logic có xu hướng dính chặt với data access logic, thường đi đôi với Transaction Script partern. Với Transaction Script chúng ta sẻ rất khó để chia thành các layer. Trong trường hợp chúng ta sử dụng Relational Database thì Business Logic thường chứa các lệnh thủ tục với database luôn, do đó việc phát triển ứng dụng thường được bắt đầu bằng việc design database.

Database-Centric Approach cùng với Transaction Script thật sự tốt khi ứng dụng chúng ta chỉ có CRUD đơn giản và chứa ít Business Rules.

Markdowm Image

Với cách tiếp cận này để có thể reuse code chúng ta thường dùng database làm điểm tích hợp giữa các thành phần. Bằng việc sử dụng các thư viện, helper liên quan đến Database như CrudRepository với Java Bean trong Spring chẳng hạn, với cơ chế auto implement rất tiện lợi sẻ giúp chúng ta tiết kiệm rất nhiều effort trong việc phát triển ứng dụng. Trong Go và Nodejs ngày trước mình cũng sử dụng luôn các thư viện ORM cho việc implement data access logic, chúng thực sự hữu ích và tiện lợi đối với ứng dụng nhỏ.

Markdowm Image

Tuy nhiên với Database Centric approach, vì Business Logic có xu hướng dính chặt với Data Access Logic đâm ra lúc Business cần thay đổi, mở rộng hoặc gở bỏ thường làm Dev rất vất vã do mọi thứ dính chùm với nhau. Tương tự việc optimize cũng diễn ra thực sự nhọc nhằn.

Cùng với đó là sự gia tăng của complexity gây áp lực lên ứng dụng dựa trên Database Centric approach. Mình có cơ hội thấy hồi kết của approach này với một ứng dụng thực sự phức tạp. Sau một thời gian nhất định, mọi nổ lực để thêm mới Business Rules hoặc thay đổi một thứ gì đó gần như là không thể.

Với cách tiếp cận này mình cũng có một trãi nghiệm cay đắng đó là khi phát triển một service dựa trên code base có sẳn với mô hình 3 lớp như trên. Về bản chất 3-Layered Architecture thực sự mạnh mẻ và có khả năng mở rộng cao, điều đó được chứng minh qua thời gian bởi đã có rất nhiều project thành công với lối kiến trúc này.

3-Layered Architecture đã tách bạch các concerns với 3 tầng Presentation Layer, Business Layer và Data Layer. Điều đó gíup chúng ta đạt được tính linh hoạt và khả năng mở rộng cao cho một hệ thống phần mềm. Tuy nhiên khi làm việc cùng Database Centric Approach, các engineer thường vướng một sai lầm, mình cũng đã tiếp nối sai lầm của các engineer trước đó của một bộ source. Đó là việc dùng Transaction Script partern.

Transaction Script biểu diễn business logic bằng code thủ tục với database, dễ dàng sử dụng cũng như giúp chúng ta tránh được những vấn đề performance với Database tuy nhiên sẻ làm cho Data Access Layer và Business Layer dính chặt với nhau.

Khi bước vào với dự án mình đã cảm giác có gì đó không ổn và muốn chia thêm layer theo hướng Clean Architecture, nhưng với Database Centric approach, với Anamic Model approach và Transaction Script thì việc chia tầng là điều không thể làm đối với một lượng logic lớn. Bên cạnh đó việc thiếu khả năng thuyết phục cấp trên một khoản upfront cost cho một kiến trúc phức tạp ngay từ ban đầu với Domain Centric approach và Clean Architecture, cùng với đó là các yêu cầu business ập đến bắt buộc phãi sống chung với lũ, do đó technical debt liên tục tăng lên đến một thời điểm dường như không nhúc nhích được.

Với Database Centric approach nói riêng và Infrastructure nói chung thì không chỉ gây ra cục phức tạp mà còn dẫn đến quá tải trong cách tiếp cận với đề bởi các concerns khác nhau. Ví dụ như khi mới tiếp cận một vấn đề nếu chúng ta tiếp cận thoe hướng Domain Centric chúng ta sẻ thiết kế domain model và các business rule trước, sau đó mới lựa chọn database, cache, message…và design, optimize cho phù hợp thì ngược lại nếu tiếp cận theo hướng Infrastructure Centric thì rõ ràng chúng ta đã gom việc implement business logic và optimize design vào một, điều đó sẻ dẫn đến nhiều quá tải cho engineer.

3. Anemic Domain Model vs Rich Domain Model

Markdowm Image

Có một thực tế rằng trước khi nghiêm túc với DDD và apply nó vào dự án thì cho dù sau một số dự án lớn nhỏ dù đã biết những ý tưởng về Clean Architecture đi chăng nữa mình vẫn chưa nhận thức được tính thiết yếu của Rich Domain Model đối với một ứng dụng phức tạp.

Với sự thích thú đối với Functioncal Programming đặc biệt là ghét tính mutable đã dẫn lối mình luôn sử dụng Anemic Domain Model. Khi đó các Domain Model của mình chỉ là các Enitity model được biểu diễn bởi các class/struct chỉ chứa cấu trúc dữ liệu và mối quan hệ giữa chúng với các Entity khác. Với việc các Entity Model này ít khi chứa logic nghiệp vụ dẫn đến bắt buộc tụi mình phãi đẩy hết logic nghiệp vụ vào tầng service(đối với mô hình ba lớp) và đẩy vào tầng usecases(Clean Architecture).

Cũng theo một lẻ tự nhiên, các Entity đó lập tức được ánh xạ quán tính 1:1 với một Table trong Database ngay. Lúc đó việc quản lí việc persisting của các Entity trở nên rối rắm hổn độn.

Hai tình huống xãy ra đó lúc đó: Một là chấp nhận việc lỏng lẻo trong consystency. Hai là dùng transaction script giữa nhiều Entity(đại diện cho nhiều Table) để quản lí transaction. Rõ ràng hãy thử tưởng tượng với một lượng logic khổng lồ xem nó sẻ rối như thế nào khi dùng transaction script.

Việc sử dụng Anemic model cũng là một nguyên nhân che mắt làm chúng ta không thấy rõ được giá trị của việc design các Aggregate tương ứng với các Repository. Điều đó gây nên một số hậu quả dẫn tới vấn đề concurrency conflict hoặc làm phức tạp hoá việc implement các usecases chịu ảnh hưởng yếu tố concurrency.

Markdowm Image

DDD với OOP mindset mang lại cách tiếp cận hoàn toàn khác về việc chia code theo từng layer và model hoá dựa trên Rich Domain Model thay vì Anemic Model đã giúp giảm đi sự gia tăng phức tạp theo thời gian.

Rich Domain Model vừa mô tả Domain Data Structure vừa mô tả các bihaviour của chúng. Các domain service/usecases chỉ cần chỉ định các Object Model thực thi domain logic(bihaviour trong chúng). Do đó giảm tải domain logic lên các service, usecases đồng thời giúp chúng ta dễ mở rộng hơn.

4. Conclusion

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora