Onion architecture


torsdag 17. september 2009 Design OO Patterns

en løk Onion Architecture er navnet Jeffrey Palermo har foreslått for å beskrive et software design pattern som er mye brukt spesielt i Alt.Net-miljøet. Palermo presanterte arkitekturen i en blogpost-serie i juli/august 2008.

Arkitekturen fokuserer på hvordan vi håndterer avhengigheter i programvaren vår, og er et steg bort fra den tradisjonelle lagdelingen vi alle har vært vandt til. Den beskrives som en "løk" med flere lag, hvor kjernen inneholder domenet vi modellerer og forretningslogikken, mens infrastruktur og brukergrensesnitt befinner seg helt ytterst. Alle avhengigheter går innover i løken - ingen ting får lov til å avhenge av noe i et lag lengre ute enn seg selv. Gjennom mye bruk av Dependency Inversion Principle designer man slik en løsning hvor forretningsverdien er skjermet, og ting som endres oftere og som har flere eksterne avhengigheter (UI teknologi, databaser etc) ligger på utsiden og kan lettere byttes ut.

La oss som et konkret eksempel ta en titt på hvordan løsningen jeg utviklet i TDD i praksis og Validering kan være et domene-ansvar ser ut i denne typen løk-modell (klikk på løken for full størrelse om du ikke er langsynt):

onion

Helt innerst i arkitekturen ligger domenemodellen, som i mitt tilfelle er User-klassen. Den får ikke lov til å referere noe annet som ikke også ligger i domenemodellen.

Laget utenfor kaller vi for Domain Services – domenetjenester. Her finner man de tjenestene som har mest med forretningslogikken å gjøre. I mitt eksempel er dette blant annet spesifikasjonen for hva som er en gyldig brukerregistrering – d.v.s. ValidRegistrationSpecification, og alle de andre klassene som inngår i den. Som du ser på tegningen avhenger denne spesifikasjonen først og fremst av User-klassen, men én av spesifikasjonene avhenger også av en annen domenetjeneste, nemlig den abstrakte UserRepositor'en. Jeg vet ikke om alle er helt enige i at en repository er en domenetjeneste – jeg har bl.a. sett noen definere et eget lag mellom domenemodellen og domenetjenestene for repositoriene.

Ett nivå lengre ute finner vi Application Services – tjenester som har mest å gjøre med operasjonene man skal kunne gjøre med programvaren. Jeg definerer f.eks. både controller'en og det abstrakte view'et i Model View Presenter til å være en del av dette laget. Disse klassene er mer eller mindre uavhengig av hvilken presentasjonsteknologi som er brukt, og skal derfor ikke ha noen avhengigheter til laget utenfor, slik som reglene i Onion Architecture dikterer. De bruker derimot domenetjenestene og domenemodellen til å utføre handlinger initiert av brukeren.

I det ytterste laget finner vi de konkrete implementasjonene som er avhengig av spesifik teknologi knyttet til brukergrensesnitt og infrastruktur. I mitt tilfelle er det de konkrete view'ene (ASP.NET brukerkontrollene) og den konkrete repositori'en (som bruker en db4objects-database). Vi kan også plassere testene våre her, for å illustrere hvordan de anhenger av hele arkitekturen og bruker et bestemt testrammeverk. Mens klassene i lagene nedenfor kan betraktes som POCO's (Plain Old CLR Objects), er klassene i dette ytterste laget som du forstår tettere knyttet bestemte teknlogier. Gjennom å følge prinsippene i Onion Architecture har vi gjort oss mindre sårbare for endringer i disse teknologiene.

Det er vedt å merke seg at Onion Architecture har mye til felles med Evans' Domain-Driven Design, men at det ikke følger "boka" punkt for punkt. Faktisk mener jeg at "reglene" i denne arkitekturen er mere rigide på noen punkter, men også mere universelt anvendelige.

Inversion of Control

UserRepository og Db4oUserRepository er kanskje den aller tydeligste bruken av Dependency Inversion i dette eksempelet. Som du ser avhenger tjenestene i løsningen av den abstrakte UserRepository, mens "ingenting" avhenger av Db4o-varianten. Ingenting er i hermetegn, fordi den jo må instantieres ett eller annet sted.

Når man følger disse prinsippene, og ikke minst driver frem designet gjennom testdreven utvikling, får man MANGE slike "løse" avhengigheter, og det er her Inversion of Control-rammeverk (IoC)kommer inn i bildet. Dependency Inversion Principle gir oss et design som er lettere å endre, men som er vanskeligere å initialisere. IoC er enkelt fortalt en slags startmotor for programvaren din. Arkitekturen er veldig løs, men IoC-containeren syr alt sammen igjen. Bruk av IoC-rammeverk er derfor ikke livsviktig, men veldig praktisk, når man følger Onion Architecture.

Det finnes en bra sampling med IoC-rammeverk for .NET-utviklere, og er dette ukjent materie for deg bør du absolutt ta en titt. Ikke forvent øyeblikkelige resultater, men ta deg god tid, og forsøk å introdusere bruken gradvis. Noen av de mest kjente rammeverkene er: Castle Windsor, StructureMap, Spring.NET, og min personlige favoritt Ninject. Anbefaler dessuten å ta en titt på relevante DimeCasts for å få en god start på dependency injection.

Så det var min introduksjon til Onion Architecture, pluss en liten rant om IoC – jeg håper det gav mening. Det ryktes forresten at Fredrik Kalseth snakker om Onion Architecture på MSDN Live som pågår i disse dager. Det er fortsatt ikke for sent å melde seg på til showene i Bergen (22. sep), Trondheim (24. sep) og Oslo (29. sep).

Noen knagger: , , , , , , ,


comments powered by Disqus