Plus “policy” – del 1

(English edition)

Jeg har efterhånden nævnt Plus kontoer på vores eksperimentielle site www.miljoparkering.se nogle gange, men det har altid været i forbindelse med andre emner her på eventdriven.dk.

Når jeg er i dialog med forretningsansvarlige og andre arkitekter, og beskriver den Plus policy vi bruger www.miljoparkering.se, samt hvor relativt let det var at indføre den på sitet, får jeg typisk meget positiv feeback. Noget af det kan muligvis stamme fra min entisuasme når jeg fortæller, da det er et ret godt eksempel på hvordan man kan bruge NServiceBus-sagaer i modelleringen og programmeringen af en sådan forretningsregel. :-)

Så jeg tænkte det måske var en god idé at beskrive den her, så jeg kan dele entusiasmen og viden med flere:
Continue reading

Startups, services og NServiceBus

For godt et halvt år siden blev jeg kontaktet af en gut, som ønskede at at lave et nyt softwareprodukt (app / SaaS).

Ud fra et Lean Startup perspektiv, som vi begge delte, er målet jo at lave et såkaldt MVP (Minimum Viable Product), og hurtigst muligt at se om tingene holder vand. Hvis produktet holder vand, så skal der udvikles videre med høj hastighed, og selvfølgelig skal man stort set kunne vende det hele på hovedet, hvis nye muligheder viser sig, som man kan tjene penge på (tænk Twitter).

Vi talte lidt frem og tilbage om de teknologiske muligheder – og givet at den teknologiske platform er Microsoft, hvordan laver man en god MVP? Og hvordan sørger man for, at det man laver, hurtigt kan rettes til? Continue reading

Eksempel på Composite UI på miljoparkering.se

Vi arbejder på www.miljoparkering.se med et “Plus” koncept. Det er funktioner ud over standardfunktionerne. Når brugeren får adgang, udsendes et CustomerPreferredEvent (og tilsvarende, når brugeren ikke længere er det).

På et tidspunkt ville vi udvide med en ny lille feature, hvor der skulle udsendes en påmindelse månedligt. Vi lavede en helt separat service, med følgende ansvar:

  • Holde information om brugerens setting (ja eller nej til at modtage påmindelsen).
  • Holde information om brugeren ret til at bruge funktionen (CustomerPreferred blev oversat til EligibleForMonthlyNotification).
  • Udsende månedlige påmindelser baseret på brugerens setting og ret til funktionen.

Derudover er det eneste forretningskoncept, der deles med andre services, brugerid’et.

Da vi havde alle eksisterende events og informationer på plads, havde vi ikke brug for åbne op i eksisterende kode. Vi lavede en ny vertikal service, med separat data store, message handlers, timers og UI komponent. UI komponenten kan du se nedenfor, markeret med Service 2.

Hvis brugeren ikke har ret til funktionen, vil standardværdien blive vist (Nej tak) uanset brugerens tidligere valg, og formen ikke aktiv. Den loades asynkront, og der postes asynkront. Såfremt der er en forsinkelse i events ved brugeroprettelse, vises samme tilstand som hvis brugeren ikke har ret til funktionen.

Composite UI kan godt virke lidt “op ad bakke”, men fra denne situation fik vi syn for, hvor hurtigt vi kunne levere en ny funktion (hurtigere end normalt, selv på trods af den lille større i dette tilfælde). Eftersom vi ikke skulle ind og rode i eksisterende kode, gik det rent ud sagt tæskestærkt ;o)

 

Composite UI eksempel

Et eksempel på en “policy” implementeret vha. NServiceBus

Vi har lavet en del ekstrafunktioner til www.miljoparkering.se, som vi har udgivet under titlen Miljöparkering Plus. Det er ekstrafunktionalitet, som giver brugerne mere fleksibilitet og mere end blot påmindelser.

Efter release ville vi selvfølgelig gerne udbrede Miljöparkering Plus lidt mere, og valgte at vi ville gøre det ved at give alle nye brugere 30 dage gratis adgang. Dette er noget vi modellerede som en “policy”. Her er definitionen / kravene til denne policy:

  • Den nye bruger skal have 30 dages gratis adgang til Miljöparkering Plus.
  • Brugeren skal tildeles denne adgang 1 time efter oprettelse af konto på sitet.
    (Dette gør vi af to årsager: (1) Vi sender også en velkomstmail og notifikationen om tildeling af Plus skal ikke modtages samme tid. (2) Vi vil gerne “strække” velkomsten ud, og dermed efterlade et mere positivt indtryk, der hænger fast.)

Det ser jo umiddelbart simpelt ud. Brugerne kan dog vælge at aktivere adgangen selv (ved betaling eller invites), inden der er gået en time. Efter første release af denne policy fandt vi ud af, at vi selvfølgelig ikke kunne have samme tildelingstekst til nye brugere, som havde været hurtige nok til at aktivere selv, inden vi selv tildelte. Så der kom lige et ekstra krav til:

  • Teksten skal være forskellig:
    • Hvis brugeren ikke har aktiveret selv: “Vil du prøve Miljöparkering Plus? …. …. ….”
    • Hvis brugeren har aktiveret: “Som velkomstgave har vi givet dig 30 dage ekstra…. ….. …..”

Hvad behøver man så for at implementere en sådan policy fra start til slut? Vi skal udvide systemet således, så dette sættes i gang når en ny bruger opretter sig. Og vi har brug for noget tilstandsstyring, så vi kan registrere om brugeren selv har akviteret Plus i løbet af ventetiden. Og så selvfølgelig noget, der trigger tildelingen 1 time efter brugeroprettelse. Det lugter jo af udvidelse af Bruger i domænemodellen. Og måske et “batchjob”? Eller hvad?

Heldigvis er www.miljoparkering.se udviklet efter principper fra Event-Driven Architecture. Det giver os nogle friheder til at koble ny forretningslogik på, uden nødvendigvis at rette i eksisterende (og testet) kode. Så dels dropper vi tankerne om udvide domænemodellen til at holde tilstand på denne meget specielle forretningsregel. Og dels kan vi droppe brugen af et batchjob (som alligevel ikke ville kunne imødekomme tidskravet på præcis én time for hver enkelt bruger, og som også ville kræve en batchrelateret tilstandsstyring (a la PlusAwardedAfterSignUp (True/False), CustomerTurnedPreferred(True/False))).

Dette kan man derimod med fordel implementere som en NServiceBus Saga. I det følgende ser du al kode for ovenstående forretningslogik. Gennemgang kommer lige efter, så hang in there! :)

 

Bemærk først og fremmest, at en sådan saga eksisterer for hver ny bruger på sitet.

Vi bruger  det eksisterende UserCreatedEvent til at starte vores saga. I metoden Handle(UserCreatedEvent message) initialiserer vi sagaens tilstand og opsamler vi hvad vi har brug for senere til tildeling af Plus (UserId). Vi sætter også en timeout i gang (1 time). I timeout callback’en Timeout(PreferredCustomerAfterSignupSagaTimeout state) sender vi en besked til vores eksisterende Accountsservice, som står for den konkrete tildeling af adgang, og lukker sagaen. Metoden GetSpecialMessageForCustomer() benyttes til at få den tekst, som skal sendes med tildelingen. Denne metode checker værdien CustomerTurnedPreferred. I Handle(CustomerSetPreferredEvent message) lytter vi på, uanset hvilken årsag (betaling eller hurtig respons på invite), om brugeren er blevet tildelt adgang i sagaens levetid. Når det sker, sætter vi CustomerTurnedPreferred til true.

Der er flere positive sider ved implementere en policy på denne måde: Dels behøver vi ikke at gå ind at rette i den eksisterende domænemodel, for at opfylde denne specifikke forretningsregel. Dels er hele forretningsreglen samlet ét sted: opstart, state, timeout, nedlukning (ja, af samme årsag er de 30 dage og tekst ikke konstanter eller fra configuration, da de ikke deles med andre komponenter) – ikke noget med at det er splittet op i web og batch.

Brugen af NServiceBus giver andet end robusthed. Og det er sådanne strategier vi blandt andet taler om, når vi i detalje deler vores erfaringer fra www.miljoparkering.se med andre virksomheder. Har du lyst til at vi kommer forbi og fortæller mere, så er du velkommen til at fange mig her: @christerdk

Har dit system en puls?

En af de ting, vi har haft meget nytte af på www.miljoparkering.se, hvor applikationsserveren benytter NServiceBus som en integreret det af løsningen, er tidsbundne events, som hver dag sendes ud i sytemet: DayStartedEvent og DayEndedEvent. Begge events indeholder event data om den dag, de repræsenterer.

Det er lige så simpelt som det lyder. Hver morgen kl. 00.00 sendes DayStartedEvent ud, hver aften kl. 23:59:59 sendes DayEndedEvent ud. På www.miljoparkering.se har vi derudover MonthStartEvent og MonthEndedEvent, som er afledt af DayStartEvent og DayEndedEvent, men de udsendes kun når det er henholdsvis den første eller sidste i måneden.

Brugen og fordelene kan variere fra system til system, her er hvad vi har brugt dem til:

  • DayStartedEvent – når det er sidste dag i måneden, skal der sendes en særlig påmindelse til brugere, der har valgt at modtage denne.
  • MonthStartedEvent – en rapport startes op med et unikt ID, der består af år og måned kombineret. Denne rapport lytter på udvalgte events i systemet, særligt omkring alle de påmindelser vi sender ud hver dag.
  • MonthEndedEvent – afslutter månedsrapporten og gør to ting: sender en mail til os administratorer med månedens tal, og derefter en besked til Twitter i stil med “Förra månaden skickade vi 481 st påminnelser. Det sparade 288 600 kr i böter.” (faktisk besked fra 1. oktober).
  • DayStartedEvent – hvis det er den sidste dag i måneden, sendes en tweet i stil med “Snart starter en ny måned. Tilmeld dig og undgå bøder”. Vi har i øvrigt gjort det således, at den faktiske, “fysiske” udsendelse udføres af en SendTweet NServiceBus saga, så det er muligt for os at skemalægge beskeden til kl. 10 om formiddagen, hvor der vil være flere læsere fra vores målgruppe.

Det giver god mening for selv et lille system som www.miljoparkering.dk at have sådanne infrastrukturelle events, men de ville kunne gøre endnu mere nytte for større enterprise systemer. Et særligt område, som kunne få nytte af dette er batchjobs – eller beregninger, som normalt lægges i batchjobs. Ofte møder jeg udviklere og arkitekter, der taler om natlige batch jobs, der stoppede fordi filmen knækkede et eller andet sted undervejs, og det er bare for ærgeligt. Ovenstående fordele blev realiseret uden brug af batch jobs, hvilket ellers ville have været en klassisk måde at implementere det på.

Ved brug af eksempelvis et DayEndedEvent i stedet for at lave et nyt batchjob vil have den fordel, at (1) man kan læse eksempelvis alle 100.000 IDs op som skal processeres, og herefter sende 100.000 beskeder, som (2) ville blive processeret adskildt og i mindre transaktioner, og (3) processering er en integreret del af løsningen, ikke noget, der er placeret eksternt. At processering af et enkelt element i serien fejler vil ikke have konsekvens for hele batchjobbet (hvis man tænker over det, så er det faktisk ikke specielt ofte, at et helt batchjob behøver at blive gennemført som alt-eller-intet).