Katakanlah kita memiliki sistem pencatatan tugas, ketika tugas dicatat, pengguna menentukan kategori dan tugas tersebut default ke status 'Luar Biasa'. Anggaplah dalam contoh ini bahwa Kategori dan Status harus diimplementasikan sebagai entitas. Biasanya saya akan melakukan ini:
Lapisan Aplikasi:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Kesatuan:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Saya melakukannya seperti ini karena saya secara konsisten diberitahu bahwa entitas tidak boleh mengakses repositori, tetapi akan lebih masuk akal bagi saya jika saya melakukan ini:
Kesatuan:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
Repositori status adalah dependecy yang disuntikkan, jadi tidak ada ketergantungan nyata, dan ini terasa lebih bagi saya karena itu adalah domain yang membuat keputusan bahwa tugas default ke luar biasa. Versi sebelumnya terasa seperti aplikasi layeer yang membuat keputusan itu. Adakah mengapa kontrak repositori sering kali berada dalam domain jika ini tidak boleh menjadi kemungkinan?
Berikut adalah contoh yang lebih ekstrem, di sini domain memutuskan urgensi:
Kesatuan:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
Tidak ada cara Anda ingin lulus dalam semua versi Urgency yang mungkin, dan tidak mungkin Anda ingin menghitung logika bisnis ini di lapisan aplikasi, jadi tentu ini akan menjadi cara yang paling tepat?
Jadi apakah ini alasan yang sah untuk mengakses repositori dari domain?
EDIT: Ini juga bisa terjadi pada metode non-statis:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
sumber
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
merupakan aturan bisnis , yang dapat Anda baca sebagai "Bisnis menentukan status awal semua tugas akan menjadi Luar Biasa" dan inilah sebabnya baris kode itu tidak termasuk dalam repositori, yang hanya menyangkut manajemen data melalui operasi CRUD.Saya tidak tahu apakah contoh status Anda adalah kode nyata atau di sini hanya untuk demonstrasi, tetapi tampaknya aneh bagi saya bahwa Anda harus menerapkan Status sebagai Entitas (belum lagi disebut Agregat Root) ketika ID-nya didefinisikan konstan dalam kode -
Constants.Status.OutstandingId
. Bukankah itu mengalahkan tujuan status "dinamis" yang dapat Anda tambahkan sebanyak yang Anda inginkan dalam basis data?Saya akan menambahkan bahwa dalam kasus Anda, konstruksi a
Task
(termasuk pekerjaan mendapatkan status yang tepat dari StatusRepository jika perlu) mungkin layakTaskFactory
daripada tetap di dalamTask
dirinya sendiri, karena itu adalah kumpulan objek non-sepele.Tapi:
Pernyataan ini tidak tepat dan terlalu sederhana, paling menyesatkan dan berbahaya paling buruk.
Sudah cukup umum diterima dalam arsitektur berbasis domain bahwa suatu entitas seharusnya tidak tahu cara menyimpannya sendiri - itulah prinsip ketidaktahuan yang gigih. Jadi tidak ada panggilan ke repositori untuk menambahkan dirinya ke repositori. Haruskah ia tahu bagaimana (dan kapan) menyimpan entitas lain ? Sekali lagi, tanggung jawab itu tampaknya berada di objek lain - mungkin objek yang menyadari konteks eksekusi dan kemajuan keseluruhan kasus penggunaan saat ini, seperti layanan lapisan Aplikasi.
Bisakah entitas menggunakan repositori untuk mengambil entitas lain ? 90% dari waktu yang seharusnya tidak harus, karena entitas yang dibutuhkan biasanya dalam lingkup agregat atau diperoleh dengan melintasi objek lain. Tetapi ada kalanya mereka tidak. Jika Anda mengambil struktur hierarkis, misalnya, entitas sering perlu mengakses semua leluhur mereka, cucu tertentu, dll. Sebagai bagian dari perilaku intrinsik mereka. Mereka tidak memiliki referensi langsung ke kerabat terpencil ini. Akan merepotkan untuk menyerahkan kerabat ini kepada mereka sebagai parameter operasi. Jadi mengapa tidak menggunakan Repositori untuk mendapatkannya - asalkan mereka adalah akar agregat?
Ada beberapa contoh lainnya. Masalahnya adalah, terkadang ada perilaku yang tidak dapat Anda tempatkan dalam layanan Domain karena tampaknya cocok dengan entitas yang ada. Namun, entitas ini perlu mengakses Repositori untuk menghidrasi root atau kumpulan root yang tidak dapat diteruskan ke sana.
Jadi mengakses Repository dari Entitas tidak buruk dalam dirinya sendiri , dapat mengambil bentuk yang berbeda yang hasil dari dari berbagai keputusan desain mulai dari bencana ke diterima.
sumber
Ini adalah salah satu alasan saya tidak menggunakan Enums atau tabel pencarian murni dalam domain saya. Urgensi dan Status adalah kedua Negara dan ada logika yang terkait dengan keadaan yang termasuk dalam keadaan secara langsung (misalnya status apa yang dapat saya transisikan ke keadaan saat ini). Juga, dengan merekam keadaan sebagai nilai murni Anda kehilangan informasi seperti berapa lama tugas itu dalam keadaan tertentu. Saya mewakili status sebagai hierarki kelas seperti itu. (Dalam C #)
Implementasi CompletedTaskStatus akan hampir sama.
Ada beberapa hal yang perlu diperhatikan di sini:
Saya membuat konstruktor default terlindungi. Ini agar kerangka dapat menyebutnya saat menarik objek dari kegigihan (baik EntityFramework Code-first dan NHibernate menggunakan proxy yang berasal dari objek domain Anda untuk melakukan keajaibannya).
Banyak pemukim properti dilindungi karena alasan yang sama. Jika saya ingin mengubah tanggal akhir suatu Interval, saya harus memanggil fungsi Interval.End () (ini adalah bagian dari Desain Domain Driven, memberikan operasi yang berarti daripada Objek Domain Anemik.
Saya tidak menunjukkannya di sini, tetapi Task juga akan menyembunyikan detail bagaimana ia menyimpan statusnya saat ini. Saya biasanya memiliki daftar HistoricalStates yang dilindungi yang saya izinkan untuk ditanyakan kepada publik jika mereka tertarik. Kalau tidak, saya mengekspos keadaan saat ini sebagai pengambil yang menanyakan HistoricalStates.Single (state.Duration.End == null).
Fungsi TransitionTo signifikan karena dapat berisi logika tentang negara mana yang valid untuk transisi. Jika Anda hanya memiliki enum, logika itu harus terletak di tempat lain.
Semoga ini membantu Anda memahami pendekatan DDD sedikit lebih baik.
sumber
Saya telah mencoba untuk memecahkan masalah yang sama untuk beberapa waktu, saya memutuskan saya ingin dapat memanggil Task.UpdateTask () seperti itu, meskipun saya lebih suka itu akan menjadi spesifik domain, dalam kasus Anda mungkin saya akan menyebutnya Task.ChangeCategory (...) untuk menunjukkan suatu tindakan dan bukan hanya CRUD.
Bagaimanapun, saya mencoba masalah Anda dan datang dengan ini ... ambil kue saya dan memakannya juga. Idenya adalah bahwa tindakan terjadi pada entitas tetapi tanpa injeksi semua dependensi. Sebaliknya pekerjaan dilakukan dalam metode statis sehingga mereka dapat mengakses status entitas. Pabrik menyatukan semuanya dan biasanya akan memiliki semua yang diperlukan untuk melakukan pekerjaan yang perlu dilakukan entitas. Kode klien sekarang terlihat bersih dan jelas dan entitas Anda tidak bergantung pada injeksi repositori.
sumber