Pisanie oprogramowania dla biznesu może wywołać różne emocje. Od znudzenia po uber zaangażowanie. Szczególnie wówczas, gdy dziedzina problemowa jest ciekawa i stanowi wyzwanie. Problemy biznesowe się powielają. Kolejna integracja, kolejny CRM, kolejny magazyn i tak w koło Macieju. Jest taka książka, która porusza najpowszechniejsze case’y biznesowe, z którymi programiści na co dzień borykają się w swojej pracy. Podlinkuję tytuł. Zawiera ona eleganckie rozwiązania architektoniczne dla takich przypadków. Powtórne wynalezienie koła będzie zbędne.
Refactoring
Pisałeś kiedyś aplikację generującą raport? Kiedykolwiek, jakikolwiek? Ja pisałem. Chyba większość zderzyła się z implementacją generowania jakiegokolwiek raportu dla biznesu. Biznes może wygenerować raport i zapisać go w kilku dostępnych formatach. Yaaaawn. Nawet przy takiej względnie prostej aplikacji powinniśmy się postarać o dobre praktyki. Zerknij na ten kod na githubie. Poniżej fragment.
To by pewnie działało. W zasadzie nie jest to najgorszy kod jaki można sobie wyobrazić. A mimo to i tak czujesz przeszywający smutek, bo przeczytaliśmy przecież tonę artykułów, gdzie napisane jest, że ify są złe. Jak to przetestować i jak bez kłopotu utrzymywać i rozwijać. Nie jest łatwo. Można lepiej.
Po fragmencie kodu widzimy, że zależnie od tego jaki raport chce wygenerować zostanie wywołana odpowiednia logika. Wyobraź sobie, że gdzieś jest jeszcze druga metoda oprócz generate, która na podstawie typu raportu wykonuje jakąś inną akcję. Musielibyśmy skopiować znowu te wszystkie biedne ify i wpiąć tam wywołanie metody wykonującej akcję B. Jeśli doszedłby nowy sposób generowania raportu, to musielibyśmy pamiętać, żeby dodać nowego ifa w dwa miejsca. Na odległość coś tutaj śmierdzi i prosi się o mały refactoring.
Z pomocą mógłby przyjść polimorfizm. Jak wiemy, kod wspierany polimorfizmem jest łatwy w testowaniu, utrzymaniu i rozwoju. Dookoła wszyscy tak mówią.
Jak to mogłoby wyglądać (całość przypominam na githubie).
Serwis ReportService pozwala na wstrzyknięcie dowolnego sposobu generowania raportu. Każda z tych klas musi jedynie implementować interfejs ReportGeneration wyglądający tak:
Struktura naszego pakietu rozrosła się, ale biorąc pod uwagę koszt utrzymania, testowania i rozszerzania mizernie napisanego programu, jest to koszt niewielki.
Każdy sposób generowania raportu stanowi osobną klasę. Dzięki temu, pozbywamy się całej ifologii. Dodanie nowego sposobu generowania raportu ogranicza się do dodania nowej klasy. Nie musimy modyfikować żadnych plików zawierających logikę biznesową. A przecież głównie o to nam chodzi. Chcemy, aby nasza aplikacja była łatwo rozszerzalna.
Więcej możesz przeczytać tu.
Możesz też sięgnąć po https://martinfowler.com/articles/refactoring-2nd-ed.html
“Jest taka książka, która porusza najpowszechniejsze case’y biznesowe, z którymi programiści na co dzień borykają się w swojej pracy. Podlinkuję tytuł. ”
Jakbyś mógł, to poprosze
Sprawdź Patterns of Enterprise Application Architecture. Przejrzę w wolnej chwili książki, bo nie wiem czy o jeszcze inną mi przypadkiem nie chodziło.
Jak rozwiązujesz logikę wyboru który z algorytmów ma być wykorzystany do generowania raportu? Mapa? Fajnie by było jakby taki rozszerzony przykład pojawił się tutaj / na githubie, żeby do końca pokazać jak takie rzeczy się realizuje.
Cześć Kuba. W przypadku generowania raportów, wyobrażałem to sobie tak, że użytkownik w swojej aplikacji może wybrać z listy rozwijanej format raportu. Następnie klika przycisk ‘generuj’ i dane do raportu oraz owa informacja o typie raportu trafiają np.: do restowego kontrolera. Tam, fabryka na podstawie typu raportu zwraca daną implementację i następuje generowanie. Ta ifologia będzie wówczas w tej fabryce. Gdzieś być musi. Grunt, że nie ma jej w naszej logice biznesowej. Zwracanie odpowiedniej implementacji nie powinno być odpowiedzialnością logiki biznesowej. Dlatego fabryka będzie zdecydowanie lepszym rozwiązaniem. Więc jest to sposób nr 1. Gdybym użył Springa, to mógłbym wstrzyknąć odpowiednią implementację generowania raportu np.: na podstawie jakiegoś parametru z konfiguracji. Mogą do tego posłużyć annotacje @ConditionalOnProperty/@ConditionalOnExpression (np.: jeśli instancja aplikacji jest zainstalowana u klienta X to generuj raport XML – bo tak sobie klient X zażyczył lub np.: zapłacił za taką wtyczkę, a dla wszystkich innych generuj PDFa). Wrzuciłem na githuba przykład z fabryką. Jakbyś miał jeszcze jakieś pytania to pisz.