Budowanie domeny aplikacji to najważniejsza i najtrudniejsza część każdego projektu. Ten złożony proces posiada trzy najważniejsze składniki. Zaczniemy od SOLID-nych encji, czyli zaprojektowania i zaimplementowania wyglądu naszych danych. Nastepnie, już w osobnym wpisie, będzie logika biznesowa i komunikacja ze światem zewnętrznym.
Artykuł jest częścią serii “Czysta architektura w Pythonie”. Zachęcam cię do przeczytania pierwszej, wstępnej części, jeżeli jeszcze tego nie zrobiłeś.
Czym jest domena aplikacji?
Często widzę, że ludzie mają problem ze zrozumieniem czym jest domena aplikacji. To naturalne, też tak miałem. Jest to spowodowane tym, że ogólnie czysta architektura zmienia całkowicie sposób patrzenia na nasz kod i etapy jego wytwarzania.
Domena aplikacji to najważniejsza część naszego systemu. Składa się ona z trzech elementów:
- danych, zwanych encjami
- logiki biznesowej, czyli przypadków użycia
- schematu komunikacji ze światem zewnętrznym – interfejsów
Gdzie tkwi problem? W tym, że encje to nie schemat bazy danych. Domena aplikacji nie skupia się na sposobie przechowywania danych. Domena definiuje jedynie jak te dane mają wyglądać i nic więcej. Ostatecznie, jedna encja może zostać zapisana do kilku różnych baz danych, lub odwrotnie, jedna tabelka będzie przechowywać dane kilku encji. Jest to bardzo trudno przewidzieć na początku, dlatego tego teraz nie robimy 🙂
Mini projekt
Najprościej będzie nam zobaczyć domenę tworząc prosty projekt, o którym już wspominałem w poprzedniej części. W końcu jest to seria “Czysta architektura w Pythonie”, więc kiedyś trzeba wreszcie zacząć kodowanie.
Stworzymy sobie jedną, małą usługę, będącą częścią dużego sklepu. Nasza usługa będzie odpowiadać za składanie zamówień.
Jej przypadki użycia to:
- tworzenie zamówienia
- przeglądanie listy zamówień
- dodawanie przedmiotów do zamówienia
Projekt będziemy implementować w kolejności, jaka mi była potrzebna do zbudowania dużego, produkcyjnego systemu. Nie korzystałem tutaj bezpośrednio z TDD, ale nie martw się, ten temat będzie poruszany na łamach migawka.it.
Encje
Pierwszym elementem domeny, który będziemy budować są encje. Zaprojektujemy w kodzie wygląd naszych danych. Dobrze jest chwilę przemyśleć jak będą one wyglądać, jednak pamiętajmy, że techniki zwinne zakładają zmienność. Nasze encje nie muszą być idealne i obsługiwać wszystkich przyszłych funkcjonalności. Zamiast tego nasz kod powinien być otwarty na przyszłe zmiany.
Wybór technologii
Jak w Pythonie możemy zdefiniować model danych? Zazwyczaj wykorzystalibyśmy do tego SqlAlchemy albo Django ORM. Przywiązaliśmy się zatem do bazy danych i sposobu przechowywania tych danych. Ktoś powie, że przecież w każdym z tych ORM-ów możemy łączyć się z różnymi bazami danych: Postgresem, MySQL, SQLite itp. Jednak to nadal duże ograniczenie. Jeśli do tworzenia encji skorzystamy z ORM-a to przywiążemy się do baz relacyjnych, a przecież na nich świat się nie kończy. Co się stanie jeśli będziemy chcieli skorzystać z bazy no-sql, albo grafowej?
Do definiowania encji powinniśmy skorzystać z czegoś prostszego. W ten sposób pozostajemy elastyczni, a silniki bazodanowe będziemy mogli zmieniać jak rękawiczki. W takich przypadkach najczęściej sięgam po dataclasses. Są to proste klasy do przechowywania danych, które zostały wprowadzone do standardowej biblioteki Pythona od wersji 3.7. Nie martwcie się, w wersji 3.6 jest dostępna paczka więc możecie je doinstalować za pomocą komendy pip install dataclasses
Encja bazowa
Przygotowywanie encji zaczniemy od stworzenia encji bazowej.
Nie robi ona wiele, jednak wszystkie nasze encje będą z niej dziedziczyć. W ten sposób będziemy mogli stworzyć mechanizmy wspólne dla całej naszej domeny. Na pewno się to przyda pod koniec serii, ale na razie musisz mi zaufać 🙂
Nasze dane
Oto w jaki sposób możemy zdefiniować wygląd naszych danych. Jak widzisz, dzięki dataclass nie kosztuje to nas dużo wysiłku. Deklarujemy tylko jakie pola chcemy przechowywać. Nie wymaga to od nas przygotowania migracji, stawiania bazy danych i całej serii kroków potrzebnych przy korzystaniu z ORM, a to tylko bonus jaki otrzymujemy z takiego podejścia. Prawdziwy zysk zobaczymy, gdy dotrzemy do testów jednostkowych.
Typowanie
Istotną kwestią dla naszej wygody, a także bezpieczeństwa i stabilności aplikacji, jest typowanie. Co ważne to typowanie musi być precyzyjne, na co najlepszym przykładem jest `Order.items`. Nie tylko zdefiniowaliśmy, że jest to lista, ale także jasno określiliśmy, jakiego typu są jej elementy – produkty.
Dzięki dokładnemu definiowaniu typów, nie musimy szukać miejsca w kodzie, gdzie ta lista jest tworzona, aby sprawdzić co właściwie się w niej znajduje. Kompletna informacja znajduje się w definicji encji. Teraz wiemy, jakie operacje możemy na niej wykonać.
Nie tylko dataclasses
Dataclasses to nie jedyna możliwość na przechowywanie encji w czystej architekturze. Jeśli korzystamy ze starszych wersji Pythona 🙈albo chcemy aby nasze encje robiły więcej, np wykonywały też walidację danych 👮, mamy kilka innych bibliotek:
Bez problemu można z nich skorzystać i przejść przez całą naszą serię bez zmian w dalszych częściach. Zdecydowanie jednak odradzam robienia zbyt wielu rzeczy w encjach. Może to doprowadzić do zmieszania się logiki biznesowej z modelem. To zmniejsza czytelność kodu i może spowodować problemy, np. przy rozbijaniu kodu na mniejsze usługi. Ciężko jest zdefiniować, jakie operacje mogą być zdefiniowane w encjach, a jakie już nie. Dlatego całkowicie tego unikam i korzystam z najprostszych – dataclasses.
Dalsze kroki
Definiowanie encji, to dopiero pierwszy etap w przygotowaniu naszej domeny, pozostały nam jeszcze dwa. Najważniejsza, logika biznesowa oraz komunikacja ze światem zewnętrznym, ale to w następnym wpisie.
Jeśli masz jakieś uwagi odnośnie formy, prezentacji, to pamiętaj: te wpisy są dla was! Daj znać, które tematy powinniśmy szerzej omówić. Więcej teorii, więcej kodu? Wystarczy, że wyślesz swoje uwagi na grzegorz@migawka.it, albo prościej – napiszesz komentarz pod artykułem.
Do zobaczenia!