Khi nào một Root tổng hợp có chứa AR khác (và khi nào thì không nên)


15

Hãy để tôi bắt đầu bằng cách xin lỗi đầu tiên về độ dài của bài đăng, nhưng tôi thực sự muốn truyền đạt chi tiết như trước để tôi không mất thời gian của bạn qua lại trong các bình luận.

Tôi đang thiết kế một ứng dụng theo cách tiếp cận DDD và đang tự hỏi tôi có thể làm theo hướng dẫn nào để xác định liệu Root tổng hợp có nên chứa AR khác hay không nếu chúng được để lại dưới dạng AR riêng biệt, "tự do".

Hãy xem trường hợp của ứng dụng Đồng hồ thời gian đơn giản cho phép Nhân viên tự đồng hồ vào hoặc ra trong ngày. Giao diện người dùng cho phép họ nhập ID nhân viên và mã PIN sau đó được xác thực và trạng thái hiện tại của nhân viên được truy xuất. Nếu nhân viên hiện đang có đồng hồ, giao diện người dùng sẽ hiển thị nút "Đồng hồ hết"; và ngược lại, nếu chúng không có đồng hồ, nút này ghi "Đồng hồ trong". Hành động được thực hiện bằng nút cũng tương ứng với trạng thái của nhân viên.

Ứng dụng này là một máy khách web gọi một máy chủ phụ được hiển thị thông qua giao diện dịch vụ RESTful. Lần đầu tiên tôi vượt qua trong việc tạo các URL trực quan, dễ đọc dẫn đến hai điểm cuối sau:

http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout

LƯU Ý: Chúng được sử dụng sau khi ID nhân viên và mã PIN đã được xác thực và "mã thông báo" đại diện cho "người dùng" được chuyển trong tiêu đề. Điều này là do có "chế độ người quản lý" cho phép người quản lý hoặc người giám sát đồng hồ vào hoặc ra một nhân viên khác. Nhưng, vì lợi ích của cuộc thảo luận này, tôi đang cố gắng làm cho nó đơn giản.

Trên máy chủ, tôi có ApplicationService cung cấp API. Ý tưởng ban đầu của tôi cho phương thức ClockIn là:

public void ClockIn(String id)
{
    var employee = EmployeeRepository.FindById(id);

    if (employee == null) throw SomeException();

    employee.ClockIn();

    EmployeeRepository.Save();
}

Điều này có vẻ khá đơn giản cho đến khi chúng tôi nhận ra rằng thông tin thẻ thời gian của Nhân viên thực sự được duy trì như một danh sách các giao dịch. Điều đó có nghĩa là mỗi lần tôi gọi ClockIn hoặc ClockOut, tôi không trực tiếp thay đổi trạng thái của Nhân viên mà thay vào đó, tôi đang thêm một mục mới vào TimeSheet của Nhân viên. Trạng thái hiện tại của Nhân viên (có trong hoặc không) được lấy từ mục nhập gần đây nhất trong TimeSheet.

Vì vậy, nếu tôi sử dụng mã được hiển thị ở trên, kho lưu trữ của tôi phải nhận ra rằng các thuộc tính bền vững của Nhân viên không thay đổi nhưng mục nhập mới đã được thêm vào TimeSheet của Nhân viên và thực hiện chèn vào kho lưu trữ dữ liệu.

Mặt khác (và đây là câu hỏi cuối cùng của bài đăng), TimeSheet có vẻ như đó là một Root tổng hợp cũng như nó có danh tính (ID nhân viên và thời gian) và tôi có thể dễ dàng thực hiện logic tương tự như TimeSheet.ClockIn (Mã hiệu công nhân).

Tôi thấy mình đang tranh luận về ưu điểm của hai cách tiếp cận và, như đã nêu trong đoạn mở đầu, tự hỏi tôi nên đánh giá tiêu chí nào để xác định cách tiếp cận nào phù hợp hơn cho vấn đề.


Tôi đã chỉnh sửa / cập nhật bài đăng để câu hỏi rõ ràng hơn và sử dụng kịch bản tốt hơn (hy vọng).
SonOfPirate

Câu trả lời:


4

Tôi sẽ có xu hướng triển khai một dịch vụ để theo dõi thời gian:

public interface ITimeSheetTrackingService
{
   void TrackClockIn(Employee employee, Timesheet timesheet);

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

Tôi không nghĩ rằng trách nhiệm của mình là bấm giờ hay hết giờ, đó không phải là trách nhiệm của nhân viên.


3

Các gốc tổng hợp không được chứa lẫn nhau (mặc dù chúng có thể chứa ID cho người khác).

Đầu tiên, TimeSheet có thực sự là một gốc tổng hợp không? Thực tế là nó có bản sắc làm cho nó trở thành một thực thể, không nhất thiết phải là một AR. Kiểm tra một trong các quy tắc:

Root Entities have global identity.  Entities inside the boundary have local 
identity, unique only within the Aggregate.

Bạn xác định danh tính của TimeSheet là ID nhân viên và khoảng thời gian, điều này cho thấy TimeSheet là một phần của Nhân viên với khoảng thời gian là danh tính cục bộ. Vì đây là ứng dụng Đồng hồ thời gian , mục đích chính của Nhân viên là trở thành vật chứa cho TimeSheets?

Giả sử bạn phải sử dụng AR, ClockIn và ClockOut có vẻ giống các hoạt động của TimeSheet hơn là nhân viên. Phương thức lớp dịch vụ của bạn có thể trông giống như thế này:

public void ClockIn(String employeeId)
{
    var timeSheet = TimeSheetRepository.FindByEmployeeAndTime(employeeId, DateTime.Now);    
    timeSheet.ClockIn();    
    TimeSheetRepository.Save();
}

Nếu bạn thực sự cần theo dõi trạng thái đồng hồ trong cả Nhân viên và TimeSheet, thì hãy xem Sự kiện Miền (Tôi không tin rằng chúng có trong sách của Evans, nhưng có rất nhiều bài viết trực tuyến). Nó sẽ trông giống như: Employee.ClockIn () làm tăng sự kiện EmployeeClockedIn, một trình xử lý sự kiện chọn và lần lượt gọi TimeSheet.LogEmployeeClockIn ().


Tôi thấy điểm của bạn. Tuy nhiên, có một số quy tắc nhất định ràng buộc nếu và khi Nhân viên có thể được bấm giờ. Các quy tắc này chủ yếu được điều khiển bởi trạng thái hiện tại của Nhân viên, chẳng hạn như họ có bị chấm dứt, đã đồng hồ, được lên lịch để làm việc vào ngày hôm đó hoặc thậm chí sự thay đổi hiện tại, vv TimeSheet có nên sở hữu kiến ​​thức này không?
SonOfPirate

3

Tôi đang thiết kế một ứng dụng theo cách tiếp cận DDD và đang tự hỏi tôi có thể làm theo hướng dẫn nào để xác định liệu Root tổng hợp có nên chứa AR khác hay không nếu chúng được để lại dưới dạng AR riêng biệt, "tự do".

Một gốc tổng hợp không bao giờ nên chứa một gốc tổng hợp khác.

Trong mô tả của bạn, bạn có một thực thể Nhân viên , và tương tự như một thực thể Timesheet. Hai thực thể này là khác biệt, nhưng có thể bao gồm các tham chiếu cho nhau (ví dụ: đây là bảng chấm công của Bob).

Đó là nhiều mô hình cơ bản.

Câu hỏi gốc tổng hợp là hơi khác nhau. Nếu hai thực thể này khác biệt về mặt giao dịch với nhau, thì chúng có thể được mô hình chính xác thành hai tập hợp riêng biệt. Bằng cách giao dịch khác biệt, tôi có nghĩa là không có bất biến kinh doanh nào đòi hỏi phải biết trạng thái hiện tại của Nhân viên và trạng thái hiện tại của TimeSheet.

Dựa trên những gì bạn mô tả, Timesheet.clockIn và Timesheet.clockOut không bao giờ cần kiểm tra bất kỳ dữ liệu nào trong Nhân viên để xác định xem lệnh có được phép hay không. Vì vậy, đối với phần lớn vấn đề này, hai AR khác nhau có vẻ hợp lý.

Một cách khác để xem xét các ranh giới AR sẽ là hỏi loại chỉnh sửa nào được phép xảy ra đồng thời. Người quản lý có được phép cho nhân viên ra ngoài đồng thời nhân sự đang loay hoay với hồ sơ của nhân viên không?

Mặt khác (và đây là câu hỏi cuối cùng của bài đăng), TimeSheet có vẻ như đó là một Root tổng hợp cũng như nó có danh tính (ID nhân viên và thời gian)

Danh tính chỉ có nghĩa là nó là một thực thể - đó là bất biến kinh doanh thúc đẩy liệu nó có nên được coi là một gốc tổng hợp riêng biệt hay không.

Tuy nhiên, có một số quy tắc nhất định ràng buộc nếu và khi Nhân viên có thể được bấm giờ. Các quy tắc này chủ yếu được điều khiển bởi trạng thái hiện tại của Nhân viên, chẳng hạn như họ có bị chấm dứt, đã đồng hồ, được lên lịch để làm việc vào ngày hôm đó hoặc thậm chí sự thay đổi hiện tại, v.v. TimeSheet có nên sở hữu kiến ​​thức này không?

Có lẽ - tổng hợp nên. Điều đó không nhất thiết có nghĩa là Bảng thời gian nên.

Điều đó có nghĩa là, nếu các quy tắc sửa đổi Bảng thời gian phụ thuộc vào trạng thái hiện tại của thực thể Nhân viên, thì Nhân viên và Bảng thời gian chắc chắn cần phải là một phần của cùng một tổng hợp và toàn bộ tổng thể chịu trách nhiệm đảm bảo rằng các quy tắc đó theo sau.

Một tập hợp có một thực thể gốc; xác định nó là một phần của câu đố. Nếu một Nhân viên có nhiều Timesheet và cả hai đều là một phần của cùng một tổng hợp, thì Timesheet chắc chắn không phải là root. Điều đó có nghĩa là ứng dụng không thể trực tiếp sửa đổi hoặc gửi các lệnh đến bảng thời gian - chúng cần được gửi đến đối tượng gốc (có lẽ là Nhân viên), có thể ủy thác một số trách nhiệm.

Một kiểm tra khác sẽ được xem xét làm thế nào Timesheets được tạo ra. Nếu chúng được tạo ra một cách ngầm định khi một nhân viên đồng hồ vào, thì đó là một gợi ý khác rằng họ là một thực thể cấp dưới trong tổng hợp.

Như một bên, không chắc rằng tập hợp của bạn nên có ý thức về thời gian của riêng họ. Thay vào đó, thời gian nên được truyền cho họ

employee.clockIn(when);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.