Wzorce projektowe są rozwiązaniem problemów, które napotykali programiści przez kilkadziesiąt lat istnienia branży IT. Warto się z nimi zapoznać, aby mieć wspólną podstawę do rozmów z kolegami po fachu. Pierwszym krokiem wtajemniczenia do ich poznania według modelu braci Dreyfus jest po prostu nauczenie się wybranych wzorców na pamięć. Dopiero kolejnym etapem jest ich świadome wykorzystywanie w kodzie. Dzisiaj sprawdzimy w jaki sposób możemy to zrobić z wzorcem fasady.
Co było przyczyną do powstania fasady?
Okazuje się, że genezą powstania fasady była chęć oddzielenia swojego kodu od zewnętrznych zależności. Rozważmy taką sytuację, gdzie przychodzi wymaganie, żeby zmienić zewnętrznego dostawcę do generowania faktur. Ten po prostu był nieresponsywny oraz chciał zbyt wysoką stawkę za swoje usługi w porównaniu do dostarczanej jakości. Jednak okazuje się, że nie jest to takie proste jakby mogło się wydawać… Kod biblioteki jaką nam dostarczył dostawca rozlał się po całej aplikacji, że nawet część odpowiedzialna za zarządzanie magazynem o niej wie. Mamy wysokie sprzężenie (coupling) zewnętrznego rozwiązania ze swoją aplikacją.
Właśnie takie problemy zmusiły programistów do zastanowienia się nad swoim losem i wyciągnięcia z nich nauczki. Wpadli na pomysł, aby stworzyć twór, który były murem oddzielającym całe zło świata zewnętrznego od swojej bezpiecznej przystani. Wystawiając API poprzez metodę issueInvoice
w fasadzie nasz kod nie dość, że ma do użycia tylko jedno, proste wywołanie to jeszcze nie przesiąknie całą logiką od zewnętrznego dostawcy. Jeśli więc zajdzie potrzeba wymiany rozwiązania do wystawiania faktur to będziemy dotykać tylko tego jednego miejsca. Czyż to nie jest piękne?
Grafika wyjaśniająca działanie fasady
Poniżej chciałbym przedstawić Ci obrazek, który może lepiej przedstawi ideę stojącą za fasadą. Dostęp do naszego rozwiązania otrzymujemy poprzez klasę będącą właśnie fasadą. Jest ona publiczna oraz ma publiczne API reprezentowane słowem kluczowym public
. Broni ona dostępu do wewnętrznej implementacji reprezentowanej przez szare kwadraty. Te z kolei powinny się cechować dostępem pakietowym, czyli w przypadku Javy jest to domyślne zachowanie. W sumie można pokusić się o stwierdzenie, że powinno się dać wymienić te wszystkie małe komponenty bez najmniejszego problemu, ponieważ nikt o nich nie wie. Są one po prostu tylko szczegółem implementacyjnym naszej funkcjonalności. Zostały opakowane w zgrabny moduł, który można dostarczyć w dowolne miejsce jako całość bez większej potrzeby konfiguracji.
Ukrycie szczegółów implementacyjnych za fasadą
Oczywiście wewnątrz mogą istnieć zależności do bazy danych czy też zewnętrznego API. Jednak najlepszą praktyką będzie również odcięcie się od ich jakichkolwiek konkretnych implementacji poprzez np. interfejs. Zobaczmy jak by to mogło wyglądać w praktyce.
Fasada w praktyce
Zostańmy przy tematyce generowania faktur. Stwórzmy, więc moduł odpowiedzialny właśnie za to zadanie. Punktem wejścia do niego będzie, więc klasa InvoiceFacade
. Natomiast reszta klas jak InvoicePublisher
czy InvoiceNumberGenerator
to już wewnętrzna implementacja, która odpowiada na pytanie “jak” coś jest zrobione.
W celu komunikacji z naszym modułem powstały pakiety takie jak dto
oraz exception
. W nich trzymane są struktury danych oraz wyjątki. Klasy w nich zawarte będę miały publiczne modyfikatory dostępu. Natomiast jeśli dodatkowo oprzemy naszą aplikację o kontener zależności taki jak np. Spring to w pakiecie controller
będziemy mogli trzymać wystawione REST API do naszego modułu. Klasa kontrolera będzie mogła mieć dostęp pakietowy, gdyż framework na to pozwala. To podejście jest zaczerpnięte z świetnej prezentacji Jakuba Nabrdalika.
Zastosowanie wzorca fasady w praktyce
W klasie InvoiceConfiguration
tworzony jest nasz bean, którego cykl życia będzie właśnie podlegał Springowi. Uwagę natomiast trzeba zwrócić na interfejsy. Repozytorium jest z dostępem pakietowym, ponieważ tylko pakiet invoice
powinien mieć dostęp do danych o fakturach.
Natomiast przy InvoiceExternalApi
oraz InvoiceMailSender
chcemy odwrócić zależność. Nie chcemy, żeby to moduł fakturowania nimi zarządzał. Tutaj zależy nam, aby nic on nie widział o konkretnych implementacjach tych rozwiązań. Z tego powodu muszą one być publiczne, aby ktoś mógł je zaimplementować i wstrzyknąć w to miejsce. Z drugiej strony można by też oprzeć architekturę o zdarzenia przez co nie byłoby konieczności upubliczniania tych interfejsów.
Taki moduł mógłby w swoich bebechach mieć zależność do zewnętrznej biblioteki dostawcy od fakturowań. Gdyby zaszła potrzeba jego wymiany ograniczylibyśmy się tylko do tego jednego modułu. Z ciekowości zajrzyjmy jeszcze do klasy konfiguracyjnej InvoiceConfiguration
.
Konfiguracja fasady
Produktem metody wytwórczej jest InvoiceFacade
, która potrzebuje tylko trzech zależności: InvoiceRepository
, InvoiceMailSender
oraz InvoiceExternalApi
. Czyli tak jak Kuba Nabrdalik wspominał w swojej prezentacji, są to operacje I/O. O reszcie Spring nawet nie musi wiedzieć. Nie trzeba bawić się w adnotacje @Component
czy @Service
. Wszystko mamy zdefiniowane w tym jednym miejscu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
class InvoiceConfiguration {
@Bean
InvoiceFacade invoiceFacade(
InvoiceExternalApi invoiceExternalApi,
InvoiceMailSender invoiceMailSender,
InvoiceRepository invoiceRepository
) {
InvoiceNumberGenerator invoiceNumberGenerator = new InvoiceNumberGenerator();
InvoiceFactory invoiceFactory = new InvoiceFactory(invoiceNumberGenerator);
InvoicePublisher invoicePublisher = new InvoicePublisher(invoiceMailSender);
//... rest of elements
return new InvoiceFacade(invoiceFactory, invoicePublisher, ...);
}
}
W ten sposób uzyskujemy piękny komponent, który jest czarną skrzykną. Możemy go testować bez konieczności zastanawiania się co ma w środku. Jeśli wrzucimy do niego komendę to spodziewamy się otrzymać np. informację o wybranej fakturze. Skupiamy się tylko i wyłącznie na obserwowalnych zachowaniach. Dodatkowo całość możemy przetestować w sposób jednostkowy bez żadnych mocków co znacząco przyspiesza uruchamianie takich testów. Tak jak w klasycznej szkole, o której mówił Kuba Pilomon w swojej prezentacji na temat testów. Zewnętrzne zależności możemy utworzyć jako klasy w pamięci i wrzucić do produkcyjnej konfiguracji beana znajdującej się w klasie InvoiceConfiguration
.
Podsumowanie
Wzorzec fasady to naprawdę przydatne narzędzie. Ma on wiele zalet w postaci prostszego stosowania, testowania oraz trzymania bałaganu tylko w jednym miejscu, za zamkniętymi murami. Oczywiście i tutaj można popełnić sporo błędów przy projektowaniu i zamknąć zbyt dużo funkcjonalności za taką fasadą. Przez to pakiet będzie puchł za bardzo i w końcu eksploduje w postaci wyrzucania klas po za jego granice oczywiście z dostępem publicznym. Ta droga poprowadzi nas tylko do frustracji i kodu spaghetti.
A mówią, że spaghetti jest takie dobre… :)
Smacznego!