Systeemarchitectuur/Interfaces en Separation of Concerns
Over interfaces en Separation of Concerns
Een interface scheidt en verbindt het gebruik (de functionaliteit) van een component van de implementatie (interne opbouw en werking) daarvan.
Een component kan in dit verband een kleine hardwarecomponent zijn, zoals een logische poort; maar het kan ook een complete processor zijn, of een server. In de software kan het gaan om een eenvoudige functie, of om een compleet Operating System.
Een interface is een voorbeeld van Separation of Concerns. De maker van een component hoeft niet te weten hoe deze gebruikt wordt. De gebruiker van een component hoeft niet te weten hoe deze in elkaar zit.
- Bij het programmeren vervul je vaak zowel de rol van maker als die van gebruiker. Op het ene moment ben je bezig met het implementeren van een component, en op het andere moment met het gebruik daarvan. Separation of Concerns betekent dan dat je je niet met alle details tegelijk bezig hoeft te houden.
Je kunt een interface zien als een tweezijdige vorm van abstractie: de maker van een component abstraheert van de details van het gebruik, de gebruiker van een component abstraheert van de details van de implementatie.
Veranderingen; evolutie
Het gebruik van interfaces heeft ook gevolgen voor de manier waarop systemen aangepast en veranderd kunnen worden. Je kunt een component vervangen door een andere component met dezelfde interface. Voor het gebruik heeft dat geen functionele gevolgen: aan de kant van het gebruik hoeft er niets aangepast te worden. Een dergelijke aanpassing kan wel gevolgen hebben voor de prestaties. Omgekeerd kun je ook het gebruik van een component veranderen zonder gevolgen voor de implementatie van die component.
Evolutie van componenten en van interfaces
Niet alleen de implementatie van componenten verandert: ook interfaces veranderen. De evolutie van interfaces verloopt wel veel langzamer dan de veranderingen in de implementatie. Bij de veranderingen van een component kunnen we met de volgende situaties te maken hebben:
- er is alleen sprake van een verandering van de implementatie; als deze nog steeds voldoet aan de oorspronkelijke interface, hoeven we aan de kant van het gebruik niets te veranderen.
- de interface wordt uitgebreid, waarbij de implementatie nog steeds voldoet aan de oorspronkelijke interface ("backwards compatible"). Programmadelen die de vorige interface gebruiken hoever niet aangepast te worden. Maar je kunt wel van de nieuwe mogelijkheden gebruik maken.
- de interface verandert op een manier die niet compleet compatible is met het vorige interface. In dit geval moet je mogelijk componenten aanpassen die gebaseerd zijn op de vorige interface.
Semantic versioning
Deze verschillende mogelijkheden vinden we terug in de Semantic Versioning (http://semver.org). Je duidt de versie van een component aan met drie getallen gescheiden door een punt: major.minor.patch., bijvoorbeeld 2.0.157.
- volgend patch versie: als de interface gelijk blijft, en alleen de implementatie verandert, bijvoorbeeld het verbeteren van foute code;
- volgende minor versie: als de interface verandert op een manier die "backwards compatible" is;
- volgende major versie: als de interface verandert op een incompatibele manier.
Over het veranderen van interfaces
Waarom zou je een interface veranderen op een incompatibele manier? Dit geeft toch alleen maar vervelende gevolgen voor gebruikers?
Het ontwerpen van een goede interface is lastig. Je probeert een interface zo te maken dat:
- het gebruik van een component zo eenvoudig mogelijk is: hoe meer details een gebruiker over kan laten aan de component, en aan de implementatie daarvan, des te beter. De gebruiker maakt dan minder fouten, en is sneller klaar.
- een component breed inzetbaar is: hoe meer toepassingen van een component gebruik kunnen maken, des te hoger je de kwaliteit van de component wordt, in de loop van de tijd.
- meer gebruikers betekent dat er meer fouten uit de implementatie gehaald worden; en dat de druk om bekende fouten te verbeteren groter is.
- een component een eenheid vormt, die redelijk eenvoudig te implementeren is, en die los van de rest van het systeem getest kan worden.
Deze eisen zijn voor een deel tegengesteld: dit betekent dat je een afweging moet maken, die in de loop van de tijd kan veranderen. Je kunt ook te maken hebben met het veranderen van de omstandigheden, bijvoorbeeld doordat er andere componenten beschikbaar komen.
In het ontwerpproces
Je kunt de ontwerpbeslissingen voor de implementatie van een component veranderen zonder dat je de ontwerpbeslissingen voor het gebruik hoeft te veranderen, en omgekeerd.
Voor veel ontwerpbeslissingen probeer je de scope zo klein mogelijk te houden. Daarvoor moet je een aantal globale ontwerpbeslissingen nemen: deze leg je vast in de architectuur.
- Onder "scope" van een beslissing verstaan we hier het deel van de beslissingen in het systeem die afhankelijk zijn van deze beslissing.
Variaties in implementatie
Zoals gezegd scheidt een interface de domeinen van gebruik en van implementatie. Dit betekent dat in elk van deze domeinen variatie mogelijk is, zonder dat dit gevolgen heeft voor het andere domein. Variatie aan de kant van het gebruik komt veel voor, maar variatie in de implementatie is ook mogelijk.
- Dit maakt het bijvoorbeeld mogelijk om dezelfde software op totaal verschillende computers te laten werken. Hiervoor moeten deze computers de gewenste interface bieden - bijvoorbeeld in de vorm van een bepaalde Operating System. En ze moeten aan bepaalde minimumeisen voldoen, bijvoorbeeld wat de rekensnelheid en de omvang van het werkgeheugen betreft (eisen aan de beschikbare resources).
Dit idee was één van de uitgangspunten van de IBM/360 serie: een reeks computers met dezelfde hardware/software-interface (de "instructiesetarchitectuur"), uiteenlopend van een 8-bits seriële implementatie tot een 64-bits parallelle implementatie. Het verschil in prestaties voor de eerste aangekondigde modellen was ongeveer een factor 50: tussen het kleinste model 30 en het grootste model 70 (later geleverd, als model75).
Tegenwoordig vinden we dergelijke variaties in de implementatie onder andere bij standaarden als USB en Bluetooth - voor het verbinden van randapparaten, en CD-ROM (en volgende). Om na te gaan of de verschillende leveranciers eenzelfde interpretatie hebben van de standaard, worden er zogenaamde "plug fests" georganiseerd om allerlei combinaties uit te proberen. Een dergelijke standaard werkt alleen als de gebruikers erop kunnen vertrouwen dat combinaties van producten van verschillende leveranciers werken zoals verwacht.