Jak rozumiem, w DDD właściwe jest użycie wzorca repozytorium z zagregowanym katalogiem głównym. Moje pytanie brzmi: czy powinienem zwrócić dane jako obiekt lub domenę / DTO?
Może jakiś kod wyjaśni moje pytanie dalej:
Jednostka
public class Customer
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Czy powinienem zrobić coś takiego?
public Customer GetCustomerByName(string name) { /*some code*/ }
Lub coś w tym stylu?
public class CustomerDTO
{
public Guid Id { get; set; }
public FullName { get; set; }
}
public CustomerDTO GetCustomerByName(string name) { /*some code*/ }
Dodatkowe pytanie:
- Czy w repozytorium powinienem zwrócić IQueryable czy IEnumerable?
- W usługi lub repozytorium, należy zrobić coś podobnego ..
GetCustomerByLastName
,GetCustomerByFirstName
,GetCustomerByEmail
? lub po prostu stworzyć metodę podobną doGetCustomerBy(Func<string, bool> predicate)
?
GetCustomerByName('John Smith')
zwróci, jeśli masz w bazie dwudziestu John Smiths? Wygląda na to, że zakładasz, że nie ma dwóch osób o tym samym imieniu.Odpowiedzi:
Cóż, to całkowicie zależy od twoich przypadków użycia. Jedynym powodem, dla którego mogę pomyśleć o zwróceniu DTO zamiast pełnego bytu, jest to, że twój byt jest ogromny i wystarczy pracować nad jego podzbiorem.
W takim przypadku być może powinieneś ponownie rozważyć model domeny i podzielić duży podmiot na powiązane mniejsze podmioty.
Dobrą zasadą jest zawsze zwracanie możliwie najprostszego (najwyższego w hierarchii dziedziczenia) typu. Więc zwróć,
IEnumerable
chyba że chcesz pozwolić klientowi repozytorium pracować zIQueryable
.Osobiście uważam, że powrót
IQueryable
jest nieszczelną abstrakcją, ale spotkałem kilku programistów, którzy z pasją twierdzą, że tak nie jest. Moim zdaniem cała logika zapytań powinna być zawarta i ukryta przez Repozytorium. Jeśli pozwolisz kodowi wywołującemu dostosować zapytanie, jaki jest sens repozytorium?Z tego samego powodu, o którym wspomniałem w punkcie 1, zdecydowanie nie używaj
GetCustomerBy(Func<string, bool> predicate)
. Z początku może się to wydawać kuszące, ale właśnie dlatego ludzie nauczyli się nienawidzić ogólnych repozytoriów. Jest nieszczelny.Takie rzeczy
GetByPredicate(Func<T, bool> predicate)
są przydatne tylko wtedy, gdy są ukryte za konkretnymi klasami. Tak więc, gdybyś miał abstrakcyjną klasę bazową o nazwieRepositoryBase<T>
odsłoniętej,protected T GetByPredicate(Func<T, bool> predicate)
która była używana tylko przez konkretne repozytoria (np.public class CustomerRepository : RepositoryBase<Customer>
), To byłoby w porządku.źródło
Istnieje spora społeczność ludzi, którzy używają CQRS do wdrażania swoich domen. Mam wrażenie, że jeśli interfejs twojego repozytorium jest analogiczny do najlepszych praktyk przez nich stosowanych, to nie zbłądzisz zbyt daleko.
Na podstawie tego, co widziałem ...
1) Programy obsługi poleceń zwykle używają repozytorium do ładowania agregacji za pośrednictwem repozytorium. Polecenia są ukierunkowane na jedną konkretną instancję agregatu; repozytorium ładuje katalog główny według identyfikatora. Nie ma, jak widzę, przypadku, w którym polecenia są uruchamiane względem kolekcji agregatów (zamiast tego najpierw należy uruchomić zapytanie, aby uzyskać kolekcję agregatów, a następnie wyliczyć kolekcję i wydać polecenie każdemu z nich.
Dlatego w kontekstach, w których zamierzasz modyfikować agregację, oczekiwałbym, że repozytorium zwróci encję (inaczej root root agregatu).
2) Programy obsługi zapytań w ogóle nie dotykają agregatów; zamiast tego pracują z rzutami obiektów o wartościach agregujących, które opisują stan agregatu / agregatów w pewnym momencie. Pomyśl więc ProjectionDTO zamiast AggregateDTO, a masz dobry pomysł.
W kontekstach, w których zamierzasz uruchamiać zapytania względem agregatu, przygotowując je do wyświetlenia itd., Spodziewam się, że zostanie zwrócone DTO lub kolekcja DTO, a nie jednostka.
Wszystkie twoje
getCustomerByProperty
rozmowy wyglądają dla mnie jak zapytania, więc należą do tej drugiej kategorii. Prawdopodobnie chciałbym użyć jednego punktu wejścia do wygenerowania kolekcji, więc chciałbym sprawdzić, czyjest rozsądnym wyborem; procedury obsługi zapytań konstruują następnie odpowiednią specyfikację na podstawie podanych parametrów i przekazują tę specyfikację do repozytorium. Minusem jest to, że podpis naprawdę sugeruje, że repozytorium jest kolekcją w pamięci; nie jest dla mnie jasne, że predykat dużo kupuje, jeśli repozytorium jest jedynie abstrakcją uruchamiania instrukcji SQL względem relacyjnej bazy danych.
Istnieją jednak pewne wzorce, które mogą pomóc. Na przykład zamiast ręcznie budować specyfikację, przekaż do repozytorium opis ograniczeń i pozwól implementacji repozytorium zdecydować, co należy zrobić.
Ostrzeżenie: wykryto pisanie java
Podsumowując: wybór między dostarczeniem agregatu a dostarczeniem DTO zależy od tego, czego oczekujesz od konsumenta. Sądzę, że będzie to jedna konkretna implementacja obsługująca interfejs dla każdego kontekstu.
źródło
getCustomersThatSatisfy(Specification spec)
jest tylko lista właściwości, których potrzebowałem dla opcji wyszukiwania. Czy mam rację?Na podstawie mojej wiedzy o DDD powinieneś to mieć,
To zależy, w tym pytaniu znajdziesz różne poglądy różnych ludzi. Ale osobiście uważam, że jeśli twoje repozytorium będzie wykorzystywane przez usługę taką jak CustomerService, możesz użyć IQueryable, w przeciwnym razie trzymaj się IEnumerable.
Czy repozytoria powinny zwracać IQueryable?
W swoim repozytorium powinieneś mieć ogólną funkcję, jak powiedziałeś,
GetCustomer(Func predicate)
ale w swojej warstwie usług dodaj trzy różne metody, ponieważ istnieje prawdopodobieństwo, że twoja usługa zostanie wywołana z różnych klientów i potrzebują różnych DTO.Możesz również użyć Ogólnego Wzorca Repozytorium dla wspólnej CRUD, jeśli jeszcze tego nie wiesz.
źródło