Skip to content
Menu
Power-Dev
  • Clean Architecture
  • Blazor
  • O mnie
Power-Dev

Repository Pattern – Domain Layer bez EntityFramework’a.

Opublikowano 10 maja, 202111 maja, 2021

Podstawą Clean Architecture jest odizolowanie domeny biznesowej od warstwy aplikacyjnej. Pomoże nam w tym Repository Pattern. Jeśli chcesz się dowiedzieć jak go użyć, to zapraszam do dalszego czytania. Wpis ten jest kontynuacją serii.

Spis treści

  • Model
  • Logika domenowa
  • Baza ukryta za IRepository
  • Infrastruktura
  • WebApi
  • Podsumowanie

Model

Załóżmy, że chcemy stworzyć system obsługi sportowców, oczywiście w bardzo uproszczony sposób.

Zacznijmy od utworzenia abstrakcyjnej klasy oznaczającej encję w bazie danych. Dzięki temu nie będziemy musieli dodawać powtarzających się pól jak np. identyfikator, data utworzenia oraz data modyfikacji.

Kolejną rzeczą, jaką zrobimy to dodawanie modelu naszego atlety, dlatego w ApplicationCore/Entities dodajmy klasę Athlete która dziedziczy po Entity.

Widzimy tutaj zastosowanie się do zasad hermetyzacji dany, więcej o tym przeczytasz >TUTAJ<. W odróżnieniu od klasycznego podejścia, obiekt inicjujemy za pomocą statycznej metody RegisterNew, zamiast bezpośrednio konstruktora.

Jakie są plusy takiego podejścia? Przede wszystkim w łatwy sposób jesteśmy w stanie rozróżnić przypadki, w jakich powołujemy nową instancję. Załóżmy, że w inny sposób rejestrowalibyśmy zawodników <18 roku życia, tych 18+ oraz tzw. masters (40+). Używając jedynie konstruktorów, wymagalibyśmy by ktoś z zewnątrz znał logikę im towarzyszącą oraz to który wybrać. Wykorzystując w taki sposób statyczne metody, nasz kod staje się dużo bardziej przyjazny innym programistom.

Tl;dr; cała logika domenowa powinna być zamknięta za metodami na obiekcie. Zewnętrzne klasy powinny jedynie zarządzać przepływem ich wywołania.

Logika domenowa

Stwórzmy AthleteService, który będzie odpowiedzialny za zarejestrowanie nowego zawodnika, wylistowanie wszystkich oraz pobranie szczegółów jednego z nich. Interfejs może wyglądać następująco:

A tak wyglądają użyte modele wejściowe i wyjściowe. Co prawda większość danych się powtarza, jednak jestem zwolennikiem rozbijania tych klas. Dzięki temu mamy mniej związaną ze sobą logikę – przez co łatwiej będzie ją modyfikować w przyszłości. Więcej o tym >TUTAJ<

Pomyślmy, jak moglibyśmy podzielić implementację RegisterNew:

  • Na start pewnie dokonamy jakiejś walidacji – np. czy taka osoba już istnieje w systemie.
  • Stworzymy nowy obiekt reprezentujący atletę.
  • Dodamy go do jakiegoś trwałego miejsca przechowywania danych (persistant store) np. relacyjnej bazy danych.
  • Użyjemy logger’a by zachować informację o tym co się wydarzyło.

Baza ukryta za IRepository

Aby uzyskać dostęp do bazy danych musielibyśmy użyć EntityFramework’a w warstwie domenowej – a tego nie chcemy. Aby temu zapobiec wyabstrahujmy interfejs obsługujący dowolne „trwałe miejsce przechowywania danych” i nazwiemy go IRepository.

Wiele osób zaznajomionych już z Repository Pattern, pewnie oburzy się, widząc SaveChangesAsync w repozytorium. Wytłumaczenie, dlaczego tak zrobiłem, jest bardzo proste. Mianowicie nie chciałem wprowadzać kolejnego wzorca, jakim jest Unit Of Work w prosty tutorial. Wszystko w swoim czasie 🙂

Używając takiego generycznego interfejsu, jesteśmy w stanie w łatwy sposób napisać implementacje dla innej bazy danych. Co za tym idzie, jej zmiana w trakcie życia projektu nie będzie bolesna. A na czas testowania zamockujemy odpowiednie metody.

Mając to wszystko, przejdźmy do implementacji AthleteService.

Zauważmy tutaj dwie rzeczy, w ListAll dokonujemy mapowania z modelu domenowego na wynikowy (AthleteOverview). Przy takiej implementacji zostanie to wykonane to w pamięci aplikacji, a mogłoby być wykonane na bazie danych.

Drugim mechanizmem, który warto używać w takiej metodzie to paginacja, sortowanie, filtrowanie itd. Pobieranie wszystkich elementów naraz rzadko kiedy jest potrzebne. Zazwyczaj chcemy pobrać 10/20/40 itd. elementów per ekran, a później dociągać kolejne.

Takie rozwiązania mogłyby działać lepiej z punktu widzenia wydajności, jednak najlepiej to porównywać w konkretnych przypadkach. Nie zawsze idealne rozwiązanie będzie optymalnym, musimy pamiętać o jego adekwatności do problemu.

Nie strzelaj do muchy z katapulty

Konfucjusz

Podsumujmy dotychczasowe zmiany i zobaczmy na plik projektu ApplicationCore. Czy udało się nam nie użyć EntityFramework’a? A no udało!

Infrastruktura

Pora skonfigurować naszą bazę danych. Na start zainstalujmy odpowiednie biblioteki i stwórzmy AppDbContext.

Paczki, które musimy zainstalować to (tyczy się to projektu Infrastructure):

  • Microsoft.EntityFrameworkCore – podstawowa paczka do EF Core
  • Microsoft.EntityFrameworkcore.Relational – tę paczkę potrzebujemy do ustawienia DefaultSchemaName
  • Microsoft.EntityFrameworkCore.SqlServer – implementacja EF Core dla SqlServera
  • Microsoft.EntityFrameworkcore.tools – bez tego nie dodamy migracji danych
  • Dziedziczymy po DbContext, czyli wbudowanej klasie z EntityFramework’a, dodajemy konstruktor.
  • Dodajemy DbSet jako property klasy. Czyli będziemy mieli tabele o nazwie „Athletes” o strukturze klasy Athlete
  • OnModelCreating to metoda dostarczana z DbContext, możemy w niej dodać swoją dodatkową konfigurację.
    • Ustawiamy domyślną nazwę schema (czyli takiego logicznego bytu, który pomaga podzielić bazę danych na podczęści, coś jak namespace).
    • Użycie ApplyConfigurationsFromAssembly sprawia, że w naszej aplikacji wyszukiwane są wszystkie klasy implementujące IEntityTypeBuilder<T> i ich kod jest wykonywany podczas tworzenia modelu bazy. Przykładowo zmienimy nazwę tabeli, do której będą zapisywane nasze obiekty. Najczęściej jednak definiujemy tutaj relacje pomiędzy encjami.

Mamy już bazę danych, to teraz dodajmy implementację naszego IRepository (oczywiście w projekcie Infrastructure).

WebApi

Obecnie mamy wiele różnych elementów, jednak nie są one ze sobą połączone. Aby to osiągnąć, użyjemy Dependency Injection, czyli mechanizmu który za nas podstawi implementację w miejsce interfejsów.

  • linie 28-31 – dodajemy Swaggera, czyli narzędzie do obsługi OpenApi. W skrócie jest to specyfikacja naszego API wraz z GUI.
  • linie 33-39 – dodajemy nasz AppDbContext, podając ConnectionString (czyli adres naszej bazy)
  • linia 40 – wskazujemy, że interfejs IRepository będzie implementowany przez klasę Repository. Dodajmy, że jest on generyczny, więc dla każdej klasy rozszerzającej Entity
  • linia 41 – analogicznie do wcześniejszego punktu IAthleteService -> AthleteService
  • linie 50-55 – oznaczamy żeby aplikacja w środowisku developerskim uruchamiała Swagger’a na głównym adresie (czyli po / )

Stwórzmy controller, a w nim trzy metody:

  • dodanie nowego atlety
  • wylistowanie wszystkich
  • pobranie szczegółów po ID

Nie będę opisywał już tego kodu, jednak nadmienię tylko, że wstrzykujemy nasz domenowy IAthleteService. Pochodzi on z kontenera DI, który wcześniej konfigurowaliśmy.

Brakuje nam jeszcze schematu bazy danych. Możemy ją łatwo utworzyć używając Package Manager Console oraz komendy Add-migration. Następnie nanosimy stworzoną migrację na istniejącą bazę przy użyciu polecenia Update-Database.

Repository Pattern
Pamiętaj żeby wybrać projekt Infrastructure jako „Default project”, a żeby projekt WebApi był ustawiony jako „Startup Project”

Nie pozostaje nic innego jak uruchomić API i sprawdzić czy wszystko działa.

Podsumowanie

Przeszliśmy od stworzenia pierwszego modelu, przez dodanie bazy danych, aż po uruchomienie API opartego o Clean Architecture. Zależało mi by skupić się na pokazywaniu kodu i mojego podejścia do problemu, aniżeli pokazywaniu diagramów, które znamy już z innych samouczków.

Jak widzimy, takie podejście do kodu jest bardzo praktyczne i wygodne. Oczywiście w każdym momencie możemy wykorzystać dedykowane repozytorium danych (zamiast generycznego) albo co też pokaże w kolejnych wpisach Specification Pattern.

Link do repozytorium znajdziesz >TUTAJ<

Dodaj komentarz Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Wyszukaj

Blazor newsletter

loader
Coś poszło nie tak... Spróbuj ponownie
Udało się!

Newsletter | power-dev.pl

Zapisz się by niczego nie przeoczyć!

Najnowsze wpisy

  • Repository Pattern – Domain Layer bez EntityFramework’a.
  • Blazor 102 – Zacznijmy od WebAssembly
  • Blazor 101 – SPA w C#
  • Programowanie 101. Enkapsulacja, hermetyzacja czy kapsułkowanie?
  • Clean Architecture 101. Tworzenie solucji w .NET CORE.
©2023 Power-Dev | Powered by SuperbThemes & WordPress