“To może się kiedyś przydać w innym module” jest jednocześnie dobrą obserwacją i niebezpiecznym uzasadnieniem architektonicznym.
Dobrą, ponieważ część mechanizmów rzeczywiście należy do wspólnej platformy. Niebezpiecznym, ponieważ łatwo zbudować framework dla modułów, które jeszcze nie istnieją i których potrzeb biznesowych nie znamy.
Platforma i domena zmieniają się z innych powodów
Platforma odpowiada za stabilne mechanizmy używane przez wiele obszarów:
- tożsamość i uwierzytelnianie,
- organizacje, członkostwa i role,
- autoryzację i impersonację,
- pliki,
- audit log,
- kontekst requestów i jobów,
- wspólne zasady prywatności.
Moduł domenowy zachowuje własne znaczenie:
- modele i stany,
- komendy przypadków użycia,
- polityki domenowe,
- zdarzenia,
- wymagane dokumenty,
- reguły decyzji i zamknięcia procesu.
Nowy moduł korzysta z identity, organizacji lub audytu bez kopiowania infrastruktury. Nie musi jednak dopasowywać się do “uniwersalnego workflow engine”, który powstał przed poznaniem jego wymagań.
DRY nie oznacza podobnego kodu
Dwa fragmenty kodu nie są tą samą abstrakcją tylko dlatego, że mają podobne instrukcje warunkowe. Jeżeli reprezentują różne decyzje biznesowe i mogą zmienić się z innych powodów, ich połączenie tworzy zależność zamiast usuwać duplikację.
Współdzielimy mechanizm, gdy jego kontrakt jest stabilny:
- rejestrowanie zdarzenia audytowego,
- sprawdzanie członkostwa,
- propagowanie correlation ID,
- przechowywanie pliku,
- uruchamianie idempotentnej komendy.
Nie współdzielimy automatycznie znaczenia: kto zatwierdza decyzję, kiedy proces jest gotowy, które dokumenty są obowiązkowe ani co oznacza zakończenie w konkretnej domenie.
Pytaj klasy o ich odpowiedzialność
Pomaga proste pytanie: czy ta klasa opisuje mechanizm platformowy, czy regułę konkretnego procesu?
Jeżeli odpowiedź brzmi “jedno i drugie”, prawdopodobnie ma zbyt wiele powodów do zmiany. Warto wtedy pozostawić orkiestrację w komendzie domenowej, a techniczny mechanizm schować za małym kontraktem platformy.
Domain Command
├─ Access Context
├─ File Storage
├─ Audit Recorder
└─ Domain Models
Domena zależy od prostych, stabilnych usług. Platforma nie musi znać znaczenia decyzji domenowej.
SOLID bez ceremonii
SOLID nie polega na maksymalnej liczbie interfejsów. Klasa powinna mieć czytelny powód do zmiany, a zależności powinny wskazywać od zmiennej domeny do stabilnych kontraktów.
W Rails często wystarczają zwykłe obiekty Ruby:
- command do transakcyjnego przypadku użycia,
- query do bezpiecznego odczytu,
- policy do autoryzacji,
- value object do kontekstu,
- presenter albo component do UI.
Abstrakcja powinna pojawić się wtedy, gdy drugi rzeczywisty przypadek ujawni wspólny kontrakt. Nie wtedy, gdy próbujemy przewidzieć hipotetyczny piąty moduł.
Najprostsza droga do rozszerzalności
Projektuj pierwszy moduł dobrze. Obserwuj, co powtarza się znaczeniowo. Wyciągaj mechanizm dopiero wtedy, gdy umiesz nazwać jego odpowiedzialność, wejście, wynik oraz sposób awarii.
To pozwala rozwijać modularny monolit bez dwóch skrajności: kopiowania wszystkiego między modułami i budowania wewnętrznego frameworka, którego nikt nie potrafi zmienić.
Projektuj platformę do ponownego użycia, ale nie projektuj domeny, której jeszcze nie znasz.
Zobacz, jak łączymy takie granice z technologią, procesem delivery i dedykowanymi systemami. Jeśli potrzebujesz uporządkować modularność istniejącego produktu, zacznij rozmowę.

