Det som jag tycker kännetecknar en bra arkitektur är att den är så ren och enkel som möjligt. En sak som jag tycker har en tendens att smutsa ned och komplicera arkitekturen är en överanvändning av Dependency Injection (DI). Ett populärt syfte med att använda DI är att förbättra och snabba upp testbarheten av systemet genom tex mockning. Om DI till stor del baseras på detta syfte finns det en risk för suboptimering, dvs man optimerar testbarheten av systemet på bekostnad av renhet och enkelhet i den tekniska arkitekturen.
Den största nackdelen med en omfattande klassisk implementering av DI är att antalet interface i arkitekturen ökar. Detta leder i sin tur till att arkitekturen blir mer svårnavigerad för utvecklaren. Detta eftersom interfacen försvårar för utvecklaren att snabbt finna naturliga flöden i systemet. Om utvecklaren tex stegar igenom koden och stöter på ett interface så finns det olika stöd (tex "Find all references" i Visual Studio) för att finna den aktuella implementering av interface:t. Detta arbetssätt kan jag acceptera om DI implementeras av andra syften än för att förbättra testbarhet samt om DI implementeras med rimlig omfattning.
Jag gillar heller inte att arkitekturen smutsas ned med kod bara för att förbättra testbarhet av systemet. Testbarhet har egentligen inget med systemet i sig att göra och därför bör den påverka arkitekturen så lite som möjligt.
Kan man förbättra testbarhet av systemet utan att arkitekturen tar större skada av det? Det finns lite olika lösningar för detta. Det bästa vore om olika integrationslösningar (som tex Nhibernate, Entity Framework, Enterprise Library, Windows Communication Foundation) erbjöd ett standardiserat stöd för mockning av objekt och andra resultat. Detta vore den bästa lösningen men troligtvis kommer det inte att inträffa inom den närmaste framtiden.
En annan lösningsvariant är att skapa en enkel återanvändbar wrapper runt en specifik integrationslösning. En sådan wrapper har i huvudsak två funktioner:
1) Den första funktionen är att autogenerera mock-objektet eller resultatet utifrån olika metoder som anropar den faktiska integrationslösningen. Detta innebär i sin tur att utvecklaren slipper att manuellt skapa mock-objekt. Möjligheten för att skapa manuella mock-objekt bör dock finnas. Det borde även finnas en möjlighet att skapa flera olika mock-objekt för en och samma metod där identifieraren för mock-objektet är inparametrarna till integrationsmetoden.
2) Den andra funktionen med wrappern är att utifrån en enkel configurationsinställning avgöra när den faktiska integrationslösningen eller den mockade varianten ska användas.
En sådan här wrapper kommer med stor sannolikhet hålla arkitektur ren och enkel samtidigt som den förbättrar testbarheten av systemet. Wrappern bör inte vara svår att skapa. Själv har jag inte skapat en sådan wrapper fullt ut men jag har gjort några lyckade experiment. Jag började med att pröva spara de olika autogenererade mock-objekten i textfiler men jag kom ganska snabbt fram till att det bästa var att spara mock-objekten och resultaten i en databas. Det blir en liten overhead att använda sig av en mock-databas. Denna overhead är dock relativt marginell och skulle man vilja så kan man bygga in cachning av mock-objekten.
Den wrapper jag gjorde var väldigt simpel men när jag gjorde den så fick jag en mängd nya idéer på hur den skulle kunna förbättras. En sak som jag saknade var tex ett lätt sätt att administrera de olika mock-objekten som skapats. En annan sak som vore bra att bygga in är en möjlighet att upptäcka inaktuella och icke-valida mock-objekt. En integrationswrapper öppnar även möjligheter för tracing, loggning m.m.
För att avrunda detta blogginlägg så kan man säga att en förbättrad testbarhet av systemet är förenligt med en ren och enkel teknisk arkitektur. Lösningen för att uppnå denna förening är inte genom en omfattande implementering av Dependency Injection utan genom att skapa wrappers runt olika integrationslösningar.
Hör gärna av dig om du testar att skapa en integrationswrapper. Det är alltid intressant att höra andras erfarenheter!
PS. Kanske bör jag tillägga att jag också är skeptisk till en överanvändning av DI för att uppnå flexibilitet i arkitekturen. Allt behöver inte vara flexibelt bara för att det går. I vissa fall behövs det medan i andra fall är det överdesign. Denna överdesign är en slöseri med kundens pengar och något som inte främjar enkelhet och renhet i den tekniska arkitekturen. Flexibilitet låter bra men den har sina konsekvenser som måste beaktas.