Od wielu lat przyjętym standardem w świecie Javy jest podział backendu aplikacji webowych na 3 główne warstwy: 1) kontrolery (endpointy) przyjmujące żądania od interfejsu użytkownika, 2) warstwa logiki biznesowej zbudowana z encji i serwisów na nich operujących, 3) warstwa persystencji, która udostępnia odczyt i zapis owych encji w bazie danych lub warstwa z zewnętrznymi usługami. Encje, czyli obiekty domenowe reprezentujące rzeczowniki zebrane podczas analizy wymagań mają swoje pola, gettery i settery (tzw. anemiczny model domenowy), których pola i odzwierciedlają kolumny tabeli w bazie danych. Endpointy korzystają i zależą od warstwy logiki biznesowej, a logika biznesowa korzysta i zależy od warstwy persystencji. W skrócie: warstwa wyższa zależy od warstwy niższej i jest skazana na korzystanie z tego co ona umożliwia. O ile w przypadku obiektów, których logika sprowadza się do tworzenia, edycji, usuwania i wyszukiwania (CRUDy – Create, Read, Update, Delete) to w przypadku, gdy pojawiają się pewne reguły biznesowe taka architektura staje się mało wygodna z kilku powodów.
Po pierwsze w serwisach (warstwa logiki biznesowej) zaczyna pojawiać się coraz więcej ifów przez co coraz łatwiej pominąć w którejś z metod jakiś warunek. Po drugie testowanie wymaga co najmniej bazy danych w pamięci, co wydłuża proces testowania (jeżeli zdecydujemy się na z jakichś względów na użycie typu lub funkcji dostępnej tylko w konkretnej bazie danych to szybkie testowanie bez prawdziwej bazy danych staje się niemożliwe). Po drugie wymusza zaczęcie prac od przygotowania warstwy persystencji – musimy wybrać i skonfigurować narzędzie, z którego chcemy korzystać, ponieważ to od niego zależy jakie metody udostępnimy warstwie logiki biznesowej. Po trzecie, w przypadku korzystania z zewnętrznych usług (np. kolejka wiadomości, zewnętrzne usługi, biblioteka do autoryzacji) do warstwy logiki biznesowej przeciekają szczegóły tych warstw, a co za tym idzie, zmiany w warstwach niższych będą wymuszać zmiany w kodzie z logiką biznesową – co jest złamaniem zasady pojedynczej odpowiedzialności (Single Responsibility Principle), która mówi, że nie powinno być więcej niż jednego powodu do zmiany klasy.
Rozwiązaniem powyższych problemów byłoby odwrócenie zależności – to nie warstwa logika biznesowa powinna zależeć od warstw niższych, ale to warstwa niższa powinna dostarczać to co potrzebuje logika biznesowa.
Powyższy przykład jest jedną z wielu opcji struktury pakietów. W przypadku małych domen taka liczba pakietów może być przerostem formy nad treścią. Wtedy można ograniczyć się na przykład do pakietów “api”, “domain”, “infrastructure”. Z kolej przy rozbudowanych modelach możemy chcieć pogrupować porty w podpakietach “primary” i “secondary”. Można zacząć od wersji prostszej i, gdy uznamy, że kod się znacząco rozrósł, przeprowadzić refaktoryzację do większej liczby podpakietów. Najważniejsze jest ustalić w ramach całej organizacji, zespołu lub projektu wspólny standard.