Warstwa Abstrakcji - stanowi izolację pomiędzy kodem programisty a zewnętrznymi modułami, pozwala odseparować system od jego zależności przez co czyni go podatnym na zmiany i aktualizacje. Przywiązując system do innej usługi czynimy go słabym.

Zewnętrzne zależności w modelu biznesowym post

2021-02-01

Model biznesowy, domena, logika biznesowa - jakkolwiek nie nazwiemy kodu, który w założeniu reprezentuje sedno projektu, często zgadzamy się co do jednego. Ten model nie powinien zawierać zewnętrznych zależności, tylko czy aby na pewno?

Domain Model, model zapisany w wybranym języku programowania mający odzwierciedlić proces/problem biznesowy, składając się z zachowań i danych.

DDD, czyli podejście opisane w 2003 roku przez Erica Evansa w książce o tym samym tytule, skupia się właśnie na tej części oprogramowania. Pasuje ono idealnie do często skomplikowanych procesów biznesowych i ma na celu ich ujednolicenie i zorganizowanie w czytelny i zrozumiały sposób. Zrozumiały zarówno dla programistów, poprzez wprowadzanie szeregu building blocków z których mogą korzystać, jak również dla osób nietechnicznych za pomocą spójnej nomenklatury zwanej Ubiquitous Language.

Kluczową częścią książki zwanej potocznie Blue Book (okładka książki jest niebieska) jest idea segregacji i organizacji skomplikowanych modeli biznesowych za pomocą Bounded Contextów. Temat ten jest na tyle interesujący, że dookoła samego procesu odkrywania złożoności logiki biznesowej zdefiniowano specjalne techniki pozwalające na jej odkrywanie, segregacje i agregację. Więcej na ten temat można przeczytać tutaj: https://www.eventstorming.com/.

Wracając jednak do modelu biznesowego, modelu domeny. W systemach rozwiązujących skomplikowane i złożone procesy biznesowe, to właśnie ten model jest kluczowy a jego odpowiednie przygotowanie i odkrycie nie jest wcale łatwym zadaniem. Świadczy o tym chociażby mnogość publikacji na ten temat, liczne materiały online, książki, techniki, kursy czy też szkolenia. Patrząc na to wszystko z tej perspektywy można wysnuć więc wniosek, że jest to najistotniejsza część systemu. Ta część, która powinna być idealnie zaprojektowana, zaimplementowana i przetestowana. Tutaj nie ma miejsca na zbędne byty. Nie ma miejsca na błędy. Model domeny musi być nieskazitelny.

Ponieważ sam bardzo często popadam w różne skrajności, nie dziwi mnie, że bardzo często możemy spotkać się właśnie z takim przekonaniem. Co tutaj dużo mówić, przekonałem się o tym na własnej skórze i właśnie o tym będzie ten wpis.

Jednym z większych błędów jakie popełniłem przy projektowaniu modelu domeny było całkowite wyzbycie się wszelkich zależności na rzecz czystości kodu i błędnie pojętego puryzmu. Zaczęło się dość niewinnie, bo od zwykłych identyfikatorów. PHP, w którym to budowałem te modele nie dostarczał własnej implementacji UUID, nie zagłębiałem się nigdy w powód odrzucenia rfc:uuid. Prawdopodobnie chodziło o to, że implementacja jest możliwa w userlandzie (w phpie) więc dokładanie jej do struktury języka nie daje wielkich korzyści a dokłada pracy przy utrzymaniu - trzeba by się zagłębić w dyskusję na temat tego RFC.

Nie brnąc dalej w powody, które na potrzeby tego wpisu nie są istotne pomyślałem, że dobrze będzie, aby w modelu domeny identyfikatory reprezentowane były przez dedykowane obiekty. Obiekty, które będą uniemożliwiać pomyłkę i użycie niewłaściwego identyfikatora w niewłaściwym miejscu. Wyglądało to mniej więcej tak:

<?php

declare (strict_types=1);

namespace Example\Domain;

use Ramsey\Uuid\UuidInterface;
use Ramsey\Uuid\Uuid;

final class UserId
{
    private UuidInterface $id;

    private function __construct()
    {
        $this->id = Uuid::uuid4();
    }

    public static function generate() : self
    {
        return new self();
    }
}

Jak widać na załączonym przykładzie pozbycie się zewnętrznych zależności nie do końca wyszło. Zależność istniała, ja jedynie przykryłem ją drobną abstrakcją, która przy drobnej zależności nie sugerowała jeszcze żadnych poważniejszych konsekwencji. Po pewnym czasie zorientowałem się, że w ten sposób tylko powielam byty i każda nowa encja wymuszała posiadanie dedykowanej klasy identyfikatora. Co więcej patrząc na przykłady kodu w innych językach, które GUID posiadają jako część swojego interfejsu, wyglądało to trochę na przerost formy nad treścią. Trochę tak jakbym budował wrapper na coś bardzo reużywalnego tylko po to, aby nie dało się tego globalnie używać.

Kiedy zauważyłem tę niepotrzebną duplikację zacząłem się zastanawiać gdzie właściwie przebiega granica, tego co można użyć a czego nie można w modelu domeny. Natrafiłem na kolejną niespójność w swoim rozumowaniu. Praktycznie każdy model domeny wcześnie czy później dotknie kwestię daty i czasu. Tak też było w moim wypadku, sporo encji czy też zdarzeń domenowych zależnych było od klasy \DateTimeImmutable a w zasadzie od prostego interfejsu Calendar który to aktualny czas dostarczał (lub pozwalał przesuwać na potrzeby testów). W tym konkretnym wypadku PHP dostarcza obiektową reprezentację problemu, którego dotykał mój model domeny. Należy dodać, że model biznesowy nie opierał się on zarówno ani o GUID, ani o DateTimeImmutable jednak gdyby nie te dwie klasy, których w zasadzie nie stworzyłem, mój model byłby niekompletny. O ile GUID z punktu widzenia ubiquitous language jest zbędny i mogłem go jeszcze traktować jako zło konieczne czy też nieistotny detal techniczny, tak o wiele trudniej było zignorować fakt, że pod \DateTimeImmutable kryła się bardzo skomplikowana logika.

Przez chwilę nawet zacząłem się zastanawiać czy przypadkowo nie skaziłem swojego idealnego modelu. Idąc jednak tym tokiem rozumowania szybko moglibyśmy dojść, do pomysłu, że sam język programowania jest niechcianą zależnością a model biznesowy powinien być przecież niezależny. Z tego miejsca już tylko jeden krok do paranoi i pokoju bez klamek w którym przy użyciu węgielka z kominka, na ścianie rysujemy niezależne i nieskazitelne modele domenowe.

Ok, model biznesowy opera się o inne modele, reprezentujące generyczne pojęcia jak unikalna identyfikacja, czy data i czas. Gdybyśmy chcieli wszystko pisać samodzielnie to nieskazitelność modelu bardzo szybko zniknęłaby pod warstwami zbędnej logiki. Na jej implementację najprawdopodobniej musielibyśmy poświęcić miesiące pracy. Czytelność zastąpiłyby nieistotne detale techniczne, które finalnie i tak najprawdopodobniej okazałyby się błędne. Model domeny, zamiast uprościć i tak już złożony problem biznesowy, jeszcze bardziej by bo skomplikował skupiając się na kwestiach, które są dla niego istotne, ale nie stanowią jego esencji. Na problemach, które ktoś, gdzieś już kiedyś rozwiązał w generyczny sposób, na tyle generyczny, że przyjęły się jako standard i stały prostym i intuicyjnym rozwiązaniem często skomplikowanego problemu.

Jak jednak wyznaczyć granicę, jak zdecydować co jest, a co nie jest na tyle generyczne, aby skorzystać z gotowego rozwiązania? Nie wiem czy na to pytanie istnieje jedno dobre rozwiązanie, postaram się jednak przybliżyć trochę bardziej mój tok rozumowania, na podstawie którego podejmuje decyzję.

Przez ostatnie lata pracowałem głównie z modelami opartymi o e-commerce. Często natrafiałem na problemy finansowe, logistyczne, marketingowe. Wszystkie one w mniej lub bardziej złożony sposób dotykały problemu daty i czasu. Zamówienia, faktury, zlecenia, zwroty, wypłaty. Te wszystkie operacje muszą być ulokowane w czasie. Najczęściej jest to przeszłość, czasami przyszłość, jednak prawie zawsze da się je umiejscowić na osi czasu. Bardzo często też, ich umiejscowienie dyktuje konkretne reguły biznesowe lub pozwala na odpowiednią agregację, która dopiero skutkuje konkretnymi decyzjami biznesowymi.

Im głębiej w las, tym gęściej. Logika i pewne problemy oparte o oś czasu zaczynają się powielać, niektóre w sposób bardzo spójny, inne wymagają dodatkowej abstrakcji. Osobiście najbardziej problematyczne były dla mnie iteracje oraz operację na zakresach czasu, aczkolwiek i strefy czasowe okazały się kilka razy niemałym wyzwaniem. Każdy kontekst, nad którym pracowałem, każdy system, posiadał co najmniej kilka duplikacji. Duplikacji, które w najlepszym wypadku były niemal identyczne a w najgorszym różniły się tak nieznacząco, że błędy znajdowały się dopiero na produkcji.

Pomyślałem wtedy, że bardzo przydałaby mi się ujednolicona, czytelna i solidnie przetestowana abstrakcja problemu Daty i Czasu, która przy okazji byłaby na tyle generyczna, aby móc wykorzystać ją przy tworzeniu modelu domeny. Inspiracją w tym zakresie była dla mnie biblioteka z Javy, Joda-Time. Kto programował w Javie ten wie, że dostarczana przez język abstrakcja daty i czas nie była zbytnio przyjazna dla programistów. PHP jednak niczego takiego nie miał, przynajmniej nic takiego co spełniałoby moje założenia.

Istnieje wprawdzie świetna biblioteka o nazwie Carbon, która jednak mimo wszystko jest rozszerzeniem natywnego \DateTime. Ja potrzebowałem czegoś bardziej generycznego, czytelnego i mnie przesiąkniętego PHP'em. Czegoś, co jasno pokazałoby w modelu domeny, że aktualną datę i czas nie bierzemy z powietrza czy statycznej fabryki now() a z kalendarza. Jeżeli jakiś proces potrzebuje aktualnej daty i czasu, zależy więc od kalendarza, nie od statycznej fabryki. Potrzebowałem czegoś, co już poprzez swoje API pokaże programiście, że istnieją względne i bezwzględne jednostki czasu. Kiedy ktoś z biznesu powie, że dany proces trwa miesiąc, znajomość problemu daty i czasu powinna nasunąć pytanie, a ile to jest miesiąc? Chciałem, żeby nawet bez zadawania tego pytania, programista próbując zrobić TimeUnit::month() zorientował się, że miesiąc, miesiącowi nierówny. Chciałem też, aby konstruując obiekt DateTime trzeba było jawnie zdeklarować, dzień, godzinę i strefę czasową. Strefa czasowa to nie jest najlepszy kandydat na niejawną zależność braną z systemu czy konfiguracji środowiska.

Mniej więcej w ten sposób powstały podwaliny projektu Aeon PHP, czyli generycznej, niezmiennej, czystej, solidnie przetestowanej i przyjaznej w użytkowaniu biblioteki będącej generycznym modelem Daty i Czasu. Modelem, na tyle uniwersalnym, aby dało się z niego zrobić zależność modelu domeny, który nie będzie odwracał uwagi od tego co istotne, a jednocześnie nie będzie wymagał większego skupienia podczas czytania.

Pierwotnie planowałem stworzyć tylko model Calendar ale na ten moment składa się on z kilku bibliotek, z których w zasadzie każda może posłużyć jako wsparcie modelu domeny.

Całość można by już nazwać małym frameworkiem do pracy z datą i czasem oraz jego pochodnymi.

Czy jednak fakt rozwiązywania jakiegoś konkretnego problemu jest wystarczającym kryterium do określenia stopnia zagrożenia płynącego z wykorzystania danej biblioteki?

Osobiście poza czytelnością interfejsu samej biblioteki i upewniam się, czy nie będzie ona wprowadzać mylnej/niespójnej z modelem biznesowym nomenklatury zwracam uwagę na kilka innych rzeczy. Jedną z tych rzeczy są zależności własne. Jeżeli biblioteka, z której planuje korzystać przychodzi z workiem własnych zależności, stają się one automatycznie moimi zależnościami, które w bardzo niejawny sposób dziedziczę. Poza oczywistymi komplikacjami jak np. możliwość wystąpienia konfliktów, czy wstrzymanie ważnych aktualizacji (np. upgrade wersji php), istnieją również problemy mniej oczywiste. Więcej zależności oznacza większą podatność na różnego rodzaju ataki. Kwestie bezpieczeństwa wymuszają więc częste aktualizacje. Aktualizację mogą oznaczać złamanie wstecznych kompatybilności. Złamanie wstecznej kompatybilności może oznaczać zmiany w naszym modelu.

Decydując się na wprowadzenie zależności rozważam również jej zasięg i rozmiar. Jeżeli tak jak w przykładzie Daty i Czasu, zależność rozwiązuje za mnie generyczny problem, niebędący sednem mojego modelu domeny a przy okazji wymusza spójność w podejściu do skomplikowanego zagadnienie, super. Jeżeli jednak do mojego kontekstu finansowego, chciałbym wprowadzić generyczny model faktur, który sam w sobie jest bardzo obszerny i złożony na tyle, że nie jest już kolejnym małym building blokiem a wręcz przejmuje rolę dominującą w kontekście, zapaliłaby mi się czerwona lampka. Lampka sugerująca, że skoro moja zależność, nie jest tylko zależnością wykorzystywaną przez moje obiekty a wręcz je zastępuje to być może należy wrócić do etapu planowania Bounded Contextów. Być może jest to subdomena pomocnicza. Coś czego nie opłaca się samemu pisać, gdyż biznes na tym nie polega a poza tym istnieją już odpowiednie rozwiązania na rynku. Faktury są więc zbyt dużym modelem abym osobiście rozważał go jako zależność modelu nad którym pracuje. Prędzej próbowałbym wyciągnąć to do osobnego kontekstu, którego w idealnych okolicznościach wolałbym nawet nie tworzyć a jedynie zintegrować. Pozostając jednak w dziedzinie finansów idealnym kandydatem na zależność jest natomiast biblioteka Money.

Pieniądze i waluty to również skomplikowane i jednocześnie stosunkowo proste zagadnienie, które źle zaimplementowane (podobnie zresztą jak data i czas) może prowadzić do poważnych konsekwencji. Nikt chyba nie chciałby dostać 10 tysięcy indyjskich rupii zamiast 10 tysięcy złotych wypłaty, prawda?

Przykłady można mnożyć, niektóre z nich znajdą pokrycie w książkach o archetypach oprogramowania, inne będą protezami na ubytki w danym języku. W PHP bardzo zgrabną i przyjemną protezą mogą okazać się biblioteki reprezentujące listy i kolekcje obiektów jak przykładowo munus czy psl.

W zasadzie im mniejsza, bardziej sprecyzowana odpowiedzialność danej biblioteki, tym lepiej.

Warto też zwrócić uwagę czy daną zależność jest na tyle generyczna, aby jej wykorzystanie było możliwe na wielu warstwach systemu zachowując przy tym jednolity środek transportu w postaci DTO opartych o typy proste. Czy da się z warstwy user interface'u przesłać za pomocą DTO do warstwy niżej datę i czas, kwotę w danej walucie, lub unikalny identyfikator? Oczywiście, że tak. Pieniądz reprezentuje kwota i waluta , identyfikator to ciąg znaków a data i czas ma nawet określony standard ISO 8601. Tego samego nie można jednak powiedzieć o fakturze. Ciężko i niewygodnie byłoby przepychać cały model faktury poprzez wszystkie warstwy systemu, o wiele lepiej jest przerzucić numer faktury, czyli jej identyfikator.


Masz pytanie?
Napisz: kontakt@zawarstwaabstrakcji.pl
Akceptuję

Ten serwis używa plików cookies. Więcej o plikach cookies.