Building Domain Driven Microservices

Chandra Ramalingam

Follow

1.7, 2020 – 18 min luettu

Kuvan krediittejä:

Termi ”mikro” mikropalveluissa, vaikka se kertookin palvelun koosta, ei ole ainoa kriteeri, joka tekee sovelluksesta mikropalvelun. Kun tiimit siirtyvät mikropalvelupohjaiseen arkkitehtuuriin, ne pyrkivät lisäämään ketteryyttään – ottamaan ominaisuuksia käyttöön itsenäisesti ja usein. Tälle arkkitehtuurityylille on vaikea löytää yhtä tiivistä määritelmää. Pidin tästä Adrian Cockcroftin esittämästä lyhyestä määritelmästä: ”palvelukeskeinen arkkitehtuuri, joka koostuu löyhästi kytketyistä elementeistä, joilla on rajattuja konteksteja.”

Vaikka tämä määrittelee korkean tason suunnitteluheuristiikan, mikropalveluarkkitehtuurilla on joitain ainutlaatuisia piirteitä, jotka erottavat sen menneen ajan palvelukeskeisestä arkkitehtuurista. Muutama näistä ominaisuuksista alla. Nämä ja muutama muu on hyvin dokumentoitu – Martin Fowlerin artikkeli ja Sam Newmanin Building Microservices, muutamia mainitakseni.

  1. Palveluilla on tarkoin määritellyt rajat, jotka keskittyvät liiketoimintakontekstin ympärille, eikä mielivaltaisten teknisten abstraktioiden ympärille
  2. Kätkevät toteutusdetaljit ja paljastavat toiminnallisuuden aikomuksen paljastavien rajapintojen välityksellä
  3. Palvelut eivät jaa sisäisiä rakenteitaan rajojensa ulkopuolelle. Esimerkiksi tietokantoja ei jaeta.
  4. Palvelut ovat vikasietoisia.
  5. Tiimit omistavat toimintonsa itsenäisesti ja niillä on kyky julkaista muutoksia itsenäisesti
  6. Tiimit omaksuvat automaatiokulttuurin. Esimerkiksi automatisoitu testaus, jatkuva integrointi ja jatkuva toimitus

Lyhyesti sanottuna voimme tiivistää tämän arkkitehtuurityylin seuraavasti:

Liukasti kytketty palvelukeskeinen arkkitehtuuri, jossa kukin palvelu on suljettu hyvin määriteltyyn rajattuun kontekstiin, mikä mahdollistaa sovellusten nopean, tiheän ja luotettavan toimituksen.

Toimialalähtöinen suunnittelu ja rajatut kontekstit

Mikropalveluiden voima syntyy niiden vastuun selkeästä määrittelystä ja niiden välisen rajan rajaamisesta. Tarkoituksena on rakentaa korkea koheesio rajan sisäpuolelle ja matala kytkentä sen ulkopuolelle. Toisin sanoen asioiden, joilla on taipumus muuttua yhdessä, pitäisi kuulua yhteen. Kuten monissa tosielämän ongelmissa, tämä on helpommin sanottu kuin tehty – yritykset kehittyvät ja oletukset muuttuvat. Siksi kyky refaktorointiin on toinen kriittinen asia, joka on otettava huomioon järjestelmiä suunniteltaessa.

Domain-driven design (DDD) on keskeinen ja mielestämme välttämätön työkalu, kun suunnitellaan mikropalveluja, olipa kyse sitten monoliitin murtamisesta tai greenfield-projektin toteuttamisesta. Toimialuepohjainen suunnittelu, jonka Eric Evans teki tunnetuksi kirjallaan , on joukko ideoita, periaatteita ja malleja, jotka auttavat suunnittelemaan ohjelmistojärjestelmiä perustuen liiketoiminta-alueen taustalla olevaan malliin. Kehittäjät ja toimialan asiantuntijat työskentelevät yhdessä luodakseen liiketoimintamalleja Ubiquitous-yleiskielellä. Sitten he sitovat nämä mallit järjestelmiin, joissa ne ovat järkeviä, ja luovat yhteistyöprotokollia näiden järjestelmien ja näiden palvelujen parissa työskentelevien tiimien välille. Vielä tärkeämpää on, että he suunnittelevat järjestelmien väliset käsitteelliset ääriviivat tai rajat.

Mikropalvelusuunnittelu ammentaa inspiraatiota näistä käsitteistä, sillä kaikki nämä periaatteet auttavat rakentamaan modulaarisia järjestelmiä, jotka voivat muuttua ja kehittyä toisistaan riippumatta.

Ennen kuin jatkamme eteenpäin, käymme nopeasti läpi joitakin DDD:n perusterminologioita. Täydellinen katsaus Domain-Driven Designiin ei kuulu tämän blogin piiriin. Suosittelemme lämpimästi Eric Evansin kirjaa kaikille, jotka yrittävät rakentaa mikropalveluja

Domain: Edustaa sitä, mitä organisaatio tekee. Alla olevassa esimerkissä se olisi vähittäiskauppa tai verkkokauppa.

Subdomain: Organisaation sisällä oleva organisaatio tai liiketoimintayksikkö. Toimialue koostuu useista alatoimialueista.

Yleiskieli: Tämä on kieli, jota käytetään mallien ilmaisemiseen. Alla olevassa esimerkissä Item on Model, joka kuuluu kunkin osa-alueen Ubiquitous-kieleen. Kehittäjät, tuotepäälliköt, toimialan asiantuntijat ja liiketoiminnan sidosryhmät sopivat samasta kielestä ja käyttävät sitä artefakteissaan – koodissa, tuotedokumentaatiossa ja niin edelleen.

Kuvio 1. Yleiskieli. Ala-alueet ja rajatut kontekstit verkkokaupan toimialueella

Bounded Contexts: Domain-driven design määrittelee Bounded contexts seuraavasti: ”Asetelma, jossa sana tai lausuma esiintyy ja joka määrittää sen merkityksen”. Lyhyesti sanottuna tämä tarkoittaa rajaa, jonka sisällä mallissa on järkeä. Yllä olevassa esimerkissä sanalla ”Item” on erilainen merkitys kussakin näistä konteksteista. Luettelokontekstissa Item tarkoittaa myytävää tuotetta, kun taas ostoskorikontekstissa se tarkoittaa tuotetta, jonka asiakas on lisännyt ostoskoriinsa. Fulfillment-kontekstissa se tarkoittaa varastossa olevaa tuotetta, joka toimitetaan asiakkaalle. Kukin näistä malleista on erilainen, ja kullakin on erilainen merkitys ja mahdollisesti eri attribuutteja. Kun erotamme ja eristämme nämä mallit omiin rajoihinsa, voimme ilmaista mallit vapaasti ja ilman epäselvyyksiä.

Huomautus: On olennaista ymmärtää Subdomains- ja Bounded-kontekstien välinen ero. Subdomain kuuluu ongelma-avaruuteen eli siihen, miten yrityksesi näkee ongelman, kun taas Bounded contexts kuuluu ratkaisuavaruuteen eli siihen, miten toteutamme ratkaisun ongelmaan. Teoriassa kullakin osa-alueella voi olla useita sidottuja konteksteja, vaikka pyrimme yhteen sidottuun kontekstiin per osa-alue.

Miten mikropalvelut liittyvät sidottuihin konteksteihin

Mihin mikropalvelut sitten sopivat? Onko reilua sanoa, että jokainen rajattu konteksti vastaa mikropalvelua? Kyllä ja ei. Näemme kohta, miksi. Voi olla tapauksia, joissa rajatun kontekstin raja tai ääriviiva on melko suuri.

Kuva 2. Rajoitettu konteksti ja mikropalvelut

Tarkastellaan yllä olevaa esimerkkiä. Hinnoittelun rajatussa kontekstissa on kolme erillistä mallia – Hinta, Hinnoitellut kohteet ja Alennukset, jotka vastaavat kukin luettelon kohteen hinnasta, luettelon kohteiden kokonaishinnan laskemisesta ja vastaavasti alennusten soveltamisesta. Voisimme luoda yhden järjestelmän, joka kattaa kaikki edellä mainitut mallit, mutta siitä voisi tulla kohtuuttoman suuri sovellus. Kuten aiemmin mainittiin, jokaisella tietomallilla on omat invarianttinsa ja liiketoimintasääntönsä. Ajan mittaan, jos emme ole varovaisia, järjestelmästä voisi tulla Iso mutapallo, jossa rajat hämärtyvät, vastuualueet menevät päällekkäin ja luultavasti palaamme sinne, mistä lähdimme liikkeelle – monoliitiksi.

Vaihtoehtoinen tapa mallintaa tämä järjestelmä on erottaa tai ryhmitellä toisiinsa liittyvät mallit erillisiksi mikropalveluiksi. DDD:ssä näitä malleja – Hinta, Hinnoitellut kohteet ja Alennukset – kutsutaan Aggregaateiksi. Aggregaatti on itsenäinen malli, joka koostuu toisiinsa liittyvistä malleista. Aggregaatin tilaa voisi muuttaa vain julkaistun rajapinnan kautta, ja aggregaatti varmistaa konsistenssin ja sen, että invariantit pitävät paikkansa.

Formallisesti Aggregaatti on joukko toisiinsa liittyviä objekteja, joita käsitellään yhtenä yksikkönä tietomuutoksia varten. Ulkoiset viittaukset on rajoitettu yhteen AGGREGAATIN jäseneen, jota kutsutaan juureksi. AGGREGATIN rajojen sisällä sovelletaan yhdenmukaisuussääntöjä.

Kuva 3. Mikropalvelut hinnoittelukontekstissa

Ei myöskään ole välttämätöntä mallintaa jokaista aggregaattia erillisenä mikropalveluna. Kuvan 3 palveluiden (aggregaattien) kohdalla näin kävi, mutta se ei välttämättä ole sääntö. Joissakin tapauksissa voi olla järkevää isännöidä useita aggregaatteja yhdessä palvelussa, erityisesti silloin, kun emme täysin ymmärrä liiketoiminta-aluetta. Tärkeää on huomata, että johdonmukaisuus voidaan taata vain yhden aggregaatin sisällä, ja aggregaatteja voidaan muuttaa vain julkaistun rajapinnan kautta. Näiden rikkomisessa on vaarana, että siitä tulee iso mutapallo.

Context maps – A way to carve out accurate microservice boundaries

Toinen olennainen työkalupakki arsenaalissasi on Context maps -konsepti – jälleen Domain Driven Designista. Monoliitti koostuu yleensä erilaisista malleista, jotka ovat useimmiten tiukasti kytkettyjä – mallit tietävät ehkä toistensa intiimit yksityiskohdat, yhden muuttaminen voi aiheuttaa sivuvaikutuksia toiseen ja niin edelleen. Kun monoliitti puretaan, on tärkeää tunnistaa nämä mallit – tässä tapauksessa aggregaatit – ja niiden väliset suhteet. Kontekstikartat auttavat meitä tekemään juuri sen. Niitä käytetään erilaisten rajattujen kontekstien ja aggregaattien välisten suhteiden tunnistamiseen ja määrittelyyn. Kun rajatut kontekstit määrittelevät mallin rajat – hinta, alennukset jne. yllä olevassa esimerkissä – kontekstikartat määrittelevät näiden mallien väliset ja eri kontekstien väliset suhteet. Kun nämä riippuvuussuhteet on tunnistettu, voimme määrittää oikean yhteistyömallin niiden tiimien välillä, jotka toteuttavat nämä palvelut.

Kontekstikarttojen täysipainoinen tutkiminen ei kuulu tämän blogin piiriin, mutta havainnollistamme sitä esimerkin avulla. Alla oleva kaavio esittää eri sovellukset, jotka käsittelevät verkkokauppatilauksen maksuja.

  1. Kori-konteksti huolehtii tilauksen verkkovaltuutuksista; Tilaus-konteksti käsittelee suorituksen jälkeisiä maksuprosesseja, kuten selvityksiä; Yhteyskeskus käsittelee kaikki poikkeustilanteet, kuten maksujen uudelleen yrittämisen ja tilauksessa käytetyn maksutavan muuttamisen
  2. Yksinkertaisuuden vuoksi oletetaan, että kaikki nämä kontekstit on toteutettu erillisinä palveluina
  3. Kaikissa näissä konteksteissa on kapseloitu sama toimintamalli
  4. Huomioi siis, että nämä toimintamallit ovat loogisesti ottaen samat. Toisin sanoen ne kaikki noudattavat samaa Ubiquitous-toimialueen kieltä – maksutapoja, valtuutuksia ja selvityksiä. Väärin määritelty kontekstikartta

    Palvelurajojen uudelleenmäärittely – Kartoita aggregaatit oikeisiin konteksteihin

    Ylläolevassa suunnittelussa (kuva 4) on muutamia ongelmia, jotka ovat hyvin ilmeisiä. Maksuaggregaatti on osa useita konteksteja. On mahdotonta valvoa invariantteja ja yhdenmukaisuutta eri palveluiden välillä, puhumattakaan näiden palveluiden välisistä samanaikaisuusongelmista. Mitä tapahtuu esimerkiksi, jos yhteyskeskus muuttaa tilaukseen liittyvää maksutapaa samalla, kun Tilaukset-palvelu yrittää kirjata aiemmin lähetetyn maksutavan selvitystä. Huomaa myös, että mikä tahansa muutos maksuportissa pakottaisi tekemään muutoksia useisiin palveluihin ja mahdollisesti lukuisiin tiimeihin, koska eri ryhmät voisivat omistaa nämä kontekstit.

    Muutamilla muutoksilla ja kohdistamalla aggregaatit oikeisiin konteksteihin saamme paljon paremman esityksen näistä osa-alueista – Kuva 5. Paljon on muuttunut. Käydään läpi muutokset:

    1. Maksut-aggregaatilla on uusi koti – Maksupalvelu. Tämä palvelu abstrahoi myös Payment gatewayn muista palveluista, jotka tarvitsevat maksupalveluja. Koska yksi rajattu konteksti omistaa nyt aggregaatin, invariantteja on helppo hallita; kaikki transaktiot tapahtuvat saman palvelurajan sisällä, mikä auttaa välttämään rinnakkaisuusongelmia.
    2. Maksut-aggregaatti käyttää korruptionestokerrosta (ACL) eristääkseen ydinalueen mallin maksuliittymän tietomallista, joka on yleensä kolmannen osapuolen palveluntarjoaja ja joka on ehkä sidottu muuttumaan. Sukellamme syvemmälle tällaisen palvelun sovellussuunnitteluun käyttäen Ports and Adapters -mallia tulevassa postauksessa. ACL-kerros sisältää yleensä sovittimet, jotka muuntavat maksuportin tietomallin Payments-aggregaatin tietomalliksi.
    3. Kori-palvelu kutsuu Payments-palvelua suorilla API-kutsuilla, koska ostoskoripalvelun on ehkä suoritettava maksun auktorisointi loppuun sillä aikaa, kun asiakkaat ovat sivustolla
    4. Kirjoita muistiin Tilaukset- ja Maksupalvelun välinen vuorovaikutus. Orders-palvelu lähettää domain-tapahtuman (tästä lisää myöhemmin tässä blogissa). Maksupalvelu kuuntelee tätä tapahtumaa ja viimeistelee tilauksen selvityksen
    5. Kontaktikeskuspalvelulla voi olla monia aggregaatteja, mutta meitä kiinnostaa tässä käyttötapauksessa vain Tilaukset-aggregaatti. Tämä palvelu lähettää tapahtuman, kun maksutapa muuttuu, ja Maksut-palvelu reagoi siihen peruuttamalla aiemmin käytetyn luottokortin ja käsittelemällä uuden luottokortin.

    Kuva 5. Yhteydenottopalvelu. Uudelleen määritelty kontekstikartta

    Monoliittisessa tai vanhassa sovelluksessa on yleensä monia aggregaatteja, joilla on usein päällekkäisiä rajoja. Kontekstikartan luominen näistä aggregaateista ja niiden riippuvuussuhteista auttaa meitä ymmärtämään niiden uusien mikropalveluiden ääriviivat, jotka väännetään näistä monoliiteista. Muista, että mikropalveluarkkitehtuurin menestys tai epäonnistuminen riippuu aggregaattien vähäisestä kytkennästä ja suuresta koheesiosta näiden aggregaattien sisällä.

    On myös tärkeää huomata, että rajatut kontekstit ovat itsessään sopivia koheesiokokonaisuuksia. Vaikka kontekstissa olisi useita aggregaatteja, koko konteksti ja sen aggregaatit voidaan koota yhdeksi mikropalveluksi. Tämä heuristiikka on mielestämme erityisen hyödyllinen aloilla, jotka ovat hieman epäselviä – ajatellaanpa vaikka uutta liiketoiminta-alaa, johon organisaatio on ryhtymässä. Sinulla ei ehkä ole riittävää näkemystä oikeista erottelurajoista, ja aggregaattien ennenaikainen purkaminen voi johtaa kalliiseen refaktorointiin. Kuvittele, että joudut yhdistämään kaksi tietokantaa yhdeksi ja siirtämään tiedot yhteen, koska satuimme huomaamaan, että kaksi aggregaattia kuuluu yhteen. Varmista kuitenkin, että nämä aggregaatit on eristetty riittävästi rajapintojen avulla, jotta ne eivät tiedä toistensa monimutkaisia yksityiskohtia.

    Event Storming – Toinen tekniikka palvelurajojen tunnistamiseen

    Event Storming on toinen olennainen tekniikka aggregaattien (ja siten mikropalvelujen) tunnistamiseen järjestelmässä. Se on hyödyllinen työkalu sekä monoliitteja hajotettaessa että suunniteltaessa monimutkaista mikropalveluiden ekosysteemiä. Olemme käyttäneet tätä tekniikkaa erään monimutkaisen sovelluksemme hajottamiseen, ja aiomme käsitellä kokemuksiamme Event Stormingista erillisessä blogissa. Tämän blogin puitteissa haluamme antaa nopean korkean tason yleiskatsauksen. Katso Alberto Brandellonin video aiheesta, jos olet kiinnostunut tutkimaan asiaa tarkemmin.

    Lyhyesti sanottuna tapahtumamyrsky on sovelluksen – meidän tapauksessamme monoliitin – parissa työskentelevien tiimien välinen aivoriihi, jonka tarkoituksena on tunnistaa erilaiset järjestelmän sisällä tapahtuvat tapahtumat ja prosessit. Tiimit tunnistavat myös aggregaatit tai mallit, joihin nämä tapahtumat vaikuttavat, ja niiden mahdolliset myöhemmät vaikutukset. Kun tiimit tekevät tätä harjoitusta, ne tunnistavat erilaisia päällekkäisiä käsitteitä, moniselitteistä toimialueen kieltä ja ristiriitaisia liiketoimintaprosesseja. He ryhmittelevät toisiinsa liittyviä malleja, määrittelevät aggregaatit uudelleen ja tunnistavat päällekkäiset prosessit. Harjoituksen edetessä tulee selväksi, mihin rajattuihin yhteyksiin nämä aggregaatit kuuluvat. Event Storming -työpajat ovat hyödyllisiä, jos kaikki tiimit ovat yhdessä huoneessa – fyysisessä tai virtuaalisessa – ja alkavat kartoittaa tapahtumia, komentoja ja prosesseja scrum-tyyliselle taululle. Tämän harjoituksen päätteeksi alla ovat tavanomaiset tulokset:

    1. Määritelty luettelo aggregaateista. Näistä tulee mahdollisesti uusia mikropalveluita
    2. Tapahtumat, joiden täytyy virrata näiden mikropalveluiden välillä
    3. Käskyt, jotka ovat suoria kutsuja muilta sovelluksilta tai käyttäjiltä

    Alhaalla on näytetty esimerkkitaulu Event Storming -työpajan lopussa. Se on loistava yhteistyöharjoitus, jonka avulla tiimit voivat sopia oikeista aggregaateista ja rajatuista konteksteista. Tapahtumamyrskytaulu

    Mikropalveluiden välinen kommunikaatio

    Kerrataksemme nopeasti, monoliitti isännöi useita aggregaatteja yhden prosessirajan sisällä. Näin ollen aggregaattien yhdenmukaisuuden hallinta tämän rajan sisällä on mahdollista. Jos esimerkiksi asiakas tekee tilauksen, voimme pienentää tuotteiden inventaariota ja lähettää asiakkaalle sähköpostin – kaikki yhden transaktion sisällä. Kaikki operaatiot joko onnistuvat tai epäonnistuvat. Mutta kun hajotamme monoliitin ja hajautamme aggregaatit eri konteksteihin, meillä on kymmeniä tai jopa satoja mikropalveluja. Prosessit, jotka tähän asti olivat yhden monoliitin rajojen sisällä, ovat nyt levinneet useisiin hajautettuihin järjestelmiin. Transaktioiden eheyden ja yhdenmukaisuuden saavuttaminen kaikissa näissä hajautetuissa järjestelmissä on hyvin vaikeaa, ja sillä on hintansa – järjestelmien käytettävyys.

    Mikropalvelut ovat myös hajautettuja järjestelmiä. Näin ollen CAP-teoreema pätee myös niihin – ”hajautettu järjestelmä voi tarjota vain kaksi kolmesta halutusta ominaisuudesta: johdonmukaisuuden, saatavuuden ja ositusten sietokyvyn (CAP:n ’C’, ’A’ ja ’P’).” Reaalimaailman järjestelmissä osioiden sietokyky ei ole neuvoteltavissa – verkko on epäluotettava, virtuaalikoneet voivat kaatua, alueiden välinen viive voi huonontua ja niin edelleen.

    Siten meille jää valittavaksi joko saatavuus tai johdonmukaisuus. Nyt tiedämme, että missään nykyaikaisessa sovelluksessa käytettävyyden uhraaminen ei myöskään ole hyvä idea.

    Kuva 7. CAP-teoreema

    Suunnittele sovellukset mahdollisen yhdenmukaisuuden ympärille

    Jos yrität rakentaa transaktioita useiden hajautettujen järjestelmien välille, päädyt taas monoliittien maahan. Tällä kertaa se on vain pahinta laatua, hajautettu monoliitti. Jos jokin järjestelmistä menee epäkuntoon, koko prosessi menee epäkuntoon, mikä johtaa usein turhauttavaan asiakaskokemukseen, epäonnistuneisiin lupauksiin ja niin edelleen. Lisäksi muutokset yhteen palveluun saattavat yleensä aiheuttaa muutoksia toiseen palveluun, mikä johtaa monimutkaisiin ja kalliisiin käyttöönottoihin. Näin ollen on parempi suunnitella sovellukset niin, että käyttötapaukset räätälöidään siten, että ne sietävät hieman epäjohdonmukaisuutta käytettävyyden hyväksi. Yllä olevassa esimerkissä voimme tehdä kaikista prosesseista asynkronisia ja siten lopulta johdonmukaisia. Voimme lähettää sähköposteja asynkronisesti muista prosesseista riippumatta; Jos luvattua tuotetta ei ole myöhemmin varastossa saatavilla, tuote voidaan tilata takaisin, tai voimme lopettaa tuotteen tilausten vastaanottamisen tietyn kynnysarvon ylittyessä.
    Tällöin saatat kohdata skenaarion, joka saattaa vaatia vahvoja ACID-tyylisiä transaktioita kahden aggregaatin välillä eri prosessien rajoissa. Se on erinomainen merkki tarkastella näitä aggregaatteja uudelleen ja ehkä yhdistää ne yhdeksi. Event Storming ja Context Maps auttavat tunnistamaan nämä riippuvuudet jo varhaisessa vaiheessa, ennen kuin aggregaatteja aletaan pilkkoa eri prosessirajoihin. Kahden mikropalvelun yhdistäminen yhdeksi on kallista, ja sitä meidän tulisi pyrkiä välttämään.

    Suosikaa tapahtumapohjaista arkkitehtuuria

    Mikropalvelut voivat lähettää olennaisia muutoksia, jotka tapahtuvat niiden aggregaateissa. Näitä kutsutaan toimialueen tapahtumiksi, ja kaikki näistä muutoksista kiinnostuneet palvelut voivat kuunnella näitä tapahtumia ja ryhtyä vastaaviin toimiin toimialueillaan. Tällä menetelmällä vältetään käyttäytymiskytkentä – yksi toimialue ei määrää, mitä muiden toimialueiden pitäisi tehdä, ja ajallinen kytkentä – prosessin onnistunut loppuunsaattaminen ei riipu siitä, että kaikki järjestelmät ovat käytettävissä samaan aikaan. Tämä tarkoittaa tietenkin sitä, että järjestelmät ovat lopulta yhdenmukaisia.

    Kuvio 8. Järjestelmät ovat lopulta yhdenmukaisia. Tapahtumapohjainen arkkitehtuuri

    Yllä olevassa esimerkissä Tilaukset-palvelu julkaisee tapahtuman – Tilaus peruutettu. Muut palvelut, jotka ovat tilanneet tapahtuman, käsittelevät oman toimialueensa toimintoja: Payment-palvelu palauttaa rahat, Inventory-palvelu säätää tuotteiden varastoa ja niin edelleen. Muutamia huomioitavia asioita tämän integraation luotettavuuden ja joustavuuden varmistamiseksi:

    1. Tuottajien tulisi varmistaa, että ne tuottavat tapahtuman vähintään kerran. Jos tässä epäonnistutaan, heidän tulisi varmistaa, että on olemassa varamekanismi, jolla tapahtumat voidaan käynnistää uudelleen
    2. Kuluttajien tulisi varmistaa, että ne kuluttavat tapahtumia idempotenttisesti. Jos sama tapahtuma toistuu, kuluttajan päässä ei pitäisi olla mitään sivuvaikutuksia. Tapahtumat voivat saapua myös epäjärjestyksessä. Kuluttajat voivat käyttää aikaleima- tai versionumerokenttiä taatakseen tapahtumien ainutkertaisuuden.

    Joidenkin käyttötapausten luonteen vuoksi tapahtumapohjaista integraatiota ei välttämättä ole aina mahdollista käyttää. Tutustu ostoskoripalvelun ja maksupalvelun väliseen integraatioon. Se on synkroninen integraatio, ja siksi siinä on muutamia asioita, joita on syytä varoa. Se on esimerkki käyttäytymiseen perustuvasta kytkennästä – Ostoskoripalvelu kutsuu kenties maksupalvelun REST API:ta ja kehottaa sitä hyväksymään tilauksen maksun – ja ajallisesta kytkennästä – maksupalvelun on oltava käytettävissä, jotta Ostoskoripalvelu voi hyväksyä tilauksen. Tällainen kytkentä vähentää näiden kontekstien itsenäisyyttä ja saattaa aiheuttaa ei-toivottua riippuvuutta. On olemassa muutamia tapoja välttää tämä kytkentä, mutta kaikilla näillä vaihtoehdoilla menetämme kyvyn antaa välitöntä palautetta asiakkaille.

    1. Muunnetaan REST API tapahtumapohjaiseksi integraatioksi. Tämä vaihtoehto ei kuitenkaan välttämättä ole käytettävissä, jos maksupalvelu paljastaa vain REST-API:n
    2. Cart-palvelu hyväksyy tilauksen välittömästi, ja on olemassa erätyö, joka poimii tilaukset ja kutsuu maksupalvelun API:ta
    3. Cart-palvelu tuottaa paikallisen tapahtuman, joka sitten kutsuu maksupalvelun API:ta

    Yllämainittujen yhdistelmällä, jossa on uudelleenkokeiluja vikojen ja ylempänä olevan riippuvuuden – maksupalvelun – saavuttamattomuuden varalta, voidaan saada aikaan paljon kestävämpi rakenne. Esimerkiksi ostoskorin ja maksupalvelun välistä synkronista integraatiota voidaan tukea tapahtumalla tai eräpohjaisilla uudelleenkäynnistyksillä vikatilanteissa. Tällä lähestymistavalla on lisävaikutus asiakaskokemukseen – asiakkaat ovat saattaneet syöttää virheellisiä maksutietoja, eikä meillä ole niitä verkossa, kun käsittelemme maksut offline-tilassa. Tai epäonnistuneiden maksujen takaisinperinnästä voi aiheutua yritykselle lisäkustannuksia. On kuitenkin todennäköistä, että hyödyt, joita saadaan siitä, että ostoskoripalvelu kestää paremmin maksupalvelun käyttökyvyttömyyttä tai vikoja, ovat suuremmat kuin puutteet. Voimme esimerkiksi ilmoittaa asiakkaille, jos emme pysty keräämään maksuja offline-tilassa. Lyhyesti sanottuna käyttäjäkokemuksen, häiriönsietokyvyn ja käyttökustannusten välillä on kompromisseja, ja on viisasta suunnitella järjestelmät pitäen nämä kompromissit mielessä.

    Välttäkää orkestrointia palveluiden välillä kuluttajakohtaisten tietotarpeiden vuoksi

    Yksi minkä tahansa palvelukeskeisen arkkitehtuurin antikuvioista on se, että palvelut palvelevat kuluttajien erityisiä käyttötapoja. Yleensä näin tapahtuu, kun kuluttajatiimit tekevät tiivistä yhteistyötä palvelutiimien kanssa. Jos tiimi työskentelee monoliittisen sovelluksen parissa, se luo usein yhden API:n, joka ylittää eri aggregaattien rajat, jolloin nämä aggregaatit kytkeytyvät tiukasti toisiinsa. Tarkastellaanpa esimerkkiä. Sanotaan, että Web- ja mobiilisovellusten Tilauksen tiedot -sivun on näytettävä yhdellä sivulla sekä Tilauksen tiedot että Tilauksen yhteydessä käsiteltyjen palautusten tiedot. Monoliittisessa sovelluksessa Order GET API – olettaen, että kyseessä on REST API – kysyy tilauksia ja palautuksia yhdessä, yhdistää molemmat aggregaatit ja lähettää yhdistetyn vastauksen kutsujille. Tämä on mahdollista tehdä ilman suurta ylikuormitusta, koska aggregaatit kuuluvat samaan prosessirajaan. Näin kuluttajat saavat kaikki tarvittavat tiedot yhdellä kutsulla.

    Jos Orders ja Refunds kuuluvat eri konteksteihin, tiedot eivät enää ole yhden mikropalvelun tai aggregaatin rajan sisällä. Yksi vaihtoehto saman toiminnallisuuden säilyttämiseksi kuluttajille on se, että Order-palvelu vastaa Refunds-palvelun kutsumisesta ja luo yhdistetyn vastauksen. Tämä lähestymistapa aiheuttaa useita ongelmia:

    1. Tilauspalvelu integroituu nyt toiseen palveluun pelkästään niiden kuluttajien tukemiseksi, jotka tarvitsevat palautustietoja yhdessä tilaustietojen kanssa. Tilauspalvelu on nyt vähemmän itsenäinen, koska kaikki muutokset palautusaggregaatissa johtavat muutokseen tilausaggregaatissa.

    2. Tilauspalvelulla on toinen integraatio ja siten toinen vikaantumispiste, joka on otettava huomioon – jos palautuspalvelu ei toimi, voiko tilauspalvelu silti lähettää osittaista dataa ja voivatko kuluttajat vikaantua tyylikkäästi?

    3. Jos kuluttajat tarvitsevat muutosta, jotta he voivat noutaa enemmän dataa palautusaggregaatista, kaksi tiimiä osallistuu nyt muutoksen tekemiseen

    4. Jos tätä mallia noudatetaan koko alustalla, se voi johtaa monimutkaiseen riippuvuuksien verkostoon eri toimialueen palveluiden välillä, ja kaikki tämä johtuu siitä, että nämä palvelut palvelevat soittajien erityisiä käyttötapoja.

    Backend for Frontends (BFFs)

    Tämän riskin pienentämiseksi voidaan käyttää lähestymistapaa, jossa kuluttajatiimit hoitavat eri toimialueen palveluiden välisen orkestraation. Loppujen lopuksi kutsujat tuntevat pääsymallit paremmin, ja he voivat hallita täysin kaikki näihin malleihin tehtävät muutokset. Tämä lähestymistapa irrottaa toimialueen palvelut esitystasosta, jolloin ne voivat keskittyä keskeisiin liiketoimintaprosesseihin. Mutta jos verkko- ja mobiilisovellukset alkavat kutsua eri palveluita suoraan monoliitin yhden yhdistetyn API:n sijaan, se voi aiheuttaa näille sovelluksille suorituskyvyn ylikuormitusta – useita kutsuja pienemmän kaistanleveyden verkoissa, eri API:ista tulevan datan käsittelyä ja yhdistämistä ja niin edelleen.

    Sen sijaan voitaisiin käyttää toista mallia nimeltä Backend for Front-ends. Tässä suunnittelumallissa kuluttajien – tässä tapauksessa web- ja mobiilitiimien – luoma ja hallinnoima backend-palvelu huolehtii integraatiosta useiden toimialueiden palveluiden välillä pelkästään front-end-kokemuksen tuottamiseksi asiakkaille. Web- ja mobiilitiimit voivat nyt suunnitella datasopimukset niiden käyttötapausten perusteella, joita ne palvelevat. He voivat jopa käyttää GraphQL:ää REST API:iden sijasta tehdäkseen joustavia kyselyjä ja saadakseen takaisin juuri sen, mitä he tarvitsevat. On tärkeää huomata, että tämän palvelun omistavat ja ylläpitävät kuluttajatiimit eivätkä toimialueen palveluja omistavat tiimit. Front-end-tiimit voivat nyt optimoida tarpeidensa mukaan – mobiilisovellus voi pyytää pienempää hyötykuormaa, vähentää mobiilisovelluksen kutsujen määrää ja niin edelleen. Tutustu alla olevaan orkestroinnin uudistettuun näkymään. BFF-palvelu kutsuu nyt sekä Orders- että Refunds-toimialueen palveluita käyttötapaustaan varten.

    Kuva 9. Backend for Frontends

    BFF-palvelun rakentaminen varhaisessa vaiheessa on myös hyödyllistä, ennen kuin katkaistaan monoliittisesta monoliittisesta yksiköstä lukuisa joukko palveluja. Muuten joko toimialueen palveluiden on tuettava toimialueiden välistä orkestrointia tai web- ja mobiilisovellukset joutuvat kutsumaan useita palveluita suoraan front-endistä. Molemmat vaihtoehdot johtavat suorituskyvyn ylikuormitukseen, heitteillejätettävään työhön ja tiimien välisen autonomian puutteeseen.

    Johtopäätös

    Tässä blogissa käsittelimme erilaisia käsitteitä, strategioita ja suunnittelun heuristiikkoja, joita kannattaa ottaa huomioon, kun uskaltaudumme mikropalveluiden maailmaan, tarkemmin sanottuna silloin, kun yritämme pilkkoa monoliittisen monoliittisen palvelukokonaisuuden useiksi toimialueisiin pohjautuviksi mikropalveluiksi. Monet näistä ovat laajoja aiheita itsessään, emmekä mielestäni ole tehneet tarpeeksi oikeutta selittää niitä yksityiskohtaisesti, mutta halusimme esitellä joitakin kriittisiä aiheita ja kokemuksiamme niiden käyttöönotosta. Further Reading (linkki) -osiossa on joitakin viitteitä ja hyödyllistä sisältöä kaikille, jotka haluavat jatkaa tätä polkua.

    Päivitys: Sarjan kaksi seuraavaa blogia on julkaistu. Näissä kahdessa blogissa käsitellään Cart-mikropalvelun toteuttamista koodiesimerkkien avulla käyttäen Domain-Driven Design -periaatteita sekä Ports and Adapters -suunnittelumalleja. Näiden blogien pääpaino on havainnollistaa, miten nämä kaksi periaatetta/mallia auttavat meitä rakentamaan modulaarisia sovelluksia, jotka ovat ketteriä, testattavia ja refaktoroitavia – lyhyesti sanottuna kykenevät vastaamaan nopeatempoiseen ympäristöön, jossa me kaikki toimimme.

    Implementing Cart Microservice using Domain Driven Design and Ports and Adapters Pattern – Part 1

    Implementing Cart Microservice using Domain Driven Design and Ports and Adapters Pattern – Part 2

    Further Reading

    1. Eric Evansin Domain Driven Design

    2. Vaughn Vernonin Implementing Domain Driven Design

    3. Martin Fowlerin artikkeli Microservices

    4. Sam Newmanin Building Microservices

    5. Event storming

    7. Backend for Frontends

    8. Fallacies of distributed computing

Vastaa

Sähköpostiosoitettasi ei julkaista.