Tôi đã điều chỉnh CQRS 1 của người nghèo từ khá lâu rồi vì tôi thích sự linh hoạt của nó để có dữ liệu dạng hạt trong một kho dữ liệu, cung cấp khả năng phân tích tuyệt vời và do đó tăng giá trị doanh nghiệp và khi cần một dữ liệu khác có chứa dữ liệu không chuẩn hóa để tăng hiệu suất .
Nhưng thật không may, ngay từ đầu tôi đã phải vật lộn với vấn đề chính xác là tôi nên đặt logic kinh doanh trong loại kiến trúc này.
Theo những gì tôi hiểu, một lệnh là một phương tiện để truyền đạt ý định và bản thân nó không có quan hệ với một miền. Chúng cơ bản là dữ liệu (câm - nếu bạn muốn) chuyển đối tượng. Điều này là để làm cho các lệnh dễ dàng chuyển giữa các công nghệ khác nhau. Áp dụng tương tự cho các sự kiện như phản ứng với các sự kiện hoàn thành thành công.
Trong một ứng dụng DDD điển hình, logic nghiệp vụ nằm trong các thực thể, các đối tượng giá trị, gốc tổng hợp, chúng rất phong phú về cả dữ liệu cũng như hành vi. Nhưng một lệnh không phải là một đối tượng miền, do đó không nên giới hạn trong việc biểu diễn dữ liệu miền, vì điều đó gây quá nhiều căng thẳng cho chúng.
Vì vậy, câu hỏi thực sự là: logic chính xác ở đâu?
Tôi đã phát hiện ra rằng tôi có xu hướng đối mặt với cuộc đấu tranh này thường xuyên nhất khi cố gắng xây dựng một tổng hợp khá phức tạp, đặt ra một số quy tắc về sự kết hợp các giá trị của nó. Ngoài ra, khi mô hình hóa các đối tượng miền tôi muốn theo mô hình không nhanh , biết khi nào một đối tượng đạt đến một phương thức thì nó ở trạng thái hợp lệ.
Giả Car
sử tổng hợp sử dụng hai thành phần:
Transmission
,Engine
.
Cả hai Transmission
và Engine
đối tượng giá trị được biểu diễn như các loại siêu và có theo loại phụ, Automatic
và Manual
được truyền đi, hay Petrol
và Electric
động cơ tương ứng.
Trong miền này, việc tự mình tạo thành công Transmission
, có thể Automatic
hoặc Manual
hoặc một trong hai loại Engine
là hoàn toàn tốt. Nhưng Car
tổng hợp giới thiệu một vài quy tắc mới, chỉ áp dụng khi Transmission
và Engine
các đối tượng được sử dụng trong cùng một bối cảnh. Cụ thể là:
- Khi một chiếc xe sử dụng
Electric
động cơ, loại truyền động duy nhất được phép làAutomatic
. - Khi một chiếc xe sử dụng
Petrol
động cơ, nó có thể có một trong hai loạiTransmission
.
Tôi có thể bắt vi phạm kết hợp thành phần này ở cấp độ tạo lệnh, nhưng như tôi đã nói trước đây, từ những gì tôi hiểu rằng không nên thực hiện vì lệnh sau đó sẽ chứa logic nghiệp vụ nên được giới hạn trong lớp miền.
Một trong các tùy chọn là di chuyển xác thực logic nghiệp vụ này sang lệnh của trình xác nhận hợp lệ, nhưng điều này dường như cũng không đúng. Cảm giác như tôi sẽ giải mã lệnh, kiểm tra các thuộc tính của nó được lấy bằng cách sử dụng getters và so sánh chúng trong trình xác nhận và kiểm tra kết quả. Điều đó hét lên như một sự vi phạm luật pháp của Demeter đối với tôi.
Loại bỏ tùy chọn xác nhận được đề cập bởi vì nó có vẻ không khả thi, có vẻ như người ta nên sử dụng lệnh và xây dựng tổng hợp từ nó. Nhưng logic này nên tồn tại ở đâu? Nó có nên nằm trong bộ xử lý lệnh chịu trách nhiệm xử lý một lệnh cụ thể không? Hoặc có lẽ nó nên nằm trong trình xác nhận lệnh (tôi cũng không thích cách tiếp cận này)?
Tôi hiện đang sử dụng một lệnh và tạo tổng hợp từ nó trong trình xử lý lệnh có trách nhiệm. Nhưng khi tôi làm điều này, nếu tôi có một trình xác nhận lệnh thì nó sẽ không chứa bất cứ thứ gì cả, bởi vì nếu CreateCar
lệnh tồn tại thì nó sẽ chứa các thành phần mà tôi biết là hợp lệ trong các trường hợp riêng biệt nhưng tổng hợp có thể nói khác nhau.
Chúng ta hãy tưởng tượng một kịch bản khác nhau trộn các quá trình xác nhận khác nhau - tạo một người dùng mới bằng cách sử dụng một CreateUser
lệnh.
Lệnh chứa Id
một người dùng sẽ được tạo và của họ Email
.
Hệ thống nêu các quy tắc sau cho địa chỉ email của người dùng:
- phải là duy nhất,
- không được để trống
- phải có tối đa 100 ký tự (độ dài tối đa của cột db).
Trong trường hợp này, mặc dù có một email duy nhất là một quy tắc kinh doanh, việc kiểm tra nó trong một tổng hợp có rất ít ý nghĩa, bởi vì tôi sẽ cần phải tải toàn bộ bộ email hiện tại trong hệ thống vào bộ nhớ và kiểm tra email trong lệnh chống lại tổng hợp ( Eeeek! Something, Something, Performance.). Do đó, tôi sẽ chuyển kiểm tra này sang trình xác nhận lệnh, sẽ sử dụng UserRepository
như một phụ thuộc và sử dụng kho lưu trữ để kiểm tra xem người dùng có email có trong lệnh đã tồn tại hay không.
Khi nói đến điều này, đột nhiên có ý nghĩa để đặt hai quy tắc email khác trong trình xác nhận lệnh. Nhưng tôi có cảm giác các quy tắc nên thực sự được trình bày trong một User
tổng hợp và trình xác nhận lệnh chỉ nên kiểm tra tính duy nhất và nếu xác thực thành công, tôi nên tiến hành tạo User
tổng hợp trong CreateUserCommandHandler
và chuyển nó vào kho lưu trữ để lưu.
Tôi cảm thấy như vậy bởi vì phương thức lưu của kho lưu trữ có khả năng chấp nhận một tổng hợp đảm bảo rằng một khi tổng hợp được thông qua, tất cả các bất biến đều được đáp ứng. Khi logic (ví dụ như không trống rỗng) chỉ xuất hiện trong chính xác thực lệnh, một lập trình viên khác hoàn toàn có thể bỏ qua xác thực này và gọi phương thức lưu trong UserRepository
một User
đối tượng trực tiếp có thể dẫn đến lỗi cơ sở dữ liệu nghiêm trọng, vì email có thể có đã quá lâu.
Làm thế nào để cá nhân bạn xử lý các xác nhận và biến đổi phức tạp? Tôi chủ yếu hài lòng với giải pháp của mình, nhưng tôi cảm thấy mình cần khẳng định rằng ý tưởng và cách tiếp cận của tôi không hoàn toàn ngu ngốc để khá hài lòng với các lựa chọn. Tôi hoàn toàn cởi mở với những cách tiếp cận hoàn toàn khác nhau. Nếu bạn có một cái gì đó mà cá nhân bạn đã cố gắng và làm việc rất tốt cho bạn, tôi rất muốn thấy giải pháp của bạn.
1 Làm việc như một nhà phát triển PHP chịu trách nhiệm tạo các hệ thống RESTful, việc giải thích CQRS của tôi làm lệch đi một chút so với cách tiếp cận xử lý lệnh async tiêu chuẩn , chẳng hạn như đôi khi trả về kết quả từ các lệnh do cần xử lý đồng bộ các lệnh.
CommandDispatcher
.