Raczej żaden z deweloperów nie lubi pisać dokumentacji. Jest to coś do czego od zawsze nie pałaliśmy sympatią. Sam byłem w takiej sytuacji, gdzie na projekcie tworzenie dokumentacji technicznej polegało na tępym kopiowaniu i wklejaniu kodu do plików Word. Był to jeden z wielu powodów, który spowodował, że już tam nie pracuję (pomimo możliwości codziennego grania w piłkarzyki 😅).
Dlaczego o tym wspominam na wstępie? Ponieważ tworzenie dokumentacji wcale nie musi być monotonne, ciężkie i nudne. Niekoniecznie musimy pisać te nudne ściany tekstu, których i tak nikt nie czyta. W sumie, kto powiedział, że dokumentacja musi być samym tekstem? Przecież możemy tworzyć ciekawe diagramy zależności wynikające z naszego kodu. Właśnie tutaj z pomocą przychodzi nam Modulith. Gdy przy jego pomocy stworzymy modularny monolit to za darmo dostaniemy automatycznie wygenerowaną dokumentację. W jaki sposób? Zapraszam do dalszej części artykułu.
W tym artykule, jak i poprzednich, dalej korzystam ze Spring Modulith w wersji 0.3.0. Oczywiście nic się nie zmieniło i ta wersja jest ciągle w fazie experimental. Gdzieś słyszałem, że podobno można z niej korzystać na produkcji, ale dalej nie jest częścią Spring Boot. Moim zdaniem lepiej jest się z nią jeszcze wstrzymać.
Przypadek użycia
Zacznijmy od zdefiniowania sytuacji, dla której chcielibyśmy stworzyć diagram powiązań pomiędzy modułami. To on finalnie będzie naszą dokumentacją. Na potrzebę tego wpisu wymyśliłem następujący schemat reprezentujący obecny system.
Schemat komunikacji pomiędzy modułami w obecnym systemie
Pomarańczowe kółka są odzwierciedleniem modułów znajdujących się w aplikacji. Strzałki natomiast to relacje pomiędzy nimi. Czarne określają bezpośrednią zależność/wywołanie, natomiast czerwone przedstawiają połączenie poprzez zdarzenia, o których więcej była mowa w poprzednim wpisie.
Dla lepszego rozeznania zajrzyjmy jeszcze do wnętrza jednego z modułów. Ujrzymy tam jak może wyglądać powiązanie pomiędzy komponentami poszczególnych modułów.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.csanecki.modulith.dzeta;
import io.csanecki.modulith.epsilon.EpsilonComponent;
import io.csanecki.modulith.gamma.GammaEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class DzetaComponent {
private static final String DZETA = "dzeta";
private final EpsilonComponent epsilonComponent;
public DzetaComponent(EpsilonComponent epsilonComponent) {
this.epsilonComponent = epsilonComponent;
}
@EventListener
public void handle(GammaEvent event) {
System.out.println("caught gamma event in " + DZETA);
}
public void callAll() {
epsilonComponent.doSomething(DZETA);
}
public void doSomething(String component) {
System.out.println("calling dzeta from " + component);
}
}
Istniejące metody tak naprawdę nie mają żadnego znaczenia. Najważniejsze są zależności do innych modułów (np. pola klasy czy obsługiwane zdarzenia). W tym przypadku będzie to odwołanie do EpsilonComponent
oraz nasłuchiwanie na event GammaEvent
. Dobra, pora wziąć się do działania i stworzyć pierwszy diagram naszej dokumentacji.
Pierwszy przykład dokumentacji w Modulith
Przeglądając witrynę Modulith natrafimy na krótki kod, który pomoże nam wygenerować upragnioną dokumentację. Skorzystamy z niego w jednym z naszych testów. Będzie to wyglądało następująco.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package io.csanecki.modulith;
import org.junit.jupiter.api.Test;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.docs.Documenter;
class ModulithDocumentationTest {
ApplicationModules modules = ApplicationModules.of(
ModulithDocDemoApplication.class);
@Test
void createDocumentation() {
new Documenter(modules)
.writeModulesAsPlantUml()
.writeIndividualModulesAsPlantUml();
}
}
W ten sposób uzyskujemy diagram dla wszystkich naszych modułów jako całości oraz dla każdego indywidualnie. Odpowiedzialne za to są metody writeModulesAsPlantUml
oraz writeIndividualModulesAsPlantUml
klasy Documenter
. Musimy tylko wskazać dla jakiej aplikacji mają być odczytane moduły. Robimy to przez podanie do ApplicationModules
naszej głównej klasy projektu ModulithDocDemoApplication
(klasa rozruchowa Spring Boota).
Po uruchomieniu testu wystarczy wejść do katalogu target/spring-modulith-docs
. W nim znajdziemy pliku o rozszerzeniu .puml
. Ten o nazwie components
będzie zawierał wszystkie moduły aplikacji wraz z ich powiązaniami. Wystarczy otworzyć ten plik w odpowiednim eksploratorze. Ja wykorzystałem do tego plantuml.com i otrzymałem taki diagram.
Wygenerowany diagram dokumentujący relacje pomiędzy modułami
Wygląda identycznie jak nasz schematyczny rysunek z początku artykułu. Z tą różnicą, że powstał on bez żadnej naszej ingerencji twórczej. Teraz każda zmiana w relacjach pomiędzy modułami zostanie odzwierciedlona natychmiast. Zajrzyjmy jeszcze do diagramu jednego z modułów. Weźmy na tapet ‘dzeta’.
Wygenerowany diagram dla modułu ‘dzeta’
Jest to nic innego jak wycinek diagramu reprezentującego całą aplikację. Widzimy tutaj dokładnie od czego zależy wybrany moduł. Przy okazji pokazywane są również powiązania pomiędzy zależnymi modułami. Nie mamy natomiast poglądu na to jakie moduły zależą od tego wybranego na schemacie. W naszym przypadku byłby to ‘alfa’ zależny od ‘dzeta’.
Stary, dobry UML…
Gdyby jednak ktoś zamiast PlantUML chciał skorzystać z szaty graficznej tradycyjnego UML to istnieje taka możliwość. Należałoby w takim przypadku utworzyć następujący test.
1
2
3
4
5
6
7
8
@Test
void createOldStyleDocumentation() {
DiagramOptions oldStyleUml = DiagramOptions.defaults()
.withStyle(DiagramStyle.UML);
new Documenter(modules)
.writeModulesAsPlantUml(oldStyleUml)
.writeIndividualModulesAsPlantUml(oldStyleUml);
}
Czyli musimy podać wybraną opcje dla diagramu, aby nie korzystać z domyślnie ustawionych. Wybieramy więc styl UML i podajemy go do wcześniej opisanych metod. Voila! Poniżej mamy diagramy z inną szatą graficzną.
Wygenerowany diagram dokumentujący relacje pomiędzy modułami - klasyczny UML
Wygenerowany diagram dla modułu ‘dzeta’ - klasyczny UML
Tworzenie Module Canvases
Na koniec zobaczymy jeszcze jak możemy utworzyć canvas w adoc
. Jest to również banalnie proste jak wcześniejsze przypadki. Wystarczy oczywiście napisać kolejny test.
1
2
3
4
5
@Test
void createDocumentationCanvas() {
new Documenter(modules)
.writeModuleCanvases();
}
I tyle. Wskazujemy, że chcemy wygenerować canvas poprzez metodę writeModuleCanvases
klasy Documenter
. Utworzą się one tylko dla modułów. Nie ma tutaj całościowego podglądu. Oczywiście wszystkie z nich znajdą się w katalogu target/spring-modulith-docs
.
[%autowidth.stretch, cols="h,a"]
|===
|Base package
|`io.csanecki.modulith.dzeta`
|Spring components
|_Event listeners_
* `i.c.m.d.DzetaComponent` listening to `i.c.m.g.GammaEvent`
|Bean references
|* `i.c.m.e.EpsilonComponent` (in Epsilon)
|Events listened to
|* `i.c.m.g.GammaEvent`
|===
Tak wygląda zawartość pliku dla modułu ‘dzeta’. Nie jest to za bardzo czytelne dla ludzkiego oka. Wystarczy jednak skopiować dany tekst do dowolnego edytora AsciiDoc
, aby uzyskać lepszy obraz. Polecam do tego, w celach testowych, tutorialspoint.com.
Wygenerowany model canvas dla modułu ‘dzeta’
Zawarta jest tutaj lista zależności naszego modułu. Widać zależność do zdarzenia GammaEvent
oraz do beana EpsilonComponent
.
Podsumowanie
Dzisiejszy wpis jest o wiele krótszy od pozostałych. Myślę natomiast, że jest on na równi z nimi jeśli chodzi o wartość. W projekcie, na którym właśnie pracuję, dokumentacja wytwarzana właśnie w ten sposób bardzo by nam pomogła. Chociaż, aby tak się stało, trzeba by dostosować swoje moduły do restrykcji stawianych przez Modulith. Co za pewne nie jest trywialną sprawą dla projektów legacy…
Mam nadzieję, że artykuł pokazał kolejną ciekawą funkcjonalność biblioteki Modulith. Liczę na to, że ten projekt dalej będzie rozwijany w ten sposób i niedługo zobaczymy go w Spring Boot w wersji 1.0.0.
Zapraszam Cię teraz do następnego wpisu, w którym chciałbym się skupić na metrykach i tym samym będę zamykał tą krótką serię o Modulith.
Link do GitHub: https://github.com/cezarysanecki/code-from-blog