Marcin Markowski - 20.02.2018

4 sposoby persystencji agregatów DDD

Persystencja zawsze budzi wiele emocji i skrajnych opinii. W tym artykule pokażemy, jakie opcje są do wyboru przy persystowaniu agregatów z DDD i jak pragmatycznie do nich podejść. Przejdziemy kolejno przez bezpośredni zapis agregatów, robienie Snapshotów, tworzenie osobnego modelu danych na podstawie zdarzeń, a na koniec dotrzemy do Event Sourcingu.

Bezpośredni zapis agregatu

Najbardziej oczywistym rozwiązaniem jest bezpośredni zapis samego agregatu. Jeżeli nie jest to pojedynczy obiekt, tylko cały ich graf, to oczywiście zapisowi podlegają wszystkie obiekty.

W świecie anemicznych encji

W przypadku anemicznych encji było to rozwiązanie zwykle dość proste. Przy braku złożonych zasad kompozycji, wystarczyło zmapować w jakiś sposób encje na schemat bazy danych i zadanie wykonane.

Za mapowanie odpowiadał najczęściej ORM. To czy bazował on na konwencjach, atrybutach / adnotacjach, czy konfiguracji jest tu nieistotne. Analogiczne zagadnienia wchodzą w grę przy dowolnej serializacji obiektów (np. do JSONa), gdyż zawsze trzeba jakoś przekazać metadane, jeżeli konwencje nie wystarczają.

Czy adnotacje ORMa w modelu są złe?

Atrybuty / adnotacje są najwygodniejszym sposobem przekazania metadanych. Są bardziej czytelne, nie wymagają hardkodowania nazw, zapewniają wygodny dostęp do prywatnych pól. Świadczą one jednak ewidentnie wpływie na model technicznych rozwiązań użytych aktualnie do persystencji. Dla wielu jest to sytuacja nie do zaakceptowania.

Inni uważają, że nie ma w tym nic złego, a rozwiązanie jest bardzo wygodne, więc czemu go nie stosować. Spór ten jest jednak całkowicie drugorzędny. W projekcie opartym na anemicznych encjach metadane mechanizmu do presystencji to naprawdę najmniej bolesne z licznych, bardzo mocnych powiązań. Nie oszukujmy się, wymiana bazy danych w takich projektach raczej nie jest przez nikogo traktowana na serio. Zwykle to baza jest tam sercem całego systemu.

Jak się to ma do agregatów DDD?

W przypadku agregatów z DDD sprawa nie jest już jednak tak prosta. Podstawową cechą tych obiektów jest enkapsulacja struktur danych i sposobów ich przetwarzania. Publicznie dostępne są jedynie zachowania. Czasami to za mało dla ORM. Wystawienie publicznie danych agregatu tylko po to, żeby ORM mógł je zmapować stwarza pokusę użycia ich również w innych miejscach. W praktyce przypilnowanie nieużywania publicznych właściwości obiektów jest niemożliwe. W grę wchodzą również inne modyfikacje modelu, takie jak dostosowywanie typów danych do tych, jakie wspiera ORM.

Architektura wspierająca eksperymenty

Drugą kwestią jest inne podejście do architektury i wyborów technologicznych w DDD. To co chcemy sprawdzić najszybciej to model, a więc zachowania, reguły biznesowe i ich ochrona. Jest to najtrudniejsza część, wymagająca często eksperymentowania i sprawdzenia wielu podejść. Da się ją zrobić abstrahując całkowicie od mechanizmu do persystencji. Skoro tak, to luźne powiązania zaczynają nabierać realnego uzasadnienia. Chcemy, aby nasza architektura wspierała różne opcje, bo odsuwamy w czasie decyzję o ostatecznym wyborze technologii. Nie chcemy się też zajmować ograniczeniami związanymi z konkretnym ORM, bo to odciąga nas od modelowania, które jest na tym etapie najistotniejsze.

Model ma pierwszeństwo

W tym przypadku spór nie jest już drugorzędny. Po pierwsze wybrana technologia do persystencji nie powinna wpływać na kształt modelu. Jeżeli ingerencja ogranicza się jedynie do adnotacji, to takie rozwiązanie może być ok. Jeżeli wymaga głębszych zmian, to jest to wyraźny sygnał do wybrania innej opcji. Istotne jest również skomplikowanie kodu zapewniającego meta informacje do mapowania i ilość technologicznych kruczków, które trzeba mieć w głowie, żeby zrobić je dobrze. Przy złożonych agregatach może to być naprawdę duża przeszkoda w szybkim wprowadzaniu zmian.

Po drugie wprowadzanie kodu wspierającego mapowania powinno się odbywać na dalszym etapie. Pierwszeństwo ma model! Architektura powinna zapewniać możliwość pisania testów modelu bez podejmowania wyborów technologicznych.

Zapis agregatu przez snapshot

Zapis przez snapshot jest wariacją poprzedniego rozwiązania. Zapisowi podlega tu jednak nie sam agregat tylko wygenerowany przez niego Value Object. Snapshot jest zestawem tylko tych informacji, które są niezbędne do odtworzenia agregatu w dokładnie tym samym stanie. Jest więc on osobnym modelem danych, ale definiowanym w warstwie domeny.

Oddzielenie kodu wspierającego mapowanie

Cały kod wspierający mapowanie dotyczy więc teraz snapshotu, a nie agregatu. Wpływ na model zostaje wyeliminowany, a ewentualny „brzydki” kod wymuszony przez mechanizm do persystencji ma swoje wydzielone miejsce poza agregatem.

Dalej mamy to oczywiście dość silne związanie z warstwą dostępu do danych, ale jego wpływ na model jest silnie ograniczony. Podejście to dobrze wspiera też opóźnianie wyborów technologicznych i pisanie testów na sztucznym magazynie danych. Łatwiej jest przygotować taki magazyn dla snapshotu niż dla agregatu. Kod wymagany przez ORM może bez problemu zostać dodany później. Łatwiej jest również pisać testy sprawdzające poprawność zmian w stanie obiektów, gdyż jest on dostępny publicznie przez Snapshot.

Budowa modelu danych na podstawie eventów domenowych

Model danych można budować także po stronie warstwy dostępu do danych. Podejście to polega na generowaniu zdarzeń domenowych (obiektów opisujących każdą zmianę), które trafiają do repozytorium i tam inicjują aktualizację odpowiednich struktur danych. Na zakończenie żądania struktury te są zapisywane do bazy danych.

Tak jak w poprzednim przypadku mamy dwa osobne modele: model domenowy i model danych. Dzięki wykorzystaniu zdarzeń, które są jednym z mechanizmów odwracania kontroli, domena nie musi już jednak nic wiedzieć o persystencji.

Pełna separacja domeny i dostępu do danych

Daje to zupełną niezależność i brak jakichkolwiek mocnych powiązań między warstwą domeny i warstwą dostępu do danych. Ma to swoje zalety i z pewnością jest to bardzo „czyste” podejście. Kosztem jest konieczność utrzymywanie aktualizacji dodatkowego modelu na podstawie eventów domenowych. Jest to jednak koszt niewiele tylko większy niż koszt robienia snapshotów.

Czy to nie Event Sourcing?

Podejście to może przypominać Event Sourcing jednak bardzo istotnie się od niego różni. Tu cały czas mamy struktury danych odzwierciedlające aktualny stan agregatu. Stanu tego nie trzeba odtwarzać przy pomocy logiki nakładającej zmiany wprowadzane przez kolejne zdarzenia.

Podejście to może być jednak dobrym wstępem do Event Sourcingu pozwalającym odsunąć w czasie ostateczną decyzję. Same zdarzenia i mechanizm komunikacji z repozytorium już mamy. To co stanie się z tymi zdarzeniami jest jednak sprawą warstwy dostępu do danych. Można więc rozwijać i testować model używając tymczasowego mechanizmu udającego prawdziwą persystencję.

Event Sourcing

Agregat jako seria zdarzeń

Ostatnią techniką jest Event Sourcing. W tym podejściu model domenowy tak jak w poprzednim generuje zdarzenia i to na nich opiera się persystencja. Nie ma tu jednak dodatkowego modelu danych, persystowany jest bezpośrednio każdy event. Po zapisaniu informacja ta jest już niezmienna, gdyż jest odzwierciedleniem faktu biznesowego, który zaszedł w przeszłości, nie może więc być zmieniony.

Agregat odtwarzany jest nie na podstawie gotowej informacji o aktualnym stanie obiektów, tylko przez nakładanie kolejno wszystkich przeszłych zdarzeń. Separacja domeny i persystencji jest tu, tak jak w poprzednim podejściu, pełna. Posiadanie informacji o wszystkich zdarzeniach otwiera również wiele niedostępnych dotąd możliwości. Szersze omówienie tego podejście wykracza jednak poza tematykę tego artykułu.

Event Sourcing wymaga nowych kompetencji

Skorzystanie Event Sourcingu wymaga zmiany sposobu patrzenia na obiekty, co może nie być trywialne. Zupełnie innych problemów niż przy tradycyjnym podejściu możemy się spodziewać przy naprawie błędnie wprowadzonego stanu, modyfikacji eventów, debugowaniu, etc. To czy z niego skorzystać powinno opierać się bardzo mocno na kompetencjach zespołu. Istotne jest również to, czy biznes postrzega swoje działanie jako serię zdarzeń, czy nie. W bankowości jest to jedyny sensowny sposób patrzenia na biznes, w innych branżach nie musi to już być takie oczywiste.

Podsumowanie

Wybór podejście powinien być dokonany pragmatycznie. Każde z tych podejść może być dobre, lub złe w konkretnej sytuacji. Nadrzędnym celem powinno być zawsze szybkie stworzenie jak najlepszego modelu. Pod uwagę trzeba wziąć również inne aspekty takie jak kompetencje zespołu, czy wizja rozwoju systemu.

Więcej o technikach implementacji wszystkich tych opcji napiszę w kolejnych artykułach.


Marcin Markowski

Zdjęcie Marcin Markowski
Trener

Architekt, trener, zwolennik podejścia Software Craftsmanship i ścisłej współpracy z biznesem. Specjalizuje się w modelowaniu opartym o Domain Driven Design i projektowaniu architektury systemów.

Zaczynał od consultingu biznesowego, później przeszedł do IT. Pracował zarówno nad systemami „enterprise”, jak i tworzył od podstaw rozwiązania dla małych firm. Próbował wejść w świat startupów z własnym produktem. Ostatecznie został jednak w IT, gdzie działa jako konsultant i trener.