OSGi och Spring Dynamic Modules
Denna artikel ingår i serien Spring från början och kommer behandla det springstöd som finns för OSGi. Denna artikel är inte direkt en ”tutorial” utan ger främst en orientering till OSGi och Spring Dynamic Modules och gör ett försök till att peka på lämpliga användningsområden.
Vad är OSGi och vad är det bra för?
Förkortningen , vilket i vart fall inte hjälper mig att förstå vad OSGi egentligen är.
OSGi står tydligen för Open Service Gateway Initiative, men det har inte hjälpt någon att förstå vad det OSGi egentligen är får något. OSGi-standarden är helt kort en standard för modularisering av javaapplikationer.
OSGi används i allt från inbäddade system, mobiltelefoner till en flerskiktade webbapplikationer. Standarden har funnits i flera år och har tidigare främst används i inbäddade system. Det är först på senare år som OSGi har blivit aktuellt att använda i serverapplikationer. Den kanske mest kända applikationen som bygger på OSGi är den populära utvecklingsmiljön Eclipse, som använder OSGi i sitt plug-in system.
Mer konkret så specificerar standarden tre områden; paketering av moduler, tjänsteregister och modulers livscykler.
Paketering
Själva grunden i OSGi är paketeringen av applikationsmoduler i något som kallat bundles men som jag väljer att kalla moduler eftersom det ordet fungerar bättre på svenska. En OSGi-modul specificerar vilka javapaket den är beroende av och vilka javapaket som är synliga för andra moduler. En modul är egentligen en glorifierad jar-fil med extra OSGi-specifika headers i manifestfilen.
OSGi tilllåter att flera versioner av samma modul är installerade samtidigt.
Livscykler
OSGi specificerar hur en OSGi-container dynamiskt kan installera, ta bort, starta, stoppa och uppgradera moduler. Moduler kan med andra ord tillkomma, försvinna och uppgraderas utan att containern eller applikationen behöver startas om.
Tjänster
Till sist behandlar standarden hur en modul kan exponera tjänster till andra moduler. Dessa tjänster registreras i ett centralt serviceregister som är åtkomligt från alla moduler som är installerade i samma OSGi-container. En tjänst definieras på enklast möjliga sätt genom ett helt vanligt java-interface.
En sak som är viktigt att komma ihåg är att OSGi omfattar bara vad som händer inom en och samma JVM-instans, det finns inga mekanismer för att exportera distribuerade tjänster etc.
Varför ska man bry sig om OSGi?
Fördelar under utvecklingsfasen
- Strikt kontroll av modulers beroenden och versioner: OSGi har starka mekanismer för att upprätthålla separationen mellan moduler. En modul måste explicit ange vilka javapaket som exponeras ut mot andra moduler, detta gör att man hela tiden har kontroll på vilka delar av en modul som är publika och vilka som är privata. Arkitekturen kan därmed hållas intakt utan att delar av ditt system flätas samman okontrollerat under projektets gång. Denna kontroll upprätthålls även när systemet körs och en modul kan inte installeras i OSGi-containern om den inte har alla sina externa beroenden uppfyllda.
- Strikt kontroll av tjänsters synlighet: På samma sätt som du får kontroll på dina modulers typberoenden så får du kontroll på dina tjänster. Bara tjänster som explicit exponeras är synliga för andra moduler. Tjänster avsedda för intern konsumtion kommer helt garanterat vara oåtkomliga för andra moduler. Du kommer inte komma på att nån använder din DAO på andra sidan systemet.
- Snabbare utveckling för stora projekt: När moduler har tydliga gränser, väldefinierade gränssnitt och beroenden så blir samarbetet mellan utvecklingsteam mycket smidigare utan att man är i vägen för varandra. En väldefinierad modul kan lättare stubbas och mockas och blir således enklare att integrera i ett senare skede.
- Multipla versioner av samma modul: Som jag nämnt ovan så kan man ha flera versioner av samma modul installerad samtidigt i systemet. Det möjliggör scenarion där en del av systemet kan springa före och använda en ny version av en modul medan andra delar kan vänta, eller rent utav stanna kvar på en äldre version om man vill det. Så länge dessa moduler inte utbyter typer är det inga problem, och skulle de trots allt göra det kommer OSGi-containern upptäcka konflikten vid deploy.
Fördelar vid applikationsdrift
- Bättre kontroll av driftsmiljön: I en OSGi-miljö kan man direkt se vilka moduler och versioner av dessa som är installerade. Vidare finns information om vilka tjänster och paket som varje modul exporterar.
- Isolerade förändringar: Eftersom det är möjligt att bland annat installera och uppgradera moduler separat kan man genomföra förändringar i systemet stegvis. Man får dessutom bättre kontroll på att man inte inför oavsiktliga förändringar. Man har dessutom potentiellt bättre tillgänglighet för systemet eftersom man kan införa förändringar utan omstart.
- Delade tjänster: I många projekt har man en situation där en del av applikationen består av en webbapplikation och en annan del består av någon typ av backend för till exempel batchjobb och så vidare. I en klassisk J2EE arkitektur så delade man EJB:er mellan de olika applikationerna. I en modern Spring applikation kör man ofta hela applikationen som en webbapplikation och det finns inget självklart sätt att dela applikationskontext med en backend applikation. Det slutar ofta med att man skapar dubbla applikationskontexter med en del bygghuvudvärk som följd.I en OSGi-miljö blir det naturligt att dela tjänster mellan olika applikationer. Man kan till exempel tänka sig att man låter har en vanlig webbapplikation, en webserviceapplikation och en batchapplikation dela på samma service lager.
OSGi implementationer
Det finns lite olika OSGi-implementationer, både kommersiella och fritt tillgängliga under någon typ av open source licens.
Nedan följer de populäraste open source implementationerna:
- Apache Felix
- Eclipse Equinox används i Eclipse och i Spring Source DM server.
- Knopflerfish
Spring Dynamic Modules
Ok, om man nu vill hoppa på OSGi-tåget och redan använder sig av Spring framework så är den absolut enklaste vägen att gå via Spring Dynamic Modules. Poängen med Dynamic Modules är att man deklarativt kan exportera och importera dina vanliga springbönor som OSGi-tjänster. Om man dessutom kan välja applikationsserver bör man ta en titt på Spring Source DM Server som precis har kommit i en allmänt tillgänglig version.
Installera Spring DM
Det är rätt enkelt att installera Spring DM eftersom hela produkten består av OSGi-moduler, allt man behöver göra är att installera Spring DM:s moduler plus några andra moduler, dessa följer med distributionen om man väljer att ladda ner ”with-dependencies” varianten. Använder man Spring Source DM server så är Spring DM redan installerat.
Att skapa en modul
En OSGi-modul är ju som sagt en jar-fil vars META-INF/MANIFEST.MF innehåller headers som känns igen av OSGi-plattformen. När en modul installeras identifierar Spring-extendern ”Spring-drivna” moduler genom att det finns xml filer i modulens META-INF/spring katalog eller genom att sökvägen till applikationskontexten pekas ut med springspecifika headers i manifestfilen.
Ditt applikationskontext kommer konstrueras av alla xml-filer som hittas META-INF/spring. Jag förslår att man delar upp sitt kontext i åtminstone två filer: module-context.xml och osgi-context.xml. Module-context.xml innehåller dina vanliga bean-definitioner medan osgi-context.xml innehåller definitioner föra att exportera och importera OSGi-tjänster. I osgi-context.xml bör man dessutom använda sig av Spring Dynamic Modules OSGi schema för att underlätta konfigurationen.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<osgi:service id="simpleServiceOsgi" ref="simpleService" interface="se.cygni.MyService" />
</beans>
Det finns fyra vägar att gå när man jobbar med OSGi-moduler i eclipse:
- Manuell editering av MANIFEST.MF
- Apache Felix Maven plug-in
- Eclipse plug-in projekt
Är man ute efter att lära sig OSGi och inte har nåt jätteprojekt rekommenderar jag att du hanterar dina manifestfiler manuellt. För seriösa projekt rekommenderar jag det tredje alternativet eftersom Maven medför många andra fördelar.
Exportera en tjänst
När man exporterar en tjänst från sin modul så måste man allra minst ange vilken böna som ska exporteras och vilket interface som ska annonseras.
<osgi:service ref="myService" interface="com.xyz.MessageService"/>
Nu kommer bönan myService finnas tillgänglig för andra moduler under interfacet com.xyz.MessageService.
Man kan såklart deklarera bönan som en anonym inre böna:
<osgi:service interface="com.xyz.MessageService">
<bean class="se.cygni.MyService"/>
</osgi:service>
Vill man att varje modul som importerar tjänsten ska få en egen instans så finns det ett nytt scope som heter bundle. Dessa instanser lever så länge som den importerande modulen lever.
<osgi:service ref="myService" interface="com.xyz.MessageService"/>
<bean id="myService" class="se.abc.MyService" scope="bundle" />
Det är också möjligt att annonsera flera interface för en och samma böna:
<osgi:service ref="myService">
<osgi:interfaces>
<value>se.cygni.ServiceA</value>
<value>se.cygni.ServiceB</value>
</osgi:interfaces>
</osgi:service>
Är man lat så kan man också exportera interface automatiskt genom att ange auto-export attributet istället för interface attributet.
auto-export kan ha fyra olika värden:
- disabled
- interfaces: alla interface som bönan implementerar registreras
- class-hierarchy: bönans klass och superklasser registreras
- all-classes: de båda ovanstående alternativen tillsammans
Personligen anser jag att exportera allt som är löst förtar lite av poängen med väldefinierade moduler och rekommenderar inte denna ansats.
Importera en tjänst
Reference elementet används för att importera en tjänst från service registret. På motsvarande sätt som man exporterar en tjänst anges vilket interface det handlar om.
<osgi:reference id="myService" interface="com.xyz.MessageService"/>
Man kan också ange flera interface som tjänsten ska implementera.
<osgi:reference id="myService">
<osgi:interfaces>
<value>com.xyz.MessageService</value>
<value>se.cygni.MarkerInterface</value>
</osgi:interfaces>
</osgi:reference>
Bönan ovan kommer att implementera samtliga angivna interface.
Om man är intresserad av alla tjänster som implementerar ett specifikt interface så har Spring DM stöd för detta genom list och set elementen. List kommer innehålla samtliga tjänster medan set endast innehåller unika tjänster. Likheten avgörs av de implementerande klassernas equals
-metod.
<osgi:list id="myListeners" interface="com.xyz.EventListener" />
Listans innehåll hanteras dynamiskt av Spring, listan uppdateras allt efter tjänster kommer och går i tjänsteregistret.
Det är möjligt att sortera listan med hjälp av en comparator.
<osgi:list id="myListeners" interface="com.xyz.EventListener" comparator-ref="someComparator"/>
eller en nästlad:
<osgi:list id="myListeners" interface="com.xyz.EventListener" comparator-ref="someComparator">
<osgi:comparator>
<bean class="se.cygni.SomeComparator" />
</osgi:comparator>
</osgi:list>
Övrig funktionalitet
Spring DM innehåller mer funktionalitet än vad som behandlats i denna artikel, värda att nämna är till exempel de lyssnare som man kan registreras om man är intresserad av en tjänsts livscykel.
Vidare har man lagt stor möda på att få vanliga webbapplikationer (.war) att fungera i en OSGi-miljö och Spring DM kan integreras med Tomcat eller Jetty.
Tredjepartsbibliotek
Ett problem med OSGi är att man inte kan använda tredjepartskod hur som helst. Alla jarfiler man använder måste vara OSGi-kompatibla. Även om det är fullt möjligt att modifiera befintliga jarfiler själv innebär det en hel del arbete. Som tur är driver gossarna på Spring Source ett eget repository som innehåller OSGi-versioner av alla vanliga open source bibliotek.
Repositoryt hittar du här: http://www.springsource.com/repository/app/
Modulariserad arkitektur
När man kommit så långt att man ska skissa upp en ny modulbaserad system arkitektur så ställer man sig frågan: hur ska man dela sina moduler?
Mina erfarenheter är att det inte är lämpligt att göra för finkorniga moduler, dels har Spring svårt att klara av cirkulära och ömsesidiga beroenden mellan tjänster som hör till olika moduler och det är svårare att debugga dessa typer av problem i en OSGi-miljö än om de hade varit begränsade till en applikationskontext.
Det finns lite olika aspekter på modularisering, alla är relativt självklara men jag tar upp dem ändå:
Logiska moduler
Den mest självklara modulariseringen man kan göra är att dela upp applikationens logiska lager i egna moduler. Har man redan en applikation med väldefinierade lager borde det vara en smal sak att migrera till en OSGi-arkitektur. Steg två blir att dela upp sin applikation i vertikala moduler, så att till exempel orderhantering hamnar i en modul och användarregistrering i en annan.
Tekniska moduler
Vissa tekniska aspekter av en applikation lämpar sig väldigt väl för modularisering, till exempel vilket persistensramverk man väljer. Om man delar upp sitt DAO-lager i två moduler, ett API och en implementationsspecifik så har man helt frikopplat DAO-konsumenterna från implementationen. Kom ihåg att det inte bara är typerna som är frikopplade, även dina applikationskontexter är helt separerade och ramverksspecifika bönor hamnar där de hör hemma och blir oåtkomliga från övriga moduler.
Ett kanske ännu bättre exempel är att kapsla in applikationens integrationspunkter i egna moduler. En sådan inkapsling kommer också bestå av två moduler, en API-modul och en implementaionsmodul. Då kan man relativt enkelt stubba bort det där dammiga stordatorsystemet tills de riktiga integrationstesterna börjar:)
Organisatoriska moduler
Ofta kommer säkert de logiska och tekniska modulerna att passa bra rent organisatoriskt, men det kan kanske vara motiverat att skapa moduler enbart av organisatoriska skäl. Modulernas gränssnitt kommer då fungera som en naturlig avstämningsyta mellan olika delar av organisationen.
När Spring DM olämpligt att använda?
Om man ska göra en mindre webbapplikation har jag svårt att se nyttan med OSGi och därmed Spring DM.
Har man redan har god ordning på kodbasen och inte har behov av att uppgradera systemet under drift har jag också svårt att tro att det är mödan värt.
Lite förvånande är att RMI och OSGi inte fungerar särskilt bra tillsammans, orsaken är rätt olika syn på klassladdning, så om du inte är road av att brottas med klassladdare så undvik denna kombination.
Slutsats
Många av de fördelar som OSGi och Spring DM ger går att få utan att anamma dessa tekniker. Dock ställer det högre krav på disciplin hos alla medlemmar i projektet och ständigt arbete med att upprätthålla kodhygien. I många projekt är det vanligt att projektmedlemmar kommer och går relativt ofta och systemarkitekturen eroderar snabbare än nödvändigt. OSGi kan vara till stor hjälp att upprätthålla den ursprungliga arkitekturen och Spring DM förenklar väsentligt om man redan har ett springbaserat system.
Sedan får man inte glömma bort OSGi:s unika fördelar: installation / uppdatering av moduler under drift samt möjligheten att köra flera versioner av samma modul i samma VM.
Även om det går att köra en OSGi-container inbäddad i en vanlig applikationsserver är nog den svagaste punkten i dagsläget bristen på applikationsservrar med explicit OSGi-stöd, Spring Source egna DM-server som nyligen släpps verkar mycket lovande men har en del att bevisa i skarp drift och andra leverantörer pratar men har inte visat upp något konkret ännu. Ett annat problem beroende på hur man ser det är att all man kastar ut stora delar av JEE standarden med detta arbetssätt.