Struktura projektu na przykładzie Django i FastAPI

Styczeń 10, 2024 | #django , #python , #fastapi

000000
011100
010110
100001

Planując nową aplikację decydujemy się na wybór technologii, z pomocą których będziemy go realizować. Zazwyczaj opieramy się o język i narzędzia, z którymi mamy już jakieś pozytywne doświadczenie i gwarancję sukcesu, ale czasami z tych lub innych względów stajemy przed koniecznością zaprzyjaźnienia się z nieznaną nam technologią. Nowy język to zbyt duże wyzwanie, żeby na start myśleć o samodzielnej realizacji poważnego projektu, ale nowy framework to dla doświadczonego developera ciekawa przygoda.

Ważne żeby dobrze zacząć. Większość tutoriali, czy to z oficjalnej dokumentacji danego narzędzia, czy też przygotowanych przez użytkowników traktuje o podstawach, nie przywiązując wagi do prawidłowej architektury projektu. Przykładowe endpointy, połączenie z bazą danych, autoryzacja itp. realizowane są w jednym pliku co nie jest tragicznym pomysłem w przypadku mikroserwisu, ale zaczyna szybko kłuć w miarę rozwoju aplikacji.

Django MVT

Elastyczny framework umożliwia realizację dowolnej struktury projektu. Nie mniej na start lepiej jest podążać za dobrymi praktykami - rozwiązaniami które się sprawdziły i są powszechnie stosowane. Zaczynając pracę z Django od samego początku jesteśmy zaznajamiani z konwencją, zgodnie z którą struktura katalogów opiera się głównie na podziale projektu na aplikacje oraz oddzielaniu różnych aspektów projektu w logiczne segmenty.

Django używa wariantu wzorca architektonicznego MVC (Model-View-Controller), który jest często nazywany wzorcem MVT (Model-View-Template). W klasycznym MVC :

  • Model (Model) reprezentuje dane aplikacji ale też logikę biznesową potrzebną do manipulowania danymi,

  • Widok (View) odpowiada za prezentację tych danych użytkownikowi,

  • Kontroler (Controller) łączy te dwie warstwy. W praktyce kontroler interpretuje dane wejściowe z widoku, przekazuje je do modelu, a następnie odbiera aktualizacje modelu i przekazuje je z powrotem do widoku.

W Django rolę kontrolera pełnią widoki (View), a rolę widoków szablony (Template) z kolei w Django Rest Framework (DRF) przeznaczonym do budowania API REST-owego szablony są zastąpione przez serializery (Serializer), które przekształcają dane najczęściej do formatu JSON i możemy je uznać za warstwę prezentacji. W DRF możemy mówić o wariancie MVS (Model-VIew-Serializer).

Przykładowa struktura katalogów dla typowego projektu Django wygląda następująco

myproject/
    manage.py
    myproject/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    app1/
        migrations/
        static/
        templates/
        admin.py
        apps.py
        models.py
        tests.py
        views.py
    app2/
        migrations/
        static/
        templates/
        admin.py
        apps.py
        models.py
        tests.py
        views.py
    static/
    templates/
    media/

Każdy developer dołączając do projektu bazującego na Django natychmiast odnajduje się w kodzie. Wie gdzie szukać konfiguracji, gdzie definiować modele, czy klasy odpowiedzialne za generowanie panelu administracyjnego, gdzie serializery w przypadku Django Rest Framework, czy endpointy jak w Django Ninja. Framework ten i jego pochodne mają swoje problemy ale jeśli chodzi o strukturę katalogów i plików to przyjęte podejście bardzo ułatwia programistom rozpoczęcie nauki lub też wdrożenie się w już istniejący projekt.

FastAPI 3-Tier

FastAPI nie forsuje preferowanej struktury projektu. W przypadku większych aplikacji podpowiada możliwą strukturę plików, ale nie jest wprost powiedziane do jakiego wzorca architektonicznego nawiązuje.

├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│      ├── __init__.py
│      ├── items.py
│      └── users.py
│   └── internal
│       ├── __init__.py
│       └── admin.py

Na podstawie zaprezentowanej w dokumentacji próbki możemy jednak od razu dostrzec, że Sebastián Ramírez nie zdecydował się na grupowanie plików według aplikacji, jak to ma miejsce w Django.

Wzorzec architektoniczny 3-Tier, znany również jako trójwarstwowa architektura, jest wzorcem projektowym stosowanym w projektowaniu aplikacji oprogramowania, w którym struktura aplikacji jest podzielona na trzy główne poziomy lub warstwy:

  • Warstwa prezentacji (Presentation Layer): Jest to najwyższa warstwa architektury. Odpowiada za interakcję z użytkownikiem końcowym, zbieranie danych wejściowych użytkownika i prezentowanie informacji.

  • Warstwa logiki biznesowej (Business Logic Layer, BLL): Ta warstwa znajduje się pomiędzy warstwą prezentacji a warstwą danych. Jest odpowiedzialna za przetwarzanie danych wejściowych z warstwy prezentacji, stosowanie określonych reguł biznesowych, wykonywanie operacji i algorytmów, a następnie przekazywanie wyników z powrotem do warstwy prezentacji lub dalej do warstwy danych.

  • Warstwa danych (Data Layer): Obejmuje ona przechowywanie, zarządzanie i dostęp do danych. Warstwa danych może obejmować bazy danych, serwisy plików, lub inne mechanizmy przechowywania danych. Jest odpowiedzialna za dostarczanie danych do warstwy logiki biznesowej, a także za ich odbiór i zapisywanie.

Wzorzec 3-Tier w podstawowych założeniach jest podobny do architektury MVC, ale jest bardziej związany z fizycznym podziałem aplikacji, gdzie każda warstwa może być rozłożona na różne serwery lub klastry lub jak w tym przypadku zgrupowana w osobne foldery. W związku z tym wstępny podział plików na aplikacje mijał by się z celem. Dlatego struktura projektu opartego na frameworku FastAPI zgodna z 3-Tier mogłaby się kształtować zgodnie z tym co znajdziemy w artykule pt. Structuring FastAPI Project Using 3-Tier Design Pattern

app/  
├── db/   ├── config.py   └── session.py  
├── crud/  ├── auth.py
│ ├── item.py   └── ...
├── models/  ├── auth.py   ├── base.py
│ ├── item.py
│ └── ...
├── routers/  ├── auth.py
│ ├── item.py  └── ...  
├── schemas/  ├── auth.py
│ ├── item.py  └── ...  
├── services/   ├── auth.py   ├── base.py   └── ... 
└── main.py

gdzie:

Warstwa prezentacji składa się z endpointów API, które są definicją interfejsów, przez które użytkownicy komunikują się z aplikacją.

app/ 
├── routers/   ├── auth.py   └── ... 
├── schemas/   ├── auth.py   └── ... 
└── main.py

Warstwa logiki biznesowej zawiera logikę specyficzną dla aplikacji.

app/
├── services/   ├── auth.py  └── ...

Warstwa danych zajmuje się wszystkim, co jest związane z przechowywaniem, pobieraniem i zarządzaniem danymi.

app/  
├── db/
│ └── session.py
├── crud/   ├── auth.py
│ └── ...
├── models/   ├── auth.py
│ └── ...

W przeciwieństwie do Django w FastAPI nie ma ustalonej konwencji nazewnictwa. Operacje na modelach bazy danych mogą być trzymane w katalogu crud albo repositories, endpointy odnajdziemy w api albo routers a logika znajdzie swoje swoje miejsce w services, ale może też się znaleźć w core czy utils.

Z analogiczną architekturą spotkamy się zarówno boilerplate-ach - ułatwiających szybki start projektu opartego na FastAPI, ale też w większości tutoriali.

Alternatywne architektury

Yerassyl Zhanymkanov w artykule pt. FastAPI Best Practices zaprezentował z kolei strukturę inspirowaną narzędziem Dispatch (narzędzie open-source stworzone przez Netflix, które służy do zarządzania kryzysami i incydentami), twierdząc, że lepiej spełnia ona wymagania mocno rozbudowanych projektów czy wręcz monolitów.

app/  
├── alembic/
├── src
│   ├── auth
│      ├── router.py
│      ├── schemas.py
│      ├── models.py
│      ├── dependencies.py
│      ├── config.py
│      ├── constants.py
│      ├── exceptions.py
│      ├── service.py
│      └── utils.py
│   ├── aws
│      ├── client.py
│      ├── schemas.py
│      ├── config.py
│      ├── constants.py
│      ├── exceptions.py
│      └── utils.py
│   └── posts
│      ├── router.py
│      ├── schemas.py
│      ├── models.py
│      ├── dependencies.py
│      ├── constants.py
│      ├── exceptions.py
│      ├── service.py
│      └── utils.py
│   ├── config.py
│   ├── models.py
│   ├── exceptions.py
│   ├── pagination.py
│   ├── database.py
│   └── main.py
├── tests/
│   ├── auth
│   ├── aws
│   └── posts
├── templates/
│   └── index.html
├── requirements
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
├── .env
├── .gitignore
├── logging.ini
└── alembic.ini

Jest wiele wzorców architektonicznych (MVVM, MVP, SPA) i wiele z nich ma wpływ logikę podziału kodu często odzwierciedloną przez hierarchię plików i katalogów. Ostateczny kształt struktury ma niebagatelny wpływ na kilka aspektów związanych z projektowaniem i implementacją aplikacji a są to:

  1. Separacja obaw (Separation of Concerns): Wzorce architektoniczne pomagają oddzielić różne aspekty aplikacji (np. interfejs użytkownika, logikę biznesową, dostęp do danych) i jest to podział wertykalny, ale może też odnosić się do podziału horyzontalnego, który w praktyce daje np. możliwość wyłączenia z działania części funkcjonalności.

  2. Ponowne użycie kodu (Code Reusability): Modułowa budowa, ułatwia wykorzystanie istniejącego kodu w różnych częściach aplikacji.

  3. Elastyczność i skalowalność: Dobrze zaprojektowana struktura plików umożliwia łatwe wprowadzanie zmian, rozbudowę aplikacji i skalowanie bez konieczności przeprojektowywania całej aplikacji. W razie potrzeby ułatwia też wydzielenie pewnych funkcjonalności do nowych projektów lub wycofywanie zdeprecjonowanych rozwiązań.

  4. Testowanie i konserwacja: Wyraźne oddzielenie komponentów ułatwia testowanie jednostkowe i debugowanie. Składniki aplikacji mogą być testowane niezależnie, co znacząco poprawia jakość kodu.

  5. Unifikacja pracy: Wprowadzenie konwencji ułatwia wdrażanie nowych programistów do zespołu, co pozytywnie wpływa na współpracę i komunikację.

  6. Integracja: Zdefiniowane interfejsy i moduły ułatwiają integrację z zewnętrznymi systemami, bibliotekami lub serwisami.

  7. Przewidywalność: Uznane struktury oparte na wzorcach architektonicznych pozwalając unikać błędów popełnionych już przez kogoś innego. Z reguły są dobrze opisane i wiadomo jakie mają mocne i słabe strony i czym się charakteryzują.

Nie ma jednego idealnego przepisu i dobór rozwiązania zależy od specyfiki projektu. O ile domyślna architektura aplikacji zbudowanych na FastAPI jest postrzegana jako preferowana dla małych projektów typu mikroserwisy, o tyle duże projekty, bogate w zależności i rozbudowaną hierarchię wewnętrzną mogą wymagać już innej struktury lepiej oddającą wewnętrzne podziały.

Akceptuję Ta strona zapisuje niewielkie pliki tekstowe, nazywane ciasteczkami (ang. cookies) na Twoim urządzeniu w celu lepszego dostosowania treści oraz dla celów statystycznych. Możesz wyłączyć możliwość ich zapisu, zmieniając ustawienia Twojej przeglądarki. Korzystanie z tej strony bez zmiany ustawień oznacza zgodę na przechowywanie cookies w Twoim urządzeniu.