Obecnie mam repozytorium dla prawie każdej tabeli w bazie danych i chciałbym dostosować się do DDD, redukując je tylko do zagregowanych korzeni.
Załóżmy, że mam następujące tabele User
i Phone
. Każdy użytkownik może mieć jeden lub więcej telefonów. Bez pojęcia zbiorczego korzenia mógłbym zrobić coś takiego:
//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);
Pojęcie korzeni kruszywa jest łatwiejsze do zrozumienia na papierze niż w praktyce. Nigdy nie będę mieć numerów telefonów, które nie należą do użytkownika, więc czy sensowne byłoby pozbycie się PhoneRepository i włączenie metod związanych z telefonem do UserRepository? Zakładając, że odpowiedź brzmi tak, mam zamiar przepisać poprzedni przykład kodu.
Czy mogę mieć metodę w UserRepository, która zwraca numery telefonów? Czy też powinien zawsze zwracać odniesienie do Użytkownika, a następnie przechodzić przez relację za pośrednictwem Użytkownika, aby uzyskać dostęp do numerów telefonów:
List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone
Niezależnie od tego, w jaki sposób kupię telefony, zakładając, że zmodyfikowałem jeden z nich, jak mam je zaktualizować? Moje ograniczone rozumienie jest takie, że obiekty znajdujące się pod korzeniem powinny być aktualizowane przez korzeń, co skierowałoby mnie do wyboru nr 1 poniżej. Chociaż będzie to działać doskonale z Entity Framework, wydaje się to wyjątkowo nieopisowe, ponieważ czytając kod, nie mam pojęcia, co właściwie aktualizuję, mimo że Entity Framework utrzymuje kartę zmienionych obiektów na wykresie.
UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);
Wreszcie, zakładając Mam kilka tabel przeglądowych, które nie są bardzo przywiązane do niczego, takie jak CountryCodes
, ColorsCodes
, SomethingElseCodes
. Mogę ich użyć do zapełnienia listy rozwijanej lub z jakiegokolwiek innego powodu. Czy są to samodzielne repozytoria? Czy można je połączyć w jakieś logiczne grupowanie / repozytorium, takie jak CodesRepository
? Czy jest to sprzeczne z najlepszymi praktykami.
Odpowiedzi:
Możesz mieć dowolną metodę w swoim repozytorium :) W obu wymienionych przypadkach sensowne jest zwrócenie użytkownika z wypełnioną listą telefonów. Zwykle obiekt użytkownika nie byłby w pełni wypełniony wszystkimi podanymi informacjami (powiedzmy wszystkimi adresami, numerami telefonów) i możemy mieć różne metody wypełniania obiektu użytkownika różnymi rodzajami informacji. Nazywa się to leniwym ładowaniem.
User GetUserDetailsWithPhones() { // Populate User along with Phones }
W przypadku aktualizacji w tym przypadku aktualizowany jest użytkownik, a nie sam numer telefonu. Model pamięci masowej może przechowywać telefony w innej tabeli i w ten sposób możesz pomyśleć, że tylko telefony są aktualizowane, ale tak nie jest, jeśli myślisz z perspektywy DDD. Jeśli chodzi o czytelność, natomiast wiersz
sam nie przekazuje tego, co jest aktualizowane, powyższy kod wyjaśniłby, co jest aktualizowane. Najprawdopodobniej byłaby to część wywołania metody frontendu, która może oznaczać aktualizowane.
W przypadku tabel odnośników, a właściwie nawet w innych przypadkach, warto mieć GenericRepository i używać tego. Repozytorium niestandardowe może dziedziczyć z GenericRepository.
public class UserRepository : GenericRepository<User> { IEnumerable<User> GetUserByCustomCriteria() { } User GetUserDetailsWithPhones() { // Populate User along with Phones } User GetUserDetailsWithAllSubInfo() { // Populate User along with all sub information e.g. phones, addresses etc. } }
Wyszukaj Generic Repository Entity Framework i dobrze byłoby wiele ładnych implementacji. Użyj jednego z nich lub napisz własny.
źródło
Twój przykład w repozytorium Aggregate Root jest w porządku, tj. Każdy podmiot, który nie może istnieć bez zależności od innego, nie powinien mieć własnego repozytorium (w twoim przypadku Phone). Bez tego rozważania możesz szybko znaleźć się w eksplozji repozytoriów w mapowaniu 1-1 do tabel db.
Powinieneś przyjrzeć się używaniu wzorca jednostki pracy do zmian danych, a nie samych repozytoriów, ponieważ myślę, że powodują one pewne zamieszanie co do zamiaru, jeśli chodzi o utrwalanie zmian z powrotem do bazy danych. W rozwiązaniu EF jednostka pracy jest zasadniczo otoką interfejsu wokół kontekstu EF.
W odniesieniu do Twojego repozytorium danych wyszukiwania, po prostu tworzymy ReferenceDataRepository, które staje się odpowiedzialne za dane, które nie należą konkretnie do jednostki domeny (kraje, kolory itp.).
źródło
Jeśli telefon nie ma sensu bez użytkownika, jest to byt (jeśli zależy Ci na jego tożsamości) lub obiekt wartościowy i zawsze powinien być modyfikowany przez użytkownika i wspólnie pobierany / aktualizowany.
Pomyśl o źródłach zagregowanych jako o definicjach kontekstu - rysują one lokalne konteksty, ale same są w kontekście globalnym (Twoja aplikacja).
Jeśli stosujesz projekt oparty na domenie, repozytoria powinny być 1: 1 na zbiorcze korzenie.
Bez wymówek.
Założę się, że są to problemy, z którymi się borykasz:
Nie jestem pewien, jak rozwiązać pierwszy problem, ale zauważyłem, że naprawienie drugiego rozwiązuje wystarczająco dobrze. Aby zrozumieć, co mam na myśli mówiąc o zorientowaniu na zachowanie, wypróbuj ten artykuł .
Ps Redukcja repozytorium do zagregowanego katalogu głównego nie ma sensu.
Unikaj PPS
"CodeRepositories"
. Prowadzi to do skoncentrowania się na danych -> kodzie proceduralnym.Ppps Unikaj wzorca pracy. Korzenie agregatów powinny określać granice transakcji.
źródło
To stare pytanie, ale warto zamieścić proste rozwiązanie.
static void updateNumber (int userId, string oldNumber, string newNumber)
static void updateNumber(int userId, string oldNumber, string newNumber) { using (MyContext uow = new MyContext()) // Unit of Work { DbSet<User> repo = uow.Users; // Repository User user = repo.Find(userId); Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault(); oldPhone.Number = newNumber; uow.SaveChanges(); } }
źródło
Jeśli jednostka Phone ma sens tylko w połączeniu z zagregowanym użytkownikiem root, to uważam również, że ma sens, aby za operację dodania nowego rekordu Phone odpowiedzialny był obiekt domeny użytkownika za pomocą określonej metody (zachowanie DDD), a to mogłoby ma sens z kilku powodów, natychmiastowym powodem jest to, że powinniśmy sprawdzić, czy obiekt użytkownika istnieje, ponieważ jednostka Phone zależy od jego istnienia i być może utrzymywać na nim blokadę transakcji, wykonując więcej sprawdzeń poprawności, aby upewnić się, że żaden inny proces nie usunął wcześniej agregatu głównego zakończyliśmy walidację operacji. W innych przypadkach z innymi rodzajami agregatów głównych możesz chcieć zagregować lub obliczyć jakąś wartość i zachować ją we właściwościach kolumny agregatu głównego, aby później wydajniej przetwarzać je przez inne operacje.
Ponadto, jeśli chcesz użyć metod, które pobierają wszystkie telefony niezależnie od użytkowników, którzy są ich właścicielami, nadal możesz to zrobić za pośrednictwem repozytorium użytkowników, potrzebujesz tylko jednej metody zwracającej wszystkich użytkowników jako IQueryable, a następnie możesz zmapować je, aby uzyskać wszystkie telefony użytkowników i wykonać udoskonaloną zapytaj z tym. Więc w tym przypadku nie potrzebujesz nawet PhoneRepository. Poza tym wolałbym raczej użyć klasy z metodą rozszerzeń dla IQueryable, której mogę używać w dowolnym miejscu, nie tylko z klasy Repository, jeśli chciałbym abstrakcyjne zapytania za metodami.
Tylko jedno zastrzeżenie dotyczące możliwości usuwania jednostek Phone za pomocą tylko obiektu domeny, a nie repozytorium Phone, musisz się upewnić, że identyfikator użytkownika jest częścią klucza podstawowego Phone lub innymi słowy klucz podstawowy rekordu Phone jest kluczem złożonym składa się z UserId i innej właściwości (sugeruję tożsamość generowaną automatycznie) w encji Phone. Ma to sens intuicyjnie, ponieważ rekord Phone jest „własnością” rekordu użytkownika, a jego usunięcie z kolekcji nawigacji użytkownika byłoby równoznaczne z całkowitym usunięciem z bazy danych.
źródło