Vad är en produkt?
Utveckling av mjukvaruprodukter skiljer sig till viss del från traditionell systemutveckling. Detta inlägg belyser några viktiga skillnader mellan systemutveckling och produktutveckling, hur man kan undvika vissa fallgropar genom att arbeta strukturerat och genom att använda rätt typer av verktyg.
I detta inlägg är definitionen av en produkt ett mjukvarusystem som används av flera olika kunder och där varje kund har olika krav på systemet och kräver kundunika anpassningar. Grunden eller basen i en produkt är den del som delas av samtliga kunder och kallas här standardprodukt.
Olika sätt att utveckla en produkt
En produkt med kundunika anpassningar kan byggas på olika sätt. Några exempel är:
- En ny kodlinje per kund
När det är dags för en ny kundanpassning så skapas en ny kodlinje baserad på produktens baseline. Denna kodlinje kommer med tiden att divergera från produktens baseline vilket kommer att leda till att kodlinjerna måste underhållas separat. - Skriva generisk kod som är konfigurerbar
Skriv generell kod som täcker alla varianter av alla kunders krav. Affärslogiken måste vara väldigt konfigurerbar för att tillgodose olika kunders krav. - Använda en pluggbar komponentbaserad arkitektur
Skriv kod som täcker de flesta fall av olika kunders krav och använd en komponentmodell som tillåter utökningar av befintliga komponenter men även tillägg av nya komponenter.
Det första alternativet är en snabb lösning som typiskt leder till ett hårdkodat och svårförvaltat system ju mer kodlinjerna divergerar. Denna lösning är förvånansvärt vanlig och typiskt så måste varje kodlinje efterhand bemannas med ett eget förvaltningsteam.
Det andra alternativet är bra om alla krav från alla typer av kunder är kända. Problemet med denna typ av lösning är att det krävs enorm kunskap om alla tänkbara krav och det tar väldigt lång tid att utveckla systemet eftersom alla fall måste täckas in. Systemet blir dessutom ofta svårtestat på grund av att ett konfigurerbart system tillåter många olika kombinationer av funktionalitet.
Det tredje tillvägagångssättet erbjuder ett flexibelt sätt att bygga produkter där produkten kan möta kundens krav genom att lägga till kundunika komponenter och affärskonfiguration. Detta leder till kortare utvecklingstider av standardprodukten. Standardprodukten kan testas genom att endast testa standardkomponenter och en kundanpassning testas med den konfiguration som är satt just för den specifika kunden. Genom att använda en container för IoC (Inversion of Control) kan komponenterna kopplas ihop under exekveringstid snarare än vid kompileringstid vilket innebär att det är möjligt att göra utökningar i kundanpassningarna. Ett exempel på en IoC-container är Spring Framework. Genom att använda DI (Dependency Injection) ökar dessutom testbarheten av produkten och kundanpassningen avsevärt.
Det sistnämnda tillvägagångssättet är det som jag rekommenderar eftersom produkten får en flexibel arkitektur, kortare utvecklingstid och kodlinjer som kräver mindre underhåll över tiden.
Kodlinjer
När man använder en pluggbar komponentarkitektur kan man hantera de kundunika komponenterna på olika sätt.
- Samma kodlinje
De kundunika komponenterna existerar i samma kodlinje som standardkomponenterna. - Separata kodlinjer
De kundunika komponenterna existerar i en separat kodlinje (typiskt per kund) och kundanpassningen bygger på en binär release av standardprodukten.
Fördelar med alternativ ett:
- Kundanpassningen är alltid ”up-to-date” med ”latest and greatest”-versionen av standardprodukten.
- Om standardprodukten refaktoriseras så följer kundanpassningen med på köpet eftersom de ligger i samma kodlinje.
Nackdelar med alternativ ett:
- Standardprodukten blir svår att testa eftersom flera kundanpassningar också måste testas samtidigt (de ligger ju i samma kodlinje).
- Kodmassan blir större för standardprodukten och kan bli ohanterlig när det finns många kunder i kodbasen.
- Skillnaden mellan standardprodukten och kundanpassningen kan bli otydlig eftersom komponenterna kan ligga mixade i samma kodlinje.
Genom att använda det andra alternativet så kan standardprodukten utvecklas oberoende av kundanpassningen (eftersom anpassningen baseras på binära releaser av standardprodukten). Det är också kristallklart vilka delar som hör till kundanpassningen respektive standardprodukten. När en kundanpassning baseras på en binär standardrelease måste standardreleasen packas i ett format som tillåter enkel import (exempelvis så fungerar en EAR-fil inte så bra för import). Därför bör de artefakter som produceras för både standardprodukten och kundanpassningen använda ett standardiserat paketeringssätt som är lättillgängligt för utvecklingsteamet.
Alternativ två leder även till att standardprodukten blir enklare att testa men innebär också att standardkomponenterna måste designas för att kunna anpassas/utökas i ett kundanpassningslager.
Utökningar
Om man använder en IoC-container så blir de komponenter som skapas och hanteras av IoC-containern i princip automatiskt utökningsbara. Det är dock viktigt att dokumentera sådana komponenter så att det är tydligt i en kundanpassning hur utökningen ska gå till. Utöver detta bör andra typer av komponenter designas för utökning ex GUI, domänmodell, datamodell och externa gränssnitt. Detta kan åstadkommas på olika sätt ex genom att använda Hibernate för domänmodellen, genom att tillhanda DDL-filer för datamodellen etc.
Övriga självklara utökningsmöjligheter som bör byggas in i en produkt är standardiserad internationalisering och resurshantering.
När ny funktionalitet ska införas i en viss kundanpassning kan detta vara en kandidat för att införas i standardprodukten. Typiskt så kan detta ske på två sätt:
- Skapa funktionen direkt i standardproduktens kodlinje.
Detta innebär att kundanpassningen måste vänta in en release av standardprodukten för att få tillgång till den aktuella funktionen eftersom kundanpassningen bygger på binära standardreleaser. En fördel med är att fler kunder kan utnyttja den nya funktionen när den väl blir tillgänglig och standardprodukten blir mer ”komplett”. - Skapa funktionen i kundanpassningens kodlinje.
När tiden är mogen kan funktionen lyftas in till standardkodlinjen. Detta sätt är ofta snabbare än att skapa funktionen direkt i standardproduktens kodlinje och ger ingen påverkan på den befintliga standardprodukten.
Releasehantering
När det gäller releasehantering för en produkt som är tänkt att utökas i en kundanpassning finns det massvis av saker att tänka på. Några av dessa är:
- Releasen måste vara korrekt versionsnumrerad (alpha, beta, milestone, release candidate etc.) och en underhållsbranch bör existera för att hantera buggfixar på releaser i produktionsmiljöer.
- Externa gränssnitt, komponenter tänkta för utökning, datamodell etc. bör vara väl dokumenterade.
- Dokumentation av nya funktioner, fixade buggar etc. bör finnas tillgängligt.
- Produktens paketering bör vara tydlig och enkel att förstå.
- Produkten måste typiskt vara bakåtkompatibel alternativt bör uppgraderingsinstruktioner finnas tillgängliga.
För att kunna jobba med binära standardreleaser i en kundanpassning bör ett bygg-, assemblerings- och paketeringsförfarande finnas tillgängligt som kan producera rätt artefakter. Artefakterna måste dessutom kunna importeras i kundanpassningen.
Detta kan lösas genom att exempelvis använda Maven som bland annat erbjuder:
- Intuitiva och tydliga byggsteg. Maven har många användare och integrerar väl med de flesta utvecklingsverktyg.
- Distribution av artefakter till ett delat repository. Maven kan distribuera både releaseartefakter och ögonblicksbilder av artefakter som kan användas under utveckling.
- Olika typer av paketering (EAR, ZIP, JAR etc). Maven har tydliga paketeringssätt som är väl dokumenterade.
- Transitiva beroenden leder till att en kundanpassning enkelt får med de tredjepartsmoduler som krävs för att kunna bygga/köra produkten.
Utöver detta erbjuder Maven även möjligheten att generera rapporter inom byggprocessen. En webbsajt kan genereras till att exempelvis innehålla följande:
- Release notes dvs. vilka funktioner som implementerats (ex via JIRA-integration).
- Defektlistor
- HTML-versioner av källkoden
- JavaDoc
- QA (metrics etc)
- FAQ
- How-To dokument
Dokumentation gällande hur produkten kan uppgraderas från tidigare versioner bör alltid vara en del av en produktrelease, detta kan antingen vara scripts eller dokument.
Summering
För att kunna bygga en produkt krävs att många bitar är på plats. Några av dessa är:
- En stabil byggprocess som kan hantera artefakter, beroenden mellan moduler, generering av dokumentation etc.
- Bra dokumentation av externa gränssnitt och av komponenter som är designade för utökningar.
- Produkten måste vara bakåtkompatibel, antingen out-of-the-box eller via dokumentation.
- Basera kundanpassningar på binära releaser av standardprodukten.
- Separera kodlinjer för kundanpassningar och standardprodukt.
- Använd en pluggbar komponentbaserad arkitektur.