Inleiding tot ArchUnit

1. Overzicht

In dit artikel laten we zien hoe u de architectuur van een systeem kunt controleren met ArchUnit.

2. Wat is ArchUnit?

Het verband tussen architectuurkenmerken en onderhoudbaarheid is een goed bestudeerd onderwerp in de software-industrie. Het definiëren van een degelijke architectuur voor onze systemen is echter niet voldoende. We moeten controleren of de geïmplementeerde code eraan voldoet.

Simpel gezegd, ArchUnit is een testbibliotheek waarmee we kunnen verifiëren dat een applicatie voldoet aan een bepaalde set architecturale regels. Maar wat is een architectonische regel? Sterker nog, wat bedoelen we met architectuur in deze context?

Laten we met het laatste beginnen. Hier gebruiken we de term architectuur verwijzen naarde manier waarop we de verschillende lessen in onze applicatie indelen in pakketten.

De architectuur van een systeem bepaalt ook hoe pakketten of groepen pakketten - ook wel bekend als lagen - interactie. In meer praktische termen definieert het of code in een bepaald pakket een methode kan aanroepen in een klasse die tot een andere behoort. Laten we bijvoorbeeld aannemen dat de architectuur van onze applicatie drie lagen bevat: presentatie, onderhoud, en volharding.

Een manier om te visualiseren hoe die lagen samenwerken, is door een UML-pakketdiagram te gebruiken met een pakket dat elke laag vertegenwoordigt:

Alleen al door naar dit diagram te kijken, kunnen we enkele regels bedenken:

  • Presentatieklassen mogen alleen afhankelijk zijn van serviceklassen
  • Serviceklassen mogen alleen afhankelijk zijn van persistentieklassen
  • Persistentieklassen mogen niet van iemand anders afhangen

Als we naar die regels kijken, kunnen we nu teruggaan en onze oorspronkelijke vraag beantwoorden. In deze context is een architecturale regel een bewering over de manier waarop onze applicatieklassen met elkaar omgaan.

Dus nu, hoe controleren we of onze implementatie aan die regels voldoet? Hier is waar ArchUnit komt binnen. Het stelt ons in staat om onze architectonische beperkingen uit te drukken met behulp van een vloeiende API en valideer ze naast andere tests tijdens een normale build.

3. ArchUnit Project instellen

ArchUnit integreert mooi met de JUnit testraamwerk, en dus worden ze meestal samen gebruikt. Het enige wat we hoeven te doen is het archunit-junit4 afhankelijkheid die past bij onze JUnit versie:

 com.tngtech.archunit archunit-junit4 0.14.1 test 

Zoals het artefact-id impliceert dat deze afhankelijkheid specifiek is voor de JUnit 4 kader.

Er is ook een archunit-junit5 afhankelijkheid als we JUnit 5:

 com.tngtech.archunit archunit-junit5 0.14.1 test 

4. Schrijven ArchUnit Tests

Zodra we de juiste afhankelijkheid aan ons project hebben toegevoegd, gaan we beginnen met het schrijven van onze architectuurtests. Onze testapplicatie zal een eenvoudige SpringBoot REST-applicatie zijn die Smurfen bevraagt. Voor de eenvoud bevat deze testapplicatie alleen de Controller, Onderhoud, en Opslagplaats klassen.

We willen controleren of deze applicatie voldoet aan de regels die we eerder hebben genoemd. Laten we dus beginnen met een eenvoudige test voor de regel "presentatieklassen mogen alleen afhangen van serviceklassen".

4.1. Onze eerste test

De eerste stap is het maken van een set Java-klassen die worden gecontroleerd op overtredingen van de regels. Dit doen we door het instantiëren van het ClassFileImporter class en vervolgens een van zijn importXXX () methoden:

JavaClasses jc = nieuwe ClassFileImporter () .importPackages ("com.baeldung.archunit.smurfs");

In dit geval is het JavaClasses instantie bevat alle klassen van ons hoofdtoepassingspakket en zijn subpakketten. We kunnen dit object beschouwen als analoog aan een typisch proefpersoon die wordt gebruikt in reguliere unit-tests, aangezien het het doelwit zal zijn voor regelevaluaties.

Architecturale regels gebruiken een van de statische methoden uit de ArchRuleDefinition klasse als uitgangspunt voor zijn vloeiende API oproepen. Laten we proberen de eerste regel die hierboven is gedefinieerd te implementeren met behulp van deze API. We gebruiken de klassen() methode als ons anker en voeg vanaf daar aanvullende beperkingen toe:

ArchRule r1 = klassen () .that (). ResideInAPackage (".. presentatie ..") .should (). OnlyDependOnClassesThat () .resideInAPackage (".. service .."); r1.check (jc);

Merk op dat we het controleren() methode van de regel die we hebben gemaakt om de controle uit te voeren. Deze methode duurt een JavaClasses object en zal een uitzondering genereren als er een overtreding is.

Dit ziet er allemaal goed uit, maar we krijgen een lijst met fouten als we proberen het tegen onze code uit te voeren:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Regel 'klassen die zich in een pakket bevinden' ..presentation .. 'zouden alleen afhankelijk moeten zijn van klassen die zich in een pakket' ..service .. '' bevinden is geschonden ( 6 keer): ... foutenlijst weggelaten 

Waarom? Het grootste probleem met deze regel is de onlyDependsOnClassesThat (). Ondanks wat we in het pakketdiagram hebben gestopt, onze daadwerkelijke implementatie heeft afhankelijkheden van JVM- en Spring-frameworkklassen, vandaar de fout.

4.2. Onze eerste test herschrijven

Een manier om deze fout op te lossen, is door een clausule toe te voegen die rekening houdt met die extra afhankelijkheden:

ArchRule r1 = klassen () .that (). ResideInAPackage (".. presentatie ..") .should (). OnlyDependOnClassesThat () .resideInAPackage (".. service ..", "java ..", "javax .. "," org.springframework .. "); 

Met deze wijziging zal onze controle niet meer mislukken. Deze aanpak heeft echter te kampen met onderhoudsproblemen en voelt een beetje hacky aan. We kunnen voorkomen dat deze problemen onze regel herschrijven met behulp van de geen lessen() statische methode als ons uitgangspunt:

ArchRule r1 = noClasses () .that (). ResideInAPackage (".. presentatie ..") .should (). DependOnClassesThat () .resideInAPackage (".. persistentie .."); 

Natuurlijk kunnen we er ook op wijzen dat deze benadering dat is op ontkennen gebaseerd in plaats van de op basis van toestemming een die we eerder hadden. Het cruciale punt is dat welke benadering we ook kiezen, ArchUnit zal gewoonlijk flexibel genoeg zijn om onze regels uit te drukken.

5. Gebruik de BibliotheekAPI

ArchUnit maakt het creëren van complexe architecturale regels een gemakkelijke taak dankzij de ingebouwde regels. Die kunnen op hun beurt ook worden gecombineerd, waardoor we regels kunnen maken met een hoger abstractieniveau. Uit de doos, ArchUnit biedt de Bibliotheek API, een verzameling voorverpakte regels die een oplossing bieden voor veelvoorkomende architectuurproblemen:

  • Architecturen: Ondersteuning voor regelcontroles voor gelaagde en ui (ook bekend als zeshoekige of "poorten en adapters") architecturen
  • Plakjes: Wordt gebruikt om cirkelvormige afhankelijkheden of ‘cycli’ te detecteren
  • Algemeen: Verzameling van regels met betrekking tot beste coderingspraktijken zoals logboekregistratie, gebruik van uitzonderingen, enz.
  • PlantUML: Controleert of onze codebase voldoet aan een bepaald UML-model
  • Freeze Arch-regels: Schendingen opslaan voor later gebruik, zodat u alleen nieuwe kunt melden. Bijzonder handig om technische schulden te beheren

Het dekken van al deze regels valt buiten het bereik van deze inleiding, maar laten we eens kijken naar het Architectuur rule pakket. Laten we in het bijzonder de regels in de vorige sectie herschrijven met behulp van de gelaagde architectuurregels. Het gebruik van deze regels vereist twee stappen: eerst definiëren we de lagen van onze applicatie. Vervolgens bepalen we welke laagtoegang is toegestaan:

LayeredArchitecture arch = layeredArchitecture () // Definieer lagen .layer ("Presentation"). DefinedBy (".. presentatie ..") .layer ("Service"). DefinedBy (".. service ..") .layer (" Persistence "). DefinedBy (" .. persistence .. ") // Beperkingen toevoegen .whereLayer (" Presentation "). MayNotBeAccessedByAnyLayer () .whereLayer (" Service "). MayOnlyBeAccessedByLayers (" Presentation ") .whereLayer (" Persistence ") .mayOnlyBeAccessedByLayers ("Service"); arch.check (jc);

Hier, gelaagde Architectuur () is een statische methode van de Architecturen klasse. Wanneer het wordt aangeroepen, retourneert het een nieuw Gelaagde architectuur object, dat we vervolgens gebruiken om namenlagen en beweringen met betrekking tot hun afhankelijkheden te definiëren. Dit object implementeert het ArchRule interface zodat we deze net als elke andere regel kunnen gebruiken.

Het leuke van deze specifieke API is dat het ons in staat stelt om in slechts een paar regels coderegels te maken waarvoor we anders meerdere individuele regels zouden moeten combineren.

6. Conclusie

In dit artikel hebben we de basisprincipes van het gebruik van ArchUnit in onze projecten. Het adopteren van deze tool is een relatief eenvoudige taak die een positieve invloed kan hebben op de algehele kwaliteit en op de lange termijn de onderhoudskosten kan verlagen.

Zoals gewoonlijk is alle code beschikbaar op GitHub.