Załóżmy, że mamy system rejestrowania zadań. Gdy zadanie jest rejestrowane, użytkownik określa kategorię, a zadanie domyślnie ma status „Zaległy”. Załóżmy w tym przypadku, że kategorię i status należy zaimplementować jako byty. Normalnie zrobiłbym to:
Warstwa aplikacji:
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);
}
}
Jednostka:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Robię to w ten sposób, ponieważ konsekwentnie mówi mi się, że podmioty nie powinny uzyskiwać dostępu do repozytoriów, ale byłoby to dla mnie znacznie rozsądniejsze, gdybym to zrobił:
Jednostka:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
Repozytorium statusu i tak jest wstrzykiwane zależnie, więc nie ma żadnej rzeczywistej zależności, a to wydaje mi się bardziej, że to domena decyduje, że zadanie domyślnie zalega. Poprzednia wersja wydawała się być warstwową aplikacją podejmującą tę decyzję. Jakieś powody, dla których umowy repozytorium są często w domenie, jeśli nie powinno to być możliwe?
Oto bardziej ekstremalny przykład, tutaj domena decyduje o pilności:
Jednostka:
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;
}
}
Nie ma możliwości, abyś przeszedł we wszystkie możliwe wersje Pilności, i nie ma możliwości obliczenia tej logiki biznesowej w warstwie aplikacji, więc na pewno byłby to najbardziej odpowiedni sposób?
Czy to jest ważny powód, aby uzyskać dostęp do repozytoriów z domeny?
EDYCJA: Może tak być również w przypadku metod niestatycznych:
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;
}
}
źródło
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
na zasadę biznesową , którą można przeczytać jako „Firma dyktuje, że początkowy status wszystkich zadań będzie Znakomity” i dlatego ta linia kodu nie należy do repozytorium, którego jedynym przedmiotem jest zarządzanie danymi za pośrednictwem operacji CRUD.Nie wiem, czy twój przykład statusu to prawdziwy kod, czy tutaj tylko dla celów demonstracyjnych, ale wydaje mi się dziwne, że powinieneś wdrożyć Status jako Entity (nie wspominając o Agregacji Korzenia), gdy jego identyfikator jest stałą zdefiniowaną w kodzie -
Constants.Status.OutstandingId
. Czy nie jest to sprzeczne z celem „dynamicznych” statusów, które możesz dodać tyle, ile chcesz w bazie danych?Dodam, że w twoim przypadku konstrukcja
Task
(w tym w razie potrzeby uzyskanie odpowiedniego statusu z StatusRepository) może zasługiwać na toTaskFactory
, aby nie pozostawać wTask
sobie, ponieważ jest to niebanalny zbiór obiektów.Ale :
To stwierdzenie jest w najlepszym razie nieprecyzyjne i zbyt uproszczone, w najgorszym przypadku wprowadzające w błąd i niebezpieczne.
W architekturach opartych na domenie jest powszechnie akceptowane, że jednostka nie powinna wiedzieć, jak się przechowywać - taka jest zasada niewiedzy uporczywości. Więc nie ma wywołań do jego repozytorium, aby dodać się do repozytorium. Czy powinien wiedzieć, jak (i kiedy) przechowywać inne podmioty ? Ponownie, ta odpowiedzialność wydaje się należeć do innego obiektu - być może obiektu, który jest świadomy kontekstu wykonania i ogólnego postępu bieżącego przypadku użycia, takiego jak usługa warstwy aplikacji.
Czy jednostka może użyć repozytorium do pobrania innej jednostki ? 90% czasu nie powinno być konieczne, ponieważ jednostki, których potrzebuje, są zwykle w zakresie ich agregacji lub można je uzyskać przez przemieszczenie innych obiektów. Ale są chwile, kiedy tak nie jest. Jeśli na przykład przyjmujesz strukturę hierarchiczną, byty często muszą uzyskać dostęp do wszystkich swoich przodków, konkretnego wnuka itp. W ramach swojego wewnętrznego zachowania. Nie mają bezpośredniego odniesienia do tych odległych krewnych. Byłoby niewygodne przekazywanie im tych krewnych jako parametrów operacji. Dlaczego więc nie skorzystać z repozytorium, aby je zdobyć - pod warunkiem, że są one zagregowanymi źródłami?
Istnieje kilka innych przykładów. Chodzi o to, że czasami występuje zachowanie, którego nie można umieścić w usłudze domeny, ponieważ wydaje się idealnie pasować do istniejącej jednostki. A jednak ten byt musi uzyskać dostęp do repozytorium, aby nawodnić root lub zbiór korzeni, których nie można przekazać do niego.
Dostęp do repozytorium z jednostki nie jest sam w sobie zły , może przybierać różne formy, wynikające z różnych decyzji projektowych, od katastroficznych po akceptowalne.
źródło
To jeden z powodów, dla których nie używam Enums ani czystych tabel odnośników w mojej domenie. Pilność i status są stanami i istnieje logika związana ze stanem, który bezpośrednio należy do tego stanu (np. Jakie stany mogę przejść do danego stanu). Ponadto, rejestrując stan jako czystą wartość, tracisz informacje, takie jak czas trwania zadania w danym stanie. Reprezentuję statusy jako taka hierarchia klas. (W C #)
Implementacja CompletedTaskStatus byłaby prawie taka sama.
Należy tutaj zwrócić uwagę na kilka rzeczy:
Zabezpieczam domyślnych konstruktorów. Jest to tak, że środowisko może je wywoływać podczas wyciągania obiektu z trwałości (zarówno EntityFramework Code-first, jak i NHibernate używają proxy, które są uzyskiwane z obiektów twojej domeny, aby wykonywać swoją magię).
Wiele podmiotów ustawiających właściwości jest chronionych z tego samego powodu. Jeśli chcę zmienić datę końcową interwału, muszę wywołać funkcję Interval.End () (jest to część projektowania opartego na domenie, zapewniającego znaczące operacje zamiast anemicznych obiektów domenowych.
Nie pokazuję go tutaj, ale Zadanie również ukryłoby szczegóły dotyczące tego, jak przechowuje swój obecny status. Zwykle mam chronioną listę Historycznych stanów, które pozwalam opinii publicznej zapytać, czy są zainteresowani. W przeciwnym razie udostępniam bieżący stan jako moduł pobierający, który wysyła zapytanie do HistoricalStates.Single (state.Duration.End == null).
Funkcja TransitionTo jest znacząca, ponieważ może zawierać logikę, które stany są ważne dla przejścia. Jeśli masz tylko wyliczenie, ta logika musi leżeć gdzie indziej.
Mamy nadzieję, że pomoże to nieco lepiej zrozumieć podejście DDD.
źródło
Od jakiegoś czasu próbuję rozwiązać ten sam problem, zdecydowałem, że chcę móc wywołać Task.UpdateTask () w ten sposób, chociaż wolałbym, aby był specyficzny dla domeny, w twoim przypadku może nazwałbym to Task.ChangeCategory (...) aby wskazać akcję, a nie tylko CRUD.
tak czy inaczej, próbowałem twojego problemu i wymyśliłem to ... zjedz moje ciasto i też jem. Chodzi o to, że działania mają miejsce na bycie, ale bez zastrzyku wszystkich zależności. Zamiast tego praca odbywa się metodami statycznymi, aby mogły uzyskać dostęp do stanu encji. Fabryka składa wszystko razem i zwykle będzie miała wszystko, czego potrzebuje do wykonania pracy, którą jednostka musi wykonać. Kod klienta wygląda teraz na czysty i przejrzysty, a twoja jednostka nie jest zależna od żadnego wstrzyknięcia repozytorium.
źródło