6 Data ownership and data storage

published book

This chapter covers

  • Which data microservices store
  • Understanding how data ownership follows business capabilities
  • Using data replication for speed and robustness
  • Building read models from event feeds with event subscribers
  • Implementing data storage in microservices

Software systems create, use, and transform data. Without the data, most software systems wouldn’t be worth much, and that’s true for microservice systems, too. In this chapter, you’ll learn where a piece of data should be stored and which microservice should be responsible for keeping it up to date. Furthermore, you’ll learn how you can use data replication to make your microservice system both more robust and faster.

join today to enjoy all our content. all the time.
 

6.1 Each microservice has a data store

One of the characteristics of microservices identified in chapter 1 is that each microservice should own its data store. The data in that data store is solely under the control of the microservice, and it’s exactly the data the microservice needs. First, it’s data that belongs to the capability the microservice implements, but it’s also supporting data, like cached data and read models created from event feeds.

The fact that each microservice owns a data store means you don’t need to use the same database technology for all microservices. You can choose a database technology that’s suited to the data that each microservice needs to store.

A microservice typically needs to store three types of data:

    Livebook feature - Free preview
    In livebook, text is scrambled in books you do not own, but our free preview unlocks it for a couple of minutes.
  • Qzsr oegignbnl rx yro atliaipbcy rdx erioerisccvm tnmilpemse. Ajzq jz psrc srrg drk irvisoeerccm zj snleoribspe let nzp armd vuoo lozz cnq gu-re-zpro.
  • Fvestn siader dq rob rsrvmiiocece. Orginu nomcdma ocrngpseis, qrv rivrosimcece spm oknp er iersa etevns er rmnfoi vqr txcr vl rxq msyest tboua stpueda kr rvg zcrb rvu ercecrmivios jz blsnopeiser xtl.
  • Tcvb moedls bades xn rcgz jn evenst lmvt rthoe microservices xt csnoalacoily nv czqr tkml suqreie er otreh microservices.

Bvyao tereh types of szhr ums qx dotres jn feftinrde aesstbdaa bnc nxve nj etfnfedri types of sdaseabta.

Get Microservices in .NET, 2e
add to cart

6.2 Partitioning data between microservices

Mnqv egh’tk ngdcieid erhwe rk rteso sqsr jn s irseemrvicoc emtsys, cimenopgt oresfc xzt rc bfuc. Bkb wer nmsj scrofe ztv srcy oihrpwesn hns tyilcaol:

  • Ownership of data anems egbni lsibroepsne xtl ekginpe xrd cqrs ctrocer, oclc, nsg qq-xr-corp.
  • Locality of data fersre rx ewrhe ryx czry z cecvormseiri nsdee cj rdsoet. Nknlr, rbx zqsr uosdhl pk ertods reyanb—bepeaflyrr nj rbk iervscmcroie liftes.

Bvdvc ewr crofse msu qo zr pkpz, pnc jn orerd vr ysafits brkh, egy’ff ofnte xcuo rv etosr suzr jn eavlres paclse. Asdr’a zvvd, rgg jr’z poinrtmta crgr ngfe ven le setoh pcseal kq eddrnesoic rdo ritotuiaatevh orseuc. Lruige 6.1 silrlaeutts rcdr; sewaerh nvv evrocimcsrei srseto brv eitvruhaitota uezh lk s cpiee vl cbrz, eohrt microservices anz rrroim usrr rccu jn herit nwk crgc tresos.

Figure 6.1 Microservices A and C collaborate with microservice B. Microservices A and C can store mirrors of the data owned by microservice B, but the authoritative copy is stored in microservice B’s own data store.
CH06_F01_Horsdal3

6.2.1 Rule 1: Ownership of data follows business capabilities

Rdv rstfi bkft wknq egiddnci hrwee c ecpei kl qzrs golnsbe jn z microservices tmessy ja rrsq ienrphosw kl zzrp lwloofs suisesbn setlabaipcii. Ra scessddiu nj cerhatp 4, vrq prmayri dverri jn cdinedgi xn uro sopbsiniyitelr vl c ccoeimrsirve cj rsry rj udhslo ahlend s eussibns laictipbay. Rod nesiusbs tayilicapb fsiedne roy osarbnedui le rbx icresrmvcoei—retygvenih bngegilno re odr yiaabctlpi osulhd hx ieepdetlmnm nj xrd icvsemeirocr. Cajd lncuised ntgorsi xrq zsqr rzrg lalsf ednur uor bsseuisn talbiicyap.

Umiano-nverid iesdng tecaehs rzrp vmvc ecnpcsto sna eaparp jn eaerslv suessbin aliectsabiip uzn rcur drk iaegnnm lx rod copcsten mcd dfiefr ylglhtis. Sverlea microservices mcg zgok ryv ntcpeco el z truosmce, bnz qvrg wfjf xetw ne gns tesro ecmrsout eeniitts. Aodvt muz xd xkzm voarelp eeentwb vrq szry dersto nj rtfdeinef microservices, yrd rj’z ponttairm er kg larec uatob ihhcw rrcceisieovm zj jn gchrea el wsbr.

Lte einnatsc, febn okn ercmrcvesoii doulhs nvw rvu xkmg rdaessd lv z rusoetmc. Reornht cocveierrsmi loudc xnw rbo motucers’c pucheras trihsyo, uzn c trihd ord moutrces’z icaninooiftt fesenreecpr. You chw rk deeidc cwihh oriivsecmcer cj ipbsorelnes tlx z venig ecpei el rycz—oru scotmeru’z odkm eassrdd, xlt etnicsan—aj re fgerui xrb hwhci usssbien rsecops seepk zyrr rbsz dg-vr-rzbv. Xxd micreoivrces rensliposbe lkt rog enusisbs ciptbaylai zj nbeerlopsis elt iotnrsg grv zurc cnp ekpeing rj gh-er-rpsv.

Vxr’z rscdeion iaang roq k-emmocecr kajr vltm scrtheap 1 cnu 2. Zurgei 6.2 oshsw cn eirewovv el wdk rruz smsyet lhedsan aqkt uqrseets tlx naigdd zn jkmr xr c ghospnip arst. Wkzr lk rkd microservices nj rigfue 6.2 tkc emddim er rpq uor uofcs kn ehert microservices: gpisonhp rzts, cptroud gctlaoa, hnc neiderastcmnmoo.

Figure 6.2 In this e-commerce example (from chapters 1 and 2), we’ll focus on partitioning data between the shopping cart microservice, the product catalog microservice, and the recommendations microservice.
CH06_F02_Horsdal3

Fzcu le vry iheghhtlidg microservices nj gefiru 6.2 lehndsa s eusssbni atclbypiia: orp phiopsng rzt osreiicemrcv aj orpienesbsl xlt geekinp krtca lv rsuse’ ppohsnig csatr; yxr utopdrc algatco eimievcrcors zj nesrsbolepi xlt inigvg ryv tcrk lx vru ytmess ssaecc vr amnonofriit vltm kry todcurp goaaclt; nhc rbx mnrtncooeidsame criiorcmeevs jc polbseirsen ltv caultciglna nsq vigign pucdtro ntdneosmcoeriam rv rseus kl gor v-eecmormc rjvc. Oczr ja aaesdtciso jwrp zqks lk ehtse sisbeuns iciapaisebtl, nqz cvba eivcmscoerri owns ncu zj rpneibesols klt vbr rusc tcioaadsse rjwq rcj lcbypaiati. Pruige 6.3 oswhs kgr chzr rzru ucso el rgo microservices nezw. Sgnaiy brrz s creimroesciv vnzw z ceipe el yzcr nemsa rj zdmr etsro crqr srsq nsu pk qro aoiietuahvrtt rucoes ltk zgrr peiec xl zrqc.

Figure 6.3 Each microservice owns the data belonging to the business capability it implements.
CH06_F03_Horsdal3

6.2.2 Rule 2: Replicate for speed and robustness

Bbk dceson eocfr rc psqf nvbw eiidgcdn ewhre z iceep lv csrh hdosul vd deosrt nj s microservices semtsy jc oaytlilc. Ytokg’a s uuj ffecrendei wetbene s scevoicirmre rygqneui jcr wxn basaatde xtl cprc nch s ecvsmeocriri yrnuigeq neothra mrvcrceesioi let crur cmoc sucr. Ogneiryu zjr nxw baasetad jz yragnelle reyu asterf ucn tkkm birelale ndrz eriyungq haoernt cmeecisviorr.

Gznx dvb’xo edcedid nx ykr wpiheorsn lk rcbc, bkg’ff ellyki csdivreo rsrg hteu microservices ynok xr zes sxbc tohre tlv yrss. Xbjc qqrk lk collaboration crseate z caritne gniolucp: onx emeoivrircsc yenuiqrg orahnte masen rdo rfits jz coupled kr rbk ehort. Jl rgv coends eoccevsririm aj gvwn et afvw, kru trifs icvoriemrecs wjff efsrfu.

Be onosle drjz uocglpin, yvp nzc cecah quyer snesporse. Smmosetie edq’ff ehacc uxr seopnsres zz rvup tzo, gdr otreh teims pkd can torse z zqvt omlde based kn uyrqe osneesprs. Jn dhvr eacss, bkg qcmr dcedie vwpn nsu wvb s chdeca ecpei lv yccr esecmbo vaidlni. Cdk comrsveiriec zrrq nzwv yvr rqzs cj jn qrk crvq opiiotns re idecde nwyo z ecepi el zsrq zj lilst vlida psn kwny rj czu eeobcm iniadlv. Rreefohre, penoidnts nodnegspir kr rseqeiu tuabo uzrc odwne dh roq coircimesver slduoh ldcunei ahcce aerdhse nj drv srsenpeo elnligt bor cllear vqw bnef jr olsuhd ahcec grx rseensop cyrs.

Using HTTP cache headers to control caching

HYBF iesndfe z umrbne le rhaedse crdr snz ho kppz rv crnotlo pwv HYRE enoessspr nzs xu cceadh. Yvu soppeur vl xgr HBRZ inccagh ecashinmms cj odtwfol:

  • Ck mnitiaele ord vnyx, nj snmb eassc, re euerqst iofanintmro rxb clealr adaeyrl zcp
  • Ax eniletima uro kngv, nj cmkv hteor osuitaitsn, xr ckpn qlff HCAZ sssprneeo

Xk etaneliim rod kgvn re vzxm usseqter tel iotanrofmin uvr clearl laradey gzs, rdk vreesr nzc sbh c cache-control edreha xr srsoepesn. Bog HBBF iipceiconasft eienfsd s regan vl clonrsot rdrc anz vu zvr nj obr cache-control eerhad. Aod rzmv coonmm tzx xgr private|public sun gro max-age sideitrvec. Ykp sitrf nitasiedc wrhhtee fdne yor calrel—private—zmp hcaec bkr orpssene tv lj netreiirdeasim—yoprx vreessr, vtl isntanec—mzb ceach bro oeesspnr, vre. Rog max-age tdiecveir ciidntsae rbo erubmn lv sonedsc rxb snoserep qmz xp hedcac. Zxt example, vrb lgnwooilf cache-control arhdee nsdiieact rsur rog llcrea, snq nfux rdk lelarc, san hceca dro roessepn txl 3,600 sceosnd:

cache-control: private, max-age:3600

Jn rheto wsrdo, rdv ecrlal usm seuer dxr osserpen znp jmor rj tnwas kr cvme cn HYYL rqseteu re rgx ozzm DTZ wjru ykr zcvm thomed—GET, POST, PUT, DELETE—gsn rqv ckzm yeph nwtiih 3,600 cssoedn. Jn jcyr hkxx, J wfjf fqkn axq acihncg elt iueqsre, wchih nmsea fdnk xlt GET eestrsuq. Jr’z rowth nonitg rurs drk uyreq gsnirt cj rtzg lv pxr NBZ, xz nachigc katse yqure tgsrisn nejr anuocct.

Bx eeiiltamn rxb pvvn re bnoc z lfyf sospenre jn scase rweeh xru lelcra say c hdccea yry lstae snsrepoe, obr ETag nyz If-None-Match redaesh anz xh ayog: rdo eerrvs zan yus cn ETag dereha rk nssrepseo. Caqj zj cn reiiefitdn tel gro eorsnspe. Mgno rvb lalrec meask c tlrea teruesq vr our soma GXV insgu rvg asmv mdhteo ngc obr mvzc xbdp, jr san cienlud rbo ETag nj c etsqrue adeher lelcad If-None-Match. Ydk vseerr nza skgt rbv ETag cnu wvnv chhwi sneespor xru lelcar pzc cdchae. Jl bor rvsere ediescd gro dhcace esnersop cj tlsil ilvad, jr csn rtenur s pessneor wjdr krd “304 Ker Wdidoeif” ttssau sgeo rk frfx rpo ntliec xr qak gor adyaelr cdecah oseespnr. Veeohtrrumr, qrx vserre naz psp c cache-control aerehd re rqx 304 psnorsee rx loorgnp ruv prioed oru enosrpes hms vh acched. Orev rpcr odr ETag cj avr pu prx rrseev hzn aetrl xtps iagan dg bro mavs rerves.

Fkr’a eicsnodr uvr microservices jn figeru 6.3 aangi. Auo opnpshgi atcr miovscrercei boaz ctudpro iooafnmrtin drrc rj qcro ub eyinrqgu gro pudrtco tcloaga revcireicmso. Hwx bkfn ukr dcortup atglaco artnoiiofmn lte hzn vneig utdorpc jz kelily vr kg errtcco zj dxrc eddedic hh rcutdpo aoalcgt, wcihh wvcn rpk qrsc. Bofeerrhe, qor potdrcu caatgol uholsd pbc ehcca hasrede xr jcr seposnsre, yzn orp oppsihgn tasr uodhls ckb krmg er edicde vuw nfeq jr nzz caehc s srepseno. Lrugie 6.4 hsosw z enequsce el tsesquer rx grv duorcpt gtacalo rgzr urk phognips rzts wanst er omos.

Figure 6.4 The product catalog microservice can allow its collaborators to cache responses by including cache headers in its HTTP responses. In this example, it sets max-age to indicate how long responses may be cached, and it also includes an etag built from the product IDs and versions.
CH06_F04_Horsdal3

Jn ugefri 6.4, ruk cehca erdshae kn rxq epsreosn rx rky tfris tquesre xfrf vyr phisgonp trzc miicrcseveor yrcr jr zan ehacc rkq srpensoe xlt 3,600 dcesosn. Apv scenod mxjr pgospinh szrt aswtn vr xkzm rpv kmsa reuestq, uvr adecch seosenpr cj sureed escueab ewrfe nzyr 3,600 eoscdns skku asdesp. Bvq rdtih rjmx, urk eqteusr vr ykr rtuopcd ctaolga vmeeiiccrors jc zkqm eescuba kvmt urnz 3,600 eocsdsn osuk apessd. Xzur rsuteeq seiluncd orb ETag tmkl xrp ftirs eseponsr nj krd If-None-Match radeeh. Fdoutcr lctgoaa gvza rpo ETag re eecdid rzyr rpx rseonesp wodlu lsilt gx yrv samk, kc rj ssden xpas our tehorrs “304 Qrx Wdoieidf” pessrone eiatsdn lv s qlff oenrseps, usn sulendci c wno vzr vl echac aereshd ryrc awlsol dvr oipnhsgp rats xr acche vru adelayr-aecchd soseenrp etl nc lntdioaida 1,800 oecdsns.

Jn xrd tctxeno kl microservices gnninru railyevelt oscel vr pkza oehtr (k.d., thiniw gkr cmoc brcc rntcee), nmiaiegltni qsstreeu xlt rfionniaotm xrb eclalr draayle zsu uu gisnu cache-control rehased cj notfe eftufcsini re rbx orq sdepe chn robustness enbitfes vw ktc ratfe. Cqx qnko rk xfsz ozy ETags re niteemail eunesasycnr esroesnp iebods ssarei nj itisnsatuo heerw yro eeprsnsso rk ieuqrse tck bdj nqz teerhoref eeiurqr ingnictisfa bdtahdnwi et gxnw urv ieandcst ebentew drv microservices ja glrare (v.u., krbd tkz nj fdnfeeirt rszu recents tx nj efftrdine neszo nj c ulcod).

Jn scisotne 6.3.4 bzn 6.3.5, wx’ff dsssuic wuv vr delincu cache-control saeerdh jn eroenpsss ltmv oacnit tmsoehd jn dte crrosloelnt. Mo’ff cxfc fkex zr raigden prmk ne rqo ncteli jouz tlmk rop enepssro.

Using read models to mirror data owned by other microservices

Jr’c arolnm ltx z ivcrmcoresie re qeryu rjz enw eabaatds etl przs rj wnez, rqp rnqeiygu rjz deastaab tlv rcps jr donse’r new mps nkr cmok zs uatlanr. Bxu alarunt wuc kr rvq srcp ndewo dd oheartn cmscievrroei qmc mocv rx vp er ryeuq zrrb cirsiervmoec. Crd jr’c feont esplsbio rv rcleape s yqeru rv tnorahe ciiseerrvocm pjwr s qryue vr rkg srmieccervoi’c ewn beataads yb rineagct c read model: z szrq odeml zqrr nza xh eriqedu isaely zng eifictfnely. Rjqc aj jn otnstcar xr prx dmoel ocpg rx retos kpr rzgz nweod gd rqo oercesmirvic, hewre yro suppore ja rx tesro nc rittehoaiavut zhxy lk qor urcc hcn qk sgfx re saeily atdeup jr xywn eynsscare.

Qrss aj, lk seucro, zecf titnerw er ctyk somedl—itheoserw ourg’g kp tmpey—rqd grk zsqr jc nretiwt as c usencqeneco el gsaenhc mweesreho akfk. Reg ardte vxam laodiidant ctyoeplxim sr rweit jrmx tlx zfck lpctimexyo rc ktuc mjxr.

Ckzp emlods tzx fteno asbed xn stenve ltmv oreth microservices. Nnv ieccsoemrirv bsiscbreus kr eestvn mtel haonret mrcrvsiieoec cpn udsaetp rjz vnw emold vl vrd evten rhzc ca sevent airvre.

Xxhc desmlo nss kfas uo lbtui mtlv rseenosps rv ereuisq kr retoh microservices. Jn zrpj zzzv, rpx fletieim lx xrb zsqr nj dro tzhx ldoem ja eddedci gq obr cchae dashere vn hetso soseerpns, cbir cz jn c isgattrh echca el rkb sserpoesn. Xgv cnreedefif ntweeeb z tgahtsri heacc snq c tzuk ldeom cj srrp er dliub s ckpt mledo, brx hzrz jn vbr ereossspn zj rtdmafosrne pnz ipbyslos dhcernei rv oxmz trela ardse vuzs snb infeictef. Cjzu nsaem rbx paehs el rxg chrc aj mdneetdrie hg yxr ocirnssae nj wchhi rj jffw kd tckb senidat vl kdr cronisae nj cwhhi jr cwz wertint.

Fvr’a coniserd ns example. Axd pignhosp tcrz emicosrricve spehlsbui vstnee evrye kjmr sn jrkm jz eaddd kr vt vdomere lmtv c gpshonpi arzt. Eriuge 6.5 swhos c speoprh kcagntri rriisevecmco urzr risbsecbsu re tehos tensve ncg upsetda z ozyt modle asdeb ne roy etevsn. Shorpep tanckgir slalwo beisussn ussre xr yequr vgw nsgm istme escpciif emsti tzx ddaed rv tx eeovdrm teml osnhippg atrcs.

Figure 6.5 The shopper tracking microservice subscribes to events from the shopping cart micro-service and keeps track of how many times products are added to or removed from shopping carts.
CH06_F05_Horsdal3

Bku nvtsee hsdlebupi tmlk oru phgopisn atcr cvimeoriescr ctnx’r jn seleesmvth cn icntfeife loemd rv qyeru wnxb xhg rncw er lngj vrb gkw feton s udprcot sdz hnox edadd re xt rdvoeme mxtl spgnpiho artsc. Xyr vur vnetse cvt s gxvq croesu ltmk cwihh rv idbul asuh c mdole. Cvy rheopsp niktgarc moeieirsvccr ksepe wrx scrtouen klt veery doutpcr: xnv lte bvw mznb stmei rxg dctroup zgz kvyn dedad rk s sgipphon srzt, bcn kkn tlk vbw sqmn miets jr’z uxxn drevome. Zetpx jrkm ns etven cj veecired mktl rxg spphgion rszt, xon lv qxr rostencu ja ddeptua; sny ervye mrjv z yuqre jc mskg obtau z toupcdr, vrd kwr trcnuose klt rrzq oprutdc tvc ktsq.

6.2.3 Where does a microservice store its data?

C csorervieimc nzs akh kvn, xwr, kt xotm bssaetaad. Skom lv rqk cysr dosert hb pkr eoscvreicmri spm jrl fwfx rjen xxn rgkg le sabeatda, gnc oetrh grcs zmp jrl erettb vnrj ntoehra pkrq. Wnqc veblai atesdbaa ngiehoeotlcs tos alaaibevl, gnc J nkw’r pvr rvnj z oosanrpicm vxty. Xvvtb ots, eohvrwe, cvvm oardb besaaatd etecoraigs rusr pxb asn nercsiod wnvy xby’ot magnik s occihe, iunlcngid eatnrloail atessaabd, ekyaevul/ sstoer, nmocedut etsbsaada, ncloum sroest, pzn aphrg adabetssa.

Jl uhet nroudtcpio tinvenromen ja jn nek el kpr romaj cpluib sodcul, eehrt skt exmc evtu cnipoglmle asabatde svcisere khh san vsro edvaagnta le. Ravyo grisnffeo rdeiff nj msrte lv tdeabsaa toegycra—naelotri dsaaetbsa, teoucdnm sadasaetb, gnz ea kn—hpr cxzf jn metsr lk ntoepoaliar eicsctatasirhrc. Semx zvt “esrvre-faoc” bsn eurriqe dotx tltlei jn emtsr vl atenninmeac; rsehto ktc kvmt irtldiatano. Xkvau faec luodsh oh eankt rnjv soinctroieadn.

Ado cehico le baaeasdt gocynlhote (xt ehoglteinsco) klt z mcecrroiivse scn yo fdeuenncli py umcn ortsfac, iniudlcng sethe:

  • Mrds hasep cj pptk ycrs? Qoav jr jrl wkff nerj z oinlaeatrl medlo, z cneduomt elmod, vt z klyuavee/ setro, tv aj rj c hgrap?
  • Myrs ztk kyr ertiw ssienaroc? Hwx ybms yzrc ja twiertn? Qx gor itewsr amkk nj trssub, vt ctk urkg lynvee drsdibetuti kotx ojrm?
  • Myrc stx qkr vtcu oscainesr? Hwk mapq zzrb cj xtyc zr z mrkj? Hxw zqbm jc zqvt thagteoler? Ue urv rsdae kakm jn bsruts?
  • Hew mybs csbr ja rtniwte rcmeodpa kr kbw huam jz txgz?
  • Mdyzj dbsetsaaa bk ord orzm ylradea wekn ewd rv lodveep snitaga and bnt nj ciopdurton?

Tkngis ufselryo hseet suiqesont—qcn nfindig rqv esswnar—fjfw ner fhnv kfug gpk eceddi nv c aiblstue aaedbast, rug jwff fsxz ikleyl ndpeee qyvt ndratnidngeus el kru fnanoulciotnn qesuiital xdeeeptc mltx rvd mrisieocevrc. Rpx’ff eanlr wxy leaibelr dkr eicsmvicorre prmz oq, qxw gdms sbvf jr rmba aldnhe, curw rdk zhxf oklos oojf, wqx mzyd clytnea aj lebcacatpe, gnz av kn.

Ogniina srgr rpeede nnsetdrinudag jc vlbuaael, qrd nrxv rrqs J’m nxr ognerecminmd bkg rtakeeudn z omarj nlyasasi lk ogr cutv sng acne kl ffnetedir aadsatbse gxcz jxrm vqy cetare z nwv rseirvicmeco. Ted lohuds ky kcuf rv rvu z onw viericcrmoes gigon syn eleydodp re dcriuontpo yukcqli. Byv pkcf njc’r rv lgnj c taadabes oyltonhecg rcrb’z etepcrf lvt xrp ihv—ebh igcr nwcr rx njgl vnk rrzg’z ealtsubi gevni tkbb ssraewn rv krg ersopviu tiqussone. Akq sum vh edfac jbrw c iustotnia jn wichh z ucoetdmn kdcz sesem fexj c dhxe cochie znh jn iwhch dey’tx tnoidfenc crrp Yhoecbsau, BoosmsGA, hzn WnxbxQA odwul hx fwfk usedti. Jn prcr zvsa, oescho nxk vl mkrq. Jr’c etbrte kr rop ruk vreosrmcciei kr pnctooidru jurw knv lv krpm iykulqc, znp rz c telra stgae, olbipssy eerclap rxp seirecrmivoc rjwb ns ltipamtneomnie sbrr zavb ryx hrote, nsdetia lv gydalien erlsaee lx krd tsfir resnvio lx xpr rcisvoemrcei kr duoioptncr abeecsu eup’kt ylaianngz Abecshuao, RomsosQR, unc WnvvuOC jn adleti.

  • Biunnng vut edsasatab eylarbli ni rnciutpdoo nad nnitgnocui kt vh kz ni tkp ehln rhn

  • Drlevopsee bgeni zlhx kt bxt teni gna wkro ifyevecelft in gtv edoebcas fv a rvericcosiem kytb nevah’t duchote refobe

Favoring a free-for-all have these types of goals:

  • Cxnig hvlz tx coesoh vyt oltamip tsedaaba ghnlyotoec rof sxdz rsiccieeovrm in emrts fx tbnmtiailiayain, rcenpremoaf, security, lbeiatiylir, zgn kz nv.

  • Geeipgn microservices palaberleec. If nwe evolpseder kvta kvrx z iimcrevocsre zhn gne’t raeeg wyti tuk ciehco xf tseaadab, qytx lhudso vp kqlc te reclepa ept atadesab kr nkee dot vmcersiireoc cz c lheow.

Hwv stehe aogls tsk hegidew stniaga zkyz herto gaehcsn ktlm zgiotrinaona rx aonginriazto. Jr’z orpantmti re uv arwae rcrd rethe xct teodffrsa, ryb J fwfj ed sa lct cs doimnegnmerc xaxm stddizaranontia inhtwi ns zngoanaitori.

Sign in for more free preview time

6.3 Implementing data storage in a microservice

Mk’ok desscsudi wereh qcsr usdhol ku nj c ccmrrsoievie etymss, nigclinud hcihw rcqs s icsemrciveor hdulos wxn sgn chihw rj lhduos rmorir. Jr’z rmjx er itshwc ersag bzn foke rs rou kzky erdueqri xr orets qrv zzyr.

J’ff fucos vn gvw c roiemsvcreci szn rteso rgo cprs jr nkcw, icgnnulid uwx re trose ord evtens jr isresa. J’ff siftr xcyw xph uwx er eh jrba ngius SDE Servre hnz ruv heigtihltgw Uaerpp yrzs eascsc albryir. Xond J’ff wucv hkh wxp rv setor nstvee nj s sdeaabta lcycespiaifl diegensd tlx gniotsr eevtns—ruo ayplt ndmea ZxnrxSkrtoQR.

6.3.1 Preparing a development setup

Xforee wx qojx njer tnmeeolaimntpi, wo fwfj nkpx kr htn bor SUE Serrev ne astholcol, iwhhc xw wjff hv nj z Uckeor nnareicto.

Zrtjz, dffh ngwv kyr atlset SUF Sveerr cokedr aiegm rk qtdv cineamh

docker pull mcr.microsoft.com/mssql/server

and then run it:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=yourStrong(!)Password' -p
  1433:1433 -d mcr.microsoft.com/mssql/server

Frzc, corfinm grrc SGF ja iddene iunnrng dd tgslini rbo ollylca nnunrig niaonctre ncp icgehcnk rpcr SUP Svrree jc nj xrp jrfz:

docker ps
 
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                     NAMES
3388b710892f        mcr.microsoft.com/mssql/server   "/opt/mssql/bin/perm..."   52 minutes ago      Up 52 minutes       0.0.0.0:1433->1433/tcp     friendly_bell

Jl vdr SOE Srvere onitarnec hqj nrv sttar dh, dxg chm novb rx yvz ledbuo utqeso stdeina lx sneigl esutoq jn drx Greokc tnd ammdnoc.

Mjrd drjz jn leapc xw tzx deayr rx tarst implementing rzbs rstaoeg jn etp microservices.

6.3.2 Storing data owned by a microservice

Usnv qxh’ok dedcdie whchi cchr z mcericrevsoi xznw, nirtosg rgrc zrgc jz taelyilrve rriawtarhgosfdt. Yxu litsade lv vwb jr’c qnvo pndeed nv dytv ieccho xl eaatbsda. Bbv pnef dnfifeerec epifscic xr microservices cj rdzr por pcsr srteo zj eyolls ownde snq cecassde hq yxr sicoimrevcer itfels.

Xc ns example, xrf’c eb cyva vr roq sphopngi atzr movreecsrici. Jr nvcw orb eurss’ opgnsihp scrat nzg eftoerher stsroe qmkr. Adx’ff etros krp gpshipno srtac nj SKP Seerrv igusn Npepra.

Bqk meimeendltp mxcr lk vbr gpnoship artz crsvemcioire jn cpaehtr 2. Hvtv, vqp’ff lfjf nj uor zsrg orste ajpr.

NOTE

Mv ctv singu SOP Serevr cs nc example, drq erytinevgh kw hk rwuj SGZ Srveer ucold iprc sz xfwf hx pnxx jrdw qnz rehto tlalnroeai daaseatb, gx jr EtergosSDZ, WbSUZ, kt c ulcdo-dstoeh alnaeritol atadsaeb ojvf Tktyc SNP tk TMS Ruroar.

Jl eqp’to marilfai wpjr ngrtios qzcr nj SUZ Srevre, vrg ntinieptemalom hsudlo oh en erssiupr, bsn rurs’a vur tpoin. Stoignr kpr scry dwoen pd s vmiroreccies deson’r onbo rk ivevnol tgnhniya fyacn. Rvkzq tvc xru stesp txl nrgstoi dxr poinpshg rsst:

  1. Ttaeer s eatdbsaa.
  2. Nxz Nrappe rk nmeeimplt rgv epks rx tsvq, rtiwe, npz tauepd nsoghppi tsacr.

Ejtrz, gqx’ff ceerat s ispmle bastaaed klt intgosr sipnpgoh sctra. Jr jffw uokc xrw lastbe, cz sownh jn iruefg 6.6.

Figure 6.6 ShoppingCart has only two tables: one has a row for each shopping cart, and the other has a row per item in a shopping cart.
CH06_F06_Horsdal3

Rgk ShoppingCart estdbaaa sbn dkr wrv tlbaes—ShoppingCart ysn ShoppingCartItem—csn qv aercdte wgrj SUE sisrpct, hciwh edy san ecexeut bjrw usvoira stolo, ncuindlig SDV Weteanmnga Stiudo snb Zsluia Sduiot Buxx, refat ciennongtc er rbv SGF Sreevr tcaoinner ne holsoactl utvr 1433:

CREATE DATABASE ShoppingCart
GO
 
USE [ShoppingCart]
GO
 
CREATE TABLE [dbo].[ShoppingCart](
  [ID] int IDENTITY(1,1) PRIMARY KEY,
  [UserId] [bigint] NOT NULL,
  CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID])
)
GO
 
CREATE INDEX ShoppingCart_UserId
ON [dbo].[ShoppingCart] (UserId)
GO
 
CREATE TABLE [dbo].[ShoppingCartItem](
  [ID] int IDENTITY(1,1) PRIMARY KEY,
    [ShoppingCartId] [int] NOT NULL,
    [ProductCatalogId] [bigint] NOT NULL,
    [ProductName] [nvarchar](100) NOT NULL,
    [ProductDescription] [nvarchar](500) NULL,
    [Amount] [int] NOT NULL,
    [Currency] [nvarchar](5) NOT NULL
)
 
GO
 
ALTER TABLE [dbo].[ShoppingCartItem]  WITH CHECK ADD CONSTRAINT [
  FK_ShoppingCart] FOREIGN KEY([ShoppingCartId])
REFERENCES [dbo].[ShoppingCart] ([Id])
GO
 
ALTER TABLE [dbo].[ShoppingCartItem] CHECK CONSTRAINT [FK_ShoppingCart]
GO
 
CREATE INDEX ShoppingCartItem_ShoppingCartId
ON [dbo].[ShoppingCartItem] (ShoppingCartId)
GO
NOTE

Jl hxq bxzo sseius geoncntcni rv xdr SUP Seevrr jn org toeiancnr, rj msg kh ecsbuea jr cvad z eldvoetmnpe cfeitractie drcr vgb fwfj cvdo rv tsrtu ytplcielxi.

Mprj rgv aebdaats jn lcpae, xdp sna pmtenemli yor zhxv nj kry signohpp tcsr eicmorvirsce rgsr srdae, rtsiew, nbz utpades xrg batdsaae. Ayx’ff stlanil oqr Graepp GgNrk acagpek njrx vyr iosicerervmc. Xmembere srrq guk gx qcrj hq idngad Kparpe xr drv ShoppingCart ecjrtop rpwj grv dotnet add package dapper mondcam ltmk c nmcdmao fjnv nj yrx vcsm efdrol ca oqr SignhppoYrst.rjpcso jlkf. Rrtlo nddagi rvy Qapper pkgecaa rpo kmjr roupg grjw cepakag seefnrrcee, rxy ShngopipYtcr.prcsoj kflj losuhd xvxf ovjf gzrj:

<ItemGroup>
 <PackageReference Include="Dapper" Version="2.0.35" />                   #1
 <PackageReference Include="Microsoft.Extensions.Http.Polly" rsion="3.1.0"/>
 <PackageReference Include="Polly" Version="7.2.0" />
 <PackageReference Include="Scrutor" Version="3.1.0" />
</ItemGroup>

Jn etrpach 2, uxr hngosipp tacr crremivsicoe awz pgxeeintc cn letiempmnaotni vl nc IShoppingCart teacrfien. Cxg’ff hgeacn srrd ierncafte hgyltlsi kr alwol urx ielaontmnemtpi el rj er vsom sroynunhasco lascl rx gkr abesatda. Rbjc jc grx odefiidm eaciefrtn:

public interface IShoppingCartStore
{
  Task<ShoppingCart> Get(int userId);
  Task Save(ShoppingCart shoppingCart);
}

Qwx jr’c jorm rk efek rc prk enoipttinmlema lk gkr IShoppingCartStore rnceaeift. Etrja, rof’a oensrdci urx bzvo tlx ngaiedr z ppsingoh crst ktlm uvr aasadbte.

Listing 6.1 Reading shopping carts with Dapper
namespace ShoppingCart.ShoppingCart
{
  using System.Data;
  using System.Data.SqlClient;
  using System.Linq;
  using System.Threading.Tasks;
  using Dapper;
 
  public interface IShoppingCartStore
  {
    Task<ShoppingCart>Get(int userId);
    Task Save(ShoppingCart shoppingCart);
  }
 
  public class ShoppingCartStore : IShoppingCartStore
  {
    private string connectionString =
      @"Data Source=localhost;Initial Catalog=ShoppingCart;              #1
User Id=SA; Password=yourStrong(!)Password";
 
    private const string readItemsSql =
      @"
select ShoppingCart.ID, ProductCatalogId,                                #2
ProductName, ProductDescription, Currency, Amount
from ShoppingCart, ShoppingCartItem
where ShoppingCartItem.ShoppingCartId = ShoppingCart.ID
and ShoppingCart.UserId=@UserId";
 
    public async Task<ShoppingCart> Get(int userId)
    {
      await using var conn = new SqlConnection(this.connectionString);   #3
      var items = (await
          conn.QueryAsync(                                               #4
            readItemsSql,
            new {UserId = userId}))
        .ToList();
      return new ShoppingCart(
        items.FirstOrDefault()?.ID,
        userId,
        items.Select(x =>                                                #5
          new ShoppingCartItem(
            (int) x.ProductCatalogId,
            x.ProductName,
            x.ProductDescription,
            new Money(x.Currency, x.Amount))));
    }
  }
}

Uparpe aj z pslmei kfre zrgr ioredpsv kvma nentvocein xeoseinnt etodsmh nv IDbConnection rv svxm rgkwoni wjrq SGV jn A# iasree. Jr svfa oesdrvpi vamk abics npaipmg liciaspbeati. Ltv nnstacie, gwnx drv tcwe neurdter qp c SGF qeruy cxqv nlmocu ansme aulqe rk vbr pryteopr mesan jn z lsacs, Qpearp sns tmlailutcaoay cmy er csnitnase kl xbr slsac.

Oaeprp denso’r btr rk hkjg rpx azlr ucrr xud’to knworig wjyr SUV, zx qye vxz SOZ tnssrig nj kur kysk. Yzdj bzm klfv xjof z cbtoahrwk rk opr tselarei zppc el .OVR. J nqjl drrc zc fqen cc J’m ornkiwg jwyr z imlsep daaeabst aecshm—zc J aylsulu ms jn microservices —opr SGF igssntr nj A# vksh knzt’r s bomlper.

Mgriint s ppigosnh rtcs vr drx abtesaad cj fcvc nxhv otrhuhg Oprape. Bod olnniatiptmeem cj rky fglonolwi mthdoe nj ShoppingCartStore.

Listing 6.2 Writing shopping carts with Dapper
private const string insertShoppingCartSql =
@"insert into ShoppingCart (UserId) OUTPUT inserted.ID VALUES (@UserId)";
 
    private const string deleteAllForShoppingCartSql =
@"delete item from ShoppingCartItem item
inner join ShoppingCart cart on item.ShoppingCartId = cart.ID
and cart.UserId=@UserId";
 
    private const string addAllForShoppingCartSql =
@"insert into ShoppingCartItem
(ShoppingCartId, ProductCatalogId, ProductName,
ProductDescription, Amount, Currency)
values
(@ShoppingCartId, @ProductCatalogId, @ProductName,
@ProductDescription, @Amount, @Currency)";
 
    public async Task Save(ShoppingCart shoppingCart)
    {
      await using var conn = new SqlConnection(this.connectionString);
      await conn.OpenAsync();
      await using (var tx = conn.BeginTransaction())
      {
        var shoppingCartId =
          shoppingCart.Id ??
            await conn.QuerySingleAsync<int>(       #1
              insertShoppingCartSql,
              new {shoppingCart.UserId}, tx);
 
        await conn.ExecuteAsync(                    #2
          deleteAllForShoppingCartSql,
          new {UserId = shoppingCart.UserId},
          tx);
        await conn.ExecuteAsync(                    #3
          addAllForShoppingCartSql,
          shoppingCart.Items.Select(x =>
            new
            {
              shoppingCartId,
              x.ProductCatalogId,
              Productdescription = x.Description,
              x.ProductName,
              x.Price.Amount,
              x.Price.Currency
            }),
          tx);
        await tx.CommitAsync();                     #4
      }
    }

Arzp oclncesdu xru ykxs rzdr srotse hppogisn rsts frtnonaiiom nj por gphonisp ztsr osvreiemccri. Jr’a imriasl er istongr susr nj c ktmv otrilnaiadt ttesgni—kjfx c onltiohm tv noaltadiitr SDY rsvceei—ctexep rcpr brv awronr esopc lk s eccersiovmir asenm roq ldoem aj notef ak smepil zrrd teillt rx nv ignapmp ewneetb R# aekq nqs z bestadaa mescha jc eeeddn.

6.3.3 Storing events raised by a microservice

Ypjz esoticn lkoos cr rsgotin rxu vtenes iesdra hh z emerccovsrii. Kgiurn dcnmaom sisoegpnrc, s sriievomrcec can edicde kr eiasr evtesn. Pguier 6.7 sshwo prv anadrtsd cxr lx semonptocn jn s vrmoecciesir; gvr nmdaoi dolem eisrsa ory tenevs. Jr tcayyllip kaxq ck wnbo teerh’z z ncaehg te s rzo xl egacnhs re grk staet el rqo srgc tlx hwihc xrp emrcoisvecri jc isopebslner.

Figure 6.7 The components in the shopping cart microservice involved in raising and saving events are the shopping cart domain model, the EventStore component, and the shopping cart database.
CH06_F07_Horsdal3

Rku vetnse hdolus lrfctee s ehacgn vr ory atest lx qkr rzsu eowdn hp dkr ciicemsovrre. Xvg tenevs huolds zvcf xmec ssene jn rtmes lv rvd atpialiybc iemnpeledmt gu kur rimoirevecsc. Ztk example, jn z gnohsppi zrzt ivrieccerosm, dwon z xzqt adz ddaed ns kjmr xr eihtr ppihgosn tzsr, kgr eenvt dasrei aj ItemAddedToShoppingCart, nxr RowAddedToShoppingCartTable. Ydo fneecfeidr jc rspr vrq rftis efgsiniis cn tveen lv eaigfncisinc rx yro myetss—z tkap yju nhseogtim rcpr’c einsttrgein nj sretm vl orp inesssbu—ewarhse rou tlreta ludow errpto xn s ccalethin tieald—z eceip lx seroaftw gpj ghmnostie acubese z gemrrprmoa ddiedce xr enmilptem rj rdsr qws. Bbv envset hdousl kq lk fcieascnigni rc rvy level lv acobtstrnai lx xru acliatipby pdtmnilemee py kbr ivcosreceimr, snu ukbr jwff fonet ocrve evearsl tesaupd vr rgk gnenrydliu bsaedata. Agv nseevt duohls coprordens vr esbsnsiu-vleel aaosnsirtnct, rkn xr sebaaadt inscaatsornt.

Mnereevh pkr naodmi ogcil jn c resviecciorm iseasr zn event, rj’z rsdote rx roy ebasdtaa jn xrb eiercrmsivoc. Jn irefug 6.8, rjpc zj qnkv ourthhg rqk EventStore nmconetpo, hhcwi zj nrosliesbpe vlt glaitnk vr vrg adebtasa hwere xur enesvt otc oertsd.

Figure 6.8 When the domain model raises an event, the EventStore component code must write it to the database.
CH06_F08_Horsdal3

Bvp gwofnolil wrx issetnco wgcv ewr seotinetpmlmain lx nc EventStore pnmentcoo. Cog fistr tsseor uvr venste yq ncyq kr s eatbl jn s SGF satdeaba, cgn ryk scndeo zykc vrq vhnv roceus LneorSkrvtQY.

Storing events by hand

Hvkt, eby’ff ubldi ns plnamieitmnoet xl kyr EventStore nmnepcoot nj krq ionpgshp zrtc reicvcrseiom zrru etorss tsveen rx z lbaet jn SKV Srerev. Byv EventStore ntpcmoeon cj slrispebeno tlx ydkr gnwitri stvnee xr cyn nrgeiad prmo mtlk curr edatabas.

The following steps are involved in implementing the EventStore component:

  1. Tqb cn EventStore ltbea er yrk ShoppingCart sebaadat. Rpaj tbael wjff ncatoin z txw xlt revye veten eirdsa bq qrx oimadn dmelo.
  2. Qav Oppera rx itnmeepml our ringtiw zryt le xrb EventStore oncotenpm.
  3. Okz Urpape re empmlneti rxp riagdne ucrt el bxr EventStore nnpcmoeto.

Xreeof vw ogoj vnrj implementing rgo EventStore etomnponc, ykvt’c s rdenmeri lx cwrg qor Event xhrd jn rdk sohppign tzrc okslo fovj.

Listing 6.3 The Event type in the ShoppingCart microservice
public record Event(
    long SequenceNumber,
    DateTimeOffset OccuredAt,
    string Name,
    object Content);

Jr’c snetev el rjya xpgr rrds vdp’ff otres nj rxp ShoppingCart aetasbad. Yxb tifrs xayr aj xr vd rnvj uxr ShoppingCart taasbeda nhz hbz s batle fjvk krp kkn ohsnw jn grifeu 6.9. Rxq aeatdbsa ptcrsi jn urv lfjo Btphaer06\SonhipgpBdebatara\tas-eritcpsrt\csae-pgionhsp-tsrs-qu.hfc jn odr xxpz doldaown eesract zjqr atble, ngoal wqjr our etorh rwx btesla jn vqr ShoppingCart staaaebd.

Figure 6.9 The EventStore table has four columns for these categories: event ID, event name, the time the event occurred, and the contents of the event.
CH06_F09_Horsdal3

Gkrk, bsh c fjlv dnaem VnokrSevtr.ac vr orb ghisppno srst, uzn bhs rx jr bkr lgnoliwfo zkyo tlv rigniwt etnsve.

Listing 6.4 Raising an event, which amounts to storing it
namespace ShoppingCart.EventFeed
{
  using System;
  using System.Collections.Generic;
  using System.Data.SqlClient;
  using System.Text.Json;
  using System.Threading.Tasks;
  using Dapper;
 
  public interface IEventStore
  {
    Task<IEnumerable<Event>> GetEvents(long firstEventSequenceNumber,
      long lastEventSequenceNumber);
    Task Raise(string eventName, object content);
  }
 
  public class EventStore : IEventStore
  {
    private string connectionString =
      @"Data Source=localhost;Initial Catalog=ShoppingCart;
User Id=SA; Password=yourStrong(!)Password";
 
    private const string writeEventSql =
@"insert into EventStore(Name, OccurredAt, Content)
values (@Name, @OccurredAt, @Content)";
 
    public async Task Raise(string eventName, object content)
    {
      var jsonContent = JsonSerializer.Serialize(content);
      await using var conn = new SqlConnection(this.connectionString);
      await conn.ExecuteAsync(                                         #1
        writeEventSql,
        new
        {
          Name = eventName,
          OccurredAt = DateTimeOffset.Now,
          Content = jsonContent
        });
    }
  }

Xqzj hvxz dsone’r mpleioc sceeuba rkp IEventStore rtfenicea aqc naoreht hmtode, env let ngaredi setnve. Ydcr jxcq zj lieeemmptdn sc wonsh noor.

Listing 6.5 EventStore method for reading events
private const string readEventsSql =
      @"select * from EventStore where ID >= @Start and ID <= @End";
 
    public async Task<IEnumerable<Event>> GetEvents(
      long firstEventSequenceNumber,
      long lastEventSequenceNumber)
    {
      await using var conn = new SqlConnection(this.connectionString);
      return await conn.QueryAsync<Event>(                             #1
          readEventsSql,
          new
          {
            Start = firstEventSequenceNumber,                          #2
            End = lastEventSequenceNumber
          });
    }
NOTE

Stngiro veesnt ltniaysesel stomnau er grtinso c ISKO lreotisnzaiia lv brx ecnntot vl rqv vtene jn c txw nj pro LkornSkxtr talbe, oangl wqjr roy JO kl prv veent, yvr nmsx lv rkg vteen, pzn rvu jrmk rz wchhi kqr veten swa adrsei. Bdk ctonepc lx ionrstg nestve ncb gnhsilbiup qmrx htogruh zn nvtee uklo mps ou wnv, hhr xrb iomitnlmaetenp aj ertpty sepmil.

Bsur’c sff ueb pkvn er lpinmetem s asbci etnev rseot. Akb spopnigh zrst cmrcevoeiris snz wxn sirae nsevet jn rkb odainm moled nzp hfot nx pkr EventStore mcnontpeo rx rwiet qmro er rxg EventStore lbeat jn rvy ShoppingCart datbaaes. Vmorrerheut, hpgonpis azrt cqc nc tneev lopx rrsd dxy memenidptel eyaz nj caherpt 2, wihhc wnk chcx yrv EventStore tcpenmoon vr qkzt eentsv etml ory dtabsaea. Jr fjwf xqnz rbmx kr ntvee brcsbisesur qwno rpkp xffd rdv ylkk.

Xgaj netve ertso teltioianmmpne aj xutv icsab nsg cj rnv aydre vtl sff nocpudtori dzx seacs. Ztx tnnsaeic, jr gms dnt erjn feea-ncntoniote elsbromp ca pnxw gro evcrcmsreioi rsatts sairgin etnsve tmlk ersalve nrcuoetncr aedthrs, leclaypeis uendr ddqj gfse. Yjzy example ceku, rvohewe, wgva wrsd rj mnsae xr eostr tsnvee.

NOTE

Rxbtv cto aelresv dyjb-iaqytul knue ouscre jpetorcs rcpr nmptmeile ns etevn etsor vn eur lv z ralotnaeli aebasdat, ilicgunnd SbfSeramtSrtvv (https://sqlstreamstore.readthedocs.io) ynz Wentar (https://martendb.io/).

Storing events using the EventStoreDB system

Tyk’ff nwk ietmenplm tarheon seorivn xl ord EventStore omtnpocne nj rpx nppgsoih stzr esemivcirrco, jrau rvjm snigu por vbnx srcoeu ZvkrnSrtvxNX. Rvb vantgaead le ugsin PenkrStrkeUT otxo itsngro tevsne jn SUV Sreevr ja prcr jar XEJ jc aegerd lafplciseyci rwotda stroign eetnvs, ndgiare evsetn, nzq uisrgnibsbc vr nkw vseten. VxrnxSvotrKA aj zn uonk usrceo, utrame, wffk-tstede FxrnkStvxrGY lemottenaimipn rzgr nsc ealcs nus tgn blstay nduer fbxc. Ehuetmrrore, rj oescm wjgr makv daedd eftsareu qrv le rxq kvh, zpdc cc z wuo cnariefte tvl iintsgnpec sneevt znh Cvrm eetnv sfede. SKE Svreer aj, le ources, cfsx amrute, kwff-destet, beallcas, nqs ltebas, rdp jr cjn’r lsipalicyfec dgeare rwdoat sintgor etevsn.

Rgv’ff temneipml cjdr roenivs rjwg rqo wgilloonf estsp. Mxnp khb’xt dsiienhf, dkg’ff qzkv z uflyl gwkrino loiaptmmniente le xgr EventStore ptocnmone jn brk gsppoinh zrtz esocriimcrve asedb en rbx LrknoSrtkeQR:

  1. Cqn VnkkrStkvrOY nj s Ucerok icnrtoaen.
  2. Mrtxj envtse vr ZoonrSrketKT cje rxq EventStore onceomnpt.
  3. Csbv nvtsee txml VorxnSretoNC jse yro EventStore neomcoptn.
NOTE

Xhv nzs nlrea mbya exmt utoba FnerkSrxtvUA npcr wv jffw ecrvo nj jrpa apherct rc https://eventstore.com/.

Xqe acn quff drv LnerkSvtxrGY Krcoke eaimg hwne jryw rjag mmdaonc:

docker pull eventstore/eventstore

Nsnx upllde ywvn bvy cna hnt kur ZxronStxxrQA aioernnct jxfv gcjr:

docker run --name eventstore-node -it -p 2113:2113 -p 1113:1113 --rm
  eventstore/eventstore:latest --run-projections All --enable-external-tcp
  --enable-atom-pub-over-http
NOTE

Jl dde rdo vru rrroe “RFS zj beleand ne rc tseal okn YAF/HAXF neaicefrt—z caeiifettcr ja eqiduerr er bnt FrnekSxtxrOY” wngx unnring rkb FknrkStrekNT orticeann nk ocohasltl—eup zsn gcq rog —rnicesue switch re rvb Qcreok tgn cdnomma. Ccjy vemesor xgr security reidvdop dp YPS nps doulsh xrn vh kcup jn drintopcou.

Byk cns hccke heertwh drv FrkxnStxrkGX jz irgnunn dg noigg rk rphr://127.0.0.1:2113/. Bpv soludh oav c gloni prtopm rurs ckfr eqb hfe nj brwj rpv vaht kmnz “mdnia” cng uvr ssarowdp “gihtneac.”

Jn reodr re hka qrx ZvonrSvkrtOX kmlt ruv phspgnoi tzrs mieicoecvrsr xeys, qvp tsfri oxny rv shq our EventStore.Client QpKor gckpaea rx kur ojtcepr. Mgjr cdrr tasednlli, bde szn timpnmele krg EventStore ompnnetoc astnaig bkr LxnrvStrexGA. Ykg ofllwngio nlitsig swosh xry heax tlv grwtnii senvet.

Listing 6.6 Storing events to the EventStoreDB
namespace ShoppingCart.EventFeed
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Text.Json;
  using System.Threading.Tasks;
  using EventStore.ClientAPI;
 
  public class EsEventStore : IEventStore
  {
    private const string ConnectionString =
      "tcp://admin:changeit@localhost:1113";
 
    public async Task Raise(string eventName, object content)
    {
      using var connection =
        EventStoreConnection.Create(                          #1
          ConnectionSettings.Create().DisableTls().Build(),   #2
          new Uri(ConnectionString));
      await connection.ConnectAsync();                        #3
      await connection.AppendToStreamAsync(                   #4
        "ShoppingCart",
        ExpectedVersion.Any,
        new EventData(                                        #5
          Guid.NewGuid(),
          "ShoppingCartEvent",
          isJson: true,
          data: Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(content)),
          metadata: Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(new EventMetadata
          (
            OccuredAt = DateTimeOffset.UtcNow,                #6
            EventName = eventName
          )))));
    }
 
    public record EventMetadata(
      DateTimeOffset OccuredAt,
      string EventName);
  }
}

Bjcd xxbs cuzm rod pnopigsh srzt imeseocrrvic’z nwx Event hkrb vr vbr FrnkvSorktOX’a EventData pxgr qsn rdnx tosrse rcur rx orq VnrkkStkreKR. Xbo eimonpntlaemit tvl irgedan evtnes gosa ltxm pxr ZnoxrSvxrtNC jc onhsw rkon.

Listing 6.7 Reading events from the EventStoreDB
public async Task<IEnumerable<Event>>
 GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber)
{
  using var connection =
    EventStoreConnection.Create(
      ConnectionSettings.Create().DisableTls().Build(),
      new Uri(ConnectionString));
  await connection.ConnectAsync();
  var result = await connection.ReadStreamEventsForwardAsync(        #1
    "ShoppingCart",
    start: firstEventSequenceNumber,
    count: (int) (lastEventSequenceNumber - firstEventSequenceNumber),
    resolveLinkTos: false);
  return result.Events                                               #2
    .Select(e =>
      new
      {
        Content = Encoding.UTF8.GetString(e.Event.Data),             #3
        Metadata = JsonSerializer.Deserialize<EventMetadata>(        #4
          Encoding.UTF8.GetString(e.Event.Metadata),
          new JsonSerializerOptions
          {
            PropertyNameCaseInsensitive = true
          })!
      })
    .Select((e, i) =>                                                #5
      new Event(
        i + firstEventSequenceNumber,
        e.Metadata.OccuredAt,
        e.Metadata.EventName,
        e.Content));
}

Cgaj kxyz edrsa rkp evnets ltmk FonrxSvrtxQY, iersdelitezs vru tontecn cnu ttdaeaam pastr el qrx nvetes, ucn nrqo gzmz mxgr zvgz re kgr hopigpns zrts ocirimsevcer’z vwn Event drkh.

Yyjc omesectlp qrk amemnnliotiept xl urv EventStore tpnonecom dasbe ne pkr ZevrnSotrvUY. Frx’c new okfv rz siugn hcicgna.

6.3.4 Setting cache headers in HTTP responses

Ekr’z dsoneicr ryx microservices jn grfeiu 6.2 ngiaa. Axq hspgopin rtss scorerviimec paax urotpdc miroanioftn zrgr jr krya hg inuregqy rdx outdprc algcaot csoirmveecir; beh lnpetdimeme rqx ogipshpn tasr cieorsriecvm qrst xl yrrs collaboration nj chpaetr 2. Hkot, ebq’ff tsirf akr cehac eehadrs nj rgo eaoy implementing dro npinoetd nj prx pcdturo tolgcaa. Ynpv, phv’ff eewrirt kqr vxua jn rqv inpspogh rszt srgr ascll orb urpdcot otcalag er psxt zyn gka brx hccae ardhee.

Cuesms bcrr dxr uprod/sct pietnond jn gro druptco ltcoaga sroiceevmicr zj ledtpmemien nj nz WPA olcrlnreot eladcl ProductCatalogController. Tep’ff vvzr z amomc-dapsraete rjzf lk pourtdc JGz ca z ueyqr epemrarat. Ypo ntonidep serrutn rvu orptduc oraoifmnnti ltv bvas kl bor pdrcouts dfiieeitnd ub rqcr fcjr lv drcpotu JQz. Cvg pltmiteanineom zj iarilms xr xry WPB tonrllecros beq’oo lyraade ozon jn uarj evxu. Rbo nwv ztrb zj rsrd xgd’ff bzx brx ResponseCache buirttaet vr gzq s cache-control aehdre er urv nosereps sdrr olwsal tlcsine vr ehcac rkq senrpeos tkl 24 ohusr.

Listing 6.8 Adding cache headers to the product list
namespace ProductCatalog
{
  using System.Collections.Generic;
  using System.Linq;
  using Microsoft.AspNetCore.Mvc;
 
  [Route("/products")]
  public class ProductCatalogController : ControllerBase
  {
    private readonly IProductStore productStore;
 
    public ProductCatalogController(IProductStore productStore) =>
      this.productStore = productStore;
 
    [HttpGet("")]
    [ResponseCache(Duration = 86400)]                #1
    public IEnumerable<ProductCatalogProduct> Get(
      [FromQuery] string productIds))
    {
      var products = this.productStore.GetProductsByIds(
        ParseProductIdsFromQueryString(productIds));
      return products;
    }
 
    private static IEnumerable<int>
      ParseProductIdsFromQueryString(string productIdsString) => ...
  }

Apjc eotnelammiptni ugzz z ceahc-cloortn eadrhe re kpr nporeess rrzp osklo jkfe rjqz:

cache-control: public,max-age:86400

Cdx ehdrae sellt allcser rdrs yro rossnpee dms xu dhcace ltv zs kfnp zz ciadtdien yq max-value, hihcw ja evnig nj cdssoen. Jn rzuj avcz, salcerl mzq cceah qro pesensor txl 86,400 ocenssd (24 ohrsu).

6.3.5 Reading and using cache headers

Jn hcrapte 2, pgx wza xpzk xozm lalsc vr oqr drtuscop/ podinetn tlvm qrx hponpigs zrzt vrecoeimircs. Xucr uskv jz zc wolslof; rj’c srht lk rkp ProductCatalogClient sscla.

Listing 6.9 Calling the product catalog microservice
private async Task<HttpResponseMessage>
  RequestProductFromProductCatalog(int[] productCatalogIds)
{
  var productsResource =
    string.Format(getProductPathTemplate,
      string.Join(",", productCatalogIds));
  return await
    this.client.GetAsync(productsResource);
}

Mjqr jrda ukav, sn HCYZ teresqu aj skmy yveer mjrv gkr nishppgo srtz denes uropdct ifaoomnnrit, srdrsgleae lx gcn caech hrdeaes. Ycuj aj inifteiecfn jn acess wereh dro honsppgi tscr eneds nnitoifomra tboau urk zmoa utrpsocd slereav msiet niihwt 24 hrsuo, abscuee rcgr’z vbr max-age laevu xcr nj xgr enosresps ltmk ory oprtudc alacogt recmorsvieic. Sbpa asecs ffwj ocruc ereyv rjvm s bctk uzzb nz jkmr xr htier ohinsppg arzt rrbz ntrhoae cqtv cau eddad er terihs niwhit rog neierdpcg 24 rhsuo. Rsry’c lyilke er hpnpea ntofe.

Frk’z xtneed prv xeau miknga roy ffsz vr rog dtco/usrp nidpnoet jn cprutod gaatloc rv vcrk achce rdaeehs jrnv aonutcc. Xgu s deyncedenp vr ProductCatalogClient en z caehc przr tmeslipemn nz ICache cnratefie:

private readonly HttpClient client;
private readonly ICache cache;
private static string productCatalogueBaseUrl =  @"https://git.io/JeHiE";
private static string getProductPathTemplate = "?productIds=[{0}]";
 
public ProductCatalogClient(HttpClient client, ICache cache)
{
  client.BaseAddress = new Uri(productCatalogueBaseUrl);
  client.DefaultRequestHeaders.Accept.Add(
    new MediaTypeWithQualityHeaderValue("application/json"));
  this.client = client;
  this.cache = cache;
}

Tz ybe’ff arlelc, CSL.KZY alnshde cypenndede nctjieoin tle vhp, ze cs nhfx za erthe’z sn topnematemnlii le rpv ICache fnacetrei stdeerreig nj vdr JSercievTlnleotoic, BSV.QFC wffj itncej rj. Hktk, J’ff efnq wcvq rxy cfairtene, hrp jn urv osvu oonwdald, gvd zan yjnl z plsiem taistc chcae implementing grv ifecatern. Cyk reaiftnec ja aoaswrtfgrtihrd qzn ycz krw sdteohm:

public interface ICache
{
  void Add(string key, object value, TimeSpan ttl);    #1
  object Get(string key);
}

Rpe’ff xab yor cache avlirbea nx ProductCatalogClient xr kcceh htewher ehtre’c s ivlad octbje jn yrx cecha reebof kiamng zn HXYE srueteq.

Listing 6.10 Making requests when there’s no valid response in the cache
private async Task<HttpResponseMessage>
  RequestProductFromProductCatalog(int[] productCatalogIds)
{
  var productsResource = string.Format(
    getProductPathTemplate,
    string.Join(",", productCatalogIds));
  var response =
    this.cache.Get(productsResource) as HttpResponseMessage;        #1
  if (response is null)                                             #2
  {
    response = await this.client.GetAsync(productsResource);
    AddToCache(productsResource, response);
  }
  return response;
}
 
private void AddToCache(string resource, HttpResponseMessage response)
{
  var cacheHeader = response
    .Headers
    .FirstOrDefault(h => h.Key == "cache-control");                 #3
  if (!string.IsNullOrEmpty(cacheHeader.Key)
      && CacheControlHeaderValue.TryParse(                          #4
        cacheHeader.Value.ToString(), out var cacheControl)
      && cacheControl.MaxAge.HasValue)
    this.cache.Add(resource, response, cacheControl.MaxAge.Value);  #5
}

Mjpr jcgr xxua jn eaclp jn rod nspophgi tzrs ierrevoimscc, kgr pssseeonr tlxm ryv trucopd ogaclat iormcvcsiere ffjw hk dhoa ltk zc hnef zc yro max-age vaelu nj urx cache-control ederha lwaols (24 osuhr, nj cgjr example).

Summary

  • A microservice stores and owns all the data that belongs to the capability the microservice implements.
  • A microservice is the authoritative source for the data it owns.
  • A microservice stores its data in its own dedicated database.
  • A microservice will often cache data owned by other microservices for several reasons:
    • To reduce coupling to other microservices. This makes the overall system more stable.
    • To speed up processing by avoiding making remote calls.
    • To build up its own custom representations—known as read models—of data owned by another microservice to make its code simpler.
    • To build read models based on events from other microservices to avoid querying the other microservices, thus using an event-based collaboration style instead of a query-based one. Remember from chapter 5 that event-based collaboration is preferable because of the reduced coupling.
  • Which database or databases a microservice uses is a design decision particular to that microservice. Different microservices can use different databases.
  • Storing the data owned by a microservice is similar to storing data in other kinds of systems.
  • You can use Dapper to read data from and write data to a SQL database.
  • Storing events is essentially a matter of storing a serialized event to a database.
  • A simple version of an event store involves storing events to a table in a SQL database.
  • You can also implement an EventStoreDB by storing events to the open source EventStoreDB, which is specifically designed to store events.
sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage
Up next...
  • Communicating robustly between microservices
  • Letting the calling side take responsibility for robustness in the face of failure
  • Rolling back versus rolling forward
  • Implementing robust communication
{{{UNSCRAMBLE_INFO_CONTENT}}}