O debiucie w podcast Ostra Piła oraz o tym co sądzę o Onion Architecture
Cześć!
Dopiero styczeń, a mnie udało się zaliczyć nowe doświadczenie życiowe. Zadebiutowałem jako gość podkastu Ostra Piła!. Porozmawialiśmy z Jarkiem i Pawłem, o Architekturach Opartych na Zdarzeniach oraz Event Sourcing. Porozmawialiśmy to mało powiedziane - wyszło z tego ponad trzy i pół godziny materiału. Wstępnie miało być dwie, ale dobrze się gadało i postanowiliśmy zrobić dogrywkę. Podobno pobiliśmy rekord najdłuższego odcinka (a jest to już 69 wydanie).
https://ostrapila.pl/architektury-oparte-o-zdarzenia
Przesłuchałem całość przed wypuszczeniem i wydaje mi się, że wyszła z tego interesująca rozmowa. Jeśli lubisz takie luźne, ale też mięsne i szczere rozmowy to myślę, że powinno Ci się to spodobać. Skoro mi się udało samego siebie słuchać, to liczę, że Tobie się też uda. Chłopaki się ze mnie śmiali, że bardziej zrażam niż zachęcam, więc marketingu tam nie ma. Dobra okazja, żeby nie tylko poczytać, ale też mnie posłuchać.
Omówiliśmy m.in.:
- Czym się różni Event-Driven Architecture od Event Sourcing.
- Dlaczego warto używać Event-Driven i Event Sourcing w swoich projektach?
- Czy Kafka to narzędzie do Event Sourcing?
- Co to jest Marten, co to jest Event Store DB.
- Spójność zdarzeń, Eventual Consistency.
- Gwarancje dostarczalności.
- Kolejność zdarzeń.
- Jak wersjonować zdarzenia?
- Jak zacząć wdrażanie podejścia opartego na zdarzeniach w swoim projekcie?
- Kiedy Event-Driven, Event Sourcing się sprawdzi, a kiedy nie?
- Wady Architektur opartych na zdarzenia.
- Najczęstsze fuck-upy.
- Wydajność.
- Czy te systemy mogą współgrać z klasycznymi.
- Rabbit vs Kafka
Więc chyba wszystkie podstawowe tematy, plus dlaczego:
- System.Text.Json ssie,
- sprinty zerowe i refactoringowe to patologia,
- irytuje mnie gdy Architekt zakłada, że “programista powinien to wiedzieć”
Zachęcam do słuchania! Będę bardzo wdzięczny za udostępnienia feedback i komentarze co do treści!
Niedawno też wziąłem udział w dyskusji na temat tego jakie jest moje podejście do CQRS i Onion Architecture. Punktem wyjściowym było pytanie jak w niej umieścić obsługę Query.
Niespecjalnie do mnie przemawia Onion Architecture, Hexagonal nieco bardziej (ogólnie nie jestem fanem Roberta C. Martina, ale to na inny temat). Ale w skrócie - wg mnie jest przekomplikowana. Staram się używać jak najmniej warstw jak możliwe.
Ja ogólnie skupiam się na podziale logika biznesowa (“model domenowy”) i reszta. Do reszty kwalifikuję warstwę aplikacji (czyli kontrolery, handlery, etc. - wszystko co skleja domene z resztą), można tez to nazwać “anti-corruption layer” jeśli się chce. Dla mnie infrastruktura to po prostu kod, który fizycznie wykonuje jakieś operacje. Dużo bardziej do mnie przemawia podział tunelowy (“vertical slices”), czyli tak jak w CQRS - mamy do wykonania logikę biznesową, więc pobieramy encję (najlepiej po id), wykonujemy na niej logikę biznesową, zapisujemy zmodyfikowany stan.
Co do query to po prostu odbieramy żądanie, wykonujemy query w jakimś miejscu (np. query handlerze) i zwracamy to z powrotem. Przy takim założeniu logikę biznesową testujemy unit testami, a resztę tak naprawdę integracyjnie, bo i tak unit testy nam za dużo nie powiedzą bo dotyczą konkretnych technologii/bibliotek.
W jednym z moich poprzednim projekcie naliczyłem 7 warstw przy zapisie prostego rekordu do bazy (nie ja to wymyśliłem…). No i to jest zdecydowanie nie tak. My backendowcy lubujemy się w warstwach, interfejsach. Oczywiście jakieś abstrakcje są potrzebne, ale nie tyle ile zwykle się widuje. Np. po co tworzyć interfejs do czegoś co nigdy nie będzie podmienione i zawsze będzie tą samą klasą?
Należy pamiętać, że wprowadzając interfejs i wstrzykując go zwiększamy niepewność naszych testów, bo wtedy musimy przyjąć, że coś to robi. Lepiej wg mnie skupić się na podstawowych rozważaniach typu coupling vs cohesion, zamiast ślepo wprowadzać interfejs i czuć się dobrze, bo go wstrzyknęliśmy. Uważam, że jak się z tym przesadza to potem każdą warstwę trzeba testować jednostkowo, naklepać ten kod itd. Powoduje to stratę czasu, zwiększenia punktu zrozumienia architektury. Abstrakcje w tych przypadkach bym widział gdybyśmy np. chcieli mieć wstrzykiwaną strategię (np. generowanie numeru rezerwacji ,, który może to gdzieś robić np. sekwencją na bazie). Inny przykład to repozytorium mające metody GetById, Add, Delete, Update. Zwykle łatwiej takie coś zamockować czy przetestować jednostkowo niż rozbudowane interfejsy ORMów.
W przypadku read modeli to tak jak napisałem już poprzednio. Myślę, że takie repozytoria z wieloma metodami pochodza mocno z “tradycyjnego podejścia” i źle postawionych granic - czyli gdy potrzebujemy pobrać wiele rekordów, żeby zmodyfikować jeden. Dla mnie to jest Code Smell jeśli trzeba to mieć. Wg mnie wprowadzanie dodatkowej warstwy abstrakcji na query jest ryzykowne, bo szczególnie każde queryable ma swoje ograniczenia i powinniśmy wprost wiedzieć z czego korzystamy.
Ja widzę to tak: Model zewnętrzny (np. request z WebApi) powinien być osobny, bo jemu nie możemy ufać. To powinniśmy zmapować/zwalidować do właściwych typów domenowych (np. komenda/query). Jeżeli będziemy używać dobrego typowania (na które pozwala już nam C# z nullable reference types). To potem takim typom możemy zaufać, że one sa poprawne. Taki request po prostu wykonujemy na modelu biznesowym (oczywiście poprzedzając walidacją biznesową) i zapisujemy nowy stan. Nawet w trudniejszych biznesowych przypadkach wg mnie to tak działa. Kwestia “tylko” odpowiedniej kompozycji.
Tak naprawdę podstawowy podział dla mnie to na model logiczny (biznesowy) i podział techniczny. Reszta to tylko konwencja.
Z mojego doświadczenia im więcej dziwnych nazw, im więcej warstw i interfejsów tym architektura staje się trudniejsza do zrozumienia i droższa w rozwoju i utrzymaniu. Każda dodatkowa warstwa, każda dodatkowa abstrakcja zwiększa czas na zrozumienie, testowanie, wytwarzanie oprogramowania. Oczywiście nie chodzi mi, żeby z nich rezygnować, co raczej zachować zdrowy pragmatyzm. Kod powinien być jak najmniejszy, ale nie mniejszy 😉
Stąd pewnie też dużo ludzi uważa, że CQRS i Event Sourcing jest trudny i nadmiarowy, bo własnie tak dużo narośli dookoła niego się pojawiło. Gdzie te wzorce są same w sobie dosyć proste, praktyczne i konkretne (w każdym razie wg mnie).
Przykłady? Proszę bardzo:
- kontroller - https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/Tickets/Tickets.Api/Controllers/ReservationsController.cs#L49
- query z walidacją semantyczną - https://github.com/oskardudycz/EventSourcing.NetCore/blob/af92a94320cc64808a0a790594224c4b52311b96/Sample/Tickets/Tickets/Reservations/Queries/GetReservations.cs#L9
- query handler - https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/Tickets/Tickets/Reservations/ReservationQueryHandler.cs#L37
I tyle!
Polecam do obejrzenia też wystąpienie Kenta Becka w podobnym klimacie:
Co sądzisz?
Pozdrawiam Oskar
p.s. jak co tydzień zachęcam do lektury nowego wpisu na blogu, pociągnąłem temat mikroserwisów od nieco innej strony “Sociological Aspects of Microservices” - https://event-driven.io/en/how_to_cut_microservices/ p.s.2 Architecture Weekly mocno mięsiste w tym tygodniu- https://github.com/oskardudycz/ArchitectureWeekly#25th-january-2021. Cały zestaw linków o Open Telemetry w .NET oraz kontrowersyjnej zmianie licencjonowania ElasticSearch