logoMột hệ thống khó hiểu thì cũng khó thay đổi
DDD - Part 5: Aggregate (2)

phần trước, chúng ta đã bàn về Aggregate - một building block quan trọng của DDD, ở đó, các Domain Object được gom lại với nhau trong một ranh giới nhất quán. 

Trong ví dụ đó, thay vì sử dụng các Entity Box và Seal một cách rời rạc (dù là tốt hơn nhiều so với cách triển khai Transaction Script) khiến cho mô hình nghiệp vụ trở nên phức tạp, khó kiểm soát, dễ dẫn đến mất nhất quán bằng một Box Aggregate giải quyết ổn thỏa vấn đề. Nhưng, đó là do nghiệp vụ rất phù hợp: Seal chỉ được tạo khi dán vào Box. 

Còn với yêu cầu này thì sao: Người dùng có thể tạo ra một tập Seal với SealCode có sẵn cũng như các thông tin khác như trạng thái (Created – khởi tạo) và ngày giờ tạo. Khi cần niêm phong một Box, người dùng chỉ cần lấy Seal trong tập ra và dán vào Box là xong chứ không cần tạo Seal nữa.

Với yêu cầu này, ta thấy vòng đời của Seal và Box là khác nhau và chúng vẫn là hai Entity trong Domain của ta. Box không còn quản lý vòng đời của Seal được nữa. Do vậy, đây là hai Aggregate độc lập nhau: Box Aggregate và Seal Aggregate. Chúng tham chiếu nhau qua ID chứ không được trực tiếp (vì trực tiếp thì lại trở thành nested nhưng vẫn được dùng bên ngoài nên phá vỡ quy định của Aggregate). Bức tranh sau cho thấy điều này:

Hình 1: Hai Aggregates Box và Seal tham chiếu nhau chỉ qua ID của chúng

Hình này cho thấy một điều khó hiểu: nó giống như code ban đầu của ta ở hình này. Liệu ta sẽ lại phải sử dụng hai lời gọi boxRepository.save(box)sealRepository.save(seal) đồng thời?

Cách tiếp cận Eventual Consistency

DDD cho rằng, nếu bạn không thể gom chúng, Box và Seal, lại trong một Aggregate thì hãy sử dụng Eventual Consistency. Cụ thể quá trình này thực hiện như sau:

1.      Box được chuyển trạng thái trước và công bố một event, BoxSealed. Sau đó save trạng thái Box qua BoxRepository.

2.      Chính Bounded Context này lắng nghe BoxSealed event rồi chuyển trạng thái Seal tương ứng. Sau đó save trạng thái Seal qua SealRepository.

Tạm thời bỏ qua Domain Event, ta có hai use case như sau:

Use case 1: Tiếp nhận thao tác người dùng: Dán Seal => Cập nhật trạng thái chỉ cho Box và tạo Domain Event:

Hình 2: Use case sealBox giờ thay đổi cả signature và cách hoạt động với sự tham gia của Domain Event BoxSealed. Hệ thống chưa đạt trạng thái nhất quán.

Giao diện use case thay đổi so với trước. Bởi Seal đã tồn tại và người dùng đang dùng Seal để dán vào Box nên use case tiếp nhận hai tham số sealCode boxId.

Tương tác giữa Box và Seal là trực tiếp ở hàm box.sealBox(seal) chứ không còn là sealCode nữa. Chú ý rằng, bên trong hàm box.sealBox(), Box không thay đổi trạng thái của Seal mà chỉ dùng Seal để kiểm tra logic, Box chỉ thay đổi trạng thái của nó. Kết thúc, hệ thống chưa đạt trạng thái nhất quán.

Use case 2: Tiếp nhận Domain Event trên, lấy ra Seal và Box, cập nhật trạng thái chỉ cho Seal và kết thúc use case dán Seal cho Box:

Hình 3: Tiếp nhận BoxSealed event, hệ thống cập nhật Seal. Hệ thống đạt trạng thái nhất quán.

Hàm updateSeal() là một nửa còn lại để hoàn thiện luồng xử lý. Nó tiếp nhận Domain Event BoxSealed bởi bên trong event này đã chứa những thông tin cần thiết như BoxId, SealCode cần thiết. Use case lấy ra các đối tượng cần thiết và gọi hàm seal.applySeal(box) để cập nhật trạng thái của Seal. Lúc này, trạng thái dữ liệu hệ thống trở thành nhất quán sau lời gọi sealRepository.save(seal).

Code của Seal.applySeal có dạng:

Hình 3: Hành vi applySeal của Seal đã thay đổi với logic đơn giản hơn

Một phần nghiệp vụ check trạng thái Box đã không còn cần thiết vì điều này đã xảy ra ở bên Box, do đó, tôi đã comment chúng. Ngoài ra, tôi còn bỏ đi lệnh cập nhật sealCode cho Box vì điều này cũng đã xảy ra. Seal Aggregate và Box Aggregate không tác động dữ liệu của nhau.

Aggregate thế nào là hợp lý?

<<...>>

 

Bình luận
Gửi bình luận
Bình luận