Din blog burde hedde “Stop med at bruge batchjobs”

Jeg talte med en af mine venner forleden om tilgangen til udvikling via DDD, Commands og Events, samt nogle af de seneste blog posts jeg havde lavet, som især omhandler brugen af NServiceBus.

Han afsluttede samtalen med “Din blog burde hedde ‘Stop med at bruge batchjobs'”. Og det er på en måde rigtigt nok.

Batchjobs er og bliver i mange sammenhænge et slags nødvendigt onde, som efter min mening bliver brugt alt for meget. Jeg taler ikke her om overførsel og processering af kæmpestore datamængder – i integrationsøjemed kan det være den helt rigtige vej at gå.

Men ved brug til domænelogik er min holdning noget anderledes. Her vil jeg forsøge at forklare hvorfor.

For en typisk full stack web udvikler findes der typisk to runtime tidsrum, hvor systemet er aktivt: Request time og batch job time. Disse udnyttes (og efter min mening misbruges) typisk på denne måde i forhold til domænelogik:

  • Request time
    En request sendes ind til systemet, fra UI front end eller via et API, og der udføres en handling. Der kan lægges data ind i systemet, læses data ud fra systemet, der kan ændres eksisterende data. Det er egentlig ganske ok.
    Der fyldes dog typisk også på med forskellige sekundære handlinger i forhold til den primære handling, som ved brugeroprettelse, hvor (1) brugeren oprettes, (2) velkomstmail udsendes, (3) default ressourcer tildeles, (4) der pushes en besked til mobilen osv.
    I web applikationer, hvor der ikke er mulighed for eller ønske om at køre batch jobs, ser man eksempelvis også, at der ved udlæsning af data også udføres tilstandsskifte på data (Er den gamle udgave af dokumentet stadig aktuel, eller er start date på kommende udgave overskredet? Så skifter vi da lige, gemmer og returnerer.).
  • Batch job time
    Et job igangsættes for at udføre forskellige forretningslogikker, som med mellemrum checke, at et svar er kommet fra en ekstern service, eller at checke, om en brugers abonnement er udløbet eller om der skal ske andre tilstandsskifte. Dette er simple regler. Nogen gange ser man et eller flere batchjobs, der tilsammen bruges til at implementere en kompleks process.

Ovenstående er selvfølgelig grælle eksempler, men de forekommer nu engang her og der. Ulempen i begge typer af applikationsaktivitet er bl.a. uventede sideeffekter og kerneforretningslogik, der ligger spredt ud i løsningen.

Et alternativ er at benytte en mere DDD-orienteret tilgang i implementationen, samt NServiceBus eller tilsvarende teknologi, som ikke bare er et API til durable messaging, men også tilbyder en model for implementation af long running processes. Listen udvides og ændres:

  • Request time
    Uanset ophav (UI / API), så afsendes ved oprettelse / ændring en Command (alternativt gemmes noget state inden, men det er en anden blog post) til en entitet / process, som processerer disse commands i baggrunden.
    I tilfældet med brugeroprettelse, så ville et CreateNewUserCommand blive lagt på bussen, og API’et kan med det samme returnere med et ID / en cookie / whatever.
  • Worker time
    I workeren processes CreateNewUserCommand – dette er applikationskoden, der benytter domænemodellen. Brugeren oprettes, og der publiceres et Event. Dette event kan adskillige message handlers i samme eller andre services abonnere på. Disse message handlers står så for at udsende mails, tildele ressourcer, pushe beskeder – i sin egen tid og transaktion.

Allerede her opnår vi den fordel, at brugeren oprettes korrekt i det lokale system, før eksterne systemer involveres, og når de gør, så er det i egne transaktioner. Selv hvis mail/push-serveren er nede, så overlever brugeroprettelsen. Det er faktisk en meget vigtig fordel.

Men det stopper ikke der. Med NServiceBus sagaer findes der mulighed for at modellere andre typer af aktivitetstider, som ikke bare gør selv halvkomplekse ting lette at implementere, men også gør, at det er lettere at lægge sig op ad forretningskravene:

  • Udfør noget lige om lidt
    Send ikke velkomstmailen nu, men lidt senere, så brugeren har haft tid til at lære systemet at kende. Send en opfølgningsmail nogle måneder senere, for lige at høre, om alt er ok. Forsink en mail eller tweet til når nogen rent faktisk er online til at se den og vil reagere på den.
  • Udfør noget på et bestemt tidspunkt
    Brugeren har betalt for en service frem til en given dato – på den dato, fratag brugerens ret til at bruge servicen (og inden da, advar om at det sker).
  • Udfør noget, når et antal events er blevet modtaget
    Afvent et antal events både fra egen og andre services – udfør en handling, når alle disse events er modtaget. Det klassiske eksempel er, at først når Billing og Warehouse har sagt ok, kan Delivery igangsættes.
  • Udfør noget, når et antal opgaver er udførte
    En stor video uploades og skal konverteres til tre filer af forskellige størrelse / codec. Læg et antal subtasks på køen. Når alle konverteringer er udførte, publicér et event.
  • Udfør noget, hvis det forventede ikke skete
    Vi forventer at en vigtig opgave skal være udført inden for et bestemt tidsrum efter igangsættelse. Afvent forekomst af resultat-event og udsend event (eller udfør kompenserende handling), hvis resultatet ikke er modtaget inden for forventet tidsrum.

Som du kan se, at ovenstående muligheder er meget værdifulde, og jeg kan klart anbefale at arbejde hen mod muligheden for at programmere på denne måde.

Christer Ø. on twitterChrister Ø. on linkedin
Christer Ø.
Software Development Consultant stratal ApS
Christer Østergaard er freelance forretningsanalytiker og softwarearkitekt med mere end 16 års erfaring. Har arbejdet med STAR (tidl. Arbejdsmarkedsstyrelsen), DSB, Kommunernes Landsforening, Digitaliseringsstyrelsen, Egmont, Banedanmark, Elsparefonden, Armada Shipping, Unik HR, Fødevareministeriet og Ministeriet for Flygtninge, Indvandrere og Integration, Knowledge Cube, mikz.com, Ankestyrelsen m.fl. Dertil et par Open Source projekter. Lige nu læser Christer Data and Reality. Christer er klar til at sikre leverance af nye softwareprodukter fra 1. Januar 2016.