Clean Architecture - ekonomiczne pobieranie DTO z bazy w warstwie aplikacji

Clean Architecture - ekonomiczne pobieranie DTO z bazy w warstwie aplikacji
0

Cześć,
tworzę API dla nauki (C# ASP.NET). Tak wygląda moja struktura projektu:
screenshot-20250126212255.png
(Główny projekt API odwołuje się też do Infrastructure, aby móc zarejestrować jego repozytoria w dependency injection).

Z tego co rozumiem, to warstwa aplikacji powinna komunikować się z bazą tylko i wyłącznie za pomocą repozytoriów z warstwy infrastruktury (... tzn. interfejsów tych repozytoriów, bo bezpośredniego dostępu do infrastruktury nie ma :). Czyli operacja dodawania klienta w aplikacji wyglądałaby tak:

Kopiuj
public class CustomerService
{
  private readonly ICustomerRepository _customerRepository;

  public CustomerService(ICustomerRepository customerRepository)
  ...

  public void AddCustomer(AddCustomerDto dto)
  {
    Customer c = // mapowanie DTO na encję
    _customerRepository.AddCustomer(c);
  }
}

Ale co z pobieraniem danych? API nie zwraca zwykle całych encji, tylko odpowiednio dostosowane klasy DTO. ChatGPT powiedział mi, że repozytoria powinny operować tylko na encjach, a nie na DTO (czy to prawda?), więc jedyne co mi zostaje to:

Kopiuj
public CustomerDto GetCustomerById(int id)
{
  Customer c = _customerRepository.GetCustomerById(id);
  return // mapowanie encji na DTO
}

Ale jest to bardzo nieekonomiczne rozwiązanie, bo pobieram więcej kolumn z bazy, niż jest mi potrzebne. Bezpośrednich selectów nie mogę zrobić, bo przecież warstwa aplikacji nie ma dostępu do infrastruktury ani do bazy danych. Co zrobić w takiej sytuacji? Chat podpowiada żeby stworzyć osobne CustomerQueryRepository, które będzie zwracało DTO, ale kiedyś na forum czytałem, że jednak niektórzy wykonują bezpośrednie selecty w serwisach.

Z góry bardzo dziękuję.

1

public void AddCustomer(AddCustomerDto dto)

Tu nie powinno być DTO. DTO jest użyte tylko w konkretnym miejscu np. żeby przemapować obiekt domenowy <-> JSON albo baza danych <-> obiekt domenowy. Tu powinno być AddCustomer(AddCustomerRequest. Interfejsy używane przez domenę nie powinny tykać żadnych DTO, DTO to tylko szczegół implementacji związany z konkretną technologię

Ale jest to bardzo nieekonomiczne rozwiązanie, bo pobieram więcej kolumn z bazy, niż jest mi potrzebne

To rób więcej metod skrojonych pod twój przypadek użycia w repozytorium albo zaakceptować nieekonomiczność.

Wydaje mi się, że problem wynika z faktu, że chcesz za małą domenę. Jeśli masz coś co zmienia obiekt domenowy to powinno to być w domenie a nie w aplikacji. Aplikacja to tylko mały wrapper nad domeną, nie powinno być tam żadnej logiki domenowej

7
bbhzp napisał(a):

Z tego co rozumiem, to warstwa aplikacji powinna komunikować się z bazą tylko i wyłącznie za pomocą repozytoriów z warstwy infrastruktury

W clean architecture nie ma mowy o repozytoriach. Ważne, żeby gateway do bazy był w warstwie infrastruktury, a czy będzie nim repozytorium, czy nie, to zależy od projektu i sytuacji.

ChatGPT powiedział mi, że repozytoria powinny operować tylko na encjach, a nie na DTO (czy to prawda?)

Tak, to prawda - i nie trzeba do tego czatu. :P

Ale jest to bardzo nieekonomiczne rozwiązanie, bo pobieram więcej kolumn z bazy, niż jest mi potrzebne. Bezpośrednich selectów nie mogę zrobić, bo przecież warstwa aplikacji nie ma dostępu do infrastruktury ani do bazy danych. Co zrobić w takiej sytuacji?

Ja bym po prostu napisał jakiś CustomerDataReader, którego bym wywoływał z serwisu. Nie widzę potrzeby mieszania jakichś repozytoriów do kodu wczytującego dane dla DTO/view modeli, nie do tego służą repozytoria.

3

Cześć @bbhzp! 🥳 Fajnie że próbujesz dodać warstwę między persystencją aplikacją. Jeśli się to zrobić dobrze, to w fajny sposób nadaje strukturę Twoją aplikację. Kudos.

bbhzp napisał(a):

Ale jest to bardzo nieekonomiczne rozwiązanie, bo pobieram więcej kolumn z bazy, niż jest mi potrzebne. Bezpośrednich selectów nie mogę zrobić, bo przecież warstwa aplikacji nie ma dostępu do infrastruktury ani do bazy danych. Co zrobić w takiej sytuacji? Chat podpowiada żeby stworzyć osobne CustomerQueryRepository, które będzie zwracało DTO, ale kiedyś na forum czytałem, że jednak niektórzy wykonują bezpośrednie selecty w serwisach.

Po pierwsze - zastanów się czy to w ogóle jest problem. Owszem, pobierasz te kolumny, ale co z tego? Czy faktycznie w jakimkolwiek stopniu spowalnia to aplikację? Jeśli nie, to ja bym się nie przejmował.

Po drugie - jeśli faktycznie chcesz nie wczytywać tych kolumn, to powinieneś zrobić osobne metody w gateway'u, jakoś tak:

Kopiuj
interface CustomerGateway {
  public fetchCustomerName(int customerId): string;                   // robi selecta tylko na imie
  public fetchCustomerPersonalInfo(int customerId): CustomerPersonal; // robi selecta tylko z trzema kolumnami
  public fetchCustomer(int customerId): Customer;                     // robi pełnego selecta
}

Dlaczego osobne metody, a nie np. parametry czy builder? Dlatego że są najprostsze do napisania. Pamiętaj, że Twój gateway nie musi być perfekcyjnie generyczny żeby dało się go użyć zawsze i wszędzie - nie musisz pisać biblioteki ORM'owej, nie musisz mieć wszystkich funkcji które DbContext ma. Jak pododajesz funkcje "tylko po to żeby były", jak np. selektywne wczytywanie pól albo dodatkowe funkcjie filtrujące to przeinżynierujesz i nie będzie się tego dało używać. Gateway nie jest po to żeby wystawić dokładnie takie same funkcje jak DbContext - to nie ma sensu, po co Ci drugi taki sam interfejs? Wszystkie zalety które normalnie płyną z posiadania gateway'a, dla Ciebie nie przypłyną 😄 Gateway powinien być najprostszy jak się da i mieć tylko te metody które są koniecznie wymagane przez aplikację. Jeśli aplikacja w żadnym miejscu nie korzysta z pola np. created_at, to nie wczytuj go i nie zwracaj go z gateway'a. Robiąc gateway chcesz mieć swój, prostszy interfejs, ograniczony do absolutnego minimum. Większość ludzi się martwi: "ale co jak będę potrzebował tego pola w przyszłości? 😮". Nie martw się tym - jak będziesz potrzebował to wtedy sobie dodasz.

To jest normalne że to co wychodzi z gateway'a do aplikacji będzie miało inny kształt i formę niż struktura bazy danych - to jest cały ich sens.

2

Do tego co robisz nie potrzebujesz repozytorium, ono cię tylko ogranicza. DbContext w serwisie aplikacyjnym jest w pełni wystarczający do tego co chcesz zrobić, czyli efektywnie pobierać dane i mapować na DTO.

Abstrakcje w postaci repozytorium mają sens, gdy stosujesz DDD, CQRS czy masz jakiś inny silny powód dla którego chcesz ukryć szczegóły implementacyjne bazy danych. Przez takie ukrywanie na siłę projekt robi się w dłuższym czasie nieutrzymywalny, bo zamiast zrobić w serwisie context.Users.Find(id).Select(x => new UserDto {...}); musisz przebijać się przez jakieś dodatkowe warstwy i metr niepotrzebnego mułu, żeby pobrać przykładowo tylko dwie kolumny.

0
Riddle napisał(a):

Pamiętaj, że Twój gateway nie musi być perfekcyjnie generyczny żeby dało się go użyć zawsze i wszędzie

Nic nigdy nie jest perfekcyjne to fakt, ale jak to sie ma do:

Warstwa Domeny reprezentuje podstawową logikę i zasady biznesowe. Istotne jest, aby Warstwa Domeny sprzyjała utrzymaniu, klarowności i wyrazistości w odniesieniu do potrzeb biznesowych.

Kluczem jest tu przymiotnik podstawowa, a dokladniej mowiac:

... dobrze zaprojektowana Warstwa Domeny powinna być zbudowana w sposób ogólny, aby nie zakładać ani nie wdrażać specyficznych wymagań dotyczących warstwy aplikacji.

Kluczem jest tu przymiotnik specyficznych , a to o czym pisze @Riddle jest zaprzeczeniem powyzszego, poniewaz odnosci sie wlasnie do "specyficznego" dla warstwy aplikacji wymagania odnosnie do takich a nie innych kolumn:

Riddle napisał(a):

.. Po drugie - jeśli faktycznie chcesz nie wczytywać tych kolumn, to powinieneś zrobić osobne metody ...

Reasumujac, to co pisze @somekind o customowych DataReader'ach jest moim zdaniem optymalnym podejsciem.

Ponadto:

  • miejscem RepositoryInterface jest Domain layer a nie Application
  • Appllication komunikuje sie zarowno z Infrastructure jak i Domain
  • Infrastructure komunikuje sie wylacznie z Domain

jeszcze jedno, osobiscie mam calkiem spory bol d...py z ta niebieska strzalka laczaca API z Infrastructure 😆

0
markone_dev napisał(a):

Do tego co robisz nie potrzebujesz repozytorium, ono cię tylko ogranicza. DbContext w serwisie aplikacyjnym jest w pełni wystarczający do tego co chcesz zrobić, czyli efektywnie pobierać dane i mapować na DTO.

Abstrakcje w postaci repozytorium mają sens, gdy stosujesz DDD, CQRS czy masz jakiś inny silny powód dla którego chcesz ukryć szczegóły implementacyjne bazy danych. Przez takie ukrywanie na siłę projekt robi się w dłuższym czasie nieutrzymywalny, bo zamiast zrobić w serwisie context.Users.Find(id).Select(x => new UserDto {...}); musisz przebijać się przez jakieś dodatkowe warstwy i metr niepotrzebnego mułu, żeby pobrać przykładowo tylko dwie kolumny.

Repozytoria pisałem z myślą, że metodę dodawania klienta (nieograniczającą się do samego context.Customers.Add()) będę wykorzystywał w wielu miejscach... ale w zasadzie to używałbym ją tylko raz w jednym CustomerService, więc faktycznie repozytoria wydają się tylko zbędną abstrakcją abstrakcji :)

Z cyt. rozumiem, że tyczy to się głównie pobierania danych. Ale w takim razie warstwa aplikacji powinna mieć dostęp do bazy, która jest w infrastrukturze, tzn. DbAccess, a według tego obrazka (image), nie powinna?

1

Clean Architecture jest fajną i popularną architekturą. Ma swoje zastosowanie i czasem ma sens a czasem nie ma sensu. Jak każda architektura służy do rozwiązywania konkretnych problemów.

Po prostu musisz liczyć się z tym że decydując się na implementację CA w jej książkowej formie, narzucasz sobie dodatkowej roboty. I teraz od ciebie zależy czy chcesz sobie tej dodatkowej roboty dokładać czy nie. Jak napisałem wyżej w pewnych projektach taka dodatkowa robota będzie miała sens a w innych nie.

Jak piszesz aplikacje typu przeglądarka do bazy danych to CA pewnie nie będzie miała sensu. Jak piszesz duży e-commerce czy ERPa w architekturze modularnego monolitu, czy innej podobnej, to CA pewnie będzie miała sens. I tak dalej.

Dobieraj narzędzia i architektury aplikacji stosownie do wymagań projektowych i będzie ok.

0

ChatGPT bredzi. Repozytoria mogą mieć niestandardowe metody i zwracać inne rzeczy niż obiekty reprezentujące encje. Jeżeli architektura ma być Clean, to jedyne, co jest wymagane, to aby przez te metody nie wyciekały szczegóły implementacyjne danej bazy danych i żebyś nie pakował logiki biznesowej do wnętrza implementacji repozytoriów. Odpowiadając na pytanie: jeśli chcesz uniknąć pobierania niepotrzebnych kolumn, to potrzebujesz wprowadzić obiekt który opisuje tę projekcję która cię interesuje i dodać wyspecjalizowaną funkcję w repository. Czy to nazwiesz DTO czy VO jest mało istotne. Może jakiś wspólny interfejs, który ma zarówno Customer jak i BasicCustomerInfo?

Zastanów się, czy nie robisz premature optimization. Problem ściągania niepotrzebnych kolumn często jest urojony i nie warto się nim przejmować poniżej miliona wierszy na sekundę. Dodatkowe kolumny siedzą w tych samych blokach na dysku co reszta danych. Pragmatyzm. Najpierw zaimplementuj żeby działało w zdroworozsądkowym podejściu, zmierz wydajność i dopiero wtedy poprawiaj hotspoty.

1
ArchitektSpaghetti napisał(a):

ChatGPT bredzi. Repozytoria mogą mieć niestandardowe metody i zwracać inne rzeczy niż obiekty reprezentujące encje.

Akurat nie bredzi. Repozytoria mają swój cel i definicję - jak każdy wzorzec projektowy.

Nazywanie repozytorium czegoś, co repozytorium nie jest, to jest taka kwintesencja patodeweloperki - zupełnie jak nazywanie apartamentem klitki 20m².

Zastanów się, czy nie robisz premature optimization. Problem ściągania niepotrzebnych kolumn często jest urojony i nie warto się nim przejmować poniżej miliona wierszy na sekundę. Dodatkowe kolumny siedzą w tych samych blokach na dysku co reszta danych. Pragmatyzm. Najpierw zaimplementuj żeby działało w zdroworozsądkowym podejściu, zmierz wydajność i dopiero wtedy poprawiaj hotspoty.

Brak pobierania zbędnych danych to taki bonus do tego, że dzieląc kod na zapytania i polecenia, mamy mniej kodu do napisania, więc i mniej szans na błędy.

0

Ale jak proponujesz zaimplementować projekcje?
Jeśli nie wolno zwracać własnych obiektów z repozytorium, to chyba pozostaje żeby specification miało opcję wybrania kolumn. Jakieś .Select(). W sumie to jest to samo ale with extra steps.

Ja jestem zdania, że jeżeli metoda w interfejsie repozytorium może być zaimplementowana jako default na podstawie innych metod z tego interfejsu to to jest nadal Clean i nadal jest to repository. Może nie jest to DDD według Evansa ale ja się zajmuję programowaniem, nie filozofią.

1

Ale po co wam te repozytoria w tym przypadku? Jeszcze rozumiem jakby op robił DDD i potrzebował pobierać i zapisywać transakcyjne agregaty, to repo ma sens.

Op chcę obsłużyć request, zmapować na encje i zrobić insert lub update. Przy pobieraniu danych odwrotnie. Zrobić select from tabelka, zmapować na dto i puścić w świat. W takim przypadku wystarczy sam DbContext wstrzyknięty do serwisu.

Za chwilę op będzie miał case zapisu kilku encji w jednej transakcji co przy podejściu repo per encja sprawi że repozytoria będzie opakowywał w Unit of Work, itd.

Szkoda czasu na takie rzeczy chyba że ktoś lubi wynajdować koło na nowo i utrudniać sobie i innym życie w imię no właśnie, czego?

0

Chciałbym kontynuować kwestię, którą poruszył autor wątku. @somekind @Riddle @markone_dev
Co w przypadku Clean Architecture, DDD i CQRS?

W query handlerze który jest w warstwie Application chciałbym pobrać tylko podzbiór właściwości modelu domenowego Customer. Więc tworzę w warstwie Application CustomerDto. No i teraz w query handlerze chciałbym wywołać metodę repozytorium, która zwróci CustomerDto, wstrzykuję więc do query handlera interfejs repozytorium:

Kopiuj
interface ICustomerRepository
{
   CustomerDto GetBasicCustomer(int id);
}

Jednak interfejscy repozytoriów powinny być w projekcie Domain, a projekt Domain nie ma dostępu do CustomerDto, który jest w projekcie Application. Jakie jest rozwiązanie?

0
wiewiorek napisał(a):

Jednak interfejscy repozytoriów powinny być w projekcie Domain, a projekt Domain nie ma dostępu do CustomerDto, który jest w projekcie Application. Jakie jest rozwiązanie?

Czy CustomerDto zawiera szczegóły implementacyjne warstwy aplikacji albo persystencji?

  • Jeśli zawiera, to należałoby się albo ich stamtąd pozbyć albo stworzyć inną klasę, wolną od nich - np CustomerDetails czy coś takiego.
  • Jeśli nie zawiera, to nie ma powodu czemu nie można tego wsadzić do domeny.
1

Jak się tak ściśle trzymać jakichś definicji to repozytorium raczej nie służy do zwracania projekcji na potrzeby widoku. Jak już było wspomniane repozytorium operuje na obiektach domenowych.

Jak na potrzeby widoku potrzeba jakiejś projekcji to nic nie stoi na przeszkodzie zaimplementować sobie do tego prosty handler w warstwie aplikacji/infrastruktury który to odczyta - czy przez ORMa pomijając repo, czy odpali kawałek SQLa, czy widok bazodanowy to tutaj już sprawa drugorzędna.

1

Do tej pory tworzyłem tylko aplikacje desktopowe, więc repozytorium było dla mnie niczym innym jak serwis - opakowanie poszczególnych metod ORMa do metod. Obejrzałem kilka tutoriali i w moim API niepotrzebnie chciałem stworzyć repozytorium, mimo posiadania serwisów - taka naleciałość z desktopa :) Przemyślałem sprawę i @markone_dev nakierował mnie na właściwą drogę, za co bardzo dziękuję :) Teraz moja struktura projektu wygląda tak i jestem z niej zadowolony:

Kopiuj
KopytkoCRM.Customers.Domain
|- Enums
|- Filters
|- Models

KopytkoCRM.Customers.Configuration
|- konfiguracja encji dla ORMa

KopytkoCRM.Customers.DAL
|- DTOs (np. CustomerDetailDto)
|- Requests (np. AddCustomerRequest)
|- Validators (tutaj, bo niektóre walidatory muszą mieć dostęp do bazy)
|- Services

I serwisy bezpośrednio odwołują się do bazy:

Kopiuj
public class CustomersService : ICustomersService
{
  public async Task<CustomerDto> GetCustomers()
  {
    return _ctx.Customers
      .Select(c => new CustomerDto
      {
        Id = c.Id,
        FirstName = c.FirstName,
        ...
      });
  }

  public async Task<int> AddCustomer(AddCustomerRequest request)...
}

Czasami najprostsze rozwiązanie jest najlepsze :)

0

a projekt Domain nie ma dostępu do CustomerDto

@wiewiorek domena nie dotyka DTO tam powinno być po prostu Customer

Jedyny sens istnienia DTO to parsowanie/serializacja zewnętrznych formatów danych takich jak:

  • JSON określonego API
  • wiersze z bazy danych

DTO występuje tam gdzie rozumiany jest dany format np. w implementacji repozytorium przy użyciu okreśłonego ORM. DTO jest używane przez ORM do zmapowania wierszy na DTO. Następnie twoim zadaniem jest zmapowanie i zwalidowanie DTO i stworzenie obiektu biznesowego na jego podstawie.

Równie dobrze nie musisz mieć DTO. Zamiast

Kopiuj
select(...).To(customerDTO)

Możesz zrobić

Kopiuj
select(...).To(&userId, &name)

W obu przypadkach interfejs repozytorium się nie zmienia. DTO to tylko sztuczka umożliwiająca użyć refleksji, która na podstawie nazw pól i adnotacji ogarnia co i jak sparsować. Nic więcej

Dodam jeszcze wpis z wiki

This pattern is often incorrectly used outside of remote interfaces. This has triggered a response from its author[3] where he reiterates that the whole purpose of DTOs is to shift data in expensive remote calls.

Nie ma nic złego, żeby domena zwracała jakiś okrojony model głównego modelu. W końcu im prostszy interfejs tym lepiej i nie ma sensu zwracać pól, których nikt nie używa. Ale to nie jest DTO (Data transfer object). Sama nazwa mówi po co to jest xd

2
bbhzp napisał(a):

Do tej pory tworzyłem tylko aplikacje desktopowe, więc repozytorium było dla mnie niczym innym jak serwis - opakowanie poszczególnych metod ORMa do metod. Obejrzałem kilka tutoriali i w moim API niepotrzebnie chciałem stworzyć repozytorium, mimo posiadania serwisów

Lepiej nie mieć serwisów wcale - to taki wytrych słowny, z którego w ogóle nie wynika, do czego toto służy, więc nie wiadomo jak w nich pilnować porządku.

Teraz moja struktura projektu wygląda tak i jestem z niej zadowolony:

Kopiuj
KopytkoCRM.Customers.Domain
|- Enums
|- Filters
|- Models

KopytkoCRM.Customers.Configuration
|- konfiguracja encji dla ORMa

KopytkoCRM.Customers.DAL
|- DTOs (np. CustomerDetailDto)
|- Requests (np. AddCustomerRequest)
|- Validators (tutaj, bo niektóre walidatory muszą mieć dostęp do bazy)
|- Services

Tyle, że:

  • konfiguracja encji dla ORMa to jest właśnie element DAL;
  • Requesty i Serwisy to element warstwy aplikacji, a nie DAL;
  • Te validatory z dostępem do bazy brzmią groźnie. No i jeśli tylko niektóre muszą mieć do niej dostęp, to jaki jest sens trzymać wszystkie w tym miejscu?
0
somekind napisał(a):

Dziękuję bardzo za wskazówki :)

Lepiej nie mieć serwisów wcale - to taki wytrych słowny, z którego w ogóle nie wynika, do czego toto służy, więc nie wiadomo jak w nich pilnować porządku.

Patrząc na różne przykłady też na to zwróciłem uwagę :) W moim przypadku serwisy tworzę po to, żeby nie pchać całej logiki bezpośrednio do kontrolerów.

  • Te validatory z dostępem do bazy brzmią groźnie. No i jeśli tylko niektóre muszą mieć do niej dostęp, to jaki jest sens trzymać wszystkie w tym miejscu?

Jedyna rola bazy w tych walidatorach to sprawdzanie, czy login użytkownika lub indeks (nie chodzi tu o ID, tylko raczej taki symbol jak w Subiekcie) są unikatowe. Faktycznie, większość z nich sprowadza się tylko do IsNullOrWhitespace i sprawdzania długości pól, ale skoro już część jest w DALu, to dziwnie mi było dawać resztę w innej warstwie :)

  • konfiguracja encji dla ORMa to jest właśnie element DAL
  • Requesty i Serwisy to element warstwy aplikacji, a nie DAL;

Nazwę DAL zastosowałem tu po prostu jako określenie na warstwę, która ma dostęp do bazy danych / ORMa. Postanowiłem ostatecznie nie trzymać się kurczowo czystej architektury.

Requesty w moim przypadku to po prostu klasy wystawiane na endpointach PUT I POST, czyli w zasadzie takie DTO - żeby nie można było edytować całej encji; na razie nie implementuję jeszcze CQRS :)

1
bbhzp napisał(a):

Patrząc na różne przykłady też na to zwróciłem uwagę :) W moim przypadku serwisy tworzę po to, żeby nie pchać całej logiki bezpośrednio do kontrolerów.

No to dobrze, że nie chcesz mieć logiki w kontrolerach, z serwisami chodzi mi o to, ze na ogół można znaleźć lepszą nazwę dla danej klasy.

Jedyna rola bazy w tych walidatorach to sprawdzanie, czy login użytkownika lub indeks (nie chodzi tu o ID, tylko raczej taki symbol jak w Subiekcie) są unikatowe.

Tylko, że to nie ma sensu, do tego służy unique w bazie.

Faktycznie, większość z nich sprowadza się tylko do IsNullOrWhitespace i sprawdzania długości pól, ale skoro już część jest w DALu, to dziwnie mi było dawać resztę w innej warstwie :)

Ale dlaczego? Skoro jedne walidatory działają na innym poziomie niż inne, to czemu mają być w jednej warstwie?

Nazwę DAL zastosowałem tu po prostu jako określenie na warstwę, która ma dostęp do bazy danych / ORMa. Postanowiłem ostatecznie nie trzymać się kurczowo czystej architektury.

To nie ma związku z czystą architekturą, w jakiejkolwiek architekturze jest tak, że warstwy aplikacji i dostępu do danych są oddzielone.

0
wiewiorek napisał(a):

W query handlerze który jest w warstwie Application chciałbym pobrać tylko podzbiór właściwości modelu domenowego Customer. Więc tworzę w warstwie Application CustomerDto. No i teraz w query handlerze chciałbym wywołać metodę repozytorium, która zwróci CustomerDto, wstrzykuję więc do query handlera interfejs repozytorium:

Kopiuj
interface ICustomerRepository
{
   CustomerDto GetBasicCustomer(int id);
}

Jednak interfejscy repozytoriów powinny być w projekcie Domain, a projekt Domain nie ma dostępu do CustomerDto, który jest w projekcie Application. Jakie jest rozwiązanie?

Jak na siłę chcesz się trzymać repozytoriów w imię uncle bobowej religii i fanatyzmu, to rozwiązanie jest jedno, że repozytorium zwraca obiekt domenowy, który jest mapowany na DTO, coś w tym stylu

Kopiuj
public class GetBasicCustomerHandler
{
  /... blabla

  public CustomerDto Handle(int id)
  {
    var customer = customerRepository.Get(id);
    var customerDto = customer.Map<CustomerDto>(customer);

    return customerDto;
  }
}

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.