Beskeder i en serviceorienteret arkitektur

(Joan Zapata) (8. december 2020)

Serviceorienteret arkitektur (SOA) har vist sig dets styrker i næsten alle industrier til at producere skalerbare og evolutive systemer. SOA har mange betydninger, men dets kerneidee om at bryde en kompleks applikation i mindre, genanvendelige og løst koblede tjenester er kommet langt siden slutningen af ​​1990erne. Vi har taget konceptet fra dag ét, og efter tre år har vi nu omkring tyve domænestyrede afkoblede tjenester, der strukturerer vores backend-applikationer. Det tillod os at bruge de sprog, vi kunne lide – som Kotlin , Elixir – og værktøjerne vi ønskede – ligesom Sourcing af begivenheder – til den aktuelle opgave.

Men at have flere tjenester kommer med sine egne udfordringer, den første er hvordan man får tjenester til at kommunikere med hinanden ? Der er mange strategier derude, og denne artikel er et afkast af erfaringerne med dem, vi bruger i Memo Bank.

Lad os begynde med to tjenester og denne enkle brugssag som et eksempel:

HVILLE over HTTPS

Som mange backend-applikationer startede vi med REST kalder over HTTPS.

Cykliske afhængigheder mellem tjenester skal undgås for enhver pris, så kontrakter kan ikke direkte bede kunder om at aktivere den nye kunde, så snart kontrakten er underskrevet. I stedet skal kunder sende anmodninger til kontrakter regelmæssigt for at vide, hvornår opgaven er færdig.

HTTPS er en god start, og bestemt noget at gemme i værktøjskassen: det er nemt at konfigurere, bredt understøttet og bruges også til at udsætte APIer til frontend-applikationer. Men der er et stort problem med denne ordning, det er synkron (dog ikke nødvendigvis blokering). Det betyder, at mens kontrakter fungerer, venter kunder med en aktiv forbindelse. Når det bruges i skala, betyder det, at den langsomste service kan bremse hele systemet. Det betyder også, at når kontrakter -tjenesten ikke er tilgængelig, er kunder -tjenesten heller ikke, hvilket kaldes kaskadefejl.

Hvordan tacklede vi denne sag? Vi brugte to forskellige mønstre: diskret og kontinuerlig messaging.

Diskret messaging

Lad os tage den første brugssag: kunder ønsker kontrakter for at sende en kontrakt.

Til dette kan vi løse problemet ved hjælp af en diskret meddelelsesmekanisme: køer . Du kan se en kø som en tredjepartspostkasse med høj tilgængelighed. Selv når hovedtjenesten ikke er tilgængelig, accepterer postkassen beskeder. Flere forekomster af den samme tjeneste kan forbruge den samme kø, i hvilket tilfælde køen fungerer som en belastningsafbalancering og sikrer, at hver besked håndteres mindst en gang. Når en meddelelse er håndteret, kan postkassen glemme det.

Vi bruger køer til at sende kommandoer (f.eks. “SendContract”) fra en tjeneste til en anden. Det har flere fordele:

  • Opkaldstjenesten er ikke afhængig af tilgængeligheden den modtagende tjeneste og kan genoptage sit arbejde, så snart kommandoen er i køen;
  • Modtageren kan håndtere kommandoerne i sit eget tempo , og vi kan let skalere modtagertjenesten op eller ned afhængigt af belastningen på dens køer;
  • Som en bonus failure kan let isoleres og håndteres manuelt (vi håber at dække emnet dead letter køer emnet i en anden artikel).

Kontinuerlig meddelelse

Lad os nu se den anden brugssag, når kontrakten er underskrevet, kontrakter skal lade kunder vide det sket, så det kan aktivere kunden.

Det er fristende at bruge en anden kø her, men som vi sagde før, vil vi ikke have en cyklisk afhængighed, så kontrakter ved ikke hvad som helst om kunder . Således kan den ikke sende en “ActivateCustomer” -kommando, og den kan ikke vide, hvornår eller hvor den skal sende denne kommando.

Vi løste dette problem ved hjælp af en kontinuerlig meddelelsesmekanisme: streamer .Du kan se en stream som en ordnet række af begivenheder, der kan forbruges i realtid, men som også gøres tilgængelige over tid. I modsætning til køer, der ikke er relaterede kortvarige kommandoer, fortæller streams en vedvarende historie .

Hver tjeneste hos Memo Bank sender en strøm af begivenheder , der beskriver livscyklussen for dets ressourcer. Opbygning og vedligeholdelse af disse begivenheder er en integreret del af enhver udvikling , uanset om denne begivenhed er straks nødvendig. Det er en del af rutinen, ligesom automatiske tests og metrics.

Disse begivenheder bliver således en pålidelig tidsstemplet log for uforanderlig fakta . Og da de er en del af APIen for den emitterende tjeneste, kan de forbruges af enhver anden tjeneste, både i realtid (en efter en) og i fremtiden. Disse begivenheder har en enorm værdi for sporbarhed og dataanalyse .

For at opsummere:

  • Køer bruges til at sende kommandoer til en bestemt tjeneste;
  • Streams bruges til at udsætter fakta fra en bestemt tjeneste.

Alt om afhængigheder

Diagrammet ovenfor kan få dig til at undre dig over at se på pilene, introducerer det ikke en cyklisk afhængighed mellem kunder og kontrakter?

Nej det gør ej. En afhængighed defineres ikke af dataens retning, men af ​​den viden, tjenesterne har af hinanden. Her ved kunder om kontrakter, det fortæller det, hvad det skal gøre, og det lytter til dets historie. Men kontrakter kender intet til kunder, det behøver ikke at vide, hvem der sender kommandoerne, eller hvem der lytter til dens historie.

Begge køen og strømmen er en del af kontrakter API, og kunder afhænger af denne API.

Har en , der navngiver konvention for kommandoer og fakta er meget vigtigt at formidle denne idé. Vi bruger altid en basisformular til kommandoer, som “SendContract”, og en form for tidligere participium til fakta, som “ContractSent”.

Bemærk, at den bekvemt matcher vores Core Banking System-arkitektur baseret på CQRS / ES . I denne terminologi er kommandoer de samme, og begivenheder er fakta.

Sådan vælger du afhængigheden

I betragtning af de principper, der blev forklaret før, ville denne løsning være lige så gyldig:

Men hvis begge løsninger er gyldige, hvordan vælger man den ene over den anden? Nå, det er op til dig.

Alt kommer ned til retningen for afhængigheden du ønsker at indstille.

A: kunder afhænger af kontrakter . B: kontrakter afhænger af kunder .

Her er nogle spørgsmål, vi normalt stiller os selv:

  • Kan en tjeneste let være agnostisk for andre?
    Her for eksempel, så længe vi giver indholdet til kontrakter , kontrakter kan være helt agnostiske over, hvilken tjeneste der bruger den. Det er sværere at forestille sig, at kunderne er agnostiske over, at det kræver en kontrakt. Det er til fordel for A.
  • Hvad hvis en tjeneste var en tredjepart?
    For F.eks. ville det ikke give mening for Memo Bank at outsource kunder men det kunne være for kontrakter . Således er dette også til fordel for A.
  • Orkestrerer en tjeneste andre tjenester?
    Implicit orkestrering er dårlig, du kan finde mere om det i denne samtale af Bernd Ruecker. Oprettelse af en kunde er en kompleks arbejdsgang som involverer mange tjenester (afsendelse af e-mails, underretninger, oprettelse af en bankkonto osv.), Så kunder er sandsynligvis en orkestrator her.At gøre orkestrator afhængig af andre tjenester – og ikke omvendt – gør koden meget lettere at forstå, fordi den fulde arbejdsgang kan findes på et enkelt sted. Det er også til fordel for A.
  • Skaber det en cyklus i den overordnede arkitektur?
    Selvom der ikke er nogen forbindelse mellem de to tjenester, afhænger de begge af andre tjenester. Lad os sige kunder afhænger af brugere , og brugere afhænger allerede af kontrakter . Hvis vi valgte løsning B, ville det skabe en cyklus med de tre tjenester. Det er også til fordel for A.

Konklusion

Beskeder er et af de første spørgsmål, vi skal svare på, når vi opretter en serviceorienteret arkitektur. Brug af HTTPS og REST virker i første omgang som den mest ligefremme løsning, men det har sine begrænsninger. Vi afsluttede vores arsenal med køer og streams , og vi sætter hovedsageligt to retningslinjer.

For det første skal hver tjeneste streame begivenheder når fakta sker inden for denne tjeneste, selvom vi endnu ikke har brug for disse begivenheder. Disse begivenheder skal fortælle en historie af, hvad der sker i tjenesten, som “ContractSent”, “ContractSigned”. Dette er fantastisk til sporbarhed – hvilket kræves som bank – men også for at konsolidere hver tjenestes API og for at gøre systemet lettere at arbejde med for alle teams.

For det andet handler det om afhængigheder . Afhængigheder former systemet, og cykliske afhængigheder er fjenden nummer et. Når afhængighederne er korrekt indstillet, er beskedværktøjerne bare her for at lade dataene strømme i alle retninger.

Oprindeligt offentliggjort på https://memo.bank/da/magazine .

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *