Kapan seharusnya Agregat Root mengandung AR lain (dan kapan seharusnya tidak)

15

Biarkan saya mulai dengan terlebih dahulu meminta maaf atas panjang posting, tapi saya benar-benar ingin menyampaikan detail sebanyak-banyaknya di muka sehingga saya tidak meluangkan waktu untuk bolak-balik dalam komentar.

Saya merancang aplikasi mengikuti pendekatan DDD dan saya bertanya-tanya panduan apa yang dapat saya ikuti untuk menentukan apakah Agregat Root harus mengandung AR lain atau jika harus dibiarkan terpisah, "berdiri bebas" AR.

Ambil contoh aplikasi Jam Waktu sederhana yang memungkinkan Karyawan untuk masuk atau keluar pada hari itu. UI memungkinkan mereka untuk memasukkan ID dan PIN karyawan mereka yang kemudian divalidasi dan status karyawan saat ini diambil. Jika karyawan saat ini clock-in, UI menampilkan tombol "Clock Out"; dan, sebaliknya, jika tidak clock-in, tombol bertuliskan "Clock In". Tindakan yang diambil oleh tombol sesuai dengan keadaan karyawan juga.

Aplikasi ini adalah klien web yang memanggil server back-end yang diekspos melalui antarmuka layanan RESTful. Pass pertama saya dalam membuat URL intuitif dan dapat dibaca menghasilkan dua titik akhir berikut:

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

CATATAN: Ini digunakan setelah ID dan PIN karyawan divalidasi dan "token" yang mewakili "pengguna" diteruskan dalam header. Ini karena ada "mode manajer" yang memungkinkan manajer atau penyelia untuk masuk atau keluar karyawan lain. Tetapi, demi diskusi ini, saya berusaha membuatnya tetap sederhana.

Di server, saya memiliki ApplicationService yang menyediakan API. Ide awal saya untuk metode ClockIn adalah sesuatu seperti:

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

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

    employee.ClockIn();

    EmployeeRepository.Save();
}

Ini terlihat cukup mudah hingga kami menyadari bahwa informasi kartu waktu Karyawan sebenarnya disimpan sebagai daftar transaksi. Itu berarti setiap kali saya memanggil ClockIn atau ClockOut, saya tidak secara langsung mengubah status Karyawan tetapi, sebaliknya, saya menambahkan entri baru ke dalam TimeSheet Karyawan. Keadaan Karyawan saat ini (clocked in or not) berasal dari entri terbaru dalam TimeSheet.

Jadi, jika saya menggunakan kode yang ditunjukkan di atas, repositori saya harus mengenali bahwa properti yang tetap dari Karyawan tidak berubah tetapi bahwa entri baru ditambahkan ke TimeSheet Karyawan dan melakukan penyisipan ke dalam penyimpanan data.

Di sisi lain (dan inilah pertanyaan utama dari pos), TimeSheet sepertinya itu adalah Agregat Root dan juga memiliki identitas (ID dan periode karyawan) dan saya bisa dengan mudah menerapkan logika yang sama dengan TimeSheet.ClockIn (identitas pegawai).

Saya menemukan diri saya memperdebatkan manfaat dari dua pendekatan dan, sebagaimana dinyatakan dalam paragraf pembukaan, bertanya-tanya kriteria apa yang harus saya evaluasi untuk menentukan pendekatan mana yang lebih cocok untuk masalah tersebut.

SonOfPirate
sumber
Saya telah mengedit / memperbarui posting sehingga pertanyaannya lebih jelas dan menggunakan skenario yang lebih baik (semoga).
SonOfPirate

Jawaban:

4

Saya akan cenderung menerapkan layanan untuk pelacakan waktu:

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

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

Saya tidak berpikir bahwa itu adalah tanggung jawab lembar waktu untuk masuk atau keluar, juga bukan tanggung jawab karyawan.

CodeART
sumber
3

Root agregat tidak boleh mengandung satu sama lain (meskipun mereka mungkin berisi ID untuk orang lain).

Pertama, apakah TimeSheet benar-benar root agregat? Fakta bahwa ia memiliki identitas menjadikannya entitas, tidak harus AR. Lihatlah salah satu aturan:

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

Anda mendefinisikan identitas TimeSheet sebagai ID karyawan & periode waktu, yang menunjukkan bahwa TimeSheet adalah bagian dari Karyawan dengan periode waktu menjadi identitas lokal. Karena ini adalah aplikasi Jam Waktu , apakah tujuan utama Karyawan menjadi wadah TimeSheets?

Dengan asumsi Anda harus ARs, ClockIn dan ClockOut tampak lebih seperti operasi TimeSheet daripada yang Karyawan. Metode lapisan layanan Anda bisa terlihat seperti ini:

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

Jika Anda benar-benar perlu melacak status clock-in di Karyawan dan TimeSheet, maka lihatlah Domain Events (Saya tidak percaya mereka ada di buku Evans, tetapi ada banyak artikel online). Ini akan terlihat seperti: Employee.ClockIn () memunculkan event EmployeeClockedIn, yang diambil oleh pengendali event dan kemudian memanggil TimeSheet.LogEmployeeClockIn ().

Misko
sumber
Saya melihat poin Anda. Namun, ada aturan tertentu yang membatasi jika dan kapan seorang Karyawan dapat masuk. Aturan-aturan ini sebagian besar didorong oleh keadaan Karyawan saat ini, seperti apakah mereka diberhentikan, sudah di-clocking-in, dijadwalkan untuk bekerja pada hari itu atau bahkan pergeseran saat ini, dll. Haruskah TimeSheet memiliki pengetahuan ini?
SonOfPirate
3

Saya merancang aplikasi mengikuti pendekatan DDD dan saya bertanya-tanya panduan apa yang dapat saya ikuti untuk menentukan apakah Agregat Root harus mengandung AR lain atau jika harus dibiarkan terpisah, "berdiri bebas" AR.

Akar agregat tidak boleh mengandung akar agregat lain.

Dalam uraian Anda, Anda memiliki entitas Karyawan , dan juga entitas Timesheet. Kedua entitas ini berbeda, tetapi dapat menyertakan referensi satu sama lain (mis: ini adalah absen Bob).

Itu adalah pemodelan dasar.

Pertanyaan dasar agregat sedikit berbeda. Jika dua entitas ini berbeda secara transaksi dari satu sama lain, maka keduanya dapat dimodelkan dengan benar sebagai dua agregat yang berbeda. Secara transaksi berbeda, maksud saya bahwa tidak ada bisnis invarian yang membutuhkan mengetahui status Karyawan saat ini dan kondisi TimeSheet saat ini secara bersamaan.

Berdasarkan apa yang Anda gambarkan, Timesheet.clockIn dan Timesheet.clockOut tidak perlu memeriksa data apa pun di Karyawan untuk menentukan apakah perintah diizinkan. Jadi untuk banyak masalah ini, dua AR yang berbeda tampaknya masuk akal.

Cara lain untuk mempertimbangkan batasan AR adalah dengan menanyakan jenis pengeditan apa yang diizinkan terjadi secara bersamaan. Apakah manajer diizinkan untuk mengeluarkan karyawan sementara pada saat yang sama SDM mengutak-atik profil karyawan?

Di sisi lain (dan inilah pertanyaan terakhir dari pos), TimeSheet sepertinya itu adalah Agregat Root dan juga memiliki identitas (ID dan periode karyawan)

Identitas hanya berarti entitas - entitas invarian yang mendorong apakah harus dianggap sebagai akar agregat terpisah.

Namun, ada aturan tertentu yang membatasi jika dan kapan seorang Karyawan dapat masuk. Aturan-aturan ini sebagian besar didorong oleh keadaan Karyawan saat ini, seperti apakah mereka diberhentikan, sudah di-clocking-in, dijadwalkan untuk bekerja pada hari itu atau bahkan pergeseran saat ini, dll. Haruskah TimeSheet memiliki pengetahuan ini?

Mungkin - agregat seharusnya. Itu tidak selalu berarti bahwa kartu absen harus.

Artinya, jika aturan untuk memodifikasi Absen tergantung pada kondisi saat ini dari entitas Karyawan, maka Karyawan dan Absen harus menjadi bagian dari agregat yang sama, dan agregat secara keseluruhan bertanggung jawab untuk memastikan bahwa peraturan tersebut diikuti.

Agregat memiliki satu entitas root; mengidentifikasi itu adalah bagian dari teka-teki. Jika seorang Karyawan memiliki lebih dari satu Absen, dan keduanya merupakan bagian dari agregat yang sama, maka Absen jelas bukan root. Yang berarti bahwa aplikasi tidak dapat secara langsung mengubah atau mengirim perintah ke kartu absen - mereka harus dikirim ke objek root (mungkin Karyawan), yang dapat mendelegasikan sebagian tanggung jawab.

Pemeriksaan lain adalah mempertimbangkan bagaimana Timesheets dibuat. Jika mereka dibuat secara implisit ketika karyawan masuk, maka itu petunjuk lain bahwa mereka adalah entitas bawahan dalam agregat.

Selain itu, tidak mungkin agregat Anda memiliki waktu sendiri. Sebaliknya, waktu harus diberikan kepada mereka

employee.clockIn(when);
VoiceOfUnreason
sumber