Rent og pent med LTW

Jeg liker ren og pen kode akkurat like mye som hvilken som helst annen programmerer. Det kommer litt ekstra hjertebank av å se kode smellt sammen med minimal bruk av kodelinjer og like lettleselig som siste nummer av Donald Duck. Kodesnutter du bare kan trykke Ctrl+Print og levere rett til domeneeksperten og si “var det dette du hadde i tankene?”. Kildekode som hvem-som-helst kan komme når-som-helst og refaktorere hur-fort-som-helst. Jeg liker sånt:

public class InternettFasade{
...
public String leverAnmeldelse(Anmeldelse anmeldelse){
    return anmeldelse.sendFaxTilAnsvarligPolitiDistrikt()
                     .sendEpostTilPolitiDistrikt()
                     .sendBekreftelseEpostTilAnmelder()
                     .lagre()
                     .referanseNummer();
}

Sterkt preget av min egen lille forståelse av Domain Driven Design så passer sånn type kode meg ypperlig. Noen vil sikkert arrestere meg og si at i DDD så er det ikke opp til entitetene selv å drive å lagre, men jeg liker nå det engang sånn. Skader det så mye også egentlig? Sikkert masse annet jeg har bommet på med DDD også. Når jeg først har slike fine entiteter/modeller så synes jeg det er storveis å bare sende de rundt omkring hos forskjellige fasader og servicer helt uten å tenke meg om de har injisert de riktige repositories/servicer/validatorer osv, osv.

Til å begynne med gjorde jeg injiseringen i forskjellige factories som populerte entitetene med det nødvendige. Etterhvert bli disse factories’ene ganske kompliserte. Selv fikk jeg i alle fall lyst til å ordne det slik at det var bare disse factories’ene som fikk lov til å opprette domenemodellene. Da først snakker vi om skikkelig kontroll. None shall pass og one factory to rule them all. Og straks har jeg litt ekstra uønsket kompleksitet og kode. Huff. Derfor ble jeg ganske interessert i å finne ut hva denne Spring load-time-weaving var for noe. Og kunne det gjøre akkurat det samme som en factory? Bare behind the scenes? Joda. Såklart. Hvorfor hadde jeg ellers giddet å skrive denne bloggen? Here’s how!

Hva?

Load-time-weaving er i korttekst runtime instrumentering av klassene dine. Det er slik fancy AOP som legger seg rundt objektene dine og slår inn straks noen prøver seg. Prøver seg i dette tilfelle med Spring-ltw er rett etter du har opprettet et AOP-instrumentert objekt. Jeg kan illustrere med et kodeeksempel

@Configurable
public class Ansatt{
    @Autowired
    private AnsattRepository repo;
    @Autowired
    private EpostService epostService;
    ...
    public Ansatt lagre(){
        repo.lagre(this);
        return this;
    }

Her har vi først fortalt at klassen skal instrumenteres runtime av Spring vha annoteringen @Configurable. Det som skjer nå er at hver gang, og hvor som helst i koden, det kjøres en new Ansatt() så vil Spring hoppe inn og injisere EpostService og AnsattRepository fra konteksten sin. Det gjelder ikke bare koden du skriver, men også knæshe xml-to-java bibliotek og lignende. Så nå blir det fullt ut mulig å gjøre for eksempel dette:
new Ansatt(ansattNr).giSparken().sendSluttPakkePaaEpost().lagre();

Hvordan?

Hvordan er like enkelt som å skrive new Person(). Vel.. Nesten da. Kors på halsen. Det første du må gjøre er å bruke Spring. Hvis du har slengt på @Configurable så mangler du en slik en i Spring contexten din:

<context:load-time-weaver />

Da begynner Spring straks å mase om at den mangler en java-agent. Det er et annet ord for instrumenteringen som er det siste du må fikse. Det kan du gjør på forskjellige måter:

Java argument

For de fleste tilfeller må man sende inn java agenten som et java-argument. For eksempel for å starte den superenkle og populære Jetty sender du inn Spring sin java-agent gjennom Maven slik(Linux):

export MAVEN_OPTS="-javaagent:$HOME/.m2/repository/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar"

Og da er det bare å mvn jetty:run

Tomcat, GlassFish, Oracle OC4J, WebLogic

Apache Tomcat

Apache Tomcat

Med disse webserverne er det litt enklere da de har laget egne klasser for LoadTimeWeaving. For eksempel for Tomcat bruker du TomcatInstrumentableClassLoader. Den slår du på per web-app med å legge til dette i META-INF/context.xml:

<Context>
<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>

For at dette skal fungere så må Tomcat innstallasjonen kjenne til klassen TomcatInstrumentableClassLoader. Dvs du slenger
spring-tomcat-weaver.jar i lib katalogen til Tomcat, og vips - tilgjengelig java-agent! DET burde ikke være vanskelig å overbevise drift om å tillate!

Testing

Eclipse

Skal du teste at load-time-weaving koden din faktisk injiserer objektene som du håper må du også her tilby en java agent. Det gjør du ved å redigere vm argumentene til den testen eller koden du skal kjøre. Dette finner du under Run=>Run Configurations=>Arguments
Der mangler det en slik:
-javaagent:"${USER_HOME}/.m2/repository/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar

Maven

Ønsker du å kjøre enhets- eller integrasjonstester som bruker load-time-weaving så redigerer du argumentene for surefire-plugin slik at den instrumenterer likt Eclipse. Det kan du gjøre slik:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<forkMode>once</forkMode>
		<argLine>
			-javaagent:${settings.localRepository}/org/springframework/spring-agent/${spring.version}/spring-agent-${spring.version}.jar
                </argLine>
		<useSystemClassloader>true</useSystemClassloader>
	</configuration>
</plugin>

Alt vel i paradis?

Veeel... Det koster litt. Skal du opprette en drøss med objekter så går det atskillig raskere å gjøre det på gamlemåten med en Factory. En helt enkel test jeg gjorde viste at med en Factory fikk jeg laget 200 objekter på samme tid som det tok å lage ett @Configurable objekt. Så hvis du får en case med å opprette tusen på tusen av objekter med et hardt krav til responstid så må du kanskje være litt oppfinnsom. Personlig har jeg ikke erfart at load time weaving har gitt merkbart dårligere responstid.

En ting som kanskje er enda litt mer irriterende for deg som sitter og utvikler load-time-weavet kode er at du ikke kan bruke det sammen med ditt favoritt mock rammeverk. Den ene utsletter dessverre den andre og plutselig fungerer ingen tester. Det mock-rammeverket ditt trodde var et Person-objekt er ikke lengre et Person-objekt etter ltw. Nå er det et cglib objekt som utgir seg for å være et Person objekt. Personlig har jeg ikke funnet en smart måte å omgå dette på, og endt opp med å ikke teste med load time weaving men heller sørge for at enhets og integrasjonstester dekker det som dekkes skal slik at det blir middag.

For de som har lyst til å leke litt med load-time-weaving eller bare se et kjempeenkelt eksempel så har jeg snekret sammen et lite kodeeksempel her:
http://github.com/judoole/ddd-ltw-spring

3 Comments

  1. Rune Flobakk
    Posted 02/12/2009 at 22:30 | Permalink

    Kult skrevet! Har lyst til å teste ut dette selv. Synes det virker besnærende å kunne kalle f.eks. store() direkte på entiteter. At entitetene mine plutselig blir til cglib-proxyer virker litt… intimidating? Men det er kanskje uten grunn?

  2. Ole Christian Langfjæran
    Posted 03/12/2009 at 06:58 | Permalink

    @Rune Flobakk Det er vel kanskje litt intimidating ja. Men folk er jo passe vant til det hvis de har snekret litt med Hibernate også tenker jeg. Der proxies det jo over hele linja. Selv må jeg si at jeg har nesten bare gode erfaringer med å bruke ltw. Koden blir skikkelig pen og jeg drister meg til å si at du utvikler raskere også :)

    At jeg sier nesten kommer av den kjedelige grunnen med at du ikke kan bruke ltw sammen med favoritt-mocke-rammeverket mitt Mockito. Ikke verdens undergang likevel da du fort finner en vei rundt å ikke “trenge” ltw i testene dine. Men jeg håper jeg finner en måte en dag å bruke best of both worlds. Da skal jeg si fra!

  3. Posted 11/06/2010 at 11:27 | Permalink

    Bra skrevet og fint eksempel!

    Bare en liten rettelse rundt ltw. Det er ikke Spring load-time weaving, men AspectJ load-time weaving.

    Spring Aspects støtter Spring AOP og AspectJ. Men kun AspectJ støtter @Configurable. AspectJ kan også benytte compile-time weaving, noe som gjør at man kan benytte @Configurable uten å bruke en javaagent.

Post a Comment

Your email is never shared. Required fields are marked *

*
*

Spam Protection by WP-SpamFree