kwi 22, 2022

Greenfield efektywny kosztowo

Bytespack

Projektując architekturę, bardzo często zastanawiamy się, w jaki sposób osiągnąć aplikację odporną na zmiany. W środowisku startupowym rynek i wymagania zmieniają się bardzo dynamicznie, co niesie za sobą drastyczne zmiany w kodzie. Jak obejść się bez sformułowania „To teraz musimy wszystko przepisać” we wczesnych fazach produktu, który jeszcze nie trafił na rynek?

CQRS - rozdzielenie odpowiedzialności zapisu i odczytu

Model ten, choć z pozoru wydaje się droższy niż klasyczny CRUD z jednym modelem, pozwala na bardzo łatwe modyfikacje encji zapisu lub odczytu, co umożliwia dużą elastyczność. Mimo iż implementacja CQRS jest bardziej kosztowna niż CRUDa, inwestycja zwraca się w długim terminie.

DRY - wczesna replikacja nad wczesną abstrakcją

Stawiając nowe projekty, często dochodzimy do miejsca, w którym struktury w kodzie przestają odpowiadać na nasze potrzeby. Pojawia się konieczność refaktoryzacji, która potrafi trwać miesiącami. Łatwo wówczas o przypadkowe  złamanie zasady Liskov Substitution Principle. Zbyt wczesna definicja abstrakcji lub wczesne wprowadzanie domenowych typów generycznych może spowodować, że zachowanie kilku encji o pozornie tej samej abstrakcji będzie się logicznie rozgałęziać w przeciwnych kierunkach. W obliczu uodpornienia się na te zmiany, na wczesnych etapach projektu warto dopuścić duplikację kodu. Zdecydowanie mniejszym kosztem będzie jej usunięcie duplikacji kodu niż definiowanie modelu na nowo.

Kompozycja nad dziedziczenie

Dziedziczenie ma dwie podstawowe wady, które dają się we znaki w trakcie rozwijania nowych aplikacji. Zależności nie da się zmieniać w runtime aplikacji i każda nowo dodana zmiana powoduje problemy z zarządzaniem modelem, co we fluktuujących modelach biznesowych ostatecznie przeradza się w dependency hell. Z pomocą przychodzą delegacja i kompozycja. Małe, atomowe moduły z dedykowaną odpowiedzialnością pozwalają nie tylko na łatwiejsze testowanie, ale też na znaczną elastyczność w przypadku dużych zmian.

Używanie kompozycji powoduje, że finałowy model może wykrystalizować się bardzo późno, bez jakichkolwiek złych skutków dla aplikacji, tym samym znacznie obniżając koszty modyfikacji nieprzynoszących nowych funkcjonalności.

Open-closed principle

Jak radzić sobie ze zmianą, jeśli kod ma być zamknięty na modyfikacje? Jeśli posiadamy przetestowane fragmenty aplikacji, to nie powinniśmy modyfikować takiego fragmentu. Zamiast tego lepiej napisać testy, stworzyć nowy fragment, użyć go tam, gdzie jest potrzebny, a następnie sprawdzić, czy stary jest jeszcze używany. Pozwoli to na szybki development oraz zmniejszenie wpływu zmian na obszarze całej aplikacji, co przekłada się korzystnie na koszt developmentu oraz kontrolę nad projektem.

Od atomowych operacji do atomowych feature’ów

Zdecydowanie lepiej przejść duży most na dwa kroki niż zdecydować, że jeden krok będzie miał rozmiar mostu. Każda nowa akcja dostępna w aplikacji powinna być niepodzielna, nawet jeśli proces jest skomplikowany. Obsługa zmian w wymaganiach staje się dzięki temu jasna i przejrzysta. Posiadając atomowe funkcje w programie możemy łatwo przewidzieć scope zmiany, co zapobiega powstawaniu dużych refactorów.

Czy przestrzeganie tych zasad pomoże Ci uzyskać brak refactorów? Zdecydowanie nie. Pomoże Ci jednak projektować kod gotowy na bardzo duże zmiany relatywnie niewielkim kosztem, co przekłada się na wysoki komfort pracy i codehealth.