Przejdź do treści

✔️ Spring Framework – Jak Bronić Się Przed Oddaniem Kontroli Frameworkowi? ⛔️⛔️

Spring Framework powstał jako alternatywa dla programowania aplikacji z użyciem Javy EE w 2002 roku. Dziś jest kompleksowym rozwiązaniem dla wielu innych problemów a jego znajomość wśród developerów Javy jest już standardem.


Cześć 🙂

Spring sprawia, że programowanie w Javie jest szybsze, łatwiejsze i bezpieczniejsze dla każdego. Spring skupia się na szybkości, prostocie i produktywności, dzięki czemu stał się najpopularniejszym na świecie frameworkiem Java.
spring.io

Cytat powyżej doskonale opisuje znaczenie Springa w dzisiejszym świecie (2020). Spring jest wszędzie – niezależnie od tego, czy chodzi o streaming telewizji, samochody z dostępem do internetu, zakupy online, czy też niezliczone inne innowacyjne rozwiązania. Dzięki swojej elastyczności, ilości projektów jakie rozwija (w sposób zadowalający programistów na całym świecie) oraz społeczeństwa (community) jakie zostało wokół niego stworzone, z pewnością można stwierdzić, że Spring Framework jest częścią obecnego świata.

Należy jednak pamiętać, że nadal jest to tylko framework a rynek bywa nieprzewidywalny. Co w przyadku, kiedy okaże się, że Spring jest już przeżytkiem? Czy będziesz musiał przepisać całą aplikację od zera tylko dlatego, że pozwoliłeś się mu uwięzić?

W dzisiejszym artykule:


Elastyczność a kontrola

Spring Boot, czyli jeden z podprojektów springa, który drastycznie poprawia produktywność. Pozwala na postawinie aplikacji od zera za pomocą dosłownie kilku kliknięć przy pomocy Spring Initializr. W dobie, kiedy duży nacisk jako IT kładziemy na architekturę, gdzie każdy moduł ma konkretną odpowiedzialność (mikroserwisy), Spring Boot jest świetnym wyborem bo dostarcza nam przygotowany projekt, który musimy tylko odpowiednio zakodować.

Skupiam się szczególnie na projekcie Spring Boot’owym, ale sprawa zabetonowania kodu frameworkiem równomiernie dotyczy każdego innego projektu. Generalnie chodzi o komponenty, gdzie w dużej mierze polegamy na sercu Springa – IoC, AOP. Zyskujemy dzięki temu dużą elastyczność bo możemy szybko dostarczać funkcjonalności, które chce biznes, ale jednocześnie płacimy za to tym, że polegamy na mechanizmach frameworka.

Czy jest z tym jakiś problem? Dopóki wszystko działa to nie. Problem pojawia się gdy coś przestaje działać. Wtedy okazuje się, że z 10-osobowego zespołu, jedna… może dwie osoby, mniej lub więcej wiedzą co dzieje się pod spodem (dobrze jeżeli chociaż tyle osób się znajdzie) i umieją rozwiązać problem. To nie znaczy, że mamy wywalić wszystkie frameworki z naszego kodu i napisać je samemu. Efekt końcowy będzie taki sam. Osoby, które napiszą daną rzecz za jakiś czas są nieobecne – inny projekt / zmiana pracy / emerytura.


Gdzie leży granica?

Niezaprzeczalnym faktem jest, że w znacznej większości przyapadków nie będziesz przepisywać funkcjonalności, które dostarczają Ci zewnętrzni dostawcy (biblioteki / frameworki). Dobrze byłoby wiedzieć co dzieje się pod spodem danego komponentu, jakie są kompromisy jego użycią – co zyskujemy a co tracimy. Niestety, znajomość, co dzieje się pod spodem, często nie jest adekwatna. Żeby nie teoryzować już za wiele i przejść do konkretów, to moim zdaniem granice wyznacza czas.

Załóżmy, że na projekcie używasz Spring Boota i działasz w branży księgowości. Dostałeś zadanie aby dodać nową funkcjonalność obsługi subskrypcji (użytkownicy co miesiąc płacą za usługę księgowości). Dla uproszczenia przyjmimy, że taka subskrypcja może być utworzona (użytkownik dołącza do planu księgowości), wyszukiwana (np. przez adminów systemu), przedłużana oraz może nastąpić rezygnacja przez użytkownika.

Na pierwszy rzut oka, typowy CRUD, ale tak nie jest. Nie będziemy wchodzić w domenę bo to nie jest temat tego artykułu, ale pozwól, że przedstawię Ci kilka argumentów utrudniających domenę:

  • co z użytkownikami, którzy są stałymi klientami (zniżki / rabaty)
  • użytkownicy, którzy wykupuja plan na kilka lat z góry (zniżki / rabaty)
  • różne plany subskrypcji (np. najdroższy plan posiada support dostępny 24/7)
  • subskrypcje w wersji trial (14/30 dni)

Znalazłoby się jeszcze kilka(naście) innych utrudnień, ale nie do tego zmierzamy. Jak byś to zaczął opędzlowywać w springu? @RestController, @Service, @Component nad każdą z klas, później dla metod, w których może wystąpić rollback @Transactional i jazda? Po miesiącu kodowania okazuje się, że Twój kod jest i działa. Tylko problem, że jest on w 100% zależny od Springa i jego mechanizmów.

Czy jest złoty środek, który pomoże w walce o niezależność kodu?


Technika obrony przed oddaniem władzy w ręce Springa

Skoro znamy już problem i wiemy, że Spring Framework daje nam dużo benefitów ale jednocześnie częściowo uniezależnia nas od siebie jako dostawcy, to jak możemy się tego ustrzec? Mam zarówno dobrą jak i złą wiadomość. Zacznijmy od złej – nie da się tego ustrzec. Dobra natomaist jest taka, że możemy to załagodzić.

Dlaczego nie możemy się ustrzec? Używając Springa w naszych aplikacjach zapewne będziemy chcieli skorzystać z jego głównych zalet – kontenera IoC czy też mechanizmu AOP (Aspect Oriented Programming). Mechanizmy wokół kontenera IoC są odpowiedzialne za inicjalizację, konfigurowanie i składanie obiektów (beanów), a sam kontener zarządza ich cyklem życia co znacznie ułatwia programowanie. Natomiast, jeżeli chodzi o AOP – jest to mechanizm wykorzystywany chociażby przy transakcjach, logowaniu, obsłudze wyjątków czy też kwestiach bezpieczeństwa (security).

To jak załagodzić standardowe flow?

Do tego celu przyda nam się:

  • umiejętność tworzenia konfiguracji i beanów samodzielnie,
  • wiedza jak działa wzorzec projektowy fasada.

I tyle. Te dwie rzeczy pomogą nam częściowo uwolnić się od zależności frameworka. Lecimy z przykładem wcześniej wspomnianych subskrypcji.

Na samym początku trzeba zdefiniować fasadę, która przykrywa wszystkie istotne akcje biznesowe (utworzenie, wyszukanie, przedłużenie, rezygnację):

public class CompanySubscriptionFacade {

	private final CreateNewCompanySubscriptionService createNewCompanySubscriptionService;
	private final UnsubscriptionService unsubscribeFromSubscriptionService;
	private final CompanySubscriptionRepository companySubscriptionRepository;

	CompanySubscriptionFacade(CreateNewCompanySubscriptionService createNewCompanySubscriptionService,
							  UnsubscriptionService unsubscribeFromSubscriptionService,
							  CompanySubscriptionRepository companySubscriptionRepository) {
		this.createNewCompanySubscriptionService = createNewCompanySubscriptionService;
		this.unsubscribeFromSubscriptionService = unsubscribeFromSubscriptionService;
		this.companySubscriptionRepository = companySubscriptionRepository;
	}

	public Result createCompanySubscription(UUID subscriberId) {
		return createNewCompanySubscriptionService.create(subscriberId);
	}

	public CompanySubscription update(CompanySubscription sub) {
		return companySubscriptionRepository.save(sub);
	}

	public Result unsubscribe(SubscriberId subscriber, SubscriptionId subscription) {
		return unsubscribeFromSubscriptionService.unsub(subscriber, subscription);
	}

	public Optional<CompanySubscription> findSubscription(SubscriptionId subscriberId) {
		return companySubscriptionRepository.findBy(subscriberId);
	}
}

Jest to czysta klasa Javowa, podobnie zresztą jak CreateNewCompanySubscriptionService oraz UnsubscriptionService (z perspektywy kodu Javowego przed konfiguracją, która za chwilę nastąpi). Teraz czas na samodzielną konfigurację:

@Configuration
public class CompanySubscriptionConfiguration {

	/**
	 * wykorzystywane w testach
	 */
	public CompanySubscriptionFacade companySubscriptionFacade() {
		InMemoryCompanySubscriptionRepository companySubscriptionRepository = new InMemoryCompanySubscriptionRepository();

		return companySubscriptionFacade(
				createNewCompanySubscriptionService(companySubscriptionRepository),
				enrollToCompanySubscription(companySubscriptionRepository, new NoOpDomainEventPublisher()),
				companySubscriptionRepository);
	}

	@Bean
	CompanySubscriptionFacade companySubscriptionFacade(CreateNewCompanySubscriptionService createNewCompanySubscriptionService,
														UnsubscriptionService unsubscriptionService,
														CompanySubscriptionRepository companySubscriptionRepository) {
		return new CompanySubscriptionFacade(createNewCompanySubscriptionService, unsubscriptionService, companySubscriptionRepository);
	}

	@Bean
	CompanySubscriptionRepository companySubscriptionRepository() {
		return new InMemoryCompanySubscriptionRepository();
	}

	@Bean
	UnsubscriptionService unsubscriptionService(CompanySubscriptionRepository companySubscriptionRepository,
													  DomainEventPublisher domainEventPublisher) {
		return new UnsubscriptionService(companySubscriptionRepository, domainEventPublisher);
	}

	@Bean
	CreateNewCompanySubscriptionService createNewCompanySubscriptionService(CompanySubscriptionRepository companySubscriptionRepository) {
		return new CreateNewCompanySubscriptionService(companySubscriptionRepository);
	}
}

Klasa konfiguracyjna natomiast jest właśnie tą klasą, która odpowiada za utworzenie beanów. Sama posiada adnotację @Configuration oraz jest odpowiedzialna za prawidłowe powiązanie zależności.

Jeżeli chcesz dodać wsparcie dla transakcji / aspektów, które wykorzystują mechanizmy Springa to nie ma z tym najmniejszego problemu. Wystarczy że dodasz nowy @Aspect dla nowego aspektu, a w przypadku transakcji @Transactional na odpowiednim beanie lub jednej z metod. Posiadasz nadal pełne wsparcie mechanizmów Springa a jednocześnie nie betonujesz kodu na wymianę na inny framework. Będzie się to wiązało z przepisaniem konfiguracji (i oczywiście wsparcia dla ewentualnie napisanych aspektów / transakcji przez owy framework).


Podsumowanie

Koncept, który tu przedstawiłem jest poniekąd inspirowany podejściem jakie może być spotkane w architekturze hexagonalnej. Jeżeli chciałbyś dowiedzieć się więcej na ten temat to w źródłach zostawiam link do świetnej prezentacji Jakuba Nabrdalika z warszawskiego JUGA oraz dodatkowo takie podejście jest przedstawione w bardzo dobrym kursie DNA (tylko drogim).

Co do samego podejścia. Plusy? Sami kontrolujemy tworzenie beanów, więc doskonale wiemy, co jest tworzone i jakie są zależności. Przy klasach z większą ilością zależności szybciej dostrzegamy problemy z kodem bo sami musimy zarządzać zależnościami. Minusem natomiast jest to, że musimy wykonać więcej pracy. Jeżeli wszystkie klasy w naszym projekcie są bean’ami (najczęściej występuje to przy wykorzystaniu architektury warstwowej), to prawdopodobnie lepiej już oddać władze w ręce Springa i niech się dzieje co się ma dziać.

Źródła:

0 0 votes
Article Rating
Subscribe
Powiadom o
guest
0 komentarzy
najnowszy
najstarszy oceniany
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x