Introduktion til ArchUnit

1. Oversigt

I denne artikel viser vi, hvordan man kontrollerer arkitekturen i et system ved hjælp af ArchUnit.

2. Hvad er? ArchUnit?

Forbindelsen mellem arkitekturegenskaber og vedligeholdelsesevne er et velstuderet emne i softwareindustrien. At definere en lydarkitektur til vores systemer er dog ikke nok. Vi skal kontrollere, at den implementerede kode overholder den.

Kort fortalt, ArchUnit er et testbibliotek, der giver os mulighed for at kontrollere, at en applikation overholder et givet sæt arkitektoniske regler. Men hvad er en arkitektonisk regel? Endnu mere, hvad mener vi med arkitektur i denne sammenhæng?

Lad os starte med sidstnævnte. Her bruger vi udtrykket arkitektur at referere tilden måde, hvorpå vi organiserer de forskellige klasser i vores ansøgning i pakker.

Arkitekturen i et system definerer også, hvordan pakker eller grupper af pakker - også kendt som lag - interagere. Mere praktisk definerer det, om kode i en given pakke kan kalde en metode i en klasse, der tilhører en anden. Lad os for eksempel antage, at vores applikations arkitektur indeholder tre lag: præsentation, serviceog udholdenhed.

En måde at visualisere, hvordan disse lag interagerer, er ved at bruge et UML-pakkediagram med en pakke, der repræsenterer hvert lag:

Bare ved at se på dette diagram kan vi finde ud af nogle regler:

  • Præsentationskurser bør kun afhænge af serviceklasser
  • Serviceklasser bør kun afhænge af persistensklasser
  • Persistensklasser bør ikke afhænge af nogen anden

Når vi ser på disse regler, kan vi nu gå tilbage og besvare vores originale spørgsmål. I denne sammenhæng er en arkitektonisk regel en påstand om, hvordan vores applikationsklasser interagerer med hinanden.

Så nu, hvordan kontrollerer vi, at vores implementering overholder disse regler? Her er hvor ArchUnit kommer ind. Det giver os mulighed for at udtrykke vores arkitektoniske begrænsninger ved hjælp af en flydende API og valider dem sammen med andre tests under en regelmæssig build.

3. ArchUnit Projektopsætning

ArchUnit integreres pænt med JUnit test framework, og så bruges de typisk sammen. Alt, hvad vi skal gøre, er at tilføje archunit-junit4 afhængighed til at matche vores JUnit version:

 com.tngtech.archunit archunit-junit4 0.14.1 test 

Som dens artefaktId indebærer, at denne afhængighed er specifik for JUnit 4 ramme.

Der er også en archunit-junit5 afhængighed, hvis vi bruger JUnit 5:

 com.tngtech.archunit archunit-junit5 0.14.1 test 

4. Skrivning ArchUnit Test

Når vi først har tilføjet den relevante afhængighed af vores projekt, skal vi begynde at skrive vores arkitekturtests. Vores testapplikation vil være en simpel SpringBoot REST-applikation, der forespørger på Smølfer. For enkelheds skyld indeholder denne testapplikation kun Controller, Service, og Datalager klasser.

Vi vil kontrollere, at denne applikation overholder de regler, vi har nævnt før. Så lad os starte med en simpel test for "præsentationsklasser skal kun afhænge af serviceklasser" -reglen.

4.1. Vores første test

Det første trin er at oprette et sæt Java-klasser, der kontrolleres for overtrædelse af regler. Vi gør dette ved at instantiere ClassFileImporter klasse og derefter bruge en af ​​dens importXXX () metoder:

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

I dette tilfælde er JavaClasses forekomst indeholder alle klasser fra vores vigtigste applikationspakke og dens underpakker. Vi kan tænke på dette objekt som analogt med et typisk testperson, der bruges i regelmæssige enhedstest, da det vil være målet for regelevalueringer.

Arkitektoniske regler bruger en af ​​de statiske metoder fra ArchRuleDefinition klasse som udgangspunkt for dens flydende API opkald. Lad os prøve at implementere den første regel, der er defineret ovenfor ved hjælp af denne API. Vi bruger klasser () metode som vores anker og tilføj yderligere begrænsninger derfra:

ArchRule r1 = klasser () .that (). ResideInAPackage (".. præsentation .."). Skal (). OnlyDependOnClassesThat () .resideInAPackage (".. service .."); r1.check (jc);

Bemærk, at vi skal ringe til kontrollere() metode til den regel, vi har oprettet for at køre kontrollen. Denne metode tager en JavaClasses objekt og kaster en undtagelse, hvis der er en overtrædelse.

Alt dette ser godt ud, men vi får en liste over fejl, hvis vi prøver at køre det mod vores kode:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Regel 'klasser, der ligger i en pakke' ..præsentation .. 'bør kun afhænge af klasser, der er i en pakke' ..service .. '' blev overtrådt ( 6 gange): ... fejlliste udeladt 

Hvorfor? Det største problem med denne regel er onlyDependsOnClassesThat (). På trods af hvad vi har sat i pakkediagrammet, vores faktiske implementering har afhængigheder af JVM- og Spring-rammeklasser, deraf fejlen.

4.2. Omskrivning af vores første test

En måde at løse denne fejl på er at tilføje en klausul, der tager højde for disse yderligere afhængigheder:

ArchRule r1 = klasser () .that (). ResideInAPackage (".. præsentation .."). Skal (). OnlyDependOnClassesThat () .resideInAPackage (".. service ..", "java ..", "javax .. "," org.springframework .. "); 

Med denne ændring holder vores check op med at mislykkes. Denne tilgang lider imidlertid under vedligeholdelsesproblemer og føles lidt hacky. Vi kan undgå disse problemer med at omskrive vores regel ved hjælp af noClasses () statisk metode som udgangspunkt:

ArchRule r1 = noClasses () .that (). ResideInAPackage (".. præsentation .."). Skal (). DependOnClassesThat () .resideInAPackage (".. persistens .."); 

Naturligvis kan vi også påpege, at denne tilgang er benægte-baseret i stedet for tilladelsesbaseret en vi havde før. Det kritiske punkt er, at uanset hvilken tilgang vi vælger, ArchUnit vil normalt være fleksible nok til at udtrykke vores regler.

5. Brug af BibliotekAPI

ArchUnit gør oprettelsen af ​​komplekse arkitektoniske regler til en let opgave takket være de indbyggede regler. Disse kan igen også kombineres, så vi kan skabe regler ved hjælp af et højere abstraktionsniveau. Ud af boksen, ArchUnit tilbyder Bibliotek API, en samling af færdigpakkede regler, der adresserer almindelige arkitektoniske bekymringer:

  • Arkitekturer: Understøttelse af lag og løg (aka sekskantede eller "porte og adaptere") arkitekturkontrol
  • Skiver: Bruges til at registrere cirkulære afhængigheder eller "cyklusser"
  • Generel: Indsamling af regler relateret til bedste kodningspraksis såsom logning, brug af undtagelser osv.
  • PlantUML: Kontrollerer, om vores kodebase overholder en given UML-model
  • Frys bueregler: Gem overtrædelser til senere brug, og tillad kun at rapportere nye. Særligt nyttigt til styring af teknisk gæld

Dækning af alle disse regler er uden for denne introduktion, men lad os se på Arkitektur regelpakke. Lad os især omskrive reglerne i det foregående afsnit ved hjælp af de lagdelte arkitekturregler. Brug af disse regler kræver to trin: først definerer vi lagene i vores applikation. Derefter definerer vi, hvilke lagadgang der er tilladt:

LayeredArchitecture arch = layeredArchitecture () // Definer lag .layer ("Præsentation"). DefinedBy (".. præsentation ..") .layer ("Service"). DefinedBy (".. service ..") .layer (" Persistence "). DefinedBy (" .. persistence .. ") // Tilføj begrænsninger .whereLayer (" Præsentation "). MayNotBeAccessedByAnyLayer () .whereLayer (" Service "). MayOnlyBeAccessedByLayers (" Præsentation ") .whereLayer (" Persistence ") .mayOnlyBeAccessedByLayers ("Service"); arch.check (jc);

Her, layeredArchitecture () er en statisk metode fra Arkitekturer klasse. Når den påberåbes, returnerer den en ny LayeredArchitecture objekt, som vi derefter bruger til at definere navnelag og påstande om deres afhængighed. Dette objekt implementerer ArchRule interface, så vi kan bruge det ligesom enhver anden regel.

Den seje ting ved denne specielle API er, at den giver os mulighed for at oprette på blot et par linjer med kode regler, der ellers ville kræve, at vi kombinerer flere individuelle regler.

6. Konklusion

I denne artikel har vi undersøgt det grundlæggende ved brug ArchUnit i vores projekter. Vedtagelse af dette værktøj er en relativt enkel opgave, der kan have en positiv indflydelse på den overordnede kvalitet og reducere vedligeholdelsesomkostningerne på lang sigt.

Som sædvanlig er al kode tilgængelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found