microservices with clojure: develop event-driven, scalable ... · chapter 3, microservices for...

417

Upload: others

Post on 10-Jun-2020

59 views

Category:

Documents


10 download

TRANSCRIPT

Page 1: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 2: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MicroserviceswithClojure

Developevent-driven,scalable,andreactivemicroserviceswithreal-timemonitoring

AnujKumar

Page 3: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

BIRMINGHAM-MUMBAI

Page 4: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MicroserviceswithClojureCopyright©2018PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishingoritsdealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

CommissioningEditor:RichaTripathiAcquisitionEditor:AiswaryaNarayananContentDevelopmentEditor:AkshadaIyerTechnicalEditor:AbhishekSharmaCopyEditor:SafisEditingProjectCoordinator:PrajaktaNaikProofreader:SafisEditingIndexer:FrancyPuthiryGraphics:JasonMonteiroProductionCoordinator:DeepikaNaik

Firstpublished:January2018

Productionreference:1230118

PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.

ISBN978-1-78862-224-0

www.packtpub.com

Page 5: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Tomymother,Mrs.InduSrivastava,myfather,Mr.DilipKumar,andtomylovelywife,Aishwarya,fortheircontinuoussupportandencouragement.AllthetimethatIhavespentonthisbookshouldhavebeenspentwiththem.FortripsthatwecanceledandforweekendsthatIspentatmydesk.

Tomyfamily,teachers,andcolleagues.Theyhaveextendedtheircontinuoussupport,providedcriticalfeedback,andmadeitpossibleformetofocusonthisbook.

Page 6: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

mapt.io

Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.

Page 7: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals

ImproveyourlearningwithSkillPlansbuiltespeciallyforyou

GetafreeeBookorvideoeverymonth

Maptisfullysearchable

Copyandpaste,print,andbookmarkcontent

Page 8: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

PacktPub.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

Page 9: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Contributors

Page 10: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AbouttheauthorAnujKumaristheco-founderandchiefarchitectofFORMCEPT,adataanalyticsstartupbasedinBangalore,India.Hehasmorethan10yearsofexperienceindesigninglarge-scaledistributedsystemsforstorage,retrieval,andanalytics.

Hehasbeeninindustryhacking,mainlyintheareaofdataintegration,dataquality,anddataanalyticsusingNLPandmachinelearningtechniques.HehaspublishedresearchpapersatACMconferences,gotafewpatentsgranted,andhasspokenatTEDx.

PriortoFORMCEPT,hehasworkedwiththeOracleServerTechnologiesdivisioninBangalore,India.

Iwouldliketothankmytechnicalreviewer,MichaelVitz,forhisvaluablefeedbackandthePackteditorialteamforanexcellentfeedbacklooptocomeupwithgoodqualitycontent.IwouldalsoliketothankmyteachersandFORMCEPTteammembers,whohavehelpedmeonvarioustopicscoveredinthisbook.Andespecially,Iwouldliketothankmyparents,mywife,andmyentirefamilyfortheircontinuousencouragement.

Page 11: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AboutthereviewerMichaelVitzhasmanyyearsofexperiencebuildingandmaintainingsoftwarefortheJVM.Currently,hismaininterestsincludemicroserviceandcloudarchitectures,DevOps,theSpringFramework,andClojure.

AsaseniorconsultantforsoftwarearchitectureandengineeringatINNOQ,hehelpsclientsbybuildingwell-craftedandvalue-providingsoftware.

HealsoisthewriterofacolumnintheGermanmagazine,JavaSPEKTRUM,wherehepublishesarticlesaboutJVM,infrastructure,andarchitecturaltopicseverytwomonths.

Page 12: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.

Page 13: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TableofContents

PrefaceWhothisbookisfor

Whatthisbookcovers

TogetthemostoutofthisbookDownloadtheexamplecodefiles

Conventionsused

GetintouchReviews

1. MonolithicVersusMicroservicesDawnofapplicationarchitecture

Monolithicarchitecture

MicroservicesDatamanagement

Whentousewhat

MonolithicapplicationstomicroservicesIdentifyingcandidatesformicroservices

Releasecycleandthedeploymentprocess

Summary

2. MicroservicesArchitectureDomain-drivendesign

Boundedcontext

Identifyingboundedcontexts

Organizingaroundboundedcontexts

ComponentsHexagonalarchitecture

Page 14: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MessagingandcontractsDirectmessaging

Observermodel

Servicecontracts

ServicediscoveryServiceregistry

Servicediscoverypatterns

DatamanagementDirectlookup

Asynchronousevents

Combiningdata

Transactions

AutomatedcontinuousdeploymentCI/CD

Scaling

Summary

3. MicroservicesforHelpingHandsApplicationDesign

Usersandentities

Userstories

Domainmodel

MonolithicarchitectureApplicationcomponents

Deployment

Limitations

MovingtomicroservicesIsolatingservicesbypersistence

Isolatingservicesbybusinesslogic

Page 15: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Messagingandevents

Extensibility

WorkflowsforHelpingHandsServiceproviderworkflow

Serviceworkflow

Serviceconsumerworkflow

Orderworkflow

Summary

4. DevelopmentEnvironmentClojureandREPL

HistoryofClojure

REPL

ClojurebuildtoolsLeiningen

Boot

ClojureprojectConfiguringaproject

Runningaproject

Runningtests

Generatingreports

Generatingartifacts

ClojureIDE

Summary

5. RESTAPIsforMicroservicesIntroducingREST

RESTfulAPIsStatuscodes

Page 16: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Namingconventions

UsingRESTfulAPIsviacURL

RESTAPIsforHelpingHandsConsumerandProviderAPIs

ServiceandOrderAPIs

Summary

6. IntroductiontoPedestalPedestalconcepts

Interceptors

Theinterceptorchain

ImportanceofaContextMap

CreatingaPedestalserviceUsinginterceptorsandhandlers

Creatingroutes

Declaringrouters

Accessingrequestparameters

Creatinginterceptors

Handlingerrorsandexceptions

Logging

Publishingoperationalmetrics

Usingchainproviders

Usingserver-sentevents(SSE)CreatinginterceptorsforSSE

UsingWebSocketsUsingWebSocketwithPedestalandJetty

Summary

7. AchievingImmutabilitywithDatomic

Page 17: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatomicarchitectureDatomicversustraditionaldatabase

Developmentmodel

Datamodel

Schema

UsingDatomicGettingstartedwithDatomic

Connectingtoadatabase

Transactingdata

UsingDatalogtoquery

Achievingimmutability

Deletingadatabase

Summary

8. BuildingMicroservicesforHelpingHandsImplementingHexagonalArchitecture

Designingtheinterceptorchainandcontext

CreatingaPedestalproject

DefininggenericinterceptorsInterceptorforAuth

Interceptorforthedatamodel

Interceptorforevents

CreatingamicroserviceforServiceConsumerAddingroutes

DefiningtheDatomicschema

Creatingapersistenceadapter

Creatinginterceptors

Testingroutes

Page 18: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforServiceProviderAddingroutes

DefiningDatomicschema

Creatingapersistenceadapter

Creatinginterceptors

Testingroutes

CreatingamicroserviceforServicesAddingroutes

DefiningaDatomicschema

Creatingapersistenceadapter

Creatinginterceptors

Testingroutes

CreatingamicroserviceforOrderAddingroutes

DefiningDatomicschema

Creatingapersistenceadapter

Creatinginterceptors

Testingroutes

CreatingamicroserviceforLookupDefiningtheElasticsearchindex

Creatingqueryinterceptors

Usinggeoqueries

Gettingstatuswithaggregationqueries

CreatingamicroserviceforalertsAddingroutes

CreatinganemailinterceptorusingPostal

Page 19: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Summary

9. ConfiguringMicroservicesConfigurationprinciples

Definingconfigurationparameters

Usingconfigurationparameters

UsingOmniconfforconfigurationEnablingOmniconf

IntegratingwithHelpingHands

ManagingapplicationstateswithmountEnablingmount

IntegratingwithHelpingHands

Summary

10. Event-DrivenPatternsforMicroservicesImplementingevent-drivenpatterns

Eventsourcing

UsingtheCQRSpattern

IntroductiontoApacheKafkaDesignprinciples

GettingKafka

UsingKafkaasamessagingsystem

UsingKafkaasaneventstore

UsingKafkaforHelpingHandsUsingKafkaAPIs

InitializingKafkawithMount

IntegratingtheAlertServicewithKafka

UsingAvrofordatatransfer

Summary

Page 20: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

11. DeployingandMonitoringSecuredMicroservicesEnablingauthenticationandauthorization

IntroducingTokensandJWT

CreatinganAuthserviceforHelpingHandsUsingaNimbusJOSEJWTlibraryforTokens

CreatingasecretkeyforJSONWebEncryption

CreatingTokens

Enablingusersandrolesforauthorization

CreatingAuthAPIsusingPedestal

MonitoringmicroservicesUsingELKStackformonitoring

SettingupElasticsearch

SettingupKibana

SettingupLogstash

UsingELKStackwithCollectd

Loggingandmonitoringguidelines

DeployingmicroservicesatscaleIntroducingContainersandDocker

SettingupDocker

CreatingaDockerimageforHelpingHands

IntroducingKubernetes

GettingstartedwithKubernetes

Summary

OtherBooksYouMayEnjoyLeaveareview-letotherreadersknowwhatyouthink

Page 21: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Preface

Themicroservicearchitectureissweepingtheworldasthedefactopatternforbuildingscalableandeasy-to-maintainweb-basedapplications.Thisbookwillteachyoucommonpatternsandpractices,showingyouhowtoapplythemusingtheClojureprogramminglanguage.ItwillteachyouthefundamentalconceptsofarchitecturaldesignandRESTfulcommunication,andshowyoupatternsthatprovidemanageablecodethatissupportableindevelopmentandatscaleinproduction.ThisbookwillprovideyouwithexamplesofhowtoputtheseconceptsandpatternsintopracticewithClojure.

Whetheryouareplanninganewapplicationorworkingonanexistingmonolith,thisbookwillexplainandillustratewithpracticalexampleshowteamsofallsizescanstartsolvingproblemswithmicroservices.Youwillunderstandtheimportanceofwritingcodethatisasynchronousandnon-blocking,andhowPedestalhelpsusdothis.Later,thebookexplainshowtobuildReactivemicroservicesinClojure,whichadheretotheprinciplesunderlyingtheReactiveManifesto.Wefinishoffbyshowingyouvarioustechniquestomonitor,test,andsecureyourmicroservices.Bytheend,youwillbefullycapableofsettingup,modifying,anddeployingamicroservicewithClojureandPedestal.

Page 22: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

WhothisbookisforIfyouarelookingforwardtomigrateyourexistingmonolithicapplicationstomicroservicesortakingyourfirststepsintomicroservicearchitecture,thenthisbookisforyou.YoushouldhaveaworkingknowledgeofprogramminginClojure.However,noknowledgeofRESTfularchitecture,microservices,orwebservicesisexpected.

Page 23: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

WhatthisbookcoversChapter1,MonolithicVersusMicroservices,introducesmonolithicandmicroservicearchitectureanddiscusseswhentousewhat.Italsocoversthepossiblemigrationplansofmovingfrommonolithicapplicationstomicroservices.

Chapter2,MicroservicesArchitecture,coversthebasicbuildingblocksofmicroservicearchitectureanditsrelatedfeatures.Itdiscusseshowtosetupmessagingandcontracts,andmanagedataflowsamongmicroservices.

Chapter3,MicroservicesforHelpingHandsApplication,introducesasampleHelpingHandsapplicationanddescribesthestepsthatwillbetakenintherestofthebooktobuildtheapplicationusingmicroservices.Further,thechaptercomparesandcontraststhebenefitsofusingamicroservices-basedarchitecturecomparedwithamonolithicone.

Chapter4,DevelopmentEnvironment,coversClojureandREPLatahighlevelandintroducestheconceptsofLeiningenandBoot—thetwomajorbuildtoolsforanyClojureproject.TheemphasiswillbeonLeiningenwithabasicintroductiontoBootonhowtosetupaClojureprojectforimplementingmicroservices.

Chapter5,RESTAPIsforMicroservices,coversthebasicsoftheRESTarchitecturalstyle,variousHTTPmethods,whentousewhat,andhowtogivemeaningfulnamestoRESTfulAPIsofmicroservices.ItalsocoversthenamingconventionsforRESTAPIsusingtheHelpingHandsapplicationasanexample.

Chapter6,IntroductiontoPedestal,coverstheClojurePedestalframeworkindetailwithalltherelevantfeaturesprovidedbyPedestal,includinginterceptorsandhandlers,routes,WebSockets,server-sentevents,andchainproviders.

Chapter7,AchievingImmutabilitywithDatomic,givesanoverviewoftheDatomicdatabasealongwithitsarchitecture,datamodel,transactions,andDatalogquerylanguage.

Chapter8,BuildingMicroservicesforHelpingHands,isastep-by-step,hands-on

Page 24: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

guidetobuildandtestmicroservicesfortheHelpingHandsapplicationusingthePedestalframework.

Chapter9,ConfiguringMicroservices,coversthebasicsofmicroservicesconfigurationanddiscusseshowtocreateconfigurablemicroservicesusingframeworkssuchasOmniconf.Italsoexplainsthestepstomanagetheapplicationstateeffectivelyusingavailablestate-managementframeworkssuchasMount.

Chapter10,Event-DrivenPatternsforMicroservices,coversthebasicsofevent-drivenarchitecturesandshowshowtouseApacheKafkaasamessagingsystemandeventstore.Further,itdiscusseshowtouseApacheKafkabrokersandsetupconsumergroupsfortheeffectivecoordinationofmicroservices.

Chapter11,DeployingandMonitoringSecuredMicroservices,coversthebasicsofmicroservicesauthenticationusingJWTandhowtosetupareal-timemonitoringsystemusingtheELKStack.ItalsoexplainsthebasicconceptsofcontainersandorchestrationframeworkssuchasKubernetes.

Page 25: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TogetthemostoutofthisbookTheJavaDevelopmentKit(JDK)isrequiredtorunanddevelopapplicationsusingClojure.YoucangettheJDKfromhttp://www.oracle.com/technetwork/java/javase/downloads/index.html.Itisalsorecommendedthatyouuseatexteditororanintegrateddevelopmentenvironment(IDE)ofyourchoiceforimplementation.SomeoftheexamplesinthechaptersrequireLinuxasanoperatingsystem.

Page 26: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Downloadtheexamplecodefiles

Youcandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packtpub.com/supportandregistertohavethefilesemaileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregisteratwww.packtpub.com.2. SelecttheSUPPORTtab.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchboxandfollowtheonscreen

instructions.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux

ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Microservices-with-Clojure.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 27: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ConventionsusedThereareanumberoftextconventionsusedthroughoutthisbook.

CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandles.Hereisanexample:"ThepersistenceprotocolServiceDBconsistsofupsert,entity,anddeletefunctions."

Ablockofcodeissetasfollows:

{

"query":{

"term":{

"status":"O"

}

}

}

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

(defnhome-page

[request]

(log/counter::home-hits1)

(ring-resp/response"HelloWorld!"))

Anycommand-lineinputoroutputiswrittenasfollows:

%leinrun

Noname|Hello,World!

%leinrunClojure

Clojure|Hello,World!

Bold:Indicatesanewterm,animportantword,orwordsthatyouseeonscreen.Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereisanexample:"ClickontheCreateavisualizationbutton."

Warningsorimportantnotesappearlikethis.

Tipsandtricksappearlikethis.

Page 28: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 29: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GetintouchFeedbackfromourreadersisalwayswelcome.

Generalfeedback:Emailfeedback@packtpub.comandmentionthebooktitleinthesubjectofyourmessage.Ifyouhavequestionsaboutanyaspectofthisbook,[email protected].

Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewouldbegratefulifyouwouldreportthistous.Pleasevisitwww.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetails.

Piracy:IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,wewouldbegratefulifyouwouldprovideuswiththelocationaddressorwebsitename.Pleasecontactusatcopyright@packtpub.comwithalinktothematerial.

Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,pleasevisitauthors.packtpub.com.

Page 30: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!

FormoreinformationaboutPackt,pleasevisitpacktpub.com.

Page 31: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MonolithicVersusMicroservices

"Theoldorderchangethyieldingplacetonew"

-AlfredTennyson

Awell-designedmonolithicarchitecturehasbeenthekeytomanysuccessfulsoftwareapplications.However,microservices-basedapplicationsaregainingpopularityintheageoftheinternetduetotheirinherentpropertyofbeingautonomousandflexible,theirabilitytoscaleindependently,andtheirshorterreleasecycles.Inthischapter,youwill:

LearnaboutthebasicsofmonolithicandmicroservicesarchitecturesUnderstandthemonolithic-firstapproachandwhentostartusingmicroservicesLearnhowtomigrateanexistingmonolithicapplicationtomicroservicesCompareandcontrastthereleasecycleanddeploymentmethodologyofmonolithicandmicroservices-basedapplications

Page 32: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DawnofapplicationarchitectureEversinceAdaLovelace(https://en.wikipedia.org/wiki/Ada_Lovelace)wrotethefirstalgorithmforAnalyticalEngine(https://en.wikipedia.org/wiki/Analytical_Engine)inthe19thcenturyandAlanTuring(https://en.wikipedia.org/wiki/Alan_Turing)formalizedtheconceptsofalgorithmandcomputationviatheTuringmachine(https://en.wikipedia.org/wiki/Turing_machine),softwarehasgonethroughmultiplephasesinitsevolution,bothintermsofhowitisdesignedandhowitismadeavailabletoitsendusers.Theearliersoftwarewasdesignedtorunonasinglemachineinasingleenvironment,andwasdeliveredtoitsendusersasanisolatedstandaloneentity.Intheearly1990s,asthefocusshiftedtoapplicationsoftware,theindustrystartedexploringvarioussoftwarearchitecturemethodologiestomeetthedemandsofchangingrequirementsandunderlyingenvironments.Oneofthesoftwarearchitecturesthatwaswidelyadoptedwasmultitierarchitecture,whichclearlyseparatedthefunctionsofdatamanagement,businesslogic,andpresentation.Whentheselayerswerepackagedtogetherinasingleapplication,usingasingletechnologystack,runningasasingleprogram,itwascalledamonolithicarchitecture,stillinusetoday.

Withtheadventoftheinternet,softwarestartedgettingofferedasaserviceovertheweb.Withthischangeindeploymentandusage,itstartedbecominghardtoupgradeandaddfeaturestosoftwarethatadoptedamonolithicarchitecture.Technologystartedchangingrapidlyandsodidprogramminglanguages,databases,andunderlyinghardware.Companiesthatwereabletodisintegratetheirmonolithicapplicationsintoloosely-coupledservicesthatcouldtalktoeachotherwereabletoofferbetterservices,betterintegrationpoints,andbetterperformancetotheirusers.Theywerenotonlyabletoupgradetothelatesttechnologyandhardware,butalsoabletooffernewfeaturesandservicesfastertotheirusers.Theideaofdisintegratingamonolithicapplicationintoloosely-coupledservicesthatcanbedeveloped,deployed,andscaledindependentlyandcantalktootherservicesoveralightweightprotocol,wascalledmicroservices-basedarchitecture(https://en.wikipedia.org/wiki/Microservices).

Page 33: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CompaniessuchasNetflix,Amazon,andsoonhavealladoptedamicroservices-basedarchitecture.IfyoulookatGoogleTrendsintheprecedingscreenshot,youcanseethatthepopularityofmicroservicesisrisingdaybyday,butthisdoesn'tmeanthatmonolithicapplicationsareobsolete.Thereareapplicationsthatarestillsuitedformonolithicarchitecture.Microserviceshavetheiradvantages,butatthesametimetheyarehardtodeploy,scale,andmonitor.Inthischapter,wewilllookatbothmonolithicandmicroservices-basedarchitectures.Wewilldiscusswhentousewhatandalsotalkaboutwhenandhowtomigratefromamonolithictoamicroservices-basedarchitecture.

Page 34: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MonolithicarchitectureMonolithicarchitectureisanall-in-onemethodologythatencapsulatesalltherequiredservicesasasingledeployableartifact.Itworksonasingletechnologystackandisdeployedandscaledasasingleunit.Sincethereisonlyonetechnologystacktomaster,itiseasytodeploy,scale,andsetupamonitoringinfrastructureformonolithicapplications.EachteammemberworksononeormorecomponentsofthesystemandfollowsthedesignprincipleofSeparationofConcerns(SoC)(https://en.wikipedia.org/wiki/Separation_of_concerns).Suchapplicationsarealsoeasiertorefactor,debug,andtestinasinglestandalonedevelopmentenvironment.

Applicationsbasedonmonolithicarchitecturemayconsistofoneormoredeployableartifactsthatarealldeployedatthesametime.SuchamonolithicarchitectureisoftenreferredtoasaDistributedMonolith.

Forexample,averycommonmonolithicapplicationisawordprocessingapplication;MicrosoftWordisinstalledviaasingledeployableartifactandisentirelybuiltonMicrosoft.NETFramework(https://www.microsoft.com/net/).Therearevariouscomponentswithinwordprocessingapplication,suchastemplates,import/export,spell-checker,andsoon,thatworktogethertohelpcreateadocumentandexportittheformatofchoice.

Monolithicarchitectureappliesnotonlytostandaloneapplications,butalsotoclient-serverbasedapplicationsthatareprovidedasaserviceovertheweb.Suchclient-serverbasedapplicationshaveaclearlydefinedmultitierarchitecturethatprovidestherelevantservicestoitsendusersviaauserinterface.

Theuserinterfacetalkstoapplicationendpointsthatcanbeprogrammedusingwell-definedinterfaces.

Page 35: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Atypicalclient-serverapplicationmayadoptathree-tierarchitecturetoseparatethepresentation,businesslogic,andpersistencelayerfromeachother,asshownintheprecedingdiagram.Componentsofeachlayertalkstrictlytothecomponentsofthelayerbelowthem.Forexample,thecomponentsofthepresentationlayermaynevertalktothepersistencelayerdirectly.Iftheyneedaccesstodata,therequestwillberoutedviathebusinesslogiclayerthatwillnotonlymovethedatabetweenthepersistencelayerandthepresentationlayer,butalsodotherequiredprocessingtoservetherequest.Adoptingsuchacomponent-basedlayeredarchitecturealsohelpsinisolatingtheeffectofchangetoonlythecomponentsofdependentlayersinsteadoftheentireapplication.Forexample,changestothecomponentsofthebusinesslogiclayermayrequireachangeinthedependentcomponentsofthepresentationlayerbutcomponentsofthepersistencelayermayremainintact.

EventhoughamonolithicapplicationisbuiltonSoC,itisstillasingleapplicationonasingletechnologystackthatprovidesallrequiredservicestoitsusers.Anychangetosuchanapplicationrequirestobecompatiblewithalltheencapsulatedservicesandunderlyingtechnologystack.Inadditiontothat,itisnotpossibletoscaleeachserviceindependently.Anyscalingrequirementismetbydeployingmultipleinstancesoftheentiresystemasasingleunit.Ateam

Page 36: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

workingonsuchamonolithicapplicationscalesovertimeandhastoadapttonewertechnologiesasawhole,whichisoftenchallengingduetotherapidlychangingtechnologylandscape.Iftheydonotchangewiththetechnology,theentiresoftwarebecomesobsoleteovertimeandisdiscardedduetoincompatibilitywithnewersoftwareandhardware,orashortageoftalent.

Page 37: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MicroservicesMicroservicesareafunctionalapproachwellappliedtosoftware.Ittriestodecomposetheentireapplicationfunctionallyintoasetofservicesthatcanbedeployedandscaledindependently.Eachservicedoesonlyonejobanddoesitwell.Ithasitsowndatabase,decidesitsownschema,andprovidesaccesstodatasetsandservicesthroughwell-definedapplicationprogramminginterfacesthatarebetterknownasAPIs,oftenpairedwithauserinterface.APIsfollowasetcommunicationprotocols,butservicesarefreetochoosetheirowntechnologystackandcanbedeployedonhardwareofchoice.

Inamicroserviceenvironment,asshownintheprecedingdiagram,therearenolayerslikeinmonoliths;instead,eachserviceisorganizedaroundaboundedcontext(https://en.wikipedia.org/wiki/Domain-driven_design#Bounded_context)thataddsabusinesscapabilitytotheapplicationasawhole.Newcapabilitiesinsuchanapplicationareaddedasnewservicesthataredeployedandscaledindependently.Eachuserrequestinamicroservices-basedapplicationmaycalloneormoreinternalmicroservicetoretrievedata,processit,andgeneratetherequiredresponse,asshowninthefollowingdiagram.Suchsoftwareevolvesfasterandhaslowtechnologydebt.Theydonotgetmarriedtoaparticulartechnologystackandcanadoptanewtechnologyfaster:

Page 38: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 39: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatamanagementInamicroservices-basedapplication,databasesareisolatedforeachbusinesscapabilityandaremanagedbyonlyoneserviceatatime.AnyrequestthatneedsaccesstothedatamanagedbyanotherservicestrictlyusestheAPIsprovidedbytheservicemanagingthedatabase.Thismakesitpossibletonotonlyusethebestdatabasetechnologyavailabletomanagethebusinesscapability,butalsotoisolatethetechnologydebttotheservicemanagingit.However,itisrecommendedforthecallingservicetocacheresponsesovertimetoavoidtightcouplingwiththetargetserviceandreducethenetworkoverheadofeachAPIcall.

Forexample,aservicemanaginguserinterestsmightuseagraphdatabase(https://en.wikipedia.org/wiki/Graph_database)tobuildanetworkofusers,whereasaservicemanagingusertransactionsmightusearelationaldatabase(https://en.wikipedia.org/wiki/Relational_database)duetoitsinherentACID(https://en.wikipedia.org/wiki/ACID)propertiesthataresuitablefortransactions.ThedependentserviceonlyneedstoknowtheAPIstoconnecttotheservicefordataandnotthetechnologyoftheunderlyingdatabase.

Thisiscontrarytoamonolithiclayeredarchitecture,wheredatabasesareorganizedbybusinesscapability,whichmaybeaccessedbyoneormorepersistencemodulesbasedontherequest.Iftheunderlyingdatabaseisusingadifferenttechnology,theneachofthemodulesaccessingthedatabaseshavetocomplywiththesametechnology,thusinheritingthecomplexityofeachdatabasetechnologythatithasaccessto.

Databaseisolationshouldbedoneatthedatabaselevelandnotatthedatabasetechnologylevel.Avoiddeployingmultipleinstancesofthesamerelationaldatabaseorgraphdatabaseasmuchaspossible.Instead,trytoscalethemondemandandusetheisolationcapabilityofthesesystemstomaintainseparatedatabaseswithinthemforeachservice.

Theconceptofmicroservicesisverysimilartoawell-knownarchitecturecalled

Page 40: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

service-orientedarchitecture(SOA)(https://en.wikipedia.org/wiki/Service-oriented_architecture).Inmicroservices,thefocusisonidentifyingtherightboundedcontextandkeepingthemicroservicesaslightweightaspossible.Insteadofusingacomplexmessage-orientedmiddleware(https://en.wikipedia.org/wiki/Message-oriented_middleware)suchasESB(https://en.wikipedia.org/wiki/Enterprise_service_bus),asimplemodeofcommunicationisusedthatisoftenjustHTTP.

"ArchitecturalStyle[ofMicroservices]isreferredtoasfine-grainedSOA,perhapsserviceorientationdoneright"

-MartinFowleronmicroservices

Page 41: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

WhentousewhatThemonolithiclayeredarchitectureisoneofthemostcommonarchitecturesinuseacrossthesoftwareindustry.Monolithicarchitecturesarewellsuitedfortransaction-orientedenterpriseapplicationsthathavewell-definedfeatures,changelessoften,andhavecomplexbusinessmodels.Forsuchapplications,transactionsandconsistencyareofprimeimportance.Theyrequireadatabasetechnologywithbuilt-insupportforACIDpropertiestostoretransactions.Ontheotherhand,microservicesaresuitedbetterforSoftware-as-a-Service,internet-scaleapplicationsthatarefeature-firstapplicationswitheachfeaturefocusedonasinglebusinesscapability.Suchapplicationschangerapidlyandarescaledpartiallyperbusinesscapabilityondemand.Transactionsandconsistencyinsuchapplicationsarehardtoachieveduetomultipleservices,ascomparedtomonolithsthatareimplementedassingleapplications.

Itisrecommendedtostartwithawelldesigned,modularmonolithicapplicationirrespectiveofthedomaincomplexityortransactionalnature.Generally,allapplicationsstartasamonolithicapplicationthatcanbedeployedfasterasasingleartifactandlatersplitintomicroserviceswhentheapplication'scomplexitybeginstooutweightheproductivityoftheteam.

Theproductivityoftheteammaystartdecreasingwhenchangestothe

Page 42: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

monolithicapplicationstartaffectingmorethanonecomponent,asshownintheprecedingdiagram.Thesechangesmaybearesultofanewfeaturebeingaddedtotheapplication,adatabasetechnologyupgrade,ortherefactoringofexistingcomponents.Anychangesmadetotheapplicationmustkeeptheentireteamin-sync,especiallythedeploymentteam,ifthereareanychangesrequiredinthedeploymentprocesses.Communicatingsuchchangesinalargeteamoftenresultsinacoordinationnightmare,multiplechangerequests,andin-turn,reducestheoverallproductivityoftheteamworkingontheapplication.

Productivityalsodependsontheinitialchoicesmadewithrespecttothetechnologystackanditsflexibilityofimplementation.Forexample,ifanewfeaturerequiresalibrarythatisreadilyavailablewithadifferenttechnologystackoraprogramminglanguage,itbecomeschallengingtoadoptasitdoesnotconformtotheexistingtechnologystackoftheapplicationcomponents.Insuchcases,theteamendsupimplementingthesamefeaturesetforthecurrenttechnologystackfromscratch,andthatinturnreducesproductivityandfurtheraddstothetechnologydebt.

Beforestartingwithmicroservices,firstsetupbestdesignprinciplesamongteammembers.Next,trytoevaluatetheexistingmonolithwithregardtocomponentsandtheirinteraction.Ifrefactoringcanhelpreducethedependencybetweenthecomponents,dothatfirstinsteadofdisintegratingyourapplicationintomicroservices.

Page 43: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MonolithicapplicationstomicroservicesMostapplicationsstartasamonolith.Amazon(http://highscalability.com/amazon-architecture)startedwithamonolithicPerl(https://en.wikipedia.org/wiki/Perl)/C++(https://en.wikipedia.org/wiki/C%2B%2B)application,andTwitter(http://highscalability.com/blog/2013/7/8/the-architecture-twitter-uses-to-deal-with-150m-active-users.html)startedwithamonolithicRails(https://en.wikipedia.org/wiki/Ruby_on_Rails)application.Bothorganizationshavenotonlygonethroughmorethanthreegenerationsofsoftwarearchitecturalchanges,buthavealsotransformedtheirorganizationalstructuresovertime.Today,allofthemarerunningonmicroserviceswithteamsorganizedaroundservicesthataredeveloped,deployed,scaled,andmonitoredbythesameteamindependently.Theyhavemasteredcontinuousintegrationandcontinuousdeliverypipelineswithautomateddeployment,scaling,andmonitoringofservicesforreal-timefeedbacktotheteam.

Page 44: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IdentifyingcandidatesformicroservicesThetop-mostchallengeinmigratingfromamonolithicapplicationtomicroservicesistoidentifytherightcandidatesformicroservices.Awellstructuredandmodularizedmonolithicapplicationalreadyhaswell-definedboundaries(boundedcontexts)thatcanhelpdisintegratetheapplicationintomicroservices.Forexample,theUser,Orders,andInterestmodulesalreadyhavewell-definedboundariesandaregoodcandidatestocreatemicroservicesfor.Iftheapplicationdoesnothavewell-definedboundaries,thefirststepistorefactortheexistingapplicationtocreatesuchboundedcontextsformicroservices.Eachboundedcontextmustbetiedtoabusinesscapabilityforwhichaservicecanbecreated.

Anotherapproachinidentifyingtherightcandidatesformicroservicesistolookatthedataaccesspatternsandassociatedbusinesslogic.Ifthesamedatabaseisbeingupdatedbymultiplecomponentsofamonolithicapplication,thenitmakessensetocreateaservicefortheprimarycomponentwithassociatedbusinesslogicthatmanagesthedatabaseandmakesitaccessibletootherservicesviaAPIs.Thisprocesscanberepeateduntildatabasesandtheassociatedbusinesslogicaremanagedbyoneandonlyoneservicethathasasmallsetofresponsibilities,modeledaroundabusinesscapability.

Page 45: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Forexample,amonolithicapplicationconsistingofUser,Interest,andOrderscomponentscanbemigratedintomicroservicesbypickingonecomponentatatimeandcreatingamicroservicewithanisolateddatabase,asshownintheprecedingdiagram.Tostartwith,firstpicktheonewiththeleastdependency,theUsermodule,andcreatetheUserServiceservicearoundit.AllothercomponentscannowtalktothisnewUserServiceforUserManagement,includingauthentication,authorization,andgettinguserprofiles.Next,picktheOrdersmodulebasedontheleastdependencylogic,andcreateaservicearoundit.Finally,picktheInterestmoduleasitisdependentonboththeUserandOrdersmodules.Sincewehavethedatabasesisolated,wecanalsoswapoutthedatabaseforInterestwithmaybeagraphdatabasethatisefficienttostoreandretrieveuserinterestsduetoitsinherentcapabilityofstoringrelationshipsasagraph.

Inadditiontoorganizingyourmicroservicesaroundbusinesscapabilitiesanddatabaseaccesspatterns,lookforcommonareas,suchasauthentication,authorization,andnotification,thatcanbeperfectedonceasaserviceandcanbeleveragedbyoneormore

Page 46: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

microserviceslater.

Page 47: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ReleasecycleandthedeploymentprocessOnceamonolithicapplicationisdisintegratedintomicroservices,thenextstepistodeploythemintoproduction.Monolithicapplicationaremostlydeployedasasingleartifact(JARs,WARs,EXEs,andmore)thatarereleasedafterextensivetestingbythequalityassurance(QA)team.Typically,developersworkonvariouscomponentsoftheapplicationandreleaseversionsfortheQAteamtopickandvalidateagainstthespecification,asshownundertheOrgStructureofmonolithicarchitectureinthefollowingdiagram.Eachiterationmayinvolvetheadditionorremovaloffeaturesandbugfixes.Thereleasegoesthroughmultipledevelopers(dev)andQAteamiterationsuntiltheQAteamflagsoffthereleaseasstable.OncetheQAteamflagsofftherelease,thereleasedartifactishandedovertotheITopsteamtodeployitinproduction.Ifthereareanyissuesinproduction,theITopsteamasksthedevteamtofixthem.Oncetheissuesarefixed,thedevteamtagsanewreleaseforQAthatagaingoesthroughthesamedev-QAiterationsbeforebeingmarkedasstableandeventuallyhandedovertoIT/ops.Duetothisprocess,anyreleaseforamonolithicapplicationsmayeasilytakeuptoamonth,oftenthreemonths.

Ontheotherhand,formicroservices,teamsareorganizedintogroupsthatfully

Page 48: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ownaservice.Theteamisresponsiblefornotonlydevelopingtheservice,butalsoforputtingtogetherautomatedtestcasesthatcantesttheentireserviceagainsteachchangesubmittedfortheservice.Sincetheserviceistobetestedinisolationforitsfeatures,itisfastertorunentiretestsuitesfortheserviceforeachchangesubmittedbythedevelopers.Additionally,theteamitselfcreatesdeployablebinariesoftenpackagedintocontainers(https://en.wikipedia.org/wiki/Linux_containers),suchasDocker(https://en.wikipedia.org/wiki/Docker_(software)),thatarepublishedtoacentralrepositoryfromwheretheycanbeautomaticallydeployedintoproductionbysomewell-knowntools,suchasKubernetes(https://en.wikipedia.org/wiki/Kubernetes).Theentiredevelopmenttoproductiontimelineiscutshorttodays,oftenhours,astheentiredeploymentprocessisautomated.WewilllearnmoreaboutdeployingmicroservicesinproductionandhowtousethesedeploymenttoolsinPart-4,thelastpartofthisbook.

Thereisareasonwhyalotofmicroserviceprojectsfailandonlyafewsucceed.Migratingfromamonolithicarchitecturetomicroservicesmustnotonlyfocusonidentifyingtheboundedcontexts,butalsotheorganizationalstructureanddeploymentmethodologies.Teamsmustbeorganizedaroundservicesandnotprojects.Eachteammustowntheservicerightfromdevelopmenttoproduction.Sinceeachteamownstheresponsibilityfortesting,validation,anddeployment,theentireprocessshouldbeautomatedandtheorganizationmustmasterit.Developmentanddeploymentcyclesmustbeshortwithimmediatefeedbackviafine-grainedmonitoringofthedeployedmicroservices.

Automationiskeyforanysuccessfulmicroservicesproject.Testing,deployment,andmonitoringmustbeautomatedbeforemovingmicroservicestoproduction.

Page 49: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,welearnedaboutmonolithicandmicroservicesarchitecturesandwhymicroservicesarebecomingpopularintheindustry,especiallywithweb-scaleapplications.Welearnedabouttheimportanceofdatabaseisolationwithmicroservicesandhowtomigrateamonolithicapplicationtomicroservicesbyobservingthedatabaseaccesspattern.Wealsodiscussedtheimportanceofthemonolith-firstapproachandwhentomovetowardsmicroservices.Weconcludedwithacomparisonofmonolithicandmicroservicesarchitectureswithregardtothereleasecycleanddeploymentprocess.

Thenextchapterofthisbookwilltalkaboutmicroservicearchitectureindetail;wewilllearnmoreaboutdomain-drivendesignandhowtoidentifytherightsetofmicroservices.InChapter3,MicroservicesforHelpingHandsApplication,thelastchapterofPart-1,wewillpickareal-lifeusecaseformicroservicesanddiscusshowtodesignitusingtheprinciplesofmicroservicearchitecture.

Page 50: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MicroservicesArchitecture

"Gathertogetherthethingsthatchangeforthesamereasons.Separatethosethingsthatchangefordifferentreasons."

-RobertMartin,SingleResponsibilityPrinciple

Softwarearchitectureplaysakeyroleinidentifyingthebehaviorofthesystembeforeitisbuilt.Awell-designedsoftwarearchitectureleadstoflexible,reusable,andscalablecomponentsthatcanbeeasilyextended,verified,andmaintainedovertime.Sucharchitecturesevolveovertimeandhelppavethewayfortheadoptionofnext-generationarchitectures.Forexample,awell-designedmonolithicapplicationthatisbuiltontheprinciplesofSeparationofConcern(SoC)iseasiertomigratetomicroservicesthananapplicationthatdoesnothavewell-definedcomponents.Inthischapter,youwill:

LearnasystematicapproachtodesigningmicroservicesusingtheboundedcontextLearnhowtosetupcontractsbetweenmicroservicesandisolatefailuresLearnhowtomanagedataflowsandtransactionsamongmicroservicesLearnaboutservicediscoveryandtheimportanceofautomateddeployment

Page 51: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Domain-drivendesignIdealenterprisesystemsaretightlyintegratedandprovideallbusinesscapabilitiesasasingleunitthatisoptimizedforaparticulartechnologystackandhardware.Suchmonolithicsystemsoftengrowsocomplexovertimethatitbecomeschallengingtocomprehendthemasasingleunitbyasingleteam.Domain-drivendesignadvocatesdisintegratingsuchsystemsintosmallermodularcomponentsandassigningthemtoteamsthatfocusonasinglebusinesscapabilityinaboundedcontext(https://en.wikipedia.org/wiki/Domain-driven_design#Bounded_context).Oncedisintegrated,allsuchcomponentsaremadeapartofanautomatedcontinuousintegration(CI)processtoavoidanyfragmentation.Sincethesecomponentsarebuiltinisolationandoftenhavetheirowndatamodelsandschema,thereshouldbeawell-definedcontracttointeractwiththecomponentstocoordinatevariousbusinessactivities.

ThetermDomain-drivendesignwasfirstcoinedbyEricJ.Evansasthetitleofhisbookin2003.InPart-IV,Evanstalksabouttheboundedcontextandtheimportanceofcontinuousintegration,whichformsthebasisofanymicroservicesarchitecture.

Page 52: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

BoundedcontextAdomainmodelisaconceptualmodelofabusinessdomainthatformalizesitsbehavioranddata.Asingleunifieddomainmodeltendstogrowincomplexitywithbusinesscapabilitiesandincreasesthecollaborationoverheadamongtheteamduetohighcoupling.Toreducecoupling,domain-drivendesignrecommendsdefiningamodelforeachbusinesscapabilitywithawell-definedboundarytoseparatethedomainconceptswithinthemodelfromtheonesoutside.Eachsuchmodelthenfocusesonthebehavioranddataconfinedtoasinglebusinesscapability,andthusgetsboundedbyasingleapplicationcontext,calledaboundedcontext.Monolithicapplicationstendtohaveaunifieddomainmodelfortheentirebusinessdomain,whereasformicroservices,domainmodelsaredefinedforeachidentifiedboundedcontext.

Forexample,insteadofdefiningasingleunifieddomainmodelforane-commerceapplication,itisbettertodividetheapplicationintoboundedcontextsofCustomer,Sales,andMarketinganddefineadomainmodelforeachofthesecontexts,asshownintheprecedingdiagram.Suchfocuseddomainmodelscanthenconquereachcontextbasedonbusinesscapabilities.Forexample,CustomerContextcanfocusonlyonuserandprofilemanagement,SalesContextcanhandleordersandtransactions,andMarketingContextcankeeptrackofuserinterestsforfocusedmarketing.

Page 53: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IdentifyingboundedcontextsOneofthemostchallengingtasksindesigningamicroservices-basedarchitectureistogettheboundedcontextrightforeachmicroservice.Itisaniterativeprocessthatrequiresathoroughunderstandingofbusinesscapabilitiesandbusinessdomain.Businesscapabilitiesmustnotbeconfusedwithbusinessfunctionsorprocesses.Businesscapabilitiestargetthewhatpartofabusinessandhaveanoutcome,whereasabusinessprocesstargetsthehowpart.Forexample,alertingisabusinesscapability,whereassendinganemailisabusinessprocess.Abusinesscapabilitymayincorporateoneormorebusinessprocesses.

Boundedcontextsmusttargetbusinesscapabilitiesandnotbusinessprocesses.Toidentifytherightboundedcontexts,itisrecommendedtostartwithamonolithicapplicationwithasingleunifiedmodelandanalyzeititerativelyovertimeforhighcouplingareas.Often,highcouplingareasaregoodtargetpointstosplitthedomainmodelintosub-domainmodelsthatcaninteractusingfixedcontractsandhelpreducetightcoupling.However,suchsub-domainsmustbefurthervalidatedagainstbusinesscapabilitiestomakesurethattheytargetonlyonebusinesscapability.

Microservicesmustbeorganizedaroundabusinesscapabilitywithinaboundedcontextandowntheirpresentation,businessdomain,andpersistencelayer.Theymusttakeresponsibilityfortheend-to-enddevelopmentstackincludingfunctions,datamodel,persistence,userinterface,andthecontracttoaccesstheserviceusingAPIs,oftenoverHTTP(S).

Page 54: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Organizingaroundboundedcontexts"Anyorganizationthatdesignsasystem(definedbroadly)willproduceadesignwhosestructureisacopyoftheorganization'scommunicationstructure"

-MelvynConway,1967

Generally,theorganizationstructurecontributesheavilytothedesignoftheapplication.Therefore,boundedcontextsshouldneverbeidentifiedonthebasisoftheexistingstructureoftheorganization.Instead,theorganizationmustbestructuredaroundboundedcontextssuchthattheentireteamcanworkonaserviceinisolation.

Atypicalorganization,workingonamonolithicapplication,isbuiltaroundlayersofpresentation,businesslogic,andpersistence,asshownintheprecedingdiagram.TheytendtohaveaseparateteamofUIdesignersandUI/UXexpertsforthepresentationlayer,ateamofbackenddeveloperstoimplementthedomainmodel,andateamofdatabaseadministratorstocreateadatabasefordeveloperstoaccess.Suchanorganizationstructureisidealforanapplicationwithasmallersetofbusinesscapabilities.

Oncetheapplicationgrows,anychangestotheapplicationrequirecommunicationtobemadeacrosstheteamofUIengineers,backenddevelopers,anddatabaseadministrators.Often,suchcommunicationleadstosomanyback-and-forthexchangesinvolvingdesigndocumentsandspecificationchangesthat

Page 55: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

itbecomesoverwhelmingfortheteamstotranslatetherequirementscorrectlytotheimplementation.Suchcommunicationoverheadaddsdelaystotheprojectandbringsdowntheproductivityoftheentireteamworkingontheproductasawhole.

Boundedcontext,ifidentifiedcorrectly,solvesthisproblembylocalizingtheteamofUIdevelopers,backenddevelopers,anddatabaseadministratorstofocusonasinglebusinesscapability.Suchboundariesmakesurethatthecommunicationbetweentheteamsisboundedbyafixedcontractthatissetattheservicelevel.Thismakesitpossibletoreduceaconsiderablecommunicationoverhead,asanychangesmadetoaserviceareconfinedwithintheteamworkingontheservice.Forexample,thelocalizedteamofUserServiceandOrdersServicewillcommunicateonlytodiscusstheserviceAPIsthattheuserserviceisexposingforOrdersServicetogetthecustomerdetails.Sinceanychangestothecustomerschemaortheordersschemashouldnotimpacteachotherasperthedefinitionofboundedcontext,itisnotrequiredtocommunicatesuchchangestotheotherservice.

Page 56: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Components

Oncetheboundedcontextsareidentifiedformicroservicesandtheorganizationstructureisaligned,eachmicroservicemustbeconsideredasaproductthatistested,deployed,andscaledinisolationbythesameteamthatdevelopedit.Awell-designedmicroservicemustneverexposeitsinternaldatamodeltotheoutsideworlddirectly.Instead,itmustmaintainaservicecontractthatmapstoitsinternalmodelsuchthatitcanevolveovertimewithoutaffectingthedependentmicroservices.

Component-basedsoftwareengineering(https://en.wikipedia.org/wiki/Component-based_software_engineering)definesacomponentasareusablemodulethatisbasedontheprinciplesofSoCandencapsulatesasetofrelatedfunctionsanddata.Inthecontextofmicroservicesarchitecture,itisrecommendedtoimplementeachserviceasacomponentthatisindependentlyswappableanddeployablewithoutaffectinganyothermicroservices.

Page 57: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

HexagonalarchitectureHexagonalarchitecture(http://alistair.cockburn.us/Hexagonal+architecture),alsoknownastheportsandadapterspattern,aimstodecouplebusinesslogicfromotherpartsofthecomponent,especiallythepersistenceandserviceslayers.Acomponent,builtontheportsandadapterspattern,exposesasetofportstowhichoneormoreadapterscanbeaddedasnecessary.Forexample,totestandverifythecorebusinesslogicinisolation,amockdatabaseadaptercanbepluggedinandlaterreplacedwitharuntimedatabaseadapterinproduction.

Aportisanentrypointthatisprovidedbythecorebusinesslogictointeractwithotherpartsofthecomponent.Anadapterisanimplementationofaport,andtheremaybemorethanoneadapterdefinedforasingleportbasedontherequirement.Forexample,aRESTadapterisusedtoacceptrequestsfromexternalusersorothermicroservicescomponents.ItinternallycallstheserviceAPIportdefinedbythecorebusinesslogicthatperformsthatrequestedoperationandgeneratesaresponse.Similarly,adatabaseadapterisusedbythecorebusinesslogictointeractwiththeexternaldatabaseviaitsdatabaseport,asshowninthefollowingdiagram:

Page 58: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Basedontheirapplicabilityandusage,theRESTadapteranddatabaseadapterareoftenreferredtoasprimaryandsecondaryadaptersrespectively.Similarly,theserviceAPIportanddatabaseportarereferredtoasprimaryandsecondaryportsrespectively.Primaryportsarecalledbytheprimaryadaptersandtheyactasthemaininterfacebetweenthecorebusinesslogicanditsusers,whereassecondaryportsandsecondaryadaptersareusedbythecorebusinesslogictogenerateeventsorinteractwithexternalservices,likeadatabase.Primaryadaptershelpvalidateservicerequestswithrespecttoaserviceschemaandcallcorebusinesslogicfunctions,whereasthecorebusinesslogiccallsthefunctionsofsecondaryadapterstohelptranslatetheapplicationschematotheexternalserviceschema,likethatofadatabase.Primaryadaptersareinitializedwiththeapplication,butreferencestothesecondaryadaptersarepassedtothecorebusinesslogicviadependencyinjection.

Thenamehexagonalarchitectureisderivedfromthestructureofacomponentthathassixports,butthatisnotarule.Theideaofrepresentingthearchitectureasahexagonisjusttoremovethenotionofone-dimensionallayeredarchitectureandhaveroomtoinsertportsandadaptersasrequired.

Page 59: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Messagingandcontracts

Inmonolithicapplications,messagingbetweencomponentsismostlyachievedusingfunctioncalls,whereasformicroservices,itisachievedusinglightweightmessagingsystems,oftenHTTP(S).Usingalightweightmessagingsystemisoneofthemostpromisingfeaturesofmicroservicesandmakesiteasiertoadoptandscale,ascomparedtoservice-orientedarchitecture(SOA)thatusesacomplexmessagingsystemwithmultipleprotocols.Microservicesaremoreaboutkeepingtheendpointssmartandthecommunicationchannelsassimpleaspossible.

Inamicroservicesarchitecture,oftenmultiplemicroservicesneedtointeractwitheachothertoachieveaparticulartask.Theseinteractionscanbeeitherdirect,viarequest-response-based(https://en.wikipedia.org/wiki/Request-response)communication,orthroughalightweightmessage-orientedmiddleware(MOM)(https://en.wikipedia.org/wiki/Message-oriented_middleware).Directmessagingissynchronous,thatis,therequesterwaitsfortheresponsetobereturned,whereasamessage-orientedmiddlewareisprimarilyusedforasynchronouscommunication.

Page 60: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DirectmessagingIndirectmessaging,eachrequestissentdirectlytothemicroserviceonitsAPIendpoint.Suchrequestsmaybeinitiatedbyusers,applications,orbyothermicroservicesthatintegratewiththetargetmicroservicetocompleteaparticulartask.MostlytheendpointstohandlesuchrequestsareimplementedusingREST(https://en.wikipedia.org/wiki/Representational_state_transfer),whichallowsresourceidentifierstobeaddresseddirectlyviaHTTPbasedAPIswithasimplemessagingstyle.

RESTisanarchitecturalstylewithpredefinedoperationsbasedonHTTPrequestmethods,suchasGET,PUT,POST,andDELETE.ThestatelessnatureofRESTmakesitfast,reliable,andscalablewithmultiplecomponents.

Forexample,inatypicalmicroservices-basede-commerceapplication,anadministrativeusermaywishtocreate,update,ormanageusersviatheUserService'sRESTendpoint.Inthatcase,theusercandirectlycalltheRESTAPIoftheUserService,asshowninthefollowingdiagram:

Page 61: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Similarly,amobileapplicationmaywanttoqueryuserinterestsviaInterestService'sRESTendpointofthee-commerceapplication.SincetheInterestServicedependsonboththeUserServiceandtheOrdersService,itmayfurtherinitiatetwoseparatecallstotheRESTendpointofthesetwoservicestogetthedesiredresponsewhichitcanmergewiththeuserinterestsdataandgeneratetheresponsefortherequestingapplication.Mostly,allthesekindsofrequest-responsearesynchronousinnatureasthesenderexpectsaresponsewiththeresultsinthesamecall.Asynchronousrequestsaremostlyusedtosendalertsormessagestorecordanoperation,andinthosecases,thesenderdoesn'twaitfortheresponseandexpectstheactiontobetakeneventually.

Avoidbuildinglongchainsofsynchronouscallsasthatmayleadtohighlatencyandanincreasedpossibilityoffailureduetomultiplehopsofintermediaterequestsandnetworkround-tripsamongtheparticipatingservices.

MessageformatsusedwithRESTendpointsaremostlytext-basedmessageformatssuchasJSON,XML,orHTML,assupportedbytheendpointimplementation.BinarymessageformatssuchasThrift(https://en.wikipedia.org/wiki/Apache_Thrift),ProtoBuf(https://en.wikipedia.org/wiki/Protocol_Buffers),andAvro(https://avro.ap

Page 62: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ache.org/)arealsopopularduetotheirwidesupportacrossmultipleprogramminglanguages.

Usedirectmessagingonlyforsmallermicroservices-baseddeployments.Forlargerdeployments,itisadvisabletogowithAPIgatewaysthatactasthemainentrypointforallclients.SuchAPIgatewayshelpmonitorrequestsformicroservicesandalsoassistinthemaintenanceandupgradeoperations.

Page 63: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ObservermodelTheobservermodelusesamessagebroker(https://en.wikipedia.org/wiki/Message_broker)atitscoretosendmessagesamongmicroservices.Amessagebrokerprovidescontentandtopic-basedroutingusingthepublish-subscribepattern(https://en.wikipedia.org/wiki/Publish-subscribe_pattern),whichmakesthesenderandreceiverindependentofeachother.Allobservingmicroservicessubscribetooneormoretopicsthroughwhichtheycanreceivethemessagesandalsoconnecttotopicsonwhichtheycanpublishthemessagesforotherobservers.Allinteractionsdoneviamessagebrokersareasynchronousinnatureanddonotblockthesender.Thishelpsinscalingboththepublishersandsubscribersindependently.Amicroservicesarchitecturethatisbuiltononlyasynchronousandnon-blockinginteractionsusingmessagebrokerscalesverywell.

Messagebrokersarealsousedtomanageworkloadsinscenarioswheretherateofpublishedmessagesishigherthantherateatwhichthesubscriberisabletoprocessthemessages.Messagebrokersalsoprovidereliablestorage,multipledeliverysemantics(atleastonce,exactlyonce,andsoon),andalsotransactionmanagementthatisusefulfordatamanagementacrossmicroservices.BinarymessageformatssuchasThrift,ProtoBuf,andAvroarepreferredovertextformatsformessagebrokers.

Page 64: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Intheobservermodel,alltherequestsgeneratedbyusers,applications,ormicroservicesarepublishedonatopictowhichoneormoremicroservicescansubscribeandreceivethemessageforprocessing,asshownintheprecedingdiagram.Thegeneratedresultscanalsobewrittenbacktoatopicthatcanbelaterpickedbyanothermicroservice,whichmayeitherreporttheresponsebacktotheapplicationorpersistitwithinadatastore.Ifasubscriberfails,themessagebrokercanreplaythemessage.Similarly,ifallsubscribersarebusy,themessagebrokercanaccumulatethemessagesuntiltheyareprocessedbythesubscribers.

Theobservermodelhelpstoachievebetterscalabilityascomparedtodirectmessagingattheexpenseofasinglepointoffailureofthemessagebroker.

Page 65: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Servicecontracts

Contractsarerequiredforentitiestointeractwitheachother.Theydefinethemessageformatandmediumofcommunicationfortheparticipatingentities.Inamonolithicenvironment,itissimplerforcomponentstointeractwiththetargetcomponentsusingtheinterfacesandfunctionsexposedbythem.Sinceafunctionclearlydefinesitsmessageformatasinputparameters,themessagepassingbetweenthecomponentscanbedonebyjustafunctioncallwiththerequiredinputparameters.Functioncallsarefurthersimplifiedinamonolithduetoacommonunderlyingtechnologystack.Contractsarealsoeasiertomaintainformonolithicapplicationsbecauseanychangedonetothecontractistestedandverifiedacrosscomponentsforcompatibility.Suchapplicationsarealsoversionedasawholeandnotper-component.Thefollowingtablecomparesandcontrastsmonolithicandmicroservicearchitecturalstyles:

Monolithicarchitecture Microservices

Entity Components Servicesbycapability

EndpointInterfacesandfunctions

RESTURIs(HTTP)/Thrift/Avro/Protobuf

Medium Functioncalls

HTTP/publish-subscribeviamessagebroker(observer)

Contract Functiondefinition

APIspecification(Swagger,RAML)/messageserialization(ThriftIDL,AvroSchema)

Version Singleversion Separateversionsforeachservice

Technology Single Polyglot

Ascomparedtoamonolith,inamicroservices-basedenvironment,thereisa

Page 66: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

servicedeployedforeachbusinesscapabilitythatmayormaynotbeusingthesametechnologystack.Insuchcases,servicecontractsbecomeabsolutelymandatoryformicroservicestounderstandthemessageformatsandcommunicationmediumacceptedbyothermicroservicesandinteractwiththem.Moreover,thesemessageformatsneedtobelanguage-agnostictoallowanymicroservicetocommunicateirrespectiveofthetechnologystackinwhichtheyareimplemented.

Microservicesshouldneverexposetheirinternaldatamodeldirectlyasapartofamessagecontracttotheexternalworld.Theinternaldatamodelmustbedecoupledfromtheexternalservicecontractandthereshouldbeawaytoconvertandvalidatethecontractatentryandexit.Thishelpstoevolvethedatamodelofamicroserviceinisolationwithoutaffectingthecontractwithothermicroservices.Ifthereisachangerequiredintheservicecontract,itmustbeversionedandeachversionofthecontractmustbesupportedbythemicroserviceaslongasitisinusebyanyexternalservice.Aversionshouldbediscardedonlywhentherearenootherservicesusingtheobsoleteversionandthereisnolongeraneedtorollbacktheservicetoitspreviousversion.

Avoidmultipleversionsofservicecontracts(andmessageformats)asmuchaspossible.Chooseaflexiblemessageformatthatcanevolveovertimewithoutbreakingpreviousversions.

MicroservicesthatexposeRESTAPIsprimarilyusetheRESTAPIdefinitionandHTTPverbs(https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)todefinewell-formedURIs.ServicecontractsforREST-basedAPIsaredefinedusingframeworkssuchasSwagger(https://swagger.io/)andRAML(https://raml.org/).MicroservicesthatusetheobserverpatterntendtoacceptmessagesinThrift,Avro,orProtoBufformats.Eachoftheseframeworkshasawaytodefinelanguage-agnosticspecificationsandsupportsmostofthepopularprogramminglanguages.

Page 67: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServicediscoveryAlltheAPIsexposedbyamicroserviceareaccessibleviatheIPaddressandportofthehostmachineonwhichthemicroserviceisdeployed.SincemicroservicesaredeployedinavirtualmachineoracontainerthathasadynamicIP,itisquitepossiblethattheIPsandportsthatareallocatedtothemicroserviceAPIsmaychangeovertime.Therefore,IPaddressesandportsofservicesshouldneverbehardcodedbythedependingmicroservice.Instead,thereshouldbeacommondatabaseofalltheservicesthatareactiveforthecurrentapplication.Suchadatabaseofservicesiscalledtheserviceregistryinmicroservicesarchitectureandisalwayskeptuptodatewiththelocationofthemicroservices.ItkeepsthedetailsofalltheactivemicroservicesincludingtheircurrentIPaddressandport.Microservicesthenquerythisserviceregistrytodiscoverthecurrentlocationoftherequiredmicroservicesandconnecttothemdirectly.

Page 68: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServiceregistryTheserviceregistryactsasadatabaseofmicroservices.ItmusthaveadedicatedstaticIPaddressorafixedDNSnamethatmustbeaccessiblefromalltheclients,asshowninthefollowingdiagram.Sincealltheclientsdependonaserviceregistrytolookupthetargetservices,italsobecomesasinglepointoffailurefortheentiremicroservicesarchitecture.Therefore,theimplementationoftheserviceregistrymustbeextremelylightweightandshouldsupporthighavailabilitybydefault.SomecommontoolsthatcanbeusedasaserviceregistryareApacheZookeeper(http://zookeeper.apache.org/),etcd(https://github.com/coreos/etcd),andconsul(https://www.consul.io/):

Tokeeptheregistryuptodate,microservicesshouldeitherimplementthestartup/shutdowneventtoregister/deregisterwiththeserviceregistrythemselvesorthereshouldbeanexternalserviceconfiguredtokeeptrackofservicesandkeeptheregistryuptodate.Someorchestrationtools,suchasKubernetes(https://kubernetes.io/),supportserviceregistryoutoftheboxandmaintaintheregistryfortheentireinfrastructure.

Clientsmustcachethelocationoffrequentlyusedmicroservicestoreducedependencyontheserviceregistry,butthelocationmustbesyncedperiodicallywiththeserviceregistryforup-to-dateinformation.

Page 69: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServicediscoverypatternsMicroservices-basedapplicationsmayoftenscaletosuchalargenumberofservicesthatitmaynotbefeasibleforeachmicroservicetokeepatrackofallotheractiveservicelocations.Insuchscenarios,theserviceregistryhelpsindiscoveringmicroservicestoperformaparticulartask.Thereareprimarilytwopatternsforservicediscovery—client-sidediscoveryandserver-sidediscovery,asshowninthefollowingdiagram.

Intheclient-sidediscoverypattern,theresponsibilityfordeterminingthelocationofservicesbyqueryingtheserviceregistryisontheclient.Therefore,theserviceregistrymustbeaccessibletotheclienttolookupthelocationoftherequiredservices.Also,eachclientmusthaveservicediscoveryimplementationbuilt-inforthispatterntowork.

Ontheotherhand,intheserver-sidediscoverypattern,theresponsibilityforconnectingwiththeserviceregistryandlookingupthelocationofservicesisofarouteroragatewaythatactsasaloadbalanceraswell.Clientsjustneedtosendarequesttoarouterandtheroutertakescareofforwardingtherequesttotherequiredservice.OrchestrationtoolssuchasKubernetessupportserver-sidediscoveryusingproxies.

Page 70: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Theserver-sidediscoverypatternmustbepreferredforalarge-scaledeployment.Itcanalsobeusedasacircuitbreakertopreventresourceexhaustionbycontrollingthenumberofopenrequeststoaservicethathasencounteredconsecutivefailuresorisnotavailable.

Page 71: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatamanagementInamicroservices-basedarchitecture,thedatamodelandschemamustnotbesharedamongboundedcontexts.Eachmicroservicemustimplementitsowndatamodelbackedbyadatabasethatisaccessibleonlythroughtheserviceendpoints.Microservicesmayalsopublisheventsthatcanbeconsideredasalogofthechangestheserviceappliestoitsisolateddatabase.Keepingapplicationdatauptodateacrossmicroservicesmayalsoaddtothenetworkoverheadanddataduplication.

Page 72: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DirectlookupAlthoughmicroserviceshavetheirownisolatedpersistence,anapplicationimplementedusingmicroservicesmayneedtosharedataamongasetofservicestoperformtasks.Inamonolithicenvironment,sincethereisacommondatabase,itiseasiertosharedataandmaintainconsistencyusingtransactions.Inamicroservicesenvironment,itisnotrecommendedtoprovidedirectaccesstothedatabasemanagedbyaservice,asshowninthefollowingdiagram:

Forexample,whenauserplacesaneworder,theOrderServicemayneedaccesstothedeliveryaddress;thatis,theuseraddressfromtheUserService.Similarly,oncetheorderisplaced,theUserServicewouldliketoupdatethetotalordersplacedtilldatebytheuserinitsdatabase.SincetheuserdatabaseismaintainedbytheUserServiceandtheOrdersDatabaseismaintainedbytheOrdersService,thesetwoserviceswillgettherequireddetailsviatheAPIsexposedbytheotherserviceonly.Theyshouldnotbeallowedtodirectlyupdateoraccessdatabasesmaintainedbyanotherservice.Thishelpsinalwaysmaintainingasinglecopyoftheuserandorderdatabasesthatisnotaccessibletoanyothermicroservicedirectly.

Page 73: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AsynchronouseventsGettingdataviaserviceendpointssynchronouslymaybecomeoverwhelmingforservicesthatmaintainawidelyuseddatabase,liketheusersdatabase.Therefore,itisrecommendedforservicestomaintainaread-onlycacheforsuchdatabasesandkeepituptodateasynchronouslyusingevents,asshowninthefollowingdiagram:

Forexample,insteadoflookinguptheaddressorordercountusingserviceendpointssynchronously,servicessuchasUserServiceandOrdersServicecanpublishtheeventsofinterestonamessagequeueinorderofoccurrence.TheUserServicecanthenreceivetheorderseventfromtheOrdersServiceviatheMessageBrokerandupdateitsdatabasewiththeorderscountorcacheit.Similarly,theOrdersServicecanreceiveanyaddressupdateeventfromtheUserService,keeptheaddressuptodatefortheuserwithinitscache,andrefertoitasandwhenrequiredtogenerateordersforusers.

Microservicesshouldalwayshaveanisolateddatabase,butitisnotrecommendedtocreateseparateservicestoisolateimmutabledatabasessuchasgeolocations,PINcodes,domainknowledge,andsoon.Sincethesedatabasedonotchangethatoften,itisfair

Page 74: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

enoughtoshareandcachetheseacrossmicroservices.

Page 75: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CombiningdataInamonolithicenvironment,combiningdataiseasy;youneedtojustjointwotablestocreatetherequiredview.Inmicroservices,datasetsaredistributedacrossmicroservicesandcombiningthemrequiresmovingthedataacrossmicroservices,whichmayinvolvesignificantnetworkandstorageoverhead.Italsobecomeschallengingtokeepthecombineddatauptodate.Therearemultiplewaystosolvetheproblemofcombiningdataorjoinsinamicroservicesarchitecturebasedonthescopeoftherequest.

Forexample,ifyouwishtobuildanordersummarypageforaparticularuser,youneedtogetonlythatuser'sdatafromtheUserServiceandalltheordersforthatuserfromtheOrdersService.Thesecanbeobtainedindependentlyandjoinedattherequestingserviceleveltogeneratetheordersummary,asshownintheprecedingdiagram.Thesekindsofjoinworkwellfor1:Njoins.

Real-timejoinsworkwellforlimiteddatasets,butitisexpensivetocombinedatainrealtimeforeachrequest.ImaginetensofthousandsofsimilarrequestshittingtheOrderSummaryServiceeverysecond.Insuchscenarios,services

Page 76: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

shouldinsteadkeepdenormalized(https://en.wikipedia.org/wiki/Denormalization)combineddatainacachethatiskeptuptodateusingtheeventsgeneratedbythesourceservices.Theservicecanthenrespondtotherequestsbyjustlookingupthisdenormalizeddatacacheinrealtime.Thisapproachscaleswellattheexpenseofdatabeingnearrealtime.Thedatainthecachemightbeoffbythetimesourceservicegeneratestheeventandtargetservicepicksitupandmakeschangestoitscache.

Forexample,asshownintheprecedingdiagram,anInterestServicemayreceiveuserinterestsviaitsAPIendpoint,butitmayneedtheuserandorderdetailsfromtheUserandOrdersservicesrespectively.Insteadofdirectlylookingupdetailsforeachuserinterest,theInterestServicemaysubscribetotheeventsgeneratedbytheuserandordersserviceandinternallykeepadenormalizedcacheviewofinterestdatathatisreadilyavailablewithalltherequireddetailsofusersandorders.

Page 77: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TransactionsEachmicroservicecanuseadatabaseofitschoice.ThechosendatabasesmayormaynothavetheACIDproperty(https://en.wikipedia.org/wiki/ACID)andsupporttransactions.Thisisoneofthereasonswhydistributedtransactionsarehardtoimplementwithmicroservices.However,businesstransactionsinvolvingchangesacrossmultiplebusinessentitiescannotbeomittedentirely,andthereforemicroservicesimplementdistributedtransactionsbyusingdataworkflows,asshowninthefollowingdiagram:

Microservicespublisheventswhenevertheymakeachangetothedatabase.Theeventscontainthetypeofchangealongwithimmutabledataaboutthebusinessentitiesthatwereaffectedbythischange.Otherservicesthenlistentotheseeventsasynchronouslyandperformthechangesstrictlyintheorderinwhicheventswerepublished.Asingletransactionmaycontainoneormoreeventsthatmayresultincascadingeventsgeneratedbythemicroservicesthatareaffectedbyit.Duetotheasynchronousnatureoftheeventflow,theconsistencyachievedacrossmicroservicesinthiscaseiseventual(https://en.wikipedia.org/wiki/Eventual_consistency).

Page 78: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Ifatransactionfails,theservicethatencountersthefailuregeneratescompensatoryeventstonullifythechangesmadeacrossmicroservicesthathavealreadyprocessedthetransactioneventsinthechain.Thecompensatoryeventsflowbackwardstowardstheoriginofthetransaction,asshownintheprecedingdiagram.Compensatoryeventsareidempotentinnatureandretrieduntiltheysucceed.

ThetransactionpatternformicroservicesisinspiredbySagas(http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf)andwasproposedbyHectorGarcia-MolinaandKennethSalem,aspublishedinanACMpaperin1987.Sagasmaybeimplementedasasetofworkflows,whereateachstepoffailureacompensatingactionistriggeredtobringthesystembacktoitsoriginalstateasitwasbeforetheworkflowwastriggered.

Page 79: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AutomatedcontinuousdeploymentThecorephilosophyofamicroserviceenvironmentmustbebasedontheYoubuildit,yourunit(https://queue.acm.org/detail.cfm?id=1142065)model;thatis,theteamworkingonthemicroservicemustownitendtoend,rightfromdevelopmenttodeployment.Theinfrastructurerequiredfortheteamtointegrate,test,anddeployanychangesmustbecompletelyautomated.Thismakesitpossibletobuildacontinuousintegrationandcontinuousdelivery(CD)pipeline,whichisthebackboneofmicroservices-basedarchitectures.

Page 80: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CI/CDThetermCI/CDcombinesthepracticesofCIandCDtogetherasaseamlessprocess.Inatypicalmicroservices-basedsetup,theteamcontinuouslyworksonenhancingthefeaturesofthemicroserviceandfixingtheissuesthatareencounteredinproduction.

Theentirecyclefromdevelopmenttodeploymenthasthreemajorphases,asshowninthefollowingdiagram:

Inthefirstphase,theteamcommitsthechangestoaversioncontrolrepository.Theversioncontrolrepositoryusedformicroservicesshouldbecommonforalltheservicesandapplications.Consolidatingalltheimplementationsinasingleversioncontrolsystemhelpsinautomationandrunningapplication-wideintegrationandacceptancetests.Someversioncontrolservicesalsosupportafine-grainedcollaborationsystemthatallowsthedeveloperstonotonlysharetheirchangeswithagroupofdevelopers,butalsohaveactivereviewsandafeedbacksystemwithintheteambeforethechangesarecommitted.

VersioncontrolsystemslikeGitareidealforadistributedteamworkingonmultiplemicroservicesduetoitsinherentfeatures.ServicelikeGitHubandBitbucketaresomecloudhosting

Page 81: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

providersforGitthatalsohavethecapabilitytobuildtriggersforCI/CDsystems.

Inthesecondphase,aCI/CDsystemsuchasJenkinsisusedtobuildthechangesandruntheunittestsforthemicroserviceforwhichthechangeiscommitted.Runningthetestsforeachchangerequesthelpsindetectingissuesbeforethechangesareintegratedwiththerestoftheapplication.Italsohelpstodetectanyregressions(https://en.wikipedia.org/wiki/Software_regression)thatmighthavebeenintroducedduetotherecentchanges.Ifthereareanytestfailures,analertissentbacktotheteam,especiallythecommitterwhosubmittedthechangerequest.

Theteamthenfixesthetestsandsendsthechangerequestagaintotheversioncontrolsystemthatin-turntriggersthebuildtoretestthechanges.Thisprocessrepeatsuntilallthetestssucceed.Oncethetestssucceed,theCI/CDsystemmergesthechangeswiththemainlineandpreparesareleaseartifactfortheservice.Artifactsformicroservicesareoftenpackagedascontainersthatarereadilydeployablebyorchestrationtools.Packagingmicroservicesinacontaineralsohelpsinautomatingthedeploymentandscalingoftheserviceson-demand.

Dockerisonepreferredtechnologyforpackagingmicroservices.IthasseamlessintegrationwithmultipleorchestrationtoolssuchasKubernetesandMesos(http://mesos.apache.org/).

Inthethirdphase,theCI/CDsystempublishesthereleasestoacentralrepositoryandinstructstheorchestrationtoolstopickthelatestversionofthemicroservicethatcontainstherecentchanges.Theorchestrationenginethenpullsthelatestreleasefromtherepositoryanddeploysitinproduction.Alltheinstancesinproductionaremonitoredbyautomatedtoolsthatgeneratealertsfortheteamifthereareanyissues.Ifthereareanyissuesencounteredbytheteam,theteamfixestheissuesandsubmitsthechangerequesttotheversioncontrolsystemthattriggersthebuildandtheentireprocessrepeatstopushthechangestoproduction.

Generally,theorchestrationenginedoesarollingupgradeoftheservicewhiledeployingupdates,butsometeamsprefertodotheA/Btestingbyupgradingonlyasubsetofdeployedserviceinstancesandroll-outonlywhenthetestssucceedforthatsubset.SuchdeploymentsareoftenreferredtoasBlueGreenDeployment(h

Page 82: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ttps://martinfowler.com/bliki/BlueGreenDeployment.html).

Suchanautomatedenvironmenthelpstheteamcutshorttheentiredevelopment-to-deploymentcyclefrommonthstodaysanddaystohours.LargecompaniessuchasGoogle,Facebook,Netflix,Amazon,andsoonarenowabletopushmultiplereleasesinadayduetosuchautomatedenvironmentsandrobusttestingprocesses.

Page 83: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ScalingTheArtofScalability(http://theartofscalability.com/)bookusesascalecubemodeltodescribethreeprimaryscalingpatternsforanapplication,asshowninthefollowingdiagram.Thex-axisofthecuberepresentshorizontalscaling;thatis,deployingthesameinstanceoftheapplicationbyjustcloningthemandfront-endingbyaloadbalancertodistributetheloadevenlyamongtheinstances.Thisscalingpatternisquitecommonforhandlingahighnumberofservicerequests.Thez-axisofthecubeaddressesscalingbydatapartitioning.Inthiscase,eachapplicationinstancedealswithonlyasubsetofdata.Thisscalingpatternisparticularlyusefulforapplicationswherethepersistencelayerbecomesabottleneck:

They-axisofthecubeaddressesscalingbysplittingtheapplicationbyfunctionorservice.Thispatternrelatesdirectlytothemicroservicespattern.Thefaceofthecubecreatedusingthexy-axiscombinesthebestpracticesofscalingformicroservices-basedarchitecture.Microservicesareidentifiedbysplittinganapplicationbyboundedcontexts(y-axis)andscaledbycloningeachinstance(x-axis).Formicroservices,cloningisdonebydeployingmultipleinstancesofaservicecontainer.

Page 84: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,welearnedaboutdomain-drivendesignandtheimportanceofidentifyingtherightboundedcontextformicroservices.Welearnedaboutthehexagonalarchitectureformicroservicesandvariousmessagingpatterns.Wealsodiscusseddatamanagementpatternsformicroservicesandhowtosetupserviceregistriestodiscovermicroservices.Weconcludedwiththeimportanceofautomatingtheentiremicroservicedeploymentcycleincludingtesting,deployment,andscaling.Inthenextchapter,wewillintroduceareal-lifeusecaseformicroservicesandlearnhowtodesignanapplicationusingtheconceptslearnedinthischapter.

Page 85: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MicroservicesforHelpingHandsApplication

"Welearnbyexampleandbydirectexperiencebecausetherearereallimitstotheadequacyofverbalinstruction."

-MalcolmGladwell,Blink:ThePowerofThinkingWithoutThinking

Microservicesaregainingpopularityforinternet-scaleapplicationsthattargetconsumers.Inthischapter,youwilllearnhowtoapplyprinciplesofmicroservicesarchitecturetodesignasimilar,internet-scale,fictitiousapplicationcalledHelpingHandsthatconnectshouseholdserviceproviderswithconsumersofservicessuchashomecleaning,appliancerepair,pestcontrol,andsoon.Inthischapter,youwill:

LearnhowtogatherrequirementsandcaptureuserstoriestodesigntheHelpingHandsapplicationLearntheimportanceofmonolithic-firstdesignLearnhowtomovetowardsamicroservices-baseddesignLearnhowtouseevent-drivenarchitecturewithmicroservices

Page 86: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DesignOneofthebestwaystodesignasoftwaresystemistocapturethebusinessdomain,itsusers,andtheirinteractionwiththesystemasauserstory(https://en.wikipedia.org/wiki/User_story).Userstoriesareaninformalwayofcapturingtherequirementsofasoftwaresystem.Inuserstories,thefocusisontheendusersandtheinteractionsthatarepossiblebetweentheusersandthesystem.

Page 87: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsersandentitiesThefirststepinwritinguserstoriesfortheHelpingHandsapplicationistounderstandtheusersandentitiesofthesystem.Primarily,therearetwousersofthesystem—ServiceConsumersandServiceProviders,asshowninthefollowingdiagram.ServiceConsumerssubscribetooneormoreservicesprovidedbytheServiceProviders.Thecoreentityoftheapplicationistheservice.Aserviceisanintangible,temporal,andlimitedassetthatprovidersownandprovidetotheconsumerson-demandataprice.

ServiceProvidersregisteroneormoreserviceswiththesystemthatcanbesubscribedtobytheconsumers.Allservicesareregisteredwithavailabletimeslotsandthedurationforwhichtheycanbeoffered.EachservicedurationandtimeslothasanassociatedpricethattheServiceConsumerhastopaytomakeuseoftheservice.ServiceProvidersarealsoresponsibleformaintainingtheavailabilitystatusoftheserviceswiththesystem.ServiceConsumerscansearchforservicesavailablefromasetofprovidersandcanpickaprovideroftheirchoice.Oncechosen,theServiceConsumercanscheduleaservicefromtheServiceProviderbasedonavailability.

Page 88: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Userstories

ThenextstepistolisttheuserstoriesthatwillbesupportedbytheHelpingHandsapplication.Herearetheuserstoriesfortheapplication:

AsaServiceConsumer,IcancreateanaccountsothatIcansearchforservicesandbookthemAsaServiceConsumer,IcansearchforrequiredservicessothatIcanbookoneformytaskAsaServiceConsumer,IcansubscribetooneormoreservicessothatIcangetmytaskdoneonaregularbasisAsaServiceConsumer,IwouldliketoratetheservicesofferedsothatotherscanbenefitfromthefeedbackandchoosethebestservicesofferedforaparticulartaskAsaServiceConsumer,IwanttogetnotificationssothatIcangetremindedoftheservicescheduleAsaServiceProvider,IcancreateanaccountsothatIcanregisteroneormoreservicesforconsumersAsaServiceProvider,IwanttoregisteroneormoreservicessothatIcangetservicerequestsAsaServiceProvider,IwanttospecifytheservicelocationareasothatIcangetonlyservicerequeststhatareneartomyplaceAsaServiceProvider,IwanttospecifythepriceandavailabilitysothatIcangetonlyservicerequeststhatarefeasibletoserveandtheonesIaminterestedinAsaServiceProvider,IwanttogetnotificationswhenaservicerequestisplacedsothatIcanattendtoit

Apartfromuserstories,therearesomenon-functionalrequirements(https://en.wikipedia.org/wiki/Non-functional_requirement)aswellthatmustbeaddressedbytheHelpingHandsapplication:

Alltheimplementationsmustbetrackedandversionedinarevisioncontrol

Page 89: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

system.TheHelpingHandsapplicationwillusetheexternalhostingserviceGitHubtotrackthecodebase.Allthedependenciesmustbeexplicitlydeclared.TheHelpingHandsapplicationwillbeimplementedusingClojure(https://clojure.org/)withLeiningen(https://leiningen.org/)fordependencymanagement.Allservicesmusthaveauthenticationandauthorizationbuiltin.Configurationsmustbespecifiedexternallyandnothardcodedintheapplication.Alleventsmustbeloggedtounderstandthestateoftheapplicationandmonitoritinproduction.

Non-functionalrequirementsareapartoftwelve-factormethodology(https://12factor.net/),whichcoverstwelvedifferentaspectsthatmustbeaddressedbytheapplication.Part-3andPart-4ofthisbookaddresssomeoftheseimportantaspectsfortheHelpingHandsapplicationindetail.

Page 90: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DomainmodelBasedontheuserstories,ServiceOrderandService(catalog)arethetwocoredomainsoftheHelpingHandsapplication.Apartfromthesetwodomains,useraccounts,provideraccounts,andnotificationsarethegenericdomainsthatformtheHelpingHandsapplication.ServiceConsumer,ServiceProvider,Service,andServiceOrderareentitiesoftheHelpingHandsapplication.Theseareshownalongwiththeirfieldsinthefollowingdiagram:

EachServiceConsumerhasanIDassignedinthesystemandhasName,Address,Mobile,andEmaildefinedassomeoftheattributes.TheGeoLocationofeachconsumerisderivedfromtheaddressinformation.EachServiceProvideralsohasanIDassignedinthesystemwithName,Mobile,

Page 91: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ActiveSince,andOverallRatingassomeoftheattributes.AServiceisregisteredbytheServiceProviderandhasauniqueIDdefinedinthesystem.ItisregisteredagainstaServiceTypeandhasoneormoreServiceAreasdefined.BasedontheServiceAreas,thesystemderivestheGeoLocationBoundarywheretheserviceisofferedtotheconsumers.AServicealsohasanHourlyCostsetbytheServiceProviderandkeepsanOverallRatingbasedonthepreviousorders.

AServiceConsumersubscribestotheServiceandplacesaServiceOrderfortheServiceProvidertofulfill.EachServiceOrderhasanIDdefinedinthesystemandhasanassociatedServiceID,ProviderID,andConsumerID.EachorderentryhasanassociatedTimeSlotbasedonwhichtheCostisdeterminedbythesystem.TheServiceOrderalsohasastatusthatisupdatedbytheServiceProvideroncetheorderiscompleted.TheorderalsokeepsaRatingbetween1and5,asratedbytheconsumerfortheserviceprovided.

TheHelpingHandsapplicationalsoalertstheServiceConsumerandtheServiceProviderwithrespecttotherelatedServiceOrder.AnychangeinStatusisnotifiedtoboththeparticipantsviaSMSsentovertheregisteredmobilenumber.TheServiceConsumercanalsosubscribetoServiceandreceiveallupdateswithrespecttoservicestatus,availability,costchanges,andsoon.

Page 92: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MonolithicarchitectureTheHelpingHandsapplicationcanbedesignedusingathree-layeredarchitectureofpresentation,businesslogic,andpersistence.Basedonthedomainmodel,therecanbefourmaintablesintheHelpingHandsapplicationdatabasecorrespondingtoeachentity.Therewillbeasingledatabasethatwillstoreallthedatainthedesignatedtable.Thedatabasemustbeaccessibletoallthecomponentsofthesystem.Thebusinesslogiclayerwillhavewell-definedcomponentsbasedontheprincipleofSeparationofConcerns(SoC).ComponentswilladdressalluserstoriesfortheHelpingHandsapplication.

Page 93: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Applicationcomponents

Toaddresstheuserstories,therewillbethreemaincomponentsandtwohelpercomponents,asshowninthefollowingdiagram.TheRegistrationComponentwillmanagealltheuseraccountsandrelatedCRUDoperations.TheServiceComponentwillhandleallservice-relatedoperationssuchascreate,update,andlookup.TheOrderComponentwillhelpplacetheorders,searchforthehistoricalorder,andalsoratetheservices.ItwillalsohelptomaintainastatusfortheserviceorderandgeneraterelevantalertsintheformofSMSandemailusinganexternalservice:

Therewillbetwohelpercomponents—GeoLocationandAlerting.ThesecomponentswillhelpconnecttheapplicationtoexternalservicestogetgeolocationtagsandsendalertsintheformofSMSandemailviaprovidedAPIs.Components,theirresponsibilities,andrelateddatabasesaresummarizedinthefollowingtable:

Component Responsibilities Component

Page 94: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

RegistrationComponent

Create/update/deleteaccountforserviceconsumersCreate/update/deleteaccountforserviceproviders

GeoLocationComponenttogetthelongitudeandlatitudebasedontheaddress

ServiceComponent

Create/update/deleteservicesSearchforservicesbykeywords,types,location,andsoon

GeoLocationComponenttogetthelongitudeandlatitudebasedonservicearea

OrderComponent

Create/updateordersSearchforordersbykeywords,types,timespan

AlertingComponenttogeneratealertswithrespecttoorders

GeoLocationComponent

Lookuplongitudeandlatitudebyaddress ExternalService(API)

AlertingComponent

SendemailandSMSalerts ExternalService(API)

Componentssuchasauthenticationanduserssuchasadministratorsandsoonhavebeenintentionallyomittedfromtheexplanationtofocusonlyonthecorecomponentsandfeaturesoftheapplication.

Page 95: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DeploymentTheHelpingHandsapplicationcanbedeployedasasingleartifactinanapplicationserver.Oncetheservicebecomespopularitmustbescaledtohandleincomingservicelookupsandorderrequests.Theeasiestwaytoscaletheapplicationforincomingrequestsistodeploymultipleinstancesoftheapplicationandfrontenditwithaloadbalancer,asshowninthefollowingdiagram.Theloadbalancerthenhelpstodistributetherequestsamongthedeployedinstancesoftheapplication.Sincealltheinstancesusethesamedatabase,allofthemarealwaysin-syncandhaveaccesstoconsistentdata.Consistencyisachievedduetotheinherentcapabilitiesofthedatabaseusedfortheapplication;itsupportsACIDtransactions:

Page 96: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 97: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

LimitationsAlthoughamonolithicarchitecturefortheHelpingHandsapplicationfulfillsthepurpose,ithassomeinherentlimitations.Thereisahighcouplingofthecomponentswiththeconsumersandproviderstable.Anychangeinthesetwotableswillaffectalmostallthecomponentsofthesystemandwillrequireredeploymentoftheentireapplication.

TheHelpingHandsapplicationalsodependsontwoexternalservices.Supposeoneoftheseservicesshutsdownorthereisarequirementtomovetoabetterservice;thecorrespondinggeolocationoralertingcomponentwillbechangedandwillresultintheredeploymentofalltheinstancesoftheapplication,eventhoughtherewasnochangeinthecorefunctionalityandservicesoftheapplication.Thisaddstothedeploymentoverheadforsimplechangesaswell.

Sincetheentireapplicationisdeployedasasingleartifact,scalinganapplicationscalesallthecomponentsoftheapplicationequally.Forexample,toscalewithincomingorderandservicelookuprequests,theRegistrationComponentisunnecessarilyscaledwiththeorderandservicecomponents.Thisalsoincreasestheloadonthedatabasethatishandlingalltheincomingrequestsfromthecomponents.Often,requestsfromonecomponentcanaffectthedatabaseperformanceforothercomponentsaswellandreducetheperformanceoftheentireapplication.

AnotherlimitationofthecurrentmonolithicarchitectureoftheHelpingHandsapplicationisitsdependencyonasingledatabasetechnology.Inpractice,afuzzysearchforservicesusingtagsandlookupusinggeolocationscanbesupportedbetterbydatabasessuchasElasticsearch(https://www.elastic.co/products/elasticsearch)ascomparedtorelationaldatabasessuchasMySQL(https://www.mysql.com/).Relationaldatabasesarebettersuitedfortransactionaloperationssuchascreatingserviceordersandmaintaininguseraccounts.Withthecurrentarchitecture,thereisonlyonedatabasetechnology,andthataffectstheefficiencyoftheapplicationandmakesitlessflexible.

Page 98: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MovingtomicroservicesThelimitationsofamonolithicarchitectureforHelpingHandscanbeaddressedbyseparatingoutthecomponentsalongwiththedatabaseasamicroservice.Theseservicescanthenmakeinformedchoicesaboutthetechnologystackanddatabasethatsuitthemwell.Theseservicescanbedeveloped,changed,anddeployedinisolationaspertheconceptsofamicroservices-basedarchitecture.Toidentifytheboundedcontextforthecomponentsofanexistingmonolithicapplication,itisrecommendedtolookatthedatabaseaccesspatternandrelatedbusinesslogicfirst,isolatethem,andthenlookatthepossibilitiestoisolatethecomponentsfurtherbasedonbusinesscapabilities.

Page 99: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IsolatingservicesbypersistenceIntheexistingmonolithicapplicationofHelpingHands,theconsumersandprovidersdatabasetablesareaccessedbyallthecorecomponentsofthesystem,asshowninthefollowingdiagram.Thesetablesareprimecandidatesforbeingwrappedaroundaserviceandisolatedinaseparatedatabasethatisaccessibleonlytothecorrespondingservicedirectly.AllotherservicesmusttalktotheServiceConsumerserviceandtheServiceProviderserviceforanydetailsinsteadofdirectlyaccessingtheconsumersandprovidersdatabases.

Sincethereisaseparateservicecreatedtohandletherequestsforconsumersandproviders,thereisnoneedtohaveaservicecorrespondingtotheRegistrationComponent.TheServiceConsumerserviceandServiceProviderservicecannowhandlealltherequeststoregister,modify,ordeleteconsumersandproviders,respectively.Similarly,theserviceandorderservicescannowhandlealltherequestsrelatedtoservicesandorders,respectively,byisolatingthecorrespondingdatabases.TheorderservicecannowtalktoServiceConsumer,ServiceProvider,andServicetogettherequireddetailsfortheorder.

TheHelpingHandsapplicationwillbeusingacombinationoftheDatomic(http://www.datomic.com/)andElasticsearch(https://www.elastic.co/products/elasticsearch)databasesforvariousmicroservices.Part-3ofthisbookdiscussesthepersistencelayerindetail,andthelastchapterofPart-2introducesDatomic.

Page 100: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IsolatingservicesbybusinesslogicOncepersistence-basedservicesareisolated,thenextstepistoevaluateexistingcomponentsformicroserviceswithrespecttobusinesslogic.ApartfromdroppingtheRegistrationComponentinfavorofseparateservicesforconsumerandprovider,anewservicecalledlookupcanbecreatedtoconsolidateallthesearchoperationsintooneserviceandallowuserstosearchacrossapplicationentities,asshowninthefollowingdiagram.Sincedatabasesofconsumers,providers,services,andorderscannotbesharedwithlookupservices,itcankeepadenormalized(https://en.wikipedia.org/wiki/Denormalization)viewofthesedatabasescontainingonlythefieldsthatneedtobesearched.

Geolocation-basedquerieswillalsobelimitedtolookupservices,sothereisnoneedtomaintainaseparategeolocationservice;instead,theLookupServiceitselfcanqueryforthegeolocation.

Sincegeolocationsrarelychange,theLookupServicecancachethemandmaintainadatabaseofwell-knownandalreadyqueriedgeolocationsaswellforbetterperformance.

TheAlertingComponentcanbeisolatedasaseparateserviceasitwillberequiredbymultipleservices,includingOrder,ServiceConsumer,andService

Page 101: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Provider,tosendalertstotheusers.AlertsmaybesentviaSMSoremail,andtheAlertingServicecanuseexternalservicestosendthealerts.Sincealertsmustnotbeoverwhelmingforusers,theAlertingServicecangroupbyuserallthealertsthatarerequestedinashortperiodoftimeandsendthemasasinglenotificationmessage.

Donotattempttoaggressivelystartdisintegratingyourcomponentsintomicroservices.Focusonthebusinesscapabilitiesandnotonfeaturesandusecases.Forexample,insteadofcreatingaseparateserviceforsendingemailsandsendingSMS,itisrecommendedtocreateasingleAlertingServicewithbothcapabilities.

Page 102: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MessagingandeventsThenextstepfortheHelpingHandsapplicationistodefinetheinteractionsbetweentheidentifiedmicroservices.Microservicescaneitherinteractbydirectlysendingthemessagestootherserviceendpointssynchronouslyortheycansubscribetotheeventsgeneratedbyothermicroservicesandreceivethemessagesasynchronously.Asynchronousmessagesrelyontheunderlyingmessagebrokeranditsdurability.Messagebrokersnotonlyhelptoscaletheapplicationbyholdingthemessagesyettobeprocessedinthequeue,butalsosupportdurabledeliveries.Evenifaservicefails,itcanberestoredandallowedtostartprocessingpendingmessagesfromthepointwhereitleftoff.CombiningbothsynchronousandasynchronousmessagepatternsfortheHelpingHandsapplicationgivesusaflexibleandperformantarchitecturetoaccomplishagiventask,asshowninthefollowingdiagram:

AlltheservicesoftheHelpingHandsapplicationmustpublishchangelogeventsrelatedtothebusinessentitiesonamessagequeuethatcanbereadby

Page 103: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

anyservicethatsubscribestoit.Thepublishedeventsmustberetainedbythemessagequeueforaconfiguredamountoftime,beyondwhichtheeventsmaybediscarded.Forexample,allthecoreservices—ServiceConsumer,ServiceProvider,Service,andOrder—publisheventsontheirdesignatedmessagequeuesattheallocatedtopic.

TheLookupServicemustsubscribetoalltheeventspublishedbytheConsumer,Provider,Service,andOrderservicestomaintainalocaldenormalizeddatabasetosupportsearchqueries.Itmustaddgeolocationdetailsbyqueryingtheexternalserviceandcachingtheresultslocally.Anychangesdonetotheconsumers,providers,services,andordersdatabasesmustbecommunicatedtotheLookupServiceviaevents,asynchronously.TheLookupServicemayalsopublisheventstoitsdesignatedmessagequeueforotherservicestoconsume.Theseeventsareoftenusefultoanalyzethenumberofsearchqueriesreceived,trendingservices,andsoon.

ServicessuchasAlertingarebestsuitedforsuchasynchronousmessages.TheAlertingServiceshouldnotonlyrelyonthemessagebrokerforvariousdeliverysemantics,suchasat-leastorexactly-oncedeliverybutmustalsoreadbatchesofalerts,combinealertsforthesameuserandsendthemasasingleconsolidatedalert.

ServicessuchasOrderServicemayalsorelyondirectmessagestoretrievedetailsoftheconsumer,provider,andtheservicebeforeregisteringanorderfortheuser.Oncetheorderisregistered,achangelogeventmustbepublishedbytheOrderServicefortheLookupServicetomaketheorderavailabletobesearched.

Eventlogsarealsousefultosetupadeepmonitoringandreportinginfrastructureformicroservices.Part-4ofthisbookdescribesthemonitoringandreportingpatternforthemicroservicesarchitecturethatisbasedoneventlogs.

Page 104: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ExtensibilityAmicroservices-basedarchitecturefortheHelpingHandsapplicationnotonlymakesiteasiertodeployandscalebutalsomakesithighlyextensible.Forexample,bydeployingaseparateLookupServiceforsearchoperations,itisnowpossibletouseadatabasesuchasElasticsearchonlyforsearchoperationsandDatomicforallothermicroservicesthatrequireconsistenttransactions.

Inthefuture,ifthereisabettertechnologyavailable,itwillbeeasiertodeployserviceswithanewertechnology.Newertechnologymayalsocomewithhardwarechallenges.Forexample,adatabasesuchasMapD(https://www.mapd.com/)runsonGPUs(https://en.wikipedia.org/wiki/Graphics_processing_unit).Tousesuchdatabases,microservicesneedtorunonspecializedhardware.Sincemicroservicescanbedeployedinisolationonthesameoranentirelyseparatemachine,itispossibletodeployservicesthatneedGPUsonmachinesthatsupportGPUswithoutaffectingthewaytheyinteractwithotherservices.Thisisoneoftheadvantagesofmicroservices—youarenotboundbythetechnologyorunderlyinghardwareandanychangesdonearelocalizedwithintheboundedcontextoftheservice.

Dataanalyticsisnowanintegralpartofanyweb-basedapplication.Itnotonlyhelpsusunderstandtheusagepatternsoftheapplication,butalsohelpsprovidebetterservicestotheusers.Bygeneratingeventlogsforallthechangesdonetoentities,itisalsopossibletofurtherextendtheHelpingHandsapplicationtoanalyzeusagepatterns.Forexample,onecanlistentoeventsgeneratedbytheOrderServiceandstudyserviceusagepatternsbydemography.Itshouldalsobepossibletoanalyzethepopularityofservicesbylocationandprovidecustomizedofferstousersasnotifications.

Page 105: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

WorkflowsforHelpingHandsDataworkflowsarethebackboneofanymicroservices-basedarchitecture.Theydefinethesequenceofmessagesandeventsthataregeneratedamongtheservicestoaccomplishadesiredtask.Aworkflowmayconsistofbothsynchronousandasynchronousmessages.

Workflowsshowninthissectionareonlyforexplanatorypurposesanddonotconformtotheexactsemanticsofsequencediagrams(https://en.wikipedia.org/wiki/Sequence_diagram).Detailsofauthentication,authorization,validation,anderrorconditionshavebeenomittedintentionally.

Page 106: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServiceproviderworkflowTheserviceproviderworkflowconsistsofServiceProvider,LookupService,andAlertingService.TheServiceProviderComponentexposesendpointsforuserstocreateandupdateserviceprovidersfortheHelpingHandsapplication,asshowninthefollowingdiagram:

Forthecreateoperation,theServiceProviderservicefirstvalidatestheinputrequestfortherequiredparametersandprivileges,thenstartsatransactiontoaddanewserviceproviderwithinthesystem.AnewIDfortheserviceproviderisautomaticallygeneratedbytheServiceProvider.Oncethetransactionissuccessful,itemitsacreateeventonitseventlogqueuefortheLookupServiceandothersubscriberstopickup,andpublishesacreatealertonthemessagequeueoftheAlertingServiceforittopickandsendanalerttotheinterestedusers.

Toupdatetheserviceproviders,theusersendstheupdaterequesttotheServiceProvideranditinitiatesanewtransactiontoupdateitslocaldatabase.Oncethetransactionissuccessful,itemitsanupdateeventonitseventlogqueueforthe

Page 107: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

LookupServicetopick-up.Updateoperationsarenotsentasalertsornotificationstousers.Ifthisisarequirement,itcanbeenabledbypublishingamessagefortheAlertingServiceonitsrequestqueue.

Page 108: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServiceworkflowTheserviceworkflowdescribesthemessageinteractionamongtheServiceComponent,ServiceProvider,LookupService,andAlertingService,asshowninthefollowingdiagram.TheserviceexposesendpointsforuserstocreateandupdateservicesfortheHelpingHandsapplication.Eachservicemusthaveaserviceprovideralreadydefinedfortheapplication:

Tocreateanewservice,theusercallstheAPIendpointforServicewiththerequiredparameters.TheproviderIDmustbespecifiedtocreateanewservice.OncetheServiceComponentreceivestherequest,itfirstsendsadirectmessagetotheServiceProvidertovalidatethespecifiedproviderIDandmakesurethattheproviderisalreadyregistered.Iftheproviderisalreadyregistered,theServiceProviderreturnstheproviderdetailssynchronouslytotheService.

Oncetheproviderisvalidated,theServiceComponentstartsatransactiontoaddanewserviceintoitslocaldatabase.AnewIDfortheserviceisautomaticallygeneratedbytheServiceComponent.Oncethetransactionissuccessful,theServiceComponentemitsacreateeventonitseventlogqueuefortheLookupServiceandothersubscriberstopickup.TheLookupService

Page 109: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

pullsthecreateevent,looksupthegeolocationcorrespondingtotheserviceareas,andupdatestheservicedetailsinitslocaldenormalizeddatabase.TheServiceComponentalsopublishesanewservicealertonthemessagequeueoftheAlertingServicetosendanalerttotheinterestedusers.

Toupdatetheservicedetails,theusersendsanupdaterequesttotheServiceComponentwiththeserviceID.Sincetheupdateoperationisperformedonlyforexistingservices,theserviceproviderisnotvalidatedinthiscase.TheServiceComponentinitiatesatransactiontoupdatetheservicedetailsandupdatesthespecifiedfields.Oncethetransactionissuccessful,itemitsanupdateeventontheeventlogqueuefortheLookupServicetopickup.TheLookupServicethenupdatesthelocaldatabasewiththechangesandalsoupdatesthegeolocationsfortheserviceareasiftheyarepartofthechangelog.Alertsarenotsentforserviceupdates.

Page 110: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServiceconsumerworkflowTheserviceconsumerworkflowdescribesthemessageinteractionamongtheServiceConsumer,Lookup,andAlertingService.TheServiceConsumercomponentexposesendpointsforuserstocreateandupdateserviceconsumersfortheHelpingHandsapplication,asshowninthefollowingdiagram.

Tocreateanewserviceconsumer,theusersendsarequestwithvalidparameterstotheServiceConsumerComponent.Thecomponentthenvalidatestherequestandiftherequestisvalid,itstartsatransactiontoaddanewconsumertotheapplication.Foreachnewconsumer,anIDisautomaticallygeneratedbyServiceConsumerComponent.

Oncethetransactionissuccessful,theServiceConsumerComponentemitsacreateeventonitseventlogqueuefortheLookupServiceandothersubscriberstopickup.Afterreceivingthecreateevent,theLookupServicelooksupthegeolocationcorrespondingtotheaddressoftheconsumerandthenstorestheconsumerdetailsalongwiththegeolocationinitslocaldenormalizeddatabase.TheServiceConsumercomponentalsopublishesanewconsumeralertonthemessagequeueoftheAlertingServicetosendanalerttointerestedusers:

Page 111: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Toupdatetheconsumerdetails,theusersendstheupdaterequesttotheServiceConsumerComponentalongwiththeconsumerID.TheServiceConsumerComponentinitiatesatransactiontoupdatetheconsumerdetailsandupdatesthespecifiedfields.Oncethetransactionissuccessful,itemitsanupdateeventontheeventlogqueuefortheLookupServicetopickup.TheLookupServicethenupdatesthelocaldatabasewiththechangesandalsoupdatesthegeolocationfortheconsumeraddressifitisapartofthechangelog.Alertsarenotsentforconsumerupdates.

Page 112: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

OrderworkflowTheorderworkflowinvolvesmostoftheservicesintheHelpingHandsapplication.TheOrderServicereceivestherequestfromtheconsumertocreateaneworderforthechosenservice.TheconsumersendsthecreaterequesttotheOrderServicewiththeproviderandservicedetailsalongwiththetimeslot.TheOrderServicethenvalidatestheorderdetailsbysendingadirectsynchronousrequesttotheServiceConsumer,ServiceProvider,andService.Ifthedetailsarevalid,thatis,theserviceisalreadyregisteredbythespecifiedproviderandtheconsumerisavaliduserinthesystem,thentheOrderServicesendsadirectsynchronousrequesttotheLookupServicetomakesurethattheserviceisavailableinthevicinityoftheconsumerlocationbasedonthegeolocationoftheconsumeraddressandtheservicearea.

Iftheserviceisfeasiblefortheconsumeraddress,theOrderServicestartsatransactionandcreatesaneworderinthesystem,asshowninthefollowingdiagram:

Page 113: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Oncethetransactionissuccessful,theOrderServiceemitsacreateeventonitseventlogqueuefortheLookupService;itreceivestheevents,updatestheorderlistwithinitslocaldatabase,andmakesitavailableforuserstosearch.TheOrderServicealsopublishesaneworderalertonthemessagequeueoftheAlertingServicetosendanalerttoboththeconsumerandtheprovideroftheservice.

Toupdatetheordertimeslot,status,orrating,theusersendsanupdaterequesttotheOrderService.SinceanupdatemessagerequiresanexistingorderID,theOrderServicedoesnotvalidatetheproviderandconsumerfortherequest,itjustmakessurethatitisanexistingorder.Inadditiontovalidatinganexistingorder,theOrderServicealsosendsadirectsynchronousrequesttotheLookupServicetomakesurethattherequestedtimeslotandservicearestillavailablefortheconsumer.Iftheyareavailable,thentheOrderServicestartsa

Page 114: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

transactionandupdatesitslocaldatabase.Oncethetransactionissuccessful,itemitstheupdateeventonitseventlogqueuefortheLookupServiceandalsosendsanalerttotheAlertingServicebypublishinganupdateorderalert.Anychangemadetotheorderissentasanalerttoboththeconsumerandprovideroftheserviceorder.

Inpractice,therewillbeseparateservicesandworkflowsforauthenticationandauthorization.Thoseworkflowshavebeenintentionallyomittedfromthischaptertofocusonlyonthecoreservicesandworkflows.Part-4ofthisbookdescribesauthenticationandauthorizationservicespatternsindetail.

Page 115: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,wedesignedanapplicationcalledHelpingHands,usingthebestsoftwareengineeringpractices.Westartedwithamonolithicarchitectureandarguedwhyamicroservices-basedarchitectureiswellsuitedfortheHelpingHandsapplication.Inthenextpartofthisbook,wewillfirsttakealookatthebasicdevelopmenttoolsandlibrariesthatwewillbeusingtobuildourHelpingHandsapplication.

Page 116: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DevelopmentEnvironment

"Themechanicthatwouldperfecthisworkmustfirstsharpenhistools."

-Confucius

Thedevelopmentenvironmentconsistsoftoolsandlibrariesthatareusefultoimplement,debug,andmakechangestosoftwaresystems.Theefficiencyofadevelopmentteamishighlydependentonthedevelopmentenvironmentandtechnologystackathand.Inthischapter,youwilllearnhowtosetupadevelopmentenvironmentformicroservicesusingtheClojureecosystem.Thischapterwillhelpyouto:

LearnthehistoryofClojureandfunctionalprogrammingLearntheimportanceofREPLLearnhowtobuildyourapplicationusingClojurebuildtoolsLearnaboutwellknownintegrateddevelopmentenvironments(IDEs)

Page 117: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ClojureandREPLClojure(https://clojure.org/)isadialectoftheLisp(https://en.wikipedia.org/wiki/Lisp_(programming_language)programminglanguageandprimarilyrunsonaJavavirtualmachine(JVM).TheothertargetimplementationsincludeClojureCLR(https://github.com/clojure/clojure-clr),whichrunsonCommonLanguageRuntime(CLR),andClojureScript,whichcompilestoJavaScript.AlthoughClojureusesaJVMasitsunderlyingruntimeengine,itemphasizesafunctionalprogramminglanguagewithimmutabilityatitscore.AlldatastructuresofClojureareimmutable.SinceClojureisadialectofLisp,italsotreatscodeasdataandisknowntobehomoiconic.ItssyntaxisbuiltonS-expressions(https://en.wikipedia.org/wiki/S-expression)thatarefirstparsedasadatastructureandthentranslatedintoconstructsoftheJavaprogramminglanguagebeforebeingcompiledintoJavabytecode.Clojurealsosupportsmetaprogrammingwithmacro(https://en.wikipedia.org/wiki/Macro_(computer_science)).

ClojureintegrateswellwithexistingJavaapplicationsduetoitsprimarysupportforunderlyingJVMsanditsentireecosystem.AllexistinglibrariesthatrunonaJVMintegrateseamlesslywithClojure,includingMaven,Java'sbuildsystem.

Page 118: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

HistoryofClojureClojureisafunctionalprogramminglanguagethattreatsfunctionsasfirst-classobjects;thatis,itsupportspassingfunctionsasargumentstootherfunctionsandalsoreturningthemasvaluesfromotherfunctions.Italsosupportsanonymousfunctions,assigningfunctionstovariablesandstoringthemindatastructures.

ThetimelineintheprecedingdiagramshowstheevolutionoffunctionalprogrammingthatledtothedevelopmentofClojure.TheconceptoffunctionalprogrammingoriginatedfromaformalsystemcalledLambdacalculusthatwasintroducedbyAlonzoChurch(https://en.wikipedia.org/wiki/Alonzo_Church)in1930.Churchproposedauniversalmodelofcomputationthatwasbasedonfunctionabstractionanditsapplicationusingvariablebindingandsubstitution.Church'smodellaidthefoundationoffunctionalprogramminglanguagesthatareknowntoday,includingClojure.

Almostthreedecadeslaterin1958,JohnMcCarthy(https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist))introducedtheLispprogramminglanguage,whichwashighlyinfluencedbythenotationsofLambdacalculus.Itwasdistinctiveduetoitsfullyparenthesizedprefixnotation.Itssourcecodewasmadeoflists,hencethenameLIStProcessor.LISPintroducedmanyotherconceptsincludingtreedatastructure,dynamictyping,high-orderfunctions,andread-eval-print-loop(REPL).TwowidelypopularLispdialectsareScheme(https://en.wikipedia.org/wiki/Scheme_(programming_language))andCommonLisp(https://en.wikipedia.org/wiki/Common_Lisp),bothinventedbyGuySteele(https://en.wikipedia.org/wiki/Guy_L._Steele_Jr.).Schemewasintroducedin1970followedbyCommonLispin1984.ClojureisalsooneofthedialectsofLispthatwasintroducedin2007byRichHickey.

InadditiontobeingadialectofLISP,ClojurealsochoseJVMasitsruntimeduetoitsinherentadvantagesandmaturedecosystem.Javawasfirstintroducedin

Page 119: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

1994-1995byJamesGosling(https://en.wikipedia.org/wiki/James_Gosling).Javaisanobject-orientedlanguagewithafocusonconcurrency.Javabecamewidelypopularinenterprisessoonafteritsreleaseduetoitsefficientdependencymanagementandabilitytowriteonce,andrunanywhere(https://en.wikipedia.org/wiki/Write_once,_run_anywhere).Javaalloweddeveloperstowritetheircodeonce,compileitintobytecode,andthenrunitonallplatformsthatsupportJVMwithoutrecompilingthecode.

ClojureinheritedallthecapabilitiesofJVMwiththefocusonimmutabilityandLISP-likeparenthesizedprefixnotation.Itisbuiltontheconceptsofimmutabilityandpersistentdatastructures,whichmakesithighlyconcurrent.ItusestheEpochalTimeModel,whichorganizesprogramsbyidentities,whereeachidentityisdefinedasaseriesofimmutablestatesovertime.Duetoitshighlyconcurrentnature,Clojureisbestsuitedtobuildingmassivelyparallelsoftwaresystemsthatarerobustandutilizemodernmultiprocessorhardwaretothefullest.

TheEpochaltimemodelwasintroducedbyRichHickeyinhiskeynotetalkonArewethereyet?(https://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey)attheJVMLanguagesSummitin2009.HerevisiteditinhiskeynotetalkonEffectivePrograms-10YearsofClojure(https://www.youtube.com/watch?v=2V1FtfBDsLU)atClojure/Conjin2017.

Page 120: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

REPLRead-eval-print-loopisaninteractiveprogrammingenvironmentthattakesexpressionsasinputsfromtheuser,parsesthemintoadatastructurefortheprogramminglanguage,evaluatesthem,andprintstheresultbacktotheuser.Oncedone,itreturnstothereadstateandwaitsforuserinput,thusformingaloop,asshowninthefollowingdiagram.REPLwaspioneeredbyLispandisnowavailableforotherprogramminglanguagesaswell:

REPLisalsothedefaultinteractiveprogrammingenvironmentforClojure.ItcompilesanexpressionintoJavabytecode,evaluatesit,andreturnstheresultoftheexpression.TouseClojureREPL,downloadaClojurerelease(1.8.0)fromClojureDownloads(https://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.zip)andextractit.SinceClojurerunsonJVM,youneedtofirstmakesurethatyouhaveJava1.6+orhigherinstalledonyourmachine.TovalidateyourJavaversion,usethejavacommandwiththe-versionoption.ThisbookusesJava1.8forallexamples:

%java-version

javaversion"1.8.0_121"

Java(TM)SERuntimeEnvironment(build1.8.0_121-b13)

JavaHotSpot(TM)64-BitServerVM(build25.121-b13,mixedmode)

TouseClojureREPL,firstunziptheClojure1.8release:

%unzipclojure-1.8.0.zip

Archive:clojure-1.8.0.zip

creating:clojure-1.8.0/

Page 121: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

...

inflating:clojure-1.8.0/clojure-1.8.0-slim.jar

inflating:clojure-1.8.0/clojure-1.8.0.jar

inflating:clojure-1.8.0/pom.xml

inflating:clojure-1.8.0/build.xml

inflating:clojure-1.8.0/readme.txt

inflating:clojure-1.8.0/changes.md

inflating:clojure-1.8.0/clojure.iml

inflating:clojure-1.8.0/epl-v10.html

Intheunzippedclojure-1.8.0folder,theclojure-1.8.0.jarJAR(https://en.wikipedia.org/wiki/JAR_(file_format))isanexecutableJARthatcontainstheClojurecompiler.ItisalsousedtostartClojureREPLusingthejavacommand:

%cdclojure-1.8.0

%java-jarclojure-1.8.0.jar

Clojure1.8.0

user=>(+123)

6

user=>

TheprecedingmethodofstartingtheREPLwiththeClojureJARfileworkswelluntilthelaststablereleaseofClojure1.8atthetimeofwritingthisbook.WithClojure1.9andonwards,itisrecommendedtousetheClojurecljtool(https://clojure.org/guides/deps_and_cli)orabuildtoolsuchasLeiningen(https://leiningen.org/)tosetupaprojectandstartREPLfortheprojectwiththerequiredlibraries.

repl.it(https://repl.it)isanonlineutilitythatallowyoutoaccessClojureREPLonlineoverthewebbrowser.

Page 122: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ClojurebuildtoolsBuildtoolsareofprimeimportanceforanyprogramminglanguage.Theynotonlyhelptogenerateadeployableartifactfortheapplication,butalsomanagethedependenciesoftheapplicationthroughoutitsdevelopmentlifecycle.LeiningenandBoot(http://boot-clj.com/)aretwowidelyusedbuildtoolsforClojure.SinceClojureisahostedlanguageforJVM,ClojurebuildtoolsprimarilygenerateJARsasdeployableartifactsforallClojureprojects.

Page 123: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

LeiningenLeiningenisabuildandprojectmanagementtoolthatiswritteninClojureandiswidelyusedacrossClojureprojects.ItdescribesaClojureprojectusinggenericClojuredatastructures.ItintegrateswellwiththeMavenrepositoryfromtheJavaworldandtheClojars(https://clojars.org/)repositoryofClojurelibraries,fordependencymanagementandreleases.Leiningenhasbuilt-insupportforpluginstoextenditsfunctionality.ItalsoprovidesanoptiontodesignapplicationtemplatesthatcanhelpcreateaClojureapplicationcodelayoutwithasingleleincommand.Toseparateprojectconfigurationfromdevelopment,test,andproduction,italsohastheconceptofprofiles,whichcanbeusedtochangetheprojectconfigurationatbuildtimebyusingitwiththeleincommand.

Tosetuplein,downloadthelatestLeinscript(https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein)fromtheLeiningenGitHubrepository(https://github.com/technomancy/leiningen),makeitexecutableusingthechmodcommand,andmakesurethatthescriptisavailableonyourpath,forexample,bycopyingitto~/binor/usr/local/bininLinux.Oncetheleinscriptisavailableonyourpath,runittodownloadthelatestself-installpackageandsetuptheenvironment.LeindownloadstheexecutableJARforLeiningenfirsttime:

#Downloadthelatestscript(latestversion)

%wgethttps://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein

#Makeitexecutable

%chmoda+xlein

#Runleintosetup

%lein

DownloadingLeiningen...

LeiningenisatoolforworkingwithClojureprojects.

...

#Validateleinversion

%leinversion

Leiningen2.8.0onJava1.8.0_121JavaHotSpot(TM)64-BitServerVM

#Downgradetoversion2.7.1,i.e.theversionusedinthebook

%leindowngrade2.7.1

#Validateleinversion

%leinversion

Leiningen2.7.1onJava1.8.0_121JavaHotSpot(TM)64-BitServerVM

Page 124: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Notethattheleinscriptalwaysdownloadsthemostrecentlyreleasedversion.Onceleinissetup,youcanstartREPLdirectlyfromtheleincommand:

%leinrepl

nREPLserverstartedonport43952onhost127.0.0.1-nrepl://127.0.0.1:43952

REPL-y0.3.7,nREPL0.2.12

Clojure1.8.0

JavaHotSpot(TM)64-BitServerVM1.8.0_121-b13

Docs:(docfunction-name-here)

(find-doc"part-of-name-here")

Source:(sourcefunction-name-here)

Javadoc:(javadocjava-object-or-class-here)

Exit:Control+Dor(exit)or(quit)

Results:Storedinvars*1,*2,*3,anexceptionin*e

user=>(+123)

6

user=>

Page 125: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

BootBootisanalternativebuildsystemforClojurethatisgainingpopularity.BoottreatsbuildscriptsasClojureprogramsthatcanbeextendedusingpods(https://github.com/boot-clj/boot/wiki/Pods).PodsalsohelptoisolateclasspathsforbetterdependencymanagementandusesmultipleClojureruntimes.InsteadoftheprofilesandpluginsofLeiningen,Bootusesthecommonconceptoftasks(https://github.com/boot-clj/boot/wiki/Tasks).TasksareusedacrosstheBootprogramtomodifythebuildenvironmentasperthecontext.

TosetupBoot,downloadthelatestboot.shscript(https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh)fromtheBootGitHubrepository(https://github.com/boot-clj/boot),makeitexecutableusingthechmodcommand,andmakesurethatthescriptisavailableonyourpath,forexample,bycopyingitto~/binor/usr/local/bininLinux.OncetheBootscriptisavailableonyourpath,runittosetuptheenvironment,asshownhere:

#Downloadbootscript

%curl-fsSLoboothttps://github.com/boot-clj/boot-

bin/releases/download/latest/boot.sh

#Makeitexecutable

%chmod755boot

#Runboottosetup

%boot

Downloadinghttps://github.com/boot-clj/boot/releases/download/2.7.2/boot.jar...

Runningforthefirsttime,BOOT_VERSIONnotset:updatingtolatest.

Retrievingmaven-metadata.xmlfromhttps://repo.clojars.org/(3k)

Retrievingboot-2.7.2.pomfromhttps://repo.clojars.org/(2k)

Retrievingboot-2.7.2.jarfromhttps://repo.clojars.org/(3k)

#http://boot-clj.com

#SatOct2119:43:24IST2017

BOOT_CLOJURE_NAME=org.clojure/clojure

BOOT_VERSION=2.7.2

BOOT_CLOJURE_VERSION=1.8.0

OnceBootissetup,youcanstartREPLdirectlyfromthebootcommand:

%bootrepl

nREPLserverstartedonport33140onhost127.0.0.1-nrepl://127.0.0.1:33140

REPL-y0.3.7,nREPL0.2.12

Clojure1.8.0

JavaHotSpot(TM)64-BitServerVM1.8.0_121-b13

Exit:Control+Dor(exit)or(quit)

Commands:(user/help)

Docs:(docfunction-name-here)

Page 126: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(find-doc"part-of-name-here")

FindbyName:(find-name"part-of-name-here")

Source:(sourcefunction-name-here)

Javadoc:(javadocjava-object-or-class-here)

Examplesfromclojuredocs.org:[clojuredocsorcdoc]

(user/clojuredocsname-here)

(user/clojuredocs"ns-here""name-here")

boot.user=>(+123)

6

boot.user=>

ThisbookwillfocusonLeiningenasthebuildtoolfortheClojureprojects,butitisimportanttoknowthatLeiningenisnottheonlybuildtoolavailableforClojure.BootisalsoanoptionthatcanbeusedinsteadofLeiningen.

Page 127: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ClojureprojectAClojureprojectisadirectorythatcontainssourcefiles,testfiles,resources,documentation,andprojectmetadata.SourcefilesareprimarilyfromClojure,butaprojectmaycontainJavasourcefilesaswell.LeiningenhasadefaultprojecttemplatethatcanbeusedtoquicklycreateaClojureprojectstructureusingtheleinnew<project-name>command:

#Createanewproject'playground'

%leinnewplayground

Generatingaprojectcalledplaygroundbasedonthe'default'template.

Thedefaulttemplateisintendedforlibraryprojects,notapplications.

Toseeothertemplates(app,plugin,etc),try`leinhelpnew`.

#Showthe'playground'projectdirectorystructure

%treeplayground

playground

├──CHANGELOG.md

├──doc

│└──intro.md

├──LICENSE

├──project.clj

├──README.md

├──resources

├──src

│└──playground

│└──core.clj

└──test

└──playground

└──core_test.clj

6directories,7files

EachClojureprojectcontainsaprojectmetadatafile,project.clj,whichdefinesalltheprojectdependencies,profiles,andpluginsthatarerequiredfortheproject.Forexample,theproject.cljfileoftheplaygroundprojectliststheprojectmetadataanddependenciesbasedonthedefaultprojecttemplateofLeiningen,asshownhere:

(defprojectplayground"0.1.0-SNAPSHOT"

:description"FIXME:writedescription"

:url"http://example.com/FIXME"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]])

ThedefprojectisaClojuremacrothatisdefinedbyLeiningenandactsasacontainerforallprojectmetadatadirectives.Thedefaulttemplateomitsthe

Page 128: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

defaultconfigurationdirectives.

Page 129: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ConfiguringaprojectAgoodprojectconfigurationmustdefineseparateprofilesfordevelopment,test,andproduction.Itshouldalsohavedirectivestotesttheimplementation,checkcodequality,andgeneratetestreportsanddocumentationfortheapplication.Eachprojectconfigurationshouldalsohavetheentrypointintotheapplicationdefinedusingthe:maindirective,whichpointstothenamespacethatcontainsthemainfunction,asshownhere:

(defprojectplayground"0.1.0-SNAPSHOT"

:description"PlaygroundProject"

:url"http://example.com/playground"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]]

:mainplayground.core

:profiles{:provided{:dependencies[[org.clojure/tools.reader"0.10.0"]

[org.clojure/tools.nrepl"0.2.12"]]}

:uberjar{:aot:all:omit-sourcetrue}

:doc{:dependencies[[codox-theme-rdash"0.1.1"]]

:codox{:metadata{:doc/format:markdown}

:themes[:rdash]}}

:dev{:resource-paths["resources""conf"]

:jvm-opts["-Dconf=conf/conf.edn"]}

:debug{:jvm-opts

["-server"

(str"-agentlib:jdwp=transport=dt_socket,"

"server=y,address=8000,suspend=n")]}})

Projectconfigurationshouldexplicitlylistsourceandtestfilelocationsusingthe:source-pathsand:test-pathsdirective.IfbothClojureandJavasourcecodefilesarepresentintheproject,thenitisrecommendedtoorganizethesourcefileundersrc/cljforClojureandsrc/jvmforJava.Similarly,testfilescanbekeptundertest/cljandtest/jvmforClojureandJava,respectively.Areferenceproject.cljfilewiththerequiredprojectconfigurationisshowninthefollowingcodesnippet:

(defprojectplayground"0.1.0-SNAPSHOT"

...

:mainplayground.core

:source-paths["src/clj"]

:java-source-paths["src/jvm"]

:test-paths["test/clj""test/jvm"]

:resource-paths["resources""conf"]

...

)

Page 130: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Projectresourcefiles,suchasstartup,shutdownscripts,andmore,canbekeptintheresourcesdirectoryandtheprojectconfigurationfilescanbekeptintheconfdirectory.Boththedirectoriesmustbespecifiedunderthe:resource-pathsdirective,asshownintheprecedingsnippet.Togeneratedocumentationandtestreports,andcheckcodequality,thereareanumberofthird-partypluginsavailablethatcanbeaddedtotheprojectconfiguration.Forexample,Codox(https://github.com/weavejester/codox)canbeusedtogenerateAPIdocumentation,Cloverage(https://github.com/cloverage/cloverage)canbeusedtotestcodecoverage,andtest2junit(https://github.com/ruedigergad/test2junit)canbeusedtogeneratetestreports.Addthesepluginsintheconfigurationfile,asshownhere:

(defprojectplayground"0.1.0-SNAPSHOT"

...

:resource-paths["resources""conf"]

:plugins[[:lein-codox"0.10.3"]

;;CodeCoverage

[:lein-cloverage"1.0.9"]

;;Unittestdocs

[test2junit"1.2.2"]]

:codox{:namespaces:all}

:test2junit-output-dir"target/test-reports"

:profiles{:provided{:dependencies[[org.clojure/tools.reader"0.10.0"]

[org.clojure/tools.nrepl"0.2.12"]]}

:uberjar{:aot:all:omit-sourcetrue}

:doc{:dependencies[[codox-theme-rdash"0.1.1"]]

:codox{:metadata{:doc/format:markdown}

:themes[:rdash]}}

:dev{:resource-paths["resources""conf"]

:jvm-opts["-Dconf=conf/conf.edn"]}

:debug{:jvm-opts

["-server"

(str"-agentlib:jdwp=transport=dt_socket,"

"server=y,address=8000,suspend=n")]}})

SomeofthesepluginsmayalsorequireextraLeiningendirectivestobedefinedforthem.Dependenciesthatarecommonacrossprofilesmustbelistedunderthe:dependenciesdirectiveofthedefprojectmacro,andtherestofthedependenciesmustbelistedundertherespectiveprofiles,asshownintheprecedingconfiguration.

TheLeiningenGitHubrepositoryhasasample.project.cljfile(https://github.com/technomancy/leiningen/blob/master/sample.project.clj)thatlistsallsupportedprojectdirectivesforaClojureprojectthatismanagedbyLeiningen.ThisfileactsasdetaileddocumentationforallthefeaturesofLeiningen.

Page 131: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Runningaproject

Ifthe:maindirectiveisdefinedintheproject.cljfileoftheproject,thentheprojectcanberunbydirectlycallingtheleinruncommand.leinrunexpectsthenamespacespecifiedunder:maindirectivetocontaintheClojuremainfunction.Forexample,theplayground.corenamespacemusthaveamainfunctiondefinedforleinruntowork,asshowninthefollowingimplementation.Sincethe:source-pathsparameterintheupdatedconfigurationofproject.cljpointstosrc/cljinsteadofthedefaultsrc/,thecore.cljsourcefile,asshowninthefollowingcode,mustresidewithinthesrc/clj/playground/directoryasperthenamespaceandconfiguredsourcepaths:

(nsplayground.core

(:gen-class))

(defnfoo

"Idon'tdoawholelot."

[x]

(printlnx"|Hello,World!"))

(defn-main

[&args]

(foo(or(firstargs)"Noname")))

Ifthemainfunctionisdefinedintheplayground.corenamespace,thenitcanbeexecutedusingleinrun:

%leinrun

Noname|Hello,World!

%leinrunClojure

Clojure|Hello,World!

Page 132: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Runningtests

Torunallthetestsdefinedunderthe:test-pathsdirective,runtheleintestcommand.SincethedefaulttemplateofLeiningenhasonlyonetestdefinedanditalwaysfails,therewillbeatestfailurereported,asshowninthefollowingcodesnippet.Sincethe:test-pathsparameterintheupdatedconfigurationofproject.cljpointstotest/cljinsteadofthedefaulttest/,thegeneratedcore_test.cljtestsourcefilemustresidewithinthetest/clj/playground/directoryasperthenamespaceandconfiguredtestpathsforteststorun,asshownhere:

%leintest

leintestplayground.core-test

leintest:onlyplayground.core-test/a-test

FAILin(a-test)(core_test.clj:7)

FIXME,Ifail.

expected:(=01)

actual:(not(=01))

Ran1testscontaining1assertions.

1failures,0errors.

Testsfailed.

Page 133: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Generatingreports

AgoodprojectdeliverablemustincludetestandcoveragereportswithassociatedAPIdocumentation.Basedontheconfiguredpluginsunderthepluginsdirectiveofproject.clj,leincanbeusedtogeneratevariousreportsaspertheplugindocumentation.Theexecutionofeachofthepluginsusingtheleincommandandtheircorrespondingresultsareshownhere:

%leintest2junit

Usingtest2junitversion:1.2.2

RunningTests...

Writingoutputto:target/test-reports

Creatingdefaultbuild.xmlfile.

Testing:playground.core-test

Ran1testscontaining1assertions.

>1failures,0errors.

Testsfailed.

Testsfailed.

%leinwith-profiles+testcloverage

Loadingnamespaces:(playground.core)

Testnamespaces:(playground.core-test)

Loadedplayground.core.

Instrumentednamespaces.

Testingplayground.core-test

FAILin(a-test)(core_test.clj:7)

FIXME,Ifail.

expected:(=01)

actual:(not(=01))

Ran1testscontaining1assertions.

1failures,0errors.

Rantests.

Producedoutputinplayground/target/coverage.

HTML:target/coverage/index.html

|-----------------+---------+---------|

|Namespace|%Forms|%Lines|

|-----------------+---------+---------|

|playground.core|17.65|60.00|

|-----------------+---------+---------|

|ALLFILES|17.65|60.00|

|-----------------+---------+---------|

Errorencounteredperformingtask'cloverage'withprofile(s):

'base,system,user,provided,dev,test'

Suppressedexit

Page 134: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

%leinwith-profiles+doccodox

GeneratedHTMLdocsinplayground/target/doc

Asaresultoftheexecutionofthecommandsshownintheprecedingsnippet,theoutputoftest2junitisgeneratedunderthetarget/test-reports/xml/playground.core-test.xmlfile,whichisastandardJUnit(https://en.wikipedia.org/wiki/JUnit)testreport.Coveragereportsaregeneratedundertarget/coverage/,whichcontainsanindex.htmlfilethatcanbeviewedusingawebbrowsertoseeadetailedreportandreviewtheinstrumentedcodepathsthatwerecoveredwiththetestcases.Thedocumentationfortheprojectisgeneratedunderthetarget/docfile,whichcontainstheindex.htmlfilethatcanbeviewedinawebbrowsertoreviewthegenerateddocs.

Page 135: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Generatingartifacts

ClojureartifactsaregeneratedasrunnableJARs.TogeneratearunnableJAR,runtheleinuberjarcommand,asshowninthefollowingsnippet.Theartifactcanbeconfiguredusingtheuberjarprofileinproject.clj:

%leinuberjar

Compilingplayground.core

Createdtarget/playground-0.1.0-SNAPSHOT.jar

Createdtarget/playground-0.1.0-SNAPSHOT-standalone.jar

%java-jartarget/playground-0.1.0-SNAPSHOT-standalone.jar

Noname|Hello,World!

%java-jartarget/playground-0.1.0-SNAPSHOT-standalone.jarClojure

Clojure|Hello,World!

Page 136: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ClojureIDE

Anintegrateddevelopmentenvironment(IDE)isasoftwareapplicationthatprovidesutilitiesforprogrammerstodevelop,build,anddebugsoftwareapplications.Itconsistsofacodeeditor,buildautomationtool,andadebugger.ClojurehasagrowingnumberofIDEsavailable,outofwhichEmacs(https://en.wikipedia.org/wiki/Emacs)andVim(https://en.wikipedia.org/wiki/Vim_(text_editor))standout.AlthoughthisbookdoesnotcoverIDEs,whereveroneisreferredto,usesEmacsastheIDE.BothEmacsandVimaretexteditorsthatneedadditionalpluginstosupportClojure.EmacsneedsCIDER(https://github.com/clojure-emacs/cider),whereasVimgetsREPLsupportusingFireplace(https://github.com/tpope/vim-fireplace).

SomeotherClojureIDEsthatarewidelyusedare:

Eclipse(https://www.eclipse.org/home/index.php)withCounterclockwise(http://doc.ccw-ide.org/)LightTable(http://lighttable.com/)SublimeText(https://www.sublimetext.com/)withSublimeREPL(https://github.com/wuub/SublimeREPL)Atom(https://atom.io/)withnrepl(https://atom.io/packages/nrepl)Cursive(https://cursive-ide.com/)

Page 137: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Summary

Inthischapter,wefocusedonthedevelopmentenvironmentfortheHelpingHandsapplication.SinceClojureisourlanguageofchoiceforimplementation,wefirstlookedatthehistoryofClojureandLispandunderstoodwhyitiswellsuitedforourusecase.WealsolookedattheREPLenvironmentandtwobuildtoolsforClojure—LeiningenandBoot.Further,wedefinedareferenceLeiningenprojectconfigurationforourapplicationandlearnedhowtorunanapplicationandtestit.Wealsolearnedhowtogeneratedocumentationandreports,andhowtocreateadeployableartifact.Attheend,webrieflylookedattheClojureIDEsthatcanmakeourapplicationdevelopmentworkeasy.

Inthenextchapter,wewilllearnaboutRESTspecification.WewilllearnhowtodefineRESTAPIsformicroservicesintheHelpingHandsapplicationthatcanhelpwithdirectmessagingamongtheservices.

Page 138: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

RESTAPIsforMicroservices

"Comingtogetherisabeginning;keepingtogetherisprogress;workingtogetherissuccess."

-HenryFord

OneofthemodesofinteractionamongmicroservicesisdirectmessagingviaAPIs.RESTAPIsareoneofthewaystomakethedirectmessagingpossibleamongmicroservices.RESTAPIsenableinteroperabilityamongmicroservicesirrespectiveofthetechnologystackinwhichtheyareimplemented.Inthischapter,wewillcoverthefollowingtopics:

TheconceptofRESTHowtodefineRESTURIswithappropriatemethodswithstatuscodesHowtouseREST-basedHTTPURIsusingutilitiessuchascURLRESTAPIsfortheHelpingHandsapplication

Page 139: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroducingRESTRESTstandsforrepresentationalstatetransfer,anddefinesanarchitecturalstylefordistributedhypermediasystems.Itfocusesoncreatingindependentcomponentimplementationsthatarestatelessandscalewell.ApplicationsthatsupportRESTdonotstoreanyinformationabouttheclientstateontheserverside.SuchapplicationsrequireclientstomaintainthestatethemselvesandusetheREST-styleimplementations,suchasHTTPAPIsexposedbytheapplication,totransferthestatebetweenthemandtheserver.ClientswhousetheRESTAPImayquerytheserverforthelateststateandrepresentthesamestateattheclientside,thuskeepingtheclientandserverinsync.

Thestateofanapplicationisdefinedbythestateoftheentitiesofthesystem.EntitiescanberelatedtotheconceptofresourcesinRESTarchitecture.AresourceisakeyabstractionofinformationinREST.Itisaconceptualmappingtoasetofentitiesthatisdefinedbytheresourceidentifier.AnyinformationthatcanbenamedorbethetargetoftheRESTresourceidentifiercanbearesource.Forexample,aconsumerisaresourceforaServiceConsumerserviceandanorderisaresourceforanOrderservice.

ResourceidentifiersinRESTaredefinedasAPIendpointsthataremappedtoHTTPURIs.TheURIspointtooneormoreresourcesandallowthecallersto

Page 140: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

performoperationsontheresources,asshownintheprecedingimage.AlltheoperationssupportedbyRESTarestateless;thatis,noneoftheclientstateispersistedontheserverside.ThestatelessnatureoftheRESTAPIshelpsincreatingservicesthatscalehorizontallywithoutdependingonsharedstate.

RESTstyleissuitedtomicroservicesfordirectcommunicationandbuildingsynchronousAPIstogetaccesstotheentities.EachentitythatismanagedbyamicroservicecanbedirectlymappedtotheconceptofaresourceinREST.

TheconceptofRESTwasdefinedbyRoyFieldingin2000asapartofhisPhDdissertationonArchitecturalStylesandtheDesignofNetwork-basedSoftwareArchitectures(https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation_2up.pdf).ARESTfulsystemconformstosixguidingprinciples(https://en.wikipedia.org/wiki/Representational_state_transfer#Architectural_constraints)ofclient-server,statelessness,cacheability,layeredsystem,code-on-demand,anduniforminterface.

Page 141: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

RESTfulAPIs

WebserviceAPIsthatconformtoRESTarchitectureiscalledRESTfulAPIs.MicroservicesmostlyimplementtheHTTP-basedRESTfulAPIsthatarestatelessandhaveabaseURIandamediatype(https://en.wikipedia.org/wiki/Media_type)fortherepresentationofresources.ItalsosupportspredefinedstandardoperationsthataremappedtoHTTPmethodssuchasGET,POST,PUT,DELETE,andmore.

Forexample,asshowninthefollowingtable,theOrderservicemaydefineanAPI/orderstogetaccesstotheordersthatitmaintains.ItcansupportaGETmethodtolookupalltheordersorgetaspecificorderbyspecifyingtheorderID.ItcanalsoallowclientstocreatenewordersbyusingthePOSTmethodorcreateanorderwithaspecificIDbyusingthePUTmethod.Similarly,itcansupportthePUTmethodtoupdateorderdetailsandtheDELETEmethodtodeleteanorderbyspecifyingtheorderIDexplicitly.

URI HTTPmethod Operation Description

GEThttps://server/orders GET Read Getsalltheorders

GET

https://server/orders/1GET Read Getsthedetailsoforder

withIDas1

POST

https://server/ordersPOST Create Createsaneworderand

returnstheID

PUT

https://server/orders/100PUT Create Createsaneworderwith

IDas100

PUT

https://server/orders/2PUT Update Updatesanexistingorder

withIDas2

DELETE

https://server/orders/1DELETE Delete Deletesanexistingorder

withIDas1

Page 142: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GET,PUT,POST,andDELETEarethemostwidelyusedHTTPmethodsforRESTfulAPIs.OthermethodsincludeHEAD,OPTIONS,PATCH,TRACE,andCONNECT(https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods).EachrequestthatissenttotheURIsgeneratesaresponsethatmaybeHTML,JSON,XML,oranydefinedformat.JSONisthepreferredformatformicroservicesthathandlethecoreoperationsoftheapplication.Forexample,theresponsesgeneratedbytheRESTfulAPIsoftheOrderservicewillcontainaJSONobjectoforderdetailsoraJSONarrayoforders,whereeachorderisrepresentedasaJSONobjectwithkey-valuepairscontainingtheorderdetails.

Page 143: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Statuscodes

Statuscodesareimportantforclientstounderstandtheoutcomeoftherequestandtakerequiredactionontheresponse.Successfulrequestsreturnaresponsewiththerequiredrepresentationofresources,butfailedrequestsgeneratearesponserepresentingtheerrorinstead.Statuscodeshelpclientstobewellawareofthecontentoftheresponseandtakenecessaryactionattheclientside.

AllmicroserviceswithRESTfulAPIsmustsupportappropriateHTTPstatuscodes(https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)andsendthemtotheclientbasedontheoutcomeoftherequestedoperation.SomeofthestatuscodesthatmustbeimplementedbyallAPIsofmicroservicesare:

Statuscode Usage

200OK

Standardresponsecodeiftherequestwassuccessful.AGETrequestmustreturntheresourcedetailsintheresponse,andaPOSTrequestmustreturntheoutcomeoftheresponse.

201CreatedSentwhentherequestresultsinthecreationofaresource.TheresponsemustcontaintheURIoftheresource.

400Bad

Request

Client-sideerrorresponsecodeiftherequestcontainsinvalidorinsufficientparameters.Theresponsemustincludedetailsoftheissueswithrespecttorequestparametersforclienttocorrect.

401

Unauthorized

Sentwhenauthenticationisrequiredtoaccessaresourceandeitherclientrequestisnotauthenticatedordoesnotcontainauthentication/authorizationtokensasexpectedbytheserver.

403

Forbidden

Sentwhentheclientrequestisvalidbutitisnotallowedtoaccesstheresource,possiblyduetoinsufficientprivileges.

404Not

Found Sentwhentherequestedresourceisnotfoundontheserver.

Page 144: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

405Method

NotAllowedSentwhentherequestedmethodisnotsupportedbytheresource.Forexample,aresourcemaysupportonlytheGETmethod.Inthatcase,sendingaPUTrequestmayresultinaresponsewiththiserrorcode.

500Internal

ServerError

Genericerrorresponsecodesentbytheserverwhentherequestfailswithanunknownerrorthatisnothandledbytheserverandthereisnootherappropriateerrormessagetosend.

TheprecedingstatuscodesmustbeimplementedfortheRESTAPIs.ThereareotherstatuscodesaswellthatmaybeusedbytheAPIs.Toreviewallthedefinedstatuscodes,takealookattheHTTPstatuscodesguide.

Page 145: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Namingconventions

RESTAPIsmustbeorganizedaroundresourcesofthesystemandmustbeeasytounderstandbyjustlookingattheURIs.URIsmustfocusononeresourcetypeatatimewithoperationsmappedtoHTTPrequestmethods.TheymayhaveoneormorerelatedresourcesthatcanbefurthernestedintheURI.

GoodexamplesofURIsare/users,/consumers,/orders,and/services,not/getusers,/showorders,andmore.AbasicruletodefineAPIURIsistotargetnounsasresourcesandverbsasHTTPmethods.Forexample,insteadofcreatingaURIas/getusers,itisrecommendedtohavetheURIas/userswiththeHTTPmethodasGET.Similarly,insteadof/showorders,theURIGET/ordersmustbecreatedtoprovidealltheordersintheresponsetotheclient.GuidelinesfornamingURIsarefurtherexplainedinthefollowingdiagram:

ItisrecommendedtoalwaysuseSSL(https://en.wikipedia.org/wiki/Transport_Layer_Security)byonlyusingHTTPS-basedURIsforalltheAPIs.TheAPIsmustbeversionedtoupgradethemovertimewithoutbreakingtheintegrationwithexistingservices.TospecifytheversionoftheAPI,amajorversioncanbeusedasaprefixtoalltheAPIendpoints.Sub-versionsmustnotbeaddedtotheURI.Ifrequired,theycanbespecifiedusingacustomheaderparameter.

Versionscanalsobeintroducedviaamediatype.Itisperfectlyfine

Page 146: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

toincludesub-versionsaswellwiththemediatype.Versionsmayincludebreakingchangesaswellfortheclients,butitisrecommendedtokeepthechangesasbackwardcompatibleaspossible.

URIsmayalsocontainapplicationqualifiersthatsegmenttheAPIendpointsbasedonthetargetcomponentsorbusinessoperations.EachURImustbefocusedonasingleresourcetype,andthatnamemustbeusedinitspluralform.Toretrievespecificresourcesofthespecifiedtype,anIDmustbeappendedtotheURI.HerearesomeoftheexamplesofURIsthatfollowtheeasy-to-understandnamingconvention:

URI DescriptionGEThttps://server/v1/orders GetsalltheordersGEThttps://server:9988/v1/orders GetsalltheordersGEThttps://server/v1/tech/orders GetsalltheordersGEThttps://server/v1/orders/7 GetsanorderwithID7POSThttps://server/v1/orders CreatesaneworderandreturnstheID

PUThttps://server/v1/orders/17UpdatestheorderID17,createsnewifnotpresent

GET

https://server/v1/orders/7/feedbacks GetsallfeedbacksfororderID7GET

https://server/v1/orders/7/feedbacks/1 GetsthefeedbackwithID1fororderID7DELETEhttps://server/v1/orders/3 DeletestheorderwithID3PUThttps://server/v1/orders/7/rate UpdatestheratingfororderID7

GEThttps://server/v1/orders?

fields=ts,id,name

Getsalltheorderswithonlyts,idandnamefieldoftheordersintheresponse

GEThttps://server/v1/orders?

status=open&sort=-ts,name

Getsalltheordersthathavestatusasopen,sortedbytsindescendingorderandnameinascendingorder

Page 147: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 148: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingRESTfulAPIsviacURLcURLisacommand-lineutilitytotransferdatausingvariousprotocols,includingHTTPandHTTPS.cURLiswidelyusedtotryRESTfulAPIsusingitscommand-lineinterface.Forexample,let'stakealookattheAPIsprovidedbyMetaWeather(https://www.metaweather.com/api/)toqueryalocationandgetitsweatherinformation.SinceitisapublicAPIthatmaintainstheweatherinformationofwell-knownplaces,itallowsonlyGETrequeststogetthedetailsoftheresourcesanddoesnotallowcreatingorupdatingresources.

Togettheweatherofalocation,theMetaWeatherapplicationrequirestheresourceIDasdefinedbythesystem.TogettheresourceID,MetaWeatherprovidesasearchAPItolookuptheresourcesbyasearchquery.ThesearchAPIusestheGETmethodwiththequeryastheURLparameterintherequestandreturnstheJSONresponsewithresourcesthatmatchthequery,asshownhere:%curl-XGET"https://www.metaweather.com/api/location/search/?query=bangalore"[{"title":"Bangalore","location_type":"City","woeid":2295420,"latt_long":"12.955800,77.620979"}]

ThewoeidfieldintheresponseistheresourceIDrequiredbytheweatherAPIthatcanbeusedtogettheweatherdetailsofthequeriedlocation:

%curl-XGET"https://www.metaweather.com/api/location/2295420/"

{

"title":"Bangalore",

"location_type":"City",

"woeid":2295420,

"latt_long":"12.955800,77.620979",

"timezone":"Asia/Kolkata",

"time":"2017-10-24T21:06:34.146240+05:30",

"sun_rise":"2017-10-24T06:11:13.036505+05:30",

"sun_set":"2017-10-24T17:56:02.483163+05:30",

"timezone_name":"LMT",

"parent":{

"title":"India",

"location_type":"Country",

"woeid":23424848,

"latt_long":"21.786600,82.794762"

},

"consolidated_weather":[

{

"id":6004071454474240,

"weather_state_name":"HeavyCloud",

"weather_state_abbr":"hc",

"wind_direction_compass":"NE",

Page 149: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

"created":"2017-10-24T15:10:08.268840Z",

"applicable_date":"2017-10-24",

"min_temp":18.458000000000002,

"max_temp":29.392000000000003,

"the_temp":30.0,

"wind_speed":1.9876466767411649,

"wind_direction":51.745585344396069,

"air_pressure":969.60500000000002,

"humidity":63,

"visibility":11.150816730295077,

"predictability":71

},

...

],

"sources":[

{

"title":"BBC",

"slug":"bbc",

"url":"http://www.bbc.co.uk/weather/",

"crawl_rate":180

},

...

]

}

TheMetaWeatherapplicationAPIsalsosupporttheOPTIONSmethodthatcanbeusedtofindoutthedetailsoftheAPI.Forexample,sendingtherequesttothesamesearchAPIwiththeOPTIONSHTTPmethodprovidestherequireddetailsintheresponse:

%curl-XOPTIONS"https://www.metaweather.com/api/location/search/?query=bangalore"

{"name":"LocationSearch","description":"","renders":["application/json"],"parses":

["application/json","application/x-www-form-urlencoded","multipart/form-data"]}

TheMetaWeatherapplicationalsorespondswiththeappropriatestatuscodeandmessageintheresponseforaninvalidresourceID:

%curl-v-XGET"https://www.metaweather.com/api/location/0/"

...

*Trying172.217.26.179...

*Connectedtowww.metaweather.com(172.217.26.179)port443(#0)

...

*compression:NULL

*ALPN,serveracceptedtousehttp/1.1

>GET/api/location/0/HTTP/1.1

>Host:www.metaweather.com

>User-Agent:curl/7.47.0

>Accept:*/*

>

<HTTP/1.1404NotFound

<x-xss-protection:1;mode=block

<Content-Language:en

<x-content-type-options:nosniff

<strict-transport-security:max-age=2592000;includeSubDomains

<Vary:Accept-Language,Cookie

<Allow:GET,HEAD,OPTIONS

<x-frame-options:DENY

<Content-Type:application/json

Page 150: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

<X-Cloud-Trace-Context:507eec2981a7028e50e596fcb651acb7;o=1

<Date:Tue,24Oct201716:07:36GMT

<Server:GoogleFrontend

<Content-Length:23

<

*Connection#0tohostwww.metaweather.comleftintact

{"detail":"Notfound."}

Page 151: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

RESTAPIsforHelpingHandsTheHelpingHandsapplicationhasConsumerandProviderservicesthatexposetheRESTAPIstomanageconsumersandprovidersfortheapplication.EachserviceproviderintheapplicationcanregisteroneormoreservicesthataremanagedbyServiceAPIs.Apartfromtheseservices,thereisanOrderservicethatmanagesalltheordersplacedbytheconsumersfortheserviceandservedbytheprovidersoftheservice.TheHelpingHandsapplicationalsoprovidesanapplication-wideLookupservicethatprovidesasingleAPItolookupservicesandordersbyvicinity.AllAPIsprovidedbytheHelpingHandsmicroservicesalsohandleerrorswithappropriateerrormessagesintheresponseandthecorrespondingstatuscode.

Page 152: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ConsumerandProviderAPIs

TheAPIsprovidedbytheConsumerandProviderservicestargettheconsumersandprovidersresourcesofthesystem,respectively:

URI Method Params Description

/consumers POST Details(JSON) CreatesanewconsumerandreturnstheconsumerID

/consumers/1 PUTDetailstobeupdated(JSON)

Updatesthedetailsofanexistingconsumer

/consumers GETFields(CSV),sort(CSV),page

Getsalltheconsumersbasedonrequestparams

/consumers/1 DELETE - Deletesthespecifiedconsumer

/providers POST Details(JSON) CreatesanewproviderandreturnstheproviderID

/providers/1 PUTDetailstobeupdated(JSON)

Updatesthedetailsofanexistingprovider

/providers/1/star PUT - Incrementsthestarsfortheproviderbyone

/providers GETFields(CSV),sort(CSV),page

Getsalltheprovidersbasedonrequestparams

/providers/1 DELETE - Deletesthespecifiedprovider

Page 153: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ServiceandOrderAPIs

TheAPIsprovidedbytheServiceandOrderservicestargettheproviderservicesandordersresourcesofthesystem,respectively:

URI Method Params Description

/services POST Details(JSON) CreatesanewserviceandreturnstheserviceID

/services/1 PUTDetailstobeupdated(JSON)

Updatesthedetailsofanexistingservice

/services/1/star PUT - Incrementsthestarsfortheservicebyone

/services GETFields(CSV),sort(CSV),page

Getsalltheservicesbasedonrequestparams

/services/1 DELETE - Deletesthespecifiedservice

/orders POST Details(JSON) CreatesaneworderandreturnstheorderID

/orders/1 PUTDetailstobeupdated(JSON)

Updatesthedetailsofanexistingorder

/orders GETFields(CSV),sort(CSV),page

Getsalltheordersbasedonrequestparams

/orders/1 DELETE - Deletesthespecifiedorder

APIsforauthenticationandauthorizationhavebeenintentionallyleftoutfromthediscussion,butthesewillbeaddressedinPart-4ofthisbook.

Page 154: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 155: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Summary

Inthischapter,welearnedabouttheconceptofRESTandhowtodesignRESTfulAPIswithaneasy-to-understandnamingconvention.Wealsolearnedaboutvariousrequestmethodsandstatuscodes,andwhentousewhat.Attheend,werevisitedtheHelpingHandsapplicationtolistdowntheRESTAPIsthatwillberequiredfortheapplicationacrossmicroservices.

Inthenextchapter,wewilltakealookataClojureframeworkcalledPedestal(http://pedestal.io/)thatwewillbeusingtodesignmicroservicesfortheHelpingHandsapplication.PedestalwillalsobeusedtoexposeRESTAPIsforvariousmicroservicesoperations.

Page 156: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroductiontoPedestal

"Aconceptualframeworkisa'framethatworks'toputthoseconceptsintopractice."

-PaulHughes

Microservicesareimplementedusingaparticulartechnologystack,whichservesasingle-boundedcontextandprovidesservicesaroundit.Theservicesexposedfortheexternalworldmustbescalableandsupportdirectmessagingaswellasasynchronousrequests.Pedestal(http://pedestal.io/)isonesuchClojureframeworkthatfitsinwelltocreatereliableandscalableservicesformicroservice-basedapplications.ItalsofitsinwellwiththeClojurestackoftheHelpingHandsapplication.Inthischapter,youwill:

LearnaboutthebasicconceptsofPedestalLearnhowtodefinePedestalroutesandinterceptorsLearnhowtohandleerrorswithPedestalinterceptorsLearnhowtopublishoperationalmetricswithPedestalLearnhowtouseServer-sentEventsandWebSocketswithPedestal

Page 157: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

PedestalconceptsPedestalisanAPI-firstClojureframeworkthatprovidesasetoflibrariestobuildreliableandhighlyconcurrentservicesthataredynamicinnature.Itisanextensibleframeworkthatisdata-drivenandimplementedusingprotocols(https://clojure.org/reference/protocols)toreducethecouplingbetweenitscomponents.Itfavorsdataoverfunctionsandfunctionsovermacros.Itallowsforthecreationofdata-drivenroutesandhandlersthatcanapplyadifferentbehavioratruntimebasedonincomingrequests.Thismakesitpossibletocreatehighlyflexibleanddynamicservicesthatarewellsuitedformicroservice-basedapplications.Italsosupportsthebuildingofscalableasynchronousservicesusingserver-sentevents(SSE)andWebSockets:

ThePedestalarchitectureisbasedontwomainconcepts,InterceptorsandContextMap,andtwosecondaryconcepts,ChainProvidersandNetworkConnectors.AllthecorelogicofthePedestalframeworkhasbeenimplementedasInterceptors,buttheHTTPconnectionhandlerhasbeenseparatedtocreateaninterfaceforChainProviderthatsetsuptheinitialContextMapandqueueofInterceptorstostarttheexecution.PedestalincludesaservletchainprovideroutoftheboxthatworkswithalltheHTTPserversthatworkwithservlets.ItalsosetsuptherequiredkeysintheContextMapthatareexpectedbytheInterceptorsasperthecontract.PedestalapplicationsarenotlimitedtojustHTTP.Customchainproviderscanbewrittentosupportotherapplicationprotocolsaswell.Theycanalsoworkwithdifferenttransportprotocols,suchasreliableUDPbasedonthetargetchainproviderandtheunderlyingnetworkconnector.

Pedestalinitiallyhadtwoseparateparts—thePedestalapplication

Page 158: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

andPedestalserver.PedestalapplicationwasaClojureScript-basedfrontendframeworkthathasbeendiscontinued.NowthefocusisonlyonPedestalservertobuildreliableservicesandAPIs.

Page 159: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

InterceptorsPedestalinterceptorsarebasedonasoftwaredesignpatterncalledinterceptor.Interceptorisaserviceextensionthatregisterseventsofinterestwiththeframeworkandisinvokedbytheframeworkwhenthoseeventsoccurwithinthecontrolflow.Oncetheinterceptorisinvoked,itexecutesitsfunctionality,andthenthecontrolflowreturnstotheframework.MostofPedestalcorelogicismadeupofoneormoreinterceptorsthatcanbecomposedtogethertobuildachainofinterceptors.

InterceptorinPedestalisdefinedbyaClojureMap(https://clojure.org/reference/data_structures#Maps)thatcontains:name,:enter,:leave,and:errorkeysasshowninthefollowingdiagram.The:namekeycontainsanamespacedkeyword(https://clojure.org/reference/namespaces)fortheinterceptorandisoptional.The:enterand:leavekeysspecifyaClojurefunctionthattakesaContextMapasinputandreturnsaContextMapasoutput.Eitherthe:enteror:leavekeymustbedefinedfortheinterceptortoberegisteredwiththePedestalframework.Thefunctionspecifiedbythe:enterkeyiscalledbythePedestalframeworkwhenthedataflowsintotheinterceptor.Thefunctionspecifiedby:leaveiscalledwhentheresponseisbeingreturnedbytheinterceptor:

Thefunctionspecifiedby:erroriscalledwhenanexceptioneventistriggeredbytheinterceptorexecutionoriftheexecutionfailswithanerror.Tohandletheexceptionevent,the:errorkeyspecifiesaClojurefunctionthattakestwoarguments—aContextMapandanex-info(https://clojuredocs.org/clojure.core/ex-info)exceptionthatwasthrownbytheinterceptor.IteitherreturnsaContextMap,

Page 160: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

optionally,withtheexceptionreattachedforotherinterceptorstohandle,orthrowsanexceptionforthePedestalframeworktohandle.

Page 161: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TheinterceptorchainInterceptorsinPedestalcanbecomposedasaninterceptorchainthatfollowstheChainofResponsibility(https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)designpattern.Eachinterceptordoesexactlyonejobandwhencomposedtogetherasaninterceptorchain,theyachieveabiggertaskconsistingofoneormorejobs.

TheChainofResponsibilitypatternhelpswithnavigatingthecompositestructureoftheinterceptorchain.ThecontrolflowwithintheinterceptorchainiscontrolledbyaContextMap.SinceaContextMapitselfispassedasinputtoeachinterceptor,interceptorscanoptionallyadd,remove,orreorderinterceptorsinthechain.Thisisoneofthereasonswhymostofthemodulesofawebframework,suchasRouting,ContentNegotiation,RequestHandlers,andmore,arealsoimplementedasinterceptorsbyPedestal.

Interceptorfunctionsfor:enterand:leavemustreturnContextMapasavaluefortheexecutionflowtocontinuewiththenextinterceptor.Ifthefunctionsreturnanil(https://clojure.org/reference/data_structures#nil)value,aninternalservererrorisreportedbythePedestalframeworkandtheexecutionflowterminates.Aninterceptormayreturnacore.async(https://clojure.github.io/core.async/)channelinsteadoftheContextMap.Inthatcase,thechannelistreatedlikeapromisetodelivertheContextMapinthefuture.OncethechanneldeliverstheContextMap,thechainexecutorclosesthechannel:

Page 162: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Thechainexecutorcallseachinterceptorinthecore.asynclibrary'sgoblock(https://clojure.github.io/core.async/#clojure.core.async/go),sooneinterceptormaybecalledonadifferentthreadthanthenextbutallbindingsareconveyedtoeachinterceptor.InscenarioswheretheinterceptormaytakealongtimetoprocesstherequestormakeanexternalAPIcall,itisrecommendedtouseagoblocktosendachannelasthereturnvalueandletPedestalcontinuewiththeexecutionasynchronously.WhenPedestalreceivesachannelasanoutput,ityieldstheinterceptorthreadandwaitsforavaluetobeproducedbythechannel.Onlyonevalueisconsumedfromthechannel,anditmustbeaContextMap.

Asshownintheprecedingdiagram,whileexecutingtheinterceptorchain,all:enterfunctionsarecalledintheorderofinterceptorslistedinthechain.Onceallthe:enterfunctionsoftheinterceptorsarecalled,theresultingContextMapissentthroughthe:leavefunctionoftheinterceptorsbutinreverseorder.Sinceanyoftheinterceptorsinthechaincanreturnasynchronously,Pedestalcreateswhatitcallsavirtualcallstackofinterceptors.Itkeepsaqueueofinterceptorsforwhichithastocallthe:enterfunctionandalsomaintainsastackofinterceptorsforwhichthe:enterfunctionhasbeencalled,butthe:leavefunctionispending.KeepingastackallowsPedestaltocallthe:leavefunctioninthereverseorderofthe:enterfunctioncallsoftheinterceptorsinthechain.BothofthesequeuesandstacksarekeptintheContextMapandareaccessibletotheinterceptors.

Page 163: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SinceinterceptorshaveaccesstotheContextMap,theycanchangetheexecutionplanfortherestoftherequestbymodifyingthesequenceofinterceptorsinthechain.Theycannotonlyenqueueadditionalinterceptorsbutcanalsoterminatetherequesttoskipalltheremaininginterceptorsinthechain.

Page 164: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ImportanceofaContextMap

AcontextisjustaClojureMap(https://clojure.org/reference/data_structures#Maps)thatistakenasinputbytheinterceptorandalsogeneratedasanoutput.ItcontainsallthevaluesthatcontrolaPedestalapplication,includinginterceptors,chains,executionstacks,queues,andcontextvaluesthatmaybegeneratedbyinterceptorsorrequiredbyinterceptorsremainingintheinterceptorchain.AContextMapalsocontainsasequenceofpredicatefunctions.Ifanyoneofthepredicatefunctionreturnstrue,thechainisterminated.AlltherequiredfunctionsandsignalkeysarealsodefinedwithintheContextMaptoenableasynccapabilitiesandfacilitatetheinteractionbetweentheplatformandinterceptors.ThetableshownherelistssomeofthekeysthattheinterceptorchainkeepsintheContextMap:

Key Type Description

:bindingsMap(var->value)

Installedusingwith-bindings(https://clojuredocs.org/clojure.core/with-bindings)priortoexecution

:io.pedestal.interceptor.chain/error Exception Mostrecentexceptionthattriggerederrorhandling

:io.pedestal.interceptor.chain/execution-

id Opaque UniqueIDfortheexecution

:io.pedestal.interceptor.chain/queueQueueofinterceptors

Interceptorstobeexecutednext

:io.pedestal.interceptor.chain/stackStackofinterceptors

Interceptorslefttobeexecuted

:io.pedestal.interceptor.chain/terminators

Collectionofpredicates

Checksforvalidterminationpredicatesaftereach:enterfunction

Page 165: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Ifthe:bindingsmapisalteredbyaninterceptorandreturnedintheoutputContextMap,thenPedestalwillinstallthenewbindingsasthreadlocalbindingspriortotheexecutionofthenextinterceptorinthechain.The:io.pedestal.interceptor.chain/queuecontextkeycontainsalltheinterceptorsthatarelefttobeexecuted.Thefirstinterceptorinthequeueisthenextoneconsideredtobeexecutedbycallingthe:enterfunction.Thiskeymustbeusedonlyfordebuggingpurposes.Tomakeanychangestothequeueorexecutionflow,enqueue,terminate,orterminate-when(http://pedestal.io/api/pedestal.interceptor/io.pedestal.interceptor.chain.html#var-enqueue)intheinterceptorchainmustbecalledinsteadofchangingthevalueofthiskey.

Theterminationpredicatesspecifiedbythe:io.pedestal.interceptor.chain/terminatorskeysarecheckedforatruepredicateaftereach:enterfunctioncall.Ifthereisavalidpredicatefound,Pedestalskipsallotherremaininginterceptors':enterfunctionsandbeginsexecutingthe:leavefunctionofinterceptorsinthestacktoterminatetheexecutionflow.

The:io.pedestal.interceptor.chain/stackcontextkeycontainstheinterceptorsforwhichthe:enterfunctionhasalreadybeencalledbutthe:leavefunctionispending.Theinterceptoratthetopofthestackisexecutedfirsttomakesurethatthe:leavefunctioniscalledinthereverseorderof:enterfunctioncalls.

Inadditiontothekeysaddedbytheinterceptorchain,ContextMapmaycontainkeysthatareaddedbyotherinterceptorsaswell.Forexample,aservletinterceptor(http://pedestal.io/reference/servlet-interceptor),providedbyPedestaloutofthebox,addsservlet-specifickeystotheContextMap,suchas:servlet-request,:servlet-response,:servlet-config,and:servlet.WhenworkingwithHTTPserver,ContextMapalsohasthe:requestand:responsekeys,whichhaverequest(http://pedestal.io/reference/request-map)andresponse(http://pedestal.io/reference/response-map)mapsassignedtothem,respectively.

PedestalextendsbeyondjustHTTPservices.YoucanextendservicestoKafka-like(https://kafka.apache.org/)systemsandalsousedifferentprotocolssuchasSCTP,ReliableUDP,UDT,andmore.Thepedestal.servicePedestalmoduleisacollectionofHTTP-specificinterceptors.

Page 166: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 167: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingaPedestalservicePedestalprovidesaLeiningen(https://leiningen.org/)templatenamedpedestal-servicetocreateanewprojectwiththerequireddependenciesanddirectorylayoutforaPedestalservice.Tocreateanewprojectusingthetemplate,usetheleincommandwiththetemplatenameandaprojectnameasshownhere:

#Createanewproject'pedestal-play'withtemplate'pedestal-service'

%leinnewpedestal-servicepedestal-play

Retrievingpedestal-service/lein-template/0.5.3/lein-template-0.5.3.pomfromclojars

Retrievingpedestal-service/lein-template/0.5.3/lein-template-0.5.3.jarfromclojars

Generatingapedestal-serviceapplicationcalledpedestal-play.

Theleincommandwillcreateanewdirectorywiththespecifiedprojectnameandaddalltherequireddependenciestotheproject.cljfile.Itwillalsoinitializetheserver.cljandservice.cljfileswiththecodetemplateforasamplePedestalservice.Thecreatedprojectdirectorytreeshouldlookliketheoneshownhere:

#Showthe'pedestal-play'projectdirectorystructure

%treepedestal-play

pedestal-play

├──Capstanfile

├──config

│└──logback.xml

├──Dockerfile

├──project.clj

├──README.md

├──src

│└──pedestal_play

│├──server.clj

│└──service.clj

└──test

└──pedestal_play

└──service_test.clj

5directories,8files

Toruntheproject,justusetheleinruncommandanditwillcompileandstartthesampleservicedefinedbythetemplateatport8080.Totesttheservice,openthehttp://localhost:8080URLandhttp://localhost:8080/aboutinabrowserandobservetheresponse.ThefirstURLreturnstheresponseasHelloWorld!,whereasthesecondURLreturnsClojure1.8.0-servedfrom/about:

Page 168: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

BoththeendpointscanalsobeaccessedfromcURLasshownhere:

%curl-vhttp://localhost:8080

*RebuiltURLto:http://localhost:8080/

*Trying127.0.0.1...

*Connectedtolocalhost(127.0.0.1)port8080(#0)

>GET/HTTP/1.1

>Host:localhost:8080

>User-Agent:curl/7.47.0

>Accept:*/*

>

<HTTP/1.1200OK

<Date:Fri,03Nov201706:30:00GMT

<Strict-Transport-Security:max-age=31536000;includeSubdomains

<X-Frame-Options:DENY

<X-Content-Type-Options:nosniff

<X-XSS-Protection:1;mode=block

<X-Download-Options:noopen

<X-Permitted-Cross-Domain-Policies:none

<Content-Security-Policy:object-src'none';script-src'unsafe-inline''unsafe-eval'

'strict-dynamic'https:http:;

<Content-Type:text/html;charset=utf-8

<Transfer-Encoding:chunked

<

*Connection#0tohostlocalhostleftintact

HelloWorld!

%curl-vhttp://localhost:8080/about

*Trying127.0.0.1...

*Connectedtolocalhost(127.0.0.1)port8080(#0)

>GET/aboutHTTP/1.1

>Host:localhost:8080

>User-Agent:curl/7.47.0

>Accept:*/*

>

<HTTP/1.1200OK

<Date:Fri,03Nov201706:30:09GMT

<Strict-Transport-Security:max-age=31536000;includeSubdomains

<X-Frame-Options:DENY

<X-Content-Type-Options:nosniff

<X-XSS-Protection:1;mode=block

<X-Download-Options:noopen

<X-Permitted-Cross-Domain-Policies:none

<Content-Security-Policy:object-src'none';script-src'unsafe-inline''unsafe-eval'

'strict-dynamic'https:http:;

<Content-Type:text/html;charset=utf-8

<Transfer-Encoding:chunked

Page 169: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

<

*Connection#0tohostlocalhostleftintact

Clojure1.8.0-servedfrom/about

Toruntheapplicationindevmode,startaREPLsessionviaCIDER(https://cider.readthedocs.io/en/latest/up_and_running/#launch-an-nrepl-server-and-client-from-emacs),andcallthepedestal-play.server/run-devfunctiontostarttheserverindevmode.

Page 170: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsinginterceptorsandhandlersInthepedestal-playproject,theservice.cljsourcefiledefinestwointerceptors,about-pageandhome-page,whichareusedfortheservice.Additionally,itusestwoHTTP-specificinterceptors,body-params(http://pedestal.io/api/pedestal.service/io.pedestal.http.body-params.html#var-body-params)andhtml-body(http://pedestal.io/api/pedestal.service/io.pedestal.http.html#var-html-body),whichareprovidedoutoftheboxbythepedestal.servicemodule.Thepedestal-play.servicenamespacesnippetisshownherewiththeinterceptordeclarations:

(nspedestal-play.service

(:require[io.pedestal.http:ashttp]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[ring.util.response:asring-resp]))

;;UsedbyGET/about

(defnabout-page

[request]

(ring-resp/response(format"Clojure%s-servedfrom%s"

(clojure-version)

(route/url-for::about-page))))

;;UsedbyGET/

(defnhome-page

[request]

(ring-resp/response"HelloWorld!"))

(defcommon-interceptors[(body-params/body-params)http/html-body])

...

InsteadoftakingaContextMapasinputandreturningtheContextMap,theabout-pageandhome-pageinterceptorstaketherequestmap(http://pedestal.io/reference/request-map)asanargumentandreturntheresponsemap(http://pedestal.io/reference/response-map).SuchinterceptorsarecalledhandlersandaretreatedasfunctionsbythePedestalframework.HandlersinthePedestalframeworkdonothaveaccesstoContextMap;therefore,theycannotchangethesequenceofinterceptorsinthechainandthatisthereasontheycanonlybeusedattheendoftheinterceptorchaintoconstructtheresponsemap.Theresponsefunction,usedbythehome-pageandabout-pagehandlers,isautilityfunctionprovidedbytheRingframework(https://github.com/ring-clojure/ring)tocreateaRingresponsemapwith200status,noheaders,andagivenbodycontent.

Page 171: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Thebody-paramsfunctionreturnsaninterceptorthatparsestherequestbodybasedonitsMIMEtype(https://en.wikipedia.org/wiki/Media_type)andaddstherelevantkeystotherequestmapwiththecorrespondingbodyparameters.Forexample,itwilladda:form-paramskeywithalltheformparametersfortherequestswiththecontenttypeofapplication/x-www-form-urlencoded.Theinterceptorspecifiedbythehtml-bodyvaraddstheContent-Typeheaderparametertotheresponsewiththetypeastext/html;charset=UTF-8.Sincehtml-bodyactsontheresponse,thefunctionforthisinterceptorisdefinedforthe:leavekey,whereasforbody-paramsthefunctionisdefinedforthe:enterkeyofitsinterceptormap.

AlltheinterceptorsinPedestalimplementtheIntoInterceptorprotocol(http://pedestal.io/api/pedestal.interceptor/io.pedestal.interceptor.html#var-IntoInterceptor).PedestalextendstheIntoInterceptorprotocoltoaMap,Function,List,Symbol,oraVarandallowsinterceptorstobedefinedinanyoftheseextendedforms.ThemostcommonwayofdefininganinterceptorisaClojureMap(https://clojure.org/reference/data_structures#Maps)with:enter,:leave,and:errorkeys.Itcanbedefinedasafunction,aswell,tobeconsideredahandler,suchastheabout-pageandhome-pageofthepedestal-playproject.

Page 172: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatingroutes

InterceptorsinPedestalareattachedtoroutesthatdefinetheendpointsforclientstointeractwiththeapplication.PedestalprovidesthreeprominentwaysofdefiningRoutes(http://pedestal.io/reference/routing-quick-reference#_routes)—verbose,table,andterse.AllthreearedefinedasClojuredatastructureswithverbosebeingdefinedasaMap(https://clojure.org/reference/data_structures#Maps),tableasaset(https://clojure.org/reference/data_structures#Sets),andterseasavector(https://clojure.org/reference/data_structures#Vectors).Thesethreeformatsaredefinedforconvenienceonly.Pedestalinternallyconvertsallroutedefinitionsintotheverboseformbeforeprocessingthemusingtheconvenientfunctionexpand-routes(http://pedestal.io/api/pedestal.route/io.pedestal.http.route.html#var-expand-routes).TheverbosesyntaxisalistofMapswithkeywordsthatdefinearoute.Alistofkeywordsandtheirdescriptionsareshowninthefollowingtable:

Keyword Samplevalue Description:route-name

:pedestal-

play.service/about-page Uniquenamefortheroute:app-name :pedestal-play Optionalnamefortheapplication:path /about/:id PathURI

:method :getHTTPverb;canbe:get,:put,:post,:delete,:any,andmore

:scheme :http Optional,suchas:httpand:https:host somehost.com Optionalhostname:port 8080 Portnumber

:interceptorsVectorofinterceptors

InterceptorMapswith:name,:enter,:leave,and:errorkeys

:query-

constraints

{:name#".+":search#"

[0-9]+"} Constraintsonqueryparameters,ifany

Additionally,thereareafewmorekeysderivedfromthe:pathparameterof

Page 173: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

verbosesyntax,asfollows:

Keyword Samplevalue Description:path-re #"/\Qabout\E/([^/]+)" Regexusedtomatchthepath:path-parts ["about":id] PartsofthePathURI:path-params [:id] Pathparameters:path-constraints {:id"([^/]+)"} Constraintsforpathparameters,ifany

Thepedestal.routePedestalmodulecontainstheimplementationforbothRoutesandRouters.RoutersarespecialinterceptorsthattakeRoutesasinputinverboseformandprocesstheincomingrequeststotheRoutesbasedontheimplementation.Routersmakesurethattherequestsareprocessedthroughtheinterceptorsaspertheinterceptorchain.AnychangeintheinterceptorchainbyinterceptorsishandledbyRoutersefficiently.

Inthepedestal-playproject,theservice.cljsourcefiledefinestworoutes,GET/andGET/about,whichareboundedbytheinterceptorchainwithhome-pageandabout-pagehandlersattheendrespectively,asshowninthefollowingcode:

;;TabularRoutes

(defroutes#{["/":get(conjcommon-interceptors`home-page)]

["/about":get(conjcommon-interceptors`about-page)]})

The:app-name,:host,:port,and:schemekeyscanbespecifiedasmapsthatapplytoalltheroutesspecifiedinthelistofroutes,asshowninthefollowingcode:

(defroutes#{{:app-name"PedestalPlay":host"localhost":port8080:scheme:http}

["/":get(conjcommon-interceptors`home-page)]

["/about":get(conjcommon-interceptors`about-page)]})

Toseetheverbosesyntaxfortheroutes,usetheexpand-routes(http://pedestal.io/api/pedestal.route/io.pedestal.http.route.html#var-expand-routes)functionasshowninthefollowingREPLsession.Theoutputoftheexpand-routes(http://pedestal.io/api/pedestal.route/io.pedestal.http.route.html#var-expand-routes)functioniswhatispassedtothePedestalrouter.The:route-nameintheverboseformatisderivedfromthenameofthelastinterceptorinthechainorthesymbolattheendofthechainthatresolvestoafunction,suchas:pedestal-play.service/home-page:

Page 174: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

;;REPL

pedestal-play.server>(io.pedestal.http.route/expand-routespedestal-

play.service/routes)

({:app-name"PedestalPlay",

:route-name:pedestal-play.service/home-page,

:scheme:http,

:host"localhost",

:port8080,

:path"/",

:method:get,

:path-re#"/\Q\E",

:path-parts[""],

:path-params[],

:interceptors

[{:name:io.pedestal.http.body-params/body-params,

:enter#function[io.pedestal.interceptor.helpers/on-request/fn--9231],

:leavenil,

:errornil}

{:name:io.pedestal.http/html-body,

:enternil,

:leave#function[io.pedestal.interceptor.helpers/on-response/fn--9248],

:errornil}

{:namenil,

:enter#function[io.pedestal.interceptor/eval157/fn--158/fn--159],

:leavenil,

:errornil}]}

{:app-name"PedestalPlay",

:route-name:pedestal-play.service/about-page,

:scheme:http,

:host"localhost",

:port8080,

:path"/about",

:method:get,

:path-re#"/\Qabout\E",

:path-parts["about"],

:path-params[],

:interceptors

[{:name:io.pedestal.http.body-params/body-params,

:enter#function[io.pedestal.interceptor.helpers/on-request/fn--9231],

:leavenil,

:errornil}

{:name:io.pedestal.http/html-body,

:enternil,

:leave#function[io.pedestal.interceptor.helpers/on-response/fn--9248],

:errornil}

{:namenil,

:enter#function[io.pedestal.interceptor/eval157/fn--158/fn--159],

:leavenil,

:errornil}]})

ThesameRoutescanbedefinedinatersesyntaxaswell,usingthevector(https://clojure.org/reference/data_structures#Vectors)ofnestedvectors.Eachvectordefinesanapplication,optionally,withanapplicationname,scheme,host,andport.Eachapplicationdeclarationhasoneormorenestedvectorsthatdefinetheroutes.Eachvectoraddsapathsegmentrepresentingthehierarchicaltreestructureofroutes,asshowninthefollowingcode:

Page 175: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defroutes

`[["PedestalPlay":http"localhost"8080

["/"{:gethome-page}

^:interceptors[(body-params/body-params)http/html-body]

["/about"{:getabout-page}]]]])

Eachroutevectorcontainsapathsegment,suchas/about,interceptormetadatamap,constraintsmap,verbmap,andchildroutevectors,ifany.Interceptorsdefinedintheinterceptormetadatamapareappliedtoeveryroutedefinedintheverbmap,andtheverbkeyintheverbmapcontainsthevalueofverb-specifichandlerfunctionsoralistofinterceptors.

Page 176: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DeclaringroutersRoutersarefunctionsthatareaddedasinterceptorstothechaintoanalyzetherequestsbasedonthedefinedRoutes.Pedestalcreatesarouterbasedonthevaluesof:io.pedestal.http/routesand:io.pedestal.http/routerkeys,asspecifiedintheservicemap(http://pedestal.io/reference/service-map).TheservicemapcontainsallthedetailsforPedestaltocreateaserviceincludingarouter,routes,chain-providerproperties,andmore.Itactsasabuilder(https://en.wikipedia.org/wiki/Builder_pattern)forPedestalservices.

Pedestalprovidesthreebuilt-inroutersthatcanbespecifiedusingthe:map-tree,:prefix-tree,and:linear-searchkeywords.The:map-treeisthedefaultrouterthathasconstanttimecomplexitywhenappliedtoallroutesthatarestatic.Itfallsbacktoprefix-treeifanyrouteshavepathparametersorwildcards.

Ifthevalueof:io.pedestal.http/routerisspecifiedasafunctionthenthatfunctionisusedtoconstructarouter.Thefunctionmusttakeoneargument,thatis,thecollectionofroutesinverboseformat,andmustreturnarouterthatsatisfiesrouterprotocols(http://pedestal.io/api/pedestal.route/io.pedestal.http.route.router.html#var-Router).

Page 177: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Accessingrequestparameters

ServletChainProvider(http://pedestal.io/api/pedestal.service/io.pedestal.http.impl.servlet-interceptor.html)attachesarequestmaptotheContextMapwiththe:requestkeybeforethefirstinterceptorisinvoked.Therequestmapcontainsalltheform,query,andURLparametersspecifiedbytheclientoftheAPI.Theseparamsaredefinedasmapsofkey-valuepairs,whereeachkeyrepresentstheparameterspecifiedbytheclient.Alltheparamsareoptionalandarepresentonlyiftheyarespecifiedbytheclientintherequest.Hereisalistofthekeysthatarequestmapmaycontain:

Key Usedfor:path-

params PresentifanypathparametersarespecifiedandfoundbytheRouter

:query-

params

Presentifthequery-params(http://pedestal.io/api/pedestal.route/io.pedestal.http.route.html#var-query-params)interceptorisused(default)

:form-

params

Presentifthebody-params(http://pedestal.io/api/pedestal.service/io.pedestal.http.body-params.html#var-body-params)interceptorisusedandtheclientsendstherequestwithapplication/x-www-form-urlencodedasthecontenttype

:json-

params

Presentifthebody-paramsinterceptorisusedandtheclientsendstherequestwithapplication/jsonasthecontenttype

:edn-

params

Presentifthebody-paramsinterceptorisusedandtheclientsendstherequestwithapplication/ednasthecontenttype

:params Mergedmapofpath,query,andrequestparameters

Apartfromparameters,therequestmapalsohasthesekeysthatarealwayspresentandcanbeusedbyinterceptorsinthechain:

Key Type Usedfor

Page 178: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:async-

supported? Boolean Trueifthisrequestsupportsasynchronousoperations

:body ServletInputStream Bodyoftherequest

:headers Map Requestheaderssentbytheclientwithallnamesconvertedtolowercase

:path-info String Requestpath,belowthecontextpath;alwayspresent,atleast/

:protocol String Nameandversionoftheprotocolwithwhichtherequestwassent

:query-

string String Thepartoftherequest'sURLafterthe?character

:remote-

addr String IPAddressoftheclient(orthelastproxytoforwardtherequest)

:request-

method KeywordHTTPverbused,inlowercaseandinkeywordformasdeterminedbythemethod-paraminterceptor(default)

:server-

name String Hostnameoftheservertowhichtherequestwassent

:server-

port Int Portnumbertowhichtherequestwassent

:scheme String Thenameoftheschemeusedfortherequest,suchashttp,https,orftp

:uri String RequestURIfromtheprotocolnameuptothequerystring

Toreviewtherequestmap,addanewdebug-pagehandlertothepedestal-playprojectandmapittotheroute/debugasshowninthefollowingcodesnippet.Thehandlerdebug-pagejustreturnstherequestmapintheresponsewithonlytheparameterkeysofinterest.ItalsoconvertsitintoaJSONstringusingtheCheshire(https://github.com/dakrone/cheshire)library:(defndebug-page[request](ring-resp/response

Page 179: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(cheshire.core/generate-string(select-keysrequest[:params:path-params:query-params:form-params]))))

;;CommonInterceptorsusedforallroutes(defcommon-interceptors[(body-params/body-params)http/html-body])

(defroutes#{{:app-name"PedestalPlay":host"localhost":port8080:scheme:http}["/":get(conjcommon-interceptors`home-page)]["/about":get(conjcommon-interceptors`about-page)]["/debug/:id":post(conjcommon-interceptors`debug-page)]})

ThecURLrequesttothe/debugroutenowprovidestheentirerequestmapthatcanbeinspectedforthepath,query,andformparamsasshowninthefollowingexample:curl-XPOST-d"formparam=1""http://localhost:8080/debug/1?qparam=1"{"params":{"qparam":"1","formparam":"1"},"path-params":{"id":"1"},"query-params":{"qparam":"1"},"form-params":{"formparam":"1"}}

Page 180: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinginterceptorsAPedestalinterceptorcanbedefinedasamapwiththekeys:name,:enter,:leave,and:error.Forexample,aninterceptormsg-playcanbedefinedforthe/hellorouteofthepedestal-playprojecttochangethequeryparameternametouppercaseatthetimeofentryusingthe:enterfunctionandappendingagreetingatthetimeofexitusingthe:leavefunction.Itisfollowedbythehandlerhello-pagethatreadsthequeryparametersandaddsaHellogreeting.Takealookatthefollowingexample:

;;Handlerfor/helloroute

(defnhello-page

[request]

(ring-resp/response

(let[resp(clojure.string/trim(get-inrequest[:query-params:name]))]

(if(empty?resp)"HelloWorld!"(str"Hello"resp"!")))))

(defmsg-play

{:name::msg-play

:enter

(fn[context]

(update-incontext[:request:query-params:name]clojure.string/upper-case))

:leave

(fn[context](update-incontext[:response:body]

#(str%"Goodtoseeyou!")))})

;;CommonInterceptorsusedforallroutes

(defcommon-interceptors[(body-params/body-params)http/html-body])

(defroutes#{{:app-name"PedestalPlay":host"localhost":port8080:scheme:http}

["/":get(conjcommon-interceptors`home-page)]

["/about":get(conjcommon-interceptors`about-page)]

["/debug/:id":post(conjcommon-interceptors`debug-page)]

["/hello":get

(conjcommon-interceptors`msg-play`hello-page)]})

ThecURLrequesttothe/helloroutewiththenamequeryparameternowprovidestheresultasexpectedwithboth:enterand:leaveeventsfiringforthemsg-playinterceptor:

%curl"http://localhost:8080/hello?name=clojure"

HelloCLOJURE!Goodtoseeyou!

Ifthequeryparameterisnotspecified,itreturnstheHelloWorldgreetingaspertheimplementationofthehello-pageinterceptor:

%curl"http://localhost:8080/hello?name="

Page 181: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

HelloWorld!Goodtoseeyou!

Page 182: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

HandlingerrorsandexceptionsInterceptorsmightthrowanexceptionduetoanerrorintheimplementation.Forexample,trycallingthe/helloroutewithnoqueryparameter.Itfailsandthrowsanexceptionduetotheusageoftheupper-casefunctiononanilvalueofthe:nameparameter.Theexceptionisthrownbythe:enterfunctionofthemsg-playinterceptorbutthereisno:errorfunctiondefinedfortheinterceptortohandletheexception.SucherrorsmustbehandledgracefullyanderrorsmustbereportedtothecallerusingappropriateHTTPstatuscodes.Inthiscase,iftherequiredparameter:nameisnotdefined,thentherouteshouldreturnaresponsewithaHTTP400BadRequest,alongwithameaningfulmessageforthecaller.

PedestalunifieserrorhandlingforbothsynchronousinterceptorsthatreturnContextMap,andasynchronousinterceptorsthatreturnachannel.Itcatchesallexceptionsthrownwithinaninterceptorandbindsittothe:io.pedestal.interceptor.chain/errorkeyintheContextMap.Oncetheerrorisattachedtothekey,Pedestalstartslookingforthenextinterceptorinthechainthathasan:errorfunctionattachedtoit.

Tohandletheexceptionwiththe/hellorouteofthepedestal-playproject,an:errorfunctioncanbedefinedforthemsg-playinterceptor.The:errorfunctioncanthencatchanyexceptionthrownbytheinterceptorandassociateanappropriateresponsewiththeContextMap.Takealookatthefollowingexample:

(defmsg-play

{:name::msg-play

:enter

(fn[context]

(update-incontext[:request:query-params:name]

clojure.string/upper-case))

:leave

(fn[context](update-incontext[:response:body]

#(str%"Goodtoseeyou!")))

:error

(fn[contextex-info]

(assoccontext:response{:status400:body"Invalidname!"}))})

ThecURLrequestforthe/helloroutenowprovidesanappropriateresponsewiththecorrectstatuscodeandamessage,asshowninthefollowingexample:

%curl-i"http://localhost:8080/hello"

Page 183: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

HTTP/1.1400BadRequest

...

Invalidname!

The:errorfunctionreceivestwoarguments,ContextMapandanex-infoexception.ItcaneitherreturnaContextMaptocatchtheerrororupdatethe:io.pedestal.interceptor.chain/errorkeywiththeexceptiontolookforahandler.Itcanalsore-throwtheexceptionorthrowanewexceptiontosignalsomethingwentwrongwhilehandlingtheexception.Inbothcases,Pedestalwillstartlookingforahandleroftheexception.Pedestalkeepstrackofalltheexceptionsthatwereoverriddenbyaddingtheminsequencetothekey:io.pedestal.interceptor.chain/suppressedofContextMap.

Pedestalalsoprovidesanerror-dispatch(http://pedestal.io/api/pedestal.interceptor/io.pedestal.interceptor.error.html#var-error-dispatch)macrotobuilderror-handling(http://pedestal.io/reference/error-handling#_error_dispatch_interceptor)interceptorsthatusepatternmatchingtoselectaclause.

Page 184: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

LoggingThePedestalmodulepedestal.logcontainscomponentsforloggingandalsoreportingruntimeoperationalmetrics.PedestalusesLogback(https://logback.qos.ch/)forlogginganditcanbeconfiguredbycreatingalogback.xmlfileintheprojectconfigdirectory.Natively,logback-classicimplementsSLF4J(https://www.slf4j.org/),whichisusedbyPedestalaswellforlogging.Pedestalimplementseachlogginglevel—trace,debug,info,warn,anderror—asmacrosthattakekey-valuepairsasparameters,printedusingtheprfunction(https://clojuredocs.org/clojure.core/pr).TologanexceptionviaPedestallogger,the:exceptionkeymustbeusedwithajava.lang.Throwableobjectasavalueassignedtoit.

ThedefaultprojecttemplateofPedestalcontainsalogback.xmlfileintheconfigdirectoryalongwiththerelevantdependenciesaddedintheproject.cljfileforrequiredloggerimplementations.Thedefaultloggingconfigurationofthepedestal-playprojectlogsthelogbackconfigurationintheconsoleandalsointhelogfile,asmentionedinthelogback.xmlfile.Takealookatthefollowingexample:%leinrun18:53:30,595|-INFOinch.qos.logback.classic.LoggerContext[default]-CouldNOTfindresource[logback.groovy]18:53:30,595|-INFOinch.qos.logback.classic.LoggerContext[default]-CouldNOTfindresource[logback-test.xml]18:53:30,595|-INFOinch.qos.logback.classic.LoggerContext[default]-Foundresource[logback.xml]at[file:/pedestal-play/config/logback.xml]...18:53:30,686|-INFOinch.qos.logback.core.joran.action.AppenderAction-Abouttoinstantiateappenderoftype[ch.qos.logback.core.rolling.RollingFileAppender]18:53:30,688|-INFOinch.qos.logback.core.joran.action.AppenderAction-Namingappenderas[FILE]18:53:30,691|-INFOinch.qos.logback.core.joran.action.NestedComplexPropertyIA-Assumingdefaulttype[ch.qos.logback.classic.encoder.PatternLayoutEncoder]for[encoder]property18:53:30,711|-INFOin

Page 185: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

c.q.l.core.rolling.SizeAndTimeBasedRollingPolicy@2017577360-Archivefileswillbelimitedto[64MB]each.18:53:30,712|-INFOinc.q.l.core.rolling.SizeAndTimeBasedRollingPolicy@2017577360-Nocompressionwillbeused18:53:30,713|-INFOinc.q.l.core.rolling.SizeAndTimeBasedRollingPolicy@2017577360-Willusethepatternlogs/pedestal-play-%d{yyyy-MM-dd}.%i.logfortheactivefile18:53:30,716|-INFOinch.qos.logback.core.rolling.SizeAndTimeBasedFNATP@1e75bef1-Thedatepatternis'yyyy-MM-dd'fromfilenamepattern'logs/pedestal-play-%d{yyyy-MM-dd}.%i.log'.18:53:30,716|-INFOinch.qos.logback.core.rolling.SizeAndTimeBasedFNATP@1e75bef1-Roll-overatmidnight.18:53:30,719|-INFOinch.qos.logback.core.rolling.SizeAndTimeBasedFNATP@1e75bef1-SettinginitialperiodtoTueNov0718:53:30IST201718:53:30,719|-WARNinch.qos.logback.core.rolling.SizeAndTimeBasedFNATP@1e75bef1-SizeAndTimeBasedFNATPisdeprecated.UseSizeAndTimeBasedRollingPolicyinstead18:53:30,721|-INFOinch.qos.logback.core.rolling.RollingFileAppender[FILE]-Activelogfilename:logs/pedestal-play-2017-11-07.0.log...

Oncetheserverisstarted,thedefaultPedestalloggerlogseachrouteaccessrequestasanINFOmessageinthelog:Creatingyourserver...INFOorg.eclipse.jetty.server.Server-jetty-9.4.0.v20161208INFOo.e.j.server.handler.ContextHandler-Startedo.e.j.s.ServletContextHandler@62765e11{/,null,AVAILABLE}INFOo.e.jetty.server.AbstractConnector-StartedServerConnector@58fef400{HTTP/1.1,[http/1.1,h2c]}{0.0.0.0:8080}INFOorg.eclipse.jetty.server.Server-Started@4829msINFOio.pedestal.http-{:msg"GET/hello",:line80}

Pedestal'sservletinterceptor(http://pedestal.io/reference/servlet-interceptor)providesa

Page 186: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

defaulterrorhandlerthatlogstheHTTPrequestsandalsoexceptions,ifany.Italsoemitstheexceptionstacktraceintheresponsebodyindevelopmentmode.Pedestal'sloggingisbackedbytheLoggerSourceprotocol,whichcanbeimplementedforcustomloggers.

Page 187: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

PublishingoperationalmetricsOperationalmetricsareusefultounderstandtheusageandperformanceoftheapplicationbyobservingitsruntimestateasreportedbythemetrics.PedestalprovidesaloggingcomponentthatusestheMetricslibrary(http://metrics.dropwizard.io/3.2.3/)topublishthemetricstoJMX(https://en.wikipedia.org/wiki/Java_Management_Extensions)bydefaultviaMetricRegistry(http://metrics.dropwizard.io/3.1.0/getting-started/#the-registry).TheprotocolimplementedbyPedestalmetricsisMetricRecorder,whichcanbeimplementedforcustommetricsimplementation.Bydefault,MetricRecorderprovidesfourtypesofrecordersasshowninthefollowingtable:

Metricrecorder Usage

Gauge Usedfortheinstantaneousmeasurementofavalue

Counter Usedtoincrement/decrementasinglenumericmetric

Histogram Usedtomeasurethestatisticaldistributionofvalues(min,max,mean,median,percentiles)

Meter Usedtomeasuretherateofatickingmetric

Tocountthenumberofrequestsreceivedforeachrouteofthepedestal-playproject,acountercanbeaddedandincrementedeverytimethecorrespondinghandleriscalled:

(nspedestal-play.service

(:require[io.pedestal.http:ashttp]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.log:aslog]

[ring.util.response:asring-resp]))

(defnabout-page

[request]

(log/counter::about-hits1)

(ring-resp/response(format"Clojure%s-servedfrom%s"

(clojure-version)

(route/url-for::about-page))))

(defnhome-page

[request]

(log/counter::home-hits1)

(ring-resp/response"HelloWorld!"))

Page 188: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defndebug-page

[request]

(log/counter::debug-hits1)

(ring-resp/response

(cheshire.core/generate-string

(select-keysrequest[:params:path-params:query-params:form-params]))))

(defnhello-page

[request]

(log/counter::hello-hits1)

(ring-resp/response

(let[resp(clojure.string/trim(get-inrequest[:query-params:name]))]

(if(empty?resp)"HelloWorld!"(str"Hello"resp"!")))))

Bydefault,thecounterwillbepublishedviaJMXandcanbelookedupusingtheJVMmonitoringtoolJConsole(https://en.wikipedia.org/wiki/JConsole).TopublishthemetricstoJMX,startthePedestalapplicationinREPLusingpedestal-play.server/run-devandaccessvariousroutesdefinedforthepedestal-playapplication.Next,openJConsoleandconnecttotheclojure.mainprocessformetrics.Itwillstartlistingtheroutemetricsundertheio.pedestal.metricsMBean(https://en.wikipedia.org/wiki/Java_Management_Extensions#Managed_beans).Takealookatthefollowingscreenshot:

Page 189: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 190: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingchainprovidersPedestalprovidesservletinterceptoraschainprovidersforHTTP-basedwebapplicationsoutofthebox.Itconnectsanyservletcontainertotheinterceptorchain.Bydefault,thePedestalapplicationtemplateusestheJettywebserver(https://www.eclipse.org/jetty/),butitalsohassupportforchainprovidersthatworkwithserversotherthanJettyaswell,suchasImmutant(http://immutant.org/)andTomcat(https://tomcat.apache.org/).

Tostarttheapplicationwiththedefaultserverandchainprovider,thatis,forJetty,runleinrunwithinthepedestal-playapplicationandobservethelogmessages.ItshowsthelogsfortheJettyserver,thatis,theserverinuse:

Creatingyourserver...

INFOorg.eclipse.jetty.server.Server-jetty-9.4.0.v20161208

INFOo.e.j.server.handler.ContextHandler-Started

o.e.j.s.ServletContextHandler@62765e11{/,null,AVAILABLE}

INFOo.e.jetty.server.AbstractConnector-StartedServerConnector@4789995a{HTTP/1.1,

[http/1.1,h2c]}{0.0.0.0:8080}

INFOorg.eclipse.jetty.server.Server-Started@4755ms

INFOio.pedestal.http-{:msg"GET/about",:line80}

INFOio.pedestal.http-{:msg"GET/hello",:line80}

Touseadifferentchain-provider,say,Immutant,changetheservicemaptouseImmutantasthechain-provider.Takealookatthefollowingcode:

(defservice{:env:prod

::http/routesroutes

::http/resource-path"/public"

;;Either:jetty,:immutantor:tomcat

::http/type:immutant

::http/port8080

...

})

Also,changetheproject.cljfiletousetheImmutantimplementationasfollows:

(defprojectpedestal-play"0.0.1-SNAPSHOT"

:description"FIXME:writedescription"

:url"http://example.com/FIXME"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

Page 191: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

;;Removethislineanduncommentoneofthenextlinesto

;;useImmutantorTomcatinsteadofJetty:

;;[io.pedestal/pedestal.jetty"0.5.3"]

[io.pedestal/pedestal.immutant"0.5.3"]

;;[io.pedestal/pedestal.tomcat"0.5.3"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-

api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

:min-lein-version"2.0.0"

:resource-paths["config","resources"]

...

:main^{:skip-aottrue}pedestal-play.server)

Now,theleinrunlogsshowUndertow(http://undertow.io/)beingused,thatis,thewebserverusedbyImmutantlibrariesfortheweb.TheroutesandloggingworkexactlysameaswiththeJettywebserver:

INFOorg.xnio-XNIOversion3.4.0.Beta1

INFOorg.xnio.nio-XNIONIOImplementationVersion3.4.0.Beta1

WARNio.undertow.websockets.jsr-UT026010:Bufferpoolwasnotseton

WebSocketDeploymentInfo,thedefaultpoolwillbeused

INFOorg.projectodd.wunderboss.web.Web-Registeredwebcontext/

Creatingyourserver...

INFOio.pedestal.http-{:msg"GET/about",:line80}

INFOio.pedestal.http-{:msg"GET/hello",:line80}

Pedestalisnotjustlimitedtowebapplications.InterceptorsinPedestalcanbeusedinmessageprocessinganddataflowapplications,aswell,andarenotlimitedtoonlyrequest/reply-basedwebservices.

Page 192: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Usingserver-sentevents(SSE)Server-sentevents(SSE)(https://www.w3.org/TR/eventsource/)isastandardthatenablesefficientserver-to-clientstreamingusingatwo-partimplementation.ThefirstpartistheEventSourceAPIthatisimplementedattheclientsidetoinitiatetheSSEconnectionwiththeserver,andthesecondpartisthepushprotocolthatdefinestheeventstreamdataformatthatisusedfortheserver-to-clientcommunication.

TheEventSourceAPIofSSEisdefinedasapartoftheHTML5standardbyW3C(https://en.wikipedia.org/wiki/World_Wide_Web_Consortium)andisnowsupportedbyallthemodernwebbrowsers:

SSEsareprimarilyusedtopushreal-timenotifications,updates,andcontinuousdatastreamsfromservertoclientonceaninitialconnectionhasbeenestablishedbytheclient.Generally,thenotificationsandupdatesarepulledbytheclientbysendingarequesttotheAPIsorpolling(https://en.wikipedia.org/wiki/Polling_(computer_science))theserverforupdates.Pollingrequiresanewconnectiontobeestablishedbetweentheclientandserverforeachrequesttopullthenotificationsandupdates,asshownintheprecedingdiagram.SSEinsteadfocusesonthepushmodelinwhichtheconnectionisestablishedoncebytheclientandislong-lived.Allthenotifications,updates,anddatastreamsarepushedbytheserveroverthe

Page 193: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

sameclientconnection.Ifaconnectionislost,theclientautomaticallyreconnectswiththeservertokeeptheconnectionactive.TheflowbetweentheserverandtheclientisfurtherillustratedintheprecedingdiagramforbothpollingandSSEmodesofinteraction.

ThePedestalservicecomponentincludessupportforSSEaswell.Itsendsallitseventsasapartofasingleresponsestreamthatiskeptaliveovertimebysendingeventsand/orperiodicheartbeatdata.Iftheresponsestreamisclosedortheconnectionsisinterrupted,theclientcansendarequesttoreopenitandcontinuetoreceivetheeventsnotificationsfromtheserver.

Page 194: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinginterceptorsforSSEAninterceptorforSSEcanbecreatedusingthestart-event-stream(http://pedestal.io/api/pedestal.service/io.pedestal.http.sse.html#var-start-event-stream)functionprovidedbythePedestalservicecomponent.Ittakesafunctionasinputandreturnsaninterceptor.Thefunctionexpectedbythestart-event-streamfunctioniscalledbyPedestaloncetheinitialHTTPconnectionisestablishedwiththeclient;theHTTPresponseispreparedandPedestalnotifiestheclientthatanSSEstreamisstarting.Ittakesachannelasinput(https://clojure.github.io/core.async/),andContextMap.Tosendtheeventstotheclient,thefunctionjustpublishesthemonthechannelprovidedasanargumenttothefunction.Inadditiontothechannelprovidedasanargumenttothefunction,ContextMapalsocontainsachannelassociatedwiththe:response-channelkey.ThischannelisdirectlyconnectedwiththeresponseOutputStreamandmustnotbeusedtosendeventstotheclient.

TocreateaninterceptorforSSEinthepedestal-playproject,defineafunctionsse-stream-readyandpassitasanargumenttothestart-event-stream.Thestart-event-streamreturnsaninterceptorthatisassignedtoaroute/eventsthatinitiatesSSE.Thefunctionsse-stream-readyreadsarequestparametercounterforthenumberofmessagestobesenttotheclientanddefaultstofive.ItpublishesaMaponthechannelevent-chwithtwokeys,:nameand:data,thatcontainastringvalue.Itisrecommendedtousenamedeventsastheyarehelpfulforclientstotakeappropriateactionbasedonthenameoftheevent.Oncetherequirednumberofeventsaresent,itsendsacloseeventandclosesthechannel.Onceitclosesthechannel,Pedestalcleansuptheconnection.Takealookatthefollowingcode:

(nspedestal-play.service

(:require[io.pedestal.http:ashttp]

[io.pedestal.http.sse:assse]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.log:aslog]

[ring.util.response:asring-resp]

[clojure.core.async:asasync]))

(defnsse-stream-ready

"Startssendingcountereventstoclient."

[event-chcontext]

(let[count-num(Integer/parseInt

(or(->(context:request)

:query-params:counter)"5"))]

Page 195: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(loop[countercount-num]

(async/put!

event-ch{:name"count"

:data(strcounter",T:"

(.getId(Thread/currentThread)))})

(Thread/sleep2000)

(if(>counter1)

(recur(deccounter))

(do

(async/put!event-ch{:name"close":data"Iamdone!"})

(async/close!event-ch))))))

(defcommon-interceptors[(body-params/body-params)http/html-body])

(defroutes#{{:host"localhost":port8080:scheme:http}

["/":get(conjcommon-interceptors`home-page)]

["/about":get(conjcommon-interceptors`about-page)]

["/debug/:id":post(conjcommon-interceptors`debug-page)]

["/hello":get

(conjcommon-interceptors`msg-play`hello-page)]

["/events":get

[(sse/start-event-streamsse-stream-ready)]]})

Theroute/eventsisdefinedundertheroutesofthepedestal-playapplicationthatareusedtoreceiveeventsoverSSE.Ithasaninterceptorassignedthatiscreatedbycallingthestart-event-streamfunctionwiththesse-stream-readyfunctionthatpublishestheevents.Whenarequestreachesthisinterceptor,itpausestheinterceptorexecutionandsendsHTTPresponseheaderstotheclientstatingthataneventstreamisstarting,andinitiatesatimedheartbeattokeeptheconnectionalive.Oncetheconnectionisestablished,itcallsthesse-stream-readyfunctionwiththechannelandthecurrentContextMap.

TotrytheSSEendpoint,runthepedestal-playapplicationusingleinrunandusecURLtosendthegetrequesttothe/eventsendpoint.Takealookatthefollowingexample:

%curl-i-XGET"http://localhost:8080/events"

HTTP/1.1200OK

Date:Wed,08Nov201707:07:51GMT

X-Frame-Options:DENY

X-XSS-Protection:1;mode=block

X-Download-Options:noopen

Strict-Transport-Security:max-age=31536000;includeSubdomains

X-Permitted-Cross-Domain-Policies:none

Cache-Control:no-cache

X-Content-Type-Options:nosniff

Content-Security-Policy:object-src'none';script-src'unsafe-inline''unsafe-eval'

'strict-dynamic'https:http:;

Content-Type:text/event-stream;charset=UTF-8

Connection:close

event:count

data:5,T:22

event:count

Page 196: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

data:4,T:22

event:count

data:3,T:22

event:count

data:2,T:22

event:count

data:1,T:22

event:close

data:Iamdone!

Bydefault,itsendsfiveevents,butthatcanbecontrolledusingtherequestqueryparametercounter.Takealookatthefollowingexample:

%curl-XGET"http://localhost:8080/events?counter=2"

event:count

data:2,T:22

event:count

data:1,T:22

event:close

data:Iamdone!

TheSSEinterceptorsendsapartialHTTPresponsetotheclientasapartoftheconnectioninitializationprocessitself,therefore,anydownstreaminterceptorsarenotallowedtochangetheContextMapandtheresponsemap.Theycanonlyexaminethem.

PedestalsupportstheuseofLast-Event-ID(https://www.w3.org/TR/eventsource/#last-event-id)aswell,whichallowstheclienttoreconnectandresumefromthepointwhereitgotdisconnected.BasedontheSSEspec(https://www.w3.org/TR/eventsource/),PedestalsupportsassigningastringIDtotheSSEstreamthatcanbereferredtobytheclientintheLast-Event-IDheadertoresume.

Page 197: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingWebSocketsWebSocketsisacommunicationsprotocol(https://en.wikipedia.org/wiki/Communication_protocol)thatprovidesfull-duplex(https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX)communicationchannelsoverasingleTCPconnectionbetweenclientandserver.ItallowsclientstosendmessagestotheserverandreceiveservereventsoverthesameTCPconnectionwithoutpolling.ComparedtoServer-SentEvents(SSE)(https://www.w3.org/TR/eventsource/),WebSocketssupportfull-duplexcommunicationbetweenclientandserverinsteadofaone-waypush.Also,SSEsareimplementedoverHTTP,whichisanentirelydifferentTCPprotocolcomparedtoWebSocket.Althoughbothprotocolsaredifferent,theybothdependontheTCPlayer.

RFC6455(https://tools.ietf.org/html/rfc6455)statesthatWebSocketisdesignedtoworkoverHTTPports80and443aswellastosupportHTTPproxiesandintermediaries,thusmakingitcompatiblewiththeHTTPprotocol.Toachievecompatibility,theWebSockethandshakeusestheHTTPUpgradeheader(https://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header)tochangefromtheHTTPprotocoltotheWebSocketprotocol.

Server-senteventsareusefultosendnotificationsoralertsfromtheserverasandwhentheyoccur.Iftheapplicationrequiresaninteractivesessionbetweentheclientandtheserver,thenWebSocketsmustbepreferred.

Page 198: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingWebSocketwithPedestalandJettyPedestalprovidesout-of-the-boxsupportforJettyWebSocketsasapartofitspedestal.jettymodule.TocreateandregisteraWebSocketendpoint,Pedestalprovidestheadd-ws-endpointsfunctionthatacceptsaServletContextHandlerandaMapofWebSocketpathstotheactionMap.BasedontheprovidedWebSocketpaths,itproducesthecorrespondingservletsandaddsthemtothecontextoftheservletcontainer,thatis,Jettyinthiscase.TheservletcontainerthenmakestheWebSocketpathsavailablefortheclientstoconnecttousingtheWebSocketprotocol.TheWebSocketendpointsarecommunicatedtotheJettycontainerusingthe:context-configuratorkeyofthemapassignedtothe::http/container-optionskeyofPedestal'sservicemap.

Thepedestal-playprojectdefinesaws-pathsmapthatcontainsasingleWebSocketpath,/chat.Theactionsdefinedforthe/chatpathare:on-connect,:on-text,:on-binary,:on-error,and:on-close.Thestart-ws-connectionfunction,providedbyPedestal,acceptsafunctionoftwoarguments—theJettyWebSocketsessionanditspairedcore.asyncchannel—andreturnsafunctionthatcanbeusedasan:on-connectactionhandler.Forotheractions,thesamplepedestal-playprojectjustlogsamessage.Takealookatthefollowingexample:

(nspedestal-play.service

(:require[io.pedestal.http:ashttp]

[io.pedestal.http.sse:assse]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.log:aslog]

[io.pedestal.http.jetty.websockets:asws]

[ring.util.response:asring-resp]

[clojure.core.async:asasync]))

;;Atomtoholdclientsessions

(defws-clients(atom{}))

(defnnew-ws-client

"Keepstrackofallclientsessions"

[ws-sessionsend-ch]

(async/put!send-ch"Welcome!")

(swap!ws-clientsassocws-sessionsend-ch))

(defws-paths

{"/chat"{:on-connect(ws/start-ws-connectionnew-ws-client)

Page 199: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:on-text(fn[msg]

(log/info:msg(str"Client:"msg)))

:on-binary(fn[payloadoffsetlength]

(log/info:msg"BinaryMessage!":bytespayload))

:on-error(fn[t]

(log/error:msg"WSErrorhappened":exceptiont))

:on-close(fn[num-codereason-text]

(log/info:msg"WSClosed:"

:reasonreason-text))}})

(defservice{:env:prod

::http/routesroutes

::http/resource-path"/public"

::http/type:jetty

::http/port8080

;;Optionstopasstothecontainer(Jetty)

::http/container-options

{:h2c?true

:h2?false

:ssl?false

:context-configurator#(ws/add-ws-endpoints%ws-paths)}})

Inthepedestal-playexample,thefunctionprovidedtothestart-ws-connectionfunctionisnew-ws-client,afunctionthatsendsaWelcome!messagetoeachnewclientandkeepstrackofclientsessions.Thepedestal-playexamplealsodefinesacoupleofutilitymethods,send-and-close!andsend-message-to-all!,tosendmessagesfromtheserversidetotheclientsconnectedtotheWebSocket.Takealookatthefollowingexample:

(defnsend-and-close!

"Utilityfunctiontosendmessagetoaclientandclosetheconnection"

[message]

(let[[ws-sessionsend-ch](first@ws-clients)]

(async/put!send-chmessage)

(async/close!send-ch)

(swap!ws-clientsdissocws-session)

(log/info:msg(str"ActiveConnections:"(count@ws-clients)))))

(defnsend-message-to-all!

"Utilityfunctiontosendmessagetoallclients"

[message]

(doseq[[^org.eclipse.jetty.websocket.api.Sessionsessionchannel]

@ws-clients]

(when(.isOpensession)

(async/put!channelmessage))))

Totestthe/chatWebSocket,startthepedestal-playapplicationinREPLusingthepedestal-play.server/run-devfunction:

pedestal-play.server>(defsrv(run-dev))

Creatingyour[DEV]server...

INFOorg.eclipse.jetty.server.Server-jetty-9.4.0.v20161208

INFOo.e.j.server.handler.ContextHandler-Started

o.e.j.s.ServletContextHandler@15d129a1{/,null,AVAILABLE}

Page 200: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

INFOo.e.jetty.server.AbstractConnector-StartedServerConnector@766b5ac6{HTTP/1.1,

[http/1.1,h2c]}{0.0.0.0:8080}

INFOorg.eclipse.jetty.server.Server-Started@220932ms

#'pedestal-play.server/srv

pedestal-play.server>

Now,opentheJavaScriptconsoleinawebbrowserandstarttheWebSocketsessionusingthefollowingcommands:

//connectstothe'/chat'endpointwith'ws'protocol

w=newWebSocket("ws://localhost:8080/chat")

//logallthemessagesreceivedfromserveronconsole

w.onmessage=function(e){console.log(e.data);}

//messagetobeshownwhenserverclosestheconnection

w.onclose=function(e){

console.log("Theconnectiontotheserverhasclosed.");}

//sendamessagetoserver

w.send("HellofromtheClient-1!");

AnymessagethatissentfromtheclientisreceivedbytheserverandloggedontheREPL.Similarly,anymessagesentbytheserverusingthefunctionsend-message-to-all!isbroadcastedtoalltheactiveclientconnections.ToclosetheWebSocketconnection,callthesend-and-close!functionthatwillpickthefirstclientconnection,sendamessage,andcloseit.Italsologsthenumberofactiveclientconnectionsasshowninthefollowingcode:

INFOpedestal-play.service-{:msg"Client:HellofromtheClient-1!",:line113}

INFOpedestal-play.service-{:msg"Client:HellofromtheClient-2!",:line113}

pedestal-play.server>(pedestal-play.service/send-message-to-all!"HellofromPedestal

Server!")

nil

pedestal-play.server>(pedestal-play.service/send-and-close!"GoodbyefromPedestal

Server!")

INFOpedestal-play.service-{:msg"ActiveConnections:1",:line102}

nil

INFOpedestal-play.service-{:msg"WSClosed:",:reasonnil,:line119}

pedestal-play.server>(pedestal-play.service/send-and-close!"GoodbyefromPedestal

Server!")

INFOpedestal-play.service-{:msg"ActiveConnections:0",:line102}

nil

INFOpedestal-play.service-{:msg"WSClosed:",:reasonnil,:line119}

pedestal-play.server>

ThefollowingscreenshotcapturestheWebSocketinteractionsamongaREPLsessionandtwobrowserclients,connectedviaaJavaScriptconsole:

Page 201: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 202: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Summary

Inthischapter,welearnedabouttheconceptsofthePedestalframeworkandhowtousethemtocreateAPIs.WealsolearnedhowtologusefulinformationfordebuggingandmonitoringtheruntimestateoftheapplicationusingJMXmetrics.WealsolookedatvariouswebserverpluginsthatcanbeusedwithPedestal.Finally,welookedathowPedestalcanbeusedforSSEsandWebSocketsforclient-serverinteraction.

Inthenextchapter,wewilltakealookataDatomicdatabasethatwillbeusedforpersistencebythemicroservicesoftheHelpingHandsapplication.DatomiciswritteninClojureandfitsinwellwiththeHelpingHandsapplication,whichrequirestransactionsandtemporalqueries.

Page 203: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AchievingImmutabilitywithDatomic

"Mostofthebiggestproblemsinsoftwareareproblemsofmisconception."

-RichHickey

Microservicesdependontheunderlyingdatabasetoreliablystoreandretrievedata.OftenapplicationslikeHelpingHandsneedtostoreusertransactionsconsistentlyalongwithuserlocationsthatmaychangeovertime.Insteadofupdatingtheuserlocationpermanentlyandlosingthehistoryofthechanges,agoodapplicationmustmaintainthechangeindatasothatitcanbequeriedovertime.Suchrequirementsexpectthedatastoredinthedatabasetobeimmutable.Datomic(http://www.datomic.com/)isonesuchdatabasethatnotonlyprovidesdurabletransactionsbutalsohastheconceptofimmutabilitybuiltintoitscoresothatuserscanquerythestateofthedatabaseoveraperiodoftime.DatomicisalsowritteninClojure,whichisthetechnologystackofchoicefortheHelpingHandsapplication.Inthischapter,youwilllearnaboutthefollowing:

DatomicarchitectureanditsdatamodelHowtostoreandretrievedataasfactswithDatomicDatalogquerylanguagetoretrievefactsHowtoqueryimmutablefactswithanexample

Page 204: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatomicarchitectureDatomicisadistributeddatabasethatsupportsACID(http://docs.datomic.com/acid.html)transactionsandstoresdataasimmutablefacts.Datomicisfocusedonprovidingarobusttransactionmanagertokeeptheunderlyingdataconsistent,adatamodeltostoreimmutablefacts,andaqueryenginetohelpretrievedataasfactsovertime.Insteadofhavingitsownstorage,itreliesonanexternalstorageservice(http://docs.datomic.com/storage.html)tostorethedataondisk.

Page 205: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatomicversustraditionaldatabaseAtypicaldatabaseisimplementedasamonolithicapplicationthatcontainsthestorageengine,queryengine,andthetransactionmanagerallpackagedasasingleapplicationtowhichclientsconnecttostoreandretrievedata.Datomic,ontheotherhand,takesaradicalapproachofseparatingouttheTransactionManager(Transactor)asaseparateprocesstohandleallthetransactionsandcommitthedatatoanunderlyingStorageServicethatactsasapersistencestoreforallthedatamanagedbyDatomic.Ahigh-levelarchitectureofDatomicanditscomparisonwithtraditionaldatabasesisshownhere:

ClientsofDatomicarecalledpeersandhavetheapplicationcodeandpeerlibrary(http://docs.datomic.com/integrating-peer-lib.html)thatconnectwiththeTransactortostoredataconsistently.Peersalsoquerytheunderlyingstorageservicefordataandmaintainacachetoreducetheloadontheunderlyingstorageservice.PeersalsoreceiveupdatesfromtheTransactoraswellandaddtothecache.PeersinDatomicarethickclientsthatcanbeconfiguredtocache(http://docs.datomic.com/caching.html)thedatain-memory,oruseexternalcachingsystemslikeMemcached(https://en.wikipedia.org/wiki/Memcached)tostoretheobjects.Thecachemaintainedbypeersalwayscontainsimmutablefactsthatarealwaysvalid.

DatomicalsohasaPeerServerthatallowslightweightclientslikethatof

Page 206: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

traditionaldatabasestoconnecttoitdirectlytoquerythedatabase.Itactsasacentralqueryprocessorforalltheclientsconnectedtoit.Datomicprovidesaconsole(http://docs.datomic.com/console.html)aswell,whichhasagraphicaluserinterfacetomanageschema,examinetransactions,andexecutequeries.

Datomicisdesignedfortransactionaldataandmustbeusedtostoreuserprofiles,orders,inventorydetails,andmore.Itshouldnotbeusedforhigh-throughputusecasessuchasthosefoundinIoT(https://en.wikipedia.org/wiki/Internet_of_things),whichrequiresatime-seriesdatabasetostoreincomingdatawithhighvelocity.

Page 207: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DevelopmentmodelDatomicprovidestwodevelopmentmodels(http://docs.datomic.com/clients-and-peers.html)—PeerandClient.BoththemodelsrequireaTransactor(http://docs.datomic.com/transactor.html)toberunningtohandlethetransactionsandstorethedataconsistently.IntheClientmodel,aPeerServer(http://docs.datomic.com/peer-server.html)isrequiredinadditiontotheTransactortocoordinatethestorageandqueryrequestsfromtheclients.

Boththedevelopmentmodelsandtheparticipatingcomponentsareshowninthefollowingdiagram:

ClientsarelightweightinthecaseofaclientmodelasalltheinteractionwiththeTransactorandstorageengineishandledbythePeerServeronbehalfoftheclient,butitaddsanadditionalhopofrequestsasalltherequestsareroutedthroughPeerServerinsteadofdirectlyconnectingwiththeTransactorandthestorageengine.DatomicprovidesaseparatePeerLibraryandClientLibrary(http://docs.datomic.com/project-setup.html)forpeerandclientmodes,respectively.

Page 208: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DatamodelDatomicstoresdataasfactswitheachfactbeingafive-tuple(https://en.wikipedia.org/wiki/Tuple).ThesefactsarecalledDatoms.EachdatomconsistsofanEntityID,Attribute,andValuethatformthefirstthreepartsofthefive-tuple.Thefourthpartdefinesthetimestampatwhichthefactwascreatedandholdstrue.Thefifthpartconsistsofabooleanvaluethatdetermineswhetherthedefineddatomisanadditionorretractionofafact.Multipledatomsofthesameentitycanberepresentedasanentitymapwithanattributeandvalueaskey-valuepairs.TheEntityIDfortheentitymapisdefinedusingthekey:db/id:

Intheprecedingexample,therearefourattributes—order/name,:order/status,:order/rating,and:order/contactdefinedfortheorderentitywiththeID1234.

Page 209: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SchemaEachdatabaseinDatomichasanassociatedschemathatdefinesalltheattributesthatcanbeassociatedwiththeentities.Italsodefinesthetypeofvalueeachattributecancontain.Theattributesarethemselvestransactedasdatoms,thatis,theyarealsoconsideredasentitieswithassociatedattributesthataredefinedbyDatomic.TheattributessupportedbyDatomicforschemadefinitionsareasshowninthefollowingtable:

Attribute Type Description

:db/identNamespacedkeyword

Uniquenameofanattributeintheform<namespace>[.<nested-namespace>]/<name>,suchas:order/name.Namespacesareusefultopreventcollisionsbuttheycanbeomitted.:dbisarestrictednamespaceusedbyDatomicinternally.

:db/valueType Keyword

Definesthetypeofvalue.Supportedtypesare:

:db.type/keyword

:db.type/string

:db.type/boolean

:db.type/long

:db.type/bigint

:db.type/float

:db.type/double

:db.type/bigdec

:db.type/ref

:db.type/instant

:db.type/uuid

:db.type/uri

:db.type/bytes

:db/cardinality

Specifieswhethertheattributeissingle-valuedormulti-valued.Possiblecardinalitytypesare:

Page 210: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Keyword :db.cardinality/one

:db.cardinality/many

Datomicalsodefinessomeoptionalschema(http://docs.datomic.com/schema.html)attributessuchas:db/doc,:db/unique,:db/index,:db/fulltext,:db/isComponent,and:db/noHistory.The:db/ident,:db/valueType,and:db/cardinalityattributesaremandatory.

Datomicalsosupportsindexes(http://docs.datomic.com/indexes.html)thatcanbeenabledforanattributeusingthe:db/indexschemaattribute.Internally,DatomicmaintainsfourindexesthatcontaindatomsorderedbyEAVT,AEVT,AVET,andVAET,whereEisentity,Aisattribute,Visvalue,andTistransaction.

Page 211: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingDatomicDatomiccanbedownloadedfreelyfromitsGetDatomic(http://www.datomic.com/get-datomic.html)website.TostartwithDatomic,downloadtheDatomicfreeeditionthatincludesamemorydatabaseandembeddedDatalogqueryengine.ThefreeeditionisalsolimitedtotwosimultaneouspeersandembeddedstoragethatshouldbegoodenoughtotryoutDatomicfeaturesandworkwithitsdatamodel.

Page 212: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GettingstartedwithDatomicTosetupDatomic,downloadandextractthefreeedition'sdatomic-free-x.x.xxxx.xx.zipfile.ThefreeversionofDatomicdoesnotrequireanylicensekey.Forotherversions,registrationismandatorytoobtainalicensekeywhichmustbeaddedtotransactorpropertiesforDatomictowork.DatomicdistributioncontainstwoJARs,datomic-free-x.x.xxxx.xx.jaranddatomic-transactor-free-x.x.xxxx.xx.jar.Thedatomic-freeJARfilecontainsapeerlibraryanddatomic-transactorcontainstheimplementationofthetransactor.ThedistributionalsocontainsabinfolderthathasalltherequiredscriptstostartDatomiccomponentsasshownhere:datomic-free-0.9.5561.62├──bin├──CHANGES.md├──config├──COPYRIGHT├──datomic-free-0.9.5561.62.jar├──datomic-transactor-free-0.9.5561.62.jar├──lib├──LICENSE├──pom.xml├──README├──resources├──samples└──VERSION

5directories,8files

Tostartwiththefreeedition,createanewClojureLeiningenprojectandaddthedatomic-freedependency.Tobuilduponthepedestal-playproject,addthedependenciestotheexistingprojectconfigurationfileproject.cljofprojectpedestal-playasshownhere:(defprojectpedestal-play"0.0.1-SNAPSHOT":description"FIXME:writedescription":url"http://example.com/FIXME":license{:name"EclipsePublicLicense":url"http://www.eclipse.org/legal/epl-v10.html"}

Page 213: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:dependencies[[org.clojure/clojure"1.8.0"][io.pedestal/pedestal.service"0.5.3"][frankiesardo/route-swagger"0.1.4"]

;;Removethislineanduncommentoneofthenextlinesto;;useImmutantorTomcatinsteadofJetty:[io.pedestal/pedestal.jetty"0.5.3"];;[io.pedestal/pedestal.immutant"0.5.3"];;[io.pedestal/pedestal.tomcat"0.5.3"]

;;DatomicFreeEdition[com.datomic/datomic-free"0.9.5561.62"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-api]][org.slf4j/jul-to-slf4j"1.7.22"][org.slf4j/jcl-over-slf4j"1.7.22"][org.slf4j/log4j-over-slf4j"1.7.22"]]:min-lein-version"2.0.0":resource-paths["config","resources"]...:main^{:skip-aottrue}pedestal-play.server)

Thedependencyofcom.datomic/datomic-freewillpulltherequireddependencyfromClojars.Datomicdistributionalsoprovidesabin/maven-installscripttoinstalltheJARshippedinthedistributioninthelocalMaven(https://maven.apache.org/)repositoryfromwhereLeiningencanpullitfortheproject.

Now,startaREPLusingleinreplorjack-inusingtheEmacsCIDERplugintostartusingDatomicAPIs.Thenamespacerequiredtoaccessthepeerlibraryisdatomic.api.IncludethenamespaceinaREPLsessionasshownhere:%leinreplnREPLserverstartedonport33835onhost127.0.0.1-nrepl://127.0.0.1:33835REPL-y0.3.7,nREPL0.2.12Clojure1.8.0JavaHotSpot(TM)64-BitServerVM1.8.0_121-b13Docs:(docfunction-name-here)(find-doc"part-of-name-here")

Page 214: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Source:(sourcefunction-name-here)Javadoc:(javadocjava-object-or-class-here)Exit:Control+Dor(exit)or(quit)Results:Storedinvars*1,*2,*3,anexceptionin*e

pedestal-play.server=>(require'[datomic.api:asd])nilpedestal-play.server=>

TheDatomicfreeeditioncomeswithin-memorystorage.Thedatastoredwiththein-memorystorageisonlyavailableforthelifetimeoftheapplicationprocesswhenworkingwiththeDatomicfreeedition.

Page 215: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Connectingtoadatabase

Toconnecttoadatabase,first,definethedatabaseURIandcreateadatabaseusingthecreate-databasefunctionofthedatomic.apinamespace.IttakesasinputadatabaseURIthatdefinesthestorageenginetobeused,thatis,memforin-memoryandthedatabasetobecreated,thatis,hhorderforHelpingHandsorders.Itreturnstrueifthedatabaseiscreatedsuccessfullyasshownhere:

pedestal-play.server>(require'[datomic.api:asd])

nil

pedestal-play.server>(defdburi"datomic:mem://hhorder")

#'pedestal-play.server/dburi

pedestal-play.server>(d/create-databasedburi)

true

pedestal-play.server>

Oncethedatabaseiscreated,connecttoitusingtheconnectfunctionprovidedbythedatomic.apinamespace.Itreturnsadatomic.peer.LocalConnectionobjectthatcanbeusedtotransactwiththedatabase.Takealookatthefollowingexample:

pedestal-play.server>(defconn(d/connectdburi))

#'pedestal-play.server/conn

pedestal-play.server>conn

#object[datomic.peer.LocalConnection0x299180eb

"datomic.peer.LocalConnection@299180eb"]

pedestal-play.server>

Page 216: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TransactingdataDatomicneedstoknowabouttheattributestobeusedfortheentitiesinthedatabasebeforehand.Bothattributes(schema)andfacts(data)aretransactedasdatomsusingthetransactfunctionofthedatomic.apinamespace.Datomscanbetransactedindividuallyorclubbedtogetherasapartofsingletransactionbywrappingtheminavector(https://clojure.org/reference/data_structures#Vectors).Forexample,alltheattributesforhhordercanbetransactedusingasingletransactionbyspecifyingalltheentitymapstogetherwithinavectorandpassingthatvectorasanargumenttothetransactfunctionasshowninthefollowingexample:

pedestal-play.server>

(defresult

(d/transactconn[{:db/ident:order/name

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"DisplayNameofOrder"

:db/indextrue}

{:db/ident:order/status

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"OrderStatus"}

{:db/ident:order/rating

:db/valueType:db.type/long

:db/cardinality:db.cardinality/one

:db/doc"Ratingfortheorder"}

{:db/ident:order/contact

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"ContactEmailAddress"}]))

#'pedestal-play.server/result

Ifthe:db/idkeyisnotdefinedasapartoftheentitymap,itisaddedbyDatomicautomatically.ThetransactfunctiontakesasparameteraDatomicconnectionandavectorofoneormoredatomstotransact.Itreturnsapromise(https://en.wikipedia.org/wiki/Futures_and_promises)thatcanbedereferencedtoseethetransacteddatoms,aswellasthebeforeandafterstateofthedatabase.Takealookatthefollowingexample:

pedestal-play.server>(pprint@result)

{:db-beforedatomic.db.Db@6c5316fa,

:db-afterdatomic.db.Db@351b51d3,

:tx-data

[#datom[1319413953431250#inst"2017-11-22T13:01:30.632-00:00"13194139534312true]

#datom[6310:order/name13194139534312true]

#datom[63402313194139534312true]

#datom[63413513194139534312true]

Page 217: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

#datom[6362"DisplayNameofOrder"13194139534312true]

#datom[6344true13194139534312true]

#datom[6410:order/status13194139534312true]

#datom[64402313194139534312true]

#datom[64413513194139534312true]

#datom[6462"OrderStatus"13194139534312true]

#datom[6510:order/rating13194139534312true]

#datom[65402213194139534312true]

#datom[65413513194139534312true]

#datom[6562"Ratingfortheorder"13194139534312true]

#datom[6610:order/contact13194139534312true]

#datom[66402313194139534312true]

#datom[66413513194139534312true]

#datom[6662"ContactEmailAddress"13194139534312true]

#datom[0136513194139534312true]

#datom[0136413194139534312true]

#datom[0136613194139534312true]

#datom[0136313194139534312true]],

:tempids

{-922330166810959814363,

-922330166810959814264,

-922330166810959814165,

-922330166810959814066}}

Oncetheattributesaredefinedforthehhorderdatabase,theycanbeassociatedwiththeentities.Toaddaneworder,transactwiththeregisteredattributesasshowninthefollowingexample:

pedestal-play.server>

(deforder-result

(d/transactconn[{:db/id1

:order/name"CleaningOrder"

:order/status"Done"

:order/rating5

:order/contact"[email protected]"}

{:db/id2

:order/name"GardeningOrder"

:order/status"Pending"

:order/rating4

:order/contact"[email protected]"}]))

#'pedestal-play.server/order-result

Forexample,twoorderswithIDs1and2havebeenaddedtothehhorderdatabase,whichcannowbequeriedviaits:db/idorotherdefinedattributevalues.

Page 218: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingDatalogtoquery

Datomicofferstwowaystoretrievethedatafromthedatabase—pull(http://docs.datomic.com/pull.html)andquery(http://docs.datomic.com/query.html).ThequerymethodofretrievingfactsfromDatomicdatabasesusesanextendedformofDatalog(https://en.wikipedia.org/wiki/Datalog).Toquerythedatabase,theqfunctionofdatomic.apineedstoknowthestateofthedatabasetorunthequeryon.Thecurrentstateofthedatabasecanberetrievedusingthedbfunctionofdatomic.api.Takealookatthefollowingexample:

pedestal-play.server>(d/q'[:find?e?n?c?s

:where[?e:order/rating5]

[?e:order/name?n]

[?e:order/contact?c]

[?e:order/status?s]]

(d/dbconn))

#{[1"CleaningOrder""[email protected]""Done"]}

pedestal-play.server>

EachDatomicquerymusthaveeithera:findand:whereclauseora:findand:inclausepresentasapartofthequeryconstruct.Aquery,whengivenasetofclauses,scansthroughthedatabaseforallthefactsthatsatisfythegivenclausesandreturnsalistoffacts.Datomicquerygrammar(http://docs.datomic.com/query.html#grammar)definesallthepossiblewaystoquerythedatabase.Herearesomeoftheexamplestoquerythehhorderdatabase:

;;ReturnsonlytheentityIDoftheentitiesmatchingtheclause

(d/q'[:find?e

:where[?e:order/rating5]]

(d/dbconn))

#{[1]}

;;findalltheentitieswiththethreeattributesandentityID

(d/q'[:find?e?n?c?s

:where[?e:order/name?n]

[?e:order/contact?c]

[?e:order/status?s]]

(d/dbconn))

#{[1"CleaningOrder""[email protected]""Done"][2"GardeningOrder""[email protected]"

"Pending"]}

;;using'or'clause

(d/q'[:find?e?n?c?s

:where(or[?e:order/rating4][?e:order/rating5])

Page 219: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

[?e:order/name?n]

[?e:order/contact?c]

[?e:order/status?s]]

(d/dbconn))

#{[1"CleaningOrder""[email protected]""Done"][2"GardeningOrder""[email protected]"

"Pending"]}

;;usingpredicates

(d/q'[:find?e?n?c?s

:where[?e:order/rating?r]

[?e:order/name?n]

[?e:order/contact?c]

[?e:order/status?s]

[(<?r5)]]

(d/dbconn))

#{[2"GardeningOrder""[email protected]""Pending"]}

Page 220: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AchievingimmutabilityAllthefactspresentinaDatomicdatabaseareimmutableandarevalidforanygiventimestamp.Forexample,thestatusofanorderwith:db/id2inthecurrentstateofthedatabaseissetasPending.Takealookatthefollowingexample:

(d/q'[:find?e?s

:where[?e:order/status?s]]

(d/dbconn))

#{[1"Done"][2"Pending"]}

Now,trytoupdatethevalueofthe:order/statusattributefororderID2toDonebytransactingwithits:db/id.Takealookatthefollowingexample:

;;updatethestatusattributeto'Done'fororderID'2'

(defstatus-result(d/transactconn[{:db/id2:order/status"Done"}]))

#'pedestal-play.server/status-result

;;querythelateststateofdatabase

(d/q'[:find?e?s:where[?e:order/status?s]](d/dbconn))

#{[2"Done"][1"Done"]}

Aftertransacting,thestatusoftheorderID2nowshowstheupdatedstatusinthecurrentstateofthedatabase.AlthoughthequeryshowsthatthestatusoforderID2isnowDone,DatomicdoesnotoverwritethevalueofthestatusfororderID2in-place.Instead,itaddsanewdatomwiththerecenttransactiontimestamp.Wheneverthequeryisexecutedwiththecurrentstateofthedatabase,thatis,usingthedbfunctionofdatomic.api,italwaysreturnsthefactswiththemostrecenttimestamp.

Toretrievethepreviousstatusoftheorder2,usethestateofthedatabasebeforethetransactionthatupdatedthestate.Thereturnvalueoftransactcontainsa:db-beforekeythatcanbeusedtorunthesamestatusqueryonthedatabasestatebeforethetransaction.Takealookatthefollowingexample:

;;querythestatusonpreviousstate

(d/q'[:find?e?s

:where[?e:order/status?s]]

(@status-result:db-before))

#{[1"Done"][2"Pending"]}

TheresultreturnsthepreviousstatusoforderID2,thatis,Pending.ImmutabilityisoneofthemostpowerfulfeaturesofaDatomicdatabaseandisveryusefulto

Page 221: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

trackthechangesinthedatabase.

Page 222: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Deletingadatabase

Todeleteanexistingdatabase,usethedelete-databasefunctionofthedatomic.apinamespace.IttakesasinputthetargetdatabaseURIandreturnstrueifthedeletionsucceedsasshowninthefollowingexample:

pedestal-play.server>(d/delete-databasedburi)

true

DatomichasaDayofDatomic(http://www.datomic.com/training.html)seriesthatprovidesin-depthdetailsaboutDatomicdatabaseswithdetailedexamplesandtutorialstolearnfrom.

Page 223: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Summary

Inthischapter,welearnedaboutDatomicarchitectureandhowitisradicallydifferentfromtraditionaldatabases.Welearnedaboutitsdatamodelandhowitstoresdatoms.WealsolearnedhowtoretrievefactswithDatomicAPIsanditsDatalog-basedqueryengine.Wealsolookedatitsimmutabilityconstructsandhowtoquerydatabasesincurrentaswellashistoricalstates.

Inthenextpartofthisbook,wewillfocusontheimplementationofmicroservicesfortheHelpingHandsapplication,whichwillusePedestalasthebaseframeworktodesignAPIsandDatomicforpersistence.

Page 224: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

BuildingMicroservicesforHelpingHands

"It'snottheideas;it'sdesign,implementationandhardworkthatmakethedifference."

-MichaelAbrash

Identifyingboundedcontextisthefirststeptowardsbuildingasuccessfulmicroservices-basedarchitecture.Designingforscaleandimplementingthemwiththerighttechnologystackisthenextandthemostcrucialstepinbuildingamicroservices-basedapplication.Thischapterbringstogetherallthedesigndecisionstakeninthefirstpartofthebook(Chapter2,MicroservicesArchitectureandChapter3,MicroservicesforHelpingHandsApplication)anddescribesthestepstoimplementthemusingthePedestalframework(Chapter6,IntroductiontoPedestal).Inthischapter,youwilllearnhowto:

ImplementHexagonaldesignformicroservicesCreatescalablemicroservicesforHelpingHandsusingPedestalImplementworkflowsformicroservicesusingthePedestalinterceptorchainImplementthelookupserviceofHelpingHandstosearchforservicesandgeneratereports

Page 225: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ImplementingHexagonalArchitectureHexagonalArchitecture(http://alistair.cockburn.us/Hexagonal+architecture),asshowninthefollowingdiagram,aimstodecouplethebusinesslogicfromthepersistenceandtheservicelayer.Clojureprovidestheconceptofaprotocol(https://clojure.org/reference/protocols)thatcanbeusedtodefinetheinterfaces,thatactasportsofHexagonalArchitecture.Theseportscanthenbeimplementedbytheadapters,resultinginadecoupledimplementationthatcanbeswappedbasedontherequirement.ExecutionoftheseadapterscanthenbetriggeredviaPedestalinterceptorsbasedonthebusinesslogic.

Page 226: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Designingtheinterceptorchainandcontext

TheinterceptorchainmustbedefinedforeachmicroserviceoftheHelpingHandsapplicationseparately.Eachinterceptorchainmayconsistofinterceptorsthatauthenticatetherequest,validatethedatamodels,andapplythebusinesslogic.InterceptorscanalsobeaddedtointeractwiththePersistencelayerandtogenerateeventsaswell.ThelistofprobableinterceptorsthatmaybeapartofHelpingHandsservicesinclude:

Auth:UsedtoauthenticateandauthorizerequestsreceivedbytheAPIendpointsexposedbythemicroservice.Validation(datamodel):Usedtovalidatetherequestparametersandmaptheexternaldatamodelwiththeinternaldatamodelasexpectedbythebusinesslogicandunderlyingpersistentstore.Businesslogic:Oneormoreinterceptorstoimplementthebusinesslogic.TheseinterceptorsprocesstherequestparametersreceivedbytheAPIendpoints.Persistence:PersistthechangesusingoneormoreadaptersthatimplementthePortProtocoldefinedforthemicroservicedatamodeltobepersisted.Events:Generateeventsasynchronouslybothforothermicroservicesaswellasformonitoringandreporting.Theseinterceptorsareaddedattheendofthechaintogeneratechangelogeventsofthepersistentstoreforothermicroservicestoconsume.

PedestalcontextisaClojuremapthatcontainsallthedetailsrelatedtotheinterceptorchain,requestparameters,headers,andmore.Thesamecontextmapcanalsobeusedtosharedatawithotherinterceptorsinthechain.Insteadofaddingakeytothecontextmapdirectly,itisrecommendedtokeepaparentkey,suchastx-data,thatcontainsamapofkeysthatarerelatedtothedatabeingprocessedbythemicroservice.Itmayalsocontainthevalidateduserdetailsforothermicroservicestoconsume.

Page 227: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 228: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingaPedestalprojectTostartwiththeimplementation,createaprojectbythenameofhelping-handsandinitializeitwithaPedestaltemplateasdiscussedearlierinChapter6,IntroductiontoPedestal.Oncetheprojecttemplateisinitialized,updatetheproject.cljfileandotherconfigurationparametersasperthedevelopmentenvironmentsetupoftheplaygroundapplicationofChapter4,DevelopmentEnvironment.Onceconfigured,theproject.cljfileshouldcontainalltherequireddependenciesandplugins,asshownhere:

(defprojecthelping-hands"0.0.1-SNAPSHOT"

:description"HelpingHandsApplication"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

;;Removethislineanduncommentoneofthenextlinesto

;;useImmutantorTomcatinsteadofJetty:

[io.pedestal/pedestal.jetty"0.5.3"]

;;[io.pedestal/pedestal.immutant"0.5.3"]

;;[io.pedestal/pedestal.tomcat"0.5.3"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-

api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

:min-lein-version"2.0.0"

:source-paths["src/clj"]

:java-source-paths["src/jvm"]

:test-paths["test/clj""test/jvm"]

:resource-paths["config","resources"]

:plugins[[:lein-codox"0.10.3"]

;;CodeCoverage

[:lein-cloverage"1.0.9"]

;;Unittestdocs

[test2junit"1.2.2"]]

:codox{:namespaces:all}

:test2junit-output-dir"target/test-reports"

;;IfyouuseHTTP/2orALPN,usethejava-agenttopullinthecorrectalpn-boot

dependency

;:java-agents[[org.mortbay.jetty.alpn/jetty-alpn-agent"2.0.5"]]

:profiles{:provided{:dependencies[[org.clojure/tools.reader"0.10.0"]

[org.clojure/tools.nrepl"0.2.12"]]}

:dev{:aliases{"run-dev"["trampoline""run""-m""helping-

hands.server/run-dev"]}

:dependencies[[io.pedestal/pedestal.service-tools"0.5.3"]]

:resource-paths["config","resources"]

:jvm-opts["-Dconf=config/conf.edn"]}

:uberjar{:aot[helping-hands.server]}

:doc{:dependencies[[codox-theme-rdash"0.1.1"]]

Page 229: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:codox{:metadata{:doc/format:markdown}

:themes[:rdash]}}

:debug{:jvm-opts

["-server"(str"-agentlib:jdwp=transport=dt_socket,"

"server=y,address=8000,suspend=n")]}}

:main^{:skip-aottrue}helping-hands.server)

Theprojectdirectorystructureshouldcontaintherequiredfiles,asshownhere:

.

├──Capstanfile

├──config

│└──logback.xml

├──Dockerfile

├──project.clj

├──README.md

├──src

│├──clj

││└──helping_hands

││├──core.clj

││├──persistence.clj

││├──server.clj

││└──service.clj

│└──jvm

└──test

├──clj

│└──helping_hands

│├──core_test.clj

│└──service_test.clj

└──jvm

9directories,11files

Notethetwonewsourcefiles,core.cljandpersistence.clj,thathavebeencreatedmanuallyandaddedtotheprojectstructurealongwithacore_test.cljfilefortestcases.Thisprojectactsasatemplateforeachmicroservicethatfurtherextendstheservice.cljfilewiththeimplementationofroutesanddirectmessagingendpoints.Initializationoftheapplicationhappensincore.cljalongwiththeimplementationofinterceptorsandbusinesslogic.Thepersistencelayer,alongwithitsprotocol,isdefinedinpersistence.clj.ThenextstepistostartdefiningthegenericinterceptorsforHelpingHandsmicroservices.

Page 230: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefininggenericinterceptorsInadditiontothebusinesslogic,eachmicroserviceneedstoauthenticaterequests,validateincominginputparameters,mapexternaldatamodelstointernaldatamodelsforbusinesslogic,andgenerateeventsforothermicroservicesbasedontheactiontaken.AllofthesecapabilitiescanalsobeimplementedasaPedestalinterceptorforbetterflexibilityandreducedmaintenanceoverheadduetotheseparationofconcernofeachPedestalinterceptor.

Page 231: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

InterceptorforAuthMicroservicesthatallowuserstoregisterservices,lookupservices,andcreateordersmustintegratewiththeAuthservicetomakesurethattherequestsreceivedbytheservicearegenuineandthesenderisauthorizedtoperformtherequestedtask.InsteadofembeddingAuthlogicinallthemicroservices,itisrecommendedtoseparateitoutasamicroservicewithwhichallotherservicescaninteractviadirectmessagingtoauthenticatethesenderoftherequest.

TheinteractionwiththeAuthservicecanbeembeddedwithinaPedestalinterceptorthatfrontendstheinterceptorchainforallthesecuredAPIs.ThisinterceptorshouldcapturetheauthenticationandauthorizationdetailsintherequestandsendthemtotheAuthservicetovalidate.IftheAuthservicefailstovalidatetherequest,theinterceptorshouldterminatethechainandreturnaHTTP401Unauthorizedresponse,elseitshouldaddthesenderprofiledetailstotherequestandforwardittothenextinterceptorinthechain.

Forexample,authisaninterceptorthatlooksforatokenintherequestheader.Ifitispresent,itlooksuptheuserdetailsandpopulatesthemunderthe:userkeywordofthePedestalcontextmap.Ifatokenisnotpresentintheheader,itaddsaHTTP401Unauthorizedresponsewithamessageinthebodyandterminatesthechain:

(nshelping-hands.service

(:require[cheshire.core:asjp]

[io.pedestal.http:ashttp]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.interceptor.chain:aschain]

[ring.util.response:asring-resp]))

...

(defauth

{:name::auth

:enter

(fn[context]

(let[token(->context:request:headers(get"token"))]

(if-let[uid(and(not(nil?token))(get-uidtoken))]

(assoc-incontext[:request:tx-data:user]uid)

(chain/terminate

(assoccontext

:response{:status401

:body"Authtokennotfound"})))))

:error

Page 232: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

;;Tabularroutes

(defroutes#{["/":get(conjcommon-interceptors`auth`home-page)]})

Asofnow,forsimplicity,let'sassumethattheget-uidfunctionjustreturnsaresponsewithuidasafieldthathasafixedvalue,hhuser,thatcanbepickedbyinterceptors,suchashome-page,toconstructtheresponsefurtherdowntheinterceptorchain:

(nshelping-hands.service

(:require[cheshire.core:asjp]

[io.pedestal.http:ashttp]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.interceptor.chain:aschain]

[ring.util.response:asring-resp]))

...

(defnhome-page

[request]

(ring-resp/response

(if-let[uid(->request:tx-data:user(get"uid"))]

(jp/generate-string{:msg(str"Hello"uid"!")})

(jp/generate-string{:msg(str"HelloWorld!")}))))

(defn-get-uid

"TODO:IntegratewithAuthService"

[token]

(when(and(string?token)(not(empty?token)))

;;validatetoken

{"uid""hhuser"}))

Now,trytorequesttheroute/withandwithoutatoken.ItwillreturntheHTTP200OKresponsewiththemessagecontainingthefixeduserhhuser;whereas,withoutatoken,itwillreturnaHTTP401Unauthorizedresponse.Inthelattercase,executionofthehome-pageinterceptorisskippedentirelyincludingboththe:enterand:leavefunctions:

%curl-i-H"token:1234"http://localhost:8080

HTTP/1.1200OK

...

{"msg":"Hellohhuser!"}

%curl-ihttp://localhost:8080

HTTP/1.1401Unauthorized

...

Authtokennotfound

Page 233: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TheAuthserviceandrelatedinterceptorareexplainedindetailinPart-4ofthisbookunderChapter11,DeployingandMonitoringSecuredMicroservices.

Page 234: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

InterceptorforthedatamodelOncetherequestisauthenticatedandauthorizedbytheAuthinterceptor,itshouldbevalidatedagainstthedatamodelofthemicroservice.ThisvalidationlogiccanalsobeimplementedasaPedestalinterceptorthatcanreviewtheinputparametersspecifiedwiththerequestandmakesurethatitconformstothedatamodel.Iftherequestparametersarevalid,thisinterceptorshouldforwardtherequesttothenextinterceptorinthechainwiththerequireddetails,elseitshouldterminatethechainandreturnaHTTP400BadRequestresponse.

Thedatamodelvalidationinterceptorcanalsoaddadditionaldetailstotherequest.Forexample,iftherequestcontainsjusttheserviceID,thisinterceptorcanpullintheservicedetailsandserviceproviderdetailsandaddittothelistoftheparametersfortherestofthechaintoprocess.ItcanalsovalidatethepresenceandabsenceofthespecifiedserviceID.

Forexample,tocreateanorder,arequestmustcontaintheserviceIDforwhichtheorderistobecreated.Thedatamodelinterceptor,inthiscase,canfirstvalidatethattherequestcontainstheserviceIDandifitdoes,itcanvalidatetheserviceIDwiththeexternalmicroservicethatmanagestheServicedatabaseandpullinadditionaldetails:(defn-get-service-details"TODO:GettheservicedetailsfromexternalAPI"[sid]{"sid"sid,"name""HouseCleaning"})

(defdata-validate{:name::validate

:enter(fn[context](let[sid(->context:request:form-params:sid)](if-let[service(and(not(nil?sid))(get-service-detailssid))](assoc-incontext[:request:tx-data:service]service)(chain/terminate(assoccontext

Page 235: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:response{:status400:body"InvalidServiceID"})))))

:error(fn[contextex-info](assoccontext:response{:status500:body(.getMessageex-info)}))})

;;Tabularroutes(defroutes#{["/":post(conjcommon-interceptors`auth`data-validate`home-page)]})

Now,thePOST/endpointusesbothauthanddata-validateinterceptorstomakesurethatbeforetherequesthitsthehome-pageinterceptor,ithasbeenauthenticatedandcontainstherequiredservicedetails.ThebehavioroftheendpointisshownhereusingcURLrequests:%curl-i-XPOSThttp://localhost:8080HTTP/1.1401Unauthorized...

Authtokennotfound

%curl-i-XPOST-H"token:1234"http://localhost:8080HTTP/1.1400BadRequest...

InvalidServiceID

%curl-i-XPOST-H"token:1234"-d"sid=1"http://localhost:8080HTTP/1.1200OK...

{"msg":"Hellohhuser!"}

ValidationinterceptorscoveredinthischapteruseClojurecoreconditionalfunctionstoachievethedesiredvalidation.WiththeClojure-1.9.0release,itisrecommendedtomovetoClojurespec(ht

Page 236: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

tps://clojure.org/guides/spec)forallsuchvalidations.

Page 237: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

InterceptorforeventsHelpingHandsmicroservices,suchasServiceProviderandServiceConsumer,generatechangelogeventsfortheLookupservicetopickupandupdatethelocaldatabaseforServiceConsumerstolookup.ThisrequirescertaineventstobegeneratedeverytimeachangeispushedtothelocaldatabaseoftheServiceProviderandServiceConsumerservice.TheseeventscanbedeterminedbyaninterceptorthatispresentinthechainrightbeforethePersistenceinterceptorthatpersiststhechangestothedatabase.Apartfromchange-logevents,eachmicroservicemayalsopublisheventsformonitoringandreporting.Alltheseeventscanbegeneratedbythesameinterceptor.

Theeventsinterceptorgeneratesalltheeventsrequiredbyothermicroservicesandtomonitortheentireapplication.Chapter10,EventDrivenPatternsforMicroservices,andChapter11,DeployingandMonitoringSecuredMicroservices,talkaboutusingexternalframeworkssuchasKafkatopublishandconsumeeventsforcoordinationandbuildinganevent-drivendatapipelineformicroservices.

Page 238: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforServiceConsumer

TheServiceConsumermicroserviceexposesAPIsforenduserstoregisterasaconsumeroftheHelpingHandsapplication.Tolookupregisteredservicesandplaceanorder,theuseroftheapplicationmustberegisteredasaConsumer.AspertheworkflowofServiceConsumerdefinedinChapter3,MicroservicesforHelpingHandsApplication,itrequiresthefollowingAPIstocreatenewconsumers,getconsumerprofiles,andupdateconsumerdetails:

URI Description

GET

/consumers/:id/?

flds=name,address

Getsthedetailsoftheconsumerwiththespecified:idifthe:idisspecified,elseitgetsthedetailsoftheauthenticatedconsumer.Optionally,itacceptsaCSVoffieldstobereturnedintheresponse.

PUT

/consumers/:id CreatesanewconsumerwiththespecifiedID.POST/consumers CreatesanewconsumerandreturnstheID.DELETE

/consumers/:id DeletestheconsumerwiththespecifiedID.

Page 239: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

AddingroutesPedestalroutesareaddedforeachoftheidentifiedAPIs.TheinterceptorchainforeachAPIconsistsofAuth,Validation,BusinessLogic,andEventinterceptorsthatauthorizetheincomingrequests,validatetherequiredparameters,applythebusinesslogic,andgeneratetherelevantevents,respectively,forothermicroservicesandmonitoringframeworks.

Sincetheinterceptorchainofeachrouteendswithacommongen-eventsinterceptor,a:route-namemustbedefinedforeachroutetomakesurethattheyreceiveauniquename.If:route-nameisnotspecified,PedestalwilltrytoassignthenameofthelastinterceptortoeachrouteandwillfailwiththeRoutenamesarenotuniqueexception.Addtheroutesfortheconsumerserviceintheservice.cljfile,asshowninthefollowingcodesnippet:(nshelping-hands.consumer.service(:require[helping-hands.consumer.core:ascore][io.pedestal.http:ashttp][io.pedestal.http.route:asroute][io.pedestal.http.body-params:asbody-params]))

(defcommon-interceptors[(body-params/body-params)http/html-body])

;;Tabularroutes(defroutes#{["/consumers/:id":get(conjcommon-interceptors`auth`core/validate-id`core/get-consumer`gen-events):route-name:consumer-get]["/consumers/:id":put(conjcommon-interceptors`auth`core/validate-id`core/upsert-consumer`gen-events):route-name:consumer-put]["/consumers":post(conjcommon-interceptors`auth`core/validate`core/create-consumer`gen-events):route-name:consumer-post]

Page 240: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

["/consumers/:id":delete(conjcommon-interceptors`auth`core/validate-id`core/delete-consumer`gen-events):route-name:consumer-delete]})

Chapter11,DeployingandMonitoringSecuredMicroservices,discussestheimportanceofeventsgeneratedbythemicroservicesforreal-timemonitoringandgeneratingalerts.

Page 241: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefiningtheDatomicschema

TheServiceConsumermicroserviceusesDatomicasthelocaldatabasetostoretheconsumerdetails.Theschemafortheconsumerdatabaseconsistsofthefollowingattributes:

:db/ident :db/valueType :db/cardinality :db/index :db/fulltext

:consumer/id :db.type/string :db.cardinality/one true false

:consumer/name :db.type/string :db.cardinality/one true true

:consumer/address :db.type/string :db.cardinality/one true true

:consumer/mobile :db.type/string :db.cardinality/one false -

:consumer/email :db.type/string :db.cardinality/one true -

:consumer/geo :db.type/string :db.cardinality/one false -

Page 242: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 243: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingapersistenceadapterPersistenceprotocolfortheConsumerserviceconsistsofupsert,entity,anddeletefunctionsthatareimplementedbyeachadapterofthisport.AdaptersimplementingthepersistenceprotocolarethenusedwithinthePedestalinterceptortocreate,update,query,anddeleteConsumerdetails.Theimplementationoftheprotocolandcorrespondingrecordisshowninthefollowingcodesnippetthatmustbeaddedtothepersistence.cljsourcefile:

(nshelping-hands.consumer.persistence

"PersistencePortandAdapterforConsumerService"

(:require[datomic.api:asd]))

;;--------------------------------------------------

;;ConsumerPersistencePortforAdapterstoPlug-in

;;--------------------------------------------------

(defprotocolConsumerDB

"Abstractionforconsumerdatabase"

(upsert[thisidnameaddressmobileemailgeo]

"Adds/Updatesaconsumerentity")

(entity[thisidflds]

"Getsthespecifiedconsumerwithallorrequestedfields")

(delete[thisid]

"Deletesthespecifiedconsumerentity"))

;;--------------------------------------------------

;;DatomicAdapterImplementationforConsumerPort

;;--------------------------------------------------

(defn-get-entity-id

[connid]

(->(d/q'[:find?e

:in$?id

:where[?e:consumer/id?id]](d/dbconn)(strid))

ffirst))

(defn-get-entity

[connid]

(let[eid(get-entity-idconnid)]

(->>(d/entity(d/dbconn)eid)seq(into{}))))

(defrecordConsumerDBDatomic[conn]

ConsumerDB

(upsert[thisidnameaddressmobileemailgeo]

(d/transactconn

(vector(into{}(filter(compsome?val)

{:db/idid

:consumer/idid

:consumer/namename

:consumer/addressaddress

Page 244: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:consumer/mobilemobile

:consumer/emailemail

:consumer/geogeo})))))

(entity[thisidflds]

(when-let[consumer(get-entityconnid)]

(if(empty?flds)

consumer

(select-keysconsumer(mapkeywordflds)))))

(delete[thisid]

(when-let[eid(get-entity-idconnid)]

(d/transactconn[[:db.fn/retractEntityeid]]))))

Forexample,theConsumerDBprotocoldefinestheportforthePersistencelayeroftheConsumerservicethatisimplementedbytheConsumerDBDatomicrecord(https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/defrecord)thatactsasanadaptertomanagetheconsumerdatabasewithintheDatomicdatabase.Thehelping-hands.consumer.persistencenamespacealsoprovidesacreate-consumer-databaseutilityfunctiontoinitializethedatabaseandupdatetheschemaifthedatabaseiscreatedforthefirsttime,asshownhere:

(defncreate-consumer-database

"Createsaconsumerdatabaseandreturnstheconnection"

[d]

;;createandconnecttothedatabase

(let[dburi(str"datomic:mem://"d)

db(d/create-databasedburi)

conn(d/connectdburi)]

;;transactschemaifdatabasewascreated

(whendb

(d/transactconn

[{:db/ident:consumer/id

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"UniqueConsumerID"

:db/unique:db.unique/identity

:db/indextrue}

{:db/ident:consumer/name

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"DisplayNamefortheConsumer"

:db/indextrue

:db/fulltexttrue}

{:db/ident:consumer/address

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"ConsumerAddress"

:db/indextrue

:db/fulltexttrue}

{:db/ident:consumer/mobile

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"ConsumerMobileNumber"

:db/indexfalse}

{:db/ident:consumer/email

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

Page 245: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:db/doc"ConsumerEmailAddress"

:db/indextrue}

{:db/ident:consumer/geo

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"Latitude,LongitudeCSV"

:db/indexfalse}]))

(ConsumerDBDatomic.conn)))

Thecreate-consumer-databasefunctionacceptsadatabasenameasinput,suchasconsumer,andcreatesaDatomicdatabaseURI.ThedatabaseURIprefixdatomic:mem://signifiesanin-memorydatabaseofDatomicthatisusedinthisexample.Thecreate-consumer-databasefunctiontriestocreateadatabaseandifitsucceeds,ittransactstheschemafortheconsumerdatabase.ItcreatesaconnectiontothedatabaseandreturnsitwrappedasaConsumerDBDatomicrecordthatcanthenbeusedtoupsertconsumers,retrievethem,anddeletethembasedontherequirement.

Page 246: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinginterceptorsThehelping-hands.consumer.corenamespacedefinesalltheinterceptorsthatareusedfortheroutesoftheConsumermicroservice.Validationinterceptorsvalidatetheinputparametersandmakesurethatalltherequiredfieldsarepresentintherequest.Theyalsopreparetheinputparametersforthebusinesslogicinterceptorsbycreatinga:tx-datakeywithalltherequiredparameters.

Validationinterceptorsmayalsoperformtransformations,suchaschangingtheCSVofthefldsparametertoavectoroffieldnamesthatisrequiredasaninputfortheentityfunctiondefinedbyConsumerDBprotocol.TheyterminatetherequestwithaHTTP400BadRequeststatusiftherequiredparametersarenotpresent.TheyalsodefinetheerrorhandlertocatchtheexceptioninthechainandreportthemtotheclientwithaHTTP500InternalServerErrorstatus.Theimplementationofthevalidationinterceptorisshowninthefollowingcodesnippet:

(nshelping-hands.consumer.core

"InitializesHelpingHandsConsumerService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.consumer.persistence:asp]

[io.pedestal.interceptor.chain:aschain])

(:import[java.ioIOException]

[java.utilUUID]))

;;delaythecheckfordatabaseandconnection

;;tillthefirstrequesttoaccess@consumerdb

(def^:privateconsumerdb

(delay(p/create-consumer-database"consumer")))

;;--------------------------------

;;ValidationInterceptors

;;--------------------------------

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:path-params))]

(if(and(not(empty?params))

;;anyoneofmobile,emailoraddressispresent

(or(params:id)(params:mobile)(params:email)(params:address)))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

Page 247: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(assoccontext:tx-dataparams))

(chain/terminate

(assoccontext

:response{:status400

:body(str"OneofAddress,emailand"

"mobileismandatory")})))))

(defvalidate-id

{:name::validate-id

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidConsumerID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defvalidate

{:name::validate

:enter

(fn[context]

(if-let[params(->context:request:form-params)]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"Invalidparameters"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Thehelping-hands.consumer.corenamespacealsodefinestheinterceptorstogetconsumerdetails,performupsertoperations,createaconsumerwithageneratedID,anddeletetheconsumer,asshownhere:

;;--------------------------------

;;BusinessLogicInterceptors

;;--------------------------------

(defget-consumer

{:name::consumer-get

:enter

Page 248: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(fn[context]

(let[tx-data(:tx-datacontext)

entity(.entity@consumerdb(:idtx-data)(:fldstx-data))]

(if(empty?entity)

(assoccontext:response{:status404:body"Nosuchconsumer"})

(assoccontext:response{:status200

:body(jp/generate-stringentity)}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defupsert-consumer

{:name::consumer-upsert

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

id(:idtx-data)

db(.upsert@consumerdbid(:nametx-data)

(:addresstx-data)(:mobiletx-data)

(:emailtx-data)(:geotx-data))]

(if(nil?@db)

(throw(IOException.

(str"Upsertfailedforconsumer:"id)))

(assoccontext

:response{:status200

:body(jp/generate-string

(.entity@consumerdbid[]))}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defcreate-consumer

{:name::consumer-create

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

;;generatearandomIDifitisnotspecified

id(str(UUID/randomUUID))

tx-data(if(:idtx-data)tx-data(assoctx-data:idid))

;;createconsumer

db(.upsert@consumerdbid(:nametx-data)

(:addresstx-data)(:mobiletx-data)

(:emailtx-data)(:geotx-data))]

(if(nil?@db)

(throw(IOException.

(str"Upsertfailedforconsumer:"id)))

(assoccontext

:response{:status200

:body(jp/generate-string

(.entity@consumerdbid[]))}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 249: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defdelete-consumer

{:name::consumer-delete

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

db(.delete@consumerdb(:idtx-data))]

(if(nil?db)

(assoccontext:response{:status404:body"Nosuchconsumer"})

(assoccontext:response{:status200:body"Success"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 250: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TestingroutesTheroutesdefinedfortheConsumerserviceallowuserstocreateanewconsumer,queryforitsproperties,anddeleteit.Asofnow,fortheAuthinterceptor,assumethat123isavalidtokenthatissentintheheaderofeachrequest.Tostartwith,createaconsumerusingthePUT/consumers/:idroutewiththeIDsetas1,asshownhere:%curl-i-H"token:123"-XPUT-d"name=ConsumerA"http://localhost:8080/consumers/1HTTP/1.1200OK...

{"consumer/id":"1","consumer/name":"ConsumerA"}

Itcreatesanewconsumerandreturnstheconsumerentity.Toaddanewfield,usethesameAPIwiththesameconsumerIDandspecifythenewfields.Itwilldoanupsertoperationontheentityandaddthenewfields,asshownhere:

curl-i-H"token:123"-XPUT-d"[email protected]"

http://localhost:8080/consumers/1

HTTP/1.1200OK

...

{"consumer/id":"1","consumer/name":"ConsumerA","consumer/email":"[email protected]"}

Togettheentity,usetheGET/consumers/:idroute.IftheconsumerIDisnotfound,itreturnsaHTTP404NotFoundresponse:

%curl-i-H"token:123""http://localhost:8080/consumers/1"

HTTP/1.1200OK

...

{"consumer/id":"1","consumer/name":"ConsumerA","consumer/email":"[email protected]"}

%curl-i-H"token:123""http://localhost:8080/consumers/1?

flds=consumer/name,consumer/email"

HTTP/1.1200OK

...

{"consumer/name":"ConsumerA","consumer/email":"[email protected]"}

%curl-i-H"token:123""http://localhost:8080/consumers/2"

HTTP/1.1404NotFound

...

Nosuchconsumer

Page 251: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ThePOST/consumersroutecanalsobeusedtocreateaconsumerwitharandomID:

%curl-i-H"token:123"-XPOST-d"name=ConsumerX&[email protected]"

http://localhost:8080/consumers

HTTP/1.1200OK

...

{"consumer/id":"b46cdbbb-06a1-4375-9287-

2230e3ad8ded","consumer/name":"ConsumerX","consumer/email":"[email protected]"}

%curl-i-H"token:123""http://localhost:8080/consumers/b46cdbbb-06a1-4375-9287-

2230e3ad8ded"

HTTP/1.1200OK

...

{"consumer/id":"b46cdbbb-06a1-4375-9287-

2230e3ad8ded","consumer/name":"ConsumerX","consumer/email":"[email protected]"}

Todeleteaconsumer,usetheDELETE/consumers/:idroutewithanexistingconsumerID:

%curl-i-H"token:123"-XDELETE"http://localhost:8080/consumers/b46cdbbb-06a1-

4375-9287-2230e3ad8ded"

HTTP/1.1200OK

...

Success

%curl-i-H"token:123""http://localhost:8080/consumers/b46cdbbb-06a1-4375-9287-

2230e3ad8ded"

HTTP/1.1404NotFound

...

Nosuchconsumer

Page 252: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforServiceProvider

TheServiceProvidermicroserviceexposesAPIsforenduserstoregisterasaserviceprovideroftheHelpingHandsapplication.ServiceproviderscanregisteroneormoreserviceswiththeHelpingHandsapplicationthattheyarewillingtofulfillifanorderisplacedagainstit.AspertheworkflowofServiceProviderdefinedinChapter3,MicroservicesforHelpingHandsApplication,thefollowingAPIsarerequiredtocreatenewproviders,getproviderprofiles,andupdateproviderdetails:

URI Description

GET

/providers/:id/?

flds=name,mobile

Getsthedetailsoftheserviceproviderwiththespecified:idifthe:idisspecified,elseitgetsthedetailsoftheauthenticateduserregisteredasaserviceprovider.Optionally,itacceptsaCSVoffieldstobereturnedintheresponse.

PUT/providers/:id CreatesanewproviderwiththespecifiedID.POST/providers CreatesanewproviderandreturnstheID.PUT

/providers/:id/rate Addstothelatestratingsfortheprovider.DELETE

/providers/:id DeletestheproviderwiththespecifiedID.

Page 253: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Addingroutes

RoutesfortheServiceProvidermicroserviceareverysimilartotheConsumerservice.TheProviderserviceadditionallyprovidesadedicatedroutetoregisteraratingfortheProvider,asshownhere:

(defroutes#{["/providers/:id"

:get(conjcommon-interceptors`auth`core/validate-id

`core/get-provider`gen-events)

:route-name:provider-get]

["/providers/:id"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-provider`gen-events)

:route-name:provider-put]

["/providers/:id/rate"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-provider`gen-events)

:route-name:provider-rate]

["/providers"

:post(conjcommon-interceptors`auth`core/validate

`core/create-provider`gen-events)

:route-name:provider-post]

["/providers/:id"

:delete(conjcommon-interceptors`auth`core/validate-id

`core/delete-provider`gen-events)

:route-name:provider-delete]})

Page 254: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefiningDatomicschema

TheServiceProvidermicroserviceusesDatomicasthelocaldatabasetostoretheproviderdetails.Theschemafortheproviderdatabaseconsistsofthefollowingattributes:

:db/ident :db/valueType :db/cardinality :db/index :db/fulltext

:provider/id :db.type/string :db.cardinality/one true false

:provider/name :db.type/string :db.cardinality/one true true

:provider/mobile :db.type/string :db.cardinality/one false -

:provider/since :db.type/long :db.cardinality/one false -

:provider/rating :db.type/float :db.cardinality/many false -

Page 255: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatingapersistenceadapter

Persistenceprotocolconsistsofupsert,entity,anddeletefunctions,similartotheConsumerservice,thataredefinedinthepersistence.cljsourcefile,asfollows:

(nshelping-hands.provider.persistence

"PersistencePortandAdapterforProviderService"

(:require[datomic.api:asd]))

;;--------------------------------------------------

;;ProviderPersistencePortforAdapterstoPlug-in

;;--------------------------------------------------

(defprotocolProviderDB

"Abstractionforproviderdatabase"

(upsert[thisidnamemobilesincerating]

"Adds/Updatesaproviderentity")

(entity[thisidflds]

"Getsthespecifiedproviderwithallorrequestedfields")

(delete[thisid]

"Deletesthespecifiedproviderentity"))

;;--------------------------------------------------

;;DatomicAdapterImplementationforProviderPort

;;--------------------------------------------------

(defn-get-entity-id

[connid]

(->(d/q'[:find?e

:in$?id

:where[?e:provider/id?id]](d/dbconn)(strid))

ffirst))

(defn-get-entity

[connid]

(let[eid(get-entity-idconnid)]

(->>(d/entity(d/dbconn)eid)seq(into{}))))

(defrecordProviderDBDatomic[conn]

ProviderDB

(upsert[thisidnamemobilesincerating]

(d/transactconn

(vector(into{}(filter(compsome?val)

{:db/idid

:provider/idid

:provider/namename

:provider/mobilemobile

:provider/sincesince

:provider/ratingrating})))))

Page 256: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(entity[thisidflds]

(when-let[provider(get-entityconnid)]

(if(empty?flds)

provider

(select-keysprovider(mapkeywordflds)))))

(delete[thisid]

(when-let[eid(get-entity-idconnid)]

(d/transactconn[[:db.fn/retractEntityeid]]))))

Thehelping-hands.provider.persistencenamespacealsoprovidesacreate-provider-databaseutilityfunctiontoinitializethedatabaseandupdatetheschemaifthedatabaseiscreatedforthefirsttime:

(defncreate-provider-database

"Createsaproviderdatabaseandreturnstheconnection"

[d]

;;createandconnecttothedatabase

(let[dburi(str"datomic:mem://"d)

db(d/create-databasedburi)

conn(d/connectdburi)]

;;transactschemaifdatabasewascreated

(whendb

(d/transactconn

[{:db/ident:provider/id

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"UniqueProviderID"

:db/unique:db.unique/identity

:db/indextrue}

{:db/ident:provider/name

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"DisplayNamefortheProvider"

:db/indextrue

:db/fulltexttrue}

{:db/ident:provider/mobile

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"ProviderMobileNumber"

:db/indexfalse}

{:db/ident:provider/since

:db/valueType:db.type/long

:db/cardinality:db.cardinality/one

:db/doc"ProviderActiveSinceEPOCHtime"

:db/indexfalse}

{:db/ident:provider/rating

:db/valueType:db.type/float

:db/cardinality:db.cardinality/many

:db/doc"Listofratings"

:db/indexfalse}]))

(ProviderDBDatomic.conn)))

Page 257: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatinginterceptors

Thehelping-hands.provider.corenamespacedefinesalltheinterceptorsthatareusedfortheroutesoftheProvidermicroservice.Validationinterceptorsvalidatetheinputparametersandmakesurethatalltherequiredfieldsarepresentintherequest.ValidationinterceptorsforProviderroutesworkexactlyinthesamewayasthatoftheConsumermicroserviceroutes.Additionally,theyvalidatethedatatypeoftheratingandsincefieldstomakesurethatthebusinessmodelgetsthevaluesintheformatexpectedbytheDatomicdatabase.Theimplementationofvalidationinterceptorsisshowninthefollowingcodesnippet:

(nshelping-hands.provider.core

"InitializesHelpingHandsProviderService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.provider.persistence:asp]

[io.pedestal.interceptor.chain:aschain])

(:import[java.ioIOException]

[java.utilUUID]))

;;delaythecheckfordatabaseandconnection

;;tillthefirstrequesttoaccess@providerdb

(def^:privateproviderdb

(delay(p/create-provider-database"provider")))

;;--------------------------------

;;ValidationInterceptors

;;--------------------------------

(defn-validate-rating-ts

"Validatestheratingandtimestamp"

[context]

(let[rating(->context:request:form-params:rating)

since_ts(->context:request:form-params:since)]

(try

(let[context(if(not(nil?rating))

(assoc-incontext[:request:form-params:rating]

(Float/parseFloatrating))context)

context(if(not(nil?since_ts))

(assoc-incontext[:request:form-params:since]

(Long/parseLongsince_ts))context)]

context)

(catchExceptionenil))))

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

Page 258: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(->context:request:path-params))

ctx(validate-rating-tscontext)

params(if(not(nil?ctx))

(assocparams

:rating(->ctx:request:form-params:rating)

:since(->ctx:request:form-params:since)))]

(if(and(not(empty?params))

(not(nil?ctx))

;;anyoneofidormobile

(or(params:id)(params:mobile)))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

(assoccontext:tx-dataparams))

(chain/terminate

(assoccontext

:response{:status400

:body(str"ID,mobileismandatory"

"andrating,sincemustbeanumber")})))))

(defvalidate-id

{:name::validate-id

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidProviderID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defvalidate

{:name::validate

:enter

(fn[context]

(if-let[params(->context:request:form-params)]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"Invalidparameters"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 259: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Otherinterceptors,suchasget-provider,upsert-provider,create-provider,anddelete-provider,aresimilartointerceptorsdefinedfortheroutesoftheConsumerservice,asshownhere:

;;--------------------------------

;;BusinessLogicInterceptors

;;--------------------------------

(defget-provider

{:name::provider-get

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

entity(.entity@providerdb(:idtx-data)(:fldstx-data))]

(if(empty?entity)

(assoccontext:response{:status404:body"Nosuchprovider"})

(assoccontext:response{:status200

:body(jp/generate-stringentity)}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defupsert-provider

{:name::provider-upsert

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

id(:idtx-data)

db(.upsert@providerdbid(:nametx-data)

(:mobiletx-data)(:sincetx-data)

(:ratingtx-data))]

(if(nil?@db)

(throw(IOException.

(str"Upsertfailedforprovider:"id)))

(assoccontext

:response{:status200

:body(jp/generate-string

(.entity@providerdbid[]))}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defcreate-provider

{:name::provider-create

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

;;generatearandomIDifitisnotspecified

id(str(UUID/randomUUID))

tx-data(if(:idtx-data)tx-data(assoctx-data:idid))

;;createprovider

db(.upsert@providerdbid(:nametx-data)

(:mobiletx-data)(:sincetx-data)

Page 260: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(:ratingtx-data))]

(if(nil?@db)

(throw(IOException.

(str"Upsertfailedforprovider:"id)))

(assoccontext

:response{:status200

:body(jp/generate-string

(.entity@providerdbid[]))}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defdelete-provider

{:name::provider-delete

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

db(.delete@providerdb(:idtx-data))]

(if(nil?db)

(assoccontext:response{:status404:body"Nosuchprovider"})

(assoccontext:response{:status200:body"Success"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 261: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

TestingroutesTheroutesdefinedfortheProviderserviceallowuserstocreateanewprovider,queryforitsproperties,anddeleteit.Asofnow,fortheAuthinterceptor,assumethat123isavalidtokenthatissentintheheaderofeachrequest.Tostartwith,createaproviderusingthePUT/providers/:idroutewithIDsetas1:%curl-i-H"token:123"-XPUT-d"name=ProviderA"http://localhost:8080/providers/1HTTP/1.1200OK...

{"provider/id":"1","provider/name":"ProviderA"}

Toaddarating,usethePUT/providers/:id/rateroutewiththesameIDasthatofProviderA:

%curl-i-H"token:123"-XPUT-d"rating=5.0"http://localhost:8080/providers/1/rate

HTTP/1.1200OK

...

{"provider/id":"1","provider/name":"ProviderA","provider/rating":[5.0]}

Togettheprovider,usetheGET/providers/:idroute.IftheproviderIDisnotfound,itreturnsaHTTP404NotFoundresponse:

%curl-i-H"token:123""http://localhost:8080/providers/1"

HTTP/1.1200OK

...

{"provider/id":"1","provider/name":"ProviderA","provider/rating":[5.0]}

%curl-i-H"token:123""http://localhost:8080/providers/2"

HTTP/1.1404NotFound

...

Nosuchprovider

Todeleteaprovider,usetheDELETE/providers/:idroutewithanexistingproviderID:

%curl-i-H"token:123"-XDELETE"http://localhost:8080/providers/1"

HTTP/1.1200OK

...

Success

Page 262: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

%curl-i-H"token:123""http://localhost:8080/providers/1"

HTTP/1.1404NotFound

...

Nosuchprovider

Page 263: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforServices

TheServicemicroservicemanagesthelistofservicesofferedbytheserviceprovidersviatheHelpingHandsapplication.ItexposesAPIsforserviceproviderstoregisterservicesthatareofferedbythem.AspertheworkflowofService,definedinChapter3,MicroservicesforHelpingHandsApplication,thefollowingAPIsarerequiredtocreateanewservice,getservicedetails,andupdateservicedetails.EachservicemustalreadyhavetheserviceproviderregisteredwiththeHelpingHandsapplicationviatheServiceProvidermicroservice.Sinceonlyanexistingserviceprovidercanregisteraservice,theserviceproviderIDisretrievedbytheAuthtokenreceivedwiththerequestcallingtheServiceAPItocreateanewservice:

URI Description

GET

/services/:id/?

flds=name,mobile

Getsthedetailsoftheservicewiththespecified:idifthe:idisspecified.Optionally,itacceptsaCSVoffieldstobereturnedintheresponse.

PUT/services/:id CreatesanewservicewiththespecifiedID.POST/services CreatesanewserviceandreturnstheID.PUT

/services/:id/rate Addstothelatestratingsfortheservice.DELETE

/services/:id DeletestheservicewiththespecifiedID.

Page 264: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Addingroutes

RoutesforServiceareverysimilartotheProviderservice.Italsodefinesroutestocreate,modify,rate,anddeleteservices.TheroutetocreateaserviceexpectsaproviderIDasamandatoryparametertomakesurethateachserviceisassociatedwithaprovideratthetimeofcreation.Also,anychangeinproviderIDusingthePUT/services/:idrouteisvalidatedagainsttheProviderservicetomakesurethatthespecifiedproviderexistsandisregisteredwiththeHelpingHandsapplication.Theroutesareshowninthefollowingcodesnippet:

;;Tabularroutes

(defroutes#{["/services/:id"

:get(conjcommon-interceptors`auth`core/validate-id-get

`core/get-service`gen-events)

:route-name:service-get]

["/services/:id"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-service`gen-events)

:route-name:service-put]

["/services/:id/rate"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-service`gen-events)

:route-name:service-rate]

["/services"

:post(conjcommon-interceptors`auth`core/validate

`core/create-service`gen-events)

:route-name:service-post]

["/services/:id"

:delete(conjcommon-interceptors`auth`core/validate-id-get

`core/delete-service`gen-events)

:route-name:service-delete]})

Page 265: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefiningaDatomicschemaTheServicemicroserviceusesDatomicasthelocaldatabasetostoretheservicedetails.ServicemaintainsaproviderIDaswell.Althoughintheschemaitisdefinedastype:db.type/string;ifacommondatabaseisusedfortheproviderandservicethenitisrecommendedtodefinetheproviderIDoftype:db.type/reftomakebetteruseofDatomicentityreferences.Theschemafortheservicedatabaseconsistsofthefollowingattributes:

:db/ident :db/valueType :db/cardinality :db/index :db/fulltext

:service/id :db.type/string :db.cardinality/one true false

:service/type :db.type/string :db.cardinality/one true true

:service/provider :db.type/string :db.cardinality/one false -

:service/area :db.type/string :db.cardinality/many true true

:service/cost :db.type/float :db.cardinality/one false -

:service/rating :db.type/float :db.cardinality/many false -

:service/status :db.type/string :db.cardinality/one false -

Page 266: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ThereisalsoaGeoLocationfieldmentionedinthedatamodeloftheServicedatabaseinChapter3,MicroservicesforHelpingHandsApplication.ThisfieldisaderivedfieldthatiscomputedandstoredbytheLookupserviceinsteadofbeingstoredintheServicedatabase.Thisfieldisusedonlyforgeolocation-basedqueriesthattheLookupservicewillbehandling.

Page 267: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatingapersistenceadapter

PersistenceprotocolServiceDBconsistsofupsert,entity,anddeletefunctions,similartotheProviderservice,thataredefinedinthepersistence.cljsourcefile,asshownhere:

(nshelping-hands.service.persistence

"PersistencePortandAdapterforService"

(:require[datomic.api:asd]))

;;--------------------------------------------------

;;ServicePersistencePortforAdapterstoPlug-in

;;--------------------------------------------------

(defprotocolServiceDB

"Abstractionforservicedatabase"

(upsert[thisidtypeproviderareacostratingstatus]

"Adds/Updatesaserviceentity")

(entity[thisidflds]

"Getsthespecifiedservicewithallorrequestedfields")

(delete[thisid]

"Deletesthespecifiedserviceentity"))

;;--------------------------------------------------

;;DatomicAdapterImplementationforServicePort

;;--------------------------------------------------

(defn-get-entity-id

[connid]

(->(d/q'[:find?e

:in$?id

:where[?e:service/id?id]](d/dbconn)(strid))

ffirst))

(defn-get-entity

[connid]

(let[eid(get-entity-idconnid)]

(->>(d/entity(d/dbconn)eid)seq(into{}))))

(defrecordServiceDBDatomic[conn]

ServiceDB

(upsert[thisidtypeproviderareacostratingstatus]

(d/transactconn

(vector(into{}(filter(compsome?val)

{:db/idid

:service/idid

:service/typetype

:service/providerprovider

:service/areaarea

Page 268: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:service/costcost

:service/ratingrating

:service/statusstatus})))))

(entity[thisidflds]

(when-let[service(get-entityconnid)]

(if(empty?flds)

service

(select-keysservice(mapkeywordflds)))))

(delete[thisid]

(when-let[eid(get-entity-idconnid)]

(d/transactconn[[:db.fn/retractEntityeid]]))))

Thehelping-hands.service.persistencenamespacealsoprovidesacreate-service-databaseutilityfunctiontoinitializethedatabaseandupdatetheschemaifthedatabaseiscreatedforthefirsttime,asshownhere:

(defncreate-service-database

"Createsaservicedatabaseandreturnstheconnection"

[d]

;;createandconnecttothedatabase

(let[dburi(str"datomic:mem://"d)

db(d/create-databasedburi)

conn(d/connectdburi)]

;;transactschemaifdatabasewascreated

(whendb

(d/transactconn

[{:db/ident:service/id

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"UniqueServiceID"

:db/unique:db.unique/identity

:db/indextrue}

{:db/ident:service/type

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"TypeofService"

:db/indextrue

:db/fulltexttrue}

{:db/ident:service/provider

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"AssociatedServiceProviderID"

:db/indexfalse}

{:db/ident:service/area

:db/valueType:db.type/string

:db/cardinality:db.cardinality/many

:db/doc"ServiceAreas/Locality"

:db/indextrue

:db/fulltexttrue}

{:db/ident:service/cost

:db/valueType:db.type/float

:db/cardinality:db.cardinality/one

:db/doc"HourlyCost"

:db/indexfalse}

{:db/ident:service/rating

:db/valueType:db.type/float

:db/cardinality:db.cardinality/many

:db/doc"Listofratings"

:db/indexfalse}

Page 269: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

{:db/ident:service/status

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"StatusofService(A/NA/D)"

:db/indexfalse}]))

(ServiceDBDatomic.conn)))

Page 270: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinginterceptorsThehelping-hands.service.corenamespacedefinesalltheinterceptorsthatareusedfortheroutesoftheServicemicroservice.SincebusinessmodelinterceptorsofServiceareexactlythesameasthoseoftheProviderserviceanddependonlyontheValidationinterceptors,let'sfocusonlyontheinterceptorsthatvalidatetheinputparametersforServiceroutes.Theimplementationofthevalidationinterceptorisshowninthefollowingcodesnippet:

(nshelping-hands.service.core

"InitializesHelpingHandsServiceService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.service.persistence:asp]

[io.pedestal.interceptor.chain:aschain])

(:import[java.ioIOException]

[java.utilUUID]))

;;delaythecheckfordatabaseandconnection

;;tillthefirstrequesttoaccess@servicedb

(def^:privateservicedb

(delay(p/create-service-database"service")))

;;--------------------------------

;;ValidationInterceptors

;;--------------------------------

(defn-validate-rating-cost

"Validatestheratingandcost"

[context]

(let[rating(->context:request:form-params:rating)

cost(->context:request:form-params:cost)]

(try

(let[context(if(not(nil?rating))

(assoc-incontext[:request:form-params:rating]

(Float/parseFloatrating))context)

context(if(not(nil?cost))

(assoc-incontext[:request:form-params:cost]

(Float/parseFloatcost))context)]

context)

(catchExceptionenil))))

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:path-params))

ctx(validate-rating-costcontext)

params(if(not(nil?ctx))

(assocparams

:rating(->ctx:request:form-params:rating)

:cost(->ctx:request:form-params:cost)))]

(if(and(not(empty?params))

Page 271: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(not(nil?ctx))

(params:id)(params:type)(params:provider)

(params:area)(params:cost)

(contains?#{"A""NA""D"}(params:type))

(provider-exists?(params:provider)))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

(assoccontext:tx-dataparams))

(chain/terminate

(assoccontext

:response{:status400

:body(str"ID,type,provider,areaandcostismandatory"

"andrating,costmustbeanumberwithtype"

"havingoneofvaluesA,NAorD")})))))

(defvalidate-id

{:name::validate-id

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidServiceID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defvalidate-id-get

{:name::validate-id-get

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:path-params))]

(if(and(not(empty?params))

(params:id))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

(assoccontext:tx-dataparams))

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidServiceID"}))))

(chain/terminate

Page 272: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(assoccontext

:response{:status400

:body"InvalidServiceID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defvalidate

{:name::validate

:enter

(fn[context]

(if-let[params(->context:request:form-params)]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"Invalidparameters"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

ForService,thevalidationrulealsoincludesvalidatingagivenproviderIDagainstanexternalProviderServicetomakesurethattheprovideroftheserviceisalreadyregistered.Todoso,thevalidationinterceptorofServicemakesanexternalAPIcallasynchronouslyviatheprovider-exists?functionandpassesPedestalcontexttobusinesslogicinterceptorsonlywhenitfindsthattheproviderIDisvalid.

clj-http(https://github.com/dakrone/clj-http)isaClojurelibrarythatiswidelyusedtocreateHTTPclientstomakeAPIcallstoexternalservices.TomakeaGETcalltoanexternalservicelikethatofGET/providers/:id,seeclj-httpGET(https://github.com/dakrone/clj-http#get).

Page 273: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Testingroutes

TheroutesdefinedforServiceallowuserstocreateanewservice,queryforitsproperties,anddeleteit.Asofnow,forAuthinterceptor,assumethat123isavalidtokenthatissentintheheaderofeachrequest.HereareasampleofcURLrequeststocreate,query,anddeleteaserviceofferedbytheHelpingHandsapplication:

;;Createanewservicewithrequiredparameters

%curl-i-H"token:123"-XPUT-d"type=A&provider=1&area=bangalore&cost=250"

http://localhost:8080/services/1

HTTP/1.1200OK

...

{"service/id":"1","service/type":"A","service/provider":"1","service/area":

["bangalore"],"service/cost":250.0}

;;GetservicepropertiesbyID

%curl-i-H"token:123"http://localhost:8080/services/1

HTTP/1.1200OK

...

{"service/id":"1","service/type":"A","service/provider":"1","service/area":

["bangalore"],"service/cost":250.0}

;;Deletetheservice

%curl-i-H"token:123"-XDELETEhttp://localhost:8080/services/1

HTTP/1.1200OK

...

Success

;;Validateservicenolongerexists

%curl-i-H"token:123"http://localhost:8080/services/1

HTTP/1.1404NotFound

...

Nosuchservice

Page 274: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforOrder

TheOrderServicereceivestherequestfromServiceConsumerstocreateaneworderfortheserviceprovidedbyaparticularserviceprovider.ItexposesthefollowingAPIsforconsumerstocreateaneworder,getorderdetails,andgetthelistofordersplacedbythem.ItalsoallowstheconsumerstoratetheOrderbasedonthequalityofservicereceived.TocreateanewOrder,APIsexpectaserviceIDandproviderIDtobespecifiedalongwiththerequireddetailssuchastimeslot,andmore.TheconsumerIDispickedfromtheAuthtokenthatisreceivedasapartofrequestheaders.TheIDsspecifiedforServiceandServiceProvidermustalreadyberegisteredwiththeHelpingHandsapplicationviatheServiceandServiceProvidermicroservices.CreationofanOrderalsomakessurethattherequestedserviceisofferedwithinthevicinityoftheconsumerbasedonthegeolocationoftheconsumerandservicebeingrequested:

URI DescriptionGET/orders/?

flds=cost,status Getsalltheordersplacedbytheauthenticatedconsumer.

GET

/orders/:id/?

flds=name,mobile

Getsthedetailsoftheorderwiththespecified:idandplacedbytheauthenticatedconsumer.Optionally,itacceptsaCSVoffieldstobereturnedintheresponse.

PUT/orders/:idCreatesaneworderwiththespecifiedIDfortheauthenticatedconsumer.

POST/ordersCreatesaneworderfortheauthenticatedconsumerandreturnstheID.

PUT

/orders/:id/rate

Addstothelatestratingsfortheorder.ConsumerIDoftheordermustmatchtheauthenticatedconsumerID.

DELETE

/orders/:id

DeletestheorderwiththespecifiedIDfortheauthenticatedconsumer.ConsumerIDoftheordermustmatchtheauthenticatedconsumerID.

Page 275: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 276: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Addingroutes

TheOrderServiceallowsconsumerstocreateneworders,modifythem,rate,anddeletethem.ItalsoallowsanauthenticatedusertogetalltheordersplacedbytheauthenticateduserID.Theroutesrequiredforthisserviceareshowninthefollowingcodesnippet:

;;Tabularroutes

(defroutes#{["/orders/:id"

:get(conjcommon-interceptors`auth`core/validate-id-get

`core/get-order`gen-events)

:route-name:order-get]

["/orders"

:get(conjcommon-interceptors`auth`core/validate-all-orders

`core/get-all-orders`gen-events)

:route-name:order-get-all]

["/orders/:id"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-order`gen-events)

:route-name:order-put]

["/orders/:id/rate"

:put(conjcommon-interceptors`auth`core/validate-id

`core/upsert-order`gen-events)

:route-name:order-rate]

["/orders"

:post(conjcommon-interceptors`auth`core/validate

`core/create-order`gen-events)

:route-name:order-post]

["/orders/:id"

:delete(conjcommon-interceptors`auth`core/validate-id-get

`core/delete-order`gen-events)

:route-name:order-delete]})

Page 277: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefiningDatomicschema

TheOrdermicroserviceusesDatomicasthelocaldatabasetostoretheorderdetails.Theschemafortheorderdatabaseconsistsofthefollowingattributes:

:db/ident :db/valueType :db/cardinality :db/index :db/fulltext :db/unique

:order/id :db.type/string :db.cardinality/one true false :db.unique/identity

:order/service :db.type/string :db.cardinality/one false - -

:order/provider :db.type/string :db.cardinality/one false - -

:order/consumer :db.type/string :db.cardinality/one false - -

:order/cost :db.type/float :db.cardinality/one false - -

:order/start :db.type/long :db.cardinality/one false - -

:order/end :db.type/long :db.cardinality/one false - -

:order/rating :db.type/float :db.cardinality/many false - -

:order/status :db.type/string :db.cardinality/one false - -

Page 278: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 279: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatingapersistenceadapter

PersistenceprotocolOrderDBconsistsofupsert,entity,anddeletefunctions,similartotheProviderservice.Additionally,itdefinesanordersfunctionthatcanlistalltheordersofthegivenauthenticatedconsumerID,asshownhere:

(nshelping-hands.order.persistence

"PersistencePortandAdapterforOrder"

(:require[datomic.api:asd]))

;;--------------------------------------------------

;;OrderPersistencePortforAdapterstoPlug-in

;;--------------------------------------------------

(defprotocolOrderDB

"Abstractionfororderdatabase"

(upsert[thisidserviceproviderconsumer

coststartendratingstatus]

"Adds/Updatesanorderentity")

(entity[thisidflds]

"Getsthespecifiedorderwithallorrequestedfields")

(orders[thisuidflds]

"Getsalltheordersoftheauthenticateduserwithallorrequestedfields")

(delete[thisid]

"Deletesthespecifiedorderentity"))

;;--------------------------------------------------

;;DatomicAdapterImplementationforOrderPort

;;--------------------------------------------------

(defn-get-entity-id

[connid]

(->(d/q'[:find?e

:in$?id

:where[?e:order/id?id]](d/dbconn)(strid))

ffirst))

(defn-get-entity

[connid]

(let[eid(get-entity-idconnid)]

(->>(d/entity(d/dbconn)eid)seq(into{}))))

(defn-get-entity-uid

[connuid]

(->>(d/q'[:find?e

:in$?id

:where[?e:order/consumer?id]](d/dbconn)(struid))

(into[])flatten))

Page 280: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defn-get-all-entities

[connuid]

(let[eids(get-entity-uidconnuid)]

(map#(->>(d/entity(d/dbconn)%)seq(into{}))eids)))

(defrecordOrderDBDatomic[conn]

OrderDB

(upsert[thisidserviceproviderconsumer

coststartendratingstatus]

(d/transactconn

(vector(into{}(filter(compsome?val)

{:db/idid

:order/idid

:order/serviceservice

:order/providerprovider

:order/consumerconsumer

:order/costcost

:order/startstart

:order/endend

:order/ratingrating

:order/statusstatus})))))

(entity[thisidflds]

(when-let[order(get-entityconnid)]

(if(empty?flds)

order

(select-keysorder(mapkeywordflds)))))

(orders[thisuidflds]

(when-let[orders(get-all-entitiesconnuid)]

(if(empty?flds)

orders

(map#(select-keys%(mapkeywordflds))orders))))

(delete[thisid]

(when-let[eid(get-entity-idconnid)]

(d/transactconn[[:db.fn/retractEntityeid]]))))

Theget-all-entitiesfunctionqueriestheDatomicdatabaseforalltheordersthathave:order/consumersettothegivenconsumerIDthatisprovidedasaparametertotheendpointthatrequestsalltheordersfortheconsumer.Italsoallowstopickonlythespecifiedfieldsacrosstheorders.Thehelping-hands.order.persistencenamespacealsoprovidesacreate-order-databaseutilityfunctiontoinitializethedatabaseandupdatetheschemaifthedatabaseiscreatedforthefirsttime,asshownhere:

(defncreate-order-database

"Createsaorderdatabaseandreturnstheconnection"

[d]

;;createandconnecttothedatabase

(let[dburi(str"datomic:mem://"d)

db(d/create-databasedburi)

conn(d/connectdburi)]

;;transactschemaifdatabasewascreated

(whendb

(d/transactconn

[{:db/ident:order/id

Page 281: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"UniqueOrderID"

:db/unique:db.unique/identity

:db/indextrue}

{:db/ident:order/service

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"AssociatedServiceID"

:db/indexfalse}

{:db/ident:order/provider

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"AssociatedServiceProviderID"

:db/indexfalse}

{:db/ident:order/consumer

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"AssociatedConsumerID"}

{:db/ident:order/cost

:db/valueType:db.type/float

:db/cardinality:db.cardinality/one

:db/doc"HourlyCost"

:db/indexfalse}

{:db/ident:order/start

:db/valueType:db.type/long

:db/cardinality:db.cardinality/one

:db/doc"StartTime(EPOCH)"

:db/indexfalse}

{:db/ident:order/end

:db/valueType:db.type/long

:db/cardinality:db.cardinality/one

:db/doc"EndTime(EPOCH)"

:db/indexfalse}

{:db/ident:order/rating

:db/valueType:db.type/float

:db/cardinality:db.cardinality/many

:db/doc"Listofratings"

:db/indexfalse}

{:db/ident:order/status

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"StatusofOrder(O/I/D/C)"

:db/indexfalse}]))

(OrderDBDatomic.conn)))

Page 282: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinginterceptorsThehelping-hands.order.corenamespacedefinesalltheinterceptorsthatareusedfortheroutesoftheOrdermicroservice.TheAuthinterceptoristhegenericinterceptorthatreadsthetokenandupdatestheuserIDfield:uidfortheOrderroutestogetalltheordersforanauthenticateduser.Forsimplicityoftheimplementation,theAuthinterceptorassumesthatthetokenpassedintheheaderissettotheconsumerID.

ThevalidationinterceptorforOrderroutesvalidatesbothserviceIDandtheproviderIDoftheordertomakesurethatbothproviderandserviceareregisteredwiththeHelpingHandsapplicationandthesameproviderprovidesthespecifiedservice.Theservice-exists?,provider-exists?,andconsumer-exists?functionsvalidatetheservice,provider,andconsumer,respectively.Additionally,thevalidationinterceptorchecksfortherightvalueofstatusandthevalueofrating,cost,start,andendtobeoftypenumber,asshowninthefollowingcodesnippet.TheimplementationofthesefunctionsaresameasthatoftheConsumer,Order,andServicemicroservicesimplementationsexplainedearlier:

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:path-params))

ctx(validate-rating-cost-tscontext)

params(if(not(nil?ctx))

(assocparams

:rating(->ctx:request:form-params:rating)

:cost(->ctx:request:form-params:cost)

:start(->ctx:request:form-params:start)

:end(->ctx:request:form-params:end)))]

(if(and(not(empty?params))

(not(nil?ctx))

(params:id)(params:service)(params:provider)

(params:consumer)(params:cost)(params:status)

(contains?#{"O""I""D""C"}(params:status))

(service-exists?(params:service))

(provider-exists?(params:provider))

(consumer-exists?(params:consumer)))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

(assoccontext:tx-dataparams))

Page 283: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(chain/terminate

(assoccontext

:response{:status400

:body(str"ID,service,provider,consumer,"

"costandstatusismandatory.start/end,"

"ratingandcostmustbeanumberwithstatus"

"havingoneofvaluesO,I,DorC")})))))

(defvalidate-id

{:name::validate-id

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidOrderID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

(defvalidate-id-get

{:name::validate-id-get

:enter

(fn[context]

(if-let[id(or(->context:request:form-params:id)

(->context:request:query-params:id)

(->context:request:path-params:id))]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:path-params))]

(if(and(not(empty?params))

(params:id))

(let[flds(if-let[fl(:fldsparams)]

(maps/trim(s/splitfl#","))

(vector))

params(assocparams:fldsflds)]

(assoccontext:tx-dataparams))

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidOrderID"}))))

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidOrderID"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 284: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defvalidate-all-orders

{:name::validate-all-orders

:enter

(fn[context]

(if-let[params(->context:tx-data)]

;;GetuserIDfromauthuid

(assoc-incontext[:tx-data:flds]

(if-let[fl(->context:request:query-params:flds)]

(maps/trim(s/splitfl#","))

(vector)))

(chain/terminate

(assoccontext

:response{:status400

:body"Invalidparameters"}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Thevalidate-id-getinterceptorisusedfortheGET/orders/:idrequestsandvalidatesonlytheorderIDandtheorderfieldsparameter.Similarly,thevalidate-all-ordersinterceptorisusedwiththeGET/ordersroutetogetalltheordersoftheauthenticatedconsumer.TheimplementationofinterceptorsforthebusinesslogicoftheOrderserviceissimilartothatofthepreviousimplementationofConsumer,Provider,andService.Additionally,theOrderservicedefinesinterceptorstogetallordersoftheauthenticatedconsumerthatusetheordersfunctionoftheOrderDBprotocol,asshownhere:

(nshelping-hands.order.core

"InitializesHelpingHandsOrderService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.order.persistence:asp]

[io.pedestal.interceptor.chain:aschain])

(:import[java.ioIOException]

[java.utilUUID]))

;;delaythecheckfordatabaseandconnection

;;tillthefirstrequesttoaccess@orderdb

(def^:privateorderdb

(delay(p/create-order-database"order")))

(defget-all-orders

{:name::order-get-all

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

entity(.orders@orderdb(:uidtx-data)(:fldstx-data))]

(if(empty?entity)

(assoccontext:response{:status404:body"Nosuchorders"})

(assoccontext:response{:status200

:body(jp/generate-stringentity)}))))

Page 285: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Page 286: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Testingroutes

TotesttheroutesoftheOrderservice,createoneormoreordersbythesameconsumerIDandtrytoqueryalltheordersforthesameconsumerID.Forsimplicity,theroutesassumethevalueofthetokenspecifiedintheheaderastheconsumerIDfortheGET/ordersroute.HereisalistofcURLrequeststodemonstratetheprocessofcreating,querying,anddeletingtheorders:

;;AddanorderforConsumerwithID1

%curl-i-H"token:1"-XPUT-d"service=1&provider=1&consumer=1&cost=500&status=O"

http://localhost:8080/orders/1

HTTP/1.1200OK

...

{"order/id":"1","order/service":"1","order/provider":"1","order/consumer":"1","order/cost":500.0,"order/status":"O"}

;;AddanotherorderforConsumerwithID1

%curl-i-H"token:1"-XPUT-d"service=2&provider=2&consumer=1&cost=250&status=O"

http://localhost:8080/orders/2

HTTP/1.1200OK

...

{"order/id":"2","order/service":"2","order/provider":"2","order/consumer":"1","order/cost":250.0,"order/status":"O"}

;;AddanorderforConsumerwithID2

%curl-i-H"token:2"-XPUT-d"service=1&provider=1&consumer=2&cost=250&status=I"

http://localhost:8080/orders/3

HTTP/1.1200OK

...

{"order/id":"3","order/service":"1","order/provider":"1","order/consumer":"2","order/cost":250.0,"order/status":"I"}

;;GetallordersofconsumerwithID1

%curl-i-H"token:1"http://localhost:8080/orders

HTTP/1.1200OK

...

[{"order/id":"1","order/service":"1","order/provider":"1","order/consumer":"1","order/cost":500.0,"order/status":"O"},

{"order/id":"2","order/service":"2","order/provider":"2","order/consumer":"1","order/cost":250.0,"order/status":"O"}]

;;GetallordersofconsumerwithID2

%curl-i-H"token:2"http://localhost:8080/orders

HTTP/1.1200OK

...

[{"order/id":"3","order/service":"1","order/provider":"1","order/consumer":"2","order/cost":250.0,"order/status":"I"}]

;;GetallordersofconsumerwithID1withspecificfields

%curl-i-H"token:1""http://localhost:8080/orders?flds=order/service,order/status"

...

Page 287: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

[{"order/service":"1","order/status":"O"},{"order/service":"2","order/status":"O"}]

;;DeleteorderwithID2

%curl-i-H"token:123"-XDELETEhttp://localhost:8080/orders/2

HTTP/1.1200OK

...

Success

;;MakesurethatOrderwithID2nolongerexists

curl-i-H"token:123"-XDELETEhttp://localhost:8080/orders/2

HTTP/1.1404NotFound

...

Nosuchorder

;;CheckordersforconsumerwithID1doesnotlistOrderwithID2now

curl-i-H"token:1""http://localhost:8080/orders?flds=order/service,order/status"

HTTP/1.1200OK

...

[{"order/service":"1","order/status":"O"}]%

;;DeletetheorderwithID3thatwastheonlyorderforconsumerwithID2

%curl-i-H"token:123"-XDELETEhttp://localhost:8080/orders/3

HTTP/1.1200OK

...

Success

;;MakesuretherearenootherordersleftforconsumerwithID2

%curl-i-H"token:2"http://localhost:8080/orders

HTTP/1.1404NotFound

...

Nosuchorders

Page 288: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingamicroserviceforLookupTheLookupserviceisusedtosearchforservicesbytype,geolocation,andavailability.ItsubscribestotheeventsgeneratedbytheConsumer,Provider,Service,andOrdermicroservicesasanObserverandkeepsadenormalizeddatasetthatisfastertoqueryforrequiredServices.TheLookupservicealsoaddslongitudeandlatitudefortheServicesandConsumersthatisderivedfromtheiraddressandserviceareaorlocality.Togetthelongitudeandlatitudefromtheservicearea,itdependsonanexternalAPI.TheLookupserviceprovidesthefollowingAPIsforconsumerstosearchforaserviceandalsofiltertheservicesbytype,ratings,andproviders:

URI Description

GET/lookup/?q=queryFiltersalltheservicesbasedonthespecifiedquery.

GET/lookup/?q=query&type=typeFiltersalltheservicesofagiventypebasedonthespecifiedquery.

GET/lookup/geo/?

tl=40.73,-74.1&br=40.01,-71.12

Looksuptheservicebythegivenlatitude-longitudesetfortop-left(tl)andbottom-right(br)boundingboxpointsorwithinaradiusofapre-defineddistancefromtheconsumerlocation.

GET/validate/:service/?

tl=40.73,-74.1&br=40.01,-71.12

Validatesifthespecified:serviceIDiswithintheboundingboxregionspecifiedbythetop-left(tl)andbottomright(br)boundingboxpointsorwithinaradiusofapre-defineddistancefromtheconsumerlocation.

GET/status

Getsthecurrentstatusoftheeventsincludingthenumberofordersplacedovertime,trendingordertypes,trendinglocation,toppreferredserviceproviders,andmore.

GET/status/consumer/:idGetsthecurrentstatusoftheeventswithkeydatapointsforthespecifiedconsumerID.

Page 289: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GET/status/provider/:id GetsthecurrentstatusoftheeventswithkeydatapointsforthespecifiedproviderID.

GET/status/service/:typeGetsthecurrentstatusoftheeventswithkeydatapointsforthespecifiedtypeofservices.

TheLookupservicedoesnotprovideanyAPIstoupdatethedataasitmaintainsonlyadenormalizedviewofeventsreceivedfromConsumer,Provider,Service,andOrderservices.Ifthereareanychangesrequiredinthedata,theymustbedonewiththeAPIsexposedbytheircorrespondingmicroservicesthatmanageit.TheLookupservicewillthenreceivethechangeeventsandreflectthechangesinitslocaldatabase.

Page 290: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DefiningtheElasticsearchindex

TheLookupmicroserviceusesElasticsearchasthelocaldatabasetostorealltheeventsthataredenormalizedacrossdatabasesmaintainedbyConsumer,Provider,ServiceandOrdermicroservices.Elasticsearchprovidessub-secondresponseforthesearchqueriesandalsosupportsgeolocation-basedqueries.ItalsosupportsaggregationandanalyticsoutoftheboxtogeneratevariousreportsfortheHelpingHandsapplication.HereisamappingfortheElasticsearchindexthatisrequiredfortheLookupservice:

Field Type Mapping Analyzer Descriptionoid string - keyword OrderIDcid string - keyword UsedforconsumerID

pid string - keywordUsedforserviceproviderID

sid string - keyword UsedforserviceIDstype string - keyword Usedforservicetype

locality string - standardUsedtostorethelocalityoftheorder

geo string(lat,long) geo_point - Usedforgeolocation-basedqueries

cost float - - Usedtostorethetotalcostoftheorder

ts_startdate

(yyyyMMDD'T'HH:mm:ss) - - Startdate-timeoftheorder

ts_enddate

(yyyyMMDD'T'HH:mm:ss) - - Enddate-timeoftheorder

rating float

Ratinggivenforthe

Page 291: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

- - order

status string - keywordStatusoftheorder,oneofO,I,D,C

TheeventsarestoredintoElasticsearchwiththeprecedingindexschemainLookupindex.TheeventsarereceivedbytheLookupServiceviatheKafkatopicthatitsubscribesto.ThedetailsofKafka,theconceptoftopicsandhowtosubscribetoitforevents,havebeenexplainedinChapter10,Event-DrivenPatternsforMicroservices.Inthischapter,thefocuswillbeonhowtoquerytheElasticsearchindexthathasthefieldsaspertheschemaoftheLookupindex.

Page 292: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingqueryinterceptorsAlltheroutesoftheLookupserviceareoftype:getastheyareusedonlytoquerythedata.SincethedataresideswithElasticsearch,therequestsneedstobemappedtorelevantElasticsearchqueriestogettherequiredresult.InterceptorstoqueryElasticsearchdatareadtherequiredfieldsfromthe:tx-datafieldofthePedestalcontextassetbythevalidationinterceptors.TheimplementationofthevalidatorthatwrapsthequeriestoElasticsearchisthesameasthatofotherservicesexplainedearlier.

ElasticsearchdefinesaQueryDSL(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html)thatmustbeusedtoquerythedataagainsttheElasticsearchindex.TheQueryDSLisbasedonJSONandinvolvescreatingaJSONstructureofqueryclausesthatarewrappedwithinaqueryorafiltercontext(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html).Queryclausesmaybeoftypeleafqueryclausesorcompoundqueryclauses.

Leafqueryclauseslookforaparticularvalueinaparticularfield,asshowninthefollowingcodesnippetofanElasticsearchquery.Forexample,queryingforalltheordersthathavestatusopen;thatis,O.Thetermquery(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html),usedtogettheopenorders,queriesonlyforexactmatches.Sincethestatusfieldofthelookupschemahasthemappingdefinedasthatoftypekeyword,itsupportsexactmatchesviatheKeywordAnalyzer(https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-keyword-analyzer.html)ofElasticsearch:{"query":{"term":{"status":"O"}}}

Compoundqueryclausesareusedtowraponeormorequeryclausethatmaybeoftypeleaforcompound,asshowninthefollowingcodesnippet.Forexample,queryingforalltheordersthathavestatusdone;thatis,D,andhavearatingof

Page 293: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

fourormorethanthat.ElasticsearchprovidesaBoolQuery(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)tocreateBooleancombinationsofoneormorequeriestoformacompoundclause.Forstatus,itusesatermqueryandforratingitusesarangequery(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html)ofElasticsearchthatarecompoundedbyamustclausethatmakesboththeconditionstobesatisfiedforanordertobereturnedasaresponseofthisquery:{"query":{"bool":{"must":[{"term":{"status":"D"}},{"range":{"rating":{"gte":4}}}]}}}

ElastischisaClojureclientthatcanbeusedtocreateElasticsearchindexesandquerythem.ElasticsearchalsoprovidestheJavaRESTClient(https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html)andJavaAPIs(https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html)thatcanalsobeusedwithClojure.

Page 294: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Usinggeoqueries

Geoqueries(https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-queries.html)ofElasticsearchallowfindingtherecordsusingaboundingbox,distancefromagivengeopoint,ortherecordslyingwithinthepolygonmadeupofgeopointsspecifiedasaboundingregion.TheHelpingHandsapplicationrequiresgeoqueriesfortwoofitsroutes,GET/lookup/geoandGET/validate/:service.

Thefirstrouteallowsconsumerstolookupavailableserviceswithinthespecifiedboundingboxofalatitudeandlongitudepairthatcanbeselectedviaamapbydrawingarectangle.Alternatively,consumerscanalsolookupaservicewithin,say,a5kmradiusoftheirlocationspecifiedbytheirgeolocation.Similarly,thesecondroutevalidatesthattheselectedserviceID:servicelieswithintheallowedlimitsofaconsumergeolocationboundary.Boththeroutesinternallyuseeitheraboundingboxquery(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-bounding-box-query.html)oradistancequery(https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html)ofElasticsearch,asshownhere:

{

"query":{

"bool":{

"must":{

"match_all":{}

},

"filter":{

"geo_bounding_box":{

"geo.location":{

"top_left":{

"lat":13.17,

"lon":77.38

},

"bottom_right":{

"lat":12.73,

"lon":77.88

}

}

}

}

}

}

}

{"query":{"bool":{"must":{"match_all":{}},"filter":{"geo_distance":{

"distance":"5km","geo.location":{"lat":12.97,"lon":77.59}}}}}}

Page 295: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Geoqueriesbasedonbounding-boxanddistancerequiresfieldstohavethegeo_point(https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html)mappingdefined.Tolookupbydefiningashape,fieldsmusthavegeo_shape(https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html)mappingdefined.

Page 296: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GettingstatuswithaggregationqueriesElasticsearchalsosupportsaggregations(https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html)thatprovideaggregatedresultsbasedonthegivenqueries.AggregationsareusefulfortheHelpingHandsapplicationtogetthestatusoftheordersandgenerateanalyticsreportsthatcanbeusedtounderstandtheusageoftheapplication.

Forexample,totakealookattheordersreceivedeverymonth,adatehistogramaggregationcanbecreatedonthets_startfield:

{

"aggs":{

"monthly_orders":{

"date_histogram":{

"field":"ts_start",

"interval":"month"

}

}

}

}

Similarly,togetthestatsonratingsreceivedacrossorderssofar,statsaggregationcanbeusedontheratingfield,asshowninthefollowingexample.Statsreturnsthecount,min,max,avg,andsumofthevaluesofthespecifiedfield;thatis,ratinginthiscase:

{

"aggs":{

"rating_stats":{

"stats":{

"field":"rating"

}

}

}

}

Toknowthecurrentstatusoftheordersacrosstheapplication,termsaggregation(https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html)canbeusedonthestatusfield,asshowninthefollowingexample.Termsaggregationreturnsthecurrentcountofalltheorderstatusesacrossthesystemtogetareportofhowmanyordersareopen,inprogress,done,orclosed:

Page 297: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

{

"aggs":{

"order_status":{

"terms":{

"field":"status"

}

}

}

}

ElasticsearchisalsousedtobuildamonitoringsystemfortheHelpingHandsapplication.SuchamonitoringsystemreliesheavilyonaggregationqueriesofElasticsearchtobuildadashboardtounderstandtheruntimestateofthesystem.ThemonitoringsystemfortheHelpingHandsapplicationisdescribedinPartIV,Chapter11,DeployingandMonitoringSecuredMicroservices.

Page 298: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Creatingamicroserviceforalerts

TheAlertserviceisusedtosendemailalertsandSMS.Alertscanbegeneratedatvariouslevelsbyothermicroservices.Forexample,successfulcreationofaconsumer,provider,service,oranordermayrequireanemailtobesenttotherelevantstakeholders.Similarly,alertsmayberequiredwheneverthereisachangeinthestatusoftheorderoraratingisreceived.TheAlertservicedoesnotmaintainalocaldatabase,itjustgenerateseventsforeachsuccessfulalertsentthatcanbetrackedformonitoringpurposes.ThefollowingtableliststheendpointsfortheAlertservice:

URI Params Description

POST

/alerts/emailto,cc,subject,body

Sendsanalertviaemailtooneormorerecipients.

POST/alerts/sms to,body SendsanalertviaSMStooneormorerecipients.

Page 299: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Addingroutes

Mostly,theAlertservicewilllistenforeventsasanObserverandwillnotreceiverequeststosendalertsviaroutes.Ifitisrequiredtosendalertssynchronously,the/alerts/emailand/alerts/smsroutescanbeused,asdefinedinthefollowingcodesnippet:

(defroutes#{["/alerts/email"

:post(conjcommon-interceptors`auth`core/validate

`core/send-email`gen-events)

:route-name:alert-email]

["/alerts/sms"

:post(conjcommon-interceptors`auth`core/validate

`core/send-sms`gen-events)

:route-name:alert-sms]})

Page 300: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinganemailinterceptorusingPostalPostal(https://github.com/drewr/postal)isaClojurelibrarythatallowssendingemail.ItrequiresSMTPconnectiondetailsandthemessageasamapcontainingtherequireddetailsofto,from,cc,subject,andbodytosendasanemail.PostalcanbeusedwithinthePedestalinterceptortosendanemailiftherequiredfieldsarevalidatedandpresentinthecontext,asshownhere:

(nshelping-hands.alert.core

"InitializesHelpingHandsAlertService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[postal.core:aspostal]

[helping-hands.alert.persistence:asp]

[io.pedestal.interceptor.chain:aschain])

(:import[java.ioIOException]

[java.utilUUID]))

;;--------------------------------

;;ValidationInterceptors

;;--------------------------------

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(->context:request:form-params)]

(if(and(not(empty?params))

(not(empty?(:toparams)))

(not(empty?(:bodyparams))))

(let[to-val(maps/trim(s/split(:toparams)#","))]

(assoccontext:tx-data(assocparams:toto-val)))

(chain/terminate

(assoccontext

:response{:status400

:body"Bothtoandbodyarerequired"})))))

(defvalidate

{:name::validate

:enter

(fn[context]

(if-let[params(->context:request:form-params)]

;;validateandreturnacontextwithtx-data

;;orterminatedinterceptorchain

(prepare-valid-contextcontext)

(chain/terminate

(assoccontext

:response{:status400

:body"Invalidparameters"}))))

:error

Page 301: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

;;--------------------------------

;;BusinessLogicInterceptors

;;--------------------------------

(defsend-email

{:name::send-email

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

msg(into{}(filter(compsome?val)

{:from"[email protected]"

:to(:totx-data)

:cc(:cctx-data)

:subject(:subjecttx-data)

:body(:bodytx-data)}))

result(postal/send-message

{:host"smtp.gmail.com"

:port465

:ssltrue

:user"[email protected]"

:pass"resetme"}

msg)]

;;sendemail

(assoccontext:response

{:status200

:body(jp/generate-stringresult)})))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

PostaldependsontheunderlyingSMTPservertoacceptthecredentialsandallowthird-partyclientstosendemails.ServicessuchasGmailmayrestrictthird-partyclientstouseusernameandpassword.

Tosendalerts,itisrecommendedtouseexternalservicessuchasAmazonSES,AmazonSNS,andmoreastheyarereliabletouseandfollowapay-per-usemodel.

Page 302: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,wefocusedonthestep-by-stepimplementationofHelpingHandsmicroservicesusingthePedestalframework.WelearnedhowtoimplementHexagonalArchitectureusingClojureprotocols(https://clojure.org/reference/protocols)andPedestalinterceptors.WealsoimplementedtherequiredmicroservicesfortheHelpingHandsapplicationinPedestal.Inthenextchapter,wewilllearnhowtoconfigureourmicroservicesandmaintaintheruntimestateoftheapplicationthatincludesconnectionwithpersistentstorageandmessagequeuestostoredataandsendevents,respectively.

Page 303: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ConfiguringMicroservices

""Ican'tchangethedirectionofthewind,butIcanadjustmysailstoalwaysreachmydestination.""

-JimmyDean

Microservicesmustbeconfigurabletoadapttotheenvironmentinwhichtheyaredeployed.Theymustsupportexternalconfigurationparametersthatcanbespecifiedatruntimetoconfigurethemaspertheenvironmentinwhichtheyaredeployed.Oncetheconfigurationparametersaredefined,amicroservicemustbeabletoeffectivelypropagatetheconfigurationsacrossitsmodules.Theseconfigurationparametersmightthenbeusedtoinitializedatabaseconnectionsormaintainotherapplicationstatesthatmustbesharedacrossthemodulesofamicroservice.Allthemodulesmusthaveaccesstotheexactsamestateatruntime.Thischapterprovideseffectivesolutionstobuildsuchconfigurableservicesthatcanmanagetheirruntimestateseffectively.Inthischapter,youwilllearnhowtodothefollowing:

ApplyconfigurationprinciplestobuildhighlyconfigurableservicesUseOmniconfforconfigurationValidateconfigurationatstartupandregisteritforuseatruntimeUseamountlibrarytocomposeandmanageapplicationstates

Page 304: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ConfigurationprinciplesAlltheapplicationparametersthatarerelatedtotheenvironmentandaffecttheapplicationstatemustbemadeconfigurable.Forexample,theconnectionstringforaDatomicdatabasethatisusedbyHelpingHandsservicescanbemadeconfigurablesothatitcanbeupdatedexternallytopointtoaspecificinstanceofDatomicinproduction.Configurationparametersalsomakeitpossibletotesttheapplicationinvariousenvironments.Forexample,ifaDatomicconnectionstringismadeconfigurableforHelpingHandsservices,itcanbeusedtotesttheserviceswithanin-memoryinstanceofDatomicinlocaldevelopmentenvironmentsandlaterchangedtopointtotheproductioninstanceofDatomiconcetheyaredeployed.

Page 305: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Definingconfigurationparameters

Applicationsmustsupportmultiplewaysofdefiningtheconfigurationparameters.Usingcommand-lineargumentsisoneofthemostcommonwaysofspecifyingtheconfigurationparametersfortheapplication.Environmentvariablesandexternalconfigurationfilescanalsobespecifiedforapplicationstopicktheconfigurationparametersatruntime.SinceClojureusesJVMasitsruntimeengine,applicationsbuiltinClojurecanacceptconfigurationparametersasJavapropertiesaswell.

Applicationsthatacceptconfigurationparametersfrommultiplesourcesmustdecideonthepreferenceofvarioussources.Forexample,configurationparametersspecifiedatthecommandlineasJavapropertiescanoverwritethevaluesdefinedbytheenvironmentvariablesthatinturncanoverridethedefaultconfigurationdefinedintheconfigurationfile.

Page 306: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingconfigurationparametersOneoptiontoprovideaccesstoconfigurationparametersistoloadthematstartupandpassthemasargumentstothefunctions.Inthiscase,everytimeanewconfigurationparameterisadded,itmayresultinthechangeofthefunctionsignaturethatcanaffectallthefunctionsdependentonit.

Configurationparametersmustnotbetieddirectlytotheargumentsofthefunctionbecauseconfigurationparametersthatareloadedatstartuptimemaynotchangethroughoutthelifecycleoftheservice.Instead,configurationparameterscanbereadonceatstartupandkeptasimmutableconstantsthatcanbedirectlyaccessedbyallthefunctionsthatrequireoneormoreconfigurationparameter.

Readingtheconfigurationparametersfromvarioussourcesandmakingthemaccessibledoesnotguaranteetheconfigurationwillbecorrectunlesstheyareusedbytheapplication.Forexample,iftheapplicationneedsaportnumberasaconfigurationparameter,itmustbeverifiedassoonasitisreadandmustbeashortpositivenumberwithamaximumvalueof65535.Ifthesechecksarenotperformedatthetimetheconfigurationsareread,theyaregoingtoresultinruntimeexceptionslaterduringtheapplicationlifecycle.Detectingsuchconfigurationissuesatalaterpointintimeiscostlyastheconfigurationneedstobeupdatedandtheapplicationneedstoberedeployedtopickupontheupdatedconfiguration.

Page 307: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingOmniconfforconfigurationOmniconf(https://github.com/grammarly/omniconf)isanopensourceconfigurationlibraryforClojureprojectsthatcanbeusedtoconfiguremicroservicesoftheHelpingHandsapplication(refertoChapter3,MicroservicesforHelpingHandsApplication,andChapter8,BuildingMicroservicesforHelpingHands).Omniconfnotonlyallowstheapplicationtodefinethepreferencewithrespecttovariousconfigurationsourcesbutalsotoverifythematstartup.Internally,itkeepsalltheconfigurationparametersstoredasanimmutableconstantthatcanbeaccessedasaregularClojuredatastructure.

Omniconfisoneoftheoptionsforconfigurationmanagement.Libraries,suchasEnviron(https://github.com/weavejester/environ),Config(https://github.com/yogthos/config),Aero(https://github.com/juxt/aero),andFluorine(https://github.com/reborg/fluorine)canalsobeusedforconfigurationmanagement.

Page 308: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

EnablingOmniconf

ToenableanOmniconflibraryforanexistingproject,suchasHelpingHandsConsumerService,addtheOmniconfdependencytotheproject.cljfileandaddJVMopts,conftothedevprofilethatpointstotheconf.ednOmniconfconfigurationfile:

(defprojecthelping-hands-consumer"0.0.1-SNAPSHOT"

:description"HelpingHandsConsumerApplication"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

[io.pedestal/pedestal.jetty"0.5.3"]

;;DatomicFreeEdition

[com.datomic/datomic-free"0.9.5561.62"]

;;Omniconf

[com.grammarly/omniconf"0.2.7"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-

api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

...

:profiles{:provided{:dependencies[[org.clojure/tools.reader"0.10.0"]

[org.clojure/tools.nrepl"0.2.12"]]}

:dev{:aliases{"run-dev"["trampoline""run""-m"

"helping-hands.consumer.server/run-dev"]}

:dependencies[[io.pedestal/pedestal.service-tools"0.5.3"]]

:resource-paths["config","resources"]

:jvm-opts["-Dconf=config/conf.edn"]}

:uberjar{:aot[helping-hands.consumer.server]}

:doc{:dependencies[[codox-theme-rdash"0.1.1"]]

:codox{:metadata{:doc/format:markdown}

:themes[:rdash]}}

:debug{:jvm-opts

["-server"(str"-agentlib:jdwp=transport=dt_socket,"

"server=y,address=8000,suspend=n")]}}

:main^{:skip-aottrue}helping-hands.consumer.server)

Page 309: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntegratingwithHelpingHandsTheHelpingHandsservicesthatwereimplementedinthepreviouschapterusedafixedDatomicdatabaseURI,suchasdatomic:mem://consumer,fortheconsumerdatabasemanagedbytheconsumerservice.InsteadoffixingthenameofthedatabaseandDatomicURI,itmustbemadeconfigurablesothatitcanbechangedatthetimeofdeployment.Forexample,considerascenariowhereyouwishtoruntwoinstancesofConsumerservicebutwithseparateconsumerdatabases.ItwillnotbepossibletodosoiftheDatomicdatabaseURIishardcodedintheimplementationandnotmadeconfigurable.

Omniconfrequiresalltheconfigurationparameterstobedefinedviatheomniconf.core/definefunction.FortheConsumerserviceoftheHelpingHandsapplication,addanewhelping-hands.consumer.confignamespaceandinitializetheconfigurationasshownhere:

(nshelping-hands.consumer.config

"DefinesConfigurationfortheService"

(:require[omniconf.core:ascfg]))

(defninit-config

"Initializestheconfiguration"

[{:keys[cli-argsquit-on-error]:asparams

:or{cli-args[]quit-on-errortrue}}]

;;definetheconfiguration

(cfg/define

{:conf{:type:file

:requiredtrue

:verifieromniconf.core/verify-file-exists

:description"MECBOTconfigurationfile"}

:datomic

{:nested

{:uri{:type:string

:default"datomic:mem//consumer"

:description"DatomicURIforConsumerDatabase"}}}})

;;like-:some-option=>SOME_OPTION

(cfg/populate-from-envquit-on-error)

;;loadpropertiestopick-Dconffortheconfigfile

(cfg/populate-from-propertiesquit-on-error)

;;Configurationfilespecifiedas

;;EnvironmentvariableCONForJVMOpt-Dconf

(when-let[conf(cfg/get:conf)]

(cfg/populate-from-fileconfquit-on-error))

;;like-:some-option=>(java-Dsome-option=...)

;;reloadJVMargstooverwriteconfigurationfileparams

(cfg/populate-from-propertiesquit-on-error)

;;like-:some-option=>-some-option

(cfg/populate-from-cmdcli-argsquit-on-error)

;;Verifytheconfiguration

Page 310: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(cfg/verify:quit-on-errorquit-on-error))

(defnget-config

"Getsthespecifiedconfigparamvalue"

[&args]

(applycfg/getargs))

Inthisexample,thereisamandatoryconfigurationparameter,:conf,definedtobeof:filethatmustpointtotheconf.ednconfigurationfile.Thereisalsoaverifierattachedtoitthatvalidatesthepresenceoftheconf.ednfilebasedonthedefinedlocation.Also,thereisa:datomicconfigurationparameterdefinedthatisnestedandhasa:uriparameter,definedasstringtype,withadefaultvalueofdatomic:mem://consumerthatpointstoanin-memoryDatomicdatabase.

Afterdefiningtheconfigurationparameters,theimplementationchecksfortheconfparametervaluebyfirstloadingtheJVMproperties.The-DconfJVMpropertypointstotheconf.ednfileasdefinedinthedevprofileofproject.clj.Theconfigurationparametersarereadinthesequenceofenvironmentvariables,propertiesfile,andcommandline,eachoverwritingthevaluesdefinedbytheprevioussourceaspertheloadingsequence.

Aget-configutilitymethodisalsodefinedwithinthesamenamespaceforothermodulestolookuptheconfigurationparametersthatareloadedbythisnamespaceusingOmniconf.Toloadtheconfigurationatstartup,calltheinit-configmethodattheapplicationentrypoint,thatis,helping-hands.consumer.server,asshownhere:

(nshelping-hands.consumer.server

(:gen-class);for-mainmethodinuberjar

(:require[io.pedestal.http:asserver]

[io.pedestal.http.route:asroute]

[helping-hands.consumer.config:ascfg]

[helping-hands.consumer.service:asservice]))

...

(defnrun-dev

"Theentry-pointfor'leinrun-dev'"

[&args]

(println"\nCreatingyour[DEV]server...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

(->service/service;;startwithproductionconfiguration

...

;;Wireupinterceptorchains

server/default-interceptors

server/dev-interceptors

server/create-server

server/start))

(defn-main

Page 311: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

"Theentry-pointfor'leinrun'"

[&args]

(println"\nCreatingyourserver...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

(server/startrunnable-service))

Now,ifyoutrytoruntheConsumerserviceindevmodeatREPL,Omniconfwillloadtheconfiguration,verifyit,andmakeitavailableviathehelping-hands.consumer.config/get-configmethod.Sincewehaven'tcreatedtheconf.ednfileinthespecifiedconf/location,theverifiershouldfailattheinitializationstepitself,asshownhere:

helping-hands.consumer.server>(defserver(run-dev))

Creatingyour[DEV]server...

CompilerExceptionjava.io.FileNotFoundException:config/conf.edn(Nosuchfileor

directory),compiling:(form-init3431514182044937086.clj:118:44)

Addaconf.ednfileundertheconfigdirectoryanddefineonlytheDatomicURIconfiguration,asshownhere:

{:datomic{:uri"datomic:mem://consumer"}}

Now,theconfigurationisvalid,asitfindsthedefinedconf.ednfile.Inthiscase,asshownintheREPLsessioninthefollowingcodesnippet,Omniconfwilldumptheloadedconfigurationthatcanthenbeverifiedtomakesurealltheconfigurationparametersareloadedasexpected.Notethattherequired:confparameterisnotdefinedintheconf.ednfile,butitisdefinedastheJVMpropertythatisalsoreadinthesequence:

;;startserviceindevmode

helping-hands.consumer.server>(defserver(run-dev))

Creatingyour[DEV]server...

Omniconfconfiguration:

{:conf#object[java.io.File0x27a8b5d3"config/conf.edn"],

:datomic{:uri"datomic:mem://consumer"}}

#'helping-hands.consumer.server/server

;;trylookinguptheconfigurationparameter

helping-hands.consumer.server>(helping-hands.consumer.config/get-config:datomic)

{:uri"datomic:mem//consumer"}

helping-hands.consumer.server>(helping-hands.consumer.config/get-config:datomic

:uri)

"datomic:mem//consumer"

Now,trychangingtheDatomicURlparameterintheconfig.ednfiletodatomic:mem://consumer-sample.Itwilloverwritethedefaultvalueandcanthenbe

Page 312: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

usedbytheapplicationusingtheget-configmethodasshownhere:

helping-hands.consumer.server>(defserver(run-dev))

Creatingyour[DEV]server...

Omniconfconfiguration:

{:conf#object[java.io.File0x60e62394"config/conf.edn"],

:datomic{:uri"datomic:mem://consumer-sample"}}

#'helping-hands.consumer.server/server

helping-hands.consumer.server>(helping-hands.consumer.config/get-config:datomic)

{:uri"datomic:mem://consumer-sample"}

helping-hands.consumer.server>(helping-hands.consumer.config/get-config:datomic

:uri)

"datomic:mem://consumer-sample"

ThepersistencenamespaceoftheConsumerservicecannowreadthedatabaseURIdirectlyfromtheconfigurationinsteadofexpectingitasanargumentofthecreate-consumer-databasefunction:

(nshelping-hands.consumer.persistence

"PersistencePortandAdapterforConsumerService"

(:require[datomic.api:asd]

[helping-hands.consumer.config:ascfg]))

...

(defncreate-consumer-database

"Createsaconsumerdatabaseandreturnstheconnection"

[]

;;createandconnecttothedatabase

(let[dburi(cfg/get-config[:datomic:uri])

db(d/create-databasedburi)

conn(d/connectdburi)]

;;transactschemaifdatabasewascreated

(whendb

(d/transactconn

[{:db/ident:consumer/id

:db/valueType:db.type/string

:db/cardinality:db.cardinality/one

:db/doc"UniqueConsumerID"

:db/unique:db.unique/identity

:db/indextrue}

...

]))

(ConsumerDBDatomic.conn)))

OmniconfworkswithboththeLeiningenandBootbuildtoolsofClojure.Formoredetailsandusageinformationofallthepossibleoptions,takealookattheexample-lein(https://github.com/grammarly/omniconf/tree/master/example-lein)andexample-boot(https://github.com/grammarly/omniconf/tree/master/example-boot)projectsofOmniconf.

Page 313: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ManagingapplicationstateswithmountOncetheconfigurationparametersaredefinedusingOmniconf,theyareaccessibleacrossthenamespacesasimmutabledata.Theconfigurationparametersareoftenusedtocreatestatefulobjects,suchasdatabaseconnections.Forexample,intheConsumerserviceproject,Omniconfmadeitpossibletocreateaconsumerdatabasebydirectlylookingupthe:datomic:uriconfigurationparameterwithinthecreate-consumer-databasefunction.

Thehelping-hands.consumer.persistence/create-consumer-databasefunctionhasasideeffectofdatabasebeingcreatedandalsoanewconnectionbeinginitializedtoconnecttothecreateddatabase.ThisconnectionhasastatethatmustbesharedacrossothernamespacesoftheHelpingHandsConsumerservicethatneedaccesstothedatabase.Inthecurrentimplementation,theconnectionwasinitializedatthefirstcalltothehelping-hands.consumer.core/consumerdbasshownhere:(nshelping-hands.consumer.core"InitializesHelpingHandsConsumerService"(:require[cheshire.core:asjp][clojure.string:ass][helping-hands.consumer.persistence:asp][io.pedestal.interceptor.chain:aschain])(:import[java.ioIOException][java.utilUUID]))

;;delaythecheckfordatabaseandconnection;;tillthefirstrequesttoaccess@consumerdb(def^:privateconsumerdb(delay(p/create-consumer-database)))

Insteadofcreatingastateusingdelay,thestatemanagementcanbehandledeffectivelyusingalibrary,suchasmount(https://github.com/tolitius/mount).Creatingapplicationstatesusingmountallowsforthereloadingoftheentireapplicationstateusingstartandstopfunctionsprovidedbythemount.corenamespace.The

Page 314: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

mountlibraryalsohelpswithstatecompositionbyallowingtheapplicationtostartonlywithspecificstatesandatthesametimeswappingotherswithnewvalues.Italsosupportsruntimearguments.

Component(https://github.com/stuartsierra/component)isanotherClojurelibrarythatiswidelyusedtomanagethelifecycleofstatefulobjectsinaClojureproject.mountisanalternativetotheComponentlibrarywithkeydifferences(https://github.com/tolitius/mount/blob/master/doc/differences-from-component.md#differences-from-component),oneofthembeingComponent'srequirementoftheentireappbeingbuiltarounditscomponentobjectmodel.

Page 315: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Enablingmount

ToenablethemountlibraryfortheHelpingHandsConsumerserviceapplication,addamountdependencytotheproject.cljfileasshowninthefollowingcodesnippet.Also,createanewhelping-hands.consumer.statenamespacethatwillbeusedtodefinethestatesusingthemount.core/defstatefunction,whichcanbereferredtobyothernamespacesoftheprojecttogetaccesstothecurrentstateofthedefinedobject,suchastheDatomicdatabaseconnection:

(defprojecthelping-hands-consumer"0.0.1-SNAPSHOT"

:description"HelpingHandsConsumerApplication"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

[io.pedestal/pedestal.jetty"0.5.3"]

;;DatomicFreeEdition

[com.datomic/datomic-free"0.9.5561.62"]

;;Omniconf

[com.grammarly/omniconf"0.2.7"]

;;Mount

[mount"0.1.11"]

...

]

...

:main^{:skip-aottrue}helping-hands.consumer.server)

Page 316: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntegratingwithHelpingHandsTointegratemountwiththeHelpingHandsConsumerserviceproject,createastateforaDatomicdatabaseconnectionwithinthehelping-hands.consumer.statenamespace,asshownhere:

(nshelping-hands.consumer.state

"InitializesStateforConsumerService"

(:require[mount.core:refer[defstate]:asmount]

[helping-hands.consumer.persistence:asp]))

(defstateconsumerdb

:start(p/create-consumer-database)

:stop(.stopconsumerdb))

The:startclauseiscalledatthetimeofstartupand:stopiscalledatshutdown.ThefunctionstopisdefinedfortheConsumerDBprotocol,asshowninthefollowingexampleunderthehelping-hands.consumer.persistencenamespace:

(nshelping-hands.consumer.persistence

"PersistencePortandAdapterforConsumerService"

(:require[datomic.api:asd]

[helping-hands.consumer.config:ascfg]))

(defprotocolConsumerDB

"Abstractionforconsumerdatabase"

(upsert[thisidnameaddressmobileemailgeo]

"Adds/Updatesaconsumerentity")

(entity[thisidflds]

"Getsthespecifiedconsumerwithallorrequestedfields")

(delete[thisid]

"Deletesthespecifiedconsumerentity")

(close[this]

"Closesthedatabase"))

...

(defrecordConsumerDBDatomic[conn]

ConsumerDB

...

(close[this]

(d/shutdowntrue)))

Next,addthestartupandshutdownhooksformountattheapplicationentrypointunderthehelping-hands.consumer.servernamespace.Theshutdownhookdefinedat

Page 317: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

theapplicationentrypointcallsthe:stopclauseofmountthatmustcleanupalltheresourcesrelatedtothestatefulobject,thatis,theDatomicconnectionasshowninthefollowingexample:

(nshelping-hands.consumer.server

(:gen-class);for-mainmethodinuberjar

(:require[io.pedestal.http:asserver]

[io.pedestal.http.route:asroute]

[mount.core:asmount]

[helping-hands.consumer.config:ascfg]

[helping-hands.consumer.service:asservice]))

...

(defnrun-dev

"Theentry-pointfor'leinrun-dev'"

[&args]

(println"\nCreatingyour[DEV]server...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

(Thread.mount/stop))

(->service/service;;startwithproductionconfiguration

...

;;Wireupinterceptorchains

server/default-interceptors

server/dev-interceptors

server/create-server

server/start))

(defn-main

"Theentry-pointfor'leinrun'"

[&args]

(println"\nCreatingyourserver...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

(Thread.mount/stop))

(server/startrunnable-service))

Oncemountissetuptoinitializethestateatstartup;thedefinedstateconsumerdbcannowbereferredtoacrossnamespacesanduseddirectlyforthehelping-hands.consumer.corenamespaceasshownhere:

(nshelping-hands.consumer.core

"InitializesHelpingHandsConsumerService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.consumer.persistence:asp]

[io.pedestal.interceptor.chain:aschain]

Page 318: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

[helping-hands.consumer.state:refer[consumerdb]])

(:import[java.ioIOException]

[java.utilUUID]))

;;delaythecheckfordatabaseandconnection

;;tillthefirstrequesttoaccess@consumerdb

;;NOLONGERREQUIREDDUETOMOUNT

;;(def^:privateconsumerdb

;;(delay(p/create-consumer-database)))

...

;;Usethereferredstatefulconsumerdbdirectlyintheinterceptor

(defupsert-consumer

{:name::consumer-upsert

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

id(:idtx-data)

db(.upsertconsumerdbid(:nametx-data)

(:addresstx-data)(:mobiletx-data)

(:emailtx-data)(:geotx-data))]

...))

:error...})

Page 319: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,wefocusedonhowtobuildconfigurableapplicationsthatcanadaptaspertherequirementsanddependenciesathand.WelookedatanopensourceconfigurationutilitycalledOmniconfthatprovidesaneffectivewaytodefineandvalidateconfigurationparametersforClojureapplications.

WealsolookedathowtheruntimestateoftheapplicationcanbecomposedandsharedamongvariousnamespacesoftheClojureapplication.Welookedatanopensourcelibrarycalledmountthathelpsapplicationstomanageandcomposetheirstatesatruntimewithoutaffectingtheoverallstructureoftheimplementation.

Inthenextchapter,wewilllearnhowtoadoptevent-drivenarchitectureforHelpingHandsmicroservices.WewillalsolearnhowtobuilddataflowsforthemicroservicesofHelpingHands.

Page 320: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Event-DrivenPatternsforMicroservices

""Thesinglebiggestproblemwithcommunicationistheillusionthatithastakenplace.""

-GeorgeBernardShaw

Microservicesaddressasingleboundedcontextandaredeployedindependentlyononeormorephysicalmachinesthataredistributedacrossanetwork.Althoughtheyaredeployedinisolation,theyneedtointeractwitheachothertoaccomplishapplication-leveltasksthatmaycutacrossmultipleboundedcontexts.Thechoiceofcommunicationmediumandmethodhasagreatimpactontheperformanceanddurabilityoftheentiremicroservice-basedarchitecture.Eventsareoneofthemethodsofasynchronouscommunicationamongmicroservicestoexchangedataofinterest.Part-1ofthebookexplainstheimportanceoftheobservermodelandhowamessagebroker(https://en.wikipedia.org/wiki/Message_broker)helpsinsendingandreceivingeventsinamicroservicesarchitecture.Inthischapter,youwill:

Learnaboutevent-drivenpatternsforeffectivemessagingamongmicroservicesLearnhowtouseApacheKafkaasamessagebrokerformicroservicesLearnhowtouseApacheKafkaforEventSourcingLearnhowtointegrateApacheKafkawithHelpingHandsmicroservices

Page 321: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Implementingevent-drivenpatternsEvent-drivenpatternsaddresstheobservermodelofcommunicationtosendmessagesamongmicroservices.Themessagesaresentandreceivedthroughamessagebrokerthatactsasaconnectingbridgebetweenthesenderandthereceiver.Inamicroservicesarchitecture,thesemessagesmaybegeneratedaseventsasanoutcomeoftheactiontakenbythemicroservice.Messagesforwhichthesourcemicroservicedoesnotexpectaresponsefromthetargetservicecanbepublishedaseventsasynchronously,insteadofsendingthemoveraRESTAPIfordirectcommunication.Sinceitisnotadirectcommunication,aneventcanbepublishedonceandconsumedbymorethanonemicroservicethathassubscribedtoreceiveit.Moreover,thesenderdoesnotgetblockedbythereceiverforeacheventthatispublished.

Messagebrokersalsohelptobuildaresilientarchitectureforevent-drivencommunicationasreceiversneednotbeavailablewhiletheeventisbeingproducedandtheycanconsumethemessagesatwill.Incaseoffailures,thereceivercanberestartedanditcanstartconsumingtheeventswhereitleftoff.Duringthedowntime,themessagebrokeritselfactsasaqueueandcachesalltheeventsthatwerenotconsumedbythereceiverandmakesitavailabletothereceiverondemand.

Asynchronouscommunicationviaeventsalsodecouplesthesenderfromthereceiverthathelpsinscalingboththesidesindependently.Thisfeatureisofprimeimportanceinamicroservice-basedarchitectureasitallowsthemicroservicestobedeployedindependentlyofothermicroservicesfromwhichitconsumestheeventsviaamessage-broker.Event-drivenpatternsarealsousedforasynchronoustaskssuchasstoringauditlogstokeeptrackofruntimestateoftheapplicationandsendingalerts.

Theobservermodel,alongwithvariousdatamanagementpatterns,areexplainedinChapter2,MicroservicesArchitectureofthisbook.

Page 322: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

EventsourcingEventscanbeusedtoadvertisethechangesinthestateoftheentitiesmanagedbyamicroservice.Insuchcases,eventscarrytheupdatedstateoftheentityincludingtheentityidentifierandthevaluesofthefieldsthathavechanged.Itisalsorecommendedtoincludeauniqueidentifierandversionnumberaswellwitheachupdateevent.Anyinterestedmicroservicecanthensubscribetotheseeventsviaamessagebrokerandreceivethechangestoupdatethestateoftheentitylocally.TheLookupServiceoftheHelpingHandsapplicationisonesuchservicethatlistenstoallthestatechangeeventsgeneratedbytheconsumer,provider,andordermicroservicestokeepanupdateddatasetforuserstolookup.

Oneofthemainadvantagesofpublishingallthestatechangesacrossmicroservicesasimmutableeventsistomakesurethatthestateoftheentiremicroservice-basedapplicationcanberebuiltbyjustprocessingtheseeventsinthesequenceinwhichtheyarepublished.Boththecurrentstateoftheapplicationaswellasthestateinthepastcanbereconstructedbyprocessingtheseeventsintheexactsamesequence.Abilitytoreplaytheeventsnotonlyhelpsinrebuildingthestateoftheapplication,butalsohelpsinauditinganddebugging.

Page 323: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Messagebrokers,suchasApacheKafka(https://kafka.apache.org/),allowpublishingmessagesdecoupledfromconsumingthemandeffectivelyactasastoragesystemthatmaintainsadurablelogofpublishedevents.Suchsystemsallowtheeventstobecapturedbymultiplesystemsatthesametime,asshownintheprecedingdiagram.Forexample,theeventspublishedbyServiceAandServiceBcanbeconsumedbyServiceC,butatthesametime,theseeventscanbebacked-upinabackupstoreorcapturedinatransactionalstoreforbuildingmachinelearningmodelsorcapturedforreal-timemonitoringandreportingoftheapplicationstateinrealtime.Sincethelogsretainedbythebrokersareimmutableanddurable,applicationsthataredevelopedin-futurecanalsoreplaytheeventstobuildthetemporalstateoftheapplicationandworkonit.

Page 324: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingtheCQRSpatternTheCommandQueryResponsibilitySegregation(CQRS)patternappliesthecommand-queryseparation(https://en.wikipedia.org/wiki/Command-query_separation)principlebysplittingtheapplicationintotwoparts,querysideandcommandside.Querysideisresponsibleforgettingthedatabyonlyqueryingthestateoftheapplication,whereascommandsideisresponsibleforchangingthestateofthesystembyperformingcreate,update,ordeleteoperationsonapplicationdatathatmayresultinupdationofoneormoredatabasesusedbytheapplication.AlthoughtheCQRSpatternisoftenusedinconjunctionwiththeevent-sourcingpattern,itneednotbetiedtoeventsandcanbeappliedtoanyapplicationwithorwithoutevents.TheCQRSpatternisrecommendedforapplicationsthatarenotbalancedwithrespecttoreadandwriteloads.

CQS(https://en.wikipedia.org/wiki/Command-query_separation)principlewasdevisedbyBertrandMeyer(https://en.wikipedia.org/wiki/Bertrand_Meyer),whereasCQRStermwasfirstcoinedbyGregYoungaspartofCQRSdocuments(https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf).

TheCQRSpatternfitswellwithmicroservice-basedarchitectureastheentireapplicationissplitintoseparateservices,eachhavingtheirowndatamodelandpublishingthechangesinthestateoftheirdatamodelasevents.Butatthesametime,itisalsochallengingtokeeptheseseparatemodelsconsistent.Thisiswheresagasareuseful,whichsupporteventualconsistency.ThesagaspatternhasbeendescribedinChapter2,MicroservicesArchitecture.

FormoredetailsontheCQRSpatternanditsusage,readtheCQRSarticlebyMartinFowler(https://martinfowler.com/bliki/CQRS.html)andtheClarifiedCQRSarticlebyUdiDahan(http://udidahan.com/2009/12/09/clarified-cqrs/).

Page 325: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroductiontoApacheKafkaApacheKafkaisadistributedstreamingplatformthatallowsapplicationstopublishandsubscribetoastreamofrecords.ApacheKafkaisnotjustamessagequeue,italsoallowsapplicationstopublishtheeventsthatarethenstoredbyKafkaasanimmutableloginafault-tolerantway.Itallowstheproducersandconsumersoftheeventstoscalehorizontallywithoutaffectingeachother.SincetheeventsareloggedinthesamesequenceastheyarepublishedwithinKafka,itallowsconsumerstoreplaythelogfromanduptothedesiredpointtoreconstructviewsoftheapplicationstate.

Page 326: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DesignprinciplesKafkaisrunasaclusterofoneormoreserversthatactasmessagebrokers(https://en.wikipedia.org/wiki/Message_broker)inthesystem.Kafkacategorizesthestreamofrecordsundertopicsthatareusedbyproducersandconsumerstoproducerecordsandconsumethem,respectively.Eachrecordconsistsofakey-valuepairandatimestamp.

AtypicalKafkaclusterisshowninthefollowingdiagramalongwiththestructureofaKafkatopicanditspartitions.AtopicisacoreabstractionforastreamofrecordsinKafka.Producerspublishtherecordstoatopicthatcanhavezero,one,ormoreconsumerssubscribedtoit.Eachtopicconsistsofoneormorepartitionsthatareordered,immutablesequenceofrecordsthatisappendedtoacommitlogandstoredonadiskandreplicatedacrossserversforfault-toleranceanddurability.Eachrecordthatispublishedonatopicgetsassignedtoapartitionwithinwhichitisassignedasequentialidentifiernumberthatiscalledtheoffset.Theoffsetuniquelyidentifiesarecordwithinthepartitionandisalsousedasareferencebyconsumerstotracktheirstateofconsumption.Offsetsallowconsumerstoresetthemselvestoanearlierstatetoeitherreplaytherecordsorfast-forwardintimetoskiptherecords.AKafkaclusterretainsallthepublishedrecordsonlyforaconfigurableretentionperiod.Posttheconfiguredretentionperiod,therecordsarenolongeravailableforconsumersirrespectiveofwhethertheywereconsumedearlierornot:

Kafkaproducerspublishdatatooneormoretopicsandhavethefreedomtodecidethepartitionforthepublishedrecords.Kafkaconsumersalwaysjoinwithaconsumergrouplabelthatissharedwithonemoreconsumerthatmaybe

Page 327: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

presentononeormoremachines.TheConsumerGroupplaysanimportantroleinthewaytheKafkaclusterdeliversthepublishedrecordstotheconsumers.EachrecordpublishedonaKafkatopicisdeliveredtoonlyoneconsumerwithinaConsumerGroup.Forexample,intheprecedingdiagram,therearetwoconsumergroups—ConsumerGroupAandConsumerGroupBandsixconsumerswiththreeconsumersineachgroup.Anyrecordpublishedonpartitions0,1,2,or3issenttoonlyoneconsumerwithineachgroup.Forexample,recordspublishedtoPartition-0issenttoC1ofConsumerGroupAandC2ofConsumerGroupB.Kafkabalancestheflowofrecordsacrosstheconsumersintheconsumergroups.

ApacheKafkaprovidestotalorderingoverrecordsonlywithinapartition.Usecasesthatrequiretotalorderingovertherecordspublishedonatopicmusthavethetopicconfiguredtohaveasinglepartition.Thisconfigurationalsolimitsthethroughputtoonlyoneconsumerprocessperconsumergroup.

Page 328: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GettingKafkaApacheKafkaisanopen-sourceplatformandcanbedownloadedfromitsdownloadpage(https://kafka.apache.org/downloads).Fortheexamplesinthisbook,downloadandextractthe1.0.0release(https://www.apache.org/dyn/closer.cgi?path=/kafka/1.0.0/kafka_2.11-1.0.0.tgz)fromoneofthemirrors,asshownhere:

#downloadKafka1.0.0withScala2.11(Recommended)

%wgethttp://redrockdigimark.com/apachemirror/kafka/1.0.0/kafka_2.11-1.0.0.tgz

...

#extractKafkadistribution

%tar-xvfkafka_2.11-1.0.0.tgz

...

#switchtoKafkadirectory

%cdkafka_2.11-1.0.0

ApacheKafkaServersrequireApacheZookeeper(https://zookeeper.apache.org/)forclustercoordination.KafkadistributionpackagesasinglenodeZookeeperinstanceforconvenience;however,itisrecommendedtosetupanexternalZookeeperclusterforproductiondeployments.StartasinglenodeZookeeperwiththedefaultpropertiesfilepackagedwithKafka,asshownhere:

#startZookeeper

bin/zookeeper-server-start.shconfig/zookeeper.properties

....

INFObindingtoport0.0.0.0/0.0.0.0:2181

(org.apache.zookeeper.server.NIOServerCnxnFactory)

OncetheZookeeperisstarted,eitherswitchtoanewterminalorputtheZookeeperprocessinthebackgroundtogettheaccesstothesameterminal.Next,startaKafkaserverwiththedefaultpropertiesfilepackagedwithKafka,asshownhere:

#startKafkaServer

bin/kafka-server-start.shconfig/server.properties

...

INFOKafkaversion:1.0.0(org.apache.kafka.common.utils.AppInfoParser)

INFOKafkacommitId:aaa7af6d4a11b29d(org.apache.kafka.common.utils.AppInfoParser)

INFO[KafkaServerid=0]started(kafka.server.KafkaServer)

Thedefaultserver.propertiesfilecontainsafixedbroker.idpropertythatissetto0andthelistenersconfiguredtoadefaultportof9092withlog.dirpointingto/tmp.ForasingleKafkaserver,thesesettingsarefine,buttostartmultipleKafkaserversonthesamemachine,thesepropertiesmustbechangedforeachserverto

Page 329: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

avoidID,port,andcommitlogdirectoryclashes.Next,createatopicbythenameoftestwithasinglepartition,asshownhere:

#createatopic

%bin/kafka-topics.sh--create--zookeeperlocalhost:2181--replication-factor1--

partitions1--topictest

Createdtopic"test".

#Listandconfirmthattopicwascreated

%bin/kafka-topics.sh--list--zookeeperlocalhost:2181

test

ThecreatetopiccommandrequirestheaddresstotheZookeeperthatisusedbyKafkacluster.SinceZookeeperwasstartedwithdefaultproperties,itwouldhavetakenthe2181portifthatwasfreeonthemachine.Next,startaproducerinanewterminalandpublishsomemessages,asshownhere:

#startanewproducertoproducethemessagesontopic'test'

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topictest

>HelloKafka!

>HelloHelpingHands!

>

ThestartedproducerconnectstotheKafkaserveronport9092;thatis,thedefaultportofKafkaserver.Thestartedproducerisalsoconfiguredtopublishthemessagesonthetesttopicthatwascreatedearlier.Oncetheproducerisstarted,publishthetwomessages,asshownintheprecedingcodesnippet,andstartaconsumerforthesametopicinanewterminal,asshownhere:

#startanewconsumertoconsumethemessagesfromtesttopicfromthebeginning

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--from-

beginning

HelloKafka!

HelloHelpingHands!

Theconsumerreceivesthepublishedmessagesandprintsthemontheconsole.Now,gobacktotheproducerterminalandpublishonemoremessageandverifythatconsumerreceivesonlythenewmessage,asshownhere:

#publishanewmessage

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topictest

>HelloKafka!

>HelloHelpingHands!

>KafkaWorks!

>

#validatethemessageontheconsumerterminal

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--from-

beginning

HelloKafka!

HelloHelpingHands!

Page 330: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

KafkaWorks!

KafkaManager(https://github.com/yahoo/kafka-manager)isausefultoolthathelpsinmanagingoneormoreKafkaclustersusingasingleweb-basedinterface.

Page 331: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingKafkaasamessagingsystemKafkaprovidesbothqueuing(http://en.wikipedia.org/wiki/Message_queue)andpublish-subscribe(http://en.wikipedia.org/wiki/Publish-subscribe_pattern)constructsofamessagingsystembytheconceptofaConsumerGroup.ThemessagespublishedonatopicpartitionarebroadcastedtoaconsumerwithineachConsumerGroupandwithintheConsumerGroup,eachconsumerreceivesthemessagesfromadifferentpartitionofatopic.Therefore,thenumberofconsumerspresentintheConsumerGroupmustnotbemorethanthenumberofpartitionspresentinatopic.Iftheyaremorethanthenumberofpartitions,thentheywillbejustsittingidleandwillonlygetthemessageifoneoftheconsumersfailswithinthegroup.Forexample,todemonstratethemessagingcapabilitiesofKafka,startonemoreconsumerforthetesttopic,asshownhere:#startanotherconsumer%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--from-beginning

Oncetheconsumerisstarted,anyothermessagethatissentfromtheproducerprocessisreceivedbyboththeconsumers.ThishappensbecauseboththeconsumersareapartofthedifferentConsumerGroupandasperdesign,Kafkawillbroadcastthepublishedmessagesonatopicacrossconsumergroups.SincecurrentsetuphasonlyoneconsumerintheConsumerGroup,bothconsumersreceivethemessage.Next,stopbothoftheconsumerprocessesandstartthemasapartofthesameConsumerGroup,asshownhere:

#startfirstconsumer

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test--from-beginning

#startsecondconsumer

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test--from-beginning

Now,themessagespublishedviaproducerarereceivedbyonlyoneoftheconsumersasboththeconsumersareapartofthetestgroupandthetesttopichasadefaultpartitioncountof1.Tryterminatingthefirstconsumerprocessthatisreceivingthemessagesandsendanewmessagefromtheproducer.Noticethatnow,themessageisbeingreceivedbythesecondconsumerthatbecomesactiveandstartsconsumingthemessagesfromthetopicpartitionwheretheterminated

Page 332: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

consumerleftoff.

Page 333: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingKafkaasaneventstoreApacheKafkaisnotjustlimitedtoamessagingsystem,itcanalsobeusedasadurablestorageforimmutablerecordsandtobuildastreamingdatapipelineontopofit.Itiswellsuitedforusecasessuchaswebsiteactivitytracking,real-timemonitoring,logaggregation,andprocessingstreamsofdata.AnymessagesthatarepublishedonaKafkatopicarepersistedondiskandreplicatedacrossKafkaserversbasedontheconfigurationforfaulttolerance.SinceKafkaguaranteesthesequenceofmessageswithinatopicpartition,itallowsconsumerstocontroltheirreadpositionirrespectiveofotherconsumersofthesametopic.ThefollowingexampleshowshowtheeventspublishedbyaproduceraremadeavailabletotheconsumersbasedonthetopicandtheassociatedConsumerGroup:

#startproducerfor'test'topic

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topictest

>HelloApacheKafka!

>HelloKafkaEvent!

>

#startconsumerin'test'consumergrouplisteningto'test'topic

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test--from-beginning

HelloApacheKafka!

HelloKafkaEvent!

#consumefrombeginninginadifferentconsumergroup

#sinceitisadifferentconsumergroup,itreplaymessagesthatwere

#publishedearlieraswellastheywerenotcommittedbyanyother

#consumerinconsumergroup'test1'

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test1--from-beginning

HelloKafka!

HelloHelpingHands!

KafkaWorks!

HelloApacheKafka!

HelloKafkaEvent!

#startingaconsumerwithoutthe'from-beginning'flag

#waitsfornewmessagesonlyanddoesnotreplaypreviousmessages

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test1

#startingaconsumerwithoutthe'from-beginning'flag

#waitsfornewmessagesonlyanddoesnotreplaypreviousmessages

#evenforthenewconsumergroup'test2'

%bin/kafka-console-consumer.sh--bootstrap-serverlocalhost:9092--topictest--group

test2

Page 334: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Kafkaconsumerscandecidetheoffsetfromwheretheywishtostartconsumingthemessages.Intheprecedingexample,theconsumerwasstartedwitha--from-beginningflagthattellstheconsumertostartconsumingfromthefirstoffset,andthatiswhyeverytimetheconsumerisstartedwiththisflaginanewConsumerGroup,itwillreplaythereceivedmessagesineachConsumerGroupfromthebeginning,asshownintheprecedingexample.ThereplayofmessagesandoffsetretentionalsodependsontheretentionperiodsetusingKafkaserverproperties.

TheApacheKafkaconfiguration(https://kafka.apache.org/documentation/#configuration)pageprovidesalistofconfigurationparametersforApacheKafkaservers.

Page 335: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingKafkaforHelpingHands

TheHelpingHandsapplicationusesApacheKafkatoimplementtheobservermodelandsendasynchronouseventsamongmicroservices.ItisalsousedasaneventstoretocaptureallthestatechangeeventsgeneratedfrommicroservicesthatareconsumedbytheLookupservicetobuildaconsolidatedviewtoserverlookuprequests.TheAlertmicroserviceoftheHelpingHandsapplicationalsoreceivesthealerteventsviatheKafkatopicandsendsanemailasynchronously.

ApacheKafkaincludesfivecoreAPIs:

TheProducerAPIallowsapplicationstopublishstreamsofeventstooneormoretopicsTheConsumerAPIallowsapplicationstoconsumepublishedeventsfromoneormoretopicsTheStreamsAPIallowstransformingstreamsfrominputtopicsandpublishtheresultstooutputtopicsTheConnectAPIallowssupportforvariousinputandoutputsourcestocaptureanddumpstreamofeventsTheAdminClientAPIallowstopicsandservermanagementalongwithotherKafkamanagementoperations

TointegrateKafkawiththeHelpingHandsapplication,theProducerandConsumerAPIswillonlyberequiredtoproducestatechangeeventsandconsumethem.Therequiredtopicscanbecreatedexternallyusingkafka-topics.shscript,asshownintheprevioussection.ToincludetheproducerandconsumerclientAPIs,addtheprojectdependencyofkafka-clients,asshowninthefollowingexample,totheproject.cljfileofthecorrespondingmicroserviceproject:

(defprojecthelping-hands-alert"0.0.1-SNAPSHOT"

:description"HelpingHandsAlertApplication"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

[io.pedestal/pedestal.jetty"0.5.3"]

Page 336: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

;;DatomicFreeEdition

[com.datomic/datomic-free"0.9.5561.62"]

;;Omniconf

[com.grammarly/omniconf"0.2.7"]

;;Mount

[mount"0.1.11"]

;;postalforemailalerts

[com.draines/postal"2.0.2"]

;;kafkaclients

[org.apache.kafka/kafka-clients"1.0.0"]

;;logger

[org.clojure/tools.logging"0.4.0"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-

api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

:min-lein-version"2.0.0"

:source-paths["src/clj"]

:java-source-paths["src/jvm"]

:test-paths["test/clj""test/jvm"]

...

:main^{:skip-aottrue}helping-hands.alert.server)

Page 337: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingKafkaAPIsAlthoughClojurehasKafkawrappers(https://cwiki.apache.org/confluence/display/KAFKA/Clients#Clients-Clojure)available,thisbookfocusesonusingApacheKafkaJavaAPIsdirectlyinsteadofaClojurewrapper.TocreateaKafkaconsumer,importtherequiredJavaAPIs,asshownhere:

(nshelping-hands.alert.channel

"InitializesHelpingHandsAlertChannelConsumer"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[clojure.tools.logging:aslog]

[helping-hands.alert.config:asconf]

[postal.core:aspostal])

(:import[java.utilCollectionsProperties]

[org.apache.kafka.common.serialization

LongDeserializerStringDeserializer]

[org.apache.kafka.clients.consumer

ConsumerConsumerConfigKafkaConsumer]))

Next,defineafunctioncreate-kafka-consumerthatinitializesaKafkaconsumerandreturnsit,asshownhere:

(defncreate-kafka-consumer

"CreatesanewKafkaConsumer"

[]

(let[props(doto(Properties.)

(.putAll(conf/get-config[:kafka]))

(.putConsumerConfig/KEY_DESERIALIZER_CLASS_CONFIG

(.getNameLongDeserializer))

(.putConsumerConfig/VALUE_DESERIALIZER_CLASS_CONFIG

(.getNameStringDeserializer)))

consumer(KafkaConsumer.props)

_(.subscribeconsumer(Collections/singletonList

(get(conf/get-config[:kafka])"topic")))]

consumer))

TheKafkaconsumerrequiresasetofconfigurationstoconnecttotheKafkaserverandstartconsumingthepublishedmessages.ThisconfigurationcanberetrievedviaOmniconfconfiguration,asdiscussedinthepreviouschapter.Tosettherequiredconfigurationparameter,first,definetheconfigurationparameterforOmniconftopick,asshownhere:

(cfg/define

{:conf{:type:file

:requiredtrue

:verifieromniconf.core/verify-file-exists

:description"MECBOTconfigurationfile"}

:kafka{:type:edn

Page 338: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:default{"bootstrap.servers""localhost:9092"

"group.id""alerts"

"topic""hh_alerts"}

:description"KafkaConsumerConfiguration"}})

Thebootstrap.serversparametercanhavemorethanoneKafkaserverdefinedasacomma-separatedvaluewiththeformatofhost:port.Aspertheprecedingconfiguration,itusesasingleKafkaservertoconnecttothatisrunninglocallyonport9092.Also,ittakesasinputagroup.idthatspecifiestheConsumerGroupfortheconsumerandatopictowhichitsubscribesformessages.Oncetheconsumeriscreated,itcanbeusedtolistenformessagesontheconfiguredtopic,asshownhere:

(defncapture-records

"Consumetherecordsusinggivenconsumer"

[consumerresult]

(whiletrue

(doseq[record(.pollconsumer1000)]

(swap!resultconjrecord))

(Thread/sleep5000)))

Thecapture-recordsfunctiontakesasinputaconsumerthatiscreatedbythecreate-kafka-consumerfunction,asdefinedearlier.Thisfunctionisasamplefunctionthatalsotakesanatom(https://clojuredocs.org/clojure.core/atom)asasecondparameterthatjustcapturesthereceivedmessagesthatcanbereferredtolater.Totakealookatthereceivedmessages,theycanalsobeloggedorpassedontoafunctionasaparametertotakefurtheractiononit.Also,notethatthisfunctionhasanever-endingwhileloop,sothismustbecalledonathreadotherthantheapplicationexecutionthreadtomakesurethattheapplicationexecutionthreadisnotblocked.Totestthefunction,usetheREPL,asshowninthefollowingexample,thatinitializestheconsumerthatconnectstothesameKafkaserverthatwasstartedatthecommandlineusingthekafka-server-start.shscript:

;;initializetherequirednamespaces

helping-hands.alert.server>(require'[helping-hands.alert.channel:aschannel])

nil

helping-hands.alert.server>(require'[helping-hands.alert.config:asconf])

nil

;;initializetheconfigurationforOmniconftopick

helping-hands.alert.server>(conf/init-config{:cli-args[]:quit-on-errortrue})

Omniconfconfiguration:

{:conf#object[java.io.File0x5fa5f9a0"config/conf.edn"],

:kafka

{"bootstrap.servers""localhost:9092",

"group.id""alerts",

"topic""hh_alerts"}}

nil

Page 339: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

;;createaconsumer

helping-hands.alert.server>(defconsumer(channel/create-kafka-consumer))

#'helping-hands.alert.server/consumer

;;createanatomtocollecttheresponses

helping-hands.alert.server>(defrecords(atom[]))

#'helping-hands.alert.server/records

;;waitforrecords

helping-hands.alert.server>(channel/consume-recordsconsumerrecords)

Oncetheconsumerislisteningformessages,ontheterminal,startanewproducerforthesamehh_alertstopicthattheconsumerislisteningtousingthekafka-console-producer.shscript,asshowninthefollowingexample.Notethatwedidn'tcreatethetopicbutitisstillavailable.ThereasonisthedefaultconfigurationofKafkaallowsittoauto-createthetopicifitdoesnotexist.Theothertopic,__consumer_offsets,isusedbyKafkatomanagethecommittedoffsets:

%bin/kafka-topics.sh--list--zookeeperlocalhost:2181

__consumer_offsets

hh_alerts

test

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topichh_alerts

>SampleAlert

>SampleAlert1

Publishacoupleofmessagesfromtheproducer,asshownintheprecedingexample,andchecktherecordsatominREPL.Itshouldhavereceivedthemessagesasfollows:

;;C-c-binCIDERbreakstheexecutiontogivebackthecontroltoREPL

;;lookupthepublishedmessages

helping-hands.alert.server>(pprint(map#(.value%)@records))

("SampleAlert""SampleAlert1")

nil

helping-hands.alert.server>

Page 340: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

InitializingKafkawithMountInthepreviousimplementation,theKafkaconsumerwascreatedbycallingthecreate-kafka-consumerfunction.Insteadofcreatingaconsumerbycallingthefunctionatruntime,itcanbecreatedandmanagedusingMount,asdiscussedinthepreviouschapter.TouseMount,makesurethattheMountdependencyisaddedtotheproject.cljfile,asshownhere:

(defprojecthelping-hands-alert"0.0.1-SNAPSHOT"

:description"HelpingHandsAlertApplication"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

[io.pedestal/pedestal.jetty"0.5.3"]

;;DatomicFreeEdition

[com.datomic/datomic-free"0.9.5561.62"]

;;Omniconf

[com.grammarly/omniconf"0.2.7"]

;;Mount

[mount"0.1.11"]

;;postalforemailalerts

[com.draines/postal"2.0.2"]

;;kafkaclients

[org.apache.kafka/kafka-clients"1.0.0"]

;;logger

[org.clojure/tools.logging"0.4.0"]

[ch.qos.logback/logback-classic"1.1.8":exclusions[org.slf4j/slf4j-

api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

:min-lein-version"2.0.0"

:source-paths["src/clj"]

:java-source-paths["src/jvm"]

:test-paths["test/clj""test/jvm"]

...

:main^{:skip-aottrue}helping-hands.alert.server)

Next,asshowninthefollowingexample,createanamespaceanddefinethestateoftheKafkaconsumerthatcreatesaKafkaconsumertobeusedbytheAlertsmicroserviceandclosesitwhentheserviceisshutdown:

(nshelping-hands.alert.state

"InitializesStateforAlertService"

(:require[mount.core:refer[defstate]:asmount]

[helping-hands.alert.channel:asc]))

(defstatealert-consumer

:start(c/create-kafka-consumer)

:stop(.closealert-consumer))

Page 341: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Next,addthestartupandshutdownhooksforMountattheapplicationentrypoints,asshowninthefollowingexamplefortheAlertsmicroservice:

(nshelping-hands.alert.server

(:gen-class);for-mainmethodinuberjar

(:require[io.pedestal.http:asserver]

[io.pedestal.http.route:asroute]

[mount.core:asmount]

[helping-hands.alert.config:ascfg]

[helping-hands.alert.service:asservice]))

...

(defnrun-dev

"Theentry-pointfor'leinrun-dev'"

[&args]

(println"\nCreatingyour[DEV]server...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

(Thread.mount/stop))

(->service/service;;startwithproductionconfiguration

...)

;;Wireupinterceptorchains

server/default-interceptors

server/dev-interceptors

server/create-server

server/start))

(defn-main

"Theentry-pointfor'leinrun'"

[&args]

(println"\nCreatingyourserver...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

(Thread.mount/stop))

(server/startrunnable-service))

NotethatOmniconfconfigurationisinitializedbeforeMountisstartedtomakesurethattheconsumerisabletopicktheconfigurationparametersthatarereadbyOmniconf.OnceMountisinitialized,thekafka-consumerstatecanbeuseddirectlyacrossthenamespaces.Forexample,thefollowingREPLsessionshowshowtoinitializetheAlertserviceindevmodeandusethekafka-consumerstatemanagedbyMountthatisstartedwiththedevinstanceoftheservice:

;;startsserviceindevmode

;;alsoinitializesOmniconfand

;;setstheKafkaconsumerstate

Page 342: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

helping-hands.alert.server>(defserver(run-dev))

Creatingyour[DEV]server...

Omniconfconfiguration:

{:alert

{:from"[email protected]",

:host"smtp.gmail.com",

:port465,

:ssltrue,

:to"[email protected]",

:user"[email protected]",

:creds<SECRET>},

:conf#object[java.io.File0x266875c9"config/conf.edn"],

:kafka

{"bootstrap.servers""localhost:9092",

"group.id""alerts",

"topic""hh_alerts"}}

#'helping-hands.alert.server/server

;;refertotheconsumerstatemanagedbymount

helping-hands.alert.server>(require'[helping-hands.alert.state:refer[alert-

consumer]])

nil

;;createaatomtocapturerecords

helping-hands.alert.server>(defrecords(atom[]))

#'helping-hands.alert.server/records

;;lookformessagestocapture

helping-hands.alert.server>(helping-hands.alert.channel/capture-recordsalert-

consumerrecords)

Now,publishacoupleofmessagesfromthecommand-lineproducer,asshownhere:

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topichh_alerts

>Hello

>HiMount!

>

Thesamemessagesarecapturedwithintherecordsatom,asshowninthefollowingexample.Atthetimeofshutdown,Mountwillstoptheconsumerandcleanuptheconnection:

;;C-c-binCIDERbreakstheexecutiontogivebackthecontroltoREPL

;;lookupthepublishedmessages

helping-hands.alert.server>(pprint(map#(.value%)@records))

("Hello""HiMount!")

nil

helping-hands.alert.server>

Page 343: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntegratingtheAlertServicewithKafka

TheAlertmicroserviceoftheHelpingHandsapplicationreceivesthealertmessagesovertheKafkatopicthatisusedtosendthealertinanemail.Tosendanemailassoonasthemessageisreceived,itcanbeintegratedwithintheloopthatlooksforamessagepublishedbytheproducer,asshownhere:

(defnconsume-records

"Consumetherecordsusinggivenconsumer"

[consumerresult]

(whiletrue

(doseq[record(.pollconsumer1000)]

(try

(let[rmsg(jp/parse-string(.valuerecord))

msg(into{}(filter(compsome?val)

{:from(conf/get-config[:alert:from])

:to(getrmsg"to"(conf/get-config[:alert:to]))

:cc(rmsg"cc")

:subject(rmsg"subject")

:body(rmsg"body")}))

result(postal/send-message

{:host(conf/get-config[:alert:host])

:port(conf/get-config[:alert:port])

:ssl(conf/get-config[:alert:ssl])

:user(conf/get-config[:alert:user])

:pass(conf/get-config[:alert:creds])}

msg)])

(catchExceptione

(log/error"Failedtosendemail"e)))

(swap!resultconjrecord))

(Thread/sleep5000)))

Intheprecedingfunction,oncethemessageisreceived,itisparsedtogettheJSONwiththekeyssuchasto,cc,subject,andbodythatareusedtocreateanemailandsenditusingthePostallibrary(https://github.com/drewr/postal)thatwasdiscussedinpreviouschapters.Alltheexceptionsarecaughtandloggedforreview.Inthiscase,theproducerpublishesastringifiedJSONwiththerequiredkeys,asshownhere:

%bin/kafka-console-producer.sh--broker-listlocalhost:9092--topichh_alerts

>{"to":"[email protected]","subject":"UsageAlert","body":"Usagealertexceeded

threshold100kreq/sec"}

Page 344: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 345: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingAvrofordatatransfer

ThekeysandvaluespublishedonaKafkatopicmusthaveassociatedSerDes(https://en.wikipedia.org/wiki/SerDes).TheexamplesusedintheprevioussectionusedLongDeserializerforkeysandStringDeserializerfortheKafkaConsumer.Similarly,theKafkaProducerforthecorrespondingconsumerwilluseLongSerializerandStringSerializertopublishthekeyandvalue,respectively.SincemicroservicesmaybewritteninanyprogramminglanguageandmayneedtocollaboratewithotherservicesoverKafkatopics,thelanguage-dependentSerDesisnotagoodoption.

Avro(https://avro.apache.org/)isadataserializationformatthatislanguageagnosticandhassupportformostofthewell-knownprogramminglanguages(https://cwiki.apache.org/confluence/display/AVRO/Supported+Languages).Avrohasitsowndeclarativewayofdefiningtheschema(https://avro.apache.org/docs/current/)thatcanbemappedtothebusinessmodeldescribingtheentity.Oncetheschemaisdefined,themessageisencodedagainsttheschemaattheproducerendandthendecodedattheconsumerendusingthesameschema.Asfarastheschemaisaccessibletobothproducerandconsumer,theycancommunicateviaAvromessagesirrespectiveoftheprogramminglanguagetheyareimplementedin.

AvroClojurelibraryabracad(https://github.com/damballa/abracad)isawrapperoverAvroAPIsthatintegrateswellwiththeapplicationswritteninClojure.Touseabracad,includethedependency[com.damballa/abracad"0.4.13"]intheproject.cljfile.Oncethedependenciesareavailable,theSerDesforAvrocanbedefinedasshowninthefollowingexample,andcanbeusedinsteadofStringSerDesbytheKafkaproducerandconsumer:;;adoptedfromfranzy-avroproject;;https://github.com/ymilky/franzy-avro(deftypeKafkaAvroSerializer[schema]

Serializer

(configure[___])

Page 346: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(serialize[__data]

(whendata

(avro/binary-encodedschemadata)))(close[_]))

(deftypeKafkaAvroDeserializer[schema]

Deserializer

(configure[___])

(deserialize[__data]

(whendata

(avro/decodeschemadata)))(close[_]))

(defnkafka-avro-serializer"AvroserializerforApacheKafka.UseforserializingKafkakeysvalues.

Valueswillbeserializedaccordingtotheprovidedschema.

Ifnoschemaisprovided,adefaultEDNschemaisassumed.

Seehttps://avro.apache.org/

Seehttps://github.com/damballa/abracad"

[schema]

(KafkaAvroSerializer.(orschema(aedn/new-schema))))

Page 347: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defnkafka-avro-deserializer"AvrodeserializerforApacheKafka.

UsefordeserializingKafkakeysandvalues.

Ifnoschemaisprovided,adefaultEDNschemaisassumed.

Seehttps://avro.apache.org/

Seehttps://github.com/damballa/abracad"

[schema]

(KafkaAvroDeserializer.(orschema(aedn/new-schema))))

Page 348: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,welearnedabouttheimportanceofevent-drivenpatternsformicroservicesandhowwecanuseApacheKafkaasamessagebrokertobuildascalableanddurableevent-drivenarchitecture.Event-drivenarchitecturesarescalable,buttheyareincrediblyhardtodebugforissueswithoutbeingmonitoredinrealtime.Inthenextchapter,wewilllearnhowtosecuremicroservicesanddeploytheminproductionwithareal-timemonitoringsystem.

Page 349: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DeployingandMonitoringSecuredMicroservices

"Thesuccessofaproductiondependsontheattentionpaidtodetail."

-DavidO.Selznick

Microservicesmustbedeployedinisolationandmonitoredforusage.Monitoringthecurrentworkloadandprocessingtimealsohelpstotakeadecisiononwhentoscalethemuporscalethemdown.Anotherimportantaspectofmicroservices-basedarchitectureissecurity.Onewaytosecuremicroservicesistoalloweachoneofthemtohavetheirownauthenticationandauthorizationmodule.Thisapproachsoonbecomesaproblem,aseachmicroserviceisdeployedinisolation,anditbecomesincrediblyhardtoagreeoncommonstandardstoauthorizeauser.Also,inthiscase,theownershipofusersandtheirrolesgetsdistributedacrosstheservices.Thischapteraddressessuchissuesandprovidessolutionstosecure,monitor,andscalemicroservices-basedapplications.Inthischapter,youwilllearnthefollowingthings:

HowtoenableauthenticationandauthorizationformicroservicesHowtouseJSONWebToken(JWT)andJSONWebEncryption(JWE)HowtocreateanauthenticationservicethatworkswithJSONWebTokensHowtocaptureauditlogsandruntimemetricsforreal-timemonitoringHowtodeploymicroservicesusingDockercontainersWhyKubernetesisusefulformicroservices-baseddeployments

Page 350: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

EnablingauthenticationandauthorizationAuthenticationistheprocessofidentifyingwhotheuseris,whereasauthorizationistheprocessofverifyingwhattheauthenticateduserhasaccessto.Themostcommonwayofachievingauthenticationisbyaskinguserstospecifytheirusernameandpasswordthatcanthenbevalidatedagainstthebackenddatabaseofusercredentials.

Thepasswordsshouldneverbestoredinplaintextinthebackenddatabase.Itisrecommendedtocomputeaone-wayhashofthepasswordandstorethatinstead.Toresetthepassword,thesystemcanjustgeneratearandompassword,storeitshash,andsharetherandompasswordinplaintextwiththeuser.Alternatively,auniqueURLcanbesenttotheusertoresetthepasswordthroughaformthatcanvalidateauser'sidentityviamethodssuchaspresetquestionsandanswersandone-timepassword(OTP).

Authenticatingtheusersisnotenoughforanapplicationiftheapplicationhasmultiplesecurityboundaries.Forexample,anapplicationmayrequireonlycertainuserstosendnotificationthroughthesystemandpreventallothers.Todoso,theapplicationmustcreateasecurityboundaryforitsresourcesthatisoftendefinedusingrolesthathaveoneormorepermissionsthatcanbevalidatedbytheapplicationbeforeallowingaccesstoitsresourcesandfeatures,suchasnotification.Rolesandpermissionsarethekeyfactorsofauthorizationthatallowanapplicationtocreatemultiplesecurityboundariesforitsresources.

Page 351: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroducingTokensandJWTInamonolithicenvironment,authenticationandauthorizationarehandledwithinthesameapplicationusingamodulethatvalidatestheincomingrequestsforrequiredauthenticationandauthorizationinformation,asshowninthefollowingdiagram;thismodulealsoallowstheauthorizeduserstodefinetherolesandpermissionsandassignthemtootherusersinthesystemtoallowthemaccesstothesecuredresources:

Monolithicapplicationsmayalsomaintainasessionstoreagainstwhicheachinstanceofthemonolithicapplicationcanvalidatetheincomingrequestanddetermineavalidsessionfortheuser.Often,suchsessioninformationisstoredinacookiethatissenttotheclientasatokenoncetheuserissuccessfullyauthenticated.Thecookieisthenattachedtoeachrequestbytheclientthatcanthenbevalidatedbytheserverforavalidsessionandassociatedrolestodeterminewhethertoallowordisallowaccesstotherequestedresource,asshownintheprecedingdiagram.

Inamicroservices-basedapplication,eachmicroserviceisdeployedinisolationandmustnothavetheresponsibilityofmaintainingaseparateuserdatabaseorsessiondatabase.Moreover,theremustbeastandardwayofauthenticatingandauthorizingtheusersacrossmicroservices.ItisrecommendedtoseparateouttheauthenticationandauthorizationresponsibilityasaseparateAuthservicethatcanowntheuserdatabasetoauthenticateandauthorizeusers.ThisalsohelpsinauthenticatingtheuseronceviaAuthserviceandthenauthorizingthemto

Page 352: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

accesstheresourcesandrelatedservicesthroughothermicroservices.

SinceeachmicroservicemayuseitsowntechnologystackandhavenopriorknowledgeofAuthservice,thereshouldbeacommonstandardtovalidatetheauthenticatedusersacrossMicroservices.JSONWebTokens(JWT)isonesuchstandardthatconsistsofaheader,payload,andsignaturethatcanbeissuedasatokentotheuseraftersuccessfulauthentication.Userscanthensendthistokenwitheachrequesttoanymicroservicethatcanthenvalidateitandgrantaccesstotherequestedresources.

JWTcaneitherhavethecontentencryptedorsecuredusingdigitalsignatureormessageauthenticationcodes.JSONWebSignature(JWS)representsthecontentsecuredwithdigitalsignaturesorMessageAuthenticationCodes(MACs),whereasJSONWebEncryption(JWE)representstheencryptedcontentusingJSON-baseddatastructures.Ifthetokenisencrypted,itcanonlybereadwithakeythatwasusedtoencryptthetoken.ToreadaJWEtoken,servicesmustownthekeythatwasusedtoencryptthetoken.Insteadofsharingthekeyacrossmicroservices,itisrecommendedtosendthetokentotheAuthservicedirectlytodecryptthetokenandauthorizetherequestonbehalfoftheservice.ThismayresultinaperformancebottleneckandsinglepointoffailureduetoeachservicetryingtogetAuthservicefirstforauthorization.Thiscanbepreventedbycachingtheprevalidatedtokensateachmicroservicelevelforaconfigurableamountoftimethatcanbedecidedbasedontheexpirytimeofthe

Page 353: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

token.

ExpirytimeisanimportantcriteriawhileworkingwithJWTs.JWTswithaverylargeexpirytimemustbeavoided,asthereisnowayfortheapplicationtologouttheuserorinvalidatethetoken.Anissuedtokenremainsvalidunlessanduntilitexpires.Asfarastheuserownsavalidtoken,theyareallowedtogainaccesstotheserviceswiththeissuedtoken.Topreventtheissueoflogout,oneoptionistoletmicroservicesalwaysvalidateatokenwiththeAuthservicethatmaintainsacacheofuserauthorizationdetailsthatarekeptinsyncwiththeuser'srolesandpermissions.EverytimeanAuthservicereceivesatoken,itcanvalidateitagainstthiscache,andifthereischangeinuserrolesoranyotherproperties,itcaninvalidatethetokenthatwillforcetheusertorequestforanewtoken,andthattokenwillnowhavetheupdatedrolesandtheauthorizationdetails.

Formoredetails,refertoJWTRFC-7519(https://tools.ietf.org/html/rfc7519),JWSRFC-7515(https://tools.ietf.org/html/rfc7515),andJWERFC-7516(https://tools.ietf.org/search/rfc7516).

Page 354: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatinganAuthserviceforHelpingHands

TheAuthserviceforHelpingHandscanbebuiltusingthesamepedestalprojecttemplateasthatofothermicroservicesofHelpingHands.Inthisexample,itusesJWEtocreateJWTtokensfortheusers.Tostartwith,createanewprojectwiththedirectorystructureasshowninthefollowingexample;itcontainsanewnamespacehelping-hands.auth.jwtthatcontainstheimplementationrelatedtoJWT—therestofthenamespacesareusedasdescribedintheprecedingchapters.

.

├──Capstanfile

├──config

│├──conf.edn

│└──logback.xml

├──Dockerfile

├──project.clj

├──README.md

├──resources

├──src

│├──clj

││└──helping_hands

││└──auth

││├──config.clj

││├──core.clj

││├──jwt.clj

││├──persistence.clj

││├──server.clj

││├──service.clj

││└──state.clj

│└──jvm

└──test

├──clj

│└──helping_hands

│└──auth

│├──core_test.clj

│└──service_test.clj

└──jvm

12directories,14files

Page 355: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingaNimbusJOSEJWTlibraryforTokens

TheAuthserviceprojectwilladditionallyuseaNimbus-JOSE-JWTlibrary(https://bitbucket.org/connect2id/nimbus-jose-jwt/wiki/Home)tocreateandvalidateJSONWebTokensandapermissions(https://github.com/tuhlmann/permissions)librarytoauthorizeusersagainstasetofrolesandpermissions.AddtheNimbus-JOSE-JWTandpermissionslibrarydependencies,asshowninthefollowingproject.cljfile:

(defprojecthelping-hands-auth"0.0.1-SNAPSHOT"

:description"HelpingHandsAuthService"

:url"https://www.packtpub.com/application-development/microservices-clojure"

:license{:name"EclipsePublicLicense"

:url"http://www.eclipse.org/legal/epl-v10.html"}

:dependencies[[org.clojure/clojure"1.8.0"]

[io.pedestal/pedestal.service"0.5.3"]

[io.pedestal/pedestal.jetty"0.5.3"]

;;DatomicFreeEdition

[com.datomic/datomic-free"0.9.5561.62"]

;;Omniconf

[com.grammarly/omniconf"0.2.7"]

;;Mount

[mount"0.1.11"]

;;nimbus-joseforJWT

[com.nimbusds/nimbus-jose-jwt"5.4"]

;;usedforrolesandpermissions

[agynamix/permissions"0.2.2-SNAPSHOT"]

;;logger

[org.clojure/tools.logging"0.4.0"]

[ch.qos.logback/logback-classic"1.1.8"

:exclusions[org.slf4j/slf4j-api]]

[org.slf4j/jul-to-slf4j"1.7.22"]

[org.slf4j/jcl-over-slf4j"1.7.22"]

[org.slf4j/log4j-over-slf4j"1.7.22"]]

:min-lein-version"2.0.0"

:source-paths["src/clj"]

:java-source-paths["src/jvm"]

:test-paths["test/clj""test/jvm"]

:resource-paths["config","resources"]

:plugins[[:lein-codox"0.10.3"]

;;CodeCoverage

[:lein-cloverage"1.0.9"]

;;Unittestdocs

[test2junit"1.2.2"]]

:codox{:namespaces:all}

:test2junit-output-dir"target/test-reports"

:profiles{:provided{:dependencies[[org.clojure/tools.reader"0.10.0"]

[org.clojure/tools.nrepl"0.2.12"]]}

:dev{:aliases

Page 356: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

{"run-dev"["trampoline""run""-m"

"helping-hands.auth.server/run-dev"]}

:dependencies

[[io.pedestal/pedestal.service-tools"0.5.3"]]

:resource-paths["config","resources"]

:jvm-opts["-Dconf=config/conf.edn"]}

:uberjar{:aot[helping-hands.auth.server]}

:doc{:dependencies[[codox-theme-rdash"0.1.1"]]

:codox{:metadata{:doc/format:markdown}

:themes[:rdash]}}

:debug{:jvm-opts

["-server"(str"-agentlib:jdwp=transport=dt_socket,"

"server=y,address=8000,suspend=n")]}}

:main^{:skip-aottrue}helping-hands.auth.server)

Page 357: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingasecretkeyforJSONWebEncryptionTostartwiththeimplementationofJWTwithencryptedclaims,firstcreateaget-secretfunctiontogenerateasecretkeyforencryption.Also,addaget-secret-jwkfunctionthatisusedtocreateaJSONWebKey(https://tools.ietf.org/html/rfc7517)usingthesecretkeygeneratedbytheget-secretfunction,asshowninthefollowingcode:

(nshelping-hands.auth.jwt

"JWTImplementationforAuthService"

(:require[cheshire.core:asjp])

(:import[com.nimbusds.joseEncryptionMethod

JWEAlgorithmJWSAlgorithm

JWEDecrypterJWEEncrypter

JWEHeader$BuilderJWEObjectPayload]

[com.nimbusds.jose.crypto

AESDecrypterAESEncrypter]

[com.nimbusds.jose.jwkKeyOperationKeyUse

OctetSequenceKeyOctetSequenceKey$Builder]

[com.nimbusds.jwtJWTClaimsSetJWTClaimsSet$Builder]

[com.nimbusds.jwt.procDefaultJWTClaimsVerifier]

[com.nimbusds.jose.utilBase64URL]

[java.utilDate]

[javax.cryptoKeyGenerator]

[javax.crypto.specSecretKeySpec]))

(def^:conskhash-256"SHA-256")

(defonce^:privatekgen-aes-128

(let[keygen(KeyGenerator/getInstance"AES")

_(.initkeygen128)]

keygen))

(defonce^:privatealg-a128kw

(JWEAlgorithm/A128KW))

(defonce^:privateenc-a128cbc_hs256

(EncryptionMethod/A128CBC_HS256))

(defnget-secret

"Getsthesecretkey"

([](get-secretkgen-aes-128))

([kgen]

;;mustbecreatediffthekeyhasn't

;;beencreaedearlier.Createonceand

;;persistinanexternaldatabase

(.generateKeykgen)))

(defnget-secret-jwk

"GeneratesanewJSONWebKey(JWK)"

[{:keys[khashkgenalg]:asenc-impl}secret]

Page 358: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

;;mustbecreatediffthekeyhasn't

;;beencreaedearlier.Createonceand

;;persistinanexternaldatabase

(..(OctetSequenceKey$Builder.secret)

(keyIDFromThumbprint(orkhashkhash-256))

(algorithm(oralgalg-a128kw))

(keyUse(KeyUse/ENCRYPTION))

(build)))

TheprecedingimplementationshowngeneratesakeyusingtheAES128-bitalgorithm.Thesecretkeygeneratedbytheget-secretfunctionmustbegeneratedonlyonceforthelifetimeoftheapplication.Therefore,itisrecommendedtostoreitinanexternaldatabasethatcanbesharedamongtheinstancesoftheAuthserviceonceitisscaledtomorethanoneinstance.

Nimbus-JOSE-JWTalsosupports256-bitalgorithms.For256-bitalgorithmstowork,JREneedsexplicitJavaCryptographyExtension(JCE)UnlimitedStrengthJurisdictionPolicyFiles(http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html).

Theget-secret-jwkfunctiontakesthesecretkeyasoneofitsinputparametersandgeneratesaJWK,asshowninthefollowingREPLsession;JWKconsistsofaKeyType(kty),PublicKeyUse(use),KeyID(kid),KeyValue(k),andAlgorithm(alg)parametersthataredefinedinJWKRFC-7517(https://tools.ietf.org/html/rfc7517):

;;requirethenamespace

helping-hands.auth.server>(require'[helping-hands.auth.jwt:asjwt])

nil

;;createasecretkey

helping-hands.auth.server>(defsecret(jwt/get-secret))

#'helping-hands.auth.server/secret

;;createaJSONWebKey

helping-hands.auth.server>(defjwk(jwt/get-secret-jwk{}secret))

#'helping-hands.auth.server/jwk

;;dumptheJSONobjectofJWK

helping-hands.auth.server>(.toJSONObjectjwk)

{"kty""oct","use""enc","kid""F5UNJYT4A-GpngZwRMYfs8ZuCKsmRGt08Xo_dMQrY5w","k"

"CvTaCBfdEkAlXfuOnW7pnw","alg""A128KW"}

SinceJWKisjustarepresentationofthesecretkeyinaJSONformat,thesecretkeycanberetrievedfromtheJWKusingautilityfunction,enckey->secret,asshowninthefollowingimplementation:

(defnenckey->secret

"ConvertsJSONWebKey(JWK)tothesecretkey"

[{:keys[kkidalg]:asenc-key}]

(..(OctetSequenceKey$Builder.k)

Page 359: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(keyIDkid)

(algorithm(oralgalg-a128kw))

(keyUse(KeyUse/ENCRYPTION))

(build)

(toSecretKey"AES")))

Theenckey->secretfunctiontakesKeyID(kid)andKeyValue(k)asitsinputtocreatethesecretkeythatissameastheoneusedtocreatethesourceJSONWebKey.ThealgparameterisoptionalandfallsbacktothedefaultAES-128algorithmifitisnotspecified.ThefollowingREPLsessionshowshowtocreateasecretkeyfromJWKgeneratedearlierandvalidatethatitalwaysgeneratesthesameJWK:

;;JSONWebKey(JWK)generatedearlier

helping-hands.auth.server>(.toJSONObjectjwk)

{"kty""oct","use""enc","kid""F5UNJYT4A-GpngZwRMYfs8ZuCKsmRGt08Xo_dMQrY5w","k"

"CvTaCBfdEkAlXfuOnW7pnw","alg""A128KW"}

;;extractthesecretkey

helping-hands.auth.server>(defsecret-extracted(jwt/enckey->secret{:k(.getKeyValue

jwk):kid(.getKeyIDjwk)}))

#'helping-hands.auth.server/secret-extracted

;;generateJSONWebKeythatisexactlysameassource

helping-hands.auth.server>(.toJSONObject(jwt/get-secret-jwk{}secret-extracted))

{"kty""oct","use""enc","kid""F5UNJYT4A-GpngZwRMYfs8ZuCKsmRGt08Xo_dMQrY5w","k"

"CvTaCBfdEkAlXfuOnW7pnw","alg""A128KW"}

helping-hands.auth.server>(.toJSONObjectjwk)

{"kty""oct","use""enc","kid""F5UNJYT4A-GpngZwRMYfs8ZuCKsmRGt08Xo_dMQrY5w","k"

"CvTaCBfdEkAlXfuOnW7pnw","alg""A128KW"}

Page 360: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingTokensThenextstepistodefinethefunctionstocreateandreadJWT.SincetheJWTusedfortheHelpingHandsapplicationusesJWEtoencrypttheclaims,itisOKtoaddbothuserIDandrolesinformationwithinthepayloadthatcanbelaterretrievedfromavalidtokentoauthorizetheuser.

Thecreate-tokenandread-tokenfunctionsshowninthefollowingexampleprovideawaytocreateaJSONWebTokenandreadanexistingone,respectively.Thecreate-tokenfunctionusesautilityfunction—create-payload—tocreatetheclaimsetandthepayloadofJWT.ClaimsetsthatarerelevantforthecurrentexampleareissueTimethatdefinestheepochtimeofwhenthistokenwascreated,expirationTimethatsetsthetimebeyondwhichthetokenwillbeconsideredasexpired,anduserandrolescustomclaimsthatstoretheauthenticatedusernameandtherolesassignedtotheuseratthetimeofissuingthetoken.Formoredetailsontheavailableclaimsetoptions,takealookatJWTRFC-7519(https://tools.ietf.org/html/rfc7519).

(defn-create-payload

"CreatesapayloadasJWTClaims"

[{:keys[userroles]:asparams}]

(let[ts(System/currentTimeMillis)

claims(..(JWTClaimsSet$Builder.)

(issuer"Packt")

(subject"HelpingHands")

(audience"https://www.packtpub.com")

(issueTime(Date.ts))

(expirationTime(Date.(+ts120000)))

(claim"user"user)

(claim"roles"roles)

(build))]

(.toJSONObjectclaims)))

(defncreate-token

"Createsanewtokenwiththegivenpayload"

[{:keys[userrolesalgenc]:asparams}secret]

(let[enckey(get-secret-jwkparamssecret)

payload(create-payload{:useruser:rolesroles})

passphrase(JWEObject.

(..(JWEHeader$Builder.

(oralgalg-a128kw)

(orencenc-a128cbc_hs256))

(build))

(Payload.payload))

encrypter(AESEncrypter.enckey)

_(.encryptpassphraseencrypter)]

(.serializepassphrase)))

Page 361: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(defnread-token

"Decryptsthegiventokenwiththesaidalgorithm

ThrowsBadJWTExceptionistokenisinvalidorexpired"

[tokensecret]

(let[passphrase(JWEObject/parsetoken)

decrypter(AESDecrypter.secret)

_(.decryptpassphrasedecrypter)

payload(..passphrasegetPayloadtoString)

claims(JWTClaimsSet/parsepayload)

;;throwsexceptionifthetokenisinvalid

_(.verify(DefaultJWTClaimsVerifier.)claims)]

(jp/parse-stringpayload)))

ThefollowingREPLsessionshowsthestepstocreateandreadatokenandlaterwaitforittogetexpired.Notetheexceptionthrownbythelibrarythatcanbecapturedtomarktheeventoftokenexpiry:

;;generateanewtokenwiththeuserandroles

helping-hands.auth.server>(deftoken(jwt/create-token{:user"hhuser":roles#

{"hh/notify"}}secret))

#'helping-hands.auth.server/token

;;dumpthecompactserializationstring

helping-hands.auth.server>token

"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.FiAelEg_R8We8xEF2xRxcC908BCoH1nRYvY3nV_jkqYO8JPp-

QukBw.86-

JKq6cYFH2rtFBOXiA6A.Pxz3ZzBGKX2Cd_sjtYdEwKDltzKQiolWSvrjPbLLGL8NlShcWWEIqkd7NL2WcXHukDa6zS4ANIWnee2hNWUraItqZFEY6N_RhXZVVXQvZJsqzeiueBxvxc1fj1LFUKsyR63oOwLd5ZIIT99ItrqaYPM88enMsjchsXYBJ_Tcb-

WR6R_KirmDBxCVjqFcg7OdWjjcKTP4FcUNIQU9G8fSnQ.pfLyW8ggXV8vQnidytJmMw"

;;readthetokenback

helping-hands.auth.server>(pprint(jwt/read-tokentokensecret))

{"sub""HelpingHands",

"aud""https://www.packtpub.com",

"roles"["hh/notify"],

"iss""Packt",

"exp"1515959756,

"iat"1515959636,

"user""hhuser"}

nil

;;waitfor2mins(expirytimeasperimplementation)

;;tokenisnowexpired

helping-hands.auth.server>(pprint(jwt/read-tokentokensecret))

BadJWTExceptionExpiredJWTcom.nimbusds.jwt.proc.DefaultJWTClaimsVerifier.<clinit>

(DefaultJWTClaimsVerifier.java:62)

Page 362: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

EnablingusersandrolesforauthorizationIdeally,theAuthservicemustbebackedbyapersistentstoretokeeptheusers,roles,andthesecretkeyfortheapplication.Forthesakeofsimplicityoftheexample,createasamplein-memorydatabaseinthehelping-hands.auth.persistencenamespace,asfollows:

(nshelping-hands.auth.persistence

"PersistenceImplementationforAuthService"

(:require[agynamix.roles:asr]

[cheshire.core:asjp])

(:import[java.securityMessageDigest]))

(defnget-hash

"CreatesaMD5hashofthepassword"

[creds]

(..(MessageDigest/getInstance"MD5")

(digest(.getBytescreds"UTF-8"))))

(defuserdb

;;Usedonyfordemonstration

;;TODOPersistinanexternaldatabase

(atom

{:secretnil

:roles{"hh/superadmin""*"

"hh/admin""hh:*"

"hh/notify"#{"hh:notify""notify/alert"}

"notify/alert"#{"notify:email""notify:sms"}}

:users{"hhuser"{:pwd(get-hash"hhuser")

:roles#{"hh/notify"}}

"hhadmin"{:pwd(get-hash"hhadmin")

:roles#{"hh/admin"}}

"superadmin"{:pwd(get-hash"superadmin")

:roles#{"hh/superadmin"}}}}))

(defnhas-access?

"Checksforrelevantpermission"

[uidperms]

(r/has-permission?

(->@userdb:users(getuid))

:roles:permissionsperms))

(defninit-db

"Initializestherolesforpermissionframework"

[]

(r/init-roles(:roles@userdb))

userdb)

userdbcontainsthesamplein-memorydatabasethatconsistsofthe:secretkeythatisinitializedtoniland:usersand:rolesthatcontaininformationontheusers

Page 363: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

androles,respectively.Roledefinitionfollowstheguidelinesofthepermissionlibraryanddefinestherolesandpermissionsaspertheusageinstructions(https://github.com/tuhlmann/permissions#usage)ofthelibrary.Roleshaveaslash,/,intheirnameandpermissionshaveacolon,:,asdefinedintheprecedingroledefinition.Roledefinitionsarerecursive,andonerolecanencapsulatebothrolesandpermissions.

Theinit-dbfunctionisusedtoinitializethedatabaseandtheroledefinitions.Thehas-access?isautilityfunctionthatcanbeusedtovalidatewhetherausercontainsagivensetofpermissionsornot.ThefollowingREPLsessiondescribestheuseofthehas-access?functionwithanexample:

;;requirethepersistencenamespace

helping-hands.auth.server>(require'[helping-hands.auth.persistence:asp])

nil

;;sincethereisnosecretkeydefine,

;;initializethedatabasewithasecret-key

;;ifitdoesnotexist

helping-hands.auth.server>(let[db(p/init-db)]

;;ifkeydoesnotexist,initializeone

;;andupdatethedatabasewith:secretkey

(if-not(:secret@db)

(swap!db#(assoc%:secret(jwt/get-secret)))@db))

{:secret#object[javax.crypto.spec.SecretKeySpec0xebc150b

"javax.crypto.spec.SecretKeySpec@17ce8"],:roles{"hh/superadmin""*","hh/admin"

"hh:*","hh/notify"#{"notify/alert""hh:notify"},"notify/alert"#{"notify:email"

"notify:sms"}},:users{"hhuser"{:pwd#object["[B"0x1b46ced7"[B@1b46ced7"],:roles

#{"hh/notify"}},"hhadmin"{:pwd#object["[B"0x7b9083e6"[B@7b9083e6"],:roles#

{"hh/admin"}},"superadmin"{:pwd#object["[B"0x64083ac1"[B@64083ac1"],:roles#

{"hh/superadmin"}}}}

;;validatethat`hhuser`hasthe``hh:notify``permission

helping-hands.auth.server>(p/has-access?"hhuser"#{"hh:notify"})

true

;;validatepermissionsthatarenotdefined

helping-hands.auth.server>(p/has-access?"hhuser"#{"hh:admin"})

false

;;validatepermissionsthatareobtainedbyotherrolereferences

helping-hands.auth.server>(p/has-access?"hhuser"#{"hh:notify""notify:email"})

true

TheprecedingexampleexplicitlyinitializesthedatabaseatREPLandsetsthesecretkey.Insteadofexplicitlyinitializingthedatabase,itcanbedoneatthestartupitselfusingmount,asdiscussedinChapter9,ConfiguringMicroservices.Toallowmounttoinitializethestateofthedatabasewiththesecretkeyandmakeitavailableforothernamespaces,definethedatabasestateinthehelping-hands.auth.statenamespace,asfollows:

Page 364: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(nshelping-hands.auth.state

"InitializesStateforAuthService"

(:require[mount.core:refer[defstate]:asmount]

[helping-hands.auth.jwt:asjwt]

[helping-hands.auth.persistence:asp]))

(defstateauth-db

:start(let[db(p/init-db)]

;;ifkeydoesnotexist,initializeone

;;andupdatethedatabasewith:secretkey

(if-not(:secret@db)

(swap!db#(assoc%:secret(jwt/get-secret)))@db))

:stopnil)

Next,enablethestartandstopeventsbyaddingmount/startandmount/stopfunctionstotheserverstartupfunctionsinthehelping-hands.auth.servernamespace,asshowninthefollowingexample:

(nshelping-hands.auth.server

(:gen-class);for-mainmethodinuberjar

(:require[io.pedestal.http:asserver]

[io.pedestal.http.route:asroute]

[mount.core:asmount]

[helping-hands.auth.config:ascfg]

[helping-hands.auth.service:asservice]))

;;Thisisanadaptedservicemap,thatcanbestartedandstopped

;;FromtheREPLyoucancallserver/startandserver/stoponthisservice

(defoncerunnable-service(server/create-serverservice/service))

(defnrun-dev

"Theentry-pointfor'leinrun-dev'"

[&args]

(println"\nCreatingyour[DEV]server...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

(Thread.mount/stop))

(->service/service;;startwithproductionconfiguration

...

;;Wireupinterceptorchains

server/default-interceptors

server/dev-interceptors

server/create-server

server/start))

(defn-main

"Theentry-pointfor'leinrun'"

[&args]

(println"\nCreatingyourserver...")

;;initializeconfiguration

(cfg/init-config{:cli-argsargs:quit-on-errortrue})

;;initializestate

(mount/start)

;;Addshutdown-hook

(.addShutdownHook

(Runtime/getRuntime)

Page 365: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

(Thread.mount/stop))

(server/startrunnable-service))

Page 366: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingAuthAPIsusingPedestalThenextstepistodefineAPIsfortheAuthservicetoauthenticateandauthorizeusers.Addthe/tokensand/tokens/validateroutestothehelping-hands.auth.servicenamespace,asfollows:

(nshelping-hands.auth.service

(:require[helping-hands.auth.core:ascore]

[cheshire.core:asjp]

[io.pedestal.http:ashttp]

[io.pedestal.http.route:asroute]

[io.pedestal.http.body-params:asbody-params]

[io.pedestal.interceptor.chain:aschain]

[ring.util.response:asring-resp]))

;;Defines"/"and"/about"routeswiththeirassociated:gethandlers.

;;Theinterceptorsdefinedaftertheverbmap(e.g.,{:gethome-page}

;;applyto/anditschildren(/about).

(defcommon-interceptors[(body-params/body-params)http/html-body])

;;Tabularroutes

(defroutes#{["/tokens"

:get(conjcommon-interceptors

`core/validate`core/get-token)

:route-name:token-get]

["/tokens/validate"

:post(conjcommon-interceptors

`core/validate`core/validate-token)

:route-name:token-validate]})

;;Seehttp/default-interceptorsforadditionaloptionsyoucanconfigure

(defservice{:env:prod

::http/routesroutes

::http/resource-path"/public"

::http/type:jetty

::http/port8080

;;Optionstopasstothecontainer(Jetty)

::http/container-options{:h2c?true

:h2?false

:ssl?false}})

TheGET/tokensroutelooksforuidandpwdparametersoravalidauthorizationheadertoprocesstherequest.Iftheuidandpwdparametersarespecifiedandtheyarevalid,aJWTtokenisissuedaspartoftheauthorizationheader.IfanexistingJWTisspecifiedasapartoftheauthorizationheaderintherequest,Authservicereturnstheusernameandtherolesassociatedwithit.

ThePOST/tokens/validaterouteexpectsaformparameter—perms—andavalidauthorizationheaderwithJWTtoauthorizetheuseragainstthegiven

Page 367: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

permissions.ThisendpointisusedbyothermicroservicesoftheHelpingHandsapplicationtoauthorizetheuseragainstthepermissionsrequiredbythemicroservicestoprovideaccesstotheresourcesthatitmanages.Sincepermissionsandrolesaredefinedasstrings,administratorscaninitializetheAuthdatabasewithalltheexpectedrolesandpermissionsandassignthemtouserstoallowordisallowaccesstoservicesoftheapplication.

Theinterceptorsusedfortheroutesdefinedintheprecedingcodesnippetareimplementedinthehelping-hands.auth.corenamespace,asshowninthefollowingexample;thevalidateinterceptorpreparesthe:tx-dataparameterwithalltheavailablerequestparametersandalsovalidatesthepresenceofeitheruidandpwdoranauthorizationheader—ifoneofthemdoesnotexist,itreturnsaHTTP400BadRequestresponse:

(nshelping-hands.auth.core

"InitializesHelpingHandsAuthService"

(:require[cheshire.core:asjp]

[clojure.string:ass]

[helping-hands.auth.jwt:asjwt]

[helping-hands.auth.persistence:asp]

[helping-hands.auth.state:refer[auth-db]]

[io.pedestal.interceptor.chain:aschain])

(:import[com.nimbusds.jwt.procBadJWTException]

[java.ioIOException]

[java.textParseException]

[java.utilArraysUUID]))

;;--------------------------------

;;ValidationInterceptors

;;--------------------------------

(defn-prepare-valid-context

"Appliesvalidationlogicandreturnstheresultingcontext"

[context]

(let[params(merge(->context:request:form-params)

(->context:request:query-params)

(->context:request:headers)

(if-let[pparams(->context:request:path-params)]

(if(empty?pparams){}pparams)))]

(if(or(and(params:uid)(params:pwd))

(params"authorization"))

(assoccontext:tx-dataparams)

(chain/terminate

(assoccontext

:response{:status400

:body"InvalidCreds/Token"})))))

(defvalidate

{:name::validate

:enter

(fn[context]

(prepare-valid-contextcontext))

Page 368: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Theget-tokeninterceptorlooksforavaliduidandpwd,andissuesaJWTifauthenticationissuccessful.Iftheuidandpwdarenotpresent,itlooksforavalidauthorizationheaderofaBearertypeand,ifthetokenisvalid,itreturnstheauthenticateduserIDandassignedrolesthatareassociatedwiththeuser:

(defn-extract-token

"Extractsuserandrolesmapfromtheauthheader"

[auth]

(select-keys

(jwt/read-token

(second(s/splitauth#"\s+"))(auth-db:secret))

["user""roles"]))

(defget-token

{:name::token-get

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

uid(:uidtx-data)

pwd(:pwdtx-data)

auth(tx-data"authorization")]

(cond

(anduidpwd(Arrays/equals

(->auth-db:users(getuid):pwd)

(p/get-hashpwd)))

(let[token(jwt/create-token

{:roles(->auth-db:users(getuid):roles)

:useruid}(auth-db:secret))]

(assoccontext:response

{:status200

:headers{"authorization"(str"Bearer"token)}}))

(andauth(="Bearer"(->(s/splitauth#"\s+")first)))

(try

(assoccontext:response

{:status200

:body(jp/generate-string(extract-tokenauth))})

(catchBadJWTExceptione

(assoccontext:response

{:status401:body"Tokenexpired"})))

:else(assoccontext:response{:status401}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Theimplementationofthevalidate-tokeninterceptorshowninthefollowingexampleauthorizestheuserassociatedwiththeJWTsentasanauthorization

Page 369: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

headerandaCSVofpermissionsspecifiedasthepermsformparameter:

(defvalidate-token

{:name::token-validate

:enter

(fn[context]

(let[tx-data(:tx-datacontext)

auth(tx-data"authorization")

perms(if-let[p(tx-data:perms)]

(into#{}(maps/trim(s/splitp#","))))]

(if(andauth(="Bearer"(->(s/splitauth#"\s+")first)))

(try

(if(p/has-access?((extract-tokenauth)"user")perms)

(assoccontext:response{:status200:body"true"})

(assoccontext:response{:status200:body"false"}))

(catchBadJWTExceptione

(assoccontext:response

{:status401:body"Tokenexpired"}))

(catchParseExceptione

(assoccontext:response

{:status401:body"InvalidJWT"})))

(assoccontext:response{:status401}))))

:error

(fn[contextex-info]

(assoccontext

:response{:status500

:body(.getMessageex-info)}))})

Totesttheroutes,starttheAuthserviceusingtheleinruncommandorstartitwithinaREPLasshowninthefollowingexample;assoonastheapplicationisstarted,mountkicksinandinitializesasecretkeythatisusedtoissuetokensandalsoreadthemforauthorization:

helping-hands.auth.server>(defserver(run-dev))

Creatingyour[DEV]server...

Omniconfconfiguration:

{:conf#object[java.io.File0x979c2d2"config/conf.edn"]}

#'helping-hands.auth.server/server

helping-hands.auth.server>

Oncetheserverisupandrunning,usecURLtotryoutvariousscenarios,asshowninthefollowingexample.Iftherearenoauthenticationheadersorvalidcredentialsspecified,thevalidateinterceptorwillkickinandmarkitasabadrequest,asfollows:

%curl-i"http://localhost:8080/tokens"

HTTP/1.1400BadRequest

Date:Sun,14Jan201820:49:48GMT

...

InvalidCreds/Token

Page 370: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

%curl-i-XPOST-d"perms=notify:email""http://localhost:8080/tokens/validate"

HTTP/1.1400BadRequest

Date:Sun,14Jan201820:50:21GMT

...

InvalidCreds/Token

Ifthespecifiedcredentialsareinvalid,itwillthrowaresponsewithHTTP401Unauthorizedstatus,asshowninthefollowingexample:

%curl-i"http://localhost:8080/tokens?uid=hhuser&pwd=hello"

HTTP/1.1401Unauthorized

Date:Sun,14Jan201820:53:16GMT

...

%curl-i-H"Authorization:Bearerabc"-XPOST-d"perms=notify:email"

"http://localhost:8080/tokens/validate"

HTTP/1.1401Unauthorized

Date:Sun,14Jan201820:55:35GMT

...

InvalidJWT

Iftheparametersarevalid,endpointsworkasexpected,asshownforthehhuseruser:

%curl-i"http://localhost:8080/tokens?uid=hhuser&pwd=hhuser"

HTTP/1.1200OK

Date:Sun,14Jan201820:59:48GMT

...

Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.YY_dMY8qoqfTHeZwGacsFY70tCaUvCjjPKYNFhuA2ppOD-

Deaj5zzw.BbK36SSYyuUeVVS9jpyIXw.6UNkLFMVF5Foj5qFX5vLdKcyOoU2G2eeVtHskSWBoZuBnnAwI1NGrPc3PvQqKF4QkzlrbFfOYD2Vxd4YqYmj8Hcb1qVUQD1QgtYKiStIMujH-

-ZRltPfy7m8VW1D31ToeqAYU1LLlXYSC1W3kSjZQiMFMU1LXkMqZVdmJyfQIL_SvizfWbYuZQPcyDCxG5-

XtVeG2r09vnvUybw8tKdafg.WML7xCZ-lZur1GXpNFNKrw

Transfer-Encoding:chunked

%curl-i-H"Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.YY_dMY8qoqfTHeZwGacsFY70tCaUvCjjPKYNFhuA2ppOD-

Deaj5zzw.BbK36SSYyuUeVVS9jpyIXw.6UNkLFMVF5Foj5qFX5vLdKcyOoU2G2eeVtHskSWBoZuBnnAwI1NGrPc3PvQqKF4QkzlrbFfOYD2Vxd4YqYmj8Hcb1qVUQD1QgtYKiStIMujH-

-ZRltPfy7m8VW1D31ToeqAYU1LLlXYSC1W3kSjZQiMFMU1LXkMqZVdmJyfQIL_SvizfWbYuZQPcyDCxG5-

XtVeG2r09vnvUybw8tKdafg.WML7xCZ-lZur1GXpNFNKrw""http://localhost:8080/tokens"

HTTP/1.1200OK

Date:Sun,14Jan201821:00:11GMT

...

{"user":"hhuser","roles":["hh/notify"]}

%curl-XPOST-i-H"Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.YY_dMY8qoqfTHeZwGacsFY70tCaUvCjjPKYNFhuA2ppOD-

Deaj5zzw.BbK36SSYyuUeVVS9jpyIXw.6UNkLFMVF5Foj5qFX5vLdKcyOoU2G2eeVtHskSWBoZuBnnAwI1NGrPc3PvQqKF4QkzlrbFfOYD2Vxd4YqYmj8Hcb1qVUQD1QgtYKiStIMujH-

-ZRltPfy7m8VW1D31ToeqAYU1LLlXYSC1W3kSjZQiMFMU1LXkMqZVdmJyfQIL_SvizfWbYuZQPcyDCxG5-

XtVeG2r09vnvUybw8tKdafg.WML7xCZ-lZur1GXpNFNKrw"-d"perms=notify:email"

"http://localhost:8080/tokens/validate"

HTTP/1.1200OK

Date:Sun,14Jan201821:00:38GMT

...

Page 371: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

true%

%curl-XPOST-i-H"Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.YY_dMY8qoqfTHeZwGacsFY70tCaUvCjjPKYNFhuA2ppOD-

Deaj5zzw.BbK36SSYyuUeVVS9jpyIXw.6UNkLFMVF5Foj5qFX5vLdKcyOoU2G2eeVtHskSWBoZuBnnAwI1NGrPc3PvQqKF4QkzlrbFfOYD2Vxd4YqYmj8Hcb1qVUQD1QgtYKiStIMujH-

-ZRltPfy7m8VW1D31ToeqAYU1LLlXYSC1W3kSjZQiMFMU1LXkMqZVdmJyfQIL_SvizfWbYuZQPcyDCxG5-

XtVeG2r09vnvUybw8tKdafg.WML7xCZ-lZur1GXpNFNKrw"-d"perms=notify:random"

"http://localhost:8080/tokens/validate"

HTTP/1.1200OK

Date:Sun,14Jan201821:00:49GMT

...

false

%curl-XPOST-i-H"Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.YY_dMY8qoqfTHeZwGacsFY70tCaUvCjjPKYNFhuA2ppOD-

Deaj5zzw.BbK36SSYyuUeVVS9jpyIXw.6UNkLFMVF5Foj5qFX5vLdKcyOoU2G2eeVtHskSWBoZuBnnAwI1NGrPc3PvQqKF4QkzlrbFfOYD2Vxd4YqYmj8Hcb1qVUQD1QgtYKiStIMujH-

-ZRltPfy7m8VW1D31ToeqAYU1LLlXYSC1W3kSjZQiMFMU1LXkMqZVdmJyfQIL_SvizfWbYuZQPcyDCxG5-

XtVeG2r09vnvUybw8tKdafg.WML7xCZ-lZur1GXpNFNKrw"-d"perms=notify:email"

"http://localhost:8080/tokens/validate"

HTTP/1.1401Unauthorized

Date:Sun,14Jan201821:03:55GMT

...

Tokenexpired

AuthservicecanbedeployedinisolationandconnectedthroughtheauthinterceptorofrestoftheservicesoftheHelpingHandsapplicationtoauthorizetheusers.Userscanobtainthetokenbycallingthe/tokensendpointofAuthservicedirectlyandusethesametokentoauthenticateandauthorizethemselveswithotherservices.

Buddy(https://github.com/funcool/buddy)isanotherClojurelibrarythathasaBuddySign(https://github.com/funcool/buddy-sign)librarythatcanalsobeusedtogenerateJSONWebTokens.

Page 372: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

MonitoringmicroservicesAmicroservices-basedapplicationishighlyflexibleintermsofdeploymentandscaling.Itconsistsofmultipleservicesthatmayhaveoneormoreinstancesrunningonaclusterofmachinesacrossthenetwork.Insuchahighlydistributedandflexibleenvironment,itisofutmostimportancethateachinstanceofamicroserviceismonitoredinrealtimetogetaclearviewofthedeployedservices,theirperformance,andtocaptureissuesofinterestthatmustbereportedassoonastheyoccur.Sinceeachrequesttoamicroservice-basedapplicationmayspanouttooneormorerequestsamongmicroservices,thereshouldbeamechanismtotracktheflowofrequestsandalsolocatetheareasofbottleneckthatcanbeaddressedbyperformingarootcauseanalysisandoftenscalingtheservicesfurthertomeetthedemand.

Oneofthewaystosetupaneffectivemonitoringsystemistocollectallthemetricsacrosstheservicesandmachinesandstoretheminacentralizedrepository,asshownintheprecedingdiagram.Thiscentralizedrepositorycanthensupporttheanalysisofthecapturedmetricsandhelptogeneratealertsfortheeventsofinterestinrealtime.Acentralizedrepositoryalsohelpsinsettingupthereal-timeviewofthesystemtounderstandthebehaviorofeachserviceanddecidewhethertoscaleituporscaleitdown.Tosetupacentralizedrepositoryfortheapplication,themetricsneedtobeeitherpulledfromalltheservicesandphysicalmachinesorpushedtothecentralizedrepositorybythe

Page 373: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

servicesrunningonthephysicalmachines.Bothpushandpullmodelsareusefultosetupaneffectivemonitoringsystemthatservesthesourceoftruthforthestateofthesystemaswellastheperformanceoftheenvironmentthatiscrucialforeffectiveutilizationoftheinfrastructureusedbythemicroservices-basedapplication.

Alltheservicesmustberesponsibletopushthemetricsrelatedtothestateoftheapplicationtoacommonchannel,suchasApacheKafka(https://kafka.apache.org/),onacommontopicthatcanthenbeusedtoaggregatealltheapplication-levelmetricsacrosstheservicesandstoretheminacentralizedrepository.Application-levellogsthatarewrittentothefileonthephysicalserversandtheapplication-levelmetricsthatarepublishedviamediumssuchasJMX(http://www.oracle.com/technetwork/articles/java/javamanagement-140525.html)canbepulledbyanexternalcollectorandlaterpushedtothecentralizedstorage.Tomonitortheperformanceoftheinfrastructure,externalcollectorsmustalsocapturethestatsofthephysicalmachine,includingCPUutilization,networkthroughput,diskI/O,andmore,whichcanalsobepushedtothecentralrepositorytogetaholisticviewoftheresourceutilizationacrosstheservicesoftheapplication.

Page 374: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingELKStackformonitoringElasticsearch(https://www.elastic.co/products/elasticsearch),Logstash(https://www.elastic.co/products/logstash),andKibana(https://www.elastic.co/products/kibana),oftenreferredtoasELKStackorElasticStack(https://www.elastic.co/elk-stack),providealltherequiredcomponentstosetupareal-timemonitoringinfrastructuretocapture,pull,andpushtheapplicationandmachine-levelmetricsintoacentralizedrepositoryandbuildamonitoringdashboardforreportingandalerts.ThefollowingmonitoringinfrastructurediagramexhibitswhereeachofthecomponentsoftheELKStackfitin.Collectd(https://collectd.org/)andApacheKafka(https://kafka.apache.org/)arenotapartofELKStack,butELKStackprovidesseamlessintegrationwiththeseoutofthebox:

Collectdhelpsincapturingallthemachine-levelstats,includingCPU,memory,disk,andnetwork.ThecaptureddatacanthenbepulledthroughLogstashandpushedintoElasticsearchtoanalyzetheoverallperformanceandutilizationoftheinfrastructureusedbytheservicesoftheapplication.LogstashcanalsounderstandthestandardsetofapplicationlogsandpulltheloggedeventsfromthelogfilesgeneratedonthemachineandpushittotheElasticsearchcluster.LogstashalsointegrateswellwithApacheKafkaandcanbeusedtocapturethe

Page 375: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

applicationstateeventspublishedbytheservicesandpushthemdirectlytoElasticsearch.SinceElasticsearchactsasacentralrepositoryforallthelogs,events,andmachinestats,KibanacanbeuseddirectlyontopofElasticsearchtoanalyzethestoredmetricsandbuilddashboardsthatareupdatedinrealtimeasandwheneventsarriveinElasticsearch.Kibanacanalsobeusedtoperformrootcauseanalysisandgeneratealertsfortheintendedrecipients.

ELKStackisusefulformonitoring,butitisnottheonlyoption.ToolssuchasPrometheus(https://prometheus.io/)canalsobeusedformonitoring.Prometheussupportsadimensionaldatamodel,flexiblequerylanguageandefficienttimeseriesdatabasewithin-builtalerting.

Page 376: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SettingupElasticsearch

TosetupElasticsearch,downloadthelatestversionfromtheElasticsearchdownloadpage(https://www.elastic.co/downloads/elasticsearch)andextractit,asshowninthefollowingexample;thisbookusesElasticsearch6.1.1,thatcanbedownloadedfromthereleasepageof6.1.1(https://www.elastic.co/downloads/past-releases/elasticsearch-6-1-1):#downloadelasticsearch6.1.1tar%wgethttps://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.1.1.tar.gz--https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.1.1.tar.gzResolvingartifacts.elastic.co(artifacts.elastic.co)...184.73.156.41,184.72.218.26,54.235.82.130,...Connectingtoartifacts.elastic.co(artifacts.elastic.co)|184.73.156.41|:443...connected.HTTPrequestsent,awaitingresponse...200OKLength:28462503(27M)[application/x-gzip]Savingto:‘elasticsearch-6.1.1.tar.gz’

elasticsearch-6.1.1.tar.gz100%[==============================>]27.14M2.38MB/sin21s

...(1.30MB/s)-‘elasticsearch-6.1.1.tar.gz’saved[28462503/28462503]

#extractthedownloadedtarball%tar-xvfelasticsearch-6.1.1.tar.gz...

#makesurethatthesedirectoriesarepresent%tree-L1elasticsearch-6.1.1elasticsearch-6.1.1├──bin├──config├──lib

Page 377: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

├──LICENSE.txt├──modules├──NOTICE.txt├──plugins└──README.textile

5directories,3files

AlthoughElasticsearchwillrunstraightoutoftheboxwiththebin/elasticsearchcommand,itisrecommendedtoreviewthefollowingimportantconfigurationsandsystemsettingsforaneffectiveElasticsearchcluster.ThesettingsmarkedasESConfigaregenericforallElasticsearchdeployments,whereastheonesmarkedasSystemSettingarefortheLinuxoperatingsystem.Theenvironmentvariable—$ES_HOME—referstotheextractedElasticsearchinstallationfolder,thatis,elasticsearch-6.1.1forthecommandshownintheprecedingcodesnippet.

Type ConfigLocation ConfigParameter Value

SystemSetting

/etc/security/limits.conf memlock unlimited

SystemSetting

/etc/security/limits.conf nofile 65536

SystemSetting

/etc/sysctl.conf vm.overcommit_memory 1

SystemSetting

/etc/sysctl.conf vm.max_map_count 262144

SystemSetting

/etc/fstab Commentswapconfig -

ESJVM $ES_HOME/config/jvm.options -Xmsand-Xmx 8g,16g,

andsoon

Page 378: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Options

ESConfig

$ES_HOME/config/elasticsearch.yml cluster.name <name>

ESConfig

$ES_HOME/config/elasticsearch.yml node.name <name>

ESConfig

$ES_HOME/config/elasticsearch.yml path.data

Oneormore<pathtokeep

indexes>

ESConfig

$ES_HOME/config/elasticsearch.yml path.logs<pathtolog

directory>

ESConfig

$ES_HOME/config/elasticsearch.yml bootstrap.memory_lock true

ESConfig

$ES_HOME/config/elasticsearch.yml network.host <ip_address>

ESConfig

$ES_HOME/config/elasticsearch.yml discovery.zen.ping.unicast.hosts

Oneormore<ip>:<port>

ESConfig

$ES_HOME/config/elasticsearch.yml discovery.zen.minimum_master_nodes<numberof

nodes>

Notethatsomeofthesystemsettingsshownintheprecedingtablemayrequireasystemrestartforthemtotakeintoeffect.Also,settingslikethatofswapspacemustbedoneonlyifElasticsearchistheonlycomponentrunningonthehost

Page 379: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

operatingsystem.Onceallthesettingsareinplace,eachElasticsearchnodecanbestartedusingthefollowingcommand;eachnodewilljointheclusteriftheyhavethesameclusternameandareapartofunicasthostslistthatanodeisallowedtojoin:#changetotheextractedelasticsearchdirectory%cdelasticsearch-6.1.1

#startelasticsearch%bin/elasticsearch[2018-01-15T20:46:35,408][INFO][o.e.n.Node][]initializing......[2018-01-15T20:46:36,328][INFO][o.e.p.PluginsService][W6r6s1z]loadedmodule[aggs-matrix-stats][2018-01-15T20:46:36,329][INFO][o.e.p.PluginsService][W6r6s1z]loadedmodule[analysis-common][2018-01-15T20:46:36,329][INFO][o.e.p.PluginsService][W6r6s1z]loadedmodule[ingest-common]...[2018-01-15T20:46:36,330][INFO][o.e.p.PluginsService][W6r6s1z]loadedmodule[tribe][2018-01-15T20:46:37,491][INFO][o.e.d.DiscoveryModule][W6r6s1z]usingdiscoverytype[zen][2018-01-15T20:46:37,929][INFO][o.e.n.Node]initialized[2018-01-15T20:46:37,930][INFO][o.e.n.Node][W6r6s1z]starting...[2018-01-15T20:46:38,100][INFO][o.e.t.TransportService][W6r6s1z]publish_address{127.0.0.1:9300},bound_addresses{[::1]:9300},{127.0.0.1:9300}[2018-01-15T20:46:41,164][INFO][o.e.c.s.MasterService][W6r6s1z]zen-disco-elected-as-master([0]nodesjoined),reason:new_master{W6r6s1z}{W6r6s1zTQ96ULo2wq9Tm3w}{ykEtBVl9Sy62mkXOFo892g}{127.0.0.1}{127.0.0.1:9300}...[2018-01-15T20:46:41,196][INFO][o.e.n.Node][W6r6s1z]started[2018-01-15T20:46:41,239][INFO][o.e.g.GatewayService][W6r6s1z]recovered[0]indicesintocluster_state

Notethatthefirstnodethatisstartedisautomaticallyelectedasamasteroftheclustertowhichothernodescanjoin:%curlhttp://localhost:9200

Page 380: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

{"name":"W6r6s1z","cluster_name":"elasticsearch","cluster_uuid":"g33pKv6XRTaj_yMJLliL0Q","version":{"number":"6.1.1","build_hash":"bd92e7f","build_date":"2017-12-17T20:23:25.338Z","build_snapshot":false,"lucene_version":"7.1.0","minimum_wire_compatibility_version":"5.6.0","minimum_index_compatibility_version":"5.0.0"},"tagline":"YouKnow,forSearch"}

OncetheElasticsearchnodeisupandrunning,totesttheinstancesendaGETrequesttothedefault9200portonthemachinewhereElasticsearchisrunningusingcURL,asshownintheprecedingexample.Itshouldreturnaresponsestatingtheversionofthenode.Verifythatitistherightversion,thatis,6.1.1,forthisexample.

FormoredetailsontheimportantsettingsofElasticsearchandSystem,takealookattheElasticsearchdocsforImportantSettings(https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html)andSystemSettings(https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-system-settings.html).

TheprecedingconfigurationdiscussesatypicalclusterdeploymentforElasticsearch.ElasticsearchalsoprovidesaconceptofCrossClusterSearch(https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cross-cluster-search.html)thatallowsanynodetoactasafederatedclientacrossmultipleclustersofElasticsearch.

ThestepstakeninthissectionuseElasticsearchtarballtosetupElasticsearchcluster,butElasticsearchprovidesanumberofoptionstosetitupusingbinaries,includingRPMpackage,Debianpackage,MSIpackageforWindows,andaDockerimage.For

Page 381: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

moredetails,refertotheinstallationinstructionsathttps://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html#install-elasticsearch.

Page 382: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SettingupKibanaKibanaisthevisualizationanddashboardinterfaceforElasticsearch.ItallowsexploringdatausingitsDiscovermodule(https://www.elastic.co/guide/en/kibana/6.1/discover.html)andbuildingreal-timedashboards.Itincludesagoodnumberofvisualizationoptions(https://www.elastic.co/guide/en/kibana/current/visualize.html)thatallowuserstoaggregatedatastoredwithinElasticsearchandvisualizethemusingvariouscharts,suchasline,bar,area,maps,andtagclouds.KibanacanbeusedtobuildthemonitoringdashboardusingthevariousmetricsthatarecapturedwithinElasticsearch.

TosetupKibana,downloadthelatestversionfromtheKibanadownloadspage(https://www.elastic.co/downloads/kibana)andextractitasshowninthefollowingexample;thisbookusesKibana6.1.1,whichcanbedownloadedfromthereleasepageof6.1.1(https://www.elastic.co/downloads/past-releases/kibana-6-1-1):

#downloadKibana6.1.1tar

%wgethttps://artifacts.elastic.co/downloads/kibana/kibana-6.1.1-linux-x86_64.tar.gz

--https://artifacts.elastic.co/downloads/kibana/kibana-6.1.1-linux-x86_64.tar.gz

Resolvingartifacts.elastic.co(artifacts.elastic.co)...54.225.188.6,23.21.118.61,

54.235.82.130,...

Connectingtoartifacts.elastic.co(artifacts.elastic.co)|54.225.188.6|:443...

connected.

HTTPrequestsent,awaitingresponse...200OK

Length:64664051(62M)[application/x-gzip]

Savingto:‘kibana-6.1.1-linux-x86_64.tar.gz’

kibana-6.1.1-linux-x86_64.tar.gz100%[==============================>]61.67M2.06MB/s

in53s

...(1.10MB/s)-‘kibana-6.1.1-linux-x86_64.tar.gz’saved[64664051/64664051]

#extractthedownloadedtarball

%tar-xvfkibana-6.1.1-linux-x86_64.tar.gz

...

#makesurethatthesedirectoriesarepresent

%tree-L1kibana-6.1.1-linux-x86_64

kibana-6.1.1-linux-x86_64

├──bin

├──config

├──data

├──LICENSE.txt

├──node

├──node_modules

├──NOTICE.txt

├──optimize

├──package.json

├──plugins

Page 383: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

├──README.txt

├──src

├──ui_framework

└──webpackShims

10directories,4files

Next,configureKibanainstancebysettingthefollowingconfigurationparametersinthe$KIBANA_HOME/config/kibana.ymlfile.Theenvironmentvariable—$KIBANA_HOME—referstotheextractedKibanainstallationfolder,thatis,kibana-6.1.1-linux-x86_64forthecommandshownintheprecedingcodesnippet.

ConfigParameter Value Description

server.port 5601 Defaultserver.host <host_ip> KibanabindstothisIPaddress

server.basePath <base_prefix_URL>Shouldnotendwith`/`.UsedtomapproxyURLprefix,ifany

server.name <name> Displayname

elasticsearch.urlhttp://<es_host>:

<es_port>

ElasticsearchURLtoconnectto;defaultportis9200

kibana.index <name>IndexnameascreatedbyKibanainElasticsearch

pid.file <path_to_pid_file> PIDfilelocationlogging.dest <path_to_log_file> FiletowriteKibanalogs

Onceallthesettingsareinplace,startKibanausingthecommandshowninthefollowingexample;ensurethatElasticsearchisalreadyrunningandaccessibletotheKibananodeontheconfiguredelasticsearch.urlsetting,asdescribedintheprecedingtable.

#changetoextractedkibanadirectory

%cdkibana-6.1.1-linux-x86_64

#startkibana

%bin/kibana

log[16:41:31.409][info][status][plugin:[email protected]]Statuschangedfrom

uninitializedtogreen-Ready

Page 384: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

log[16:41:31.443][info][status][plugin:[email protected]]Statuschangedfrom

uninitializedtoyellow-WaitingforElasticsearch

log[16:41:31.461][info][status][plugin:[email protected]]Statuschangedfrom

uninitializedtogreen-Ready

log[16:41:31.484][info][status][plugin:[email protected]]Statuschangedfrom

uninitializedtogreen-Ready

log[16:41:31.646][info][status][plugin:[email protected]]Statuschangedfrom

uninitializedtogreen-Ready

log[16:41:31.650][info][listening]Serverrunningathttp://localhost:5601

log[16:41:31.668][info][status][plugin:[email protected]]Statuschangedfrom

yellowtogreen-Ready

OnceKibanaisupandrunning,opentheURLhttp://localhost:5601,asloggedintheprecedingmessagestoopenKibanainterface,asshowninthefollowingscreenshot;itshouldshowtheKibanahomepagewithoptionstovisualizeandexploredata:

ThecurrentconfigurationofKibanaallowsuserstoexploreElasticsearchinaclosednetwork.SinceKibanaprovidesfullcontrolovertheunderlyingElasticsearchclusteranddatastoredwithinit,itisrecommendedthatyouenableSSLandalsotheload-balancingoptionasdefinedintheproductionconfiguration(https://www.elastic.co/guide/en/kibana/current/production.html)foruserstoconnectandaccessdashboards.

KibananotonlyallowsuserstoexploredatasetsalreadystoredwithinElasticsearchbutalsosupportsloadingdatasetsdirectlyintoElasticsearchviaitsuserinterface.Tolearnmoreaboutthis,

Page 385: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

followtheLoadingSampleDatatutorialofKibana(https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html).

Page 386: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SettingupLogstash

Logstashallowsuserstocollect,parse,andtransformlogmessages.Itsupportsanumberofinput(https://www.elastic.co/guide/en/logstash/current/input-plugins.html)andoutput(https://www.elastic.co/guide/en/logstash/current/output-plugins.html)pluginsthatallowLogstashtocollectlogsfromavarietyofsources,parseandtransformthem,andthenwritetheresultstooneofthesupportedplugins.TosetupLogstash,downloadthelatestversionfromtheLogstashdownloadspage(https://www.elastic.co/downloads/logstash)andextractitasshowninthefollowingcodesnippet—thisbookusesLogstash6.1.1,whichcanbedownloadedfromthereleasepageof6.1.1(https://www.elastic.co/downloads/past-releases/logstash-6-1-1):

#downloadLogstash6.1.1tar

%wgethttps://artifacts.elastic.co/downloads/logstash/logstash-6.1.1.tar.gz

--https://artifacts.elastic.co/downloads/logstash/logstash-6.1.1.tar.gz

Resolvingartifacts.elastic.co(artifacts.elastic.co)...23.21.118.61,54.243.108.41,

184.72.218.26,...

Connectingtoartifacts.elastic.co(artifacts.elastic.co)|23.21.118.61|:443...

connected.

HTTPrequestsent,awaitingresponse...200OK

Length:109795895(105M)[application/x-gzip]

Savingto:‘logstash-6.1.1.tar.gz’

logstash-6.1.1.tar.gz100%[=================================>]104.71M1.09MB/sin81s

...(1.30MB/s)-‘logstash-6.1.1.tar.gz’saved[109795895/109795895]

#extractthedownloadedtarball

%tar-xvflogstash-6.1.1.tar.gz

...

#makesurethatthesedirectoriesarepresent

%tree-L1logstash-6.1.1

logstash-6.1.1

├──bin

├──config

├──CONTRIBUTORS

├──data

├──Gemfile

├──Gemfile.lock

├──lib

├──LICENSE

├──logstash-core

├──logstash-core-plugin-api

├──modules

├──NOTICE.TXT

├──tools

└──vendor

Page 387: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

9directories,5files

ThefollowingtableliststheprimaryconfigurationsettingsthatarerequiredforLogstashandmustbeaddedtothe$LOGSTASH_HOME/config/logstash.ymlfile;theenvironmentvariable—$LOGSTASH_HOME—referstotheextractedLogstashinstallationfolder,thatis,logstash-6.1.1forthecommandshownintheprecedingcodesnippet.

ConfigParameter Value Description

node.name <name>

Nodenametoidentifythenodefromoutputinterface.Goodtohaveasahostname.

path.data <path_to_data>

OneormorepathswhereLogstashanditspluginkeepsthedataforanypersistenceneeds.

pipeline.workers1,2,3,4,andsoon

Workerstoexecutefilterandoutputstages.Ifdeployedonaseparatemachine,setthistonumberofCPUcores.

pipeline.output.workers1,2,andsoon

Numberofworkerstouseperoutputplugininstance.Defaultsto1.

path.config <path_to_config>Locationtofetchpipelineconfigurationformainpipeline.

http.host <host_ip>BindaddressformetricsRESTendpoint.

http.port <host_port>

BindportforthemetricsRESTendpoint.Alsoacceptsranges,suchas(9600-9700),topickthefirstavailableport.

path.logs <path_to_logs> PathwhereLogstashwillkeepthelogs.

Page 388: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Theprecedingtablelistsonlytheprimaryconfigurationparameters;formoredetailsandallthesupportedconfigurationparameters,refertoLogstashSettingsFileguide(https://www.elastic.co/guide/en/logstash/current/logstash-settings-file.html).Onceallthesettingsareinplace,testasampleLogstashpipelinethatusesstdin(https://www.elastic.co/guide/en/logstash/current/plugins-inputs-stdin.html)asitsinputplugintoreceivemessagesandstdout(https://www.elastic.co/guide/en/logstash/current/plugins-outputs-stdout.html)asitsoutputplugintoemitthereceivedmessages,asfollows:

#changetoextractedlogstashdirectory

%cdlogstash-6.1.1

#startlogstashpipelinebyspecifyingtheconfiguration

#atcommandlineusingthe-eflag

%bin/logstash-e'input{stdin{}}output{stdout{}}'

SendingLogstash'slogstologstash-6.1.1/logswhichisnowconfiguredvia

log4j2.properties

[2018-01-15T23:11:02,245][INFO][logstash.modules.scaffold]Initializingmodule

{:module_name=>"netflow",:directory=>"logstash-6.1.1/modules/netflow/configuration"}

[2018-01-15T23:11:02,257][INFO][logstash.modules.scaffold]Initializingmodule

...

[2018-01-15T23:11:03,872][INFO][logstash.runner]StartingLogstash

{"logstash.version"=>"6.1.1"}

[2018-01-15T23:11:04,415][INFO][logstash.agent]SuccessfullystartedLogstashAPI

endpoint{:port=>9600}

[2018-01-15T23:11:06,212][INFO][logstash.pipeline]Startingpipeline

{:pipeline_id=>"main","pipeline.workers"=>4,"pipeline.batch.size"=>125,

"pipeline.batch.delay"=>5,"pipeline.max_inflight"=>500,:thread=>"#<Thread:0x77cbc3e6

run>"}

[2018-01-15T23:11:06,305][INFO][logstash.pipeline]Pipelinestarted

{"pipeline.id"=>"main"}

Thestdinpluginisnowwaitingforinput:

[2018-01-15T23:11:06,413][INFO][logstash.agent]Pipelinesrunning{:count=>1,

:pipelines=>["main"]}

helloworld

2018-01-15T17:41:19.900Zfc-machinehelloworld

2018-01-15T17:41:21.707Zfc-machine

HelloLogstash!

2018-01-15T17:41:28.566Zfc-machineHelloLogstash!

HelloELK!

2018-01-15T17:41:32.255Zfc-machineHelloELK!

HelloHelpingHandsEvents!

2018-01-15T17:41:38.685Zfc-machineHelloHelpingHandsEvents!

Logstashmaytakeafewsecondstostartthepipeline,sowaitforthePipelinerunningmessagetobelogged.Oncethepipelineisrunning,typeamessageintheconsole,andLogstashwillechothesameontheconsoleappendedwiththecurrenttimestampandhostname.Thisisaverysimplepipelinethatdoesnotdoanytransformation,butLogstashallowstransformationstobeappliedonthereceivedmessagesbeforetheyareemittedtothesink.Similartothebasicpipelineshownintheprecedingtest,theLogstashpipelineconfigurationiscreatedforeachpipelinethatisrequiredtobeexecutedbyLogstashtocapture

Page 389: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

logs,events,anddata,andstoretheminthetargetsinks.

LogstashpluginsareimplementedprimarilyinRuby(https://www.ruby-lang.org/en/).ThatiswhyallthejobconfigurationfilesforLogstashandtransformationconstructsusesyntaxofRubylanguage.

Page 390: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

UsingELKStackwithCollectdCollectdisadaemonthatcanbeconfiguredtocollectmetricsfromvarioussourceplugins,suchasLogstash.AscomparedtoLogstash,Collectdisverylightweightandportable,butitdoesnotgenerategraphs.ItcanwritetoRRDfiles,though,thatneedaRRDTool(https://en.wikipedia.org/wiki/RRDtool)toreadthemandgenerategraphstovisualizetheloggeddata.Ontheotherhand,sinceCollectdiswritteninCprogramminglanguage(https://en.wikipedia.org/wiki/C_(programming_language)),itisalsopossibletouseittocollectmetricsfromembeddedsystemsaswell.

Collectdneedstobebuiltfromsource.First,downloadtheCollectd5.8.0versionandextractthesame:

#downloadCollectd5.8.0tar

%wgethttps://storage.googleapis.com/collectd-tarballs/collectd-5.8.0.tar.bz2

--https://storage.googleapis.com/collectd-tarballs/collectd-5.8.0.tar.bz2

Resolvingstorage.googleapis.com(storage.googleapis.com)...172.217.26.208,

2404:6800:4007:802::2010

Connectingtostorage.googleapis.com(storage.googleapis.com)|172.217.26.208|:443...

connected.

HTTPrequestsent,awaitingresponse...200OK

Length:1686017(1.6M)[application/x-bzip]

Savingto:‘collectd-5.8.0.tar.bz2’

collectd-5.8.0.tar.bz2100%[===============================>]1.61M2.67MB/sin0.6s

...(2.67MB/s)-‘collectd-5.8.0.tar.bz2’saved[1686017/1686017]

#extractthedownloadedtarball

%tar-xvfcollectd-5.8.0.tar.bz2

...

#makesurethatthesedirectoriesarepresent

%tree-L1collectd-5.8.0

collectd-5.8.0

├──aclocal.m4

├──AUTHORS

├──bindings

├──build-aux

├──ChangeLog

├──configure

├──configure.ac

├──contrib

├──COPYING

├──m4

├──Makefile.am

├──Makefile.in

├──proto

├──README

├──src

Page 391: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

├──testwrapper.sh

└──version-gen.sh

6directories,11files

Next,installCollectdtoabuilddirectory,asshowninthefollowingexample.Incasetheconfigurescriptrequestsformissingdependencies,installthembeforecontinuingthesetupaspertheFirststepswiki(https://collectd.org/wiki/index.php/First_steps)ofCollectd:

#changetoextractedcollectddirectory

%cdcollectd-5.8.0

#configurethetargetbuilddirectory

#givethefullyqualifiedpathasprefix

#$COLLECTD_HOMEpointstocollectd-5.8.0directory

%./configure--prefix=$COLLECTD_HOME/build

checkingbuildsystemtype...x86_64-unknown-linux-gnu

checkinghostsystemtype...x86_64-unknown-linux-gnu

checkinghowtoprintstrings...printf

checkingforgcc...gcc

checkingwhethertheCcompilerworks...yes

checkingforCcompilerdefaultoutputfilename...a.out

checkingforsuffixofexecutables...

...

#installcollectd

%sudomakeallinstall

...

#verifythebuilddirectories

%tree-L1build

build

├──bin

├──etc

├──include

├──lib

├──man

├──sbin

├──share

└──var

8directories,0files

#owntheentirecollectddirectory

#replace<user>withyourusername

%sudochown-R<user>:<user>.

OnceCollectdisinstalled,thenextstepistoupdatethebuild/etc/collectd.conffilewiththedesiredconfigurationsandplugins.Thefollowingisasamplecollectd.conffiletoenablecpu,df,interface,network,memory,syslog,load,andswapplugins;formoredetailsontheavailablepluginsandtheirconfiguration,refertoCollectdTableofPlugins(https://collectd.org/wiki/index.php/Table_of_Plugins).

#BaseConfiguration

#replaceallpathsbelowwithfullyqualified

Page 392: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

#pathtotheextractedcollectd-5.8.0directory

Hostname"helpinghands.com"

BaseDir"/collectd-5.8.0/build/var/lib/collectd"

PIDFile"/collectd-5.8.0/build/var/run/collectd.pid"

PluginDir"/collectd-5.8.0/build/lib/collectd"

TypesDB"/collectd-5.8.0/build/share/collectd/types.db"

CollectInternalStatstrue

#Syslog

LoadPluginsyslog

<Pluginsyslog>

LogLevelinfo

</Plugin>

#Otherplug-ins

LoadPlugincpu

LoadPlugindf

LoadPlugindisk

LoadPlugininterface

LoadPluginload

LoadPluginmemory

LoadPluginnetwork

LoadPluginswap

#Plug-inConfig

<Plugincpu>

ReportByCputrue

ReportByStatetrue

ValuesPercentagefalse

</Plugin>

#replacedeviceandmountpoint

#withthedevicetobemonitored

#asshownbydfcommand

<Plugindf>

Device"/dev/sda9"

MountPoint"/home"

FSType"ext4"

IgnoreSelectedfalse

ReportByDevicefalse

ReportInodesfalse

ValuesAbsolutetrue

ValuesPercentagefalse

</Plugin>

<Plugindisk>

Disk"/^[hs]d[a-f][0-9]?$/"

IgnoreSelectedfalse

UseBSDNamefalse

UdevNameAttr"DEVNAME"

</Plugin>

#reportallinterfaceexceptloandsit0

<Plugininterface>

Interface"lo"

Interface"sit0"

IgnoreSelectedtrue

ReportInactivetrue

UniqueNamefalse

</Plugin>

<Pluginload>

ReportRelativetrue

</Plugin>

<Pluginmemory>

ValuesAbsolutetrue

ValuesPercentagefalse

Page 393: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

</Plugin>

#sendsmetricstothisporti.e.

#configuredinlogstashtoreceive

#thelogeventstobepublished

<Pluginnetwork>

Server"127.0.0.1""25826"

<Server"127.0.0.1""25826">

</Server>

</Plugin>

<Pluginswap>

ReportByDevicefalse

ReportBytestrue

ValuesAbsolutetrue

ValuesPercentagefalse

</Plugin>

Oncetheconfigurationfileisinplace,startCollectddaemon,asshownhere:

#startcollectddaemonwithsudo

#someplug-insrequiresudoaccess

%sudobuild/sbin/collectd

#makesureitisrunning

%ps-ef|grepcollectd

anuj272081768001:21?00:00:00build/sbin/collectd

...

#verifysyslogtomakesurethatcollectdisup

%tail-f/var/log/syslog

...

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"syslog"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"cpu"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"df"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"disk"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"interface"

successfullyloaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"load"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"memory"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"network"successfully

loaded.

Jan1601:40:01localhostcollectd[28725]:plugin_load:plugin"swap"successfully

loaded.

...

Jan1601:40:01localhostcollectd[28726]:Initializationcomplete,enteringread-

loop.

Next,createaLogstashpipelineconfigurationfile,$LOGSTASH_HOME/config/helpinghands.conf,toreceivethedatafromCollectdusingtheCollectdCodec(https://www.elastic.co/guide/en/logstash/current/plugins-codecs-collectd.html)pluginandsendittoElasticsearchusingitsoutputplugin(https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html):

Page 394: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

input{

udp{

port=>25826

buffer_size=>1452

codec=>collectd{

id=>"helpinghands.com-collectd"

typesdb=>["/collectd-5.8.0/build/share/collectd/types.db"]

}

}

}

output{

elasticsearch{

id=>"helpinghands.com-collectd-es"

hosts=>["127.0.0.1:9200"]

index=>"helpinghands.collectd.instance-%{+YYYY.MM}"

}

}

Next,runtheLogstashpipelinetoreceivedatafromCollectdprocessoverUDP(https://en.wikipedia.org/wiki/User_Datagram_Protocol)andsendittoElasticsearch.Ensurethatthe25826portspecifiedintheUDPconfigurationabovematchestheportofthenetworkpluginofCollectdconfiguration.BeforerunningLogstash,verifythatElasticsearchandCollectdbotharerunning:

#changeto$LOGSTASH_HOMEdirectoryandrunlogstash

%bin/logstash-fconfig/helpinghands.conf

...

SendingLogstash'slogsto/logstash-6.1.1/logswhichisnowconfiguredvia

log4j2.properties

[2018-01-16T02:03:57,028][INFO][logstash.modules.scaffold]Initializingmodule

{:module_name=>"netflow",:directory=>"/logstash-6.1.1/modules/netflow/configuration"}

[2018-01-16T02:03:57,057][INFO][logstash.modules.scaffold]Initializingmodule

{:module_name=>"fb_apache",:directory=>"/logstash-

6.1.1/modules/fb_apache/configuration"}

...

[2018-01-16T02:03:58,410][INFO][logstash.runner]StartingLogstash

{"logstash.version"=>"6.1.1"}

[2018-01-16T02:03:58,935][INFO][logstash.agent]SuccessfullystartedLogstashAPI

endpoint{:port=>9600}

[2018-01-16T02:04:02,412][INFO][logstash.outputs.elasticsearch]Elasticsearchpool

URLsupdated{:changes=>{:removed=>[],:added=>[http://127.0.0.1:9200/]}}

...

[2018-01-16T02:04:03,777][INFO][logstash.outputs.elasticsearch]NewElasticsearch

output{:class=>"LogStash::Outputs::ElasticSearch",:hosts=>["//127.0.0.1:9200"]}

...

[2018-01-16T02:04:03,883][INFO][logstash.pipeline]Pipelinestarted

{"pipeline.id"=>"main"}

[2018-01-16T02:04:03,960][INFO][logstash.inputs.udp]StartingUDPlistener

{:address=>"0.0.0.0:25826"}

[2018-01-16T02:04:03,997][INFO][logstash.agent]Pipelinesrunning{:count=>1,

:pipelines=>["main"]}

[2018-01-16T02:04:04,030][INFO][logstash.inputs.udp]UDPlistenerstarted

{:address=>"0.0.0.0:25826",:receive_buffer_bytes=>"106496",:queue_size=>"2000"}

OnceLogstashstarts,observeElasticsearchlogsthatshowsthatLogstashhascreatedanewindexbasedonthehelpinghands.collectd.instance-%{+YYYY.MM}patternasconfiguredinLogstash'sElasticsearchoutputplugin.Notethattheindex

Page 395: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

namewilldifferbasedonthecurrentmonthandyear.Maintainingatime-basedindexpatternisrecommendedforindexesthatstoretimeseriesdatasets.Itnotonlyhelpsinqueryperformancebutalsohelpsinbackupandcleanupbasedonthedataretentionpoliciesofanorganization.ThefollowingarethelogmessagesthatcanbeobservedinElasticsearchlogfilesforsuccessfulcreationoftherequiredindexforthedatacapturedbyLogstashfromCollectd:

[2018-01-16T02:04:12,054][INFO][o.e.c.m.MetaDataCreateIndexService][W6r6s1z]

[helpinghands.collectd.instance-2018.01]creatingindex,cause[auto(bulkapi)],

templates[],shards[5]/[1],mappings[]

[2018-01-16T02:04:15,259][INFO][o.e.c.m.MetaDataMappingService][W6r6s1z]

[helpinghands.collectd.instance-2018.01/9x0mla-mS0akJLuuUJELZw]create_mapping[doc]

[2018-01-16T02:04:15,279][INFO][o.e.c.m.MetaDataMappingService][W6r6s1z]

[helpinghands.collectd.instance-2018.01/9x0mla-mS0akJLuuUJELZw]update_mapping[doc]

[2018-01-16T02:04:15,577][INFO][o.e.c.m.MetaDataMappingService][W6r6s1z]

[helpinghands.collectd.instance-2018.01/9x0mla-mS0akJLuuUJELZw]update_mapping[doc]

[2018-01-16T02:04:15,712][INFO][o.e.c.m.MetaDataMappingService][W6r6s1z]

[helpinghands.collectd.instance-2018.01/9x0mla-mS0akJLuuUJELZw]update_mapping[doc]

[2018-01-16T02:04:15,922][INFO][o.e.c.m.MetaDataMappingService][W6r6s1z]

[helpinghands.collectd.instance-2018.01/9x0mla-mS0akJLuuUJELZw]update_mapping[doc]

LetthepipelinerunandstorethemachinemetricscapturedviaCollectd–Logstash–Elasticsearchpipeline.Now,opentheKibanainterfaceinthebrowserusingtheURLhttp://localhost:5601andclickontheSetupindexpatternsbuttononthetop-rightcorner.Itwillautomaticallylistthenewlycreatedindexhelpinghands.collectd.instance-2018.01,asshowninthefollowingscreenshot:

Addtheindexpatternhelpinghands.collectd.instance-*,asshownintheprecedingscreenshot,toincludealltheindexescreatedforthemetricscapturedbyCollectd.ClickontheNextstepbuttonontheright-handsideandselectTimefilterfieldnameas@timestamp,asshowninthefollowingscreenshot:

Page 396: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Next,clickontheCreateindexpatternbutton,asshownintheprecedingscreenshot.ItwillshowthelistoffieldsthatKibanawasabletoretrievefromtheElasticsearchindexmapping(https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html).Now,clickonDiscoverintheleft-handsidemenu,anditwillshowthereal-timedashboardofallthemessagesbeingcaptured,asshowninthefollowingscreenshot:

Theleft-handsidepaneloftheDiscoverscreenlistsallthefieldsbeingcaptured.Forexample,clickonhostandpluginfieldstoseethehostsbeingmonitoredandallthepluginsforwhichthedatahasbeencapturedbyCollectdandsenttoElasticsearchviatheLogstashpipeline,asshowninthefollowingscreenshot:

Page 397: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Kibanadashboardallowsbuildingdashboardwithvariousvisualizationoptions.Forexample,totakealookattheCPUutilizationsinceCollectdstartedmonitoringit,performthefollowingsteps:

1. ClickonVisualizeintheleftpaneloftheKibanaapplication2. ClickontheCreateavisualizationbutton3. ClickonLinetochoosethelinechart4. Choosetheindexhelpinghands.collectd.instance-*patternfromthesection

ontheleft5. ClickontheAddafilter+optionbelowthesearchbaratthetop6. Selectfilterasplugin.keywordiscpuandsave7. ClickontheY-axisandchangeAggregationtoAverage8. SelectthefieldasValue9. Next,clickontheX-AxisunderBuckets10. ChoosetheAggregationasDateHistogram11. Itshouldbydefaultselectthe@timestampfield12. KeeptheintervalasAuto13. ClickontheapplychangesplayiconatthetopoftheMetricspanel

Page 398: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

14. Itwillshowthechartontheright-handside,asshowninthenextscreenshot

15. Clickonthearrowiconatthebottomofthechartareatobringupthetable

Oncethesestepshavebeenperformed,youshouldbeabletoseetheCPUutilizationovertimeforthelast15minutes(default),asshowninthefollowingscreenshot.Thecreatedvisualization(https://www.elastic.co/guide/en/kibana/current/visualize.html)canbesavedandlateraddedtoadashboard(https://www.elastic.co/guide/en/kibana/current/dashboard.html)tobuildafull-fledgeddashboardtomonitorallthecapturedmetricsinrealtime:

Page 399: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes
Page 400: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

LoggingandmonitoringguidelinesMicroservicesoftheHelpingHandsapplicationmustgeneratebothapplicationandauditlogsthatcanbecapturedbytheELKStack.Applicationlogscanbegeneratedbytheservicesusingthetools.logginglibraryofClojure(https://github.com/clojure/tools.logging)thatlogsamessageinastandardlog4j(https://logging.apache.org/log4j/2.x/)stylesyntax.Logstashworkswellwithmostofthecommonloggingformats(https://www.elastic.co/guide/en/logstash/6.1/plugins-inputs-log4j.html),butitisrecommendedtousestructuredlogginginstead.

StructuredlogsareeasiertoparseandloadwithinacentralizedrepositoryusingtoolssuchasLogstash.Librariessuchastimbre(https://github.com/ptaoussanis/timbre)supportstructuredloggingandalsoallowpublishingthelogsdirectlytoaremoteserviceinsteadofloggingtoafile.Inadditiontostructuredlogging,considerincludingpredefinedstandardtagswithinthelogmessages,asshowninthefollowingtable:

Name Event<service> Thenameoftheservice,suchashelping-hands.alert

<service>-

start

Loggedfromthemainfunctiononcetheapplicationisupandrunning

<service>-

init

Oncetheserviceisupandrunningandithasbeensuccessfullyinitializedwiththerequiredconfiguration

<service>-

stop

Thelaststatementinthemainfunctionbeforetheapplicationexitsorisintheshutdownhook

<service>-

config Usedforconfiguration-relatedmessages<service>-

process Usedforprocessing-relatedmessages<service>-

exception Usedforruntimeexceptionhandlerandrelatedmessages

Tagsareparticularlyusefultofilterthelogmessagesoriginatingfromaservice

Page 401: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

ofinterestandalsohelpstofurtherdrilldownthelogsviaspecificstateleveltags,suchaslogmessagesgeneratedatstartup,duringshutdown,orloggedasaresultofanexception.

Inadditiontothetags,itisalsorecommendedtoalwaysuseUTC(https://en.wikipedia.org/wiki/Coordinated_Universal_Time)whilegeneratinglogmessages.Sincealltheselogmessagesareaggregatedinacentralizedrepository,havingdifferenttimezonemakesitchallengingtoanalyzethem,astheywillbeoutofsyncduetothetimezoneofthehostmachinesthatmayberunningindifferenttimezones.

Althoughlogmessagesarequiteusefultodebugtheissuesandprovideinformationregardingthestateoftheapplication,theyaffecttheperformanceoftheapplicationdrastically.So,logjudiciouslyandasynchronouslyasmuchaspossible.Itisalsorecommendedtopublishthelogeventsasynchronouslytochannels,suchasApacheKafka,insteadofloggingtoafilethatrequiresdiskI/O.LogstashhasaninputpluginforKafka(https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html)thatcanreadeventsfromaKafkatopicandpublishittothetargetoutputpluginlikethatofElasticsearch(https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html).

Riemann(http://riemann.io/)isanalternativetoELKstack.Itisusedtomonitordistributedsystems,suchastheonesbasedonmicroservices-basedarchitecture.Riemannisincrediblyfastandcanbeusedtogeneratealertsinnearrealtimewithoutoverwhelmingtherecipient,usingitsrollupandthrottleconstructs(http://riemann.io/howto.html#roll-up-and-throttle-events).

UsingELKstacktocollecttheeventsand,atthesametime,streamingLogstasheventsviaRiemannusingLogstashRiemannoutputplugin(https://www.elastic.co/guide/en/logstash/current/plugins-outputs-riemann.html)makesitpossibletogeneratealertsinnearrealtimeandalsousethegoodnessofElasticsearchandKibanatoprovideareal-timemonitoringdashboardfordrill-downanalysis.

Page 402: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

DeployingmicroservicesatscaleMicroservicesmustbepackagedasaself-containedartifactthatcanbereplicatedanddeployedusingasinglecommand.Theservicesshouldalsobelightweightwithshorterstarttimestomakesurethattheyareupandrunningwithinseconds.Itisrecommendedtopackagemicroserviceswithinacontainer(https://en.wikipedia.org/wiki/LXC)thatcanthenbedeployedfasterduetoitsinherentimplementationascomparedtosettingupabaremetalmachinewithahostoperatingsystemandrequireddependencies.Packagingmicroserviceswithincontainersalsomakesitpossibletomovefromdevelopmenttoproductionfasterandinanautomatedfashion.

Page 403: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroducingContainersandDockerLinuxContainers(LXC)isavirtualizationmethodattheoperatingsystemlevelthatmakesitpossibletorunmultipleisolatedLinuxsystems,alsoknownascontainers,onasinglehostOSusingasingleLinuxKernel(https://en.wikipedia.org/wiki/Linux_kernel).Theresourcesaresharedamongthecontainersusingcgroups(https://en.wikipedia.org/wiki/Cgroups)thatdonotrequirevirtualmachines.SinceeachcontainerreliesontheLinuxKernelofthehostOSthatisalreadyrunning,thestarttimeofcontainersismuchlowerascomparedtoavirtualmachinethatisrunbyaHypervisor(https://en.wikipedia.org/wiki/Hypervisor).

Docker(https://en.wikipedia.org/wiki/Docker_(software))alsoprovidesresourceisolationforthecontainersusingLinuxcgroups,kernelnamespaces(https://en.wikipedia.org/wiki/Linux_namespaces),andunionmountingoption(https://en.wikipedia.org/wiki/Union_mount)thathelpsittoavoidtheoverheadofstartingandmaintainingvirtualmachines.UsingDockercontainerformicroservicesmakesitpossibletopackagetheentireserviceanditsdependencieswithinacontainerandrunonanyLinuxserver.

Althoughitispossibletopackagetheentiremicroservice,includingthedatabasewithinaDockercontainer,itisrecommendedthatyoukeepthedatabaseoutoftheDockercontainer.Reasonbeingthatdatabasesmaynotbetheprimecandidatefordynamicscaleupandscaledownascomparedtotheservicesthemselves.

Page 404: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SettingupDockerDockerisasoftwarethathelpscreateDockerimagesthatcanbeusedtocreateoneormorecontainersandrunitonthehostoperatingsystem.TheeasiestwaytosetupDockeristousethesetupscriptprovidedbyDockerforthecommunityedition,asshownhere:

%wget-qO-https://get.docker.com/|sh

TheprecedingcommandwillsetupDocker,basedonthehostoperatingsystem.DockeralsoprovidesprebuiltpackagesforallthepopularoperationsystemsunderitsDownloadsection(https://www.docker.com/community-edition#/download).Onceinstalled,addthecurrentusertothedockergroup,asshownhere:

%sudousermod-aGdocker$USER

Youmayhavetostartanewloginsessionforgroupmembershiptobetakenintoaccount.Oncedone,Dockershouldbeupandrunning.TotestDocker,trylistingtherunningcontainersusingthefollowingcommand:

%dockerps-a

CONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES

Sincetherearenocontainersrunning,itwilljustlisttheheaders.Totestrunningacontainer,usethedockerruncommand,asshowninthefollowingexample.Itwilldownloadthehello-worldDockerimageandrunitinacontainer:

%dockerrunhello-world

Unabletofindimage'hello-world:latest'locally

latest:Pullingfromlibrary/hello-world

ca4f61b1923c:Pullcomplete

Digest:sha256:66ef312bbac49c39a89aa9bcc3cb4f3c9e7de3788c944158df3ee0176d32b751

Status:Downloadednewerimageforhello-world:latest

HellofromDocker!

Thismessageshowsthatyourinstallationappearstobeworkingcorrectly.

Togeneratethismessage,Dockertookthefollowingsteps:

1.TheDockerclientcontactedtheDockerdaemon.

2.TheDockerdaemonpulledthe"hello-world"imagefromtheDockerHub.

(amd64)

3.TheDockerdaemoncreatedanewcontainerfromthatimagewhichrunsthe

executablethatproducestheoutputyouarecurrentlyreading.

4.TheDockerdaemonstreamedthatoutputtotheDockerclient,whichsentit

toyourterminal.

Page 405: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Totrysomethingmoreambitious,youcanrunanUbuntucontainerwith:

$dockerrun-itubuntubash

Shareimages,automateworkflows,andmorewithafreeDockerID:

https://cloud.docker.com/

Formoreexamplesandideas,visit:

https://docs.docker.com/engine/userguide/

Theprecedingoutputisaresultoftheexecutionofthehello-worldimage.DumpingtheentiresetofstepsthatwereusedbyDockertogeneratethemessageishelpfultounderstandwhatasinglecommandsuchasdockerrundoesattheback.

Theprecedingcommanddownloadsthehello-worldimage,storesitlocally,andthenrunsitwithinacontainer.Theimagejustdumpsamessagewiththestepsthatwereusedtogeneratethemessageontheconsoleandexits.Since,thehello-worldimagewasusedforthefirsttime,itwasnotpresentonthelocalmachine,andthatisthereasonthedockercommanddownloadeditfirstfromremoteDockerRegistry(https://docs.docker.com/registry/).Tryrunningthesamecommandagain,and,thistime,itwilllocatetheimagewithinthelocalmachineanduseitstraightaway,asshowninthefollowingexample.Inthiscase,itjustdumpsthemessageandtheinstallationstepsasanoutputoftheexecutionofthehello-worldimageasbefore:

%dockerrunhello-world

HellofromDocker!

Thismessageshowsthatyourinstallationappearstobeworkingcorrectly.

Togeneratethismessage,Dockertookthefollowingsteps:

1.TheDockerclientcontactedtheDockerdaemon.

2.TheDockerdaemonpulledthe"hello-world"imagefromtheDockerHub.

(amd64)

3.TheDockerdaemoncreatedanewcontainerfromthatimagewhichrunsthe

executablethatproducestheoutputyouarecurrentlyreading.

4.TheDockerdaemonstreamedthatoutputtotheDockerclient,whichsentit

toyourterminal.

Totrysomethingmoreambitious,youcanrunanUbuntucontainerwith:

$dockerrun-itubuntubash

Shareimages,automateworkflows,andmorewithafreeDockerID:

https://cloud.docker.com/

Formoreexamplesandideas,visit:

https://docs.docker.com/engine/userguide/

TolisttheDockerimagesavailableonthelocalmachine,executethedockerimagescommandasshowninthefollowingexample;itlistsalltheavailableimages:

Page 406: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

%dockerimages

REPOSITORYTAGIMAGEIDCREATEDSIZE

hello-worldlatestf2a91732366c7weeksago1.85kB

Similarly,tolisttheDockercontainersthatwerecreated,usethedockerps-acommand,asusedearlier.Thistime,itshouldlistthecontainersthatwerestartedusingthehello-worldimage,asshownhere:

%dockerps-a

CONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES

e0e5678ef80ahello-world"/hello"5minutesagoExited(0)5minutesago

happy_rosalind

ea9815d87660hello-world"/hello"8minutesagoExited(0)8minutesago

fervent_engelbart

Formoredetailsontheavailablecommandsandoptions,refertotheDockerCLIcommandsreferenceguide(https://docs.docker.com/engine/reference/commandline/docker/).

FormoredetailsonDockersetupandconfigurationoptions,takealookattheDockerpostinstallationguide(https://docs.docker.com/engine/installation/linux/linux-postinstall/)

Page 407: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CreatingaDockerimageforHelpingHandsHelpingHandsservicescreatedinthepreviouschaptershaveaDockerfilecreatedasapartoftheprojecttemplate.Forexample,takealookatthedirectorystructureoftheAuthservice,asshowninthefollowingexample.TorunwithinaDockercontainer,the::http/hostkeyoftheservicedefinitionwithinthehelping-hands.auth.servicenamespacemustbesettoafixedIPaddressor0.0.0.0tobindtoalltheIPv4addressesavailablewithinthecontainer.

%tree-L1

.

├──Capstanfile

├──config

├──Dockerfile

├──project.clj

├──README.md

├──resources

├──src

├──target

└──test

5directories,4files

ChangethecontentoftheDockerfilefortheAuthservice,asshowninthefollowingexample.Itcopiesboththeconfigdirectoryandthestand-aloneJARfileoftheAuthserviceofHelpingHands.Ifthestand-aloneJARisnotpresentinthetargetfolder,createitusingtheleinuberjarcommandthatwillcreateastand-aloneJARinthetargetdirectoryoftheAuthproject.

FROMjava:8-alpine

MAINTAINERHelpingHands<[email protected]>

COPYtarget/helping-hands-auth-0.0.1-SNAPSHOT-standalone.jar/helping-hands/app.jar

COPYconfig/conf.edn/helping-hands/

EXPOSE8080

CMDexecjava-Dconf=/helping-hands/conf.edn-jar/helping-hands/app.jar

Next,createaDockerimageusingthedockerbuildcommand,asshowninthefollowingexample.ThedockerbuildcommandlooksforaDockerfileinthesamedirectorywhereitstartedfrom.IfDockerfileispresentatsomeotherlocation,explicitpathtotheDockerfilecanbespecifiedusingthe-fflag.Formoredetails

Page 408: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

onthedockerbuildcommand,refertotheusageinstructions(https://docs.docker.com/engine/reference/builder/#usage).

#buildthedockerimage

%dockerbuild-thelping-hands/auth:0.0.1.

SendingbuildcontexttoDockerdaemon48.44MB

Step1/6:FROMjava:8-alpine

8-alpine:Pullingfromlibrary/java

709515475419:Pullcomplete

38a1c0aaa6fd:Pullcomplete

5b58c996e33e:Pullcomplete

Digest:sha256:d49bf8c44670834d3dade17f8b84d709e7db47f1887f671a0e098bafa9bae49f

Status:Downloadednewerimageforjava:8-alpine

--->3fd9dd82815c

Step2/6:MAINTAINERHelpingHands<[email protected]>

--->Runningindd79676d69a4

--->359095b88f32

Removingintermediatecontainerdd79676d69a4

Step3/6:COPYtarget/helping-hands-auth-0.0.1-SNAPSHOT-standalone.jar/helping-

hands/app.jar

--->952111f1c330

Removingintermediatecontainer888323c4cc30

Step4/6:COPYconfig/conf.edn/helping-hands/

--->3c43dfd4af83

Removingintermediatecontainer028df1e03d58

Step5/6:EXPOSE8080

--->Runningin8cf6c15cab9f

--->e79d993e2c67

Removingintermediatecontainer8cf6c15cab9f

Step6/6:CMDexecjava-Dconf=/helping-hands/conf.edn-jar/helping-hands/app.jar

--->Runningin0b4549cf84f2

--->f8c9a7e746f3

Removingintermediatecontainer0b4549cf84f2

Successfullybuiltf8c9a7e746f3

#listtheimagestomakesureitisavailable

%dockerimages

REPOSITORYTAGIMAGEIDCREATEDSIZE

helping-hands/auth0.0.1f8c9a7e746f317secondsago174MB

hello-worldlatestf2a91732366c7weeksago1.85kB

java8-alpine3fd9dd82815c10monthsago145MB

Oncetheimageiscreatedandregisteredusingthespecifiednameandtag,newcontainerscanbecreatedfromthesameimage,asshownhere:

#createanewcontainerfromthetaggedimage

%dockerrun-d-p8080:8080--namehh_auth_01helping-hands/auth:0.0.1

286f21a088dd8b6b6d814f1fb5e4d27a59f46b6d8c474160628ffe72d3de2b56

#verifythatthecontainerisrunning

%dockerps-a

CONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES

286f21a088ddhelping-hands/auth:0.0.1"/bin/sh-c'exec..."5secondsagoUp3

seconds0.0.0.0:8080->8080/tcphh_auth_01

e0e5678ef80ahello-world"/hello"51minutesagoExited(0)50minutesago

happy_rosalind

ea9815d87660hello-world"/hello"54minutesagoExited(0)53minutesago

fervent_engelbart

Page 409: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

CheckthelogmessagesgeneratedbyDockertomakesurethatAuthserviceisupandrunning,asshownhere:

%dockerlogs286f21a088dd

Creatingyourserver...

Omniconfconfiguration:

{:conf#object[java.io.File0x47c40b56"/helping-hands/conf.edn"]}

TheAuthservicecannowbeaccesseddirectlyatthe8080portasshowninthefollowingexample.Theportismappedusingthedockerruncommandwith-pflag,asusedearlierwhilecreatingthecontainer.

%curl-i"http://localhost:8080/tokens?uid=hhuser&pwd=hhuser"

HTTP/1.1200OK

...

Authorization:Bearer

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.1enLmASKP8uqPGvW_bOVcGS8-

0wtR3AS0xxGolaNixXCSXaY_7LKqw.RcXp4s0397a3M_EB-DyFAQ.B6b93-

1_grZa7HJee6nkcT4LM3gV7QxmR3CIHxX9ngzFqPyyJTcBWvo2N4TTlY4gJYgeNtIyaJsAmvVYCEi7YKyp47bF1wzgFbpjkfVen6y-

580kmf5JqaP2vXQmNpFiVRB6FGGqldnAaDKdBCCrv0HRgGbaxyg_F_05j4G9AktO26hUMfXvmd9woh61Id-

lV4xvRZOcn57X6aH-HL2JuA.hUWvDD6lQWmXaRGYCf3YOQ

Transfer-Encoding:chunked

Tostopthecontainer,usethedockerstopcommand,andtodeletethecontainer,usethedockerrmcommand,asshownhere:

%dockerstophh_auth_01

hh_alert_01

%dockerrmhh_auth_01

hh_alert_01

%dockerps-a

CONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES

e0e5678ef80ahello-world"/hello"54minutesagoExited(0)54minutesago

happy_rosalind

ea9815d87660hello-world"/hello"57minutesagoExited(0)57minutesago

fervent_engelbart

FormoredetailsonhowtocreateeffectiveDockerfile,takealookatthebestpracticesforwritingDockerfiles(https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/).

Page 410: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

IntroducingKubernetesContainerizingtheservicesoftheHelpingHandsapplicationallowsthemtobedeployedacrossmultiplemachinesfaster,butscalingthemrequiresmanualeffortandinvolvementoftheDevOpsteamtoscaleitupanddown.Monitoringalltherunningcontainersmayalsobecomeoverwhelmingovertime,astheservicesmayscaletohundredsofinstancesrunningcontainersacrosstheclusterofmachines.Althoughthefailureofanyserviceorcontainercanbealertedtotheteam,butrunningthemmanuallyisatedioustask.Moreover,itisexhaustiveandoftenerror-pronetoestimateandachieveeffectiveresourceutilizationandoptimallybalancethenumberofrunninginstancesofeachservicemanually.

Toavoidsuchmanualtasksandensurethattheconfigurednumberofservicesarealwaysrunningandeffectivelyutilizingtheavailableresources,containerorchestrationenginesarerequired.Kubernetesisonesuchopensourcecontainerorchestrationenginethatiswidelyusedforautomateddeployment,scaling,andmanagementofcontainerizedapplicationssuchastheservicesoftheHelpingHandsapplication.

InaKubernetesdeployment,therearetwokindsofmachines,MasterandNode(previouslyknownasMinions).MasterinstancesarethebrainofKubernetesenginethatmakeallthedecisionsrelatedtothedeploymentofcontainersandalsorespondtovariouseventsoffailuresandnewallocationrequests.Masterinstancerunskube-apiserver(https://kubernetes.io/docs/admin/kube-apiserver/),etcd(https://kuber

Page 411: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

netes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/),kube-controller-manager(https://kubernetes.io/docs/admin/kube-controller-manager/),andkube-scheduler(https://kubernetes.io/docs/admin/kube-scheduler/).Theyalsorunkube-proxy(https://kubernetes.io/docs/admin/kube-proxy/)toworkwithinthesameoverlaynetworkasthatofnodes.ItisrecommendedtorunMasteronseparatemachinesthatarededicatedforclustermanagementtasksonly.

Nodes,ontheotherhand,areworkermachinesinaKubernetesclusterandrunsPods.APod(https://kubernetes.io/docs/concepts/workloads/pods/pod/)isasmallestunitofcomputingthatcanbecreatedandmanagedbyKubernetescluster.Itisagroupofoneormorecontainersthatsharenetwork,storage,andacommonsetofspecifications.EachNoderunsaDockerservice,kubelet(https://kubernetes.io/docs/admin/kubelet/),andkube-proxyandismanagedbythemastercomponents.ThekubeletagentrunsoneachNodeandmanagesthepodsthatareallocatedtotheNode.ItalsoreportsthestatusofthepodsbacktotheKubernetescluster.

FormoredetailsonKubernetesMasterandNodecomponents,takealookatKubernetesConceptsdocumentathttps://kubernetes.io/docs/concepts/overview/components/.

Kuberneteshasanin-builtsupportforDockercontainers.ServicesuchasAutomaticBinPacking(https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)foreffectiveutilizationoftheresources,horizontalscaling(https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)toscaletheservicesupanddownandbasedonfactorssuchasCPUusageandSelf-Healing(https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller)toautomaticallyrestartcontainersthatfailisprovidedout-of-the-boxbyKubernetes.

KubernetesalsosupportsServiceDiscoveryandLoadBalancingbyallocatingcontainerstheirownIPaddresses.ItalsoallocatesacommonDNSnameforasetofcontainersthatallowotherexternalservicestojustknowtheDNSnameandusethesametoreachtheservice.KubernetesinternallybalancestherequestsamongtheservicesrunninginthecontainersthatareregisteredwiththeDNSwiththespecifiedname.RollingUpgrades(https://kubernetes.io/docs/tutorials/kubernetes-basics/update-intro/)arealsoprovidedbyKubernetesbyincrementallyupgradingthecontainerswiththenewones.AllupdatesareversionedbyKubernetes,anditallowsrollbacktoanypreviousstableversion.

Page 412: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

FormoredetailsonKubernetes,takealookatKubernetestutorialsthatcoverallthebasicfeaturesofKuberneteswithexamplesfromhttps://kubernetes.io/docs/tutorials/.

Page 413: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

GettingstartedwithKubernetesThesimplestwaytogetstartedwithKubernetesandrunlocallyonasinglemachineistouseMinikube(https://github.com/kubernetes/minikube).TosetupMinikube,usethefollowinginstallationscript:

%curl-Lominikubehttps://storage.googleapis.com/minikube/releases/latest/minikube-

linux-amd64&&chmod+xminikube&&sudomvminikube/usr/local/bin/

TheprecedingcommanddownloadsthelatestreleaseofMinikubescriptandmakesitavailableonthepathbycopyingittothe/usr/local/bindirectory.Minikubealsorequireskube-ctltointeractwiththeKubernetescluster.Tosetupkube-ctl,usetheinstallationscriptofkube-ctl,asshownhere:

#downloadkube-ctlscript

%curl-LOhttps://storage.googleapis.com/kubernetes-release/release/$(curl-s

https://storage.googleapis.com/kubernetes-

release/release/stable.txt)/bin/linux/amd64/kubectl

#makethescriptexecutable

%chmod+x./kubectl

#makeitavailableonthepath

%sudomv./kubectl/usr/local/bin/kubectl

FormoredetailsonhowtousetheminikubecommandtocreateaKubernetesclusterandkube-ctltointeractwiththeMasteranddeploycontainers,takealookattheMinikubeprojectdocumentation(https://github.com/kubernetes/minikube).

Page 414: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

SummaryInthischapter,youlearnedhowtopreparemicroservicesforproduction.WefocusedonsecurityandmonitoringofmicroservicesoftheHelpingHandsapplication.Wealsolearnedhowtodeployandscalemicroservicesusingcontainers.WealsodiscussedorchestrationenginessuchasKubernetesandhowtheyareusefultoorchestratecontainers.Withthischapter,youareallsettobuildyournextbestapplicationusingmicroservices-basedarchitectureanddeployiteffectivelyinproduction.Whatwillyoubuildnext?

Page 415: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:

MasteringMicroserviceswithJava9-SecondEditionSourabhSharma

ISBN:978-1-78728-144-8

Usedomain-drivendesigntodesignandimplementmicroservicesSecuremicroservicesusingSpringSecurityLearntodevelopRESTservicedevelopmentDeployandtestmicroservicesTroubleshootanddebugtheissuesfacedduringdevelopmentLearningbestpracticesandcommonprincipalsaboutmicroservices

Spring5.0Microservices-SecondEditionRajeshRV

ISBN:978-1-78712-768-5

Page 416: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

FamiliarizeyourselfwiththemicroservicesarchitectureanditsbenefitsFindouthowtoavoidcommonchallengesandpitfallswhiledevelopingmicroservicesUseSpringBootandSpringCloudtodevelopmicroservicesHandleloggingandmonitoringmicroservicesLeverageReactiveProgramminginSpring5.0tobuildmoderncloudnativeapplicationsManageinternet-scalemicroservicesusingDocker,Mesos,andMarathonGaininsightsintothelatestinclusionofReactiveStreamsinSpringandmakeapplicationsmoreresilientandscalable

Page 417: Microservices with Clojure: Develop event-driven, scalable ... · Chapter 3, Microservices for Helping Hands Application, introduces a sample Helping Hands application and describes

Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!