Czy powinienem używać repozytorium w obiekcie domeny, czy też zepchnąć obiekt domeny z powrotem do warstwy usług?

10

Pochodzę ze świata skryptów transakcyjnych i dopiero zaczynam patrzeć na DDD. Nie jestem pewien prawidłowego sposobu zintegrowania projektu DDD z trwałością bazy danych. Oto co mam:

Klasa usługi o nazwie OrganisationService, której interfejs zawiera metody pobierania i zapisywania instancji obiektów domeny Organizacji. Organizacja jest zagregowanym katalogiem głównym i ma z nią związane inne dane: członków i licencje. Baza danych EF6 Najpierw DBContext jest używany w ramach usługi OrganisationService do pobierania jednostek OrganisationDB i powiązanych jednostek MemberDB i LicenseDB. Wszystkie te zostają przekształcone w ich odpowiedniki klasy obiektu domeny, gdy zostaną pobrane przez OrganisationService i załadowane do obiektu domeny Organizacji. Ten obiekt wygląda tak:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

Nie używam wzorca repozytorium w usłudze OrganisationSer ... Używam samego EF jako repozytorium, ponieważ wydaje się, że EF6 w dużej mierze sprawił, że repozytorium jest teraz zbędne.

Na tym etapie projektowania obiekt domeny Organizacji jest anemiczny: wygląda jak klasa organizacji EF POCO. Klasa OrganisationService przypomina bardzo repozytorium!

Teraz muszę zacząć dodawać logikę. Ta logika obejmuje zarządzanie licencjami organizacji i członkami. Teraz w dniach skryptów transakcyjnych dodawałbym metody do OrganisationService do obsługi tych operacji i wywoływałem repozytorium w celu interakcji z bazą danych, ale w przypadku DDD uważam, że ta logika powinna być zawarta w samym obiekcie domeny Organizacji ...

W tym miejscu nie jestem pewien, co powinienem zrobić: będę musiał zachować te dane z powrotem do bazy danych w ramach logiki. Czy to oznacza, że ​​w tym celu należy użyć DbContext w obiekcie domeny Organizacji? Czy używanie repozytorium / EF w obrębie obiektu domeny jest złą praktyką? Jeśli tak, to gdzie ta upór należy?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Czy zamiast tego powinienem po prostu mutować obiekt domeny Organizacji w pamięci, a następnie wypychać go z powrotem do OrganisationService w celu utrwalenia? Następnie muszę śledzić, co faktycznie zmieniło się na obiekcie (co EF robi z własnymi POCO! Czuję, że EF to nie tylko zastąpienie repozytorium, ale także warstwa domeny!)

Wszelkie wskazówki tutaj są mile widziane.

MrLane
źródło

Odpowiedzi:

2

Możesz wybrać jedno z tych podejść i sprawić, by działało dobrze - są oczywiście plusy i minusy.

Entity Framework jest zdecydowanie przeznaczony do wypełniania twoich domen. Działa dobrze, gdy jednostki domeny i jednostki danych są tej samej klasy. O wiele ładniej jest, jeśli możesz polegać na EF, aby śledzić zmiany i zadzwonić, context.SaveChanges()gdy skończysz pracę transakcyjną. Oznacza to również, że atrybuty sprawdzania poprawności nie muszą być ustawiane dwa razy, raz w modelach domeny i raz w trwałych jednostkach - takie rzeczy jak [Required]lub [StringLength(x)]mogą być sprawdzone w logice biznesowej , umożliwiając wychwycenie nieprawidłowych stanów danych przed próbą zrób transakcję DB i uzyskajEntityValidationException. Wreszcie kodowanie jest szybkie - nie trzeba pisać warstwy mapowania ani repozytorium, ale zamiast tego można pracować bezpośrednio z kontekstem EF. To już repozytorium i jednostka pracy, więc dodatkowe warstwy abstrakcji niczego nie osiągają.

Wadą łączenia domeny i trwałych elementów jest to, że w końcu masz wiele [NotMapped]atrybutów rozrzuconych po twoich właściwościach. Często potrzebne są właściwości specyficzne dla domeny, które są albo filtrami „tylko get” dla utrwalonych danych, albo są ustawione później w logice biznesowej i nie są utrwalane z powrotem w bazie danych. Czasami będziesz chciał wyrazić swoje dane w modelach domeny w sposób, który nie działa zbyt dobrze z bazą danych - na przykład, używając enum, Entity zamapuje je na intkolumnę - ale być może chcesz mapować do czytelnego dla człowieka string, więc nie trzeba sprawdzać bazy danych podczas sprawdzania bazy danych. Następnie otrzymujesz stringwłaściwość, która jest zmapowana, anenumwłaściwość, która nie jest (ale pobiera ciąg znaków i mapy do wyliczenia) oraz interfejs API, który udostępnia oba! Podobnie, jeśli chcesz łączyć złożone typy (tabele) między kontekstami, możesz skończyć z mappedOtherTableId i niezapisaną ComplexTypewłaściwością, które są ujawnione jako publiczne w twojej klasie. Może to być mylące dla kogoś, kto nie jest zaznajomiony z projektem i niepotrzebnie rozdęwa modele domen.

Im bardziej złożona jest moja logika / domena biznesowa, tym bardziej restrykcyjne lub kłopotliwe jest połączenie mojej domeny i trwałych podmiotów. W przypadku projektów o krótkich terminach realizacji lub niewymagających złożonej warstwy biznesowej uważam, że stosowanie jednostek EF do obu celów jest odpowiednie i nie trzeba odrywać domeny od uporczywości. W przypadku projektów, które wymagają maksymalnej łatwości rozbudowy lub wyrażenia bardzo skomplikowanej logiki, myślę, że lepiej jest oddzielić te dwa elementy i zająć się dodatkową złożonością trwałości.

Jednym ze sposobów uniknięcia kłopotów z ręcznym śledzeniem zmian encji jest przechowywanie odpowiedniego trwałego identyfikatora encji w modelu domeny. Może to zostać wypełnione automatycznie przez warstwę mapowania. Następnie, gdy trzeba zachować zmianę z powrotem na EF, pobierz odpowiednią trwałą jednostkę przed wykonaniem jakiegokolwiek mapowania. Następnie, gdy mapujesz zmiany, EF wykryje je automatycznie i możesz zadzwonić context.SaveChanges()bez konieczności ręcznego śledzenia.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}
NWard
źródło
Czy istnieje problem z połączeniem różnych podejść z różną złożonością danych? jeśli masz coś, co ładnie odwzorowuje na DB, po prostu użyj EF bezpośrednio. Jeśli masz coś, czego nie ma, wprowadź mapowanie / abstrakcję przed użyciem EF.
Euforyczny
@ Euforia różnice w domenie i db mogą być obsługiwane w mapowaniu. Nie mogę wymyślić przypadku użycia, w którym dwukrotne dodanie domeny (nietrwałe i trwałe) i ręczne śledzenie zmian zmian jest mniej pracochłonne niż dodawanie mapowań w szczególnych przypadkach. Czy możesz wskazać mi jeden? (Zakładam, że używając NHibernate, może EF jest bardziej ograniczony?)
Firo
Bardzo pomocna, praktyczna i pouczająca odpowiedź. Oznaczenie jako odpowiedź. Dzięki!
MrLane
3

nie znam EF6, ale inne ORM obsługują to w sposób transparentny, więc nie będziesz musiał wyraźnie dodawać licencji do kontekstu, wykryje je podczas zapisywania zmian. Więc metoda będzie ograniczona do

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

i kod wokół niego

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();
Firo
źródło
Moje obiekty domeny nie są Podmiotami, więc dodanie ich do kolekcji Licencji, która jest tylko Listą, nie spowoduje ich wycięcia. Pytanie dotyczy nie tyle EF, ile rzeczywistej trwałości. Cała trwałość w EF musi odbywać się w zakresie kontekstu. Pytanie brzmi, gdzie to należy?
MrLane
2
podmioty domeny i trwałe obiekty mogą / powinny być takie same, w przeciwnym razie wprowadzisz kolejną warstwę abstrakcji, która w 99% przypadków nie wnosi żadnej wartości i tracisz śledzenie zmian.
Firo