Introduksjon til ArchUnit

1. Oversikt

I denne artikkelen viser vi hvordan du kan kontrollere arkitekturen til et system ved hjelp av ArchUnit.

2. Hva er? ArchUnit?

Koblingen mellom arkitekturegenskaper og vedlikeholdsevne er et godt studert tema i programvareindustrien. Å definere en lydarkitektur for systemene våre er ikke nok. Vi må verifisere at den implementerte koden følger den.

For å si det enkelt, ArchUnit er et testbibliotek som lar oss bekrefte at en applikasjon overholder et gitt sett med arkitektoniske regler. Men hva er en arkitektonisk regel? Enda mer, hva mener vi med arkitektur i denne sammenhengen?

La oss starte med sistnevnte. Her bruker vi begrepet arkitektur å referere tilmåten vi organiserer de forskjellige klassene i søknaden vår på i pakker.

Arkitekturen til et system definerer også hvordan pakker eller grupper av pakker - også kjent som lag - samhandle. I mer praktiske termer definerer den om kode i en gitt pakke kan kalle en metode i en klasse som tilhører en annen. La oss for eksempel anta at programmets arkitektur inneholder tre lag: presentasjon, service, og standhaftighet.

En måte å visualisere hvordan disse lagene samhandler på er ved å bruke et UML-pakkediagram med en pakke som representerer hvert lag:

Bare ved å se på dette diagrammet, kan vi finne ut noen regler:

  • Presentasjonskurs bør bare avhenge av serviceklasser
  • Serviceklasser bør bare avhenge av utholdenhetsklasser
  • Utholdenhetskurs bør ikke avhenge av noen andre

Når vi ser på disse reglene, kan vi nå gå tilbake og svare på det opprinnelige spørsmålet vårt. I denne sammenhengen er en arkitektonisk regel en påstand om måten applikasjonsklassene våre samhandler med hverandre.

Så nå, hvordan sjekker vi at implementeringen vår overholder disse reglene? Her er hvor ArchUnit kommer inn. Det lar oss uttrykke våre arkitektoniske begrensninger ved hjelp av en flytende API og validere dem sammen med andre tester under en vanlig bygging.

3. ArchUnit Prosjektoppsett

ArchUnit integreres pent med JUnit testrammeverk, og så brukes de vanligvis sammen. Alt vi trenger å gjøre er å legge til archunit-junit4 avhengighet for å matche vår JUnit versjon:

 com.tngtech.archunit archunit-junit4 0.14.1 test 

Som sin artefaktId antyder, er denne avhengigheten spesifikk for JUnit 4 rammeverk.

Det er også en archunit-junit5 avhengighet hvis vi bruker JUnit 5:

 com.tngtech.archunit archunit-junit5 0.14.1 test 

4. Skrift ArchUnit Tester

Når vi har lagt til riktig avhengighet til prosjektet vårt, la oss begynne å skrive våre arkitekturtester. Testapplikasjonen vår vil være en enkel SpringBoot REST-applikasjon som spør etter Smurfer. For enkelhets skyld inneholder denne testapplikasjonen bare Kontroller, Service, og Oppbevaringssted klasser.

Vi ønsker å bekrefte at dette programmet samsvarer med reglene vi har nevnt før. Så la oss starte med en enkel test for "presentasjonsklasser bør bare avhenge av serviceklasser" -regelen.

4.1. Vår første test

Det første trinnet er å lage et sett med Java-klasser som vil bli sjekket for brudd på regler. Vi gjør dette ved å sette i gang ClassFileImporter klasse og deretter bruke en av dens importXXX () metoder:

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

I dette tilfellet JavaClasses forekomst inneholder alle klasser fra vår viktigste applikasjonspakke og dens underpakker. Vi kan tenke på dette objektet som analogt med et typisk testperson som brukes i vanlige enhetstester, da det vil være målet for regelevalueringer.

Arkitektoniske regler bruker en av de statiske metodene fra ArchRuleDefinition klasse som utgangspunkt for sin flytende API ringer. La oss prøve å implementere den første regelen som er definert ovenfor ved hjelp av dette API. Vi bruker klasser () metode som vårt anker og legg til flere begrensninger derfra:

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

Legg merke til at vi trenger å ringe Sjekk() metoden for regelen vi har laget for å kjøre sjekken. Denne metoden tar en JavaClasses objekt og vil kaste et unntak hvis det er et brudd.

Alt dette ser bra ut, men vi får en liste over feil hvis vi prøver å kjøre det mot koden vår:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Regel 'klasser som ligger i en pakke' ..presentasjon .. 'skal bare avhenge av klasser som ligger i en pakke' ..service .. '' ble krenket ( 6 ganger): ... feilliste utelatt 

Hvorfor? Hovedproblemet med denne regelen er onlyDependsOnClassesThat (). Til tross for det vi har lagt inn i pakkediagrammet, vår faktiske implementering har avhengighet av JVM og Spring rammeklasser, derav feilen.

4.2. Omskriving av vår første test

En måte å løse denne feilen på er å legge til en klausul som tar hensyn til de ekstra avhengighetene:

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

Med denne endringen vil sjekken slutte å mislykkes. Denne tilnærmingen lider imidlertid av problemer med vedlikehold og føles litt hacky. Vi kan unngå at disse problemene omskriver regelen vår ved hjelp av noClasses () statisk metode som utgangspunkt:

ArchRule r1 = noClasses () .that (). ResideInAPackage (".. presentation ..") .should (). DependOnClassesThat () .resideInAPackage (".. persistence .."); 

Selvfølgelig kan vi også påpeke at denne tilnærmingen er benekte-basert i stedet for tillatelsesbasert en vi hadde før. Det kritiske punktet er at uansett hvilken tilnærming vi velger, ArchUnit vil vanligvis være fleksible nok til å uttrykke reglene våre.

5. Bruke BibliotekAPI

ArchUnit gjør etableringen av komplekse arkitektoniske regler til en enkel oppgave takket være de innebygde reglene. Disse kan i sin tur også kombineres, slik at vi kan lage regler ved hjelp av et høyere abstraksjonsnivå. Ut av boksen, ArchUnit tilbyr Bibliotek API, en samling av ferdepakkede regler som adresserer vanlige arkitekturhensyn:

  • Arkitekturer: Støtte for lagdelte og løk (også kjent som sekskantede eller "porter og adaptere") arkitekturkontroll
  • Skiver: Brukes til å oppdage sirkulære avhengigheter eller "sykluser"
  • Generell: Samling av regler knyttet til beste kodingspraksis som logging, bruk av unntak osv.
  • PlantUML: Sjekker om kodebasen vår overholder en gitt UML-modell
  • Frys buereglene: Lagre brudd for senere bruk, slik at bare nye kan rapporteres. Spesielt nyttig for å håndtere teknisk gjeld

Å dekke alle disse reglene er utenfor omfanget for denne introduksjonen, men la oss ta en titt på Arkitektur regelpakke. Spesielt, la oss omskrive reglene i forrige avsnitt ved hjelp av lagdelte arkitekturregler. Å bruke disse reglene krever to trinn: først definerer vi lagene i applikasjonen vår. Deretter definerer vi hvilke lagtilganger som er tillatt:

LayeredArchitecture arch = layeredArchitecture () // Definer lag .layer ("Presentasjon"). DefinedBy (".. presentasjon ..") .layer ("Service"). DefinedBy (".. service ..") .layer (" Persistence "). DefinedBy (" .. persistence .. ") // Legg til begrensninger .whereLayer (" Presentasjon "). MayNotBeAccessedByAnyLayer () .whereLayer (" Service "). MayOnlyBeAccessedByLayers (" Presentasjon ") .whereLayer (" Persistence ") .mayOnlyBeAccessedByLayers ("Service"); arch.check (jc);

Her, layeredArchitecture () er en statisk metode fra Arkitekturer klasse. Når den påberopes, returnerer den en ny LayeredArchitecture objekt, som vi deretter bruker til å definere navnelag og påstander om deres avhengighet. Dette objektet implementerer ArchRule grensesnitt slik at vi kan bruke det akkurat som alle andre regler.

Det kule med denne spesielle API-en er at den lar oss lage på bare noen få linjer med koderegler som ellers vil kreve at vi kombinerer flere individuelle regler.

6. Konklusjon

I denne artikkelen har vi utforsket det grunnleggende om bruk ArchUnit i prosjektene våre. Å vedta dette verktøyet er en relativt enkel oppgave som kan ha en positiv innvirkning på den generelle kvaliteten og redusere vedlikeholdskostnadene på lang sikt.

Som vanlig er all kode tilgjengelig på GitHub.


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