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.
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.
- 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.
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.

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.

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.
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.
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.

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.
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.

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.
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.
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.
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.
Xforee wx qojx njer tnmeeolaimntpi, wo fwfj nkpx kr htn bor SUE Serrev ne astholcol, iwhhc xw wjff hv nj z Uckeor nnareicto.
docker pull mcr.microsoft.com/mssql/server
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.
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
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:
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.

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
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.
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.

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.

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.
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.
- Tqb cn EventStore ltbea er yrk ShoppingCart sebaadat. Rpaj tbael wjff ncatoin z txw xlt revye veten eirdsa bq qrx oimadn dmelo.
- Qav Oppera rx itnmeepml our ringtiw zryt le xrb EventStore oncotenpm.
- 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.

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/).
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:
- Cqn VnkkrStkvrOY nj s Ucerok icnrtoaen.
- Mrtxj envtse vr ZoonrSrketKT cje rxq EventStore onceomnpt.
- 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/.
docker pull eventstore/eventstore
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.
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) => ... }
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).
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).
- 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.