În ultimii ani, arhitecturile software au evoluat rapid, reflectând nevoile în schimbare ale dezvoltatorilor de soft și ale mediului de afaceri. Cu toate că trecerea de la monolitele tradiționale la microservicii a adus numeroase beneficii, susținătorii lor spun că utilizarea acestora implică un cost semnificativ, ele fiind utile doar în contextul unui sistem foarte complex.
În mod tradițional, "application frameworks" au oferit îndrumări structurale, cum ar fi adnotările din Spring Framework (@Controller, @Service, @Repository). Dar, în ultimul timp, s-a făcut trecerea la o paradigmă în care este recomandat ca structura codului să fie cât mai aliniată cu domeniul problemei pe care aplicația o rezolvă, observându-se că acest lucru duce la aplicații mai bine structurate, care sunt mai ușor de înțeles și de întreținut.
Sistemele bazate pe microservicii au devenit omniprezente în ultimii ani. Cu toate acestea, sistemele monolitice, modulare și-au recăpătat popularitatea recent.
"You shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile." (Martin Fowler, 2015).
În acest context, conceptul de "modular monoliths" a apărut ca o soluție intermediară care combină simplitatea și coerența monolitului cu structura și organizarea modulară. În acest articol, vom explora ceea ce este un monolit modular, avantaje și dezavantaje. Totodată, vom aborda scenariile în care această arhitectură poate fi considerată optimă.
Un monolit modular este o abordare arhitecturală în care o aplicație este dezvoltată ca un singur artefact, dar este împărțită în module distincte și autonome, menținând în același timp un singur cod sursă și un singur "deployment". Fiecare modul are responsabilități clare, fiind dezvoltat, testat și menținut independent de celelalte module. Aceasta permite echipelor să păstreze avantajele unui monolit tradițional, cum ar fi simplitatea gestionării versiunilor, în timp ce beneficiază de o structură modulară care facilitează organizarea, scalabilitatea și mentenabilitatea aplicației.
Separarea responsabilităților: Fiecare modul are o responsabilitate bine definită și este izolat de celelalte module.
Interfețe bine definite: Modulele comunică între ele prin interfețe clar definite, reducând astfel dependențele strânse.
Dezvoltare independentă: Fiecare modul poate fi dezvoltat și testat separat, permițând echipelor să lucreze în paralel fără a interfera una cu cealaltă.
Monolitele modulare permit instalarea într-un mediu de producție a unui singur artefact, reducând astfel complexitatea infrastructurii. Acest lucru este benefic în special pentru echipele mici sau pentru organizațiile care nu doresc să gestioneze complexitatea unei arhitecturi de microservicii.
Gestionarea unui singur cod sursă facilitează aplicarea actualizărilor și menținerea coerenței. Dat fiind că toate modulele sunt parte a aceleași aplicații, sincronizarea și compatibilitatea între module sunt garantate.
Monolitele modulare pot oferi performanțe superioare, evitând latențele de rețea asociate cu comunicarea între microservicii. De asemenea, coerența datelor este mai ușor de asigurat într-o arhitectură monolitică.
Dependențele între module sunt mai ușor de gestionat, deoarece toate sunt parte a aceluiaşi artefact. Acest lucru reduce riscul de incompatibilități și facilitează refactorizarea.
Deși monolitele modulare oferă un grad de scalabilitate, ele nu pot atinge flexibilitatea și granularitatea oferite de microservicii. Pentru aplicațiile care necesită scalabilitate independentă a componentelor, monolitul modular poate deveni o constrângere.
Pe măsură ce aplicația crește, gestionarea unei baze mari de cod poate deveni complicată. Necesită o disciplină strictă în dezvoltare și o structură clar definită a modulelor pentru a evita "codul spaghetti".
Toate modulele dintr-un monolit modular trebuie să fie dezvoltate folosind aceleași tehnologii și limbaje de programare, ceea ce poate limita flexibilitatea echipelor de a alege cele mai potrivite instrumente pentru fiecare componentă.
Deși modular monoliths pot reduce timpul de dezvoltare inițial, pe termen lung, gestionarea și implementarea actualizărilor poate deveni mai consumatoare de timp, mai ales dacă modulele nu sunt bine izolate.
Pentru aplicațiile de dimensiuni mici sau medii, unde complexitatea și volumul de cod nu justifică complexitatea infrastructurii necesare pentru a avea microservicii, monolitul modular este o alegere ideală. Aceasta permite o dezvoltare rapidă și o desfășurare simplificată.
Echipele de dezvoltare mici pot beneficia de pe urma monolitului modular, deoarece simplifică coordonarea și colaborarea. Evitând necesitatea gestionării unui ecosistem distribuit, echipele pot lucra mai eficient.
În fazele incipiente ale dezvoltării unui produs, unde cerințele sunt în continuă schimbare, monolitul modular permite o adaptare rapidă și iterativă, fără a introduce complexitatea suplimentară a microserviciilor. Acesta oferă o bază solidă care poate fi ulterior scalată și refactorizată pe măsură ce aplicația evoluează.
Pentru aplicațiile care necesită performanțe înalte și o latență redusă, monolitul modular poate oferi o soluție mai eficientă în comparație cu arhitecturile distribuite. Latențele de rețea sunt reduse, iar coerența datelor este mai ușor de gestionat într-o singură aplicație.
Spring Modulith este un proiect de la Spring, în opinia mea al doilea ca importanță după Spring Boot, care sprijină dezvoltatorii de programe în construirea aplicațiilor monolitice modulare. Acesta ajută la structurarea codului în jurul unor domenii, făcându-l mai ușor de gestionat și evoluat.
Putem considera modulele de domeniu sau de business ale aplicației noastre ca sub-pachete directe ale pachetului principal al aplicației. Într-o aplicație Spring Boot, un modul este o unitate de funcționalitate care constă în următoarele părți:
Un API expus altor module, implementat prin instanțe de beanuri Spring sau evenimente publicate de către modul.
Componente interne de implementare care nu ar trebui să fie accesate de alte module.
Putem folosi metoda verify() din ApplicationModules pentru a identifica dacă modularitatea codului nostru respectă constrângerile intenționate. Spring Modulith utilizează proiectul ArchUnit pentru a oferi această capacitate.
@Test
void verifiesModularStructure() {
ApplicationModules modules = ApplicationModules.of(Application.class);
modules.verify();
}
Putem documenta relațiile dintre modulele unui proiect. Spring Modulith oferă generarea de diagrame bazate pe PlantUML.
@Test
void createModuleDocumentation() {
ApplicationModules modules = ApplicationModules.of(Application.class);
new Documenter(modules)
.writeDocumentation()
.writeIndividualModulesAsPlantUml();
}
Monolitele modulare reprezintă o abordare echilibrată în arhitectura software, combinând simplitatea și eficiența monolitului tradițional cu beneficiile organizatorice ale microserviciilor. În contextul potrivit, ele pot oferi o soluție optimă pentru dezvoltarea și scalarea aplicațiilor, permițând echipelor să se concentreze pe livrarea rapidă a funcționalităților fără a sacrifica performanța sau flexibilitatea internă. Deși nu sunt o soluție universală, monolitele modulare merită luate în considerare pentru proiectele care se încadrează în scenariile discutate, oferind un compromis viabil între structura monolitică și complexitatea microserviciilor.
Această abordare acordă echipelor de dezvoltare o modalitate eficientă de a gestiona complexitatea aplicațiilor pe măsură ce acestea evoluează, asigurând în același timp că performanța și întreținerea rămân gestionabile. Monolitele modulare reprezintă, așadar, un pas important în evoluția arhitecturilor software, oferind un echilibru binevenit între tradiție și inovație.
Referințe
"Modular Monolith: A Primer", Simon Brown, 2019.
Microservices For Greenfield? , Sam Newman, 2015
"MonolithFirst", Martin Fowler, 2015.
de Mihai Darie
de Vlad Petrean