W debacie na temat modeli domen Rich vs. Anemic Internet jest pełen porad filozoficznych, ale brakuje autorytatywnych przykładów. Celem tego pytania jest znalezienie ostatecznych wytycznych i konkretnych przykładów prawidłowych modeli projektowania opartych na domenie. (Idealnie w C #.)
Na przykład w rzeczywistości ta implementacja DDD wydaje się nieprawidłowa:
Poniższe modele domen WorkItem to tylko torby właściwości, używane przez Entity Framework dla bazy danych zawierającej kod. Według Fowlera jest to niedokrwistość .
Warstwa WorkItemService jest najwyraźniej częstym błędnym postrzeganiem usług domenowych; zawiera całą logikę zachowania / biznesu dla WorkItem. Według Jemelyanova i innych jest to postępowanie proceduralne . (str. 6)
Więc jeśli poniższe informacje są nieprawidłowe, jak mogę to naprawić?
Zachowanie, tj. AddStatusUpdate lub Checkout , powinno należeć do klasy WorkItem, prawda?
Jakie zależności powinien mieć model WorkItem?
public class WorkItemService : IWorkItemService {
private IUnitOfWorkFactory _unitOfWorkFactory;
//using Unity for dependency injection
public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
_unitOfWorkFactory = unitOfWorkFactory;
}
public void AddStatusUpdate(int workItemId, int statusId) {
using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
var workItemRepo = unitOfWork.WorkItemRepository;
var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;
var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
if (workItem == null)
throw new ArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");
var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
if (status == null)
throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");
workItem.StatusHistory.Add(status);
workItemRepo.Update(workItem);
unitOfWork.Save();
}
}
}
(Ten przykład został uproszczony, aby był bardziej czytelny. Kod jest zdecydowanie nieporadny, ponieważ jest to myląca próba, ale zachowanie domeny brzmiało: aktualizacja statusu poprzez dodanie nowego statusu do historii archiwum. Ostatecznie zgadzam się z innymi odpowiedziami, to może po prostu obsłużyć CRUD).
Aktualizacja
@AlexeyZimarev udzielił najlepszej odpowiedzi, doskonałego filmu na ten temat w C # autorstwa Jimmy'ego Bogarda, ale najwyraźniej został przeniesiony do komentarza poniżej, ponieważ nie podał wystarczającej ilości informacji poza linkiem. Mam wstępny szkic notatek podsumowujących wideo w mojej odpowiedzi poniżej. Prosimy o komentarz w odpowiedzi z wszelkimi poprawkami. Film trwa godzinę, ale warto go obejrzeć.
Aktualizacja - 2 lata później
Myślę, że to oznaka rodzącej się dojrzałości DDD, że nawet po studiowaniu przez 2 lata nadal nie mogę obiecać, że znam „właściwy sposób” robienia tego. Wszechobecny język, zagregowane korzenie i jego podejście do projektowania zorientowanego na zachowanie stanowią cenny wkład DDD w przemysł. Trwałość ignorancji i pozyskiwania zdarzeń powoduje zamieszanie i myślę, że taka filozofia powstrzymuje ją przed szerszym przyjęciem. Ale gdybym musiał zrobić ten kod od nowa, z tym, czego się nauczyłem, myślę, że wyglądałby mniej więcej tak:
Nadal z zadowoleniem przyjmuję wszelkie odpowiedzi na ten (bardzo aktywny) post, który zawiera kod najlepszych praktyk dla prawidłowego modelu domeny.
"I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"
. „Encje” w żargonie Entity Framework nie są tym samym co „Encje” jak w „Modelu domeny”Odpowiedzi:
Najbardziej pomocna odpowiedź została udzielona przez Aleksieja Zimareva i uzyskała co najmniej 7 głosów poparcia, zanim moderator umieścił ją w komentarzu pod moim pierwotnym pytaniem ...
Jego odpowiedź:
Zrobiłem notatki, aby podsumować wideo z korzyścią dla mojego zespołu i podać nieco bardziej szczegółowe szczegóły w tym poście. (Film jest godzinny, ale naprawdę warty każdej minuty, jeśli masz czas. Jimmy Bogard zasługuje na wiele zasług za jego wyjaśnienie).
Możesz skomentować wszelkie inne kwestie, które Twoim zdaniem powinny zostać uwzględnione, lub jeśli uważasz, że którakolwiek z tych notatek jest nie na miejscu. Próbowałem zacytować bezpośrednio lub sparafrazować jak najwięcej.
źródło
Na twoje pytanie nie można odpowiedzieć, ponieważ twój przykład jest zły. W szczególności, ponieważ nie ma zachowania. Przynajmniej nie w obszarze twojej domeny. Przykładem
AddStatusUpdate
metody nie jest logika domeny, ale logika korzystająca z tej domeny. Tego rodzaju logika ma sens, gdy znajduje się w jakiejś usłudze, która obsługuje żądania zewnętrzne.Na przykład, jeśli istniał wymóg, aby określony element pracy mógł mieć tylko określone statusy lub że mógł mieć tylko N statusów, oznacza to logikę domeny i powinien być częścią jednej
WorkItem
lubStatusHistory
jednej metody.Przyczyną tego zamieszania jest to, że próbujesz zastosować wytyczne do kodu, który go nie potrzebuje. Modele domen są istotne tylko wtedy, gdy masz dużo złożonej logiki domeny. Na przykład. logika, która działa na same podmioty i wynika z wymagań. Jeśli kod dotyczy manipulowania jednostkami z danych zewnętrznych, najprawdopodobniej nie jest to logika domeny. Ale w momencie, gdy otrzymasz dużo
if
s na podstawie danych i encji, z którymi pracujesz, to jest logika domeny.Jednym z problemów prawdziwego modelowania domen jest to, że chodzi o zarządzanie złożonymi wymaganiami. W związku z tym jego prawdziwej mocy i zalet nie można pokazać na prostym kodzie. Potrzebujesz dziesiątek podmiotów z mnóstwem wymagań wokół nich, aby naprawdę zobaczyć korzyści. Ponownie twój przykład jest zbyt prosty, aby model domeny mógł naprawdę świecić.
Na koniec chciałbym wspomnieć, że prawdziwy model domeny z prawdziwym projektem OOP byłby naprawdę trudny do utrzymania przy użyciu Entity Framework. Podczas gdy ORM zostały zaprojektowane z odwzorowaniem prawdziwej struktury OOP na relacyjne, wciąż istnieje wiele problemów, a model relacyjny często przecieka do modelu OOP. Nawet z nHibernate, który uważam za znacznie potężniejszy niż EF, może to stanowić problem.
źródło
Twoje założenie, że enkapsulowanie logiki biznesowej związanej z WorkItem w „grubą usługę” jest nieodłącznym anty-wzorem, który, jak twierdzę, niekoniecznie.
Bez względu na twoje przemyślenia na temat anemicznego modelu domeny, standardowe wzorce i praktyki typowe dla aplikacji .NET z linii biznesowej zachęcają do warstwowego podejścia transakcyjnego składającego się z różnych komponentów. Zachęcają do oddzielenia logiki biznesowej od modelu domeny, aby ułatwić komunikację wspólnego modelu domeny między innymi komponentami .NET, a także komponentami na różnych stosach technologicznych lub na różnych poziomach fizycznych.
Jednym z przykładów może być usługa sieci Web SOAP oparta na .NET, która komunikuje się z aplikacją kliencką Silverlight, która ma bibliotekę DLL zawierającą proste typy danych. Ten projekt jednostki domeny można wbudować w zestaw .NET lub zestaw Silverlight, w którym zainteresowane komponenty Silverlight, które mają tę bibliotekę DLL, nie będą narażone na zachowania obiektów, które mogą zależeć od komponentów dostępnych tylko dla usługi.
Niezależnie od twojego stanowiska w tej debacie, jest to przyjęty i zaakceptowany wzorzec przedstawiony przez Microsoft i moim profesjonalnym zdaniem nie jest to złe podejście, ale model obiektowy, który określa własne zachowanie, niekoniecznie musi być również anty-wzorcem. Jeśli wybierzesz ten projekt, najlepiej jest zrozumieć i zrozumieć niektóre ograniczenia i problemy, na które możesz natknąć się, jeśli chcesz zintegrować się z innymi komponentami, które potrzebują modelu domeny. W tym konkretnym przypadku być może będziesz chciał, aby Translator przekonwertował obiektowy model domeny w stylu obiektowym na proste obiekty danych, które nie ujawniają pewnych metod zachowania.
źródło
Zdaję sobie sprawę, że to pytanie jest dość stare, więc odpowiedź jest dla potomnych. Chcę odpowiedzieć konkretnym przykładem zamiast opartym na teorii.
Podsumuj „zmianę statusu elementu pracy” w
WorkItem
klasie w następujący sposób:Teraz twoja
WorkItem
klasa jest odpowiedzialna za utrzymanie się w stanie prawnym. Implementacja jest jednak dość słaba. Właściciel produktu chce mieć historię wszystkich aktualizacji statusu wprowadzonych doWorkItem
.Zmieniamy to na coś takiego:
Implementacja zmieniła się drastycznie, ale osoba wywołująca
ChangeStatus
metodę nie zna podstawowych szczegółów implementacji i nie ma powodu, aby się zmieniać.To jest przykład bogatego modelu domeny, IMHO.
źródło