Kim jest senior developer?

Ciekawe pytanie, prawda? Niby wszyscy potrafią na nie odpowiedzieć. Senior developer to 3 lata doświadczenia komercyjnego i dobra znajomość springa. Taki obraz wyłania się z ogłoszeń o pracę. Jednak nie o to chodzi.

Senior developer/starszy programista powinien spełniać pewne warunki w trzech obszarach.

Wiedza i doświadczenie

Najbardziej oczywisty obszar obejmuje tak zwane umiejętności twarde. O ile wiedza techniczna jest cechą, która powinna charakteryzować każdego programistę, to starszy programista posiada też wiedzę z innych obszarów. Po pierwsze ma wiedzę ogólną na temat branży. Nie tylko wie jakich narzędzi należy używać, ale też wie, jakie są obecne trendy, co przyniesie nam najbliższa przyszłość czy też gdzie szukać rozwiązań. Wiedza to też znajomość narzędzi, oprogramowania, sprzętu w zakresie pozwalającym na przykładowo wyceny.
Po drugie wiedza domenowa, co wynika z doświadczenia. Ta wiedza pozwala na rozmowę z biznesem w znacznie elastyczniejszy sposób. Pozwala też w pewnym zakresie pozbyć się translacji dokonywanej przez analityków, a w przypadku wykorzystania narzędzi w rodzaju event stormingu na zadawanie dodatkowych pytań.
Wiedza połączona z doświadczeniem pozwoli na szybsze tworzenie oprogramowania, ponieważ w takim układzie starszy programista nie traci czasu na dopytywanie o podstawy.
Wiedza i doświadczenie pozwalają też na świadome łamanie reguł. Co do zasady osoba, która zaczyna pracę jako programista, ma ogólne pojęcie o zasadach SOLID, wzorcach projektowych czy metodykach w rodzaju TDD. Jednak nie zawsze jest to wystarczające. Umiejętność świadomego łamania tych reguł oznacza, że potrafimy uzasadnić, dlaczego je złamaliśmy oraz w jaki sposób można poprawić kod, gdy przyczyna stanie się nieaktualna. Najlepszym przykładem jest odpuszczenie części testów jednostkowych w celu wyrobienia się w czasie z projektem. Po udanym wdrożeniu możemy wrócić do naszego kodu i stworzyć odpowiedni zestaw testowy (zapewne integracyjny), który w kolejnym kroku pozwoli na refaktoryzację i stworzenie odpowiednich testów jednostkowych.
By osiągnąć ten moment, musimy mieć kompetencje w kolejnym obszarze.

Biegłość w tworzeniu kodu

Mała rzecz a cieszy. Znajomość IDE to podstawa. Senior developer to osoba, która zna swoje narzędzia na wylot. Tutaj nie ma miejsca na zastanawianie się, jaki jest skrót klawiaturowy, bo ten jest już wdrukowany w nasze mięśnie.
Tak samo z dodatkowymi narzędziami. IDE jest naprawdę zintegrowanym środowiskiem, co oznacza, że potrafimy dobrać sobie odpowiednie wtyczki tak, by poradzić sobie z praktycznie każdym zadaniem. Z własnych obserwacji – wiele razy spotykałem się z ludźmi, którzy mieli pootwierane kilka różnych edytorów. Coś do Javy, coś do SQLa, do tego terminal. Po co, skoro w IDE można mieć te wszystkie elementy dostarczone w spójny sposób? Biegłość w pisaniu kodu pozwala na skupieniu się na rzeczach istotnych i ważnych.
Starszego programistę będzie dodatkowo cechować jeszcze jedna umiejętność. Umiejętność radzenia sobie bez narzędzi. Nie zawsze mamy do dyspozycji mocny komputer z zainstalowanymi naszymi zabawkami. Czasami jest to jakaś konsola zdalna z zainstalowanymi vi i „gołym” kompilatorem. Tu właśnie doświadczenie pozwala na pracę i dostarczanie kodu, pomimo trudności. Co więcej, w takim przypadku senior developer potrafi jasno określić, czego potrzebuje, a to łączy się z kolejnym obszarem.

Umiejętności organizacyjne

Zespół cech, których spodziewamy się u menadżera, a nie u programisty. Jednak chodzi mi tu o umiejętność organizowania sobie i innym pracy w taki sposób, by była wydajna. Nie oznacza to, że starszy programista będzie dobrym menadżerem. Musi jednak umieć przekazywać swoje racje reszcie zespołu, proponować i argumentować rozwiązania oraz przekazywać swoją wiedzę w uporządkowany sposób. Jeżeli taka osoba będzie współpracować z juniorem, to będzie musiała tak podzielić zadania, by obie strony były wstanie je zrealizować, a jednocześnie czegoś się nauczyć.
Pod tym pojęciem kryje się jeszcze jedna umiejętność – radzenie sobie w korpodżungli. Pójść zapytać, podręczyć osobę odpowiedzialną, albo odpowiednio ustawić listę CC w mailu z ponagleniem i nie bać się zawracania dupy osobom odpowiedzialnym za daną rzeczy (jeżeli rzeczywiście tego wymaga sytuacja). Czyli coś, czego brakuje młodym pracownikom.

Podsumowanie

Starszy programista ma wiedzę, umiejętności i doświadczenie, które w połączeniu z rzemieślniczą biegłością w wykorzystaniu narzędzi pozwalają mu pracować na 110% bez zbędnego przepracowywania się. Jednocześnie umie ogarniać nie tylko techniczne sprawy, ale też rozmawiać z klientami oraz z innymi działami w firmie by praca była płynna i nie stawała się koszmarem.
Jak to osiągnąć? Wiedza to samokształcenie, czytanie, kursy, szkolenia, konferencje, JUGi. Doświadczenie przychodzi z czasem. Biegłość to znowuż ćwiczenia (kata!) i powtarzanie pewnych czynności, aż wejdą w nawyk. Umiejętności organizacyjne zazwyczaj wynikają z doświadczenia oraz tego, jak dobrze dogadujemy się z konkretnymi ludźmi. Choć tutaj ważny jest też element przekazywania wiedzy.

Używaj Varv.io mówili, będzie fajnie mówili

O ile oczywiście ładnie potniesz kod na mniejsze elementy, bo możesz spłodzić potwora:

Listing 1. Generowanie losowego NRB

public String generateNrb() {
    return concat(
            continually(() -> r.nextInt(10)).take(24),
            Stream.of(2, 5, 2, 1, 0, 0)
    ) 
            .zip(NrbGenerator.WEIGHTS.toStream()) 
            .unzip(t -> of(t._1() * t._2(), "" + t._1())) 
            .map((crcOfDigits, digits) ->
                    of(
                            (98 - (crcOfDigits
                                    .collect(
                                            summarizingInt(Integer::intValue)
                                    )
                                    .getSum() % 97)
                            ) + "",
                            digits.collect(
                                    joining()
                            )
                    )
            ) 
            .map(
                    (crc, val) -> of(
                            crc.length() < 2 ? "0" + crc : crc,
                            val.substring(0, 24)
                    )
            ) 
            .toSeq() 
            .map(Object::toString) 
            .collect(joining());
}

I pamiętajcie nie zostawiajcie kodu w takim stanie. Po doprowadzeniu do tego etapu należy kod jeszcze zrefaktoryzować wyciągając poszczególne kroki do osobnych funkcji/metod, tak by uzyskać:

Listing 2. To samo inaczej

public String generateNrb() {
    return concat(
            generateRandomNrbDigits(),
            getPL00Indicator()
    ) 
            .zip(NrbGenerator.WEIGHTS.toStream()) 
            .unzip(t -> of(
                    calculateDigitCrcValue(t), digitToString(t)
                    )
            ) 
            .map(this::crc, this::joinNrbDigits) 
            .map(this::addLeading0ToCrc, this::dropPL00Indicator) 
            .toSeq() 
            .map(Object::toString) 
            .collect(joining());
}

Dużo lepiej 🙂 Choć znając scalowców to by zapisali jako operator w rodzaju 8======|)—

Logger to też jest biznes

Typowa scenka gdzieś na mieście. Stoi dwóch policjantów w bramie i coś tam bazgrze w notatnikach. Co oni robią? Oczywiście notują informacje, które są istotne z ich punktu widzenia dla przebiegu patrolu. Inaczej mówiąc, logują informacje istotne z punktu widzenia biznesu. I właśnie o logowaniu dziś będzie.

Rodzaje logowania

Z punktu widzenia architekta możemy wydzielić trzy rodzaje logów. Każdy z nich odpowiedzialny jest za inny aspekt działania aplikacji, ale jeden zapis do logu może być istotny dla wielu aspektów.

Logi biznesowe

W tej grupie mamy zebrane logowanie wszystkich informacji istotnych z punktu widzenia biznesu. Mogą to byś zarówno informacje na potrzeby audytu – kto, gdzie, kiedy podjął daną akcję, przebieg procesu biznesowego, jak i informacje na potrzeby analizy pod kątem rynku np. systemu rekomendacji. W tej kategorii istotne jest określenie wspólnie z biznesem co i kiedy należy logować.

Ten log może, choć nie musi, być obiektem domenowym. Tak! Obiektem domenowym, ponieważ ma znaczenie biznesowe. Dlatego też należy przyznać temu logowi odpowiedni status i uwzględnić go na poszczególnych etapach projektowania. Ciekawym doświadczeniem może być pociśnięcie ekspertów domenowych w czasie stormingu, by określili się, co warto zapisać. Jest tu jednak zaszyta pewnego rodzaju pułapka projektowa. W wielu organizacjach, w których IT jest narzędziem, a nie istotą biznesu, istnieje silne przywiązanie do bazy danych jako miejsca składowania informacji istotnych dla biznesu. Bardzo ciężko jest w takim przypadku wytłumaczyć, że np. plik tekstowy, albo baza dokumentowa są wystarczającym rozwiązaniem.

Logi operacyjne

Logi operacyjne są najważniejsze z punktu widzenia programistów. Pozwalają na analizę stanu aplikacji, przyczyn awarii, dostarczają danych dla narzędzi monitorujących. Najczęściej logi te są wykorzystywane na etapie testów manualnych, kiedy testerzy wykorzystują je jako załączniki do zgłoszeń. Tu będą wszystkie zrzuty stacktrace, parametry wywołania metod czy czasy ich wykonania.

Oczywiście różne informacje mają różne priorytety, zatem projektując taki log, należy wziąć to pod uwagę. W efekcie będziemy tu intensywnie wykorzystywać różne poziomy logowania. Co więcej, jeżeli nie wykorzystujemy żadnego mechanizmy składowania i analizy logów w rodzaju Grayloga czy Logstasha, to koniecznie należy dopieścić konfigurację, tak by informacje o różnych aspektach trafiały do różnych plików (tak, wiem, nagle niskopoziomowo się zrobiło, ale to pewne uproszczenie myślowe). Dzięki temu analiza będzie prostsza.

Logi deweloperskie

Czyli taki bardziej rozbudowany dupadebug. Dzięki tym logom możemy śledzić przebieg programu bez konieczności korzystania z debuggera. Nie są one jakoś często stosowane, ale niekiedy są dość ważne. Tu też zbieramy informacje, które posłużą do dalszej analizy programu. Popularnym zastosowaniem jest rozrzucenie tego typu zapisów w celu odtworzenia procesów dla kodu zastanego i starego. Pozwala to na szybkie określenie warunków wykonania i ścieżek w kodzie na podstawie parametrów wywołania.

Błędy

Gdy mówimy o błędach popełnianych przy logowaniu, to zazwyczaj wskazywane są:

  • Synchronizacja zapisu do logu
  • Łączenie Stringów
  • Podwójne sprawdzanie poziomu logowania (konstrukcja w rodzaju if(log.isDebugEnable())

Rzecz w tym, że poza pierwszym z ww. problemów nie są, to poważne rzeczy. Znacznie częściej mamy do czynienia z zupełnie innymi wrzodami.

Nieprawidłowy poziom logownia

Chyba najpopularniejszy błąd. Pisząc kod, nie zastanawiamy się nad tym, co i jak będzie logowane. Wiadomo błędy na error albo podobny, reszta pomiędzy info, a debug. Błędy do olania na warrning. Nie ważne, czy informacja jest ważna dla biznesu, operatorów czy chcemy mieć jakiś marker w logu. W ten sposób do logu trafiają losowe informacje, które ciężko analizować i jeszcze ciężej coś z nich wywnioskować.

Jak sobie z tym radzić? Myśleć co się pisze oraz, oczywiście, poświęcić trochę czasu na określenie zasad logowania.

Różne systemy logowania

Drugim popularnym błędem jest mieszanie systemów logowania. Niestety wykorzystywane przez nas narzędzia nie ułatwiają nam życia. Część korzysta z log4j, część z apache logging, a jeszcze inne obudowują to w slf4j/slf4j2. Jeżeli dołożymy do tego zawsze dostępny Logger z JRE oraz stary dobry System.out.println, to okaże się, że w ramach jednej aplikacji mamy kilka niespójnych systemów logowania.

Problemy

Drugi z ww. błędów jest objawem poważnego wrzodu na ciele społeczności JVMowej. Język ma co prawda logger na pokładzie, ale pojawił się on dość późno oraz jest stosunkowo prymitywny. W efekcie mamy dużo różnych bibliotek, które mają niespójne API. To oczywiście powoduje, że wraz ze wzrostem złożoności projektu i przyrostem liczby zależności pojawią się coraz to nowsze mechanizmy logowania. Co prawda sfl4j miał rozwiązać ten problem, ale no cóż…

Drugim problemem związanym z niespójnością jest ograniczona możliwość dostosowania loggerów do potrzeb naszej aplikacji. Wybierając jakieś rozwiązanie, musimy pogodzić się z jego ograniczeniami. W ten sposób nasz kod zostaje w pewnym sensie przywiązany do biblioteki. W dodatku tracimy swobodę definiowania, jak logowanie ma działać. Utworzenie loggerów na potrzeby różnych aspektów jest trudne, a w dodatku zmiana biblioteki staje się praktycznie niemożliwa. Słabo. Bardzo słabo.

Podsumowanie

W kolejnym wpisie zabawimy się już z kodem. Mam nadzieję, że to krótkie wprowadzenie nie zabiło waszej ciekawości.

Confitura 2018 – tym razem na zimno

Pierwszy raz od dawna nie mam pomysłu jak zrelacjonować konferencję. Po prostu nie mam. Spróbujmy więc może tak.

Miejsce

Czyli hale Expo XXI na Prądzyńskiego są bardzo OK. Zastanawiam się tylko, czy osoby spoza Warszawy były wstanie ogarnąć, gdzie to jest. Szczególnie idąc od strony dworca zachodniego. Widziałem tam grupy ludzi, którzy po przyjechaniu pociągiem chyba nie do końca wiedzieli gdzie iść. Niech se kupią mapę 😉

Dużo miejsca, zarówno dla wystawców, jak i uczestników. Przestrzeń, w której nie było widać tych prawie dwóch tysięcy ludzi.

Miesce II

Kręgielnia w której była Spoina zawsze na propsie 🙂 Choć miałem wrażenie, że mało ludzi było.

Wystawcy i gadżety

Od kogoś usłyszałem, że Confitura to taka konferencja gdzie są najfajniejsze gadżety. I ta osoba miała rację. Wystawców sporo, gadżety i nagrody najróżniejsze. Fajnie, że było miejsce. I co najważniejsze, wszyscy się postarali, by było fajnie!

Papu

Papuśne, poza kawą, która tradycyjnie była niepijalna. Sytuację ratował UBS, bo mieli ekspres na stoisku. Klasycznie też pora obiadu, to pora kolejek. Choć nadal zastanawia mnie pytanie o rodzaj posiłku w ankiecie biletowej… W przerwach było inne papu i trzeba było się pilnować, by nie nawpieprzać się ciastków.

Ludzie

Wspaniali.

Prelekcje

Różnie.

Antywzorce projektowe albo jak rozwalić dowolny projekt w 4-6 tygodni – Paweł Lewtak

Taka prezentacja, w której czegoś mi zabrakło. Jakiejś katastrofy. Poza tym spoko.

Micro-monolith anti-pattern – Tomasz Fijałkowski

Całkiem fajnie opowiedziane o tym problemie. Swoją drogą widać już, że µSerwisy zaczynają być passe. Ludzie zaczynają dostrzegać ich ograniczenia.

Kariera sfrustrowanego developera – Mateusz Parada

Najmądrzejsza prezentacja nietechniczna całej tegorocznej Confitury. Może trochę przejaskrawiona, ale bardzo sensowna. Moim zdaniem mocny kandydat do nagrody na najlepszą prezentację C’18.

Blockchain: more than bitcoin! – Andrzej Grzesik

Technicznie bajka. Rzeczowo, precyzyjnie i logicznie. W końcu zaczynam rozumieć, jak to działa w bebechach.

Programistów z biznesem przypadki trudne – Jakub Kubrynski, Sławomir Sobótka, Michal Michaluk

Super prezentacja, której głównym składnikiem był show. Merytorycznie bardzo fajny temat, ale nie odkrywczy. Może to już moje doświadczenie „niweluje” wartość, ale dla młodszych stażem na pewno ważna lekcja.

Co do poprawy?

Dźwięk niósł się z jednej sali, na drugą. Jako prelegentowi mi to nie przeszkadzało, bo nie słyszałem tego (siebie też nie, odsłuchy by się przydały), ale jako słuchaczowi już tak.
Druga rzecz to streaming. Był, ale nie mogę nigdzie znaleźć nagrań streamingowych. Trzeba było je zostawić na tydzień w necie, by można było nadrobić.

Podsumowanie

Na zakończenie chciałbym przeprosić wszystkich tych, z którymi przybiłem piątkę, ale nie znalazłem czasu, by zamienić kilka słów. Może przy kolejnej okazji uda się chwilę porozmawiać o życiu i GC oraz o tym, jak małe forki się sprawują.
Dziękuję organizatorom, wolontariuszom i uczestnikom. Było super.

JLupin Next Server – dodajemy bazę danych, czyli gdzie jest classpath

Dziś będziemy dodawać bazę danych, a właściwie „bazę danych” do naszego projektu. Jest to całkiem dobry moment by przyjrzeć się skąd JLNS zaczyna wyliczać classpath.

Baza danych

Niekoniecznie relacyjna. Wielokrotnie spotkałem się z niezrozumieniem ze strony poganiaczy w dziedzinie składowania danych. Architekci przy wsparciu managerów zazwyczaj forsują rozwiązania relacyjne. Relacyjna baza danych zapewnia ich zdaniem trwałość danych (pewność ich zapisania) oraz spójność (zapis jest transakcyjny). Rzecz w tym, że bardzo szybko baza danych staje się odbiciem modelu domeny i to 1:1. Do czego to prowadzi?

Po pierwsze do ograniczenia elastyczności modelu domenowego. Jeżeli zachodzi potrzeba większej zmiany w modelu domenowym, np. przeorganizowania encji z powodów biznesowych, to jesteśmy w dupie. Nagle okazuje się, że trzeba przygotować różne dzikie migracje w strukturze tabeli. To pociąga za sobą konieczność przepisania czy to SQLi, czy to mapowiań ORMowych, a na koniec i tak się okazuje, że jest jeszcze cała masa procedur, triggerów i jobów bazodanowych, które nagle tracą sens. A my tylko chcieliśmy wynieść jeden z VO jakiegoś zagłębienia do głównej encji.

Po drugie wszelkie problemy z wydajnością, które wynikają z niedostosowania modelu relacyjnego do pracy w świecie obiektowym, zaczyna się rozwiązywać poprzez pisanie procedur po stronie bazy danych. W efekcie część logiki jest realizowana w bazie danych, część po stronie aplikacji, a część na poziomie procesowym. Oczywiście utrzymanie takiego potwora jest kosztowne i trudne, ale za hajs właściciela/inwestora baluj.

Po trzecie pojawiają się problemy ze składowaniem nietypowych, nierelacyjnych, struktur danych. JSON czy XML zapisany w bazie danych? Why not! Struktury typu słowniki z referencjami i hierarchią jako osobna instancja bazy danych? Dawaj Janusz, będzie dobrze! Co ciekawe na końcu jak całość działa w żółwim tempie, to opierdala się programistów.

Znacznie ciekawiej robi się, gdy zaczynamy wchodzić w środowiska rozproszone. Jak zapewnić transakcyjność w momencie, gdy musimy podzielić nasz system na kilka aplikacji, albo musimy całość rozrzucić w klastrze? Jeszcze zabawniejsze są jednak miny naszych mistrzów projektowania z kwadratów i beczek, gdy mówimy im, że jednak te mikroserwisy, to nie ruszą z jednym oraclem i trzeba zapewnić kilkanaście instancji. Wtedy okazuje się, że hajs i architektura mają pewne powiązania.

A można inaczej…

DataLayer

Naszą przygodę z dodawaniem bazy danych do systemu rozpoczniemy od dodania małej zależności do naszego pom-a w customer-storage/implementation:

Listing 1. Airomem na ratunek


        
        
            pl.setblack
            airomem-core
            1.1.1
        

Na początek wystarczy, że dane będą przechowywane w pamięci. Serio, serio. RAM jest tani, chyba że masz serwery IBMa, to wtedy jest drogi no ale… Następnie należy, to wszystko jakoś pokleić w kodzie.

Generowanie DAO

Plugin JLNS pozwala na wygenerowanie klas reprezentujących DAO, domyślna zawartość interfejsu jest pusta, a implementacja jest oznaczona adnotacją @Repository(value = „COŚTAMDAO”). Po dorzuceniu kilku rzeczy interfejs CustomerDAO wygląda w nastepujący sposób:

Listing 2. Interfejs CustomerDAO

public interface CustomerDAO {

	Option<Customer> getById(UUID customerId);

	Set<Customer> all();

	UUID add(Customer newCustomer);
}

Zwróćcie uwagę na klasę Customer, która będzie reprezentować encję w rozumieniu DDD. Tu będą składowane wszystkie informacje o użytkowniku, które przechowujemy na poziomie jego składowania. W Warstwie usług biznesowych też pojawi się taka klasa, ale będzie miała inne znaczenie.

Następnie musimy dodać prostą implementację, która jest też ładnym przykładem do pewnego wpisu.

Listing 3. Klasa CustomerDAOInMemory

class CustomerDAOInMemory implements CustomerDAO, Serializable {

	private Map storage = new ConcurrentHashMap<>();

	@Override
	public Option getById(UUID customerId) {
		return Option.of(storage.get(customerId));
	}

	@Override
	public Set all() {
		return storage.values()
				.stream()
				.collect(HashSet.collector());
	}

	@Override
	public UUID add(Customer newCustomer) {
		storage.put(newCustomer.getCustomerId(), newCustomer);
		return newCustomer.getCustomerId();
	}
}

Na koniec całość owiniemy w springowy bean, który będzie nam ogarniał logikę związaną z biblioteką airomem:

Listing 4. Klasa CustomerDAOAiromem

@Repository(value = "customerDAO")
public class CustomerDAOAiromem implements CustomerDAO {

	private Persistent controller;

	@Autowired
	public CustomerDAOAiromem(
			@Qualifier("storagePath") Path path,
			@Qualifier("useRoyalFoodTester") boolean useRoyalFoodTester) {
		Path resolve = Paths.get(path.toAbsolutePath().toString(), "customer");
		this.controller = Persistent.loadOptional(resolve,
				CustomerDAOInMemory::new, useRoyalFoodTester);
	}

	@Override
	public Option getById(UUID customerId) {
		return controller.query(c -> c.getById(customerId));
	}

	@Override
	public Set all() {
		return controller.query(CustomerDAOInMemory::all);
	}

	@Override
	public UUID add(Customer newCustomer) {
		return controller.executeAndQuery(c -> c.add(newCustomer));
	}

	@PreDestroy
	void close() {
		controller.close();
	}
}

Jak to uruchomimy, to okaże się, że chyba zgubiliśmy gdzieś naszą bazę danych…

Jak JLNS liczy classpath

JLNS w trakcie uruchamiania usługi podaje classpath. Należy do niego m.in. katalog ./application/customer-storage/, ale korzeniem jest katalog ./start. Trochę to nieintuicyjne, ale jak już o tym się pamięta, to można sobie policzyć classpath. I tak oto możemy zapisywać użytkowników w bazie danych. Jeżeli chcemy zmienić bazę na przykład oracla, to wystarczy podmienić implementację CustomerDAO. W przypadku ORMów trzeba by dodać jeszcze mapowania, ale po pierwsze, jeżeli chcemy to zrobić bez zmian w klasie Customer, to możemy wykorzystać XMLa, albo stworzyć klasę CustomerEntity, która będzie naszym opisem struktury bazodanowej.

Na dziś starczy.

JLupin Next Server – komunikacja między usługami – podstawy

Na początek link do repozytorium GH gdzie leży sobie kod.

Dziś zajmiemy się komunikacją pomiędzy usługami oraz podstawami testowania z wykorzystaniem JLNS.

Konfigurowanie zależności

Jak wspomniałem w poprzednim wpisie, usługi natywne są wewnętrznie podzielone na interfejsy i implementacje. Wykorzystując mavena, możemy uzależniać się od interfejsów, bez konieczności dociągania implementacji. Ma to dwie zalety. Po pierwsze ogranicza ilość kodu jaki, zostanie dodany do naszego finalnego artefaktu. Po drugie pozwala na kulturalną współpracę pomiędzy zespołami. Pośrednikiem będzie tu repozytorium mavena. Miodzio.

Zależności gateway

Gateway udostępnia nam wybrane API. Na tym poziomie musimy zatem mieć zależności do procesów biznesowych. Dlatego w ./AccessLayer/gateway-access/implementation/pom.xml musimy dodać zależności do serwisów z warstwy biznesowej:

Listing 1. pom.xml w gateway

pl.koziolekweb.jlns.banksteraccount-business-logic-interfaces1.0-SNAPSHOTpl.koziolekweb.jlns.bankstercustomer-business-logic-interfaces1.0-SNAPSHOT
...

Tu objawia się kolejna mała niedoróbka domyślnej struktury projektu JLupinowego. W takiej strukturze ciężko w jakiś sensowny sposób zarządzać wersjami usług. Z jednej strony można wszystko po prostu zapisać jako zmienną i podstawiać wszędzie gdzie trzeba. Rzecz w tym, że tych zmiennych może być dużo. Drugim sposobem może być wykorzystanie parametru ${parent.version}, ale zadziała to tylko w przypadku natywnych mikroserwisów. Mikroserwisy servletowe jako „rodzica” mają ustawionego SpringBoota w wersji 1.5, co niestety nie jest parametryzowane z poziomu kreatora i jest kolejną małą niedoróbką. Przy czym problemu wersjonowania usług w sensowny sposób to jeszcze nikt dobrze nie wymyślił, a JLNS tak naprawdę deleguje problem do użytkownika.

A co z dostępem do usług ogarniających dane? Moim zdaniem, to ważne, nie należy ich linkować bezpośrednio do gatewaya. Wynika to ze sposobu, w jaki publikowane są usługi i może spowodować, że ujawnimy za dużo. Nawet jeżeli na poziomie warstwy biznesowej serwis pośredniczący będzie tępą rurą bez logiki, to nadal daje nam to separację modelu danych na niskim poziomie od eksponowanego modelu.

Zależności w serwisach biznesowych

Tu zasada jest ta sama. Z tą małą różnicą, że zależność dodajemy tylko do pom-a w module z implementacją np.

Listing 2. zależność w ./BusinessLogicLayer/customer-business-logic/implementation/pom.xml

pl.koziolekweb.jlns.bankstercustomer-storage-data-interfaces1.0-SNAPSHOT
...

I tak oto mamy już ogarniętą konfigurację na poziomie zależności. Czas na zabawę w kodzie.

Jak korzystać z usług?

Proces konfiguracji zależności już za nami. Teraz mamy dostęp do interfejsów, a JLNS zrobi z nich interfejsy zdalne. Tyle tylko, że trzeba mu trochę w tym pomóc. Zrobimy to na przykładzie usługi customer-storage. Wystawimy z niej prosty interfejs StorageCustomerBasicInformationService, który wygląda następująco:

Listing 3. Interfejs StorageCustomerBasicInformationService

public interface StorageCustomerBasicInformationService {

	Set<BasicCustomerInformation> all();

	Option<BasicCustomerInformation> byId(UUID customerId);
}

Klasa CustomerBasicInformationService znajduje się w module common-pojo, a to dlatego, że jest to minimalny zbiór informacji o kliencie. Coś w rodzaju VO klienta. Będziemy z tej klasy korzystać w wielu miejscach i można przyjąć, że na poziomie systemu to element naszego języka domenowego (Podstawowe Dane Klienta, czyli PaDaKa). Sam interfejs znajduje się w module interfaces.

Kolejnym krokiem jest implementacja tego interfejsu w module implementation. Oczywiście nie mam zamiaru tego pisać z palca. W InteliJ rozwijamy moduł implementation i odnajdujemy pakiet COŚTAM.service.impl, naciskami alt+insert i:

Następnie podajemy nazwę serwisu bez przyrostka Service, ten zostanie dodany automatycznie.

W efekcie zostaną wygenerowane dwa pliki (interfejs i klasa):

//offtop
Po drodze zrobiłem refaktoryzację nazw, by było łatwiej, więc nie do końca trzyma się to to z obrazkami kupy.
//koniec offtopa

Upublicznienie usługi

Teraz należy jeszcze usługę upublicznić. Generator nie tylko stworzył pliki, ale też zmodyfikował klasę CustomerStorageSpringConfiguration:

Listing 4. Klasa CustomerStorageSpringConfiguration

@Configuration
@ComponentScan("pl.koziolekweb.jlns.bankster")
public class CustomerStorageSpringConfiguration {
	@Bean
	public JLupinDelegator getJLupinDelegator() {
		return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
	}

	@Bean(name = "jLupinRegularExpressionToRemotelyEnabled")
	public List getRemotelyBeanList() {
		List<String> list = new ArrayList<>();
		list.add("storageCustomerBasicInformationService");
		return list;
	}
}

JLupin na podstawie listy nazwanej jLupinRegularExpressionToRemotelyEnabled odszuka i opublikuje wybrane serwisy. Tu bardzo ważnym elementem jest nadanie naszym springowym beanom nazw. Co prawda istnieje konwencja nazewnicza, dzięki której możemy wykorzystać domyślne nazwy, ale może być ona źródłem różnych pomyłek. Zatem nasza implementacja wygląda w następująco:

Listing 5. Interfejs StorageCustomerBasicInformationServiceImpl

@Service(value = "storageCustomerBasicInformationService")
public class StorageCustomerBasicInformationServiceImpl implements StorageCustomerBasicInformationService {
	@Override
	public Set<BasicCustomerInformation> all() {
		return HashSet.empty();
	}

	@Override
	public Option<BasicCustomerInformation> byId(UUID customerId) {
		return Option.none();
	}
}

Używam tu Vavr, bo chcę mieć serializowane kontenery. Dlaczego? O tym za chwilę. Przejdźmy do konfiguracji po stronie klienta.

Konfiguracja kliencka

Klientem naszej usługi jest serwis customer-business-logic. Dlatego też udamy się teraz do klasy konfiguracyjnej CustomerSpringConfiguration:

Listing 6. Klasa CustomerSpringConfiguration

@Configuration
@ComponentScan("pl.koziolekweb.jlns.bankster")
public class CustomerSpringConfiguration {
	@Bean
	public JLupinDelegator getJLupinDelegator() {
		return JLupinClientUtil.generateInnerMicroserviceLoadBalancerDelegator(PortType.JLRMC);
	}

	@Bean(name = StorageCustomerBasicInformationService.name)
	public CustomerBasicInformationService getStorageCustomerBasicInformationService() {
	     return JLupinClientUtil.generateRemote(getJLupinDelegator(),
			     "customer-storage",
			     StorageCustomerBasicInformationService.name,
			     StorageCustomerBasicInformationService.class);
	}

	@Bean(name = "jLupinRegularExpressionToRemotelyEnabled")
	public List getRemotelyBeanList() {
		List<String> list = new ArrayList<>();
		return list;
	}
}

Uwaga! Po drodze wyrzuciłem jeszcze nazwę serwisu, którą używamy w nazwie beana na poziom interfejsu. Taka mała rzecz, a cieszy i daje trochę większą kontrolę nad tym, co się dzieje.

Metoda getStorageCustomerBasicInformationService produkuje nam proxy, które opakowuje JLupinowy mechanizm wywołań zdalnych. W naszym kodzie możemy później użyć takiego wywołania tak, jak byśmy wywoływali zwykłą, lokalną, metodę. Bardziej doświadczeni czytelnicy rozpoznają tu zapewne mechanizm RMI obecny w EJB. Podstawowa różnica pomiędzy tymi rozwiązaniami leży w sposobie ich implementacji. To, czy dany interfejs jest dostępny zdalnie, czy też nie jest definiowane na poziomie konfiguracji, a nie interfejsu. Nie ma ty adnotacji Local czy Remote, bo nie są do niczego potrzebne. Co prawda można by dodać adnotację, która by wyszukiwała za nas implementacje, ale to szczegół. Zresztą i tak musimy przepchnąć nasze obiekty dalej do gateway-a. Powtórzymy zatem proces tworzenia serwisu na poziomie warstwy usług biznesowych i skonfigurujmy usługę tak, by można było jej używać z zewnątrz. Nasza implementacja będzie wyglądać następująco:

Listing 7. Klasa CustomerSpringConfiguration

@JLupinService
@Service(value = CustomerBasicInformationService.name)
public class CustomerBasicInformationServiceImpl implements CustomerBasicInformationService {

	private final StorageCustomerBasicInformationService delegate;

	@Autowired
	public CustomerBasicInformationServiceImpl(StorageCustomerBasicInformationService delegate) {
		this.delegate = delegate;
	}

	@Override
	public Set<BasicCustomerInformation> all() {
		return delegate.all();
	}

	@Override
	public Option<BasicCustomerInformation> byId(UUID customerId) {
		return delegate.byId(customerId);
	}
}

Testy integracyjne

Na tym etapie wystarczyło by zrobić mvn clean install i po problemie. Zanim jednak tak zrobimy musimy jeszcze trochę rzeczy związanych z testowaniem. W zasadzie testowanie integracyjne w JLNS nie różni się zasadniczo od zwykłych testów integracyjnych. W module integration-test dodajmy sobie prosty test naszego CustomerBasicInformationService

Listing 8. Klasa CustomerBasicInformationServiceITest

public class CustomerBasicInformationServiceITest extends BaseTest {

	 @Test
	 public void exampleTest() {
	    CustomerBasicInformationService service = JLupinClientUtil.generateRemote(getJLupinDelegator(),
			    "customer",
			    CustomerBasicInformationService.name,
			    CustomerBasicInformationService.class);
		 Assertions.assertThat(service.all()).isEmpty();
	 }
}

Klasa BaseTest przygotowuje nam prostego klienta, który będzie komunikować się z serwerem na localhost.

Uruchomienie

Pozostało nam to wszystko uruchomić. Wystarczy zatem uruchomić serwer i następnie odpalić mvn clean install. Poniżej nagrałem ten proces (koniecznie full screen):

Górna połowa to oczywiście maven. Dolna to podgląd konsoli JLNS. Ładnie widać jak wstają kolejne serwisy.

Podsumowanie

Podstawowa komunikacja pomiędzy serwisami w ramach JLNS jest bardzo prosta, a dzięki kilku „sztuczkom ze Springiem” można to ładnie ogarnąć. Fajne, prawda?

// edit: w Listingach 6 i 8 zabrakło jednej linijki. Nazwy usługi do której się odwołujemy.

JLupin Next Server – pierwsze kroki, pierwsze usługi

W poprzednim wpisie było trochę dupereli o mikroserwisach. Dlaczego są nie do końca OK oraz jak JLupin może pomóc nam ogarnąć tę kuwetę. Dziś będziemy bawić się już w programowanie i na przykładach zademonstruję co i jak.

Co piszemy?

Jak już 11 lat bloga prowadzę, to zawsze przewijał się na nim temat bankowości. Banki duże, banki małe, systemy rozliczeniowe, windykacyjne, transakcyjne, raportowanie i cała masa innych niegodziwych elementów tego szatańskiego tworu zwanego systemem bankowym. Dlatego też bez zaskoczenia. Napiszemy bank. Dokładnie napiszemy sobie fragment związany z kontami, klientami i jakieś proste raportowanie. Docelowo będziemy mieli tu kilka aspektów, które ładnie będzie można przenieść na poszczególne usługi:

  • Klienci i konta – informacje o klientach i ich kontach, czyli takie typowe CRUD-owe usługi.
  • Operacje finansowe – tu będzie się działo mięsko związane z przelewaniem kasy.
  • Raportowanie – zazwyczaj w takim miejscu jest jakiś system raportowo-analityczny, my zrobimy sobie wydmuszkę.
  • Parametryzacja – pewne elementy banku muszą być pobierane z parametrów biznesowych.
  • API Gateway – końcówka dla klientów.

By jeszcze sobie utrudnić życie, a jednocześnie móc zademonstrować kilka elementów JLNS, wprowadzimy funkcjonalność „partnerów”, czyli coś, co na co dzień znamy jako kredyty sprzedawane w sklepach z AGD, albo karty kredytowe marketów.

// offtop

Od wczoraj żyjemy z RODO, a to oznacza, że takie produkty jak np. karta kredytowa „Stonka i Ty” muszą mieć znacznie ciekawszą konstrukcję. Jak będziecie brać kredyt w markecie, to wczytajcie się w regulaminy, bo może okazać się, że kredytodawcą jest jakiś niezbyt ogarnięty bank.

// koniec offtopa

Oczywiście będzie dużo uproszczeń (albo i nie), ale na początek zajmiemy się czymś zupełnie trywialnym Przygotujemy projekt w IntelliJ. Jest to o tyle proste, że JLNS udostępnia wtyczkę do IDE. Obrazkowo będzie.

Projekt Bankster

Na początku oczywiście menu „Create new Project” i następnie wybieramy projekt JLupinowy

Kolejnym krokiem jest określenie wersji grupy mavenowej oraz miejsca, w którym zapisaliśmy zipa zawierającego serwer. Osobiście uważam, że osadzanie serwera w ramach projektu jest po prostu wygodne. Czasami trzeba coś pogrzebać w konfiguracji czy rzucić okiem na logi.

Następnie stworzymy sobie projekt, który będzie ogarniał API Gateway:

Do tego dodajmy projekty dla klienta i rachunków oraz ich „tło” do składowania danych:

Na koniec nadajemy całości nazwę bankster i gotowe:

Na obecnym etapie otworzy się okno IDE, w którym mamy podgląd do naszej architektury:

Podgląd co prawda nie jest najlepszy, bo nie pozwala nam na tworzenie zależności za pomocą drag’n’drop, ale lepsze to niż nic. Szczególnie że przy większych projektach i tak szybciej będzie dodać zależność w pom.xml niż kombinować z myszką.

Struktura projektu

Przyjrzyjmy się teraz strukturze projektu. Pierwszą rzeczą, która rzuca się w oczy, jest organizacja modułów. Poszczególne projekty są modułami głównego projektu. I to jest nie do końca OK. Taki układ wymusza na nas pracę z projektem jak z monolitem. W dodatku trudno będzie zarządzać takim projektem, tak by poszczególne usługi miały swoje repozytoria. Moim zdaniem jest to bardzo duży błąd ze strony twórców. Z drugiej strony pozwala, to na zarządzanie całością z jednego miejsca. Trochę innym podejściem jest stworzenie projektu typu bom, w którym upakujemy konfigurację zależności. Przyjmijmy jednak, że ta domyślna konfiguracja ma jakiś głębszy sens. Przyjrzyjmy się, jak wyglądają poszczególne projekty.

Zacznijmy od gatewaya. Projekt ten umieszczony jest w katalogu ./AccessLayer/gateway-access/. Znajdziemy tam dwa katalogi. Pierwszy to additional-files, w który znajdują się dwa pliki:

  • log4j2.xml – zawiera konfigurację log4j2, która zostanie wykorzystana przez techniczne elementy usługi. Jest to też domyślny log usługi.
  • servlet_configuration.yml – tu znajdziemy konfigurację usługi, która obejmuje kanały komunikacyjne, pamięć, bufory, ale też na przykład zasady rotowania logów.

Pozostałe podprojekty mają taką samą strukturę. Przy czym projekty z warstwą biznesową znajdują się w katalogu ./BusinessLogicLaywer/{account,customer}-businesslogic/, a projekty związane z dostępem do danych w katalogu ./BusinessLogicLaywer/{account,customer}-storage-data/. Sama struktura poszczególnych projektów jest następująca:

  • ./additional-files/ – to samo co wyżej, czyli konfiguracja, ale zamiast servlet_configuration.yml, mamy configuration.yml.
  • ./interfaces/ – w tym projekcie definiujemy interfejsy naszej usługi. Co ważne inne usługi korzystające z naszego serwisu będą zależne od tego właśnie projektu.
  • ./implementation/ – implementacja naszych usług. Tym się nie musimy z nikim dzielić.

Poza wyżej wymienionymi elementami mamy jeszcze:

  • common-pojo – w tym projekcie leżą sobie klasy, które reprezentują pewne podstawowe struktury danych w naszym systemie. W praktyce to kubeł na różne VO.
  • common-utils – skrzynka na narzędzia. Tu umieść klasę StringUtils. Na pewno będziesz jej potrzebować.
  • integration-test – w tym miejscu leżą testy integracyjne, które można odpalić w procesie CI/CD.

Rodzaje mikroserwisówserwisów

Taka organizacja poszczególnych projektów jest wynikiem wyróżnienia dwóch rodzajów mikroserwisów.

Mikroserwisy servletowe

Pod tą nazwą kryją się mikoserwisy, które są aplikacjami webowymi. Co do zasady jest to spring boot. Mikroserwisy te wystawiają API na potrzeby naszych klientów. Na tym poziomie mamy dostęp do naszej aplikacji za pośrednictwem HTTP(S) i możemy pisać aplikacje zgodne ze specyfikacją servletową. Z drugiej strony usługi te są pozbawione wbudowanych kolejek, które pozwalają na tworzenie reaktywnych rozwiązań. Nie jest to nic dziwnego, bo tu nie potrzebujemy tego typu magii.

Mikroserwisy natywne

Czyli aplikacje (jary), które zawierają logikę biznesową, zarządzają danymi, komunikują się z innymi, zewnętrznymi usługami. Tych usług nie wystawiamy na zewnątrz bezpośrednio. Jednocześnie stanowią one największą część naszej aplikacji.

Podsumowanie

Praca z JLNS w zakresie tworzenia projektów jest bardzo prosta. Z drugiej strony niektóre pomysły twórców uważam za swoistą pomyłkę np. super-projekt mavenowy. Warto też zwrócić uwagę na jeszcze jeden aspekt. JLNS bardzo intensywnie wykorzystuje Spring Framework, ale jeżeli chcemy pisać bez wsparcia ze strony Springa, to nadal możemy to robić. W kolejnej części zaczniemy wiązać usługi między sobą i zaimplementujemy jakiś prosty przypadek użycia.

JLupin Next Server – wprowadzenie i instalacja

Tradycją w Polsce jest wykorzystanie urlopu/chorobowego do prowadzenia remontów. Jako że siedzę na L4, to jest to najlepsza okazja, by zadbać o blogaska. Przy okazji przedstawić wam pewną ciekawą technologię.

Mikroserwisy robione bez sensu

W epoce mikroserwisów coraz częściej można spotkać aplikacje, które są tak naprawdę „mikroserwisami”, bo nie posiadają cech wymaganych dla tej architektury. Z moich obserwacji wynika, że są dwa główne źródła takiego stanu rzeczy.

  • Źródło marketingowe – zazwyczaj typowe BDD (Buzzword Driven Development). Skoro wszyscy robią mikroserwisy i to się sprzedaje, to my też. Jest to bardzo stara przypadłość.
  • Źródło biznesowe – dokładnie, bardzo prosta domena biznesowa. Jeżeli mamy dwa przypadki użycia na krzyż, albo niewielką ilość encji, a jednocześnie potrzebujemy pewnych cech, które dają mikroserwisy np. dynamicznej skalowalności, czy ciągłości działania.

Przy czym mam świadomość, że jak w przypadku wszelkich prób opisania świata przez pryzmat własnych doświadczeń, będzie to opis niepełny.

Pierwszy punkt jest oczywisty i co kilka lat przewija się w takiej czy innej formie. Znacznie ciekawszy jest punkt drugi. Wybór architektury mikroserwisowej wynika tu z braku znajomości innych rozwiązań np. mod_cluster (który zazwyczaj wystarcza do zapewnienia dostępności i skalowalności) czy wiary, że „mikroserwisy to same to ogarną”. Jednocześnie ignoruje się dwie cechy, które powinien spełniać projekt, by w ogóle podjąć próbę użycia tego rodzaju architektury:

  • Niezależność zespołów
  • Niezależność technologii

W obu przypadkach z naciskiem na liczbę mnogą. Pozatechniczną cechą architektury mikroserwisowej jest możliwość budowania aplikacji w oparciu o rozproszone zespoły, które pracują niezależnie od siebie. Zespoły te mogą używać różnych, całkowicie niekompatybilnych, technologii. Na przykład tworząc aplikację do e-learningu, użyję innych technologii do obsługi streamingu video, a innych do ogarnięcia płatności. Pierwsze zadanie opędzę np. Elixirem, a to drugie napiszę w Javie (w zasadzie wykorzystam jakiegoś gotowca). Całość oczywiście będzie gadać ze sobą po jakimś uniwersalnym protokole.
Z drugiej strony mając jeden niewielki zespół, do stworzenia takiej aplikacji nie mogę wymagać, że będzie on ogarniać kilku różnych technologii. Muszę wybrać takie rozwiązanie architektoniczne, które pozwoli mi na jak najlepszą wydajność pracy. Być może okaże się, że odpowiednio napisany monolit będzie tu lepszy. Co ciekawe (też z własnych obserwacji), nawet jeżeli mamy architekturę mikroserwisową, która poniekąd wymusza separację komponentów, to jeżeli mamy mały zespół, to i tak zacznie powstawać, albo jakieś dziwne spaghetti pomiędzy komponentami, albo zespół będzie kodował metodami Kopiego-Pasty wszędzie tam, gdzie kod się powtarza. Dokładnie w ten sam sposób jak ma to miejsce w przypadku monolitu.

UWAGA!

Dobrze napisany monolit wymaga świadomego zespołu, który potrafi wykorzystywać clean architecture oraz opiera się nieuprawnionym operacjom.

Inne problemy z mikroserwisami

Poza wyżej wymienionymi problemami, które wynikają z niedopasowania architektury do problemu, jest też jeszcze kilka innych, które zazwyczaj umykają przy wyborze architektury.

Koszty sprzętu

Na początku mało kto zdaje sobie sprawę ile może kosztować wdrożenie mikroserwisów. Cztery jednostki m4.large w amazonowym AWS kosztują obecnie 0,60$/h jednocześnie czterokrotnie większa jednostka m4.2xlarge (4x więcej rdzeni i pamięci RAM) kosztuje 1,44$/h. Często padające hasło „mikroserwisy można wdrażać na mniejszych i tańszych serwerach” może okazać się znacznie kosztowniejsze w porównaniu z wykorzystaniem monolitu na mocniejszej maszynie.

Wydajność

Bardzo ciekawy argument, bo często mikroserwisy przedstawia się jako remedium na problemy z wydajnością. A to nie tak. Oczywiście dzięki znacznie lepszej skalowalności można poprawić wydajność w pewnych rejonach np. można przyjąć więcej żądań. Z drugiej strony mogą pojawić się nowe problemy związane z tempem komunikacji. Wywołanie zdalne nie będzie tak szybkie jak wywołanie lokalne. Nawet jeżeli jest robione w obrębie pojedynczej maszyny. Po prostu komunikacja trwa.

Koszty technologii (i ludzi)

Wprowadzając architekturę mikroserwisową do naszego świata, musimy zapewnić odpowiednie zaplecze. Jak wcześniej wspomniałem, koszty sprzętowe mogą być znaczące, dlatego chcemy mieć kontrolę nad sprzętem. Tym samym wprowadzamy różnego rodzaju narzędzia, które pozwolą nam na atomizację komponentów i niezależne zarządzanie nimi. Poszczególne mikroserwisy pakujemy w kontenery dockerowe, te rozrzucamy na środowiska przy pomocy managera w rodzaju Kubernetes, na którym zmiany w boxach są zarządzane za pomocą Ansible, a same boxy są zwirtualizowane z użyciem Mesos, dzięki czemu możemy tworzyć „dziwne” konfiguracje sprzętowe (wirtualne). Do tego procesy deploymentu są zarządzane z poziomu Jenkinsa i jego pipelien-ów. BDD pełną gębą. A przecież, ktoś to musi ogarnąć.
Koszty technologii to nie tylko koszty licencji, ale przede wszystkim koszty ludzi, którzy będą je później utrzymywać. W dodatku nasi ludzie muszą mieć wiedzę z wielu dziedzin, które niekoniecznie idą w parze. Dla dużych organizacji zatrudnienie „gościa od buildów”, którego zadaniem będzie jedynie analizowanie zepsutych zadań jenkinsowych, pod kątem potencjalnych błędów w konfiguracji nie jest dużym kosztem. Dla małej firmy może okazać się to zabójcze. Zresztą tu pojawia się pojęcie DevOps, które oznacza metodykę, ale też pewien zestaw umiejętności i wiedzy. Te z natury muszą być bardzo rozległe. Obejmują znajomość narzędzi, technik programowania, zagadnień z zakresu testowania, bezpieczeństwa, ale też całą masę umiejętności miękkich potrzebnych do pracy z wieloma różnymi ludźmi począwszy od programistów, poprzez managerów, osoby ze wsparcia technicznego, a na klientach kończąc. Wierzę, że można to ogarnąć, ale nie wierzę, że ogarniesz to w przeciągu kilku tygodni czy nawet miesięcy. W dodatku jak je już opanujesz, to może okazać się, że po zmianie organizacji tylko wiedza techniczna (i ogólne umiejętności miękkie) będzie przydatna. Nowa firma, inne procesy, inne standardy.

Typowy system javowy

Spróbujmy popatrzeć na ten problem inaczej. Po pierwsze, jeżeli nasz system jest w całości napisany pod JVM-a, to możemy pokusić się o użycie natywnych dla JVM-a rozwiązań. Po drugie, jeżeli idziemy w mikroserwisy i potrafimy pociąć naszą domenę na mikroserwisy, to zazwyczaj będą one odpowiadać poszczególnym aplikacjom. Po trzecie potrafimy jako tako w CI/CD i umiemy zapewnić sobie jako taką stabilność, to zapewne efekt będzie następujący. Kilka aplikacji Spring Bootowych, które gadają za pomocą Http REST API, bo Spring ma zarówno klienta, jak i pozwala na generowanie końcówek (endpointów) REST-owych. Oczywiście to nie jest takie proste jak się wydaje. W systemach tego rodzaju możemy wyróżnić dwa rodzaje interfejsów komunikacyjnych. Pierwszy to interfejs zewnętrzny, który jest wykorzystywany przez naszych klientów. Ten prawie na pewno powinien być zestandaryzowany. Jakiś REST, może SOAP, może całość napędzane na CORBA/kę. Drugi interfejs to wewnętrzny interfejs komunikacji. Służy on do komunikacji pomiędzy mikroserwisami. Tu zazwyczaj używamy jakiegoś binarnego ustrojstwa w rodzaju gRPC albo Aerona.

I tu wchodzi JLupin…

Największą bolączką opisanego wyżej systemu jest komunikacja wewnątrz. Trzeba jakoś dogadywać mikroserwisy, udostępniać je w ramach systemu oraz monitorować ich stan. Dużo roboty, którą dobrze by było oddelegować. JLupin Next Server jest rozwiązaniem, które pomaga ogarnąć cały ten burdel. Jest niezależne od frameworków aplikacyjnych, ale out-of-box dostarcza nam wsparcie dla Springa, a to dlatego, że twórcy wyszli z założenia, że Spring jest najpopularniejszym rozwiązaniem i trzeba je wspierać by JLNS się przyjął. Co nam daje JLNS?

Po pierwsze ogarnia komunikację wewnątrz systemową. Takie rzeczy jak Service Discovery, wewnętrzna komunikacja w oparciu o szybki binarny protokół, czy nadzór nad stanem poszczególnych mikroserwisów mamy od ręki. Ponad. to mamy zapewniony pseudo hot swap, który pozwala zachować ciągłość działania aplikacji. Mamy narzędzia do monitorowania i zarządzania naszej mikroserwisowej infrastruktury. Mamy możliwość podzielenia naszego systemu na strefy, umieszczania mikroserwisów na konkretnych maszynach w określonych strefach (logicznych) oraz co za tym idzie możliwość zarządzania widocznością mikroserwisów. Do tego twórcy przygotowali plugin do mavena, który pozwala na automatyzację deploymentu oraz plugin do IntelliJ, który pozwala na ogarnięcie zależności generowanie powiązań.

Instalacja

Instalacja sprowadza się do pobrania odpowiedniej paczki i jej rozpakowania na dysku. W przypadku wersji community należy jeszcze wyczyścić zawartość katalogu application/, bo siedzi tam przykładowa aplikacja.

Uruchomienie

W katalogu start mamy skrypt start.sh, który po uruchomieniu nie daje śladów życia, ale w katalogu logs/server/main/start będzie sobie leżał plik server.out, a logs/server/main kilka wyspecjalizowanych logów.
Jeżeli w katalogu start uruchomimy skrypt control.sh, to dostaniemy się do konsoli administracyjnej naszego serwera. Tu mamy wgląd w stan naszego noda oraz możemy zarządzać mikroserwisami. Wygląda to tak:

No trochę vintage, ale zawsze lepiej niż konsola weblogica.

Community vs Enterprise

Różnice są trzy. Po pierwsze wersja community jest ograniczona do 15 mikroserwisów na node. Po drugie może być tylko jeden node. Po trzecie nie ma dostępnego narzędzia Control Center, do zarządzania nodami (bo i po co).

Dodatki

Jeżeli chcemy możemy jeszcze dociągnąć sobie plugin do IntelliJ, bo z jakiś nieznanych mi powodów nie ma go w repozytorium JetBrains. Możemy też pobrać plugin do mavena jeżeli nie chcemy łączyć się do repozytorium JLupina.

Podsumowanie

Na dziś starczy. W kolejnych wpisach pobawimy się w tworzenie prostego systemu bankowego, z wykorzystaniem mikroserwisów i JLupina.

BDD i kryptomonady

BDD, czyli Buzzword Driven Development, to rak projektów IT. Kupujemy technologię, bo ładnie brzmi, albo jest modna, a potem płaczemy jak przychodzi do utrzymania.

I o tym mówiłem na Lambda Days 🙂

A tu macie KryptomonadyW