Piszę o programowaniu w ASPNET CORE. Jaką architekturę wybrać dla swojego projektu

Logo wieżowców w chmurach

Jaką architekturę wybrać dla swojego projektu? 3 Propozycje

Czemu to takie ważne by wybrać dobrą architekturę projektu?

W życiu przychodzi moment kiedy każdy musi zadać sobie ważne pytanie : „Jak to wszystko ułożyć, żeby się później nie narobić”. Tylko dlaczego samemu wymyślać koło na nowo? Można przecież wykorzystać już przemyślaną architekturę projektu?

„If you think good architecture is expensive, try bad architecture.”

Brian Foote and Joseph Yoder

Aktualnie najpopularniejsze są trzy typy architektur:

  • All-in-one
  • N-Layer
  • Clean Architecture

All-in-one

Pierwsza z nich polega na tym że wszystkie pliki (klasy, interfejsy itd.) są rozmieszczone w folderach, w ramach jednego projektu. Zatem nie jest to architektura dla większego projektu, niż prosty tutorial lub prostej aplikacji.

Architekturę projektu
https://docs.microsoft.com/pl-pl/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures

Plusy:

  • prostota
  • mały narzut kodu

Minusy:

  • tl;dr; Spaghetti code
  • kod jest trudny do utrzymania
  • dżungla folderów
  • brak wydzielonego miejsca na konkretny typ logiki (np. domenowa, UI, data access layer)
  • brak hierarchii pomiędzy folderami i znajdującymi się w nich komponentami
  • brak możliwości współdzielenia elementów pomiędzy aplikacjami (np. modelu domenowego pomiędzy aplikacją webową i desktopową)

Podsumowując nie jest to rozwiązanie dla jakiegokolwiek projektu, który ma przeżyć dłużej niż kilka wieczorów.

N-Layer

Mamy tutaj do czynienia z podziałem solucji na pod-projekty, gdzie każdy z nich przeznaczony jest do „czegoś”. Takie rozwiązanie jest poparte zasada pojedynczej odpowiedzialności (ang. „Single Responsibility Priciple”). Każdy- czy to student, czy samouk- przynajmniej słyszał o niej.

A MODULE SHOULD BE RESPONSIBLE TO ONE, AND ONLY ONE, ACTOR

Robert C. Martin
One scrum to rule them all

Mówi ona coś na pozór bardzo prostego: Jeden moduł (klasa, metoda), robi tylko jedną czynność. Jednak Robert C. Martin zwraca uwagę na to że „jedną czynność”, dla różnych aktorów (w różnych domenach), może oznaczać coś zupełnie innego. Przez co powinniśmy uważać na współdzielone metody. Każda zmiana w nich, może zebrać żniwo w miejscach gdzie są wywołane.

Wracając do warstw. Najczęściej wyróżniamy trzy:

  • Data Access Layer (DAL) – tutaj zazwyczaj zamieścimy encje, dbContext, migracje, klasy z metodami uzupełniającymi bazę (ang. „Seeders”)
  • Business Logic Layer (BLL) – znajdziemy tu implementację reguł biznesowych rządzących naszą aplikacją
  • User Interface Layer (UIL) – tak jak nazwa wskazuje, interfejs użytkownika np. Razor Pages

Należy pamiętać że zależności przebiegają wgłąb. Czyli elementy UI powinny odwoływać się do warstwy logiki domenowej. Zaś dostęp do bazy danych zawsze powinien odbywać się za pośrednictwem DAL.

Jaki jest problem z tym podejściem? Nasza logika domenowa oparta jest o konkretna warstwę – warstwę dostępu do bazy. „Dependency Inversion Principle” mówi o tym że najbardziej plastyczne systemy opierają się o abstrakty.

The Dependency Inversion Principle tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.

Robert C. Martin

Konkretnie – nasze serwisy powinny komunikować się nie przez konkretną implementację (np. EntityFramework’owy DbContext) ale przez interfejs który ma zdefiniowane metody typu Create, Read, Update, Delete itp. (https://pl.wikipedia.org/wiki/CRUD). Więcej o tym w sekcji o „Clean Architecture”

Plusy:

  • czytelność i łatwość odnalezienia konkretnych modułów
  • można współdzielić DAL i BLL pomiędzy aplikacjami (np. webową i desktopową)
  • Możliwość wymuszenia kierunku komunikacji modułów (poprzez referencje projektu)

Minusy:

  • Nie jest tutaj zastosowana zasada „The Dependency Inversion Principle”

Clean Architecture

No i crème de la crème, tutaj kod piszę się sam <wink>. Architektura znana również jako „Hexagonal Architecture”, „Ports-and-Adapters” lub „Onion Architecture”.

Główną różnicą w porównaniu z N-Layer jest zastosowanie odwrotnej zależności, czyli najniższą warstwą jest logika biznesowa. Jest ona niezależna. Można wydzielić osobny projekt na encje – dla wygody i porządku. Jednak z punktu widzenia logicznego podziału, oba projekty są dla nas warstwą domenową.

Czysta architektura; widok cebuli
https://docs.microsoft.com/pl-pl/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures#clean-architecture

Aby móc skorzystać z odwrotnej zależności, w najgłębszej warstwie musimy zdefiniować interfejs który będzie odpowiedzialny za dostęp danych. Jego implementacja zostanie wykonana w projekcie „Infrastructure” – zazwyczaj będzie ona wykorzystywać Entity Framework. Jednak nic nie stoi na przeszkodzie żeby wykorzystać do tego bazy NO-SQL (Redis, MongoDb, Cassandra), IMemoryCache albo system plików.

Wszystko fajnie, a co z testami?

Za przykład weźmy testy jednostkowe, najprostsze i najczęściej wykorzystywane. Z racji że „biznes” jest niezależny, więc możemy testować jego reguły bez udziału innych warstw. Musimy tylko podać jakąś implementację interfejsu dostępu do danych (np. EF + baza inMemory lub moq).

Plusy:

  • Zastosowanie „The Dependency Inversion Principle” przez co biznes jest niezależny
  • czytelność i łatwość odnalezienia konkretnych modułów
  • można współdzielić DAL i BLL pomiędzy aplikacjami (np. webową i desktopową)
  • Możliwość wymuszenia kierunku komunikacji modułów (poprzez referencje projektu)

Minusy:

  • Aby zachować niezależność biznesu może się zdarzyć że będziemy musieli definiować nadmiarowe interfejsy i kombinować

Podsumowanie

Zgodnie z pierwszym cytatem brak dobrej architektury jest drogi. Dlatego warto poświęcić swój czas na zaimplementowanie dobrego szkieletu projektu. Zaoszczędzi to naszego wysiłku w kolejnych miesiącach implementacji. Należy pamiętać że nie ma sensu używać np. Clean Architecture dla projektu na jeden wieczór, w którym sprawdzamy jak działa nowa wersja naszej ulubionej biblioteki 🙂

Więcej o samym Clean Architecture i wykorzystywaniu w nim różnych popularnych bibliotek znajdziesz w kolejnych moich wpisach.

Next

Clean Architecture 101. Tworzenie solucji w .NET CORE.

5 Comments

  1. Fajny artykuł. Konkretnie i na temat ?.

  2. revismkk

    Ja tutaj w paru elementach się nie zgodzę. Moim zdaniem:
    1. Wybór architektury nie ma nic wspólnego z Spaghetti-Code, naśmiecić można w każdym przypadku.
    2. Dżungla folderów nie różni się niczym od dżungli projektów. Zmienia się tylko poziom zagnieżdżenia bałaganu. Czytaj naśmiecić można w każdym przypadku 😉
    3. Zastosowanie DI nie jest zależne od architektury. Chociaż, większa separacja niejako wymusza lepszy kod bo inaczej można zginąć pod natłokiem „circular dependency”
    4. Na All-In-One patrzyłbym raczej od strony, że wszystko jest od wszystkiego zależne, wymieszane. Argument za umieszczeniem w jednym projekcie jest tutaj mylący Podział na kilka projektów automagicznie niczego nie naprawia. Przecież nic nie stoi na przeszkodzie, aby N-Layer był w jednym projekcie (patrz pkt 2). Struktura projektów powinna bardziej odzwierciedlać skalę systemu. Jak system jest mniejszy i nie potrzebują wielu interfejsów zewnętrznych (równolegle web, desktop, api) to jeden projekt może wystarczyć. Byle kod w nim był faktycznie odpowiednio od siebie odseparowany zgodnie ze sztuką. Jeżeli tak będzie to nawet przy rozwoju można go za potrzebą łatwo rozdzielić na kolejne projektu .

    • Dzięki za bardzo konstruktywny komentarz!
      Dodam małe sprostowanie.
      Zacznę od pkt 3. Oczywiście DI jest elementem niezależnym od architektury. Gdy mówiłem o Dependency Inversion Principle, chodziło o odwrócenie zależności czyli warstwa niższa operuje na Interfejsie dostarczonym z warstwy nadrzędnej. Najprostszym przykładem jest Serwis Notyfikacyjny, w domenie jest “Wyślij powiadomienie” a wyżej decydujemy czy to będzie mail czy sms.
      Co do 1/2/4. Oczywiście racja. Mało co nas obroni przed niechlujstwem. Podzielenie tego na projekty jednak mocno wydziela pewne granice i może ułatwić trzymanie kodu w ryzach.

      Jeszcze raz dzięki za podzielenie się opinią na pewno wezmę sobie do serca Twoje uwagi.

  3. Minusem architektury cebulowej jest narzut przy tworzeniu prostych modułów.
    Przepisuje właśnie mały projekt zrobiony w architekturze „encje na twarz i pchasz” na DDD w cebulce. Idzie przynajmniej 3-4 razy wolniej.
    Zaletą tej architektury jest fakt iż dużo łatwiej jest dobrać rozmiar mikro-serwisu tak żeby nie był nano-serwisem.

    • Dokładnie! Trzeba pamiętać o adekwatności narzędzia/rozwiązania do problemu.

      ps. kradnę „encje na twarz i pchasz” ???

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Powered by WordPress & Theme by Anders Norén