table of contents · an introduction to application scaling scaling node.js applications the three...

497

Upload: others

Post on 31-Dec-2020

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in
Page 2: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TableofContentsNode.jsDesignPatternsCreditsAbouttheAuthorAcknowledgmentsAbouttheReviewerswww.PacktPub.comSupportfiles,eBooks,discountoffers,andmore

Whysubscribe?FreeaccessforPacktaccountholders

PrefaceWhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport

DownloadingtheexamplecodeErrataPiracyQuestions

1.Node.jsDesignFundamentalsTheNode.jsphilosophy

SmallcoreSmallmodulesSmallsurfaceareaSimplicityandpragmatism

ThereactorpatternI/OisslowBlockingI/ONon-blockingI/OEventdemultiplexingThereactorpatternThenon-blockingI/OengineofNode.js–libuvTherecipeforNode.js

ThecallbackpatternThecontinuation-passingstyleSynchronouscontinuation-passingstyleAsynchronouscontinuation-passingstyleNoncontinuation-passingstylecallbacks

Page 3: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Synchronousorasynchronous?AnunpredictablefunctionUnleashingZalgoUsingsynchronousAPIsDeferredexecution

Node.jscallbackconventionsCallbackscomelastErrorcomesfirstPropagatingerrorsUncaughtexceptions

ThemodulesystemanditspatternsTherevealingmodulepatternNode.jsmodulesexplainedAhomemademoduleloaderDefiningamoduleDefiningglobalsmodule.exportsvsexportsrequireissynchronousTheresolvingalgorithmThemodulecacheCycles

ModuledefinitionpatternsNamedexportsExportingafunctionExportingaconstructorExportinganinstanceModifyingothermodulesortheglobalscope

TheobserverpatternTheEventEmitterCreateanduseanEventEmitterPropagatingerrorsMakeanyobjectobservableSynchronousandasynchronouseventsEventEmittervsCallbacksCombinecallbacksandEventEmitter

Summary2.AsynchronousControlFlowPatternsThedifficultiesofasynchronousprogramming

CreatingasimplewebspiderThecallbackhell

UsingplainJavaScriptCallbackdisciplineApplyingthecallbackdiscipline

Page 4: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SequentialexecutionExecutingaknownsetoftasksinsequenceSequentialiterationWebspiderversion2SequentialcrawlingoflinksThepattern

ParallelexecutionWebspiderversion3ThepatternFixingraceconditionsinthepresenceofconcurrenttasks

LimitedparallelexecutionLimitingtheconcurrencyGloballylimitingtheconcurrencyQueuestotherescueWebspiderversion4

TheasynclibrarySequentialexecutionSequentialexecutionofaknownsetoftasksSequentialiteration

ParallelexecutionLimitedparallelexecution

PromisesWhatisapromise?Promises/A+implementationsPromisifyingaNode.jsstylefunctionSequentialexecutionSequentialiterationSequentialiteration–thepattern

ParallelexecutionLimitedparallelexecution

GeneratorsThebasicsAsimpleexampleGeneratorsasiteratorsPassingvaluesbacktoagenerator

AsynchronouscontrolflowwithgeneratorsGenerator-basedcontrolflowusingco

SequentialexecutionParallelexecutionLimitedparallelexecutionProducer-consumerpatternLimitingthedownloadtasksconcurrency

Comparison

Page 5: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Summary3.CodingwithStreamsDiscoveringtheimportanceofstreams

BufferingvsStreamingSpatialefficiencyGzippingusingabufferedAPIGzippingusingstreams

TimeefficiencyComposability

GettingstartedwithstreamsAnatomyofstreamsReadablestreamsReadingfromastreamThenon-flowingmodeTheflowingmode

ImplementingReadablestreamsWritablestreamsWritingtoastreamBack-pressureImplementingWritablestreams

DuplexstreamsTransformstreamsImplementingTransformstreams

ConnectingstreamsusingpipesUsefulpackagesforworkingwithstreamsreadable-streamthroughandfrom

AsynchronouscontrolflowwithstreamsSequentialexecutionUnorderedparallelexecutionImplementinganunorderedparallelstreamImplementingaURLstatusmonitoringapplication

UnorderedlimitedparallelexecutionOrderedparallelexecution

PipingpatternsCombiningstreamsImplementingacombinedstream

ForkingstreamsImplementingamultiplechecksumgenerator

MergingstreamsCreatingatarballfrommultipledirectories

MultiplexinganddemultiplexingBuildingaremotelogger

Page 6: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Clientside–MultiplexingServerside–demultiplexingRunningthemux/demuxapplication

MultiplexinganddemultiplexingobjectstreamsSummary

4.DesignPatternsFactory

AgenericinterfaceforcreatingobjectsAmechanismtoenforceencapsulationBuildingasimplecodeprofilerInthewild

ProxyTechniquesforimplementingproxiesObjectcompositionObjectaugmentation

AcomparisonofthedifferenttechniquesCreatingaloggingWritablestreamProxyintheecosystem–functionhooksandAOPInthewild

DecoratorTechniquesforimplementingdecoratorsCompositionObjectaugmentation

DecoratingaLevelUPdatabaseIntroducingLevelUPandLevelDBImplementingaLevelUPplugin

InthewildAdapter

UsingLevelUPthroughthefilesystemAPIInthewild

StrategyMulti-formatconfigurationobjectsInthewild

StateImplementingabasicfail-safesocket

TemplateAconfigurationmanagertemplateInthewild

MiddlewareMiddlewareinExpressMiddlewareasapatternCreatingamiddlewareframeworkfor ØMQTheMiddlewareManager

Page 7: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AmiddlewaretosupportJSONmessagesUsingtheØMQmiddlewareframeworkTheserverTheclient

CommandAflexiblepatternThetaskpatternAmorecomplexcommand

Summary5.WiringModulesModulesanddependencies

ThemostcommondependencyinNode.jsCohesionandCouplingStatefulmodulesTheSingletonpatterninNode.js

PatternsforwiringmodulesHardcodeddependencyBuildinganauthenticationserverusinghardcodeddependenciesThedbmoduleTheauthServicemoduleTheauthControllermoduleTheappmoduleRunningtheauthenticationserver

ProsandconsofhardcodeddependenciesDependencyinjectionRefactoringtheauthenticationservertousedependencyinjectionThedifferenttypesofdependencyinjectionProsandconsofdependencyinjection

ServicelocatorRefactoringtheauthenticationservertouseaservicelocatorProsandconsofaservicelocator

DependencyinjectioncontainerDeclaringasetofdependenciestoaDIcontainerRefactoringtheauthenticationservertouseaDIcontainerProsandconsofaDependencyInjectioncontainer

WiringpluginsPluginsaspackagesExtensionpointsPlugin-controlledvsApplication-controlledextensionImplementingalogoutpluginUsinghardcodeddependenciesExposingservicesusingaservicelocatorExposingservicesusingdependencyinjection

Page 8: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ExposingservicesusingadependencyinjectioncontainerSummary

6.RecipesRequiringasynchronouslyinitializedmodules

CanonicalsolutionsPreinitializationqueuesImplementingamodulethatinitializesasynchronouslyWrappingthemodulewithpreinitializationqueues

InthewildAsynchronousbatchingandcaching

ImplementingaserverwithnocachingorbatchingAsynchronousrequestbatchingBatchingrequestsinthetotalsaleswebserver

AsynchronousrequestcachingCachingrequestsinthetotalsaleswebserverNotesaboutimplementingcachingmechanisms

BatchingandcachingwithPromisesRunningCPU-boundtasks

SolvingthesubsetsumproblemInterleavingwithsetImmediateInterleavingthestepsofthesubsetsumalgorithmConsiderationsontheinterleavingpattern

UsingmultipleprocessesDelegatingthesubsetsumtasktootherprocessesImplementingaprocesspoolCommunicatingwithachildprocessCommunicatingwiththeparentprocess

ConsiderationsonthemultiprocesspatternSharingcodewiththebrowser

SharingmodulesUniversalModuleDefinitionCreatinganUMDmoduleConsiderationsontheUMDpattern

IntroducingBrowserifyExploringthemagicofBrowserifyTheadvantagesofusingBrowserify

Fundamentalsofcross-platformdevelopmentRuntimecodebranchingBuild-timecodebranchingDesignpatternsforcross-platformdevelopment

SharingbusinesslogicanddatavalidationusingBackboneModelsImplementingthesharedmodelsImplementingtheplatform-specificcode

Page 9: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UsingtheisomorphicmodelsRunningtheapplication

Summary7.ScalabilityandArchitecturalPatternsAnintroductiontoapplicationscaling

ScalingNode.jsapplicationsThethreedimensionsofscalability

CloningandloadbalancingTheclustermoduleNotesonthebehavioroftheclustermoduleBuildingasimpleHTTPserverScalingwiththeclustermoduleResiliencyandavailabilitywiththeclustermoduleZero-downtimerestart

DealingwithstatefulcommunicationsSharingthestateacrossmultipleinstancesStickyloadbalancing

ScalingwithareverseproxyLoadbalancingwithNginx

UsingaServiceRegistryImplementingadynamicloadbalancerwithhttp-proxyandseaport

Peer-to-peerloadbalancingImplementinganHTTPclientthatcanbalancerequestsacrossmultiple

serversDecomposingcomplexapplications

MonolithicarchitectureTheMicroservicearchitectureAnexampleoftheMicroservicearchitectureProsandconsofmicroservicesEveryserviceisexpendableReusabilityacrossplatformsandlanguagesAwaytoscaletheapplicationThechallengesofmicroservices

IntegrationpatternsinaMicroservicearchitectureTheAPIproxyAPIorchestrationIntegrationwithamessagebroker

Summary8.MessagingandIntegrationPatternsFundamentalsofamessagingsystem

One-wayandrequest/replypatternsMessagetypesAsynchronousmessagingandqueues

Page 10: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Peer-to-peerorbroker-basedmessagingPublish/subscribepattern

Buildingaminimalistreal-timechatapplicationImplementingtheserversideImplementingtheclientsideRunningandscalingthechatapplication

UsingRedisasamessagebrokerPeer-to-peerpublish/subscribewithØMQIntroducingØMQDesigningapeer-to-peerarchitectureforthechatserverUsingtheØMQPUB/SUBsockets

DurablesubscribersIntroducingAMQPDurablesubscriberswithAMQPandRabbitMQDesigningahistoryserviceforthechatapplicationImplementingareliablehistoryserviceusingAMQPIntegratingthechatapplicationwithAMQP

PipelinesandtaskdistributionpatternsTheØMQfan-out/fan-inpatternPUSH/PULLsocketsBuildingadistributedhashsumcrackerwithØMQImplementingtheventilatorImplementingtheworkerImplementingthesinkRunningtheapplication

PipelinesandcompetingconsumersinAMQPPoint-to-pointcommunicationsandcompetingconsumersImplementingthehashsumcrackerusingAMQPImplementingtheproducerImplementingtheworkerImplementingtheresultcollectorRunningtheapplication

Request/replypatternsCorrelationidentifierImplementingarequest/replyabstractionusingcorrelationidentifiersAbstractingtherequestAbstractingthereplyTryingthefullrequest/replycycle

ReturnaddressImplementingthereturnaddresspatterninAMQPImplementingtherequestabstractionImplementingthereplyabstractionImplementingtherequestorandthereplier

Page 11: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryIndex

Page 12: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Node.jsDesignPatterns

Page 13: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Node.jsDesignPatternsCopyright©2014PacktPublishing

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

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:December2014

Productionreference:1231214

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78328-731-4

www.packtpub.com

CoverimagebyArtieNg(<[email protected]>)

Page 14: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CreditsAuthor

MarioCasciaro

Reviewers

AfshinMehrabani

JoelPurra

AlanShaw

CommissioningEditor

JulianUrsell

AcquisitionEditor

RebeccaYoué

ContentDevelopmentEditor

SriramNeelakantan

TechnicalEditor

MenzaMathew

CopyEditors

ShambhaviPai

RashmiSawant

ProjectCoordinator

AboliAmbardekar

Proofreaders

StephenCopestake

Page 15: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AmeeshaGreen

SteveMaguire

Indexers

HemanginiBari

MariammalChettiyar

RekhaNair

TejalSoni

Graphics

ValentinaD'silva

DishaHaria

AbhinashSahu

ProductionCoordinator

NiteshThakur

CoverWork

NiteshThakur

Page 16: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AbouttheAuthorMarioCasciaroisasoftwareengineerandtechnicalleadwithapassionforopensource.HebeganprogrammingwithaCommodore64whenhewas12,andgrewupwithPascalandVisualBasic.Hisprogrammingskillsevolvedbyexperimentingwithx86assemblylanguage,C,C++,PHP,andJava.HisrelentlessworkonsideprojectsledhimtodiscoverJavaScriptandNode.js,whichquicklybecamehisnewpassion.

Inhisprofessionalcareer,heworkedwithIBMforseveralyears—firstinRomeandthenintheDublinSoftwareLab.AtIBM,MarioworkedonproductsforbrandssuchasTivoli,Cognos,andCollaborationSolutions,usingavarietyoftechnologiesfromCtoPHPandJava.HethenplungedintotheadventurousworldofstartupstoworkfulltimeonNode.jsprojects.Heendedupworkinginalighthouse,atD4HTechnologies,whereheledthedevelopmentofareal-timeplatformtomanageemergencyoperations.

Page 17: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AcknowledgmentsThisbookistheresultofanamazingamountofwork,knowledge,andperseverancefrommanypeople.AbigthanksgoestotheentireteamatPacktwhomadethisbookareality,fromtheeditorstotheprojectcoordinator;inparticular,IwouldliketothankRebeccaYouéandSriramNeelakantanfortheirguidanceandpatienceduringthetoughestpartsofthewritingprocess.KudostoAlanShaw,JoelPurra,andAfshinMehrabaniwhodedicatedtheirtimeandexpertisetoreviewingthetechnicalcontentofthebook;everycommentandadvicewasreallyinvaluableinbringingthisworkuptoproductionquality.ThisbookwouldnotexistwithouttheeffortsofsomanypeoplewhomadeNode.jsareality—fromthebigplayers,whocontinuouslyinspiredus,tothecontributorofthesmallestmodule.

Inthesemonths,Ialsolearnedthatabookisonlypossiblewiththesupportandencouragementofallthepeoplearoundyou.Mygratitudegoestoallmyfriendswhoheardthephrase"TodayIcan't,Ihavetoworkonthebook"toomanytimes;thankstoChristopheGuillou,ZbigniewMrowinski,RyanGallagher,NataliaLopez,RuizhiWang,andDavideLionelloforstilltalkingtome.ThankstotheD4Hcrew,fortheirinspirationandunderstanding,andforgivingmethechancetoworkonafirst-classproduct.

ThankstoallthefriendsbackinItaly,tothelegendarycompanyofTavernaandCentrale,totheladsofLidoMariniforalwaysgivingmeagreattime,laughingandhavingfun.I'msorryfornotbeingpresentinthepastfewmonths.

ThankstomyMomandDad,andtomybrotherandsister,fortheirunconditionalloveandsupport.

Atlast,youhavetoknowthatthereisanotherpersonwhowrotethisbookalongwithme,that'sMiriam,mygirlfriend,whowalkedthroughoutthislongjourneywithmeandsupportedmenightandday,regardlessofhowdifficultitwas.There'snothingmoreonecouldwishfor.Isendallmyloveandgratitudetoher.Manyadventuresawaitus.

Page 18: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AbouttheReviewersAfshinMehrabaniisanopensourceprogrammer.Heisstudyingtobeacomputersoftwareengineer.Hestartedprogrammingandwebdevelopmentwhenhewas12yearsold,andstartedwithPHPaswell.Later,hejoinedtheIranTechnicalandVocationalTrainingOrganization.Hesecuredthefirstplaceandreceivedagoldmedalinacompetitionthatwasconductedacrosstheentirecountryintheareaofwebdevelopment.HebecameamemberofIran'sNationalElitesFoundationafterproducingavarietyofnewprogrammingideas.

HewasasoftwareengineeratTehranStockExchangeandisnowtheheadofthewebdevelopmentteaminYaraInternational.HecofoundedtheUsablicateaminearly2012todevelopandproduceusableapplications.HeistheauthorofIntroJs,WideArea,flood.js,andotheropensourceprojects.HehascontributedtoSocket.IO,Engine.IO,andotheropensourceprojects.Heisalsointerestedincreatingandcontributingtoopensourceapplications,writingprogrammingarticles,andchallenginghimselfwithnewprogrammingtechnologies.

HehaswrittendifferentarticlesonJavaScript,Node.js,HTML5,andMongoDB,whichhavebeenpublishedondifferentacademicwebsites.Afshinhas5yearsofexperienceinPHP,Python,C#,JavaScript,HTML5,andNode.jsinmanyfinancialandstock-tradingprojects.

JoelPurrastartedtoyingaroundwithcomputerssometimebeforehisteens,seeingthemasanotherkindofavideo-gamingdevice.Itwasnotlongbeforehetookapart(sometimesbrokeandsubsequentlyfixed)anycomputerhecameacross,inbetweenplayingthelatestgamesonthem.Itwasgamingthatledhimtodiscoverprogramminginhisearlyteens,whenmodifyingaLunarLandergametriggeredaninterestincreatingdigitaltools.SoonaftergettinganInternetconnectionathome,hedevelopedhisfirste-commercewebsite,andthushisbusinessstarted;itlaunchedhiscareeratanearlyage.

Attheageof17,Joelstartedstudyingcomputerprogrammingandanenergy/scienceprogramatanuclearpowerplant'sschool.Aftergraduation,hestudiedtobecomeaSecondLieutenantTelecommunicationsSpecialistintheSwedishArmy,beforemovingontostudyforhisMasterofSciencedegreeinInformationTechnologyandEngineeringatLinköpingUniversity.

Hehasbeeninvolvedinstartupsandothercompanies—bothsuccessfulandunsuccessful—since1998,andhasbeenaconsultantsince2007.Born,raised,andeducatedinSweden,Joelalsoenjoystheflexiblelifestyleofafreelancedeveloper,havingtraveledthroughfivecontinentswithhisbackpackandlivedabroadforseveral

Page 19: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

years.Alearnerconstantlylookingforchallenges,oneofhisgoalsistobuildandevolvesoftwareforbroadpublicuse.

Youcanvisithiswebsiteathttp://joelpurra.com/.

I'dliketothanktheopensourcecommunityforgivingmethebuildingblocksnecessarytocomposebothsmallandlargesoftwaresystems,evenasafreelanceconsultant.Nanosgigantumhumerisinsidentes.Remembertocommitearly,commitoften!

AlanShawdescribeshimselfasawebdeveloperwhodiscoversthelimitsofthepossiblebyventuringalittlewaypastthemintotheimpossible.AlanhasbuiltandstyledtheWebeverydaysincegraduatingfromtheUniversityofBathwithadegreeincomputerscience.HeisanadvocateoffunctionalprogrammingandhasworkedwithJavaScriptforaslongashecanremember.

AlanandOliEvansownandrunTABLEFLIP,awebdevelopmentcompanythatfocusesonNode.js,goodclientrelationships,andgivingbacktothecommunitythroughopensourceprojects.

Inhissparetime,Alanhacksonnpmmodules,browserifytransforms,andgruntplugins.HebuildsandmaintainsDavid(https://david-dm.org),co-organizesthemeetupsforNodebotsofLondonandMeteorLondon,hacksonhardware,pilotsnanocoptersintowalls,andisacofounderoftheJavaScriptAdventureClub.

Page 20: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt'sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt'sentirelibraryofbooks.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsfor

Page 21: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

immediateaccess.

Page 22: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

PrefaceNode.jsisconsideredbymanyasagame-changer—thebiggestshiftofthedecadeinwebdevelopment.Itislovednotjustforitstechnicalcapabilities,butalsoforthechangeofparadigmthatitintroducedinwebdevelopment.

First,Node.jsapplicationsarewritteninJavaScript,thelanguageoftheweb,theonlyprogramminglanguagesupportednativelybyamajorityofwebbrowsers.Thisaspectonlyenablesscenariossuchassingle-languageapplicationstacksandsharingofcodebetweentheserverandtheclient.Node.jsitselfiscontributingtotheriseandevolutionoftheJavaScriptlanguage.PeoplerealizethatusingJavaScriptontheserverisnotasbadasitisinthebrowser,andtheywillsoonstarttoloveitforitspragmatismandforitshybridnature,halfwaybetweenobject-orientedandfunctionalprogramming.

Thesecondrevolutionizingfactorisitssingle-threaded,asynchronousarchitecture.Besidesobviousadvantagesfromaperformanceandscalabilitypointofview,thischaracteristicchangedthewaydevelopersapproachconcurrencyandparallelism.Mutexesarereplacedbyqueues,threadsbycallbacksandevents,andsynchronizationbycausality.

ThelastandmostimportantaspectofNode.jsliesinitsecosystem:thenpmpackagemanager,itsconstantlygrowingdatabaseofmodules,itsenthusiasticandhelpfulcommunity,andmostimportantly,itsveryownculturebasedonsimplicity,pragmatism,andextrememodularity.

However,becauseofthesepeculiarities,Node.jsdevelopmentgivesyouaverydifferentfeelcomparedtotheotherserver-sideplatforms,andanydevelopernewtothisparadigmwilloftenfeelunsureabouthowtotackleeventhemostcommondesignandcodingproblemeffectively.Commonquestionsinclude:"HowdoIorganizemycode?","What'sthebestwaytodesignthis?","HowcanImakemyapplicationmoremodular?","HowdoIhandleasetofasynchronouscallseffectively?","HowcanImakesurethatmyapplicationwillnotcollapsewhileitgrows?",ormoresimply"What'stherightwayofdoingthis?"Fortunately,Node.jshasbecomeamature-enoughplatformandmostofthesequestionscannowbeeasilyansweredwithadesignpattern,aprovencodingtechnique,orarecommendedpractice.Theaimofthisbookistoguideyouthroughthisemergingworldofpatterns,techniques,andpractices,showingyouwhattheprovensolutionstothecommonproblemsareandteachingyouhowtousethemasthestartingpointtobuildingthesolutiontoyourparticularproblem.

Byreadingthisbook,youwilllearnthefollowing:

Page 23: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

The"Nodeway".HowtousetherightpointofviewwhenapproachingaNode.jsdesignproblem.Youwilllearn,forexample,howdifferenttraditionaldesignpatternslookinNode.js,orhowtodesignmodulesthatdoonlyonething.AsetofpatternstosolvecommonNode.jsdesignandcodingproblems.Youwillbepresentedwitha"Swissarmyknife"ofpatterns,ready-to-useinordertoefficientlysolveyoureverydaydevelopmentanddesignproblems.HowtowritemodularandefficientNode.jsapplications.Youwillgainanunderstandingofthebasicbuildingblocksandprinciplesofwritinglargeandwell-organizedNode.jsapplicationsandyouwillbeabletoapplytheseprinciplestonovelproblemsthatdon'tfallwithinthescopeofexistingpatterns.

Throughoutthebook,youwillbepresentedwithseveralreal-lifelibrariesandtechnologies,suchasLevelDb,Redis,RabbitMQ,ZMQ,Express,andmanyothers.Theywillbeusedtodemonstrateapatternortechnique,andbesidesmakingtheexamplemoreuseful,thesewillalsogiveyougreatexposuretotheNode.jsecosystemanditssetofsolutions.

WhetheryouuseorplantouseNode.jsforyourwork,yoursideproject,orforanopensourceproject,recognizingandusingwell-knownpatternsandtechniqueswillallowyoutouseacommonlanguagewhensharingyourcodeanddesign,andontopofthat,itwillhelpyougetabetterunderstandingaboutthefutureofNode.jsandhowtomakeyourowncontributionsapartofit.

Page 24: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WhatthisbookcoversChapter1,Node.jsDesignFundamentals,servesasanintroductiontotheworldofNode.jsapplicationdesignbyshowingthepatternsatthecoreoftheplatformitself.Itcoversthereactorpattern,thecallbackpattern,themodulepattern,andtheobserverpattern.

Chapter2,AsynchronousControlFlowPatterns,introducesasetofpatternsandtechniquesforefficientlyhandlingasynchronouscontrolflowinNode.js.Thischapterteachesyouhowtomitigatethe"callbackhell"problemusingplainJavaScript,theasynclibrary,Promises,andGenerators.

Chapter3,CodingwithStreams,divesdeeplyintooneofthemostimportantpatternsinNode.js:Streams.Itshowsyouhowtoprocessdatawithtransformstreamsandhowtocombinethemintodifferentlayouts.

Chapter4,DesignPatterns,dealswithacontroversialtopic:traditionaldesignpatternsinNode.js.ItcoversthemostpopularconventionaldesignpatternsandshowsyouhowunconventionaltheymightlookinNode.js.

Chapter5,WiringModules,analyzesthedifferentsolutionsforlinkingthemodulesofanapplicationtogether.Inthischapter,youwilllearndesignpatternssuchasDependencyInjectionandServicelocator.

Chapter6,Recipes,takesaproblem-solutionapproachtoshowyouhowsomecommoncodinganddesignchallengescanbesolvedwithready-to-usesolutions.

Chapter7,ScalabilityandArchitecturalPatterns,teachesyouthebasictechniquesandpatternsforscalingaNode.jsapplication.

Chapter8,MessagingandIntegrationPatterns,presentsthemostimportantmessagingpatterns,teachingyouhowtobuildandintegratecomplexdistributedsystemsusingZMQandAMQP.

Page 25: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WhatyouneedforthisbookToexperimentwiththecode,youwillneedaworkinginstallationofNode.jsversion0.10(orgreater)andnpm.SomeexampleswillrequireNode.js0.11orgreater.Youwillalsoneedtobefamiliarwiththecommandprompt,knowhowtoinstallannpmpackage,andknowhowtorunNode.jsapplications.Youwillalsoneedatexteditortoworkwiththecodeandawebbrowser.

Page 26: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WhothisbookisforThisbookisfordeveloperswhohavealreadyhadinitialcontactwithNode.jsandnowwanttogetthemostoutofitintermsofproductivity,designquality,andscalability.Youareonlyrequiredtohavesomepriorexposuretothetechnologythroughsomebasicexamples,sincethisbookwillcoversomebasicconceptsaswell.DeveloperswithintermediateexperienceinNode.jswillalsofindthetechniquespresentedinthisbookbeneficial.

Somebackgroundinsoftwaredesigntheoryisalsoanadvantagetounderstandsomeoftheconceptspresented.

Thisbookassumesthatyouhaveaworkingknowledgeofwebapplicationdevelopment,JavaScript,webservices,databases,anddatastructures.

Page 27: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.

Codewordsintextareshownasfollows:"Wecanincludeothercontextsthroughtheuseoftheincludedirective."

Ablockofcodeissetasfollows:

varzmq=require('zmq')

varsink=zmq.socket('pull');

sink.bindSync("tcp://*:5001");

sink.on('message',function(buffer){

console.log('Messagefromworker:',buffer.toString());

});

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

functionproduce(){

[...]

variationsStream(alphabet,maxLength)

.on('data',function(combination){

[...]

varmsg={searchHash:searchHash,variations:batch};

channel.sendToQueue('jobs_queue',

newBuffer(JSON.stringify(msg)));

[...]

}

})

[...]

}

Anycommand-lineinputoroutputiswrittenasfollows:

nodereplier

noderequestor

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:"Toexplaintheproblem,wewillcreatealittlewebspider,acommand-lineapplicationthattakesinawebURLastheinputanddownloadsitscontentslocallyintoafile."

Page 28: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

Page 29: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

Tosendusgeneralfeedback,simplysendane-mailto<[email protected]>,andmentionthebooktitlethroughthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideonwww.packtpub.com/authors.

Page 30: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/support,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.

PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

Questions

Page 31: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Youcancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.

Page 32: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter1.Node.jsDesignFundamentalsSomeprinciplesanddesignpatternsliterallydefinetheNode.jsplatformanditsecosystem;themostpeculiaronesareprobablyitsasynchronousnatureanditsprogrammingstylethatmakesheavyuseofcallbacks.However,thereareotherfundamentalcomponentsthatcharacterizetheplatform;forexample,itsmodulesystem,whichallowsmultipleversionsofthesamedependencytocoexistinanapplication,andtheobserverpattern,implementedbytheEventEmitterclass,whichperfectlycomplementscallbackswhendealingwithasynchronouscode.It'sthereforeimportantthatwefirstdiveintothesefundamentalprinciplesandpatterns,notonlyforwritingcorrectcode,butalsotobeabletotakeeffectivedesigndecisionswhenitcomestosolvingbiggerandmorecomplexproblems.

AnotheraspectthatcharacterizesNode.jsisitsphilosophy.ApproachingNode.jsisinfactwaymorethansimplylearninganewtechnology;it'salsoembracingacultureandacommunity.Wewillseehowthisgreatlyinfluencesthewaywedesignourapplicationsandcomponents,andthewaytheyinteractwiththosecreatedbythecommunity.

Inthischapter,wewilllearnthefollowingtopics:

TheNode.jsphilosophy,the"Nodeway"Thereactorpattern:themechanismattheheartoftheNode.jsasynchronousarchitectureTheNode.jscallbackpatternanditssetofconventionsThemodulesystemanditspatterns:thefundamentalmechanismsfororganizingcodeinNode.jsTheobserverpatternanditsNode.jsincarnation:theEventEmitterclass

TheNode.jsphilosophyEveryplatformhasitsownphilosophy—asetofprinciplesandguidelinesthataregenerallyacceptedbythecommunity,anideologyofdoingthingsthatinfluencestheevolutionofaplatform,andhowapplicationsaredevelopedanddesigned.Someoftheseprinciplesarisefromthetechnologyitself,someofthemareenabledbyitsecosystem,somearejusttrendsinthecommunity,andothersareevolutionsofdifferentideologies.InNode.js,someoftheseprinciplescomedirectlyfromitscreator,RyanDahl,fromallthepeoplewhocontributedtothecore,fromcharismaticfiguresinthecommunity,andsomeoftheprinciplesareinheritedfromtheJavaScript

Page 33: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

cultureorareinfluencedbytheUnixphilosophy.

Noneoftheserulesareimposedandtheyshouldalwaysbeappliedwithcommonsense;however,theycanprovetobetremendouslyusefulwhenwearelookingforasourceofinspirationwhiledesigningourprograms.

NoteYoucanfindanextensivelistofsoftwaredevelopmentphilosophiesinWikipediaathttp://en.wikipedia.org/wiki/List_of_software_development_philosophies.

SmallcoreTheNode.jscoreitselfhasitsfoundationsbuiltonafewprinciples;oneoftheseis,havingthesmallestsetoffunctionality,leavingtheresttotheso-calleduserland(oruserspace),theecosystemofmoduleslivingoutsidethecore.ThisprinciplehasanenormousimpactontheNode.jsculture,asitgivesfreedomtothecommunitytoexperimentanditeratefastonabroadersetofsolutionswithinthescopeoftheuserlandmodules,insteadofbeingimposedwithoneslowlyevolvingsolutionthatisbuiltintothemoretightlycontrolledandstablecore.Keepingthecoresetoffunctionalitytothebareminimumthen,notonlybecomesconvenientintermsofmaintainability,butalsointermsofthepositiveculturalimpactthatitbringsontheevolutionoftheentireecosystem.

SmallmodulesNode.jsusestheconceptofmoduleasafundamentalmeantostructurethecodeofaprogram.Itisthebrickforcreatingapplicationsandreusablelibrariescalledpackages(apackageisalsofrequentlyreferredtoasjustmodule;since,usuallyithasonesinglemoduleasanentrypoint).InNode.js,oneofthemostevangelizedprinciplesistodesignsmallmodules,notonlyintermsofcodesize,butmostimportantlyintermsofscope.

ThisprinciplehasitsrootsintheUnixphilosophy,particularlyintwoofitsprecepts,whichareasfollows:

"Smallisbeautiful.""Makeeachprogramdoonethingwell."

Node.jsbroughttheseconceptstoawholenewlevel.Alongwiththehelpofnpm,theofficialpackagemanager,Node.jshelpssolvingthedependencyhellproblemby

Page 34: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

makingsurethateachinstalledpackagewillhaveitsownseparatesetofdependencies,thusenablingaprogramtodependonalotofpackageswithoutincurringinconflicts.TheNodeway,infact,involvesextremelevelsofreusability,wherebyapplicationsarecomposedofahighnumberofsmall,well-focuseddependencies.Whilethiscanbeconsideredunpracticaloreventotallyunfeasibleinotherplatforms,inNode.jsthispracticeisencouraged.Asaconsequence,itisnotraretofindnpmpackagescontaininglessthan100linesofcodeorexposingonlyonesinglefunction.

Besidestheclearadvantageintermsofreusability,asmallmoduleisalsoconsideredtobethefollowing:

EasiertounderstandanduseSimplertotestandmaintainPerfecttosharewiththebrowser

Havingsmallerandmorefocusedmodulesempowerseveryonetoshareorreuseeventhesmallestpieceofcode;it'stheDon'tRepeatYourself(DRY)principleappliedatawholenewlevel.

SmallsurfaceareaInadditiontobeingsmallinsizeandscope,Node.jsmodulesusuallyalsohavethecharacteristicofexposingonlyaminimalsetoffunctionality.ThemainadvantagehereisanincreasedusabilityoftheAPI,whichmeansthattheAPIbecomesclearertouseandislessexposedtoerroneoususage.Mostofthetime,infact,theuserofacomponentisinterestedonlyinaverylimitedandfocusedsetoffeatures,withouttheneedtoextenditsfunctionalityortapintomoreadvancedaspects.

InNode.js,averycommonpatternfordefiningmodulesistoexposeonlyonepieceoffunctionality,suchasafunctionoraconstructor,whilelettingmoreadvancedaspectsorsecondaryfeaturesbecomepropertiesoftheexportedfunctionorconstructor.Thishelpstheusertoidentifywhatisimportantandwhatissecondary.Itisnotraretofindmodulesthatexposeonlyonefunctionandnothingelse,forthesimplefactthatitprovidesasingle,unmistakablyclearentrypoint.

AnothercharacteristicofmanyNode.jsmodulesisthefactthattheyarecreatedtobeusedratherthanextended.Lockingdowntheinternalsofamodulebyforbiddinganypossibilityofanextensionmightsoundinflexible,butitactuallyhastheadvantageofreducingtheusecases,simplifyingitsimplementation,facilitatingitsmaintenance,andincreasingitsusability.

Simplicityandpragmatism

Page 35: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

HaveyoueverheardoftheKeepItSimple,Stupid(KISS)principle?Orthefamousquote:

"Simplicityistheultimatesophistication."

--LeonardodaVinci

RichardP.Gabriel,aprominentcomputerscientistcoinedthetermworseisbettertodescribethemodel,wherebylessandsimplerfunctionalityisagooddesignchoiceforsoftware.Inhisessay,Theriseofworseisbetter,hesays:

"Thedesignmustbesimple,bothinimplementationandinterface.Itismoreimportantfortheimplementationtobesimplethantheinterface.Simplicityisthe

mostimportantconsiderationinadesign."

Designingasimple,asopposedtoaperfect,feature-fullsoftware,isagoodpracticeforseveralreasons:ittakeslessefforttoimplement,allowsfastershippingwithlessresources,iseasiertoadapt,andiseasiertomaintainandunderstand.Thesefactorsfosterthecommunitycontributionsandallowthesoftwareitselftogrowandimprove.

InNode.js,thisprincipleisalsoenabledbyJavaScript,whichisaverypragmaticlanguage.It'snotrare,infact,toseesimplefunctions,closures,andobjectliteralsreplacingcomplexclasshierarchies.Pureobject-orienteddesignsoftentrytoreplicatetherealworldusingthemathematicaltermsofacomputersystemwithoutconsideringtheimperfectionandthecomplexityoftherealworlditself.Thetruthisthatoursoftwareisalwaysanapproximationoftherealityandwewouldprobablyhavemoresuccessintryingtogetsomethingworkingsoonerandwithreasonablecomplexity,insteadoftryingtocreateanear-perfectsoftwarewithahugeeffortandtonsofcodetomaintain.

Throughoutthisbook,wewillseethisprincipleinactionmanytimes.Forexample,aconsiderablenumberoftraditionaldesignpatterns,suchasSingletonorDecoratorcanhaveatrivial,evenifsometimesnotfoolproofimplementationandwewillseehowanuncomplicated,practicalapproachmostofthetimeispreferredtoapure,flawlessdesign.

Page 36: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThereactorpatternInthissection,wewillanalyzethereactorpattern,whichistheheartoftheNode.jsasynchronousnature.Wewillgothroughthemainconceptsbehindthepattern,suchasthesingle-threadedarchitectureandthenon-blockingI/O,andwewillseehowthiscreatesthefoundationfortheentireNode.jsplatform.

I/OisslowI/Oisdefinitelytheslowestamongthefundamentaloperationsofacomputer.AccessingtheRAMisintheorderofnanoseconds(10e-9seconds),whileaccessingdataonthediskorthenetworkisintheorderofmilliseconds(10e-3seconds).Forthebandwidth,itisthesamestory;RAMhasatransferrateconsistentlyintheorderofGB/s,whilediskandnetworkvariesfromMB/sto,optimistically,GB/s.I/OisusuallynotexpensiveintermsofCPU,butitaddsadelaybetweenthemomenttherequestissentandthemomenttheoperationcompletes.Ontopofthat,wealsohavetoconsiderthehumanfactor;often,theinputofanapplicationcomesfromarealperson,forexample,theclickofabuttonoramessagesentinareal-timechatapplication,sothespeedandfrequencyofI/Odon'tdependonlyontechnicalaspects,andtheycanbemanyordersofmagnitudeslowerthanthediskornetwork.

BlockingI/OIntraditionalblockingI/Oprogramming,thefunctioncallcorrespondingtoanI/Orequestwillblocktheexecutionofthethreaduntiltheoperationcompletes.Thiscangofromafewmilliseconds,incaseofadiskaccess,tominutesorevenmore,incasethedataisgeneratedfromuseractions,suchaspressingakey.Thefollowingpseudocodeshowsatypicalblockingreadperformedagainstasocket:

//blocksthethreaduntilthedataisavailable

data=socket.read();

//dataisavailable

print(data);

ItistrivialtonoticethatawebserverthatisimplementedusingblockingI/Owillnotbeabletohandlemultipleconnectionsinthesamethread;eachI/Ooperationonasocketwillblocktheprocessingofanyotherconnection.Forthisreason,thetraditionalapproachtohandleconcurrencyinwebserversistokickoffathreadoraprocess(ortoreuseonetakenfromapool)foreachconcurrentconnectionthatneedstobehandled.Thisway,whenathreadblocksforanI/Ooperationitwillnotimpacttheavailabilityoftheotherrequests,becausetheyarehandledinseparate

Page 37: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

threads.

Thefollowingimageillustratesthisscenario:

Theprecedingimagelaysemphasisontheamountoftimeeachthreadisidle,waitingfornewdatatobereceivedfromtheassociatedconnection.Now,ifwealsoconsiderthatanytypeofI/Ocanpossiblyblockarequest,forexample,whileinteractingwithdatabasesorwiththefilesystem,wesoonrealizehowmanytimesathreadhastoblockinordertowaitfortheresultofanI/Ooperation.Unfortunately,athreadisnotcheapintermsofsystemresources,itconsumesmemoryandcausescontextswitches,sohavingalongrunningthreadforeachconnectionandnotusingitformostofthetime,isnotthebestcompromiseintermsofefficiency.

Non-blockingI/OInadditiontoblockingI/O,mostmodernoperatingsystemssupportanothermechanismtoaccessresources,callednon-blockingI/O.Inthisoperatingmode,thesystemcallalwaysreturnsimmediatelywithoutwaitingforthedatatobereadorwritten.Ifnoresultsareavailableatthemomentofthecall,thefunctionwillsimplyreturnapredefinedconstant,indicatingthatthereisnodataavailabletoreturnatthatmoment.

Forexample,inUnixoperatingsystems,thefcntl()functionisusedtomanipulateanexistingfiledescriptortochangeitsoperatingmodetonon-blocking(withtheO_NONBLOCKflag).Oncetheresourceisinnon-blockingmode,anyreadoperationwillfailwithareturncode,EAGAIN,incasetheresourcedoesn'thaveanydatareadytoberead.

Themostbasicpatternforaccessingthiskindofnon-blockingI/Oistoactivelypolltheresourcewithinaloopuntilsomeactualdataisreturned;thisiscalledbusy-waiting.Thefollowingpseudocodeshowsyouhowit'spossibletoreadfrommultiple

Page 38: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

resourcesusingnon-blockingI/Oandapollingloop:

resources=[socketA,socketB,pipeA];

while(!resources.isEmpty()){

for(i=0;i<resources.length;i++){

resource=resources[i];

//trytoread

vardata=resource.read();

if(data===NO_DATA_AVAILABLE)

//thereisnodatatoreadatthemoment

continue;

if(data===RESOURCE_CLOSED)

//theresourcewasclosed,removeitfromthelist

resources.remove(i);

else

//somedatawasreceived,processit

consumeData(data);

}

}

Youcanseethat,withthissimpletechnique,itisalreadypossibletohandledifferentresourcesinthesamethread,butit'sstillnotefficient.Infact,intheprecedingexample,theloopwillconsumepreciousCPUonlyforiteratingoverresourcesthatareunavailablemostofthetime.PollingalgorithmsusuallyresultinahugeamountofwastedCPUtime.

EventdemultiplexingBusy-waitingisdefinitelynotanidealtechniqueforprocessingnon-blockingresources,butluckily,mostmodernoperatingsystemsprovideanativemechanismtohandleconcurrent,non-blockingresourcesinanefficientway;thismechanismiscalledsynchronouseventdemultiplexeroreventnotificationinterface.ThiscomponentcollectsandqueuesI/Oeventsthatcomefromasetofwatchedresources,andblockuntilneweventsareavailabletoprocess.Thefollowingisthepseudocodeofanalgorithmthatusesagenericsynchronouseventdemultiplexertoreadfromtwodifferentresources:

socketA,pipeB;

watchedList.add(socketA,FOR_READ);//[1]

watchedList.add(pipeB,FOR_READ);

while(events=demultiplexer.watch(watchedList)){//[2]

//eventloop

foreach(eventinevents){//[3]

//Thisreadwillneverblockandwillalwaysreturndata

data=event.resource.read();

if(data===RESOURCE_CLOSED)

//theresourcewasclosed,removeitfromthewatched

list

Page 39: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

demultiplexer.unwatch(event.resource);

else

//someactualdatawasreceived,processit

consumeData(data);

}

}

Thesearetheimportantstepsoftheprecedingpseudocode:

1. Theresourcesareaddedtoadatastructure,associatingeachoneofthemwithaspecificoperation,inourexamplearead.

2. Theeventnotifierissetupwiththegroupofresourcestobewatched.Thiscallissynchronousandblocksuntilanyofthewatchedresourcesisreadyforaread.Whenthisoccurs,theeventdemultiplexerreturnsfromthecallandanewsetofeventsisavailabletobeprocessed.

3. Eacheventreturnedbytheeventdemultiplexerisprocessed.Atthispoint,theresourceassociatedwitheacheventisguaranteedtobereadytoreadandtonotblockduringtheoperation.Whenalltheeventsareprocessed,theflowwillblockagainontheeventdemultiplexeruntilneweventsareagainavailabletobeprocessed.Thisiscalledtheeventloop.

It'sinterestingtoseethatwiththispattern,wecannowhandleseveralI/Ooperationsinsideasinglethread,withoutusingabusy-waitingtechnique.Thefollowingimageshowsushowawebserverwouldbeabletohandlemultipleconnectionsusingasynchronouseventdemultiplexerandasinglethread:

Thepreviousimagehelpsusunderstandhowconcurrencyworksinasingle-threadedapplicationusingasynchronouseventdemultiplexerandnon-blockingI/O.WecanseethatusingonlyonethreaddoesnotimpairourabilitytorunmultipleI/Oboundtasksconcurrently.Thetasksarespreadovertime,insteadofbeingspreadacrossmultiplethreads.Thishastheclearadvantageofminimizingthetotalidletimeofthethread,asclearlyshownintheimage.Thisisnottheonlyreasonforchoosingthismodel.Tohaveonlyasinglethread,infact,alsohasabeneficialimpactonthewayprogrammersapproachconcurrencyingeneral.Throughoutthebook,wewillsee

Page 40: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

howtheabsenceofin-processraceconditionsandmultiplethreadstosynchronize,allowsustousemuchsimplerconcurrencystrategies.

Inthenextchapter,wewillhavetheopportunitytotalkmoreabouttheconcurrencymodelofNode.js.

ThereactorpatternWecannowintroducethereactorpattern,whichisaspecializationofthealgorithmpresentedintheprevioussection.Themainideabehinditistohaveahandler(whichinNode.jsisrepresentedbyacallbackfunction)associatedwitheachI/Ooperation,whichwillbeinvokedassoonasaneventisproducedandprocessedbytheeventloop.Thestructureofthereactorpatternisshowninthefollowingimage:

Thisiswhathappensinanapplicationusingthereactorpattern:

1. TheapplicationgeneratesanewI/OoperationbysubmittingarequesttotheEventDemultiplexer.Theapplicationalsospecifiesahandler,whichwillbeinvokedwhentheoperationcompletes.SubmittinganewrequesttotheEvent

Page 41: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Demultiplexerisanon-blockingcallanditimmediatelyreturnsthecontrolbacktotheapplication.

2. WhenasetofI/Ooperationscompletes,theEventDemultiplexerpushestheneweventsintotheEventQueue.

3. Atthispoint,theEventLoopiteratesovertheitemsoftheEventQueue.4. Foreachevent,theassociatedhandlerisinvoked.5. Thehandler,whichispartoftheapplicationcode,willgivebackthecontroltothe

EventLoopwhenitsexecutioncompletes(5a).However,newasynchronousoperationsmightberequestedduringtheexecutionofthehandler(5b),causingnewoperationstobeinsertedintheEventDemultiplexer(1),beforethecontrolisgivenbacktotheEventLoop.

6. WhenalltheitemsintheEventQueueareprocessed,theloopwillblockagainontheEventDemultiplexerwhichwillthentriggeranothercycle.

Theasynchronousbehaviorisnowclear:theapplicationexpressestheinteresttoaccessaresourceatonepointintime(withoutblocking)andprovidesahandler,whichwillthenbeinvokedatanotherpointintimewhentheoperationcompletes.

NoteANode.jsapplicationwillexitautomaticallywhentherearenomorependingoperationsintheEventDemultiplexer,andnomoreeventstobeprocessedinsidetheEventQueue.

WecannowdefinethepatternattheheartofNode.js.

NotePattern(reactor):handlesI/Obyblockinguntilneweventsareavailablefromasetofobservedresources,andthenreactingbydispatchingeacheventtoanassociatedhandler.

Thenon-blockingI/OengineofNode.js–libuvEachoperatingsystemhasitsowninterfacefortheEventDemultiplexer:epollonLinux,kqueueonMacOSX,andI/OCompletionPortAPI(IOCP)onWindows.Besidesthat,eachI/Ooperationcanbehavequitedifferentlydependingonthetypeoftheresource,evenwithinthesameOS.Forexample,inUnix,regularfilesystemfilesdonotsupportnon-blockingoperations,so,inordertosimulateanon-blocking

Page 42: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

behavior,itisnecessarytouseaseparatethreadoutsidetheEventLoop.Alltheseinconsistenciesacrossandwithinthedifferentoperatingsystemsrequiredahigher-levelabstractiontobebuiltfortheEventDemultiplexer.ThisisexactlywhytheNode.jscoreteamcreatedaClibrarycalledlibuv,withtheobjectivetomakeNode.jscompatiblewithallthemajorplatformsandnormalizethenon-blockingbehaviorofthedifferenttypesofresource;libuvtodayrepresentsthelow-levelI/OengineofNode.js.

Besidesabstractingtheunderlyingsystemcalls,libuvalsoimplementsthereactorpattern,thusprovidinganAPIforcreatingeventloops,managingtheeventqueue,runningasynchronousI/Ooperations,andqueuingothertypesoftasks.

NoteAgreatresourcetolearnmoreaboutlibuvisthefreeonlinebookcreatedbyNikhilMarathe,whichisavailableathttp://nikhilm.github.io/uvbook/.

TherecipeforNode.jsThereactorpatternandlibuvarethebasicbuildingblocksofNode.js,butweneedthefollowingthreeothercomponentstobuildthefullplatform:

Asetofbindingsresponsibleforwrappingandexposinglibuvandotherlow-levelfunctionalitytoJavaScript.V8,theJavaScriptengineoriginallydevelopedbyGooglefortheChromebrowser.ThisisoneofthereasonswhyNode.jsissofastandefficient.V8isacclaimedforitsrevolutionarydesign,itsspeed,andforitsefficientmemorymanagement.AcoreJavaScriptlibrary(callednode-core)thatimplementsthehigh-levelNode.jsAPI.

Finally,thisistherecipeofNode.js,andthefollowingimagerepresentsitsfinalarchitecture:

Page 43: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in
Page 44: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThecallbackpatternCallbacksarethematerializationofthehandlersofthereactorpatternandtheyareliterallyoneofthoseimprintsthatgiveNode.jsitsdistinctiveprogrammingstyle.Callbacksarefunctionsthatareinvokedtopropagatetheresultofanoperationandthisisexactlywhatweneedwhendealingwithasynchronousoperations.Theypracticallyreplacetheuseofthereturninstructionthat,asweknow,alwaysexecutessynchronously.JavaScriptisagreatlanguagetorepresentcallbacks,becauseasweknow,functionsarefirstclassobjectsandcanbeeasilyassignedtovariables,passedasarguments,returnedfromanotherfunctioninvocation,orstoredintodatastructures.Also,closuresareanidealconstructforimplementingcallbacks.Withclosures,wecaninfactreferencetheenvironmentinwhichafunctionwascreated,practically,wecanalwaysmaintainthecontextinwhichtheasynchronousoperationwasrequested,nomatterwhenorwhereitscallbackisinvoked.

NoteIfyouneedtorefreshyourknowledgeaboutclosures,youcanrefertothearticleontheMozillaDeveloperNetworkathttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures.

Inthissection,wewillanalyzethisparticularstyleofprogrammingmadeofcallbacksinsteadofthereturninstructions.

Thecontinuation-passingstyleInJavaScript,acallbackisafunctionthatispassedasanargumenttoanotherfunctionandisinvokedwiththeresultwhentheoperationcompletes.Infunctionalprogramming,thiswayofpropagatingtheresultiscalledcontinuation-passingstyle,forbrevity,CPS.Itisageneralconcept,anditisnotalwaysassociatedwithasynchronousoperations.Infact,itsimplyindicatesthataresultispropagatedbypassingittoanotherfunction(thecallback),insteadofdirectlyreturningittothecaller.

Synchronouscontinuation-passingstyleToclarifytheconcept,let'stakealookatasimplesynchronousfunction:

functionadd(a,b){

returna+b;

Page 45: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

Thereisnothingspecialhere;theresultispassedbacktothecallerusingthereturninstruction;thisisalsocalleddirectstyle,anditrepresentsthemostcommonwayofreturningaresultinsynchronousprogramming.Theequivalentcontinuation-passingstyleoftheprecedingfunctionwouldbeasfollows:

functionadd(a,b,callback){

callback(a+b);

}

Theadd()functionisasynchronousCPSfunction,whichmeansthatitwillreturnavalueonlywhenthecallbackcompletesitsexecution.Thefollowingcodedemonstratesthisstatement:

console.log('before');

add(1,2,function(result){

console.log('Result:'+result);

});

console.log('after');

Sinceadd()issynchronous,thepreviouscodewilltriviallyprintthefollowing:

before

Result:3

after

Asynchronouscontinuation-passingstyleNow,let'sconsiderthecasewheretheadd()functionisasynchronous,whichisasfollows:

functionaddAsync(a,b,callback){

setTimeout(function(){

callback(a+b);

},100);

}

Inthepreviouscode,wesimplyusesetTimeout()tosimulateanasynchronousinvocationofthecallback.Now,let'strytousethisfunctionandseehowtheorderoftheoperationschanges:

console.log('before');

addAsync(1,2,function(result){

console.log('Result:'+result);

Page 46: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

});

console.log('after');

Theprecedingcodewillprintthefollowing:

before

after

Result:3

SincesetTimeout()triggersanasynchronousoperation,itwillnotwaitanymoreforthecallbacktobeexecuted,butinstead,itreturnsimmediatelygivingthecontrolbacktoaddAsync(),andthenbacktoitscaller.ThispropertyinNode.jsiscrucial,asitallowsthestacktounwind,andthecontroltobegivenbacktotheeventloopassoonasanasynchronousrequestissent,thusallowinganeweventfromthequeuetobeprocessed.

Thefollowingimageshowshowthisworks:

Whentheasynchronousoperationcompletes,theexecutionisthenresumedstartingfromthecallbackprovidedtotheasynchronousfunctionthatcausedtheunwinding.TheexecutionwillstartfromtheEventLoop,soitwillhaveafreshstack.ThisiswhereJavaScriptcomesinreallyhandy,infact,thankstoclosuresitistrivialtomaintainthecontextofthecalleroftheasynchronousfunction,evenifthecallbackisinvokedatadifferentpointintimeandfromadifferentlocation.

Page 47: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

NoteAsynchronousfunctionblocksuntilitcompletesitsoperations.Anasynchronousfunctionreturnsimmediatelyandtheresultispassedtoahandler(inourcase,acallback)atalatercycleoftheeventloop.

Noncontinuation-passingstylecallbacksThereareseveralcircumstancesinwhichthepresenceofacallbackargumentmightmakeyouthinkthatafunctionisasynchronousorisusingacontinuation-passingstyle;that'snotalwaystrue,let'stake,forexample,themap()methodoftheArrayobject:

varresult=[1,5,7].map(function(element){

returnelement–1;

});

Clearly,thecallbackisjustusedtoiterateovertheelementsofthearray,andnottopasstheresultoftheoperation.Infact,theresultisreturnedsynchronouslyusingadirectstyle.TheintentofacallbackisusuallyclearlystatedinthedocumentationoftheAPI.

Synchronousorasynchronous?Wehaveseenhowtheorderoftheinstructionschangesradicallydependingonthenatureofafunction-synchronousorasynchronous.Thishasstrongrepercussionsontheflowoftheentireapplication,bothincorrectnessandefficiency.Thefollowingisananalysisofthesetwoparadigmsandtheirpitfalls.Ingeneral,whatmustbeavoided,iscreatinginconsistencyandconfusionaroundthenatureofanAPI,asdoingsocanleadtoasetofproblemswhichmightbeveryhardtodetectandreproduce.Todriveouranalysis,wewilltakeasexamplethecaseofaninconsistentlyasynchronousfunction.

AnunpredictablefunctionOneofthemostdangeroussituationsistohaveanAPIthatbehavessynchronouslyundercertainconditionsandasynchronouslyunderothers.Let'stakethefollowingcodeasanexample:

varfs=require('fs');

varcache={};

functioninconsistentRead(filename,callback){

Page 48: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

if(cache[filename]){

//invokedsynchronously

callback(cache[filename]);

}else{

//asynchronousfunction

fs.readFile(filename,'utf8',function(err,data){

cache[filename]=data;

callback(data);

});

}

}

Theprecedingfunctionusesthecachevariabletostoretheresultsofdifferentfilereadoperations.Pleasebearinmindthatthisisjustanexample,itdoesnothaveerrormanagement,andthecachinglogicitselfissuboptimal.Besidesthis,theprecedingfunctionisdangerousbecauseitbehavesasynchronouslyuntilthecacheisnotset—whichisuntilthefs.readFile()functionreturnsitsresults—butitwillalsobesynchronousforallthesubsequentrequestsforafilealreadyinthecache—triggeringanimmediateinvocationofthecallback.

UnleashingZalgoNow,let'sseehowtheuseofanunpredictablefunction,suchastheonethatwedefinedpreviously,caneasilybreakanapplication.Considerthefollowingcode:

functioncreateFileReader(filename){

varlisteners=[];

inconsistentRead(filename,function(value){

listeners.forEach(function(listener){

listener(value);

});

});

return{

onDataReady:function(listener){

listeners.push(listener);

}

};

}

Whentheprecedingfunctionisinvoked,itcreatesanewobjectthatactsasanotifier,allowingtosetmultiplelistenersforafilereadoperation.Allthelistenerswillbeinvokedatoncewhenthereadoperationcompletesandthedataisavailable.TheprecedingfunctionusesourinconsistentRead()functiontoimplementthisfunctionality.Let'snowtrytousethecreateFileReader()function:

varreader1=createFileReader('data.txt');

reader1.onDataReady(function(data){

Page 49: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

console.log('Firstcalldata:'+data);

//...sometimelaterwetrytoreadagainfrom

//thesamefile

varreader2=createFileReader('data.txt');

reader2.onDataReady(function(data){

console.log('Secondcalldata:'+data);

});

});

Theprecedingcodewillprintthefollowingoutput:

Firstcalldata:somedata

Asyoucansee,thecallbackofthesecondoperationisneverinvoked.Let'sseewhy:

Duringthecreationofreader1,ourinconsistentRead()functionbehavesasynchronously,becausethereisnocachedresultavailable.Therefore,wehaveallthetimetoregisterourlistener,asitwillbeinvokedlaterinanothercycleoftheeventloop,whenthereadoperationcompletes.Then,reader2iscreatedinacycleoftheeventloopinwhichthecachefortherequestedfilealreadyexists.Inthiscase,theinnercalltoinconsistentRead()willbesynchronous.So,itscallbackwillbeinvokedimmediately,whichmeansthatalsoallthelistenersofreader2willbeinvokedsynchronously.However,weareregisteringthelistenersafterthecreationofreader2,sotheywillneverbeinvoked.

ThecallbackbehaviorofourinconsistentRead()functionisreallyunpredictable,asitdependsonmanyfactors,suchasthefrequencyofitsinvocation,thefilenamepassedasargument,andtheamountoftimetakentoloadthefile.

Thebugthatwe'vejustseenmightbeextremelycomplicatedtoidentifyandreproduceinarealapplication.Imaginetouseasimilarfunctioninawebserver,wheretherecanbemultipleconcurrentrequests;imagineseeingsomeofthoserequestshanging,withoutanyapparentreasonandwithoutanyerrorbeinglogged.Thisdefinitelyfallsunderthecategoryofnastydefects.

IsaacZ.Schlueter,creatorofnpmandformerNode.jsprojectlead,inoneofhisblogpostscomparedtheuseofthistypeofunpredictablefunctionstounleashingZalgo.Ifyou'renotfamiliarwithZalgo,youareinvitedtofindoutwhatitis.

NoteYoucanfindtheoriginalIsaacZ.Schlueter'spostat

Page 50: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony.

UsingsynchronousAPIsThelessontolearnfromtheunleashingZalgoexampleisthatitisimperativeforanAPItoclearlydefineitsnature,eithersynchronousorasynchronous.

OnesuitablefixforourinconsistentRead()function,istomakeittotallysynchronous.ThisispossiblebecauseNode.jsprovidesasetofsynchronousdirectstyleAPIsformostofthebasicI/Ooperations.Forexample,wecanusethefs.readFileSync()functioninplaceofitsasynchronouscounterpart.Thecodewouldnowbeasfollows:

varfs=require('fs');

varcache={};

functionconsistentReadSync(filename){

if(cache[filename]){

returncache[filename];

}else{

cache[filename]=fs.readFileSync(filename,'utf8');

returncache[filename];

}

}

Wecanseethattheentirefunctionwasalsoconvertedtoadirectstyle.Thereisnoreasonforthefunctiontohaveacontinuation-passingstyleifitissynchronous.Infact,wecanstatethatitisalwaysagoodpracticetoimplementasynchronousAPIusingadirectstyle;thiswilleliminateanyconfusionarounditsnatureandwillalsobemoreefficientfromaperformanceperspective.

TipPattern:preferthedirectstyleforpurelysynchronousfunctions.

PleasebearinmindthatchanginganAPIfromCPStoadirectstyle,orfromasynchronoustosynchronous,orviceversamightalsorequireachangetothestyleofallthecodeusingit.Forexample,inourcase,wewillhavetototallychangetheinterfaceofourcreateFileReader()APIandadaptittoworkalwayssynchronously.

Also,usingasynchronousAPIinsteadofanasynchronousonehassomecaveats:

AsynchronousAPImightnotbealwaysavailablefortheneededfunctionality.

Page 51: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AsynchronousAPIwillblocktheeventloopandputtheconcurrentrequestsonhold.ItpracticallybreakstheNode.jsconcurrency,slowingdownthewholeapplication.Wewillseelaterinthebookwhatthisreallymeansforourapplications.

InourconsistentReadSync()function,theriskofblockingtheeventloopispartiallymitigated,becausethesynchronousI/OAPIisinvokedonlyoncepereachfilename,whilethecachedvaluewillbeusedforallthesubsequentinvocations.Ifwehavealimitednumberofstaticfiles,thenusingconsistentReadSync()won'thaveabigeffectonoureventloop.Thingscanchangequicklyifwehavetoreadmanyfilesandonlyonce.UsingsynchronousI/OinNode.jsisstronglydiscouragedinmanycircumstances;however,insomesituations,thismightbetheeasiestandmostefficientsolution.Alwaysevaluateyourspecificusecaseinordertochoosetherightalternative.

TipUseblockingAPIonlywhentheydon'taffecttheabilityoftheapplicationtoserveconcurrentrequests.

DeferredexecutionAnotheralternativeforfixingourinconsistentRead()functionistomakeitpurelyasynchronous.Thetrickhereistoschedulethesynchronouscallbackinvocationtobeexecuted"inthefuture"insteadofbeingrunimmediatelyinthesameeventloopcycle.InNode.js,thisispossibleusingprocess.nextTick(),whichdeferstheexecutionofafunctionuntilthenextpassoftheeventloop.Itsfunctioningisverysimple;ittakesacallbackasanargumentandpushesitonthetopoftheeventqueue,infrontofanypendingI/Oevent,andreturnsimmediately.Thecallbackwillthenbeinvokedassoonastheeventlooprunsagain.

Let'sapplythistechniquetofixourinconsistentRead()functionasfollows:

varfs=require('fs');

varcache={};

functionconsistentReadAsync(filename,callback){

if(cache[filename]){

process.nextTick(function(){

callback(cache[filename]);

});

}else{

//asynchronousfunction

fs.readFile(filename,'utf8',function(err,data){

Page 52: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

cache[filename]=data;

callback(data);

});

}

}

Now,ourfunctionisguaranteedtoinvokeitscallbackasynchronously,underanycircumstances.

AnotherAPIfordeferringtheexecutionofcodeissetImmediate(),which—despitethename—mightactuallybeslowerthanprocess.nextTick().Whiletheirpurposeisverysimilar,theirsemanticisquitedifferent.Callbacksdeferredwithprocess.nextTick()runbeforeanyotherI/Oeventisfired,whilewithsetImmediate(),theexecutionisqueuedbehindanyI/Oeventthatisalreadyinthequeue.Sinceprocess.nextTick()runsbeforeanyalreadyscheduledI/O,itmightcauseI/Ostarvationundercertaincircumstances,forexample,arecursiveinvocation;thiscanneverhappenwithsetImmediate().WewilllearntoappreciatethedifferencebetweenthesetwoAPIswhenweanalyzetheuseofdeferredinvocationforrunningsynchronousCPU-boundtaskslaterinthebook.

TipPattern:weguaranteethatacallbackisinvokedasynchronouslybydeferringitsexecutionusingprocess.nextTick().

Node.jscallbackconventionsInNode.js,continuation-passingstyleAPIsandcallbacksfollowasetofspecificconventions.TheseconventionsapplytotheNode.jscoreAPIbuttheyarealsofollowedvirtuallybyeveryuserlandmoduleandapplication.So,it'sveryimportantthatweunderstandthemandmakesurethatwecomplywheneverweneedtodesignanasynchronousAPI.

CallbackscomelastInNode.js,ifafunctionacceptsininputacallback,thishastobepassedasthelastargument.Let'stakethefollowingNode.jscoreAPIasanexample:

fs.readFile(filename,[options],callback)

Asyoucanseefromthesignatureoftheprecedingfunction,thecallbackisalways

Page 53: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

putinlastposition,eveninthepresenceofoptionalarguments.Themotivationforthisconventionisthatthefunctioncallismorereadableincasethecallbackisdefinedinplace.

ErrorcomesfirstInCPS,errorsarepropagatedasanyothertypeofresult,whichmeansusingthecallback.InNode.js,anyerrorproducedbyaCPSfunctionisalwayspassedasthefirstargumentofthecallback,andanyactualresultispassedstartingfromthesecondargument.Iftheoperationsucceedswithouterrors,thefirstargumentwillbenullorundefined.Thefollowingcodeshowsyouhowtodefineacallbackcomplyingwiththisconvention:

fs.readFile('foo.txt','utf8',function(err,data){

if(err)

handleError(err);

else

processData(data);

});

Itisagoodpracticetoalwayscheckforthepresenceofanerror,asnotdoingsowillmakeitharderforustodebugourcodeanddiscoverthepossiblepointsoffailures.AnotherimportantconventiontotakeintoaccountisthattheerrormustalwaysbeoftypeError.Thismeansthatsimplestringsornumbersshouldneverbepassedaserrorobjects.

PropagatingerrorsPropagatingerrorsinsynchronous,directstylefunctionsisdonewiththewell-knownthrowcommand,whichcausestheerrortojumpupinthecallstackuntilit'scaught.

InasynchronousCPShowever,propererrorpropagationisdonebysimplypassingtheerrortothenextcallbackintheCPSchain.Thetypicalpatternlooksasfollows:

varfs=require('fs');

functionreadJSON(filename,callback){

fs.readFile(filename,'utf8',function(err,data){

varparsed;

if(err)

//propagatetheerrorandexitthecurrentfunction

returncallback(err);

try{

//parsethefilecontents

parsed=JSON.parse(data);

}catch(err){

Page 54: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//catchparsingerrors

returncallback(err);

}

//noerrors,propagatejustthedata

callback(null,parsed);

});

};

Thedetailtonoticeinthepreviouscodeishowthecallbackisinvokedwhenwewanttopassavalidresultandwhenwewanttopropagateanerror.

UncaughtexceptionsYoumighthaveseenfromthereadJSON()functiondefinedpreviouslythatinordertoavoidanyexceptiontobethrownintothefs.readFile()callback,weputatry-catchblockaroundJSON.parse().Throwinginsideanasynchronouscallback,infact,willcausetheexceptiontojumpuptotheeventloopandneverbepropagatedtothenextcallback.

InNode.js,thisisanunrecoverablestateandtheapplicationwillsimplyshutdownprintingtheerrortothestderrinterface.Todemonstratethis,let'strytoremovethetry-catchblockfromthereadJSON()functiondefinedpreviously:

varfs=require('fs');

functionreadJSONThrows(filename,callback){

fs.readFile(filename,'utf8',function(err,data){

if(err)

returncallback(err);

//noerrors,propagatejustthedata

callback(null,JSON.parse(data));

});

};

Now,inthefunctionwejustdefined,thereisnowayofcatchinganeventualexceptioncomingfromJSON.parse().Let'stry,forexample,toparseaninvalidJSONfilewiththefollowingcode:

readJSONThrows('nonJSON.txt',function(err){

console.log(err);

});

Thiswouldresultintheapplicationbeingabruptlyterminatedandthefollowingexceptionbeingprintedontheconsole:

SyntaxError:Unexpectedtokend

atObject.parse(native)

Page 55: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

at[...]/06_uncaught_exceptions/uncaught.js:7:25

atfs.js:266:14

atObject.oncomplete(fs.js:107:15)

Now,ifwelookattheprecedingstacktrace,wewillseethatitstartssomewherefromthefs.jsmodule,practicallyfromthepointatwhichthenativeAPIhascompletedreadingandreturneditsresultbacktothefs.readFile()function,viatheeventloop.Thisclearlyshowsusthattheexceptiontraveledfromourcallbackintothestackthatwesaw,andthenstraightintotheeventloop,whereit'sfinallycaughtandthrownintheconsole.

ThisalsomeansthatwrappingtheinvocationofreadJSONThrows()withatry-catchblockwillnotwork,becausethestackinwhichtheblockoperatesisdifferentfromtheoneinwhichourcallbackisinvoked.Thefollowingcodeshowstheanti-patternthatwejustdescribed:

try{

readJSONThrows('nonJSON.txt',function(err,result){

[...]

});

}catch(err){

console.log('ThiswillnotcatchtheJSONparsing

exception');

}

TheprecedingcatchstatementwillneverreceivetheJSONparsingexception,asitwilltravelbacktothestackinwhichtheexceptionwasthrown,andwejustsawthatthestackendsupintheeventloopandnotwiththefunctionthattriggerstheasynchronousoperation.

Wealreadysaidthattheapplicationisabortedthemomentanexceptionreachestheeventloop;however,westillhavealastchancetoperformsomecleanuporloggingbeforetheapplicationterminates.Infact,whenthishappens,Node.jsemitsaspecialeventcalleduncaughtExceptionjustbeforeexitingtheprocess.Thefollowingcodeshowsasampleusecase:

process.on('uncaughtException',function(err){

console.error('Thiswillcatchatlastthe'+

'JSONparsingexception:'+err.message);

//withoutthis,theapplicationwouldcontinue

process.exit(1);

});

It'simportanttounderstandthatanuncaughtexceptionleavestheapplicationinastatethatisnotguaranteedtobeconsistent,whichcanleadtounforeseeableproblems.Forexample,theremightstillhaveincompleteI/Orequestsrunning,or

Page 56: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

closuresmighthavebecomeinconsistent.That'swhyitisalwaysadvised,especiallyinproduction,toexitanywayfromtheapplicationafteranuncaughtexceptionisreceived.

Page 57: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThemodulesystemanditspatternsModulesarethebricksforstructuringnon-trivialapplications,butalsothemainmechanismtoenforceinformationhidingbykeepingprivateallthefunctionsandvariablesthatarenotexplicitlymarkedtobeexported.Inthissection,wewillintroducetheNode.jsmodulesystemanditsmostcommonusagepatterns.

TherevealingmodulepatternOneofthemajorproblemswithJavaScriptistheabsenceofnamespacing.Programsrunintheglobalscopepollutingitwithdatathatcomesfrombothinternalapplicationcodeanddependencies.Apopulartechniquetosolvethisproblemiscalledrevealingmodulepatternanditlookslikethefollowing:

varmodule=(function(){

varprivateFoo=function(){...};

varprivateVar=[];

varexport={

publicFoo:function(){...},

publicBar:function(){...}

}

returnexport;

})();

Thispatternleveragesaself-invokingfunctiontocreateaprivatescope,exportingonlythepartsthataremeanttobepublic.Intheprecedingcode,themodulevariablecontainsonlytheexportedAPI,whiletherestofthemodulecontentispracticallyinaccessiblefromoutside.Aswewillseeinamoment,theideabehindthispatternisusedasabasefortheNode.jsmodulesystem.

Node.jsmodulesexplainedCommonJSisagroupwiththeaimtostandardizetheJavaScriptecosystem,andoneoftheirmostpopularproposalsiscalledCommonJSmodules.Node.jsbuiltitsmodulesystemontopofthisspecification,withtheadditionofsomecustomextensions.Todescribehowitworks,wecanmakeananalogywiththerevealingmodulepattern,whereeachmodulerunsinaprivatescope,sothateveryvariablethatisdefinedlocallydoesnotpollutetheglobalnamespace.

Ahomemademoduleloader

Page 58: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Toexplainhowthisworks,let'sbuildasimilarsystemfromscratch.Thecodethatfollowscreatesafunctionthatmimicsasubsetofthefunctionalityoftheoriginalrequire()functionofNode.js.

Let'sstartbycreatingafunctionthatloadsthecontentofamodule,wrapsitintoaprivatescope,andevaluatesit:

functionloadModule(filename,module,require){

varwrappedSrc=

'(function(module,exports,require){'+

fs.readFileSync(filename,'utf8')+

'})(module,module.exports,require);';

eval(wrappedSrc);

}

Thesourcecodeofamoduleisessentiallywrappedintoafunction,asitwasfortherevealingmodulepattern.Thedifferencehereisthatwepassalistofvariablestothemodule,inparticular:module,exports,andrequire.Makeanoteofhowtheexportsargumentofthewrappingfunctionisinitializedwiththecontentsofmodule.exports,aswewilltalkaboutthislater.

NotePleasebearinmindthatthisisonlyanexampleandyouwillrarelyneedtoevaluatesomesourcecodeinarealapplication.Featuressuchaseval()orthefunctionsofthevmmodule(http://nodejs.org/api/vm.html)canbeeasilyusedinthewrongwayorwiththewronginput,thusopeningasystemtocodeinjectionattacks.Theyshouldalwaysbeusedwithextremecareoravoidedaltogether.

Let'snowseewhatthesevariablescontainbyimplementingourrequire()function:

varrequire=function(moduleName){

console.log('Requireinvokedformodule:'+moduleName);

varid=require.resolve(moduleName);//[1]

if(require.cache[id]){//[2]

returnrequire.cache[id].exports;

}

//modulemetadata

varmodule={//[3]

exports:{},

id:id

};

//Updatethecache

require.cache[id]=module;//[4]

Page 59: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//loadthemodule

loadModule(id,module,require);//[5]

//returnexportedvariables

returnmodule.exports;//[6]

};

require.cache={};

require.resolve=function(moduleName){

/*resolveafullmoduleidfromthemoduleName*/

}

Theprecedingfunctionsimulatesthebehavioroftheoriginalrequire()functionofNode.js,whichisusedtoloadamodule.Ofcourse,thisisjustforeducativepurposesanditdoesnotaccuratelyorcompletelyreflecttheinternalbehavioroftherealrequire()function,butit'sgreattounderstandtheinternalsoftheNode.jsmodulesystem,howamoduleisdefined,andloaded.Whatourhomemademodulesystemdoesisexplainedasfollows:

1. Amodulenameisacceptedasinputandtheveryfirstthingthatwedoisresolvethefullpathofthemodule,whichwecallid.Thistaskisdelegatedtorequire.resolve(),whichimplementsaspecificresolvingalgorithm(wewilltalkaboutitlater).

2. Ifthemodulewasalreadyloadedinthepast,itshouldbeavailableinthecache.Inthiscase,wejustreturnitimmediately.

3. Ifthemodulewasnotyetloaded,wesetuptheenvironmentforthefirstload.Inparticular,wecreateamoduleobjectthatcontainsanexportspropertyinitializedwithanemptyobjectliteral.ThispropertywillbeusedbythecodeofthemoduletoexportanypublicAPI.

4. Themoduleobjectiscached.5. Themodulesourcecodeisreadfromitsfileandthecodeisevaluated,aswe

haveseenbefore.Weprovidetothemodule,themoduleobjectthatwejustcreated,andareferencetotherequire()function.ThemoduleexportsitspublicAPIbymanipulatingorreplacingthemodule.exportsobject.

6. Finally,thecontentofmodule.exports,whichrepresentsthepublicAPIofthemodule,isreturnedtothecaller.

Aswesee,thereisnothingmagicalbehindtheworkingsoftheNode.jsmodulesystem;thetrickisallinthewrapperwecreatearoundamodule'ssourcecodeandtheartificialenvironmentinwhichwerunit.

DefiningamoduleBylookingathowourhomemaderequire()functionworks,weshouldnowknowhowtodefineamodule.Thefollowingcodegivesusanexample:

Page 60: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//loadanotherdependency

vardependency=require('./anotherModule');

//aprivatefunction

functionlog(){

console.log('Welldone'+dependency.username);

}

//theAPItobeexportedforpublicuse

module.exports.run=function(){

log();

};

Theessentialconcepttorememberisthateverythinginsideamoduleisprivateunlessit'sassignedtothemodule.exportsvariable.Thecontentsofthisvariablearethencachedandreturnedwhenthemoduleisloadedusingrequire().

DefiningglobalsEvenifallthevariablesandfunctionsthataredeclaredinamodulearedefinedinitslocalscope,itisstillpossibletodefineaglobalvariable.Infact,themodulesystemexposesaspecialvariablecalledglobal,whichcanbeusedforthispurpose.Everythingthatisassignedtothisvariablewillendupautomaticallyintheglobalscope.

NotePleasenotethatpollutingtheglobalscopeisconsideredabadpracticeandnullifiestheadvantageofhavingamodulesystem.So,useitonlyifyoureallyknowwhatyouaredoing.

module.exportsvsexportsFormanydeveloperswhoarenotyetfamiliarwithNode.js,acommonsourceofconfusionisthedifferencebetweenusingexportsandmodule.exportstoexposeapublicAPI.Thecodeofourhomemaderequirefunctionshouldagainclearanydoubt.Thevariableexportsisjustareferencetotheinitialvalueofmodule.exports;wehaveseenthatsuchavalueisessentiallyasimpleobjectliteralcreatedbeforethemoduleisloaded.

Thismeansthatwecanonlyattachnewpropertiestotheobjectreferencedbytheexportsvariable,asshowninthefollowingcode:

Page 61: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

exports.hello=function(){

console.log('Hello');

}

Reassigningtheexportsvariabledoesn'thaveanyeffect,becauseitdoesn'tchangethecontentsofmodule.exports,itwillonlyreassignthevariableitself.Thefollowingcodeisthereforewrong:

exports=function(){

console.log('Hello');

}

Ifwewanttoexportsomethingotherthananobjectliteral,asforexampleafunction,aninstance,orevenastring,wehavetoreassignmodule.exportsasfollows:

module.exports=function(){

console.log('Hello');

}

requireissynchronousAnotherimportantdetailthatweshouldtakeintoaccountisthatourhomemaderequirefunctionissynchronous.Infact,itreturnsthemodulecontentsusingasimpledirectstyle,andnocallbackisrequired.ThisistruefortheoriginalNode.jsrequire()functiontoo.Asaconsequence,anyassignmenttomodule.exportmustbesynchronousaswell.Forexample,thefollowingcodeisincorrect:

setTimeout(function(){

module.exports=function(){...};

},100);

Thispropertyhasimportantrepercussionsinthewaywedefinemodules,asitlimitsustomostlyusingsynchronouscodeduringthedefinitionofamodule.ThisisactuallyoneofthemostimportantreasonswhythecoreNode.jslibrariesoffersynchronousAPIsasanalternativetomostoftheasynchronousones.

Ifweneedsomeasynchronousinitializationstepsforamodule,wecanalwaysdefineandexportanuninitializedmodulethatisinitializedasynchronouslyatalatertime.Theproblemwiththisapproachthough,isthatloadingsuchamoduleusingrequiredoesnotguaranteethatit'sreadytobeused.InChapter6,Recipes,wewillanalyzethisproblemindetailandwewillpresentsomepatternstosolvethisissueelegantly.

Note

Page 62: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Forthesakeofcuriosity,youmightwanttoknowthatinitsearlydays,Node.jsusedtohaveanasynchronousversionofrequire(),butitwassoonremovedbecauseitwasovercomplicatingafunctionalitythatwasactuallymeanttobeusedonlyatinitializationtime,andwhereasynchronousI/Obringsmorecomplexitiesthanadvantages.

TheresolvingalgorithmThetermdependencyhell,describesasituationwherebythedependenciesofasoftware,inturndependonashareddependency,butrequiredifferentincompatibleversions.Node.jssolvesthisproblemelegantlybyloadingadifferentversionofamoduledependingonwherethemoduleisloadedfrom.Allthemeritsofthisfeaturegotonpmandalsototheresolvingalgorithmusedintherequirefunction.

Let'snowgiveaquickoverviewofthisalgorithm.Aswesaw,theresolve()functiontakesamodulename(whichwewillcallhere,moduleName)asinputanditreturnsthefullpathofthemodule.Thispathisthenusedtoloaditscodeandalsotoidentifythemoduleuniquely.Theresolvingalgorithmcanbedividedintothefollowingthreemajorbranches:

Filemodules:IfmoduleNamestartswith"/"it'sconsideredalreadyanabsolutepathtothemoduleandit'sreturnedasitis.Ifitstartswith"./",thenmoduleNameisconsideredarelativepath,whichiscalculatedstartingfromtherequiringmodule.Coremodules:IfmoduleNameisnotprefixedwith"/"or"./",thealgorithmwillfirsttrytosearchwithinthecoreNode.jsmodules.Packagemodules:IfnocoremoduleisfoundmatchingmoduleName,thenthesearchcontinuesbylookingforamatchingmoduleintothefirstnode_modulesdirectorythatisfoundnavigatingupinthedirectorystructurestartingfromtherequiringmodule.Thealgorithmcontinuestosearchforamatchbylookingintothenextnode_modulesdirectoryupinthedirectorytree,untilitreachestherootofthefilesystem.

Forfileandpackagemodules,boththeindividualfilesanddirectoriescanmatchmoduleName.Inparticular,thealgorithmwilltrytomatchthefollowing:

<moduleName>.js

<moduleName>/index.js

Thedirectory/filespecifiedinthemainpropertyof<moduleName>/package.json

Note

Page 63: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thecomplete,formaldocumentationoftheresolvingalgorithmcanbefoundathttp://nodejs.org/api/modules.html#modules_all_together.

Thenode_modulesdirectoryisactuallywherenpminstallsthedependenciesofeachpackage.Thismeansthat,basedonthealgorithmwejustdescribed,eachpackagecanhaveitsownprivatedependencies.Forexample,considerthefollowingdirectorystructure:

myApp

├──foo.js

└──node_modules

├──depA

│└──index.js

├──depB

│├──bar.js

│└──node_modules

│└──depA

│└──index.js

└──depC

├──foobar.js

└──node_modules

└──depA

└──index.js

Intheprecedingexample,myApp,depB,anddepCalldependondepA;however,theyallhavetheirownprivateversionofthedependency!Followingtherulesoftheresolvingalgorithm,usingrequire('depA')willloadadifferentfiledependingonthemodulethatrequiresit,forexample:

Callingrequire('depA')from/myApp/foo.jswillload/myApp/node_modules/depA/index.js

Callingrequire('depA')from/myApp/node_modules/depB/bar.jswillload/myApp/node_modules/depB/node_modules/depA/index.js

Callingrequire('depA')from/myApp/node_modules/depC/foobar.jswillload/myApp/node_modules/depC/node_modules/depA/index.js

TheresolvingalgorithmisthemagicbehindtherobustnessoftheNode.jsdependencymanagement,andiswhatmakesitpossibletohavehundredsoreventhousandsofpackagesinanapplicationwithouthavingcollisionsorproblemsofversioncompatibility.

NoteTheresolvingalgorithmisappliedtransparentlyforuswhenweinvokerequire();

Page 64: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

however,ifneeded,itcanstillbeuseddirectlybyanymodulebysimplyinvokingrequire.resolve().

ThemodulecacheEachmoduleisloadedandevaluatedonlythefirsttimeitisrequired,sinceanysubsequentcallofrequire()willsimplyreturnthecachedversion.Thisshouldresultclearbylookingatthecodeofourhomemaderequirefunction.Cachingiscrucialforperformances,butitalsohassomeimportantfunctionalimplications:

ItmakesitpossibletohavecycleswithinmoduledependenciesItguarantees,tosomeextent,thatalwaysthesameinstanceisreturnedwhenrequiringthesamemodulefromwithinagivenpackage

Themodulecacheisexposedintherequire.cachevariable,soitispossibletodirectlyaccessitifneeded.Acommonusecaseistoinvalidateanycachedmodulebydeletingtherelativekeyintherequire.cachevariable,apracticeveryusefulduringtestingbutverydangerousifappliedinnormalcircumstances.

CyclesManyconsidercirculardependenciesasanintrinsicdesignissue,butitissomethingwhichmightactuallyhappeninarealproject,soit'susefulforustoknowatleasthowthisworksinNode.js.Ifwelookagainatourhomemaderequire()function,weimmediatelygetaglimpseofhowthismightworkandwhatareitscaveats.

Supposewehavetwomodulesdefinedasfollows:

Modulea.js:

exports.loaded=false;

varb=require('./b');

module.exports={

bWasLoaded:b.loaded,

loaded:true

};

Moduleb.js:

exports.loaded=false;

vara=require('./a');

module.exports={

aWasLoaded:a.loaded,

loaded:true

};

Page 65: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,let'strytoloadthesefromanothermodule,main.js,asfollows:

vara=require('./a');

varb=require('./b');

console.log(a);

console.log(b);

Theprecedingcodewillprintthefollowingoutput:

{bWasLoaded:true,loaded:true}

{aWasLoaded:false,loaded:true}

Thisresultrevealsthecaveatsofcirculardependencies.Whileboththemodulesarecompletelyinitializedthemomenttheyarerequiredfromthemainmodule,thea.jsmodulewillbeincompletewhenitisloadedfromb.js.Inparticular,itsstatewillbetheonethatitreachedthemomentitrequiredb.js.Thisbehaviorshouldringanotherbell,whichwillbeconfirmedifweswaptheorderinwhichthetwomodulesarerequiredinmain.js.

Ifyoutryit,youwillseethatthistimeitwillbethemodulea.jsthatwillreceiveanincompleteversionofb.js.Weunderstandnowthatthiscanbecomequiteafuzzybusinessifwelosecontrolofwhichmoduleisloadedfirst,whichcanhappenquiteeasilyiftheprojectisbigenough.

ModuledefinitionpatternsThemodulesystem,besidesbeingamechanismforloadingdependencies,isalsoatoolfordefiningAPIs.AsforanyotherproblemrelatedtoAPIdesign,themainfactortoconsideristhebalancebetweenprivateandpublicfunctionality.TheaimistomaximizeinformationhidingandAPIusability,whilebalancingthesewithothersoftwarequalitieslikeextensibilityandcodereuse.

Inthissection,wewillanalyzesomeofthemostpopularpatternsfordefiningmodulesinNode.js;eachonehasitsownbalanceofinformationhiding,extensibility,andcodereuse.

NamedexportsThemostbasicmethodforexposingapublicAPIisusingnamedexports,whichconsistsinassigningallthevalueswewanttomakepublictopropertiesoftheobjectreferencedbyexports(ormodule.exports).Inthisway,theresultingexportedobjectbecomesacontainerornamespaceforasetofrelatedfunctionality.

Page 66: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thefollowingcodeshowsamoduleimplementingthispattern:

//filelogger.js

exports.info=function(message){

console.log('info:'+message);

};

exports.verbose=function(message){

console.log('verbose:'+message);

};

Theexportedfunctionsarethenavailableaspropertiesoftheloadedmodule,asshowninthefollowingcode:

//filemain.js

varlogger=require('./logger');

logger.info('Thisisaninformationalmessage');

logger.verbose('Thisisaverbosemessage');

MostoftheNode.jscoremodulesusethispattern.

NoteTheCommonJSspecificationonlyallowstheuseoftheexportsvariabletoexposepublicmembers.Therefore,thenamedexportspatternistheonlyonethatisreallycompatiblewiththeCommonJSspecification.Theuseofmodule.exportsisanextensionprovidedbyNode.jstosupportabroaderrangeofmoduledefinitionpatterns,asthosewearegoingtoseenext.

ExportingafunctionOneofthemostpopularmoduledefinitionpatternsconsistsinreassigningthewholemodule.exportsvariabletoafunction.Itsmainstrengthit'sthefactthatitexposesonlyasinglefunctionality,whichprovidesaclearentrypointforthemodule,andmakesitsimpletounderstandanduse;italsohonorstheprincipleofsmallsurfaceareaverywell.Thiswayofdefiningmodulesisalsoknowninthecommunityassubstackpattern,afteroneofitsmostprolificadopters,JamesHalliday(nicknamesubstack).Thefollowingcodeisanexampleofthispattern:

//filelogger.js

module.exports=function(message){

console.log('info:'+message);

};

Page 67: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ApossibleextensionofthispatternisusingtheexportedfunctionasnamespaceforotherpublicAPIs.Thisisaverypowerfulcombination,becauseitstillgivesthemoduletheclarityofasingleentrypoint(themainexportedfunction),butitalsoallowsustoexposeotherfunctionalitiesthathavesecondaryormoreadvancedusecases.Thefollowingcodeshowsyouhowtoextendthemodulewedefinedpreviouslybyusingtheexportedfunctionasanamespace:

module.exports.verbose=function(message){

console.log('verbose:'+message);

};

Thefollowingcodedemonstrateshowtousethemodulethatwejustdefined:

//filemain.js

varlogger=require('./logger');

logger('Thisisaninformationalmessage');

logger.verbose('Thisisaverbosemessage');

Eventhoughexportingjustafunctionmightseemalimitation,inreality,it'saperfectwaytoputtheemphasisonasinglefunctionality—themostimportantforthemodule—whilegivinglessvisibilitytosecondaryaspects,whichareinsteadexposedaspropertiesoftheexportedfunctionitself.

NotePattern(substack):exposethemainfunctionalityofamodulebyexportingonlyonefunction.Usetheexportedfunctionasnamespacetoexposeanyauxiliaryfunctionality.

ExportingaconstructorAmodulethatexportsaconstructorisaspecializationofamodulethatexportsafunction.Thedifferenceisthatwiththisnewpattern,weallowtheusertocreatenewinstancesusingtheconstructor,butwealsogivethemtheabilitytoextenditsprototypeandforgenewclasses.Thefollowingisanexampleofthispattern:

//filelogger.js

functionLogger(name){

this.name=name;

};

Logger.prototype.log=function(message){

console.log('['+this.name+']'+message);

Page 68: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

};

Logger.prototype.info=function(message){

this.log('info:'+message);

};

Logger.prototype.verbose=function(message){

this.log('verbose:'+message);

};

module.exports=Logger;

And,wecanusetheprecedingmoduleasfollows:

//filelogger.js

varLogger=require('./logger');

vardbLogger=newLogger('DB');

dbLogger.info('Thisisaninformationalmessage');

varaccessLogger=newLogger('ACCESS');

accessLogger.verbose('Thisisaverbosemessage');

Exportingaconstructorstillprovidesasingleentrypointforthemodule,butcomparedtothesubstackpattern,itexposesalotmoreofthemoduleinternals;howeverontheothersideitallowsmuchmorepowerwhenitcomestoextendingitsfunctionality.

Avariationofthispatternconsistsinapplyingaguardagainstinvocationsthatdon'tusethenewinstruction.Thislittletrickallowsustouseourmoduleasafactory.Thefollowingcodeshowsyouhowthisworks:

functionLogger(name){

if(!(thisinstanceofLogger)){

returnnewLogger(name);

}

this.name=name;

};

Thetrickissimple;wecheckwhetherthisexistsandisaninstanceofLogger.Ifanyoftheseconditionsisfalse,itmeansthattheLogger()functionwasinvokedwithoutusingnew,soweproceedwithcreatingthenewinstanceproperlyandreturningittothecaller.Thistechniqueallowsustousethemodulealsoasafactory,asshowninthefollowingcode:

//filelogger.js

varLogger=require('./logger');

vardbLogger=Logger('DB');

accessLogger.verbose('Thisisaverbosemessage');

Exportinganinstance

Page 69: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Wecanleveragethecachingmechanismofrequire()toeasilydefinestatefulinstances—objectswithastatecreatedfromaconstructororafactory,whichcanbesharedacrossdifferentmodules.Thefollowingcodeshowsanexampleofthispattern:

//filelogger.js

functionLogger(name){

this.count=0;

this.name=name;

};

Logger.prototype.log=function(message){

this.count++;

console.log('['+this.name+']'+message);

};

module.exports=newLogger('DEFAULT');

Thisnewlydefinedmodulecanthenbeusedasfollows:

//filemain.js

varlogger=require('./logger');

logger.log('Thisisaninformationalmessage');

Becausethemoduleiscached,everymodulethatrequirestheloggermodulewillactuallyalwaysretrievethesameinstanceoftheobject,thussharingitsstate.ThispatternisverymuchlikecreatingaSingleton,however,itdoesnotguaranteetheuniquenessoftheinstanceacrosstheentireapplication,asithappensinthetraditionalSingletonpattern.Whenanalyzingtheresolvingalgorithm,wehaveseeninfact,thatamodulemightbeinstalledmultipletimesinsidethedependencytreeofanapplication.Thisresultswithmultipleinstancesofthesamelogicalmodule,allrunninginthecontextofthesameNode.jsapplication.InChapter5,WiringModules,wewillanalyzetheconsequencesofexportingstatefulinstancesandsomeofthepatternswecanuseasalternatives.

Anextensiontothepatternwejustdescribed,consistsinexposingtheconstructorusedtocreatetheinstance,inadditiontotheinstanceitself.Thisallowstheusertocreatenewinstancesofthesameobject,oreventoextenditifnecessary.Toenablethis,wejustneedtoassignanewpropertytotheinstance,asshowninthefollowinglineofcode:

module.exports.Logger=Logger;

Then,wecanusetheexportedconstructortocreateotherinstancesoftheclass,asfollows:

varcustomLogger=newlogger.Logger('CUSTOM');

Page 70: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

customLogger.log('Thisisaninformationalmessage');

Fromausabilityperspective,thisissimilartousinganexportedfunctionasnamespace;themoduleexportsthedefaultinstanceofanobject—thepieceoffunctionalitywemightwanttousemostofthetime—whilemoreadvancedfeatures,suchastheabilitytocreatenewinstancesorextendtheobject,arestillmadeavailablethroughlessexposedproperties.

ModifyingothermodulesortheglobalscopeAmodulecanevenexportnothing.Thiscanlookabitoutofplace,however,weshouldnotforgetthatamodulecanmodifytheglobalscopeandanyobjectinit,includingothermodulesinthecache.Pleasenotethattheseareingeneralconsideredbadpractices,butsincethispatterncanbeusefulandsafeundersomecircumstances(forexample,fortesting)andissometimesusedinthewild,itisworthtoknowandunderstandit.So,wesaidamodulecanmodifyothermodulesorobjectsintheglobalscope.Well,thisiscalledmonkeypatching,whichgenerallyreferstothepracticeofmodifyingtheexistingobjectsatruntimetochangeorextendtheirbehaviorortoapplytemporaryfixes.

Thefollowingexampleshowsyouhowwecanaddanewfunctiontoanothermodule:

//filepatcher.js

//./loggerisanothermodule

require('./logger').customMessage=function(){

console.log('Thisisanewfunctionality');

};

Usingournewpatchermodulewouldbeaseasyaswritingthefollowingcode:

//filemain.js

require('./patcher');

varlogger=require('./logger');

logger.customMessage();

Intheprecedingcode,patchermustberequiredbeforeusingtheloggermoduleforthefirsttimeinordertoallowthepatchtobeapplied.

Thetechniquesdescribedherearealldangerousonestoapply.Themainconcernisthat,tohaveamodulethatmodifiestheglobalnamespaceorothermodulesisanoperationwithsideeffects.Inotherwords,itaffectsthestateofentitiesoutsidetheirscope,whichcanhaveconsequencesthatarenotalwayspredictable,especiallywhenmultiplemodulesinteractwiththesameentities.Imaginetohavetwodifferent

Page 71: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

modulestryingtosetthesameglobalvariable,ormodifyingthesamepropertyofthesamemodule;theeffectsmightbeunpredictable(whichmodulewins?),butmostimportantlyitwouldhaverepercussionsontheentireapplication.

Page 72: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheobserverpatternAnotherimportantandfundamentalpatternusedinNode.jsistheobserverpattern.Togetherwithreactor,callbacks,andmodules,thisisoneofthepillarsoftheplatformandanabsoluteprerequisiteforusingmanynode-coreanduserlandmodules.

ObserverisanidealsolutionformodelingthereactivenatureofNode.js,andaperfectcomplementforcallbacks.Let'sgiveaformaldefinitionasfollows:

NotePattern(observer):definesanobject(calledsubject),whichcannotifyasetofobservers(orlisteners),whenachangeinitsstatehappens.

Themaindifferencefromthecallbackpatternisthatthesubjectcanactuallynotifymultipleobservers,whileatraditionalcontinuation-passingstylecallbackwillusuallypropagateitsresulttoonlyonelistener,thecallback.

TheEventEmitterIntraditionalobject-orientedprogramming,theobserverpatternrequiresinterfaces,concreteclasses,andahierarchy;inNode.js,allbecomesmuchsimpler.TheobserverpatternisalreadybuiltintothecoreandisavailablethroughtheEventEmitterclass.TheEventEmitterclassallowsustoregisteroneormorefunctionsaslisteners,whichwillbeinvokedwhenaparticulareventtypeisfired.Thefollowingimagevisuallyexplainstheconcept:

Page 73: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheEventEmitterisaprototype,anditisexportedfromtheeventscoremodule.Thefollowingcodeshowshowwecanobtainareferencetoit:

varEventEmitter=require('events').EventEmitter;

vareeInstance=newEventEmitter();

TheessentialmethodsoftheEventEmitteraregivenasfollows:

on(event,listener):Thismethodallowsyoutoregisteranewlistener(afunction)forthegiveneventtype(astring)once(event,listener):Thismethodregistersanewlistener,whichisthenremovedaftertheeventisemittedforthefirsttimeemit(event,[arg1],[…]):ThismethodproducesaneweventandprovidesadditionalargumentstobepassedtothelistenersremoveListener(event,listener):Thismethodremovesalistenerforthespecifiedeventtype

AlltheprecedingmethodswillreturntheEventEmitterinstancetoallowchaining.Thelistenerfunctionhasthesignature,function([arg1],[…]),soitsimplyacceptstheargumentsprovidedthemomenttheeventisemitted.Insidethelistener,thisreferstotheinstanceoftheEventEmitterthatproducestheevent.

WecanalreadyseethatthereisabigdifferencebetweenalistenerandatraditionalNode.jscallback;inparticular,thefirstargumentisnotanerror,butitcanbeanydatapassedtoemit()atthemomentofitsinvocation.

Page 74: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CreateanduseanEventEmitterLet'sseehowwecanuseanEventEmitterinpractice.Thesimplestwayistocreateanewinstanceanduseitdirectly.Thefollowingcodeshowsafunction,whichusesanEventEmittertonotifyitssubscribersinrealtimewhenaparticularpatternisfoundinalistoffiles:

varEventEmitter=require('events').EventEmitter;

varfs=require('fs');

functionfindPattern(files,regex){

varemitter=newEventEmitter();

files.forEach(function(file){

fs.readFile(file,'utf8',function(err,content){

if(err)

returnemitter.emit('error',err);

emitter.emit('fileread',file);

varmatch=null;

if(match=content.match(regex))

match.forEach(function(elem){

emitter.emit('found',file,elem);

});

});

});

returnemitter;

}

TheEventEmittercreatedbytheprecedingfunctionwillproducethefollowingthreeevents:

fileread:Thiseventoccurswhenafileisreadfound:Thiseventoccurswhenamatchhasbeenfounderror:Thiseventoccurswhenanerrorhasoccurredduringthereadingofthefile

Let'sseenowhowourfindPattern()functioncanbeused:

findPattern(

['fileA.txt','fileB.json'],

/hello\w+/g

)

.on('fileread',function(file){

console.log(file+'wasread');

})

.on('found',function(file,match){

console.log('Matched"'+match+'"infile'+file);

})

.on('error',function(err){

Page 75: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

console.log('Erroremitted:'+err.message);

});

Intheprecedingexample,weregisteredalistenerforeachofthethreeeventtypesproducedbytheEventEmitterwhichwascreatedbyourfindPattern()function.

PropagatingerrorsTheEventEmitter-asithappensforcallbacks-cannotjustthrowexceptionswhenanerrorconditionoccurs,astheywouldbelostintheeventloopiftheeventisemittedasynchronously.Instead,theconventionistoemitaspecialevent,callederror,andtopassanErrorobjectasanargument.That'sexactlywhatwearedoinginthefindPattern()functionthatwedefinedearlier.

NoteItisalwaysagoodpracticetoregisteralistenerfortheerrorevent,asNode.jswilltreatitinaspecialwayandwillautomaticallythrowanexceptionandexitfromtheprogramifnoassociatedlistenerisfound.

MakeanyobjectobservableSometimes,creatinganewobservableobjectdirectlyfromtheEventEmitterclassisnotenough,asthismakesitimpracticaltoprovidefunctionalitythatgoesbeyondthemereproductionofnewevents.Itismorecommon,infact,tohavetheneedtomakeagenericobjectobservable;thisispossiblebyextendingtheEventEmitterclass.

Todemonstratethispattern,let'strytoimplementthefunctionalityofthefindPattern()functioninanobjectasfollows:

varEventEmitter=require('events').EventEmitter;

varutil=require('util');

varfs=require('fs');

functionFindPattern(regex){

EventEmitter.call(this);

this.regex=regex;

this.files=[];

}

util.inherits(FindPattern,EventEmitter);

FindPattern.prototype.addFile=function(file){

this.files.push(file);

Page 76: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

returnthis;

};

FindPattern.prototype.find=function(){

varself=this;

self.files.forEach(function(file){

fs.readFile(file,'utf8',function(err,content){

if(err)

returnself.emit('error',err);

self.emit('fileread',file);

varmatch=null;

if(match=content.match(self.regex))

match.forEach(function(elem){

self.emit('found',file,elem);

});

});

});

returnthis;

};

TheFindPatternprototypethatwedefinedextendstheEventEmitterusingtheinherits()functionprovidedbythecoremoduleutil.Thisway,itbecomesafull-fledgedobservableclass.Thefollowingisanexampleofitsusage:

varfindPatternObject=newFindPattern(/hello\w+/);

findPatternObject

.addFile('fileA.txt')

.addFile('fileB.json')

.find()

.on('found',function(file,match){

console.log('Matched"'+match+'"infile'+file);

})

.on('error',function(err){

console.log('Erroremitted'+err.message);

});

WecannowseehowtheFindPatternobjecthasafullsetofmethods,inadditiontobeingobservablebyinheritingthefunctionalityoftheEventEmitter.

ThisisaprettycommonpatternintheNode.jsecosystem,forexample,theServerobjectofthecorehttpmoduledefinesmethodssuchaslisten(),close(),setTimeout(),andinternallyitalsoinheritsfromtheEventEmitterfunction,thusallowingittoproduceevents,suchasrequest,whenanewrequestisreceived,orconnection,whenanewconnectionisestablished,orclosed,whentheserverisclosed.

OthernotableexamplesofobjectsextendingtheEventEmitterareNode.jsstreams.WewillanalyzestreamsinmoredetailinChapter3,CodingwithStreams.

Page 77: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SynchronousandasynchronouseventsAswithcallbacks,eventscanbeemittedsynchronouslyorasynchronously,anditiscrucialthatwenevermixthetwoapproachesinthesameEventEmitter,butevenmoreimportantly,whenemittingthesameeventtype,toavoidtoproducethesameproblemsthatwedescribedintheUnleashingZalgosection.

Themaindifferencebetweenemittingsynchronousorasynchronouseventsliesinthewaylistenerscanberegistered.Whentheeventsareemittedasynchronously,theuserhasallthetimetoregisternewlistenersevenaftertheEventEmitterisinitialized,becausetheeventsareguaranteednottobefireduntilthenextcycleoftheeventloop.That'sexactlywhatishappeninginthefindPattern()function.WedefinedthisfunctionpreviouslyanditrepresentsacommonapproachthatisusedinmostNode.jsmodules.

Onthecontrary,emittingeventssynchronouslyrequiresthatallthelistenersareregisteredbeforetheEventEmitterfunctionstartstoemitanyevent.Let'slookatanexample:

functionSyncEmit(){

this.emit('ready');

}

util.inherits(SyncEmit,EventEmitter);

varsyncEmit=newSyncEmit();

syncEmit.on('ready',function(){

console.log('Objectisreadytobeused');

});

Ifthereadyeventwasemittedasynchronously,thenthepreviouscodewouldworkperfectly;however,theeventisproducedsynchronouslyandthelistenerisregisteredaftertheeventwasalreadysent,sotheresultisthatthelistenerisneverinvoked;thecodewillprintnothingtotheconsole.

Contrarilytocallbacks,therearesituationswhereusinganEventEmitterinasynchronousfashionmakessense,givenitsdifferentpurpose.Forthisreason,it'sveryimportanttoclearlyhighlightthebehaviorofourEventEmitterinitsdocumentationtoavoidconfusion,andpotentiallyawrongusage.

EventEmittervsCallbacksAcommondilemmawhendefininganasynchronousAPIistocheckwhethertouseanEventEmitterorsimplyacceptacallback.Thegeneraldifferentiatingruleissemantic:callbacksshouldbeusedwhenaresultmustbereturnedinan

Page 78: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

asynchronousway;eventsshouldinsteadbeusedwhenthereisaneedtocommunicatethatsomethinghasjusthappened.

Butbesidesthissimpleprinciple,alotofconfusionisgeneratedfromthefactthatthetwoparadigmsaremostofthetimeequivalentandallowyoutoachievethesameresults.Considerthefollowingcodeforanexample:

functionhelloEvents(){

vareventEmitter=newEventEmitter();

setTimeout(function(){

eventEmitter.emit('hello','world');

},100);

returneventEmitter;

}

functionhelloCallback(callback){

setTimeout(function(){

callback('hello','world');

},100);

}

ThetwofunctionshelloEvents()andhelloCallback()canbeconsideredequivalentintermsoffunctionality;thefirstcommunicatesthecompletionofthetimeoutusinganevent,thesecondusesacallbacktonotifythecallerinstead,passingtheeventtypeasanargument.Butwhatreallydifferentiatesthemisthereadability,thesemantic,andtheamountofcodethatisrequiredtobeimplementedorused.Whilewecannotgiveadeterministicsetofrulestochoosebetweenoneortheotherstyle,wecancertainlyprovidesomehintstohelptakethedecision.

Asafirstobservation,wecansaythatcallbackshavesomelimitationswhenitcomestosupportingdifferenttypesofevents.Infact,wecanstilldifferentiatebetweenmultipleeventsbypassingthetypeasanargumentofthecallback,orbyacceptingseveralcallbacks,oneforeachsupportedevent.However,thiscannotexactlybeconsideredanelegantAPI.Inthissituation,anEventEmittercangiveabetterinterfaceandleanercode.

AnothercasewheretheEventEmittermightbepreferableiswhenthesameeventcanoccurmultipletimes,ornotoccuratall.Acallback,infact,isexpectedtobeinvokedexactlyonce,whethertheoperationissuccessfulornot.Thefactthatwehaveapossiblyrepeatingcircumstanceshouldletusthinkagainaboutthesemanticnatureoftheoccurrence,whichismoresimilartoaneventthathastobecommunicatedratherthanaresult;inthiscaseanEventEmitteristhepreferredchoice.

Lastly,anAPIusingcallbackscannotifyonlythatparticularcallback,whileusinganEventEmitterfunctionit'spossibleformultiplelistenerstoreceivethesamenotification.

Page 79: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CombinecallbacksandEventEmitterTherearealsosomecircumstanceswhereanEventEmittercanbeusedinconjunctionwithacallback.Thispatternisextremelyusefulwhenwewanttoimplementtheprincipleofsmallsurfaceareabyexportingatraditionalasynchronousfunctionasthemainfunctionality,whilestillprovidingricherfeatures,andmorecontrolbyreturninganEventEmitter.Oneexampleofthispatternisofferedbythenode-globmodule(https://npmjs.org/package/glob),alibrarythatperformsglob-stylefilesearches.Themainentrypointofthemoduleisthefunctionitexports,whichhasthefollowingsignature:

glob(pattern,[options],callback)

Thefunctiontakespatternasthefirstargument,asetofoptions,andacallbackfunctionwhichisinvokedwiththelistofallthefilesmatchingtheprovidedpattern.Atthesametime,thefunctionreturnsanEventEmitterthatprovidesamorefine-grainedreportoverthestateoftheprocess.Forexample,itispossibletobenotifiedinreal-timewhenamatchoccursbylisteningtothematchevent,toobtainthelistofallthematchedfileswiththeendevent,ortoknowwhethertheprocesswasmanuallyabortedbylisteningtotheabortevent.Thefollowingcodeshowshowthislooks:

varglob=require('glob');

glob('data/*.txt',function(error,files){

console.log('Allfilesfound:'+JSON.stringify(files));

}).on('match',function(match){

console.log('Matchfound:'+match);

});

Aswecansee,thepracticeofexposingasimple,clean,andminimalentrypointwhilestillprovidingmoreadvancedorlessimportantfeatureswithsecondarymeansisquitecommoninNode.js,andcombiningEventEmitterwithtraditionalcallbacksisoneofthewaystoachievethat.

TipPattern:createafunctionthatacceptsacallbackandreturnsanEventEmitter,thusprovidingasimpleandclearentrypointforthemainfunctionality,whileemittingmorefine-grainedeventsusingtheEventEmitter.

Page 80: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryInthischapter,wehaveseenhowtheNode.jsplatformisbasedonafewimportantprinciplesthatprovidethefoundationtobuildefficientandreusablecode.Thephilosophyandthedesignchoicesbehindtheplatformhave,infact,astronginfluenceonthestructureandbehaviorofeveryapplicationandmodulewecreate.Often,foradevelopermovingfromanothertechnology,theseprinciplesmightseemunfamiliarandtheusualinstinctivereactionistofightthechangebytryingtofindmorefamiliarpatternsinsideaworldwhichinrealityrequiresarealshiftinthemindset.Ononehand,theasynchronousnatureofthereactorpatternrequiresadifferentprogrammingstylemadeofcallbacksandthingsthathappenatalatertime,withoutworryingtoomuchaboutthreadsandraceconditions.Ontheotherhand,themodulepatternanditsprinciplesofsimplicityandminimalismcreatesinterestingnewscenariosintermsofreusability,maintenance,andusability.

Finally,besidestheobvioustechnicaladvantagesofbeingfast,efficient,andbasedonJavaScript,Node.jsisattractingsomuchinterestbecauseoftheprincipleswehavejustdiscovered.Formany,graspingtheessenceofthisworldfeelslikereturningtotheorigins,toamorehumanewayofprogrammingforbothsizeandcomplexityandthat'swhydevelopersendupfallinginlovewithNode.js.

Inthenextchapter,wewillfocusourattentiononthemechanismstohandleasynchronouscode,wewillseehowcallbackscaneasilybecomeourenemy,andwewilllearnhowtofixthatbyusingsomesimpleprinciples,patterns,orevenconstructsthatdonotrequireacontinuation-passingstyleprogramming.

Page 81: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter2.AsynchronousControlFlowPatternsMovingfromasynchronousprogrammingstyletoaplatformsuchasNode.js,wherecontinuation-passingstyleandasynchronousAPIsarethenorm,canbefrustrating.Writingasynchronouscodecanbeadifferentexperience,especiallywhenitcomestocontrolflow.Simpleproblemssuchasiteratingoverasetoffiles,executingtasksinsequence,orwaitingforasetofoperationstocomplete,requirethedevelopertotakenewapproachesandtechniquestoavoidendingupwritinginefficientandunreadablecode.Onecommonmistakeistofallintothetrapofthecallbackhellproblemandseethecodegrowinghorizontallyratherthanvertically,withanestingthatmakesevensimpleroutineshardtoreadandmaintain.

Inthischapter,wewillseehowit'sactuallypossibletotamecallbacksandwriteclean,manageableasynchronouscodebyusingsomedisciplineandwiththeaidofsomepatterns.Wewillseehowcontrolflowlibraries,suchasasync,cansignificantlysimplifyourproblems,andwewillalsodiscoverthatthecontinuation-passingstyleisnottheonlywaytoimplementasynchronousAPI.Infact,wewilllearnhowPromisesandECMAScript6generatorscanbepowerfulandflexiblealternatives.Foreachoneoftheseparadigms,wewilllearnaboutpatternsthatwillhelpusimplementthemostcommoncontrolflows,andbytheendofthechapter,weshouldbereadyandconfidenttowritecleanandefficientasynchronouscode.

ThedifficultiesofasynchronousprogrammingLosingcontrolofasynchronouscodeinJavaScriptisundoubtedlyeasy.Closuresandin-placedefinitionofanonymousfunctionsallowasmoothprogrammingexperiencethatdoesn'trequirethedevelopertojumptootherpointsinthecodebase.ThisisperfectlyinlinewiththeKISSprinciple;it'ssimple,itkeepsthecodeflowing,andwegetitworkinginlesstime.Unfortunately,sacrificingqualitiessuchasmodularity,reusability,andmaintainabilitywillsoonerorlaterleadtotheuncontrolledproliferationofcallbacknesting,thegrowthinthesizeoffunctions,andwillleadtopoorcodeorganization.Mostofthetime,creatingclosuresisnotfunctionallyneeded,soit'smoreamatterofdisciplinethanaproblemrelatedtoasynchronousprogramming.Recognizingthatourcodeisbecomingunwieldy—orevenbetter,knowinginadvancethatitmightbecomeunwieldy—andthenactingaccordinglywiththemostadequatesolutioniswhatdifferentiatesanovicefromanexpert.

Page 82: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CreatingasimplewebspiderToexplaintheproblem,wewillcreatealittlewebspider,acommand-lineapplicationthattakesinawebURLasinputanddownloadsitscontentslocallyintoafile.Inthecodepresentedinthischapter,wearegoingtouseafewnpmdependencies:

request:AlibrarytostreamlineHTTPcallsmkdirp:Asmallutilitytocreatedirectoriesrecursively

Also,wewilloftenrefertoalocalmodulenamed./utilities,whichcontainssomehelperswhichwewillbeusinginourapplication.Weomitthecontentsofthisfileforbrevity,butyoucanfindthefullimplementation,alongwithapackage.jsoncontainingthefulllistofdependencies,inthedownloadpackforthisbookavailableathttp://www.packtpub.com.

Thecorefunctionalityofourapplicationiscontainedinsideamodulenamedspider.js.Let'sseehowitlooks.Tostartwith,let'sloadallthedependenciesthatwearegoingtouse:

varrequest=require('request');

varfs=require('fs');

varmkdirp=require('mkdirp');

varpath=require('path');

varutilities=require('./utilities');

Next,wecreateanewfunctionnamedspider(),whichtakesintheURLtodownloadandacallbackfunctionthatwillbeinvokedwhenthedownloadprocesscompletes:

functionspider(url,callback){

varfilename=utilities.urlToFilename(url);

fs.exists(filename,function(exists){//[1]

if(!exists){

console.log("Downloading"+url);

request(url,function(err,response,body){//[2]

if(err){

callback(err);

}else{

mkdirp(path.dirname(filename),function(err){

//[3]

if(err){

callback(err);

}else{

fs.writeFile(filename,body,function(err){

//[4]

if(err){

callback(err);

}else{

callback(null,filename,true);

Page 83: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

});

}

});

}

});

}else{

callback(null,filename,false);

}

});

}

Theprecedingfunctionexecutesthefollowingtasks:

1. ChecksiftheURLwasalreadydownloadedbyverifyingthatthecorrespondingfilewasnotalreadycreated:

fs.exists(filecodename,function(exists)…

2. Ifthefileisnotfound,theURLisdownloadedusingthefollowinglineofcode:

request(url,function(err,response,body)…

3. Then,wemakesurewhetherthedirectorythatwillcontainthefileexistsornot:

mkdirp(path.dirname(filename),function(err)…

4. Finally,wewritethebodyoftheHTTPresponsetothefilesystem:

fs.writeFile(filename,body,function(err)…

Tocompleteourwebspiderapplication,wejustneedtoinvokethespider()functionbyprovidingaURLasaninput(inourcase,wereaditfromthecommand-linearguments):

spider(process.argv[2],function(err,filename,downloaded){

if(err){

console.log(err);

}elseif(downloaded){

console.log('Completedthedownloadof"'+filename+'"');

}else{

console.log('"'+filename+'"wasalreadydownloaded');

}

});

Now,wearereadytotryourwebspiderapplication,butfirst,makesureyouhavetheutilities.jsmoduleandthepackage.jsoncontainingthefulllistofdependenciesinyourprojectdirectory.Then,installallthedependenciesbyrunningthefollowingcommand:

Page 84: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

npminstall

Next,wecanexecutethespidermoduletodownloadthecontentsofawebpage,withacommandlikethis:

nodespiderhttp://www.example.com

NoteOurwebspiderapplicationrequiresthatwealwaysincludetheprotocol(forexample,http://)intheURLweprovide.Also,donotexpectHTMLlinkstoberewrittenorresourcessuchasimagestobedownloadedasthisisjustasimpleexampletodemonstratehowasynchronousprogrammingworks.

ThecallbackhellLookingatthespider()functionwedefinedearlier,wecansurelynoticethateventhoughthealgorithmweimplementedisreallystraightforward,theresultingcodehasseverallevelsofindentationandisveryhardtoread.ImplementingasimilarfunctionwithdirectstyleblockingAPIwouldbestraightforward,andtherewouldbeveryfewchancestomakeitlooksowrong.However,usingasynchronousCPSisanotherstory,andmakingbaduseofclosurescanleadtoanincrediblybadcode.

Thesituationwheretheabundanceofclosuresandin-placecallbackdefinitionstransformthecodeintoanunreadableandunmanageableblobisknownascallbackhell.It'soneofthemostwellrecognizedandsevereanti-patternsinNode.jsandJavaScriptingeneral.Thetypicalstructureofacodeaffectedbythisproblemlookslikethefollowing:

asyncFoo(function(err){

asyncBar(function(err){

asyncFooBar(function(err){

[...]

});

});

});

Wecanseehowcodewritteninthiswayassumestheshapeofapyramidduetothedeepnestingandthat'swhyitisalsocolloquiallyknownasthepyramidofdoom.

Themostevidentproblemwithcodesuchastheprecedingoneisthepoorreadability.Duetothenestingbeingtoodeep,it'salmostimpossibletokeeptrackof

Page 85: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

whereafunctionendsandwhereanotheronebegins.

Anotherissueiscausedbytheoverlappingofthevariablenamesusedineachscope.Often,wehavetousesimilarorevenidenticalnamestodescribethecontentofavariable.Thebestexampleistheerrorargumentreceivedbyeachcallback.Somepeopleoftentrytousevariationsofthesamenametodifferentiatetheobjectineachscope—forexample,err,error,err1,err2,andsoon;othersprefertojusthidethevariabledefinedinthescopebyalwaysusingthesamename;forexample,err.Boththealternativesarefarfromperfect,andcauseconfusionandincreasetheprobabilityofintroducingdefects.

Also,wehavetokeepinmindthatclosurescomeatasmallpriceintermsofperformancesandmemoryconsumption.Inaddition,theycancreatememoryleaksthatarenotsoeasytoidentifybecauseweshouldn'tforgetthatanycontextreferencedbyanactiveclosureisretainedfromgarbagecollection.

NoteForagreatintroductiontohowclosuresworkinV8youcanrefertotheblogpostbyVyacheslavEgorov,asoftwareengineeratGoogleworkingonV8,athttp://mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html.

Ifwelookatourspider()function,wewillnoticethatitclearlyrepresentsacallbackhellsituationandhasalltheproblemswejustdescribed.That'sexactlywhatwearegoingtofixwiththepatternsandtechniqueswewilllearninthischapter.

Page 86: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UsingplainJavaScriptNowthatwehavemetourfirstexampleofcallbackhell,weknowwhatweshoulddefinitelyavoid;however,that'snottheonlyconcernwhenwritingasynchronouscode.Infact,thereareseveralsituationswherecontrollingtheflowofasetofasynchronoustasksrequirestheuseofspecificpatternsandtechniques,especiallyifweareusingonlyplainJavaScriptwithouttheaidofanyexternallibrary.Forexample,iteratingoveracollectionbyapplyinganasynchronousoperationinsequenceisnotaseasyasinvokingforEach()overanarray,butitactuallyrequiresatechniquesimilartoarecursion.

Inthissection,wewilllearnnotonlyabouthowtoavoidthecallbackhellbutalsoabouthowtoimplementsomeofthemostcommoncontrolflowpatternsusingonlysimpleandplainJavaScript.

CallbackdisciplineWhenwritingasynchronouscode,thefirstruletokeepinmindistonotabuseclosureswhendefiningcallbacks.Itcanbetemptingtodoso,becauseitdoesnotrequireanyadditionalthinkingforproblemssuchasmodularizationandreusability;however,wehaveseenhowthiscanhavemoredisadvantagesthanadvantages.Mostofthetimes,fixingthecallbackhellproblemdoesnotrequireanylibrary,fancytechnique,orchangeofparadigmbutjustsomecommonsense.

Thesearesomebasicprinciplesthatcanhelpuskeepthenestinglevellowandimprovetheorganizationofourcodeingeneral:

Youmustexitassoonaspossible.Usereturn,continue,orbreak,dependingonthecontext,toimmediatelyexitthecurrentstatementinsteadofwriting(andnesting)completeif/elsestatements.Thiswillhelpkeepourcodeshallow.Youneedtocreatenamedfunctionsforcallbacks,keepingthemoutofclosuresandpassingintermediateresultsasarguments.Namingourfunctionswillalsomakethemlookbetterinstacktraces.Youneedtomodularizethecode.Splitthecodeintosmaller,reusablefunctionswheneverit'spossible.

ApplyingthecallbackdisciplineTodemonstratethepoweroftheearliermentionedprinciples,let'sapplythemtofixthecallbackhellprobleminourwebspiderapplication.

Page 87: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Forthefirststep,wecanrefactorourerror-checkingpatternbyremovingtheelsestatement.Thisismadepossiblebyreturningfromthefunctionimmediatelyafterwereceiveanerror.So,insteadofhavingacodesuchasthefollowing:

if(err){

callback(err);

}else{

//codetoexecutewhentherearenoerrors

}

Wecanimprovetheorganizationofourcodebywritingthefollowingoneinstead:

if(err){

returncallback(err);

}

//codetoexecutewhentherearenoerrors

Withthissimpletrick,weimmediatelyhaveareductionofthenestinglevelofourfunctions;itiseasyanddoesn'trequireanycomplexrefactoring.

NoteAcommonmistakewhenexecutingtheoptimizationwejustdescribed,isforgettingtoterminatethefunctionafterthecallbackisinvoked.Fortheerror-handlingscenario,thefollowingcodeisatypicalsourceofdefects:

if(err){

callback(err);

}

//codetoexecutewhentherearenoerrors

Weshouldneverforgetthattheexecutionofourfunctionwillcontinueevenafterweinvokethecallback.Itisthenimportanttoinsertareturninstructiontoblocktheexecutionoftherestofthefunction.Alsonotethatitdoesn'treallymatterwhatoutputisreturnedbythefunction;therealresult(orerror)isproducedasynchronouslyandpassedtothecallback.Thereturnvalueoftheasynchronousfunctionisusuallyignored.Thispropertyallowsustowriteshortcutssuchasthefollowing:

returncallback(...)

Insteadoftheslightlymoreverboseonessuchasthefollowing:

Page 88: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

callback(...)

return;

Asasecondoptimizationforourspider()function,wecantrytoidentifyreusablepiecesofcode.Forexample,thefunctionalitythatwritesagivenstringtoafilecanbeeasilyfactoredoutintoaseparatefunctionasfollows:

functionsaveFile(filename,contents,callback){

mkdirp(path.dirname(filename),function(err){

if(err){

returncallback(err);

}

fs.writeFile(filename,contents,callback);

});

}

Followingthesameprinciple,wecancreateagenericfunctionnameddownload()whichtakesaURLandafilenameasinput,anddownloadstheURLintothegivenfile.Internally,wecanusethesaveFile()functionwecreatedearlier.

functiondownload(url,filename,callback){

console.log('Downloading'+url);

request(url,function(err,response,body){

if(err){

returncallback(err);

}

saveFile(filename,body,function(err){

console.log('Downloadedandsaved:'+url);

if(err){

returncallback(err);

}

callback(null,body);

});

});

}

Forthelaststep,wemodifythespider()function,which,thankstoourchanges,willnowlooklikethefollowing:

functionspider(url,callback){

varfilename=utilities.urlToFilename(url);

fs.exists(filename,function(exists){

if(exists){

returncallback(null,filename,false);

}

download(url,filename,function(err){

if(err){

returncallback(err);

Page 89: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

callback(null,filename,true);

})

});

}

Thefunctionalityandtheinterfaceofthespider()functionremainexactlythesame;whatchangedisonlythewaythecodewasorganized.Byapplyingthebasicprinciplesthatwediscussed,wewereabletodrasticallyreducethenestingofourcodeandatthesametimeincreaseitsreusabilityandtestability.Infact,wecouldthinkofexportingbothsaveFile()anddownload(),sothatwecanreusetheminothermodules.Thisalsoallowsustotesttheirfunctionalitymoreeasily.

Therefactoringwecarriedoutinthissectionclearlydemonstratesthatmostofthetime,allweneedisjustsomedisciplinetomakesurewedonotabuseclosuresandanonymousfunctions.Itworksbrilliantly,requiresminimaleffort,andjustusesplainJavaScript.

SequentialexecutionWenowbeginourexplorationoftheasynchronouscontrolflowpatterns.Wewillstartbyanalyzingthesequentialexecutionflow.

Executingasetoftasksinsequencemeansrunningthemoneatatime,oneaftertheother.Theorderofexecutionmattersandmustbepreserved,becausetheresultofataskinthelistmayaffecttheexecutionofthenext.Thefollowingimageillustratesthisconcept:

Therearedifferentvariationsofthisflow:

Executingasetofknowntasksinsequence,withoutchainingorpropagatingresultsUsingtheoutputofataskastheinputforthenext(alsoknownaschain,pipeline,orwaterfall)Iteratingoveracollectionwhilerunninganasynchronoustaskoneachelement,oneaftertheother

Sequentialexecution,despitebeingtrivialwhenimplementedusingthedirectstyleblockingAPI,isusuallythemaincauseofthecallbackhellproblemwhenusing

Page 90: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

asynchronousCPS.

ExecutingaknownsetoftasksinsequenceWealreadymetasequentialexecutionwhileimplementingthespider()functionintheprevioussection.Byapplyingthesimplerulesthatwestudied,wewereabletoorganizeasetofknowntasksinasequentialexecutionflow.Takingthatcodeasaguideline,wecanthengeneralizethesolutionwiththefollowingpattern:

functiontask1(callback){

asyncOperation(function(){

task2(callback);

});

}

functiontask2(callback){

asyncOperation(function(result){

task3(callback);

});

}

functiontask3(callback){

asyncOperation(function(){

callback();

});

}

task1(function(){

//task1,task2,task3completed

});

Theprecedingpatternshowshoweachtaskinvokesthenextuponthecompletionofagenericasynchronousoperation.Thepatternputstheemphasisonthemodularizationoftasks,showinghowclosuresarenotalwaysnecessarytohandleasynchronouscode.

SequentialiterationThepatternwedescribedearlierworksperfectlyifweknowinadvancewhatandhowmanytasksaretobeexecuted.Thisallowsustohardcodetheinvocationofthenexttaskinthesequence;butwhathappensifwewanttoexecuteanasynchronousoperationforeachiteminacollection?Incasessuchasthis,wecannothardcodethetasksequenceanymore,instead,wehavetobuilditdynamically.

Webspiderversion2

Toshowanexampleofsequentialiteration,let'sintroduceanewfeaturetotheweb

Page 91: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

spiderapplication.Wenowwanttodownloadallthelinkscontainedinawebpagerecursively.Todothat,wearegoingtoextractallthelinksfromthepageandthentriggerourwebspideroneachoneofthemrecursivelyandinsequence.

Thefirststepismodifyingourspider()functionsothatittriggersarecursivedownloadofallthelinksofapagebyusingafunctionnamedspiderLinks(),whichwearegoingtocreateshortly.

Also,insteadofcheckingifthefilealreadyexists,wenowtrytoreadit,andstartspideringitslinks;thisway,weareabletoresumeinterrupteddownloads.Asafinalchange,wemakesurewepropagateanewparameter,nesting,whichhelpsuslimittherecursiondepth.Theresultantcodeisasfollows:

functionspider(url,nesting,callback){

varfilename=utilities.urlToFilename(url);

fs.readFile(filename,'utf8',function(err,body){

if(err){

if(err.code!=='ENOENT'){

returncallback(err);

}

returndownload(url,filename,function(err,body){

if(err){

returncallback(err);

}

spiderLinks(url,body,nesting,callback);

});

}

spiderLinks(url,body,nesting,callback);

});

}

Sequentialcrawlingoflinks

Nowwecancreatethecoreofthisnewversionofourwebspiderapplication,thespiderLinks()function,whichdownloadsallthelinksofanHTMLpageusingasequentialasynchronousiterationalgorithm.Payattentiontothewaywearegoingtodefinethatinthefollowingcodeblock:

functionspiderLinks(currentUrl,body,nesting,callback){

if(nesting===0){

returnprocess.nextTick(callback);

}

varlinks=utilities.getPageLinks(currentUrl,body);//[1]

functioniterate(index){//[2]

if(index===links.length){

returncallback();

}

Page 92: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

spider(links[index],nesting-1,function(err){//[3]

if(err){

returncallback(err);

}

iterate(index+1);

});

}

iterate(0);//[4]

}

Theimportantstepstounderstandfromthisnewfunctionareasfollows:

1. Weobtainthelistofallthelinkscontainedinthepageusingtheutilities.getPageLinks()function.Thisfunctionreturnsonlythelinkspointingtoaninternaldestination(samehostname)

2. Weiterateoverthelinksusingalocalfunctioncallediterate(),whichtakestheindexofthenextlinktoanalyze.Inthisfunction,thefirstthingwedoischeckingiftheindexisequaltothelengthofthelinksarray,inwhichcaseweimmediatelyinvokethecallback()function,asitmeansweprocessedalltheitems.

3. Atthispoint,everythingshouldbereadyforprocessingthelink.Weinvokethespider()functionbydecreasingthenestinglevelandinvokingthenextstepoftheiterationwhentheoperationcompletes.

4. AsthelaststepinthespiderLinks()function,webootstraptheiterationbyinvokingiterate(0).

Thealgorithmwejustpresentedallowsustoiterateoveranarraybyexecutinganasynchronousoperationinsequence,whichinourcaseisthespider()function.

Wecannowtrythisnewversionofthespiderapplicationandwatchitdownloadallthelinksofawebpagerecursively,oneaftertheother.Tointerrupttheprocess,whichcantakeawhileiftherearemanylinks,rememberthatwecanalwaysuseCtrl+C.Ifwethendecidetoresumeit,wecandosobylaunchingthespiderapplicationandprovidingthesameURLweusedforthefirstrun.

NoteNowthatourwebspiderapplicationmightpotentiallytriggerthedownloadofanentirewebsite,pleaseconsiderusingitcarefully.Forexample,donotsetahighnestinglevelorleavethespiderrunningformorethanafewseconds.Itisnotpolitetooverloadaserverwiththousandsofrequests.Insomecircumstancesthiscanalsobeconsideredillegal.Doitresponsibly!

Page 93: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thepattern

ThecodeofthespiderLinks()functionthatweshowedpreviouslyisaclearexampleofhowit'spossibletoiterateoveracollectionwhileapplyinganasynchronousoperation.Wecanalsonoticethatit'sapatternthatcanbeadaptedtoanyothersituationwherewehavetheneedtoiterateasynchronouslyinsequenceovertheelementsofacollectionoringeneraloveralistoftasks.Thispatterncanbegeneralizedasfollows:

functioniterate(index){

if(index===tasks.length){

returnfinish();

}

vartask=tasks[index];

task(function(){

iterate(index+1);

});

}

functionfinish(){

//iterationcompleted

}

iterate(0);

NoteIt'simportanttonoticethatthesetypesofalgorithmsbecomereallyrecursiveiftask()isasynchronousoperation.Insuchacase,thestackwillnotunwindateverycycleandtheremightbeariskofhittingthemaximumcallstacksizelimit.

Thepatternwejustpresentedisverypowerfulasitcanadapttoseveralsituations;forexample,wecanmapthevaluesofanarrayorwecanpasstheresultsofanoperationtothenextoneintheiterationtoimplementareducealgorithm,wecanquittheloopprematurelyifaparticularconditionismet,orwecaneveniterateoveraninfinitenumberofelements.

Wecouldalsochoosetogeneralizethesolutionevenfurtherbywrappingitintoafunctionhavingasignaturesuchasthefollowing:

iterateSeries(collection,iteratorCallback,finalCallback)

Weleavethistoyouasanexercise.

Page 94: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

NotePattern(sequentialiterator):executealistoftasksinsequencebycreatingafunctionnamediterator,whichinvokesthenextavailabletaskinthecollectionandmakessuretoinvokethenextstepoftheiterationwhenthecurrenttaskcompletes.

ParallelexecutionTherearesomesituationswheretheorderoftheexecutionofasetofasynchronoustasksisnotimportantandallwewantisjusttobenotifiedwhenallthoserunningtasksarecompleted.Suchsituationsarebetterhandledusingaparallelexecutionflow,asshowninthefollowingdiagram:

ThismaysoundstrangeifweconsiderthatNode.jsissinglethreaded,butifwerememberwhatwediscussedinChapter1,Node.jsDesignFundamentals,werealizethateventhoughwehavejustonethread,wecanstillachieveconcurrency,thankstothenonblockingnatureofNode.js.Infact,thewordparallelisusedimproperlyinthiscase,asitdoesnotmeanthatthetasksrunsimultaneously,butratherthattheirexecutioniscarriedoutbyanunderlyingnonblockingAPIandinterleavedbytheeventloop.

Asweknow,ataskgivesthecontrolbacktotheeventloopwhenitrequestsanewasynchronousoperationallowingtheeventlooptoexecuteanothertask.Theproperwordtouseforthiskindofflowisconcurrency,butwewillstilluseparallelforsimplicity.

ThefollowingdiagramshowshowtwoasynchronoustaskscanruninparallelinaNode.jsprogram:

Page 95: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Inthepreviousimage,wehaveaMainfunctionthatexecutestwoasynchronoustasks:

1. TheMainfunctiontriggerstheexecutionofTask1andTask2.Asthesetriggeranasynchronousoperation,theyimmediatelyreturnthecontrolbacktotheMainfunction,whichthenreturnsittotheeventloop.

2. WhentheasynchronousoperationofTask1iscompleted,theeventloopgivescontroltoit.WhenTask1completesitsinternalsynchronousprocessingaswell,itnotifiestheMainfunction.

3. WhentheasynchronousoperationtriggeredbyTask2iscompleted,theeventloopinvokesitscallback,givingthecontrolbacktoTask2.AttheendofTask2,theMainfunctionisagainnotified.Atthispoint,theMainfunctionknowsthatbothTask1andTask2arecomplete,soitcancontinueitsexecutionorreturntheresultsoftheoperationstoanothercallback.

Inshort,thismeansthatinNode.js,wecanexecuteinparallelonlyasynchronousoperations,becausetheirconcurrencyishandledinternallybythenonblockingAPIs.InNode.js,synchronous(blocking)operationscannotrunconcurrentlyunlesstheirexecutionisinterleavedwithanasynchronousoperation,ordeferredwith

Page 96: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

setTimeout()orsetImmediate().WewillseethisinmoredetailinChapter6,Recipes.

Webspiderversion3Ourwebspiderapplicationseemslikeaperfectcandidatetoapplytheconceptofparallelexecution.Sofar,ourapplicationisexecutingtherecursivedownloadofthelinkedpagesinasequentialfashion.Wecaneasilyimprovetheperformanceofthisprocessbydownloadingallthelinkedpagesinparallel.

Todothat,wejustneedtomodifythespiderLinks()functiontomakesuretospawnallthespider()tasksatonce,andtheninvokethefinalcallbackonlywhenallofthemhavecompletedtheirexecution.Solet'smodifyourspiderLinks()functionasfollows:

functionspiderLinks(currentUrl,body,nesting,callback){

if(nesting===0){

returnprocess.nextTick(callback);

}

varlinks=utilities.getPageLinks(currentUrl,body);

if(links.length===0){

returnprocess.nextTick(callback);

}

varcompleted=0,errored=false;

functiondone(err){

if(err){

errored=true;

returncallback(err);

}

if(++completed===links.length&&!errored){

returncallback();

}

}

links.forEach(function(link){

spider(link,nesting-1,done);

});

}

Let'sexplainwhatwechanged.Aswementionedearlier,thespider()tasksarenowstartedallatonce.Thisispossiblebysimplyiteratingoverthelinksarrayandstartingeachtaskwithoutwaitingforthepreviousonetofinish:

links.forEach(function(link){

spider(link,nesting-1,done);

});

Page 97: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Then,thetricktomakeourapplicationwaitforallthetaskstocompleteistoprovidethespider()functionwithaspecialcallback,whichwecalldone().Thedone()functionincreasesacounterwhenaspidertaskcompletes.Whenthenumberofcompleteddownloadsreachesthesizeofthelinksarray,thefinalcallbackisinvoked:

functiondone(err){

if(err){

errored=true;

returncallback(err);

}

if(++completed===links.length&&!errored){

callback();

}

}

Withthesechangesinplace,ifwenowtrytorunourspideragainstawebpage,wewillnoticeahugeimprovementinthespeedoftheoverallprocess,aseverydownloadiscarriedoutinparallelwithoutwaitingforthepreviouslinktobeprocessed.

ThepatternAlso,fortheparallelexecutionflow,wecanextractournicelittlepattern,whichwecanadaptandreusefordifferentsituations.Wecanrepresentagenericversionofthepatternwiththefollowingcode:

vartasks=[...];

varcompleted=0;

tasks.forEach(function(task){

task(function(){

if(++completed===tasks.length){

finish();

}

});

});

functionfinish(){

//allthetaskscompleted

}

Withsmallmodifications,wecanadaptthepatterntoaccumulatetheresultsofeachtaskintoacollection,tofilterormaptheelementsofanarray,ortoinvokethefinish()callbackassoonasoneoragivennumberoftaskscomplete(thislastsituationinparticulariscalledcompetitiverace).

Page 98: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

NotePattern(unlimitedparallelexecution):runasetofasynchronoustasksinparallelbyspawningthemallatonce,andthenwaitforallofthemtocompletebycountingthenumberoftimestheircallbacksareinvoked.

FixingraceconditionsinthepresenceofconcurrenttasksRunningasetoftasksinparallelcanbeapainwhenusingblockingI/Oincombinationwithmultiplethreads.However,wehavejustseenthatinNode.jsthisisatotallydifferentstory;runningmultipleasynchronoustasksinparallelisinfactstraightforwardandcheapintermsofresources.ThisisoneofthemostimportantstrengthsforNode.js,becauseitmakesparallelizationacommonpracticeratherthanacomplextechniquetouseonlywhenstrictlynecessary.

AnotherimportantcharacteristicoftheconcurrencymodelofNode.jsisthewaywedealwithtasksynchronizationandraceconditions.Inmultithreadedprogramming,thisisusuallydoneusingconstructssuchaslocks,mutexes,semaphores,andmonitors,anditcanbeoneofthemostcomplexaspectsofparallelizationwhichhasconsiderableimpactonperformancesaswell.InNode.js,weusuallydon'tneedanyfancysynchronizationmechanism,aseverythingrunsonasinglethread!However,thisdoesn'tmeanthatwecan'thaveraceconditions,onthecontrary,theycanbequitecommon.Therootoftheproblemisthedelaybetweentheinvocationofanasynchronousoperationandthenotificationofitsresult.Tomakeaconcreteexample,wecanreferagaintoourwebspiderapplication,inparticular,thelastversionwecreated,whichactuallycontainsaracecondition(canyouspotit?).

Theproblemwearetalkingaboutliesinthespider()function,wherewecheckifafilealreadyexists,beforestartingtodownloadthecorrespondingURL:

functionspider(url,nesting,callback){

varfilename=utilities.urlToFilename(url);

fs.readFile(filename,'utf8',function(err,body){

if(err){

if(err.code!=='ENOENT'){

returncallback(err);

}

returndownload(url,filename,function(err,body){

[...]

Page 99: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theproblemisthat,twospidertasksoperatingonthesameURLmightinvokefs.readFile()onthesamefilebeforeoneofthetwotaskscompletesthedownloadandcreatesafile,causingbothtaskstostartadownload.Thissituationisshowninthefollowingdiagram:

TheprecedingdiagramshowshowTask1andTask2areinterleavedinthesinglethreadofNode.jsandhowanasynchronousoperationcanactuallyintroducearacecondition.Inourcase,thetwospidertasksendupdownloadingthesamefile.

Howcanwefixthat?Theanswerismuchsimplerthanwemightthinkittobe.Infact,allweneedisavariabletomutuallyexcludemultiplespider()tasksrunningonthesameURL.Thiscanbeachievedwithsomecodesuchasthefollowing:

varspidering={};

functionspider(url,nesting,callback){

if(spidering[url]){

returnprocess.nextTick(callback);

}

spidering[url]=true;

[...]

Thefixdoesnotrequiremanycomments.Wesimplyexitthefunctionimmediatelyiftheflagforthegivenurlissetinthespideringmap;otherwise,wesettheflagandcontinuewiththedownload.Forourcase,wedon'tneedtoreleasethelock,aswearenotinterestedindownloadingaURLtwotimes,evenifthespidertasksareexecutedduringtwocompletelydifferentpointsintime.

Raceconditionscancausemanyproblems,evenifweareinasingle-threadedenvironment.Insomecircumstances,theycanleadtodatacorruptionandareusuallyveryhardtodebugbecauseoftheirephemeralnature.So,it'salwaysagoodpracticetodoublecheckforthistypeofsituationwhenrunningtasksinparallel.

LimitedparallelexecutionOften,spawningparalleltaskswithoutcontrolcanleadtoanexcessiveload.Imagine

Page 100: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

havingthousandsoffilestoread,URLstoaccess,ordatabasequeriestoruninparallel.Acommonprobleminsuchsituationsisrunningoutofresources,forexample,byutilizingallthefiledescriptorsavailableforanapplicationwhentryingtoopentoomanyfilesatonce.Inawebapplication,itmayalsocreateavulnerabilitythatisexploitablewithDenialofService(DoS)attacks.Inallsuchsituations,itisagoodideatolimitthenumberoftasksthatcanrunatthesametime.Thisway,wecanaddsomepredictabilitytotheloadofourserverandalsomakesurethatourapplicationwillnotrunoutofresources.Thefollowingdiagramdescribesasituationwherewehavefivetasksthatruninparallelwithaconcurrencylimitof2:

Fromtheprecedingfigure,itshouldbeclearhowouralgorithmwillwork:

1. Initially,wespawnasmanytasksaswecanwithoutexceedingtheconcurrencylimit.

2. Then,everytimeataskiscompleted,wespawnoneormoretasksuntilwedon'treachthelimitagain.

LimitingtheconcurrencyWenowpresentapatterntoexecuteasetofgiventasksinparallelwithlimitedconcurrency:

vartasks=[...];

varconcurrency=2,running=0,completed=0,index=0;

functionnext(){//[1]

while(running<concurrency&&index<tasks.length){

task=tasks[index++];

task(function(){//[2]

if(completed===tasks.length){

returnfinish();

}

completed++,running--;

next();

});

running++;

}

}

next();

Page 101: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

functionfinish(){

//alltasksfinished

}

Thisalgorithmcanbeconsideredamixbetweenasequentialexecutionandaparallelexecution.Infact,wemightnoticesimilaritieswithboththepatternswepresentedearlierinthechapter:

1. Wehaveaniteratorfunction,whichwecallednext(),andthenaninnerloopthatspawnsinparallelasmanytasksaspossiblewhilestayingwithintheconcurrencylimit.

2. Thenextimportantpartisthecallbackwepasstoeachtask,whichchecksifwecompletedallthetasksinthelist.Iftherearestilltaskstorun,itinvokesnext()tospawnanotherbunchoftasks.

Prettysimple,isn'tit?

GloballylimitingtheconcurrencyOurwebspiderapplicationisperfectforapplyingwhatwelearnedaboutlimitingtheconcurrencyofasetoftasks.Infact,toavoidthesituationinwhichwehavethousandsoflinkscrawledatthesametime,wecanenforcealimitontheconcurrencyofthisprocessbyaddingsomepredictabilityonthenumberofconcurrentdownloads.

NoteNode.jsversionsbefore0.11arealreadylimitingthenumberofconcurrentHTTPconnectionsperhostto5.Thiscan,however,bechangedtoaccommodateourneeds.Findoutmoreintheofficialdocsathttp://nodejs.org/docs/v0.10.0/api/http.html#http_agent_maxsockets.StartingfromNode.js0.11,thereisnodefaultlimitonthenumberofconcurrentconnections.

WecouldapplythepatternwejustlearnedtoourspiderLinks()function,butwhatwewouldobtainisonlylimitingtheconcurrencyofasetoflinksfoundwithinonesinglepage.Ifwechose,forexample,aconcurrencyof2,wewouldhaveatmosttwolinksdownloadedinparallelforeachpage.However,aswecandownloadmultiplelinksatonce,eachpagewouldthenspawnanothertwodownloads,causingthegrandtotalofdownloadoperationstogrowexponentiallyanyway.

Page 102: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Queuestotherescue

Whatwereallywantthen,istolimittheglobalnumberofdownloadoperationswecanhaverunninginparallel.Wecouldslightlymodifythepatternshowedbefore,butweprefertoleavethisasanexerciseforyou,aswewanttotakethisopportunitytointroduceanothermechanism,whichmakesuseofqueues,tolimittheconcurrencyofmultipletasks.Let'sseehowthisworks.

WearenowgoingtoimplementasimpleclassnamedTaskQueue,whichwillcombineaqueuewiththealgorithmwepresentedbefore.Let'screateanewmodulenamedtaskQueue.js,andlet'sstartbydefiningitsconstructor:

functionTaskQueue(concurrency){

this.concurrency=concurrency;

this.running=0;

this.queue=[];

}

Theconstructortakesasinputonlytheconcurrencylimit,butbesidesthat,itinitializesotherinstancevariablesthatwearegoingtoneedlater.

Next,weimplementthepushTask()methodasfollows:

TaskQueue.prototype.pushTask=function(task,callback){

this.queue.push(task);

this.next();

}

Thepreviousfunctionsimplyaddsanewtasktothequeueandthenbootstrapstheexecutionoftheworkerbyinvokingthis.next().

Let'sseehowthenext()methodlooks;itsroleistospawnasetoftasksfromthequeueensuringthatitdoesnotexceedtheconcurrencylimit:

TaskQueue.prototype.next=function(){

varself=this;

while(self.running<self.concurrency&&self.queue.length)

{

vartask=self.queue.shift();

task(function(err){

self.running--;

self.next();

});

self.running++;

}

}

Page 103: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Wemightnoticethatthismethodhassomesimilaritieswiththepatternthatlimitstheconcurrencywepresentedearlier.Itessentiallystartsasmanytasksfromthequeueaspossible,withoutexceedingtheconcurrencylimit.Wheneachtaskiscomplete,itupdatesthecountofrunningtasksandthenstartsanotherroundoftasksbyinvokingnext()again.TheinterestingpropertyoftheTaskQueueclassisthatitallowsustodynamicallyaddnewitemstothequeue.Theotheradvantageisthatnowwehaveacentralentityresponsibleforthelimitationoftheconcurrencyofourtasks,whichcanbesharedacrossalltheinstancesofafunction'sexecution.Inourcase,it'sthespider()function,aswewillseeinamoment.

Webspiderversion4

Nowthatwehaveourgenericqueuetoexecutetasksinalimitedparallelflow,let'suseitstraightawayinourwebspiderapplication.Let'sfirstloadthenewdependencyandcreateanewinstanceoftheTaskQueueclass,bysettingtheconcurrencylimitto2:

varTaskQueue=require('./taskQueue');

vardownloadQueue=newTaskQueue(2);

Next,weneedtoupdatethespiderLinks()functionsothatitcanusethenewlycreateddownloadQueue:

functionspiderLinks(currentUrl,body,nesting,callback){

if(nesting===0){

returnprocess.nextTick(callback);

}

varlinks=utilities.getPageLinks(currentUrl,body);

if(links.length===0){

returnprocess.nextTick(callback);

}

varcompleted=0,errored=false;

links.forEach(function(link){

downloadQueue.pushTask(function(done){

spider(link,nesting-1,function(err){

if(err){

errored=true;

returncallback(err);

}

if(++completed===links.length&&!errored){

callback();

}

done();

});

});

});

}

Page 104: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thisnewimplementationofthefunctionisextremelyeasy,andit'sverysimilartothealgorithmforunlimitedparallelexecution,whichwepresentedearlierinthechapter.ThisisbecausewearedelegatingtheconcurrencycontroltotheTaskQueueobject,andtheonlythingwehavetodoistocheckwhenallourtasksarecomplete.Theonlyinterestingpartintheprecedingcodeishowwedefinedourtasks.

Werunthespider()functionbyprovidingacustomcallback.Inthecallback,wecheckifallthetasksrelativetothisexecutionofthespiderLinks()functionarecompleted.Whenthisconditionistrue,weinvokethefinalcallbackofthespiderLinks()function.Attheendofourtask,weinvokethedone()callbacksothatthequeuecancontinueitsexecution.

Afterwehaveappliedthesesmallchanges,wecannowtrytorunthespidermoduleagain.Thistime,weshouldnoticethatnomorethantwodownloadswillbeactiveatthesametime.

Page 105: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheasynclibraryIfwetakealookforamomentateverycontrolflowpatternwehaveanalyzedsofar,wecanseethattheycouldbeusedasabasetobuildreusableandmoregenericsolutions.Forexample,wecouldwraptheunlimitedparallelexecutionalgorithmintoafunctionwhichacceptsalistoftasks,runstheminparallel,andinvokesthegivencallbackwhenallofthemarecomplete.Thiswayofwrappingcontrolflowalgorithmsintoreusablefunctionscanleadtoamoredeclarativeandexpressivewaytodefineasynchronouscontrolflows,andthat'sexactlywhatasync(https://npmjs.org/package/async)does.Theasynclibraryisaverypopularsolution,inNode.jsandJavaScriptingeneral,todealwithasynchronouscode.Itoffersasetoffunctionsthatgreatlysimplifytheexecutionofasetoftasksindifferentconfigurationsanditalsoprovidesusefulhelpersfordealingwithcollectionsasynchronously.Eventhoughthereareseveralotherlibrarieswithasimilargoal,asyncisadefactostandardinNode.jsduetoitspopularity.

Let'stryitstraightawaytodemonstrateitscapabilities.

SequentialexecutionTheasynclibrarycanhelpusimmenselywhenimplementingcomplexasynchronouscontrolflows,butonedifficultywithitischoosingtherighthelperfortheproblemathand.Forexample,forthecaseofthesequentialexecutionflow,therearearound20differentfunctionstochoosefrom,including:eachSeries(),mapSeries(),filterSeries(),rejectSeries(),reduce(),reduceRight(),detectSeries(),concatSeries(),series(),whilst(),doWhilst(),until(),doUntil(),forever(),waterfall(),compose(),seq(),applyEachSeries(),iterator(),andtimesSeries().

Choosingtherightfunctionisanimportantstepinwritingmorecompactandreadablecode,butthisalsorequiressomeexperienceandpractice.Inourexamples,wearegoingtocoveronlyafewofthesesituations,buttheywillstillprovideasolidbasetounderstandandefficientlyusetherestofthelibrary.

Now,toshowinpracticehowasyncworks,wearegoingtoadaptourwebspiderapplication.Let'sstartdirectlywithversion2,theonethatdownloadsallthelinksrecursivelyinsequence.

However,firstlet'smakesureweinstalltheasynclibraryintoourcurrentproject:

npminstallasync

Page 106: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thenweneedtoloadthenewdependencyfromthespider.jsmodule:

varasync=require('async');

SequentialexecutionofaknownsetoftasksLet'smodifythedownload()functionfirst.Aswehavealreadyseen,itexecutesthefollowingthreetasksinsequence:

1. DownloadthecontentsofaURL.2. Createanewdirectoryifitdoesn'texistyet.3. SavethecontentsoftheURLintoafile.

Theidealfunctiontousewiththisflowisdefinitelyasync.series(),whichhasthefollowingsignature:

async.series(tasks,[callback])

Ittakesalistoftasksandacallbackfunctionthatisinvokedwhenallthetaskshavebeencompleted.Eachtaskisjustafunctionthatacceptsacallbackfunction,whichmustbeinvokedwhenthetaskcompletesitsexecution:

functiontask(callback){}

ThenicethingaboutasyncisthatitusesthesamecallbackconventionsofNode.js,anditautomaticallyhandleserrorpropagation.So,ifanyofthetasksinvokeitscallbackwithanerror,asyncwillskiptheremainingtasksinthelistandjumpdirectlytothefinalcallback.

Withthisinmind,let'sseehowthedownload()functionwouldchangebyusingasync:

functiondownload(url,filename,callback){

console.log('Downloading'+url);

varbody;

async.series([

function(callback){//[1]

request(url,function(err,response,resBody){

if(err){

returncallback(err);

}

body=resBody;

callback();

});

},

mkdirp.bind(null,path.dirname(filename)),//[2]

Page 107: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

function(callback){//[3]

fs.writeFile(filename,body,callback);

}

],function(err){//[4]

console.log('Downloadedandsaved:'+url);

if(err){

returncallback(err);

}

callback(null,body);

});

}

Ifwerememberthecallbackhellversionofthiscode,wewillsurelyappreciatethewayasyncallowsustoorganizeourtasks.Thereisnoneedtonestcallbacksanymore,aswejusthavetoprovideaflatlistoftasks,usuallyoneforeachasynchronousoperation,whichasyncwillthenexecuteinsequence.Thisishowwedefineeachtask:

1. ThefirsttaskinvolvesthedownloadoftheURL.Also,wesavetheresponsebodyintoaclosurevariable(body)sothatitcanbesharedwiththeothertasks.

2. Inthesecondtask,wewanttocreatethedirectorythatwillholdthedownloadedpage.Wedothisbyperformingapartialapplicationofthemkdirp()function,bindingthepathofthedirectorytobecreated.Thisway,wecansaveafewlinesofcodeandincreaseitsreadability.

3. Atlast,wewritethecontentsofthedownloadedURLtoafile.Inthiscase,wecouldnotperformapartialapplication(aswedidforthesecondtask),becausethevariable,body,isonlyavailableafterthefirsttaskintheseriescompletes.However,wecanstillsavesomelinesofcodebyexploitingtheautomaticerrormanagementofasyncbysimplypassingthecallbackofthetaskdirectlytothefs.writeFile()function.

4. Afterallthetasksarecomplete,thefinalcallbackofasync.series()isinvoked.Inourcase,wearesimplydoingsomeerrormanagementandthenreturningthebodyvariabletocallbackofthedownload()function.

Forthisspecificsituation,apossiblealternativetoasync.series()wouldbeasync.waterfall(),whichstillexecutesthetasksinsequencebutinaddition,italsoprovidestheoutputofeachtaskasinputtothenext.Inoursituation,wecouldusethisfeaturetopropagatethebodyvariableuntiltheendofoursequence.Asanexercise,youcantrytoimplementthesamefunctionusingthewaterfallflowandthentakealookatthedifferences.

SequentialiterationWealreadysawfromthepreviousparagraphhowwecanexecuteasetofknowntasksinsequence;weusedasync.series()todothat.Wecouldusethesame

Page 108: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

functionalitytoimplementthespiderLinks()functionofourwebspiderversion2,howeverasyncoffersamoreappropriatehelperforthespecificsituationinwhichwehavetoiterateoveracollection;thishelperisasync.eachSeries().Let'suseitthentoreimplementourspiderLinks()function(version2,downloadinseries)asfollows:

functionspiderLinks(currentUrl,body,nesting,callback){

if(nesting===0){

returnprocess.nextTick(callback);

}

varlinks=utilities.getPageLinks(currentUrl,body);

if(links.length===0){

returnprocess.nextTick(callback);

}

async.eachSeries(links,function(link,callback){

spider(link,nesting-1,callback);

},callback);

}

Ifwecomparetheprecedingcode,whichusesasync,withthecodeofthesamefunctionimplementedwithplainJavaScriptpatterns,wewillnoticethebigadvantagethatasyncgivesusintermsofcodeorganizationandreadability.

ParallelexecutionTheasynclibrarydoesn'tlackfunctionstohandleparallelflows,amongthemwecanfindeach(),map(),filter(),reject(),detect(),some(),every(),concat(),parallel(),applyEach(),andtimes().Theyfollowthesamelogicofthefunctionswehavealreadyseenforthesequentialexecution,withthedifferencethatthetasksprovidedareexecutedinparallel.

Todemonstratethat,wecantrytoapplyoneofthesefunctionstoimplementversion3ofourwebspiderapplication,theoneperformingthedownloadsusinganunlimitedparallelflow.

IfwerememberthecodeweusedearliertoimplementthesequentialversionofthespiderLinks()function,adaptingittomakeitworkinparallelisatrivialtask:

functionspiderLinks(currentUrl,body,nesting,callback){

[...]

async.each(links,function(link,callback){

spider(link,nesting-1,callback);

},callback);

}

Page 109: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thefunctionisexactlythesameonethatweusedforthesequentialdownload,butthistimeweusedasync.each()insteadofasync.eachSeries().Thisclearlydemonstratesthepowerofabstractingtheasynchronousflowwithalibrarysuchasasync.Thecodeisnotboundtoaparticularexecutionflowanymore;thereisnocodespecificallywrittenforthat,mostofitisjustapplicationlogic.

LimitedparallelexecutionIfyouarewonderingifasynccanalsobeusedtolimittheconcurrencyofparalleltasks,theanswerisyes,itcan!Wehaveafewfunctionswecanuseforthat,namely,eachLimit(),mapLimit(),parallelLimit(),queue(),andcargo().

Let'strytoexploitoneofthemtoimplementversion4ofthewebspiderapplication,theoneexecutingthedownloadofthelinksinparallelwithlimitedconcurrency.Fortunately,asynchasasync.queue(),whichworksinasimilarwayastheTaskQueueclasswecreatedearlierinthechapter.Theasync.queue()functioncreatesanewqueue,whichusesaworker()functiontoexecuteasetoftaskswithaspecifiedconcurrencylimit:

varq=async.queue(worker,concurrency);

Theworker()functionreceives,asinput,thetasktorunandacallbackfunctiontoinvoke,whenthetaskcompletes:

functionworker(task,callback)

Weshouldnoticethattaskinthiscasecanbeanything,notjustafunction.Infact,it'stheresponsibilityoftheworkertohandleataskinthemostappropriateway.Newtaskscanbeaddedtothequeuebyusingq.push(task,callback).Thecallbackassociatedtoataskhastobeinvokedbytheworkerafterthetaskhasbeenprocessed.

Now,let'smodifyourcodeagaintoimplementaparallelgloballylimitedexecutionflow,usingasync.queue().Firstofall,weneedtocreateanewqueue:

vardownloadQueue=async.queue(function(taskData,callback){

spider(taskData.link,taskData.nesting-1,callback);

},2);

Thecodeisreallystraightforward.Wearejustcreatinganewqueuewithaconcurrencylimitof2,havingaworkerthatsimplyinvokesourspider()functionwiththedataassociatedwithatask.Next,weimplementthespiderLinks()function:

Page 110: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

functionspiderLinks(currentUrl,body,nesting,callback){

if(nesting===0){

returnprocess.nextTick(callback);

}

varlinks=utilities.getPageLinks(currentUrl,body);

if(links.length===0){

returnprocess.nextTick(callback);

}

varcompleted=0,errored=false;

links.forEach(function(link){

vartaskData={link:link,nesting:nesting};

downloadQueue.push(taskData,function(err){

if(err){

errored=true;

returncallback(err);

}

if(++completed===links.length&&!errored){

callback();

}

});

});

}

Theprecedingcodeshouldlookveryfamiliar,asit'salmostthesameastheoneweusedtoimplementthesameflowusingtheTaskQueueobject.Also,inthiscase,theimportantparttoanalyzeiswherewepushanewtaskintothequeue.Atthatpoint,weensurethatwepassacallbackthatenablesustocheckifallthedownloadtasksforthecurrentpagearecompleted,andeventuallyinvokethefinalcallback.

Thankstoasync.queue(),wecouldeasilyreplicatethefunctionalityofourTaskQueueobject,againdemonstratingthatwithasync,wecanreallyavoidwritingasynchronouscontrolflowpatternsfromscratch,reducingoureffortsandsavingpreciouslinesofcode.

Page 111: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

PromisesWealreadymentionedatthebeginningofthechapterthatCPSisnottheonlywaytowriteasynchronouscode.Infact,theJavaScriptecosystemprovidesalternativestothetraditionalcallbackpattern.Oneoftheseinparticularisreceivingalotofmomentum,especiallynowthatitisgoingtobepartoftheECMAScript6specification(alsoknownasES6orHarmony),theupcomingversionoftheJavaScriptlanguage.Wearetalking,ofcourse,aboutpromises,andinparticularaboutthoseimplementationsthatfollowthePromises/A+specification(https://promisesaplus.com).

NoteThereareotherpromisesimplementationsthatarenotcomplianttothePromises/A+specification,andamongthose,themostpopularistheoneprovidedbyJQuery.Mostofthetopicsdiscussedinthissectiondonotapplytothosenoncompliantimplementations.

Whatisapromise?Inverysimpleterms,promisesareanabstractionthatallowanasynchronousfunctiontoreturnanobjectcalledapromise,whichrepresentstheeventualresultoftheoperation.Inthepromisesjargon,wesaythatapromiseispendingwhentheasynchronousoperationisnotyetcomplete,it'sfulfilledwhentheoperationsuccessfullycompletes,andrejectedwhentheoperationterminateswithanerror.Onceapromiseiseitherfulfilledorrejected,it'sconsideredsettled.

Toreceivethefulfillmentvalueortheerror(reason)associatedwiththerejection,wecanusethethen()methodofthepromise.Thefollowingisitssignature:

promise.then([onFulfilled],[onRejected])

WhereonFulfilled()isafunctionthatwilleventuallyreceivethefulfillmentvalueofthepromise,andonRejected()isanotherfunctionthatwillreceivethereasonoftherejection(ifany).Bothfunctionsareoptional.

TohaveanideaofhowPromisescantransformourcode,let'sconsiderthefollowing:

asyncOperation(arg,function(err,result){

if(err){

Page 112: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//handleerror

}

//dostuffwithresult

});

PromisesallowtotransformthistypicalCPScodeintoabetterstructuredandmoreelegantcode,suchasthefollowing:

asyncOperation(arg)

.then(function(result){

//dostuffwithresult

},function(err){

//handleerror

});

Onecrucialpropertyofthethen()methodisthatitsynchronouslyreturnsanotherpromise.IfanyoftheonFulfilled()oronRejected()functionsreturnavaluex,thepromisereturnedbythethen()methodwillbeasfollows:

FulfillwithxifxisavalueFulfillwiththefulfillmentvalueofxifxisapromiseorathenableRejectwiththeeventualrejectionreasonofxifxisapromiseorathenable

NoteAthenableisapromise-likeobjectwithathen()method.Thistermisusedtoindicateapromisethatisforeigntotheparticularpromiseimplementationinuse.

Thisfeatureallowsustobuildchainsofpromises,allowingeasyaggregationandarrangementofasynchronousoperationsinseveralconfigurations.Also,ifwedon'tspecifyanonFulfilled()oronRejected()handler,thefulfillmentvalueorrejectionreasonsareautomaticallyforwardedtothenextpromiseinthechain.Thisallowsus,forexample,toautomaticallypropagateerrorsacrossthewholechainuntilcaughtbyanonRejected()handler.Withapromisechain,sequentialexecutionoftaskssuddenlybecomesatrivialoperation:

asyncOperation(arg)

.then(function(result1){

//returnsanotherpromise

returnasyncOperation(arg2);

})

.then(function(result2){

//returnsavalue

return'done';

})

Page 113: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

.then(undefined,function(err){

//anyerrorinthechainiscaughthere

});

Thefollowingdiagramprovidesanotherperspectiveonhowapromisechainworks:

AnotherimportantpropertyofpromisesisthattheonFulfilled()andonRejected()functionsareguaranteedtobeinvokedasynchronously,evenifweresolvethepromisesynchronouslywithavalue,aswedidintheprecedingexample,wherewereturnedthestringdoneinthelastthen()functionofthechain.ThisbehaviorshieldsourcodeagainstallthosesituationswherewecouldunintentionallyreleaseZalgo,makingourasynchronouscodemoreconsistentandrobustwithnoeffort.

Nowcomesthebestpart.Ifanexceptionisthrown(usingthethrowstatement)fromtheonFulfilled()oronRejected()handler,thepromisereturnedbythethen()methodwillautomaticallyrejectwiththeexceptionastherejectionreason.ThisisatremendousadvantageoverCPS,asitmeansthatwithpromises,exceptionswillpropagateautomaticallyacrossthechain,andthatthethrowstatementisnotanenemyanymore.

NoteForadetaileddescriptionofthePromises/A+specification,youcanrefertotheofficialwebsitehttp://promises-aplus.github.io/promises-spec/.

Promises/A+implementationsInNode.js,andingeneralinJavaScript,thereareseverallibrariesimplementingthePromises/A+specifications.Thefollowingarethemostpopular:

Bluebird(https://npmjs.org/package/bluebird)

Page 114: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Q(https://npmjs.org/package/q)RSVP(https://npmjs.org/package/rsvp)Vow(https://npmjs.org/package/vow)When.js(https://npmjs.org/package/when)ES6Promises

WhatreallydifferentiatesthemistheadditionalsetoffeaturestheyprovideontopofthePromises/A+standard.Thestandard,infact,definesonlythebehaviorofthethen()methodandthepromiseresolutionprocedure,butitdoesnotspecifyotherfunctionalities,forexample,howapromiseiscreatedfromacallback-basedasynchronousfunction.

Inourexamples,wewilltrytousethesetofAPIimplementedbytheES6promises,astheywillbenativelyavailableinJavaScriptwithoutthesupportofanyexternallibrary.Luckily,theprecedinglistoflibrariesaregraduallyadaptingtosupporttheES6API,sousinganyoneofthemshouldnotforceusintoanystrongimplementationlock-inasfarasweuseonlythefeaturesetoftheES6standard.

NotePleasebearinmindthattheECMAScript6specificationisstilladraftatthetimeofwriting.Sotheremightbesomedifferencesfromwhatwillbethefinalstandard.Also,considerthatatthetimeofwriting,theversionofV8usedbyNode.jsstilldoesnotsupportpromisesnatively.So,forourexamples,wearegoingtouseoneoftheprecedinglistedimplementations,namely,Bluebird.Ofcourse,wewilluseonlythepartofitsAPIthatiscompatiblewithES6promises.

Forreference,hereisthelistoftheAPIscurrentlyprovidedbytheES6promises:

Constructor(newPromise(function(resolve,reject){})):Thiscreatesanewpromisethatfulfillsorrejectsbasedonthebehaviorofthefunctionpassedasanargument.Theargumentsoftheconstructorareexplainedasfollows:

resolve(obj):Thiswillresolvethepromisewithafulfillmentvalue,whichwillbeobjifobjisavalue.Itwillbethefulfillmentvalueofobjifobjisapromiseorathenable.reject(err):Thisrejectsthepromisewiththereasonerr.ItisaconventiontohaveerrtobeaninstanceofError.

StaticmethodsofthePromiseobject:

Promise.resolve(obj):Thiscreatesanewpromisefromathenableora

Page 115: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

value.Promise.reject(err):Thiscreatesapromisethatrejectswitherrasthereason.Promise.all(array):Thiscreatesapromisethatfulfillswithanarrayoffulfillmentvalueswheneveryiteminthearrayfulfills,andrejectswiththefirstrejectionreasonifanyitemrejects.Eachiteminthearraycanbeapromise,agenericthenable,oravalue.

MethodsofaPromiseinstance:

promise.then(onFulfilled,onRejected):Thisistheessentialmethodofapromise.ItsbehavioriscompatiblewiththePromises/A+standardwedescribedbefore.promise.catch(onRejected):Thisisjustasyntacticsugarforpromise.then(undefined,onRejected).

NoteItisworthmentioningthatsomepromiseimplementationsofferanothermechanismtocreatenewpromises;thisiscalleddeferreds.Wearenotgoingtodescribeithere,becauseit'snotpartoftheES6standard,butifyouwanttoknowmore,youcanreadthedocumentationforQ(https://github.com/kriskowal/q#using-deferreds)orWhen.js(https://github.com/cujojs/when/wiki/Deferred).

PromisifyingaNode.jsstylefunctionInNode.js,andingeneralinJavaScript,thereareonlyafewlibrariessupportingpromisesout-of-the-box.Mostofthetime,infact,wehavetoconvertatypicalcallback-basedfunctionintoonethatreturnsapromise;thisisalsoknownaspromisification.

Fortunately,thecallbackconventionsusedinNode.jsallowustocreateareusablefunctionthatwecanutilizetopromisifyanyNode.jsstyleAPI.WecandothiseasilybyusingtheconstructorofthePromiseobject.Let'sthencreateanewfunctioncalledpromisify()andincludeitintotheutilities.jsmodule(sowecanuseitlaterinourwebspiderapplication):

varPromise=require('bluebird');

module.exports.promisify=function(callbackBasedApi){

returnfunctionpromisified(){

Page 116: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varargs=[].slice.call(arguments);

returnnewPromise(function(resolve,reject){//[1]

args.push(function(err,result){//[2]

if(err){

returnreject(err);//[3]

}

if(arguments.length<=2){//[4]

resolve(result);

}else{

resolve([].slice.call(arguments,1));

}

});

callbackBasedApi.apply(null,args);//[5]

});

}

};

Theprecedingfunctionreturnsanotherfunctioncalledpromisified(),whichrepresentsthepromisifiedversionofthecallbackBasedApigivenintheinput.Thisishowitworks:

1. Thepromisified()functioncreatesanewpromiseusingthePromiseconstructorandimmediatelyreturnsitbacktothecaller.

2. InthefunctionpassedtothePromiseconstructor,wemakesuretopasstocallbackBasedApi,aspecialcallback.Asweknowthatthecallbackalwayscomeslast,wesimplyappendittotheargumentlist(args)providedtothepromisified()function.

3. Inthespecialcallback,ifwereceiveanerror,weimmediatelyrejectthepromise.4. Ifnoerrorisreceived,weresolvethepromisewithavalueoranarrayofvalues,

dependingonhowmanyresultsarepassedtothecallback.5. Atlast,wesimplyinvokethecallbackBasedApiwiththelistofargumentswe

havebuilt.

NoteMostofthepromiseimplementationsalreadyprovide,out-of-the-box,somesortofhelpertoconvertaNode.jsstyleAPItoonereturningapromise.Forexample,QhasQ.denodeify()andQ.nbind(),BluebirdhasPromise.promisify(),andWhen.jshasnode.lift().

SequentialexecutionAfteralittlebitofnecessarytheory,wearenowreadytoconvertourwebspiderapplicationtousepromises.Let'sstartdirectlyfromversion2,theonedownloading

Page 117: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

insequencethelinksofawebpage.

Inthespider.jsmodule,theveryfirststeprequiredistoloadourpromisesimplementation(wewilluseitlater)andpromisifythecallback-basedfunctionsthatweplantouse:

varPromise=require('bluebird');

varutilities=require('./utilities');

varrequest=utilities.promisify(require('request'));

varmkdirp=utilities.promisify(require('mkdirp'));

varfs=require('fs');

varreadFile=utilities.promisify(fs.readFile);

varwriteFile=utilities.promisify(fs.writeFile);

Now,wecanstartconvertingthedownload()function:

functiondownload(url,filename){

console.log('Downloading'+url);

varbody;

returnrequest(url)

.then(function(results){

body=results[1];

returnmkdirp(path.dirname(filename));

})

.then(function(){

returnwriteFile(filename,body);

})

.then(function(){

console.log('Downloadedandsaved:'+url);

returnbody;

});

}

Wecanseestraightawayhowelegantsomesequentialcodeimplementedwithpromisesis;wesimplyhaveanintuitivechainofthen()functions.Thefinalreturnvalueofthedownload()functionisthepromisereturnedbythelastthen()invocationinthechain.Thismakessurethatthecallerreceivesapromisethatfulfillswithbodyonlyafteralltheoperations(request,mkdirp,writeFile)havecompleted.

Next,it'stheturnofthespider()function:

functionspider(url,nesting){

varfilename=utilities.urlToFilename(url);

returnreadFile(filename,'utf8')

.then(

function(body){

returnspiderLinks(url,body,nesting);

},

Page 118: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

function(err){

if(err.code!=='ENOENT'){

throwerr;

}

returndownload(url,filename)

.then(function(body){

returnspiderLinks(url,body,nesting);

});

}

);

}

TheimportantthingtonoticehereisthatwealsoregisteredanonRejected()functionforthepromisereturnedbyreadFile(),tohandlethecasewhenapagewasnotalreadydownloaded(filedoesnotexist).Also,it'sinterestingtoseehowwewereabletousethrowtopropagatetheerrorfromwithinthehandler.

Nowthatwehaveconvertedourspider()functionaswell,wecanmodifyitsmaininvocationasfollows:

spider(process.argv[2],1)

.then(function(){

console.log('Downloadcomplete');

})

.catch(function(err){

console.log(err);

});

Notehowweused,forthefirsttime,thesyntacticsugarcatchtohandleanyerrorsituationoriginatedfromthespider()function.Ifwelookagainatallthecodewehavewrittensofarinthissection,wewouldbepleasantlysurprisedbythefactthatwedidn'tincludeanyerrorpropagationlogiclikewewouldbeforcedtodobyusingcallbacks.Thisisclearlyanenormousadvantage,asitgreatlyreducestheboilerplateinourcodeandthechancesofmissinganyasynchronouserror.

Now,theonlymissingbittocompletetheversion2ofourwebspiderapplicationisthespiderLinks()function,whichwearegoingtoseeinamoment.

SequentialiterationThewebspidercodesofarwasmainlyanoverviewofwhatpromisesareandhowtheyareused,demonstratinghowsimpleandelegantitistoimplementasequentialexecutionflowusingpromises.However,thecodeweconsideredsofarinvolvesonlytheexecutionofaknownsetofasynchronousoperations.So,themissingpiecethatwillcompleteourexplorationofsequentialexecutionflowsistoseehowwecanimplementaniterationusingpromises.Again,thespiderLinks()functionofweb

Page 119: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

spiderversion2isaperfectexampletoshowthat.

Let'saddthemissingpiecetothecodewewrotesofar:

functionspiderLinks(currentUrl,body,nesting){

varpromise=Promise.resolve();//[1]

if(nesting===0){

returnpromise;

}

varlinks=utilities.getPageLinks(currentUrl,body);

links.forEach(function(link){//[2]

promise=promise.then(function(){

returnspider(link,nesting-1);

});

});

returnpromise;

}

Toiterateasynchronouslyoverallthelinksofawebpage,wehadtodynamicallybuildachainofpromises:

1. First,wedefinedan"empty"promise,resolvingtoundefined.Thispromiseisjustusedasastartingpointtobuildourchain.

2. Then,inaloop,weupdatethepromisevariablewithanewpromiseobtainedbyinvokingthen()onthepreviouspromiseinthechain.Thisisactuallyourasynchronousiterationpatternusingpromises.

Thisway,attheendoftheloop,thepromisevariablewillcontainthepromiseofthelastthen()invocationintheloop,soitwillresolveonlywhenallthepromisesinthechainhavebeenresolved.

Withthis,wecompletelyconvertedourwebspiderversion2tousepromises.Weshouldnowbeabletotryitoutagain.

Sequentialiteration–thepatternToconcludethissectiononsequentialexecution,let'sextractthepatterntoiterateoverasetofpromisesinsequence:

vartasks=[...]

varpromise=Promise.resolve();

tasks.forEach(function(task){

promise=promise.then(function(){

returntask();

});

});

Page 120: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

promise.then(function(){

//Alltaskscompleted

});

AnalternativetousingtheforEach()loopistousereduce(),allowinganevenmorecompactcode:

vartasks=[...]

varpromise=tasks.reduce(function(prev,task){

returnprev.then(function(){

returntask();

});

},Promise.resolve());

promise.then(function(){

//Alltaskscompleted

});

Asalways,withsimpleadaptationsofthispattern,wecouldcollectallthetasks'resultsinanarray;wecouldimplementamappingalgorithm,orbuildafilter,andsoon.

NotePattern(sequentialiterationwithpromises):dynamicallybuildsachainofpromisesusingaloop.

ParallelexecutionAnotherexecutionflowthatbecomestrivialwithpromisesistheparallelexecutionflow.Infact,allthatweneedtodoisusethebuilt-inPromise.all()helperthatcreatesanotherpromise,whichfulfillsonlywhenallthepromisesreceivedinaninputarefulfilled.That'sessentiallyaparallelexecutionbecausenoorderbetweenthevariouspromises'resolutionsisenforced.

Todemonstratethis,let'sconsiderversion3ofourwebspiderapplication,theonedownloadingallthelinksofapageinparallel.Let'supdatethespiderLinks()functionagaintoimplementaparallelflow,usingpromises:

functionspiderLinks(currentUrl,body,nesting){

if(nesting===0){

returnPromise.resolve();

}

Page 121: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varlinks=utilities.getPageLinks(currentUrl,body);

varpromises=links.map(function(link){

returnspider(link,nesting-1);

});

returnPromise.all(promises);

}

Trivially,thepatternconsistsinstartingthespider()tasksallatonceintotheelements.map()loop,whichalsocollectsalltheirpromises.Thistime,intheloop,wearenotwaitingforthepreviousdownloadtocompletebeforestartinganewone,allthedownloadtasksarestartedintheloopatonce,oneaftertheother.Afterwards,weleveragedthePromise.all()method,whichreturnsanewpromisethatwillbefulfilledwhenallthepromisesinthearrayarefulfilled.Inotherwords,itfulfillswhenallthedownloadtaskshavecompleted;exactlywhatwewanted.

LimitedparallelexecutionUnfortunately,theES6PromiseAPIdoesnotprovideawaytoimplementalimitedparallelcontrolflownatively,butwecanalwaysrelyonwhatwelearnedaboutlimitingtheconcurrencywithplainJavaScript.Infact,thepatternweimplementedinsidetheTaskQueueclasscanbeeasilyadaptedtosupporttasksthatreturnapromise.Thiscanbedonetriviallybymodifyingthenext()method:

TaskQueue.prototype.next=function(){

varself=this;

while(self.running<self.concurrency&&self.queue.length)

{

vartask=self.queue.shift();

task().then(function(){

self.running--;

self.next();

});

self.running++;

}

}

Sonow,insteadofhandlingthetaskwithacallback,wesimplyinvokethen()onthepromiseitreturns.TherestofthecodeispracticallyidenticaltotheoldversionofTaskQueue.

Now,wecangobacktothespider.jsmodule,modifyingittosupportournewversionoftheTaskQueueclass.First,wemakesuretodefineanewinstanceofTaskQueue:

varTaskQueue=require('./taskQueue');

Page 122: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

vardownloadQueue=newTaskQueue(2);

Then,it'stheturnofthespiderLinks()functionagain.Thechangehereisalsoprettystraightforward:

functionspiderLinks(currentUrl,body,nesting){

if(nesting===0){

returnPromise.resolve();

}

varlinks=utilities.getPageLinks(currentUrl,body);

//weneedthefollowingbecausethePromisewecreatenext

//willneversettleiftherearenotaskstoprocess

if(links.length===0){

returnPromise.resolve();

}

returnnewPromise(function(resolve,reject){//[1]

varcompleted=0;

links.forEach(function(link){

vartask=function(){//[2]

returnspider(link,nesting-1)

.then(function(){

if(++completed===links.length){

resolve();

}

})

.catch(reject);

};

downloadQueue.pushTask(task);

});

});

}

Thereareacoupleofthingsintheprecedingcodethatmeritourattention:

1. First,weneededtoreturnanewpromisecreatedusingthePromiseconstructor.Aswewillsee,thisenablesustoresolvethepromisemanually,whenallofthetasksinthequeuearecompleted.

2. Second,weshouldlookathowwedefinedthetask.WhatwedidisattachanonFulfilled()callbacktothepromisereturnedbyspider(),sowecouldcountthenumberofdownloadtaskscompleted.Whentheamountofcompleteddownloadsmatchesthenumberoflinksinthecurrentpage,weknowthatwearedoneprocessing,sowecaninvoketheresolve()functionoftheouterpromise.

Note

Page 123: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThePromises/A+specificationstatesthattheonFulfilled()andonRejected()callbacksofthethen()methodhavetobeinvokedonlyonceandexclusively(onlyoneortheotherisinvoked).Acompliantpromisesimplementationmakessurethatevenifwecallresolveorrejectmultipletimes,thepromiseiseitherfulfilledorrejectedonlyonce.

Version4ofthewebspiderapplicationusingpromisesshouldnowbereadytobetriedout.Wemightnoticeonceagainhowthedownloadtasksnowruninparallel,withaconcurrencylimitof2.

Page 124: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

GeneratorsTheES6specificationintroducesanothermechanismthat,besidesotherthings,canbeusedtosimplifytheasynchronouscontrolflowofourNode.jsapplications.Wearetalkingaboutgenerators,alsoknownassemi-coroutines.Theyareageneralizationofsubroutines,wheretherecanbedifferententrypoints.Inanormalfunction,infact,wecanhaveonlyoneentrypoint,whichcorrespondstotheinvocationofthefunctionitself.Ageneratorissimilartoafunction,butinaddition,itcanbesuspended(usingtheyieldstatement)andthenresumedatalatertime.Generatorsareparticularlyusefulwhenimplementingiterators,andthisshouldringabell,aswehavealreadyseenhowiteratorscanbeusedtoimplementimportantasynchronouscontrolflowpatternssuchassequentialandlimitedparallelexecution.

NoteInNode.js,generatorsareavailablestartingfromVersion0.11,butatthemomentofwriting,thisfeatureisstillnotenabledbydefaultandit'snecessarytoinvokeNode.jswiththe--harmonyor--harmony-generatorsflagstogetgeneratorsworking.Totrytheexamplesinthissection,makesureyouhavetherightversionofNode.jsinstalled(Version0.11.0andlater),byrunningthefollowingcommand:

node--version

ThebasicsBeforeweexploretheuseofgeneratorsforasynchronouscontrolflow,it'simportantwelearnsomebasicconcepts.Let'sstartfromthesyntax;ageneratorfunctioncanbedeclaredbyappendingthe*(asterisk)operatorafterthefunctionkeyword:

function*makeGenerator(){

//body

}

InsidethemakeGenerator()function,wecanpausetheexecutionusingthekeywordyieldandreturntothecallerthevaluepassedtoit:

function*makeGenerator(){

yield'HelloWorld';

console.log('Re-entered');

}

Page 125: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Intheprecedingcode,thegeneratoryieldsastring,HelloWorld,byputtingtheexecutionofthefunctiononpause.Whenthegeneratorisresumed,theexecutionwillstartfromconsole.log('Re-entered').

ThemakeGenerator()functionisessentiallyafactorythat,wheninvoked,returnsanewgeneratorobject:

vargen=makeGenerator();

Themostimportantmethodofthegeneratorobjectisnext(),whichisusedtostart/resumetheexecutionofthegeneratorandreturnsanobjectinthefollowingform:

{

value:<yieldedvalue>

done:<trueiftheexecutionreachedtheend>

}

Thisobjectcontainsthevalueyieldedbythegenerator(value)andaflagtoindicateifthegeneratorhascompleteditsexecution(done).

AsimpleexampleTodemonstrategenerators,let'screateanewmodule.WecancallitfruitGenerator.jsandincludethefollowingcode:

function*fruitGenerator(){

yield'apple';

yield'orange';

return'watermelon';

}

varnewFruitGenerator=fruitGenerator();

console.log(newFruitGenerator.next());//[1]

console.log(newFruitGenerator.next());//[2]

console.log(newFruitGenerator.next());//[3]

Wecanrunthenewmodulewiththefollowingcommand:

node--harmony-generatorsfruitGenerator

Theprecedingcodeshouldprintthefollowingoutput:

{value:'apple',done:false}

{value:'orange',done:false}

Page 126: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

{value:'watermelon',done:true}

Thisisashortexplanationofwhathappenedintheprecedingcode:

1. ThefirsttimenewFruitGenerator.next()wasinvoked,thegeneratorstarteditsexecutionuntilitreachedthefirstyieldcommand,whichputthegeneratoronpauseandreturnedthevalueapple,tothecaller.

2. AtthesecondinvocationofnewFruitGenerator.next(),thegeneratorresumed,startingfromthesecondyieldcommand,whichinturnputonpausetheexecutionagain,whilereturningthevalueorangetothecaller.

3. ThelastinvocationofnewFruitGenerator.next()causedtheexecutionofthegeneratortoresumefromitslastinstruction,areturnstatement,whichterminatesthegenerator,returnsthevalue,watermelon,andsetsthedonepropertytotrueintheresultobject.

GeneratorsasiteratorsTobetterunderstandwhygeneratorsaresousefulfortheimplementationofiterators,let'sbuildone.Inanewmodule,whichwewillcalliteratorGenerator.js,let'swritethefollowingcode:

function*iteratorGenerator(arr){

for(vari=0;i<arr.length;i++){

yieldarr[i];

};

}

variterator=iteratorGenerator(['apple','orange',

'watermelon']);

varcurrentItem=iterator.next();

while(!currentItem.done){

console.log(currentItem.value);

currentItem=iterator.next();

}

Wecanexecutethiscodeusingthefollowingcommand:

node--harmony-generatorsiteratorGenerator

Theprecedingsimpleprogramshouldprintthelistoftheitemsinthearrayasfollows:

apple

orange

watermelon

Page 127: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Inthisexample,eachtimewecalliterator.next(),weresumetheforloopofthegenerator,whichrunsanothercyclebyyieldingthenextiteminthearray.Thisdemonstrateshowthestateofthegeneratorismaintainedacrossinvocations.Whenresumed,theloopandallthevariablesareexactlythesameaswhentheexecutionwasputonpause.

PassingvaluesbacktoageneratorToconcludeourexplorationofthebasicfunctionalityofgenerators,wewillnowlearnhowtopassvaluesbacktoagenerator.Thisisactuallyverysimple;whatweneedtodoisjustprovidinganargumenttothenext()method,andthatvaluewillbeprovidedasthereturnvalueoftheyieldstatementinsidethegenerator.

Toshowthis,let'screateanewsimplemodule:

function*twoWayGenerator(){

varwhat=yieldnull;

console.log('Hello'+what);

}

vartwoWay=twoWayGenerator();

twoWay.next();

twoWay.next('world');

Whenexecuted,theprecedingcodewillprintHelloworld.Thismeansthatthefollowinghashappened:

1. Whenthefirstnext()methodisinvoked,thegeneratorreachesthefirstyieldfunctionandisthenputonpause.

2. Whennext('world')isinvoked,thegeneratorresumesfromthepointwhereitwasputonpause,whichisontheyieldinstruction,butthistimewehaveavaluethatispassedbacktothegenerator.Thisvaluewillthenbesetintothewhatvariable.Thegeneratorthenexecutestheconsole.log()instructionandterminates.

Inasimilarway,wecanforceageneratortothrowanexception.Thisismadepossiblebyusingthethrowmethodofthegenerator,asshowninthefollowingexample:

vartwoWay=twoWayGenerator();

twoWay.next();

twoWay.throw(newError());

Usingthislastcodesnippet,thetwoWayGenerator()functionwillthrowanexceptionthemomenttheyieldfunctionreturns.Thisworksexactlyasifanexceptionwas

Page 128: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thrownfrominsidethegenerator,andthismeansthatitcanbecaughtandhandledlikeanyotherexceptionusingatry-catchblock.

AsynchronouscontrolflowwithgeneratorsYoumustbewonderinghowgeneratorscanhelpuswithhandlingasynchronousoperations.Weimmediatelydemonstratethatbycreatingafunctionthatallowsustouseasynchronousfunctionsinsideageneratorandthenresumingtheexecutionofthegeneratorwhentheasynchronousoperationcompletes.WewillcallthisfunctionasyncFlow():

functionasyncFlow(generatorFunction){

functioncallback(err){

if(err){

returngenerator.throw(err);

}

varresults=[].slice.call(arguments,1);

generator.next(results.length>1?results:results[0]);

};

vargenerator=generatorFunction(callback);

generator.next();

}

Theprecedingfunctiontakesageneratorasaninput,instantiatesit,andthenimmediatelystartsitsexecution:

vargenerator=generatorFunction(callback);

generator.next();

ThegeneratorFunction()receivesasinputaspecialcallbackfunctionthatinvokesgenerator.throw()ifanerrorisreceived;otherwise,itresumestheexecutionofthegeneratorbypassingbacktheresultsreceivedinthecallbackfunction:

if(err){

returngenerator.throw(err);

}

varresults=[].slice.call(arguments,1);

generator.next(results.length>1?results:results[0]);

Todemonstratethepowerofthissimplefunction,let'screateanewmodulecalledclone.js,which(stupidly)createsacloneofitself.PastetheasyncFlow()functionwejustcreated,followedbythecoreoftheprogram:

varfs=require('fs');

varpath=require('path');

Page 129: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

asyncFlow(function*(callback){

varfileName=path.basename(__filename);

varmyself=yieldfs.readFile(fileName,'utf8',callback);

yieldfs.writeFile('clone_of_'+fileName,myself,

callback);

console.log('Clonecreated');

});

Remarkably,withthehelpoftheasyncFlow()function,wewereabletowriteasynchronouscodeusingalinearapproach,aswewereusingblockingfunctions!Themagicbehindthisresultshouldbeclearbynow.Thecallbackpassedtoeachasynchronousfunctionwillinturnresumethegeneratorassoonastheasynchronousoperationiscomplete.Nothingcomplicated,buttheoutcomeissurelyimpressive.

Therearetwoothervariationsofthistechnique,oneinvolvingtheuseofpromisesandtheotherusingthunks.

NoteAthunkusedingenerator-basedcontrolflowisjustafunctionthatpartiallyappliesalltheargumentsoftheoriginalfunctionexceptitscallback.Thereturnvalueisanotherfunctionthatacceptsonlythecallbackasanargument.Forexample,thethunkifiedversionoffs.readFile()wouldbeasfollows:

functionreadFileThunk(filename,options){

returnfunction(callback){

fs.readFile(filename,options,callback);

}

}

Boththunksandpromisesallowustocreategeneratorsthatdonotneedacallbacktobepassedasanargument;forexample,aversionofasyncFlow()usingthunksmightbethefollowing:

functionasyncFlowWithThunks(generatorFunction){

functioncallback(err){

if(err){

returngenerator.throw(err);

}

varresults=[].slice.call(arguments,1);

varthunk=generator.next(results.length>1?results:

results[0]).value;

thunk&&thunk(callback);

};

vargenerator=generatorFunction();

varthunk=generator.next().value;

Page 130: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thunk&&thunk(callback);

}

Thetrickistoreadthereturnvalueofgenerator.next(),whichcontainsthethunk.Thenextstepistoinvokethethunkitself,byinjectingourspecialcallback.Simple!Thisallowsustowritethefollowingcode:

asyncFlowWithThunks(function*(){

varmyself=yieldreadFileThunk(__filename,'utf8');

yieldwriteFileThunk("cloneofclone.js",myself);

console.log("Clonecreated");

});

Similarly,wecouldimplementaversionofasyncFlow()thatacceptsapromiseasyieldable.WeleavethisasanexerciseasitsimplementationrequiresonlyaminimalchangeintheasyncFlowWithThunks()function.WemayalsoimplementanasyncFlow()functionthatacceptsbothpromisesandthunksasyieldables,usingthesameprinciples.

Generator-basedcontrolflowusingcoAsyoumayguess,theNode.jsecosystemalreadyprovidessomesolutionstohandleasynchronouscontrolflowsusinggenerators.Forexample,suspend(https://npmjs.org/package/suspend)isoneoftheoldestandsupportspromises,thunks,Node.js-stylecallbacks,aswellasrawcallbacks.Also,mostofthepromiseslibrariesweanalyzedearlierinthechapterprovidehelperstousepromiseswithgenerators.

AllthesesolutionsarebasedonthesameprincipleswedemonstratedwiththeasyncFlow()function;so,wemaywanttoreuseoneoftheseinsteadofwritingoneourselves.

Fortheexamplesinthissection,wechosetouseco(https://npmjs.org/package/co),whichiscurrentlyreceivingalotofmomentum.Aflexiblesolution,cosupportsseveraltypesofyieldables,someofwhichare:

ThunksPromisesArrays(parallelexecution)Objects(parallelexecution)Generators(delegation)Generatorfunctions(delegation)

coalsohasitsownecosystemofpackagesincludingthefollowing:

Page 131: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Webframeworks,themostpopularbeingkoa(https://npmjs.org/package/koa)LibrariesimplementingspecificcontrolflowpatternsLibrarieswrappingpopularAPIstosupportco

Wewillusecotoreimplementourwebspiderapplicationusinggenerators.

While,toconvertNode.jsstylefunctionstothunks,wearegoingtousealittlelibrarycalledthunkify(https://npmjs.org/package/thunkify).

SequentialexecutionLet'sstartourpracticalexplorationofgeneratorsandcobymodifyingversion2ofthewebspiderapplication.Theveryfirstthingwewanttodoistoloadourdependenciesandgenerateathunkifiedversionofthefunctionswearegoingtouse.Thesewillgoatthetopofthespider.jsmodule:

varthunkify=require('thunkify');

varco=require('co');

varrequest=thunkify(require('request'));

varfs=require('fs');

varmkdirp=thunkify(require('mkdirp'));

varreadFile=thunkify(fs.readFile);

varwriteFile=thunkify(fs.writeFile);

varnextTick=thunkify(process.nextTick);

Lookingattheprecedingcode,wecansurelynoticesomesimilaritieswiththecodeweusedearlierinthechaptertopromisifysomeAPIs.Inthisregard,itisinterestingtopointoutthatifwedecidedtousethepromisifiedversionofourfunctionsinsteadoftheirthunkifiedalternative,thecodethatwillnowfollowwouldremainexactlythesame,thankstothefactthatcosupportsboththunksandpromisesasyieldableobjects.Infact,ifwewant,wecouldevenuseboththunksandpromisesinthesameapplication,eveninthesamegenerator.Thisisatremendousadvantageintermsofflexibility,asitallowsustousegenerator-basedcontrolflowwithwhateversolutionwealreadyhaveatourdisposal.

Okay,nowlet'sstarttransformingthedownload()functionintoagenerator:

function*download(url,filename){

console.log('Downloading'+url);

varresults=yieldrequest(url);

varbody=results[1];

yieldmkdirp(path.dirname(filename));

yieldwriteFile(filename,body);

console.log('Downloadedandsaved:'+url);

returnbody;

Page 132: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

Byusinggeneratorsandco,ourdownload()functionsuddenlybecomestrivial.Allwehadtodoisjustconvertitintoageneratorfunctionanduseyieldwhereverwehadanasynchronousfunction(asthunk)toinvoke.

Next,it'stheturnofthespider()function:

function*spider(url,nesting){

varfilename=utilities.urlToFilename(url);

varbody;

try{

body=yieldreadFile(filename,'utf8');

}catch(err){

if(err.code!=='ENOENT'){

throwerr;

}

body=yielddownload(url,filename);

}

yieldspiderLinks(url,body,nesting);

}

Theinterestingdetailtonoticefromthislastfragmentofcodeishowwewereabletouseatry-catchblocktohandleexceptions.Also,wecannowusethrowtopropagateerrors!Anotherremarkablelineiswhereweyieldthedownload()function,whichisnotathunknorapromisifiedfunction,butjustanothergenerator.Thisispossible,thankstoco,whichalsosupportsothergeneratorsasyieldables.

Atlast,wecanalsoconvertspiderLinks(),whereweimplementedaniterationtodownloadthelinksofawebpageinsequence.Withgenerators,thisbecomestrivialaswell:

function*spiderLinks(currentUrl,body,nesting){

if(nesting===0){

returnyieldnextTick();

}

varlinks=utilities.getPageLinks(currentUrl,body);

for(vari=0;i<links.length;i++){

yieldspider(links[i],nesting-1);

};

}

Thereisreallylittletoexplainfromthepreviouscode,thereisnopatterntoshowforthesequentialiteration;generatorsandcoaredoingallthedirtyworkforus,sowewereabletowritetheasynchronousiterationasifwewereusingblocking,directstyleAPIs.

Page 133: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Nowcomesthemostimportantpart,theentrypointofourprogram:

co(function*(){

try{

yieldspider(process.argv[2],1);

console.log('Downloadcomplete');

}catch(err){

console.log(err);

};

})();

Thisistheonlyplacewherewehavetoinvokeco(...)towrapagenerator.Infact,oncewedothat,cowillautomaticallywrapanygeneratorwepasstoayieldstatement,andthiswillhappenrecursively,sotherestoftheprogramistotallyagnostictothefactweareusingco,eventhoughit'sunderthehood.

NoteItisimportanttonoticethattheco()functionreturnsathunk,sowehavetoinvokeittostartthespidertask.

Nowitshouldbepossibletorunourgenerator-basedwebspiderapplication.Justremembertousethe--harmonyor--harmony-generatorsflaginthecommandline:

node--harmony-generatorsspider<URL>

ParallelexecutionThebadnewsaboutgeneratorsisthattheyaregreatforwritingsequentialalgorithms,buttheycan'tbeusedtoparallelizetheexecutionofasetoftasks,atleastnotusingjustyieldandgenerators.Infact,thepatterntouseforthesecircumstancesistosimplyrelyonacallback-basedorpromise-basedfunction,whichinturncaneasilybeyieldedandusedwithgenerators.

Fortunately,forthespecificcaseoftheunlimitedparallelexecution,coalreadyallowsustoobtainitnativelybysimplyyieldinganarrayofpromises,thunks,generators,orgeneratorfunctions.

Withthisinmind,version3ofourwebspiderapplicationcanbeimplementedsimplybyrewritingthespiderLinks()functionasfollows:

function*spiderLinks(currentUrl,body,nesting){

if(nesting===0){

Page 134: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

returnnextTick();

}

varlinks=utilities.getPageLinks(currentUrl,body);

vartasks=links.map(function(link){

returnspider(link,nesting-1);

});

yieldtasks;

}

Whatwedidwasjustcollectallthedownloadtasks,whichareessentiallygenerators,andthenyieldontheresultingarray.Allthesetaskswillbeexecutedbycoinparallelandthentheexecutionofourgenerator(spiderLinks)willberesumedwhenallthetasksfinishrunning.

Ifyouthinkwecheatedbyexploitingthefeatureofcothatallowsustoyieldonanarray,wecandemonstratehowthesameparallelflowcanbeachievedusingacallback-basedsolutionsimilartowhatwehavealreadyusedearlierinthechapter.Let'susethistechniquetorewritethespiderLinks()onceagain:

functionspiderLinks(currentUrl,body,nesting){

if(nesting===0){

returnnextTick();

}

//returnsathunk

returnfunction(callback){

varcompleted=0,errored=false;

varlinks=utilities.getPageLinks(currentUrl,body);

if(links.length===0){

returnprocess.nextTick(callback);

}

functiondone(err,result){

if(err&&!errored){

errored=true;

callback(err);

}

if(++completed===links.length&&!errored){

callback();

}

}

for(vari=0;i<links.length;i++){

co(spider(links[i],nesting-1))(done);

};

}

}

Torunthespider()functioninparallel,whichisagenerator,wehadtoconvertitintoathunkandthenexecuteit.Thiswaspossiblebywrappingitwiththeco(...)

Page 135: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

function,whichessentiallycreatesathunkoutofagenerator.Thisway,wewereabletoinvokeitinparallelandsetthedone()functionascallback.Usually,allthelibrariesforgenerator-basedcontrolflowhaveasimilarfeature,soyoucanalwaystransformageneratorintoacallback-basedfunctionifneeded.

Tostartmultipledownloadtasksinparallel,wejustreusedthecallback-basedpatternforparallelexecution,whichwedefinedearlierinthechapter.WeshouldalsonoticethatwetransformedthespiderLinks()functiontoathunk(it'snotevenageneratoranymore.)Thisenabledustohaveacallbackfunctiontoinvokewhenalltheparalleltasksarecompleted.

NotePattern(generator-to-thunk):convertsageneratortoathunkinordertobeabletorunitinparallelorutilizeitfortakingadvantageofothercallback-orpromises-basedcontrolflowalgorithms.

LimitedparallelexecutionNowthatweknowhowtomoveincaseofnonsequentialexecutionflows,itshouldbeeasytoplantheimplementationofversion4ofourwebspiderapplication,theoneimposingalimitonthenumberofconcurrentdownloadtasks.Wehaveseveraloptionswecanusetodothat,someofthemareasfollows:

Usethecallback-basedversionoftheTaskQueueclassweimplementedpreviouslyinthechapter.Wewouldneedtojustthunkifyitsfunctionsandanygeneratorwewanttouseasatask.Usethepromises-basedversionoftheTaskQueueclass,andjustmakesurethateachgeneratorwewanttouseasataskisconvertedintoafunctionreturningapromise.Useasync,andthunkifyanyhelperweplantouse,inadditiontoconvertinganygeneratortoacallback-basedfunctionthatcanbeusedbythelibrary.Usealibraryfromthecoecosystem,specificallydesignedforthistypeofflow,suchas,co-limiter(https://npmjs.org/package/co-limiter).Implementacustomalgorithmbasedontheproducer-consumerpattern,thesamethatco-limiterusesinternally.

Foreducationalpurposes,wearegoingtochoosethelastoption,sowecandiveintoapatternthatisoftenassociatedwithcoroutines(butalsothreadsandprocesses).

Producer-consumerpattern

Page 136: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thegoalistoleverageaqueuetofeedafixednumberofworkers,asmanyastheconcurrencylevelwewanttoset.Toimplementthisalgorithm,wearegoingtotakeasstartingpointtheTaskQueueclasswedefinedearlierinthechapter.Let'sstartgradually;thefirstthingwewanttodoisdefinetheconstructor:

functionTaskQueue(concurrency){

this.concurrency=concurrency;

this.running=0;

this.taskQueue=[];

this.consumerQueue=[];

this.spawnWorkers(concurrency);

}

Noticetheinvocationofthis.spawnWorkers()asthisisthemethodinchargeofstartingtheworkers.Thenextstepis,ofcourse,todefineourworkers;let'sseehowtheylook:

TaskQueue.prototype.spawnWorkers=function(concurrency){

varself=this;

for(vari=0;i<concurrency;i++){

co(function*(){

while(true){

vartask=yieldself.nextTask();

yieldtask;

}

})();

}

}

Ourworkersareverysimple;theyarejustgeneratorswrappedaroundco()andexecutedimmediately,sothateachonecanruninparallel.Internally,eachworkerisrunninganinfiniteloopthatblocks(yield)waitingforanewtasktobeavailableinthequeue(yieldself.nextTask()),andwhenthishappens,ityieldsthetask(whichisanyvalidyieldable)waitingforitscompletion.Youmaybewondering,howcanweactuallywaitforthenexttasktobequeued?TheanswerisinthenextTask()methodthatwearenowgoingtodefine:

TaskQueue.prototype.nextTask=function(){

varself=this;

returnfunction(callback){//[1]

if(self.taskQueue.length!==0){

callback(null,self.taskQueue.shift());//[2]

}else{

self.consumerQueue.push(callback);//[3]

}

}

}

Page 137: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Let'sseewhathappensinthismethod,whichisthecoreofthepattern:

1. Themethodreturnsathunk,whichisavalidyieldableforco.2. Thecallbackofthereturnedthunkisinvokedbyprovidingthenexttaskinthe

taskQueue(ifthereisanyavailable).Thiswillimmediatelyunblockaworker,providingthenexttasktoyieldon.

3. Iftherearenotasksinthequeue,thecallbackitselfispushedintotheconsumerQueue.Bydoingthis,wearepracticallyputtingaworkerinidlemode.ThecallbacksintheconsumerQueuefunctionwillbeinvokedassoonaswehaveanewtasktoprocess,whichwillresumethecorrespondingworker.

Now,toknowhowtheidleworkersintheconsumerQueuefunctionareresumed,weneedtodefinethepushTask()method:

TaskQueue.prototype.pushTask=function(task){

if(this.consumerQueue.length!==0){

this.consumerQueue.shift()(null,task);

}else{

this.taskQueue.push(task);

}

}

Trivially,themethodinvokesthefirstcallbackintheconsumerQueuefunctionifavailable,whichinturnwillunblockaworker.Ifnocallbackisavailable,itmeansthatalltheworkersarebusy,sowesimplyaddanewiteminthetaskQueuefunction.

IntheTaskQueueclasswejustdefined,theworkershavetheroleofconsumers,whilewhoeverusespushTask()canbeconsideredaproducer.Thispatternshowsushowageneratorcanlookverysimilartoathread(oraprocess).Infact,theproducer-consumerinteractionisprobablythemostcommonproblempresentedwhenstudyinginter-processcommunicationtechniques,butaswealreadymentioned,itisalsoacommonusecaseforcoroutines.

LimitingthedownloadtasksconcurrencyNowthatwehaveimplementedourlimitedparallelalgorithmusinggeneratorsandtheproducer-consumerpattern,wecanapplyittolimittheconcurrencyofthedownloadtasksofourwebspiderapplication(version4).First,let'sloadandinitializeaTaskQueueobject:

varTaskQueue=require('./taskQueue')

vardownloadQueue=newTaskQueue(2);

Next,wemodifythespiderLinks()function.Itsbodyisalmostidenticaltotheone

Page 138: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

wejustusedtoimplementtheunlimitedparallelexecutionflow,sowewillonlyshowthechangedpartshere:

functionspiderLinks(currentUrl,body,nesting){

[...]

returnfunction(callback){

[...]

functiondone(err,result){

[...]

}

links.forEach(function(link){

downloadQueue.pushTask(function*(){

yieldspider(link,nesting-1);

done();

});

});

}

}

Ineachofthetasks,weinvokethedone()functionjustafteradownloadcompletes,sowecancounthowmanylinksweredownloadedandthennotifythecallbackofthethunkwhenallarecomplete.

Asanexercise,youcantrytoimplementversion4ofthewebspiderapplication,usingtheotherfourmethodswepresentedatthebeginningofthissection.

Page 139: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ComparisonAtthispointofthechapter,weshouldhaveabetterunderstandingofwhatoptionswehavetotametheasynchronousnatureofNode.js.Eachoneofthesolutionspresentedhasitsownprosandcons.Let'ssummarizetheminthefollowingtable:

Solutions Pros Cons

PlainJavaScript

DoesnotrequireanyadditionallibraryortechnologyOffersthebestperformancesProvidesthebestlevelofcompatibilitywiththird-partylibrariesAllowsthecreationofadhocandmoreadvancedalgorithms

Mightrequireextracodeandrelativelycomplexalgorithms

Async

SimplifiesthemostcommoncontrolflowpatternsIsstillacallback-basedsolutionGoodperformances

IntroducesanexternaldependencyMightstillnotbeenoughforadvancedflows

Promises

GreatlysimplifythemostcommoncontrolflowpatternsRobusterrorhandlingPartoftheES6specificationGuaranteedeferredinvocationofonFulfilledandonRejected

MightrequireanexternaldependencyRequiretopromisifycallback-basedAPIsOnlyafewthird-partylibrariessupportpromisesnativelyIntroduceasmallperformancehit

Requireacomplementarycontrolflowlibrary

Page 140: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Generators

MakenonblockingAPIlooklikeblockingSimplifyerrorhandlingPartofES6specification

StillrequirecallbacksorpromisestoimplementnonsequentialflowsNotyetavailablebydefaultonNode.jsRequiretothunkifyorpromisifynongenerator-basedAPIs

NoteItisworthmentioningthatwechosetopresentinthischapteronlythemostpopularsolutionstohandleasynchronouscontrolflow,ortheonesreceivingalotofmomentum,butit'sgoodtoknowthatthereareafewmoreoptionsyoumightwanttolookat,forexample,Fibers(https://npmjs.org/package/fibers)andStreamline(https://npmjs.org/package/streamline).

Page 141: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryAtthebeginningofthischapter,wesaidthatNode.jsprogrammingcanbetoughbecauseofitsasynchronousnature,especiallyforpeopleusedtodevelopingonotherplatforms.However,throughoutthischapterweshowedhowasynchronousAPIscanbebenttoourwill,startingwithplainJavaScript,whichprovidedusthefoundationfortheanalysisofmoresophisticatedtechniques.Wethensawthatthetoolsatourdisposalareindeedvariegatedandprovidegoodsolutionstomostofourproblems,inadditiontoofferingaprogrammingstyleforeverytaste;forexample,wemaychooseasynctosimplifythemostcommonflows,ortotallychangeparadigmbyusingpromiseswiththeirfluentchainingandrobusterrormanagement,orifwewanttogetfancy,wecanalwaysleveragegeneratorsandfeellikeweareprogrammingwithblockingAPIs.

Thischaptershouldnotonlyhavetaughtyouhowtochoosebetweenoneortheothersolutionsbutalsohowtousethemtogether,eveninthesameproject.

Thischapter,atlast,shouldhavegivenusthatconfidenceneededforefficientlyreadingandwritingasynchronouscodeandshouldhavebroughtusastepclosertomasteringNode.jsprogramming.

Inthenextchapter,wewillintroducestreams,theswissarmyknifeofNode.js.WewillseehowtheycanbeusednotonlytoefficientlyhandleI/O,butalsoasatoolfortransformingdataandobjects.

Page 142: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter3.CodingwithStreamsStreamsareoneofthemostimportantcomponentsandpatternsofNode.js.ThereisamottointhecommunitythatsaysStreamallthethings!andthisaloneshouldbeenoughtodescribetheroleofstreamsinNode.js.DominicTarr,atopcontributortotheNode.jscommunity,definesstreamsasnode'sbestandmostmisunderstoodidea.TherearedifferentreasonsthatmakeNode.jsstreamssoattractive;again,it'snotjustrelatedtotechnicalproperties,suchasperformanceorefficiency,butit'ssomethingmoreabouttheireleganceandthewaytheyfitperfectlyintotheNode.jsphilosophy.

Inthischapter,youwilllearnaboutthefollowingtopics:

WhystreamsaresoimportantinNode.jsUsingandcreatingstreamsStreamsasaprogrammingparadigm:leveragingtheirpowerinmanydifferentcontextsandnotjustforI/OPipingpatternsandconnectingstreamstogetherindifferentconfigurations

DiscoveringtheimportanceofstreamsInanevent-basedplatformsuchasNode.js,themostefficientwaytohandleI/Oisinrealtime,consumingtheinputassoonasitisavailableandsendingtheoutputassoonasitisproducedbytheapplication.

Inthissection,wearegoingtogiveaninitialintroductiontoNode.jsstreamsandtheirstrengths.Pleasebearinmindthatthisisonlyanoverview,asamoredetailedanalysisonhowtouseandcomposestreamswillfollowlaterinthechapter.

BufferingvsStreamingAlmostalltheasynchronousAPIsthatwe'veseensofarinthebookworkusingthebuffermode.Foraninputoperation,thebuffermodecausesallthedatacomingfromaresourcetobecollectedintoabuffer;itisthenpassedtoacallbackassoonastheentireresourceisread.Thefollowingdiagramshowsavisualexampleofthisparadigm:

Page 143: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Intheprecedingfigure,wecanseethat,atthetimet1,somedatawasreceivedfromtheresourceandsavedintothebuffer.Atthetimet2,anotherdatachunkisreceived—thefinalone—thatcompletesthereadoperationandcausestheentirebuffertobesenttotheconsumer.

Ontheotherside,streamsallowyoutoprocessthedataassoonasitarrivesfromtheresource.Thisisshowninthefollowingdiagram:

Page 144: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thistime,thediagramshowsyouhoweachnewchunkofdataisreceivedfromtheresourceandisimmediatelyprovidedtotheconsumer,whonowhasthechancetoprocessitstraightawaywithoutwaitingforallthedatatobecollectedinthebuffer.

Butwhatarethedifferencesbetweenthetwoapproaches?Wecansummarizethemintwomajorcategories:

SpatialefficiencyTimeefficiency

However,Node.jsstreamshaveanotherimportantadvantage:composability.Let'snowseewhatimpactthesepropertieshaveinthewaywedesignandwriteourapplications.

SpatialefficiencyFirstofall,streamsallowustodothingsthatwouldnotbepossible,bybufferingdataandprocessingitallatonce.Forexample,considerthecaseinwhichwehavetoreadaverybigfile,let'ssay,intheorderofhundredsofmegabytesorevengigabytes.Clearly,usinganAPIthatreturnsabigbufferwhenthefileiscompletelyread,isnotagoodidea.Imaginereadingafewofthesebigfilesconcurrently;ourapplicationwilleasilyrunoutofmemory.Besidesthat,buffersinV8cannotbebiggerthan0x3FFFFFFFbytes(alittlebitlessthan1GB).So,wemighthitawallwaybeforerunningoutofphysicalmemory.

Page 145: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

GzippingusingabufferedAPITomakeaconcreteexample,let'sconsiderasimpleCommand-lineInterface(CLI)applicationthatcompressesafileusingtheGzipformat.UsingabufferedAPI,suchanapplicationwilllooklikethefollowinginNode.js(errorhandlingisomittedforbrevity):

varfs=require('fs');

varzlib=require('zlib');

varfile=process.argv[2];

fs.readFile(file,function(err,buffer){

zlib.gzip(buffer,function(err,buffer){

fs.writeFile(file+'.gz',buffer,function(err){

console.log('Filesuccessfullycompressed');

});

});

});

Now,wecantrytoputtheprecedingcodeinafilenamedgzip.jsandthenrunitwiththefollowingcommand:

nodegzip<pathtofile>

Ifwechooseafilethatisbigenough,let'ssayalittlebitbiggerthan1GB,wewillreceiveaniceerrormessagesayingthatthefilethatwearetryingtoreadisbiggerthanthemaximumallowedbuffersize,suchasthefollowing:

RangeError:FilesizeisgreaterthanpossibleBuffer:

0x3FFFFFFFbytes

That'sexactlywhatweexpected,andit'sasymptomthatweareusingthewrongapproach.

GzippingusingstreamsThesimplestwaywehavetofixourgzipapplicationandmakeitworkwithbigfilesistouseastreamingAPI.Let'sseehowthiscanbeachieved;let'sreplacethecontentsofthemodulewejustcreatedwiththefollowingcode:

varfs=require('fs');

varzlib=require('zlib');

varfile=process.argv[2];

Page 146: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

fs.createReadStream(file)

.pipe(zlib.createGzip())

.pipe(fs.createWriteStream(file+'.gz'))

.on('finish',function(){

console.log('Filesuccessfullycompressed');

});

Isthatit?Youmayask.Yes,aswesaid,streamsareamazingalsobecauseoftheirinterfaceandcomposability,thusallowingclean,elegant,andconcisecode.Wewillseethisinawhileinmoredetail,butfornowtheimportantthingtorealizeisthattheprogramwillrunsmoothlyagainstfilesofanysize,ideallywithconstantmemoryutilization.Tryityourself(butconsiderthatcompressingabigfilemighttakeawhile).

TimeefficiencyLet'snowconsiderthecaseofanapplicationthatcompressesafileanduploadsittoaremoteHTTPserver,whichinturndecompressesandsavesitonthefilesystem.IfourclientwasimplementedusingabufferedAPI,theuploadwouldstartonlywhentheentirefilehasbeenreadandcompressed.Ontheotherhand,thedecompressionwillstartontheserveronlywhenallthedatahasbeenreceived.Abettersolutiontoachievethesameresultinvolvestheuseofstreams.Ontheclientmachine,streamsallowsyoutocompressandsendthedatachunksassoonastheyarereadfromthefilesystem,whereas,ontheserver,itallowsyoutodecompresseverychunkassoonasitisreceivedfromtheremotepeer.Let'sdemonstratethisbybuildingtheapplicationthatwementionedearlier,startingfromtheserverside.

Let'screateamodulenamedgzipReceive.jscontainingthefollowingcode:

varhttp=require('http');

varfs=require('fs');

varzlib=require('zlib');

varserver=http.createServer(function(req,res){

varfilename=req.headers.filename;

console.log('Filerequestreceived:'+filename);

req

.pipe(zlib.createGunzip())

.pipe(fs.createWriteStream(filename))

.on('finish',function(){

res.writeHead(201,{'Content-Type':'text/plain'});

res.end('That\'sit\n');

console.log('Filesaved:'+filename);

});

});

server.listen(3000,function(){

console.log('Listening');

});

Page 147: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theserverreceivesthedatachunksfromthenetwork,decompressesthem,andsavesthemassoonastheyarereceived,thankstoNode.jsstreams.

TheclientsideofourapplicationwillgointoamodulenamedgzipSend.jsanditlookslikethefollowing:

varfs=require('fs');

varzlib=require('zlib');

varhttp=require('http');

varpath=require('path');

varfile=process.argv[2];

varserver=process.argv[3];

varoptions={

hostname:server,

port:3000,

path:'/',

method:'PUT',

headers:{

filename:path.basename(file),

'Content-Type':'application/octet-stream',

'Content-Encoding':'gzip'

}

};

varreq=http.request(options,function(res){

console.log('Serverresponse:'+res.statusCode);

});

fs.createReadStream(file)

.pipe(zlib.createGzip())

.pipe(req)

.on('finish',function(){

console.log('Filesuccessfullysent');

});

Intheprecedingcode,weareagainusingstreamstoreadthedatafromthefile,thencompressandsendeachchunkassoonasitisreadfromthefilesystem.

Now,totryouttheapplication,let'sfirststarttheserverusingthefollowingcommand:

nodegzipReceive

Then,wecanlaunchtheclientbyspecifyingthefiletosendandtheaddressoftheserver(forexamplelocalhost):

nodegzipSend<pathtofile>localhost

Page 148: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Ifwechooseafilebigenough,wewillseemoreeasilyhowthedataflowsfromtheclienttotheserver,butwhyexactlyisthisparadigmwherewehaveflowingdatamoreefficientcomparedtousingabufferedAPI?Thefollowingdiagramshouldgiveusahint:

Whenafileisprocesseditgoesthroughasetofsequentialstages:

1. [Client]Readfromthefilesystem.2. [Client]Compressthedata.3. [Client]Sendittotheserver.4. [Server]Receivefromtheclient.5. [Server]Decompressthedata.6. [Server]Writethedatatodisk.

Tocompletetheprocessing,wehavetogothrougheachstagelikeinanassemblyline,insequence,untiltheend.Intheprecedingfigure,wecanseethat,usingabufferedAPI,theprocessisentirelysequential.Tocompressthedata,wefirsthavetowaitfortheentirefiletoberead,then,tosendthedata,wehavetowaitfortheentirefiletobebothreadandcompressed,andsoon.Wheninsteadweareusingstreams,theassemblylineiskickedoffassoonaswereceivethefirstchunkofdata,withoutwaitingfortheentirefiletoberead.Butmoreamazingly,whenthenextchunkofdataisavailable,thereisnoneedtowaitfortheprevioussetoftaskstobecompleted;instead,anotherassemblylineislaunchedinparallel.Thisworksperfectlybecauseeachtaskthatweexecuteisasynchronous,soitcanbeparallelizedbyNode.js;theonlyconstraintisthattheorderinwhichthechunksarriveineachstagemustbepreserved(andNode.jsstreamstakecareofthisforus).

Aswecanseefromthepreviousfigure,theresultofusingstreamsisthattheentireprocesstakeslesstime,becausewewastenotimeinwaitingforallthedatatobereadandprocessedallatonce.

Page 149: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ComposabilityThecodewehaveseensofarhasalreadygivenusanoverviewofhowstreamscanbecomposed,thankstothepipe()method,whichallowsustoconnectthedifferentprocessingunits,eachbeingresponsibleforonesinglefunctionalityinperfectNode.jsstyle.Thisispossiblebecausestreamshaveauniforminterface,andtheycanunderstandeachotherintermsofAPI.Theonlyprerequisiteisthatthenextstreaminthepipelinehastosupportthedatatypeproducedbythepreviousstream,whichcanbeeitherbinary,text,orevenobjects,aswewillseelaterinthechapter.

Totakealookatanotherdemonstrationofthepowerofthisproperty,wecantrytoaddanencryptionlayertothegzipReceive/gzipSendapplicationthatwebuiltpreviously.

Todothis,weonlyneedtoupdatetheclientbyaddinganotherstreamtothepipeline;tobeprecise,thestreamreturnedbycrypto.createChipher().Theresultingcodeshouldbeasfollows:

varcrypto=require('crypto');

[...]

fs.createReadStream(file)

.pipe(zlib.createGzip())

.pipe(crypto.createCipher('aes192','a_shared_secret'))

.pipe(req)

.on('finish',function(){

console.log('Filesuccesfullysent');

});

Inasimilarway,wecanupdatetheserversothatthedataisdecryptedbeforebeingdecompressed:

varcrypto=require('crypto');

[...]

varserver=http.createServer(function(req,res){

[...]

req

.pipe(crypto.createDecipher('aes192','a_shared_secret'))

.pipe(zlib.createGunzip())

.pipe(fs.createWriteStream(filename))

.on('finish',function(){

[...]

});

});

Withverylittleeffort(justafewlinesofcode),weaddedanencryptionlayertoourapplication;wesimplyhadtoreuseanalreadyavailabletransformstreamby

Page 150: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

includingitinthepipelinethatwealreadyhad.Inasimilarway,wecanaddandcombineotherstreams,asifwewereplayingwithLegobricks.

Clearly,themainadvantageofthisapproachisreusability,butaswecanseefromthecodewepresentedsofar,streamsalsoenablecleanerandmoremodularcode.Forthesereasons,streamsareoftenusednotjusttodealwithpureI/O,butalsoasameanstosimplifyandmodularizethecode.

Page 151: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

GettingstartedwithstreamsIntheprevioussection,welearnedwhystreamsaresopowerful,butalsothattheyareeverywhereinNode.js,startingfromitscoremodules.Forexample,wehaveseenthatthefsmodulehascreateReadStream()forreadingfromafileandcreateWriteStream()forwritingtoafile,thehttprequestandresponseobjectsareessentiallystreams,andthezlibmoduleallowsustocompressanddecompressdatausingastreaminginterface.

Nowthatweknowwhystreamsaresoimportant,let'stakeastepbackandstarttoexploretheminmoredetail.

AnatomyofstreamsEverystreaminNode.jsisanimplementationofoneofthefourbaseabstractclassesavailableinthestreamcoremodule:

stream.Readable

stream.Writable

stream.Duplex

stream.Transform

EachstreamclassisalsoaninstanceofEventEmitter.Streams,infact,canproduceseveraltypesofevents,suchasend,whenaReadablestreamhasfinishedreading,orerror,whensomethinggoeswrong.

NotePleasenotethat,forbrevity,intheexamplespresentedinthischapter,wewilloftenomitpropererrormanagement.However,inproductionapplicationsitisalwaysadvisedtoregisteranerroreventlistenerforallyourstreams.

Oneofthereasonswhystreamsaresoflexibleisthefactthattheycanhandlenotonlybinarydata,butpractically,almostanyJavaScriptvalue;infacttheycansupporttwooperatingmodes:

Binarymode:Thismodeiswheredataisstreamedintheformofchunks,suchasbuffersorstringsObjectmode:Thismodeiswherethestreamingdataistreatedasasequenceofdiscreetobjects(allowingtousealmostanyJavaScriptvalue)

Page 152: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThesetwooperatingmodesallowustousestreamsnotonlyforI/O,butalsoasatooltoelegantlycomposeprocessingunitsinafunctionalfashion,aswewillseelaterinthechapter.

NoteInthischapter,wewilldiscussmainlytheNode.jsstreaminterfacealsoknownasVersion2,whichwasintroducedinNode.js0.10.Forfurtherdetailsaboutthedifferenceswiththeoldinterface,pleaserefertotheofficialNode.jsblogathttp://blog.nodejs.org/2012/12/20/streams2/.

ReadablestreamsAreadablestreamrepresentsasourceofdata;inNode.js,it'simplementedusingtheReadableabstractclassthatisavailableinthestreammodule.

ReadingfromastreamTherearetwowaystoreceivethedatafromaReadablestream:non-flowingandflowing.Let'sanalyzethesemodesinmoredetail.

Thenon-flowingmode

ThedefaultpatternforreadingfromaReadablestreamconsistsofattachingalistenerforthereadableeventthatsignalstheavailabilityofnewdatatoread.Then,inaloop,wereadallthedatauntiltheinternalbufferisemptied.Thiscanbedoneusingtheread()method,whichsynchronouslyreadsfromtheinternalbufferandreturnsaBufferorStringobjectrepresentingthechunkofdata.Theread()methodhasthefollowingsignature:

readable.read([size])

Usingthisapproach,thedataisexplicitlypulledfromthestreamondemand.

Toshowhowthisworks,let'screateanewmodulenamedreadStdin.js,whichimplementsasimpleprogramthatreadsfromthestandardinput(aReadablestream)andechoeseverythingbacktothestandardoutput:

process.stdin

.on('readable',function(){

varchunk;

Page 153: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

console.log('Newdataavailable');

while((chunk=process.stdin.read())!==null){

console.log(

'Chunkread:('+chunk.length+')"'+

chunk.toString()+'"'

);

}

})

.on('end',function(){

process.stdout.write('Endofstream');

});

Theread()methodisasynchronousoperationthatpullsadatachunkfromtheinternalbuffersoftheReadablestream.Thereturnedchunkis,bydefault,aBufferobjectifthestreamisworkinginbinarymode.

NoteInaReadablestreamworkinginbinarymode,wecanreadstringsinsteadofbuffersbycallingsetEncoding(encoding)onthestream,andprovideavalidencodingformat(forexample,utf8).

Thedataisreadexclusivelyfromwithinthereadablelistener,whichisinvokedassoonasnewdataisavailable.Theread()methodreturnsnullwhenthereisnomoredataavailableintheinternalbuffers;insuchacase,wehavetowaitforanotherreadableeventtobefired-tellingusthatwecanreadagain-orwaitfortheendeventthatsignalstheendofthestream.Whenastreamisworkinginbinarymode,wecanalsospecifythatweareinterestedinreadingaspecificamountofdatabypassingasizevaluetotheread()method.Thisisparticularlyusefulwhenimplementingnetworkprotocolsorwhenparsingspecificdataformats.

Now,wearereadytorunthereadStdinmoduleandexperimentwithit.Let'stypesomecharactersintheconsoleandthenpressEntertoseethedataechoedbackintothestandardoutput.Toterminatethestreamandhencegenerateagracefulendevent,weneedtoinsertanEOF(End-Of-File)character(usingCtrl+ZonWindowsorCtrl+DonLinux).

Wecanalsotrytoconnectourprogramwithotherprocesses;thisispossibleusingthepipeoperator(|),whichredirectsthestandardoutputofaprogramtothestandardinputofanother.Forexample,wecanrunacommandsuchasthefollowing:

cat<pathtoafile>|nodereadStdin

Thisisanamazingdemonstrationofhowthestreamingparadigmisauniversal

Page 154: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

interface,whichenablesourprogramstocommunicate,regardlessofthelanguagetheyarewrittenin.

Theflowingmode

Anotherwaytoreadfromastreamisbyattachingalistenertothedataevent;thiswillswitchthestreamintousingtheflowingmodewherethedataisnotpulledusingread(),butinsteadit'spushedtothedatalistenerassoonasitarrives.Forexample,thereadStdinapplicationthatwecreatedearlierwilllooklikethisusingtheflowingmode:

process.stdin

.on('data',function(chunk){

console.log('Newdataavailable');

console.log(

'Chunkread:('+chunk.length+')"'+

chunk.toString()+'"'

);

})

.on('end',function(){

process.stdout.write('Endofstream');

});

Theflowingmodeisaninheritanceoftheoldversionofthestreaminterface(alsoknownasStreams1),andofferslessflexibilitytocontroltheflowofdata.WiththeintroductionoftheStreams2interface,theflowingmodeisnotthedefaultworkingmode;toenableit,it'snecessarytoattachalistenertothedataeventorexplicitlyinvoketheresume()method.Totemporarilystopthestreamfromemittingdataevents,wecantheninvokethepause()method,causinganyincomingdatatobecachedintheinternalbuffer.

NoteCallingpause()doesnotcausethestreamtoswitchbacktothenon-flowingmode.

ImplementingReadablestreamsNowthatweknowhowtoreadfromastream,thenextstepistolearnhowtoimplementanewReadablestream.Todothis,it'snecessarytocreateanewclassbyinheritingtheprototypeofstream.Readable.Theconcretestreammustprovideanimplementationofthe_read()method,whichhasthefollowingsignature:

Page 155: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

readable._read(size)

TheinternalsoftheReadableclasswillcallthe_read()method,whichinturnwillstarttofilltheinternalbufferusingpush():

readable.push(chunk)

NotePleasenotethatread()isamethodcalledbythestreamconsumers,while_read()isamethodtobeimplementedbyastreamsubclassandshouldneverbecalleddirectly.Theunderscoreusuallyindicatesthatthemethodisnotpublicandshouldnotbecalleddirectly.

TodemonstratehowtoimplementthenewReadablestreams,wecantrytoimplementastreamthatgeneratesrandomstrings.Let'screateanewmodulecalledrandomStream.jsthatwillcontainthecodeofourstringgenerator.Atthetopofthefile,wewillloadourdependencies:

varstream=require('stream');

varutil=require('util');

varchance=require('chance').Chance();

Nothingspecialhere,exceptthatweareloadinganpmmodulecalledchance(https://npmjs.org/package/chance),whichisalibraryforgeneratingallsortsofrandomvalues,fromnumberstostringstoentiresentences.

ThenextstepistocreateanewclasscalledRandomStreamandthatspecifiesstream.Readableasitsparent:

functionRandomStream(options){

stream.Readable.call(this,options);

}

util.inherits(RandomStream,stream.Readable);

Intheprecedingcode,wecalltheconstructoroftheparentclasstoinitializeitsinternalstate,andforwardtheoptionsargumentreceivedasinput.Thepossibleparameterspassedthroughtheoptionsobjectinclude:

TheencodingargumentthatisusedtoconvertBufferstoStrings(defaultstonull)Aflagtoenabletheobjectmode(objectModedefaultstofalse)

Page 156: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theupperlimitofthedatastoredintheinternalbufferafterwhichnomorereadingfromthesourceshouldbedone(highWaterMarkdefaultsto16KB)

Okay,nowthatwehaveournewRandomStreamconstructorready,wecanproceedwithimplementingthe_read()method:

RandomStream.prototype._read=function(size){

varchunk=chance.string();//[1]

console.log('Pushingchunkofsize:'+chunk.length);

this.push(chunk,'utf8');//[2]

if(chance.bool({likelihood:5})){//[3]

this.push(null);

}

}

module.exports=RandomStream;

Theprecedingmethodisexplainedasfollows:

1. Themethodgeneratesarandomstringusingchance.2. Itpushesthestringintotheinternalreadingbuffer.Notethat,sinceweare

pushingaString,wealsospecifytheencoding,utf8(thisisnotnecessaryifthechunkissimplyabinaryBuffer).

3. Itterminatesthestreamrandomly,withalikelihoodof5percent,bypushingnullintotheinternalbuffertoindicateanEOFsituationor,inotherwords,theendofthestream.

Wecanalsoseethatthesizeargumentgivenininputtothe_read()functionisignored,asitisanadvisoryparameter.Wecansimplyjustpushalltheavailabledata,butiftherearemultiplepushesinsidethesameinvocation,thenweshouldcheckwhetherpush()returnsfalse,asthiswouldmeanthattheinternalbufferhasreachedthehighWaterMarklimitandweshouldstopaddingmoredatatoit.

That'sitforRandomStream;wearenotreadytouseit.Let'screateanewmodulenamedgenerateRandom.jsinwhichweinstantiateanewRandomStreamobjectandpullsomedatafromit:

varRandomStream=require('./randomStream');

varrandomStream=newRandomStream();

randomStream.on('readable',function(){

varchunk;

while((chunk=randomStream.read())!==null){

console.log("Chunkreceived:"+chunk.toString());

}

});

Now,everythingisreadyforustotryournewcustomstream.Simplyexecutethe

Page 157: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

generateRandommoduleasusualandwatcharandomsetofstringsflowingonthescreen.

WritablestreamsAwritablestreamrepresentsadatadestination;inNode.js,it'simplementedusingtheWritableabstractclass,whichisavailableinthestreammodule.

WritingtoastreamPushingsomedatadownawritablestreamisastraightforwardbusiness;allweneedtodoistousethewrite()method,whichhasthefollowingsignature:

writable.write(chunk,[encoding],[callback])

TheencodingargumentisoptionalandcanbespecifiedifchunkisString(defaultstoutf8,ignoredifchunkisBuffer);thecallbackfunctioninsteadiscalledwhenthechunkisflushedintotheunderlyingresourceandisoptionalaswell.

Tosignalthatnomoredatawillbewrittentothestream,wehavetousetheend()method:

writable.end([chunk],[encoding],[callback])

Wecanprovideafinalchunkofdatathroughtheend()method;inthiscasethecallbackfunctionisequivalenttoregisteringalistenertothefinishevent,whichisfiredwhenallthedatawritteninthestreamhasbeenflushedintotheunderlyingresource.

Now,let'sshowhowthisworksbycreatingasmallHTTPserverthatoutputsarandomsequenceofstrings:

varchance=require('chance').Chance();

require('http').createServer(function(req,res){

res.writeHead(200,{'Content-Type':'text/plain'});//[1]

while(chance.bool({likelihood:95})){//[2]

res.write(chance.string()+'\n');//[3]

}

res.end('\nTheend...\n');//[4]

res.on('finish',function(){//[5]

console.log('Alldatawassent');

});

}).listen(8080,function(){

console.log('Listening');

});

Page 158: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheHTTPserverthatwecreatedwritesintotheresobject,whichisaninstanceofhttp.ServerResponseandalsoaWritablestream.Whathappensisexplainedasfollows:

1. WefirstwritetheheadoftheHTTPresponse.NotethatwriteHead()isnotapartoftheWritableinterface;infact,it'sanauxiliarymethodexposedbythehttp.ServerResponseclass.

2. Westartaloopthatterminateswithalikelihoodoffivepercent(weinstructchance.bool()toreturntruefor95percentofthetime).

3. Insidetheloop,wewritearandomstringintothestream.4. Onceweareoutoftheloop,wecallend()onthestream,indicatingthatno

moredatawillbewritten.Also,weprovideafinalstringtobewrittenintothestreambeforeendingit.

5. Finally,weregisteralistenerforthefinishevent,whichwillbefiredwhenallthedatahasbeenflushedintotheunderlyingsocket.

Wecancallthissmallmodule,entropyServer.js,andthenexecuteit.Totesttheserver,wecanopenabrowserattheaddresshttp://localhost:8080,orusecurlfromtheterminalasfollows:

curllocalhost:8080

Atthispoint,theservershouldstartsendingrandomstringstotheHTTPclientthatyouchose(pleasebearinmindthatsomebrowsersmightbufferthedata,andthestreamingbehaviormightnotbeapparent).

NoteAninterestingcuriosityisthefactthathttp.ServerResponseisactuallyaninstanceoftheoldStreamclass(http://nodejs.org/docs/v0.8.0/api/stream.html).It'simportanttostate,though,thatthisdoesnotaffectourexample,astheinterfaceandbehavioronthewritablesideremainalmostthesameinthenewerstream.Writableclass.

Back-pressureSimilartoaliquidflowinginarealpipingsystem,Node.jsstreamscanalsosufferfrombottlenecks,wheredataiswrittenfasterthanthestreamcanconsumeit.Themechanismtocopewiththisproblemconsistsofbufferingtheincomingdata;however,ifthestreamdoesn'tgiveanyfeedbacktothewriter,wemightincura

Page 159: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

situationwheremoreandmoredataisaccumulatedintotheinternalbuffer,leadingtoundesiredlevelsofmemoryusage.

Topreventthisfromhappening,writable.write()willreturnfalsewhentheinternalbufferexceedsthehighWaterMarklimit.TheWritablestreamshaveahighWaterMarkproperty,whichisthelimitoftheinternalbuffersizebeyondwhichthewrite()methodstartsreturningfalse,indicatingthattheapplicationshouldnowstopwriting.Whenthebufferisemptied,thedraineventisemitted,communicatingthatit'ssafetostartwritingagain.Thismechanismiscalledback-pressure.

NoteThemechanismdescribedinthissectionissimilarlyapplicabletoReadablestreams.Infact,back-pressureexistsintheReadablestreamstoo,andit'striggeredwhenthepush()method,whichisinvokedinside_read(),returnsfalse.However,it'saproblemspecifictostreamimplementers,sowewilldealwithitlessfrequently.

Wecanquicklydemonstratehowtotakeintoaccounttheback-pressureofaWritablestream,bymodifyingtheentropyServerthatwecreatedbefore:

varchance=require('chance').Chance();

require('http').createServer(function(req,res){

res.writeHead(200,{'Content-Type':'text/plain'});

functiongenerateMore(){//[1]

while(chance.bool({likelihood:95})){

varshouldContinue=res.write(

chance.string({length:(16*1024)–1})//[2]

);

if(!shouldContinue){//[3]

console.log('Backpressure');

returnres.once('drain',generateMore);

}

}

res.end('\nTheend...\n',function(){

console.log('Alldatawassent');

});

}

generateMore();

}).listen(8080,function(){

console.log('Listening');

});

Page 160: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Themostimportantstepsofthepreviouscodecanbesummarizedasfollows:

1. WewrappedthemainlogicintoafunctioncalledgenerateMore().2. Toincreasethechancesofreceivingsomeback-pressure,weincreasedthesize

ofthedatachunkto16KB-1Byte,whichisveryclosetothedefaulthighWaterMarklimit.

3. Afterwritingachunkofdata,wecheckthereturnvalueofres.write();ifwereceivefalse,itmeansthattheinternalbufferisfullandweshouldstopsendingmoredata.Inthiscase,weexitfromthefunction,andregisteranothercycleofwritesforwhenthedraineventisemitted.

Ifwenowtrytoruntheserveragain,andthengenerateaclientrequestwithcurl,thereisagoodprobabilitythattherewillbesomeback-pressure,astheserverproducesdataataveryhighrate,fasterthantheunderlyingsocketcanhandle.

ImplementingWritablestreamsWecanimplementanewWritablestreambyinheritingtheprototypeofstream.Writableandprovidinganimplementationforthe_write()method.Let'strytodoitimmediatelywhilediscussingthedetailsalongtheway.

Let'sbuildaWritablestreamthatreceivesobjectsinthefollowingformat:

{

path:<pathtoafile>

content:<stringorbuffer>

}

Foreachoneoftheseobjects,ourstreamhastosavethecontentpartintoafilecreatedatthegivenpath.Wecanimmediatelyseethattheinputofourstreamareobjects,andnotstringsorbuffers;thismeansthatourstreamhastoworkinobjectmode.

Let'scallthemoduletoFileStream.jsand,asthefirststep,let'sloadallthedependenciesthatwearegoingtouse:

varstream=require('stream');

varfs=require('fs');

varutil=require('util');

varpath=require('path');

varmkdirp=require('mkdirp');

Next,wehavetocreatetheconstructorofournewstream,whichinheritstheprototypefromstream.Writable:

Page 161: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

functionToFileStream(){

stream.Writable.call(this,{objectMode:true});

};

util.inherits(ToFileStream,stream.Writable);

Again,wehadtoinvoketheparentconstructortoinitializeitsinternalstate;wealsoprovideanoptionsobjectthatspecifiesthatthestreamworksinanobjectmode(objectMode:true).Otheroptionsacceptedbystream.Writableareasfollows:

highWaterMark(thedefaultis16KB):Thiscontrolstheback-pressurelimit.decodeStrings(defaultstotrue):Thisenablestheautomaticdecodingofstringsintobinarybuffersbeforepassingthemtothe_write()method.Thisoptionisignoredintheobjectmode.

Finally,weneedtoprovideanimplementationforthe_write()method:

ToFileStream.prototype._write=function(chunk,encoding,

callback){

varself=this;

mkdirp(path.dirname(chunk.path),function(err){

if(err){

returncallback(err);

}

fs.writeFile(chunk.path,chunk.content,callback);

});

}

module.exports=ToFileStream;

Thisisagoodtimetoanalyzethesignatureofthe_write()method.Asyoucansee,themethodacceptsadatachunk,anencoding(whichmakessenseonlyifweareinthebinarymodeandthestreamoptiondecodeStringsissettofalse).Also,themethodacceptsacallbackfunction,whichneedstobeinvokedwhentheoperationcompletes;it'snotnecessarytopasstheresultoftheoperationbut,ifneeded,wecanstillpassanerrorthatwillcausethestreamtoemitanerrorevent.

Now,totrythestreamthatwejustbuilt,wecancreateanewmodule,calledforexample,writeToFile.js,andperformsomewriteoperationsagainstthestream:

varToFileStream=require('./toFileStream');

vartfs=newToFileStream();

tfs.write({path:"file1.txt",content:"Hello"});

tfs.write({path:"file2.txt",content:"Node.js"});

tfs.write({path:"file3.txt",content:"Streams"});

tfs.end(function(){

console.log("Allfilescreated");

});

Page 162: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Withthis,wecreatedandusedourfirstcustomWritablestream.Runthenewmoduleasusualtocheckitsoutput.

DuplexstreamsADuplexstreamisastreamthatisbothReadableandWritable.Itisusefulwhenwewanttodescribeanentitythatisbothadatasourceandadatadestination,asforexample,networksockets.Duplexstreamsinheritthemethodsofbothstream.Readableandstream.Writable,sothisisnothingnewtous.Thismeansthatwecanread()orwrite()data,orlistenforboththereadableanddrainevents.

TocreateacustomDuplexstream,wehavetoprovideanimplementationforboth_read()and_write();theoptionsobjectpassedtotheDuplex()constructorisinternallyforwardedtoboththeReadableandWritableconstructors.Theoptionsarethesameasthosewealreadydiscussedintheprevioussections,withtheadditionofanewonecalledallowHalfOpen(defaultstotrue)thatifsettofalsewillcauseboththeparts(ReadableandWritable)ofthestreamtoendifonlyoneofthemdoes.

NoteTohaveaDuplexstreamworkingintheobjectmodeononesideandbinarymodeontheother,weneedtomanuallysetthefollowingpropertiesfromwithinthestreamconstructor:

this._writableState.objectMode

this._readableState.objectMode

TransformstreamsTheTransformstreamsareaspecialkindofDuplexstreamthatarespecificallydesignedtohandledatatransformations.

InasimpleDuplexstream,thereisnoimmediaterelationshipbetweenthedatareadfromthestreamandthedatawrittenintoit(atleast,thestreamisagnostictosucharelationship).ThinkaboutaTCPsocket,whichjustsendsandreceivesdatatoandfromtheremotepeer;thesocketisnotawareofanyrelationshipbetweentheinputandoutput.ThefollowingdiagramillustratesthedataflowinaDuplexstream:

Page 163: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Ontheotherside,TransformstreamsapplysomekindoftransformationtoeachchunkofdatathattheyreceivefromtheirWritablesideandthenmakethetransformeddataavailableontheirReadableside.ThefollowingdiagramshowshowthedataflowsinaTransformstream:

Fromtheoutside,theinterfaceofaTransformstreamisexactlylikethatofaDuplexstream.However,whenwewanttobuildanewDuplexstreamwehavetoprovidethe_read()and_write()methodswhile,forimplementinganewTransformstream,wehavetofillinanotherpairofmethods:_transform()and_flush().

Let'sshowhowtocreateanewTransformstreamwithanexample.

ImplementingTransformstreamsLet'simplementaTransformstreamthatreplacesalltheoccurrencesofagivenstring.Todothis,wehavetocreateanewmodulenamedreplaceStream.js.Asalways,wewillstartbuildingthemodulefromitsdependencies,creatingtheconstructorandextendingitsprototypewiththeparentstreamclass:

varstream=require('stream');

varutil=require('util');

functionReplaceStream(searchString,replaceString){

stream.Transform.call(this,{decodeStrings:false});

this.searchString=searchString;

this.replaceString=replaceString;

this.tailPiece='';

}

util.inherits(ReplaceStream,stream.Transform);

Weassumethatthestreamwillhandleonlytext,soweinitializetheparentconstructorbysettingthedecodeStringsoptionstofalse;thisallowsustoreceivestringsinsteadofbuffersinsidethe_transform()method.

Page 164: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,let'simplementthe_transform()methoditself:

ReplaceStream.prototype._transform=function(chunk,encoding,

callback){

varpieces=(this.tailPiece+chunk)//[1]

.split(this.searchString);

varlastPiece=pieces[pieces.length-1];

vartailPieceLen=this.searchString.length-1;

this.tailPiece=lastPiece.slice(-tailPieceLen);//[2]

pieces[pieces.length-1]=lastPiece.slice(0,-

tailPieceLen);

this.push(pieces.join(this.replaceString));//[3]

callback();

}

The_transform()methodhaspracticallythesamesignatureasthatofthe_write()methodoftheWritablestreambut,insteadofwritingdataintoanunderlyingresource,itpushesitintotheinternalbufferusingthis.push(),exactlyaswewoulddointhe_read()methodofaReadablestream.ThisconfirmshowthetwosidesofaTransformstreamareactuallyconnected.

The_trasform()methodofReplaceStreamimplementsthecoreofouralgorithm.Tosearchandreplaceastringinabufferisaneasytask;however,it'satotallydifferentstorywhenthedataisstreamingandpossiblematchesmightbedistributedacrossmultiplechunks.Theprocedurefollowedbythecodecanbeexplainedasfollows:

1. OuralgorithmsplitsthechunkusingthesearchStringfunctionasaseparator.2. Then,ittakesthelastitemofthearraygeneratedbytheoperationandextracts

thelastsearchString.length-1characters.TheresultissavedintothetailPiecevariableanditwillbeprependedtothenextchunkofdata.

3. Finally,allthepiecesresultingfromsplit()arejoinedtogetherusingreplaceStringasaseparatorandpushedintotheinternalbuffer.

Whenthestreamends,wemightstillhavealasttailPiecevariablenotpushedintotheinternalbuffer.That'sexactlywhatthe_flush()methodisfor;itisinvokedjustbeforethestreamisendedandthisiswherewehaveonefinalchancetofinalizethestreamorpushanyremainingdatabeforecompletelyendingthestream.Let'simplementittocompleteourReplaceStreamclass:

ReplaceStream.prototype._flush=function(callback){

this.push(this.tailPiece);

callback();

}

module.exports=ReplaceStream;

Page 165: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

The_flush()methodtakesinonlyacallbackthatwehavetomakesuretoinvokewhenalltheoperationsarecomplete,causingthestreamtobeterminated.Withthis,wecompletedourReplaceStreamclass.

Now,it'stimetotrythenewstream.WecancreateanothermodulecalledreplaceStreamTest.jsthatwritessomedataandthenreadsthetransformedresult:

varReplaceStream=require('./replaceStream');

varrs=newReplaceStream('World','Node.js');

rs.on('data',function(chunk){

console.log(chunk);

});

rs.write('HelloW');

rs.write('orld!');

rs.end();

Tomakelifealittlebitharderforourstream,wespreadthesearchterm(whichisWorld)acrosstwodifferentchunks;thenusingtheflowingmodewereadfromthesamestream,loggingeachtransformedchunk.Runningtheprecedingprogramshouldproducethefollowingoutput:

Hel

loNode.js

!

NoteThereisafifthtypeofstreamthatisworthmentioning:stream.PassThrough.Unliketheotherstreamclassesthatwepresented,PassThroughisnotabstractandcanbeinstantiatedstraightawaywithouttheneedtoimplementanymethod.Itis,infact,aTransformstreamthatoutputseverydatachunkwithoutapplyinganytransformation.

ConnectingstreamsusingpipesTheconceptofUnixpipeswasinventedbyDouglasMcllroy;thisenabledtheoutputofaprogramtobeconnectedtotheinputofthenext.Takealookatthefollowingcommand:

echoHelloWorld!|seds/World/Node.js/g

Page 166: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Intheprecedingcommand,echowillwriteHelloWorld!toitsstandardoutput,whichisthenredirectedtothestandardinputofthesedcommand(thankstothepipe|operator);thensedreplacesanyoccurrenceofWorldwithNode.jsandprintstheresulttoitsstandardoutput(which,thistime,istheconsole).

Inasimilarway,Node.jsstreamscanbeconnectedtogetherusingthepipe()methodoftheReadablestream,whichhasthefollowinginterface:

readable.pipe(writable,[options])

Veryintuitively,thepipe()methodtakesthedatathatisemittedfromthereadablestreamandpumpsitintotheprovidedwritablestream.Also,thewritablestreamisendedautomaticallywhenthereadablestreamemitsanendevent(unless,wespecify{end:false}asoptions).Thepipe()methodreturnsthewritablestreampassedasanargumentallowingustocreatechainedinvocationsifsuchastreamisalsoReadable(asforexampleaDuplexorTransformstream).

Pipingtwostreamstogetherwillcreateasuctionwhichallowsthedatatoflowautomaticallytothewritablestream,sothereisnoneedtocallread()orwrite();butmostimportantlythereisnoneedtocontroltheback-pressureanymore,becauseit'sautomaticallytakencareof.

Tomakeaquickexample(therewillbetonsofthemcoming),wecancreateanewmodulecalledreplace.jswhichtakesatextstreamfromthestandardinput,appliesthereplacetransformation,andthenpushesthedatabacktothestandardoutput:

varReplaceStream=require('./replaceStream');

process.stdin

.pipe(newReplaceStream(process.argv[2],process.argv[3]))

.pipe(process.stdout);

TheprecedingprogrampipesthedatathatcomesfromthestandardinputintoaReplaceStreamandthenbacktothestandardoutput.Now,totrythissmallapplication,wecanleverageaUnixpipetoredirectsomedataintoitsstandardinput,asshowninthefollowingexample:

echoHelloWorld!|nodereplaceWorldNode.js

Thisshouldproducethefollowingoutput:

HelloNode.js

Thissimpleexampledemonstratesthatstreams(andinparticulartextstreams)isan

Page 167: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

universalinterface,andpipesarethewaytocomposeandinterconnectalmostmagicallyalltheseinterfaces.

NoteTheerroreventsarenotpropagatedautomaticallythroughthepipeline.Takeforexamplethiscodefragment:

stream1

.pipe(stream2)

.on('error',function(){});

Intheprecedingpipeline,wewillcatchonlytheerrorscomingfromstream2,whichisthestreamthatweattachedthelistenerto.Thismeansthat,ifwewanttocatchanyerrorgeneratedfromstream1,wehavetoattachanothererrorlistenerdirectlytoit.Wewilllaterseeapatternthatmitigatesthisinconvenience(combiningstreams).Also,weshouldnoticethatifthedestinationstreamemitsanerroritgetsautomaticallyunpipedfromthesourcestream,causingthepipelinetobreak.

UsefulpackagesforworkingwithstreamsWenowpresentsomenpmpackagesthatmightbeveryusefulwhenworkingwithstreams.

readable-streamWealreadymentionedhowthestreamsinterfacechangedconsiderablybetweenthe0.8and0.10branchesofNode.js.Traditionally,theinterfacesupporteduntilNode.js0.8iscalledStreams1,whilethenewerinterfacesupportedbyNode.js0.10iscalledStreams2.Thecoreteamdidagreatjobinmaintainingbackward-compatibility,sothatapplicationsimplementedusingtheStreams1interfacewillcontinuetoworkwiththe0.10branch;however,theviceversaisnottrue,sousingStreams2againstNode.js0.8willnotwork.Also,theupcoming0.12releasewillprobablybeshippedwithanewversionofthestreaminterface,Streams3,andsoonuntiltheinterfacestabilizes.

NoteThestreamsinterface,asofversion0.10,isstillmarkedasunstableonthe

Page 168: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

officialdocumentation(http://nodejs.org/docs/v0.10.0/api/stream.html).

Thankfully,wehaveanoptiontoshieldourcodefromthesechanges;it'scalledreadable-stream(https://npmjs.org/package/readable-stream),anpmpackagethatmirrorstheStreams2andStreams3implementationsoftheNode.jscore.Inparticular,usingthe1.0branchofreadable-streamwecanhavetheStreams2interfaceavailableevenifwerunourcodeagainstNode.js0.8.Ifinsteadwechoosethe1.1branch(probably1.2whenNode.js0.12willbereleased)wegettheStreams3interfaceregardlessoftheversionoftheNode.jsplatformused.

Thereadable-streampackageisadrop-inreplacementforthecorestreammodule(dependingontheversion),sousingitisassimpleasrequiringreadable-streaminsteadofstream:

varstream=require('readable-stream');

varReadable=stream.Readable;

varWritable=stream.Writable;

varDuplex=stream.Duplex;

varTransform=stream.Transform;

Protectingourlibrariesandapplicationsfromthechangesofthestillunstablestreamsinterfacecangreatlyreducethedefectsthatoriginatefromplatformincompatibilities.

NoteForadetailedrationaleontheuseofreadable-streamyoucanrefertothisexcellentarticlewrittenbyRodVagg:http://www.nearform.com/nodecrunch/dont-use-nodes-core-stream-module/

throughandfromThewaywecreatedcustomstreamssofardoesnotexactlyfollowtheNodeway;infact,inheritingfromabasestreamclassviolatesthesmallsurfaceareaprincipleandrequiressomeboilerplatecode.Thisdoesnotmeanthatstreamswerebadlydesigned;infact,weshouldnotforgetthatsincetheyareapartoftheNode.jscoretheymustbeasflexibleaspossibleinordertoenableuserlandmodulestoextendthemforabroadrangeofpurposes.

However,mostofthetimewedon'tneedallthepowerandextensibilitythatprototypalinheritancecangive,butusuallywhatwewantisjustaquickandanexpressivewaytodefinenewstreams.TheNode.jscommunity,ofcourse,createda

Page 169: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

solutionalsoforthis.Aperfectexampleisthrough2(https://npmjs.org/package/through2),asmalllibrarywhichsimplifiesthecreationofTransformstreams.Withthrough2,wecancreateanewTransformstreambyinvokingasimplefunction:

vartransform=through2([options],[_transform],[_flush])

Inasimilarway,from2(https://npmjs.org/package/from2)allowsustoeasilyandsuccinctlycreateReadablestreamswithcodesuchasthefollowing:

varreadable=from2([options],_read)

Theadvantagesofusingtheselittlelibrarieswillbeimmediatelyclearassoonaswestartshowingtheirusageintherestofthechapter.

NoteThepackagesthrough(https://npmjs.org/package/through)andfrom(https://npmjs.org/package/from)aretheoriginallibrariesbuiltontopofStreams1.

Page 170: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AsynchronouscontrolflowwithstreamsGoingthroughtheexamplesthatwepresentedsofar,itshouldbeclearthatstreamscanbeusefulnotonlytohandleI/O,butalsoasanelegantprogrammingpatternthatcanbeusedtoprocessanykindofdata.Buttheadvantagesdonotendatthesimpleappearance;streamscanalsobeleveragedtoturnasynchronouscontrolflowintoflowcontrol,aswewillseeinthissection.

SequentialexecutionBydefault,streamswillhandledatainasequence,forexample,a_transform()functionofaTransformstreamwillneverbeinvokedagainwiththenextchunkofdata,untilthepreviousinvocationcompletesbyexecutingcallback().Thisisanimportantpropertyofstreams,crucialforprocessingeachchunkintherightorder,butitcanalsobeexploitedtoturnstreamsintoanelegantalternativetothetraditionalcontrolflowpatterns.

Somecodeisalwaysbetterthantoomuchexplanation,solet'sworkonanexampletodemonstratehowwecanusestreamstoexecuteasynchronoustasksinasequence.Let'screateafunctionthatconcatenatesasetoffilesreceivedasinput,makingsuretohonortheorderinwhichtheyareprovided.Let'screateanewmodulecalledconcatFiles.jsandlet'sdefineitscontentsstartingfromitsdependencies:

varfromArray=require('from2-array');

varthrough=require('through2');

varfs=require('fs');

Wewillbeusingthrough2tosimplifythecreationofTransformstreamsandfrom2-arrayinordertocreateareadablestreamfromanarrayofobjects.

Next,wecandefinetheconcatFiles()function:

functionconcatFiles(destination,files,callback){

vardestStream=fs.createWriteStream(destination);

fromArray.obj(files)//[1]

.pipe(through.obj(function(file,enc,done){//[2]

varsrc=fs.createReadStream(file);

src.pipe(destStream,{end:false});

src.on('end',function(){//[3]

done();

});

Page 171: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}))

.on('finish',function(){//[4]

destStream.end();

callback();

});

}

module.exports=concatFiles;

Theprecedingfunctionimplementsasequentialiterationoverthefilesarraybytransformingitintoastream.Theprocedurefollowedbythefunctionisexplainedasfollows:

1. First,weusefrom2-arraytocreateaReadablestreamfromthefilesarray.2. Next,wecreateathrough(Transform)streamtohandleeachfileinthe

sequence.Foreachfile,wecreateaReadablestreamandwepipeitintodestStream,whichrepresentstheoutputfile.WemakesurenottoclosedestStreamafterthesourcefilefinishesreading,byspecifying{end:false}intothepipe()options.

3. WhenallthecontentsofthesourcefilehavebeenpipedintodestStream,weinvokedone(),whichtriggerstheprocessingofthenextfile.

4. Whenallthefileshavebeenprocessed,thefinisheventisfired;wecanfinallyenddestStreamandinvokethecallback()functionofconcatFiles(),whichsignalsthecompletionofthewholeoperation.

Wecannowtrytousethelittlemodulewejustcreated.Let'sdothatinanewfile,calledconcat.js:

varconcatFiles=require('./concatFiles');

concatFiles(process.argv[2],process.argv.slice(3),function()

{

console.log('Filesconcatenatedsuccesfully');

});

Wecannowruntheprecedingprogrambypassingthedestinationfileasthefirstcommandlineargumentfollowedbyalistoffilestoconcatenate,forexample:

nodeconcatallTogether.txtfile1.txtfile2.txt

ThisshouldcreateanewfilecalledallTogether.txtcontaining,inorder,thecontentsoffile1.txtandfile2.txt.

WiththeconcatFiles()function,wewereabletoobtainanasynchronoussequentialiterationusingonlystreams.AswesawinChapter2,AsynchronousControlFlowPatterns,thiswouldhaverequiredtheuseofaniterator,ifimplementedwithpureJavaScript,oranexternallibrarysuchasasync.Wehavenowprovidedanother

Page 172: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

optionforachievingthesameresult,whichasweseeisalsoverycompactandelegant.

NotePattern:Useastream,orcombinationofstreams,toeasilyiterateoverasetofasynchronoustasksinsequence.

UnorderedparallelexecutionWejustsawthatstreamsprocesseachdatachunkinasequence,butsometimesthiscanbeabottleneckaswewouldnotmakethemostoftheNode.jsconcurrency.Ifwehavetoexecuteaslowasynchronousoperationforeverydatachunk,itcanbeadvantageoustoparallelizetheexecutionandspeeduptheoverallprocess.Ofcourse,thispatterncanonlybeappliedifthereisnorelationshipbetweeneachchunkofdata,whichmighthappenfrequentlyforobjectstreams,butveryrarelyforbinarystreams.

NoteCaution:parallelstreamscannotbeusedwhentheorderinwhichthedataisprocessedisimportant.

ToparallelizetheexecutionofaTransformstream,wecanapplythesamepatternsthatwelearnedinChapter2,AsynchronousControlFlowPatterns,butwithsomeadaptationstogetthemworkingwithstreams.Let'sseehowthisworks.

ImplementinganunorderedparallelstreamLet'sdemonstratethisimmediatelywithanexample;let'screateamodulecalledparallelStream.jsanddefineagenericTransformstreamthatexecutesagiventransformfunctioninparallel.Let'sstarttodefineitsconstructor:

varstream=require('stream');

varutil=require('util');

functionParallelStream(userTransform){

stream.Transform.call(this,{objectMode:true});

this.userTransform=userTransform;

this.running=0;

Page 173: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

this.terminateCallback=null;

}

util.inherits(ParallelStream,stream.Transform);

TheconstructoracceptsauserTransform()function,whichisthensavedasaninstancevariable;wealsoinvoketheparentconstructorandforconvenienceweenabletheobjectmodebydefault.

Next,itistheturnofthe_transform()method:

ParallelStream.prototype._transform=

function(chunk,enc,done){

this.running++;

this.userTransform(chunk,enc,

this._onComplete.bind(this));

done();

}

Inthe_transform()method,weexecutetheuserTransform()function,thenweincrementthecountofrunningtasks;finally,wenotifythatthecurrenttransformationstepiscompletebyinvokingdone().Thetrickfortriggeringtheprocessingofanotheriteminparallelisexactlythis;wearenotwaitingfortheuserTransform()functiontocompletebeforeinvokingdone(),insteadwedoitimmediately.Ontheotherhand,weprovideaspecialcallbacktouserTransform(),whichisthethis._onComplete()method(wearegoingtodefineitinamoment);thisallowsustogetnotifiedwhentheuserTransform()completes.

Next,itistheturnofthe_flush()method:

ParallelStream.prototype._flush=function(done){

if(this.running>0){

this.terminateCallback=done;

}else{

done();

}

}

The_flush()methodisinvokedjustbeforethestreamterminates,soiftherearestilltasksrunningwecanputonholdthereleaseofthefinisheventbynotinvokingthedone()callbackimmediately;instead,weassignittothethis.terminateCallbackvariable.Tounderstandhowthestreamisthenproperlyterminated,wehavetolookintothe_onComplete()method:

ParallelStream.prototype._onComplete=function(err){

this.running--;

if(err){

returnthis.emit('error',err);

Page 174: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

if(this.running===0){

this.terminateCallback&&this.terminateCallback();

}

}

module.exports=ParallelStream;

The_onComplete()methodisinvokedeverytimeanasynchronoustaskcompletes.Itcheckswhetherthereareanymoretasksrunningand,iftherearenone,itinvokesthethis.terminateCallback()function,whichwillcausethestreamtoend,releasingthefinisheventwhichwasputonholdinthe_flush()method.

TheParallelStreamclass,wejustbuiltallowsustoeasilycreateaTransformstreamwhichexecutesitstasksinparallel,butthereisacaveat:itdoesnotpreservetheorderoftheitemsastheyarereceived.Infact,asynchronousoperationscancompleteandpushdataatanytime,regardlessofwhentheyarestarted.Weimmediatelyunderstandthatthispropertydoesnotplaywellwithbinarystreamswheretheorderofdatausuallymatters,butitcansurelybeusefulwithsometypesofobjectstreams.

ImplementingaURLstatusmonitoringapplicationNow,let'sapplyourParallelStreamtoaconcreteexample.Let'simaginethatwewantedtobuildasimpleservicetomonitorthestatusofabiglistofURLs.Let'simaginealltheseURLsarecontainedinasinglefileandarenewlineseparated.

Streamscanofferaveryefficientandelegantsolutiontothisproblem,especiallyifweuseourParallelStreamtoparallelizethecheckingoftheURLs.

Let'sbuildthissimpleapplicationimmediatelyinanewmodulecalledcheckUrls.js:

varfs=require('fs');

varsplit=require('split');

varrequest=require('request');

varParallelStream=require('./parallelStream');

fs.createReadStream(process.argv[2])//[1]

.pipe(split())//[2]

.pipe(newParallelStream(function(url,enc,done){

//[3]

if(!url)returndone();

varself=this;

request.head(url,function(err,response){

self.push(url+'is'+(err?'down':'up')+'\n');

done();

});

}))

.pipe(fs.createWriteStream('results.txt'))//[4]

Page 175: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

.on('finish',function(){

console.log('Allurlswerechecked');

});

Aswecansee,withstreamsourcodelooksveryelegantandstraightforward;let'sseehowitworks:

1. First,wecreateaReadablestreamfromthefilegivenasinput.2. Wepipethecontentsoftheinputfilethroughsplit

(https://npmjs.org/package/split),aTransformstreamthatensuresoutputtingeachlineonadifferentchunk.

3. Then,it'sthetimetouseourParallelStreamtochecktheURL.WedothisbysendingaHEADrequestandwaitingforaresponse.Whenthecallbackisinvoked,wepushtheresultoftheoperationdownthestream.

4. Finally,alltheresultsarepipedintoafile,results.txt.

Now,wecanrunthecheckUrlsmodulewithacommandsuchasthis:

nodecheckUrlsurlList.txt

WherethefileurlList.txtcontainsalistofURLs,forexample:

http://www.example.com

http://www.example.com/link1

http://thiswillbedownforsure.com

Whenthecommandfinishesrunning,wewillseethatafileresults.txtwascreated.Thiscontainstheresultsoftheoperation,forexample:

http://thiswillbedownforsure.comisdown

http://www.example.com/link1isup

http://www.example.comisup

ThereisagoodprobabilitythattheorderinwhichtheresultsarewrittenisdifferentfromtheorderinwhichtheURLswerespecifiedintheinputfile.Thisisclearevidencethatourstreamexecutesitstasksinparallel,anditdoesnotenforceanyorderbetweenthevariousdatachunksinthestream.

NoteForthesakeofcuriosity,wemightwanttotryreplacingParallelStreamwithanormalthrough2stream,andcomparethebehaviorandperformancesofthetwo(youmightwanttodothisasanexercise).Wewillseethatusingthrough2isway

Page 176: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

moreslower,becauseeachURLwouldbecheckedinasequence,butalsothattheorderoftheresultsinthefileresults.txtwouldbepreserved.

UnorderedlimitedparallelexecutionIfwetrytorunthecheckUrlsapplicationagainstafilethatcontainsthousandsormillionsofURLs,wewillsurelyrunintotrouble.Ourapplicationwillcreateanuncontrollednumberofconnectionsallatonce,sendingaconsiderableamountofdatainparallelandpotentiallyunderminingthestabilityoftheapplicationandtheavailabilityoftheentiresystem.Aswealreadyknow,thesolutiontokeeptheloadandresourceusageundercontrolistolimittheconcurrencyoftheparalleltasks.

Let'sseehowthisworkswithstreamsbycreatingalimitedParallelStream.jsmodule,whichisanadaptationofparallelStream.jsthatwecreatedintheprevioussection.

Let'sseehowitlookslike,startingfromitsconstructor(wewillhighlightthechangedparts):

functionLimitedParallelStream(concurrency,userTransform){

stream.Transform.call(this,{objectMode:true});

this.userTransform=userTransform;

this.running=0;

this.terminateCallback=null;

this.continueCallback=null;

this.concurrency=concurrency;

}

Weneedaconcurrencylimittobetakenastheinputandthistimewearegoingtosavetwocallbacks,oneforanypending_transformmethod(continueCallback)andanotheroneforthecallbackofthe_flushmethod(terminateCallback).

Nextisthe_transform()method:

LimitedParallelStream.prototype._transform=

function(chunk,enc,done){

this.running++;

this.userTransform(chunk,enc,

this._onComplete.bind(this));

if(this.running<this.concurrency){

done();

}else{

this.continueCallback=done;

}

}

Page 177: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thistimeinthe_transform()method,wehavetocheckwhetherwehaveanyfreeexecutionslotsbeforeweinvokedone()andtriggertheprocessingofthenextitem.Ifwehavealreadyreachedthemaximumnumberofconcurrentrunningstreams,wecansimplysavethedone()callbackintothecontinueCallbackvariable,sothatitcanbeinvokedassoonasataskfinishes.

The_flush()methodremainsexactlythesameasintheParallelStreamclass,solet'smovedirectlytoimplementingthe_onComplete()method:

LimitedParallelStream.prototype._onComplete=

function(err,chunk){

this.running--;

if(err){

returnthis.emit('error',err);

}

vartmpCallback=this.continueCallback;

this.continueCallback=null;

tmpCallback&&tmpCallback();

if(this.running===0){

this.terminateCallback&&this.terminateCallback();

}

}

EverytimeataskcompletesweinvokeanysavedcontinueCallback()thatwillcausethestreamtounblock,triggeringtheprocessingofthenextitem.

That'sitforthelimitedParallelStreammodule;wecannowuseitinthecheckUrlsmoduleinplaceofparallelStreamandhavetheconcurrencyofourtaskslimitedtothevaluethatweset.

OrderedparallelexecutionTheparallelstreamsthatwecreatedpreviouslymightshuffletheorderoftheemitteddata,buttherearesituationswherethisisnotacceptable;sometimes,infact,itisnecessarytohaveeachchunkemittedinthesameorderinwhichitwasreceived.Butnotallthehopesarelost,wecanstillrunthetransformfunctioninparallel;allwehavetodoistosortthedataemittedbyeachtasksothatitfollowsthesameorderinwhichthedatawasreceived.

Thistechniqueinvolvestheuseofabuffertoreorderthechunkswhiletheyareemittedbyeachrunningtask.Forbrevity,wearenotgoingtoprovideanimplementationofsuchastream,asit'squiteverboseforthescopeofthisbook;whatwearegoingtodoinsteadisreuseoneoftheavailablepackagesonnpmbuiltforthisspecificpurpose,forexample,through2-parallel(https://npmjs.org/package/through2-parallel).

Page 178: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WecanquicklycheckthebehaviorofanorderedparallelexecutionbymodifyingourexistingcheckUrlsmodule.Let'ssaythatwewantourresultstobewritteninthesameorderastheURLsintheinputfile,whileexecutingourchecksinparallel.Wecandothisusingthrough2-parallel:

[...]

varthroughParallel=require('through2-parallel');

fs.createReadStream(process.argv[2])

.pipe(split())

.pipe(throughParallel.obj({concurrency:2},

function(url,enc,done){

[...]

})

)

.pipe(fs.createWriteStream('results.txt'))

.on('finish',function(){

console.log('Allurlswerechecked');

});

Aswesee,theinterfaceofthrough2-parallelisverysimilartothatofthrough2;theonlydifferenceisthatwecanalsospecifyaconcurrencylimitforthetransformfunctionthatweprovide.IfwetrytorunthisnewversionofcheckUrls,wewillnowseethattheresults.txtfileliststheresultsinthesameorderastheURLsappearintheinputfile.

NoteItisimportanttoseethat,eventhoughtheorderoftheoutputisthesameastheinput,theasynchronoustasksstillruninparallelandcanpossiblycompleteinanyorder.

Withthis,weconcludeouranalysisoftheasynchronouscontrolflowwithstreams;nextwearegoingtofocusonsomepipingpatterns.

Page 179: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

PipingpatternsAsinreal-lifeplumbing,Node.jsstreamsalsocanbepipedtogetherfollowingdifferentpatterns;wecan,infact,mergetheflowoftwodifferentstreamsintoone,splittheflowofonestreamintotwoormorepipes,orredirecttheflowbasedonacondition.Inthissection,wearegoingtoexplorethemostimportantplumbingtechniquesthatcanbeappliedtoNode.jsstreams.

CombiningstreamsInthischapter,westressedalotonthefactthatstreamsprovideasimpleinfrastructuretomodularizeandreuseourcode,butthereisonelastpiecemissinginthispuzzle:whatifwewanttomodularizeandreuseanentirepipeline?Whatifwewanttocombinemultiplestreamssothattheylooklikeonefromtheoutside?Thefollowingfigureshowswhatthismeans:

Fromtheprecedingdiagram,weshouldalreadygetahintofhowthisworks:

Whenwewriteintothecombinedstream,weareactuallywritingintothefirststreamofthepipelineWhenwereadfromthecombinedstream,weareactuallyreadingfromthelaststreamofthepipeline

AcombinedstreamisusuallyaDuplexstream,whichisbuiltbyconnectingthefirststreamtoitsWritablesideandthelaststreamtoitsReadableside.

TipTocreateaDuplexstreamoutoftwodifferentstreams,oneWritableandone

Page 180: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Readable,wecanuseannpmmodulesuchasduplexer2(https://npmjs.org/package/duplexer2).

Butthat'snotenough;infact,anotherimportantcharacteristicofacombinedstreamisthatithastocapturealltheerrorsthatareemittedfromanystreaminsidethepipeline.Aswealreadymentioned,anyerroreventisnotautomaticallypropagateddownthepipeline;so,ifwewanttohavepropererrormanagement(andweshould),wewillhavetoexplicitlyattachanerrorlistenertoeachstream.However,ifthecombinedstreamisreallyablackbox,thismeansthatwedon'thaveaccesstoanyofthestreamsinthemiddleofthepipeline;soit'scrucialforthecombinedstreamtoalsoactasanaggregatorforalltheerrorscomingfromanystreaminthepipeline.

Torecap,acombinedstreamhastwomajoradvantages:

Wecanredistributeitasablackbox,byhidingitsinternalpipelineWehavesimplifiederrormanagementaswedon'thavetoattachanerrorlistenertoeachstreaminthepipeline,butjusttothecombinedstreamitself

Combiningstreamsisaprettygenericandcommonpractice,soifwedon'thaveanyparticularneedwemightjustwanttoreuseanexistingsolutionsuchasmultipipe(https://www.npmjs.org/package/multipipe)orcombine-stream(https://www.npmjs.org/package/combine-stream),justtonameafew.

ImplementingacombinedstreamTomakeasimpleexample,let'sconsiderthecaseofthefollowingtwotransformstreams:

Onethatbothcompressesandencryptsthedata.Onethatbothdecompressesanddecryptsthedata.

Usingalibrarysuchasmultipipe,wecaneasilybuildthesestreamsbycombiningsomeofthestreamsthatwealreadyhaveavailablefromthecorelibraries(file'combinedStreams.js'):

varzlib=require('zlib');

varcrypto=require('crypto');

varcombine=require('multipipe');

varfs=require('fs');

module.exports.compressAndEncrypt=function(password){

returncombine(

zlib.createGzip(),

crypto.createCipher('aes192',password)

);

Page 181: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

module.exports.decryptAndDecompress=function(password){

returncombine(

crypto.createDecipher('aes192',password),

zlib.createGunzip()

);

}

Wecannowusethesecombinedstreams,asiftheywereblackboxes,forexample,tocreateasmallapplicationthatarchivesafile,bycompressingandencryptingit.Let'sdothatinanewmodulenamedarchive.js:

varfs=require('fs');

varcompressAndEncryptStream=

require('./combinedStreams').compressAndEncrypt;

fs.createReadStream(process.argv[3])

.pipe(compressAndEncryptStream(process.argv[2]))

.pipe(fs.createWriteStream(process.argv[3]+".gz.enc"));

Wecanfurtherimprovetheprecedingcodebybuildingacombinedstreamoutofthepipelinethatwecreated,thistimenottoobtainareusableblackboxbutonlytotakeadvantageofitsaggregatederrormanagement.Infact,aswealreadymentionedmanytimes,writingsomethingsuchasthefollowingwillonlycatchtheerrorsthatareemittedbythelaststream:

fs.createReadStream(process.argv[3])

.pipe(compressAndEncryptStream(process.argv[2]))

.pipe(fs.createWriteStream(process.argv[3]+".gz.enc"))

.on('error',function(err){

//Onlyerrorsfromthelaststream

console.log(err);

});

However,bycombiningallthestreamstogetherwecanfixtheproblemelegantly.Let'sthenrewritethe'archive.js'fileasfollows:

varcombine=require('multipipe');

varfs=require('fs');

varcompressAndEncryptStream=

require('./combinedStreams').compressAndEncrypt;

combine(

fs.createReadStream(process.argv[3]),

compressAndEncryptStream(process.argv[2]),

fs.createWriteStream(process.argv[3]+".gz.aes")

).on('error',function(err){

//thiserrormaycomefromanystreaminthepipeline

Page 182: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

console.log(err);

});

Aswecansee,wecannowattachanerrorlistenerdirectlytothecombinedstreamanditwillreceiveanyerroreventthatisemittedbyanyofitsinternalstreams.

Now,torunthearchivemodule,simplyspecifyapasswordandafileinthecommandlinearguments:

nodearchivemypassword/path/to/a/file.txt

Withthisexample,wehaveclearlydemonstratedhowimportantitistocombinestreams;fromoneaspect,itallowsustocreatereusablecompositionsofstreamsandfromanotheritsimplifiestheerrormanagementofapipeline.

ForkingstreamsWecanperformaforkofastreambypipingasingleReadablestreamintomultipleWritablestreams.Thisisusefulwhenwewanttosendthesamedatatodifferentdestinations,forexample,twodifferentsocketsortwodifferentfiles.Itcanalsobeusedwhenwewanttoperformdifferenttransformationsonthesamedata,orwhenwewanttosplitthedatabasedonsomecriteria.Thefollowingfiguregivesusagraphicalrepresentationofthispattern:

ForkingastreaminNode.jsisatrivialmatter;let'sseewhybyworkingonanexample.

ImplementingamultiplechecksumgeneratorLet'screateasmallutilitythatoutputsboththesha1andmd5hashesofagivenfile.Let'scallthisnewmodulegenerateHashes.jsandlet'sstartbyinitializingourchecksumstreams:

Page 183: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varfs=require('fs');

varcrypto=require('crypto');

varsha1Stream=crypto.createHash('sha1');

sha1Stream.setEncoding('base64');

varmd5Stream=crypto.createHash('md5');

md5Stream.setEncoding('base64');

Nothingspecialsofar;thenextpartofthemoduleisactuallywherewewillcreateaReadablestreamfromafileandforkittotwodifferentstreamsinordertoobtaintwootherfiles,onecontainingthesha1hashandtheothercontainingthemd5checksum:

varinputFile=process.argv[2];

varinputStream=fs.createReadStream(inputFile);

inputStream

.pipe(sha1Stream)

.pipe(fs.createWriteStream(inputFile+'.sha1'));

inputStream

.pipe(md5Stream)

.pipe(fs.createWriteStream(inputFile+'.md5'));

Verysimple,right?TheinputStreamvariableispipedintosha1Streamononesideandmd5Streamontheother.Thereareacoupleofthingstonote,though,thathappenbehindthescenes:

Bothmd5Streamandsha1StreamwillbeendedautomaticallywheninputStreamends,unlesswespecify{end:false}asanoptionwheninvokingpipe()Thetwoforksofthestreamwillreceivethesamedatachunks,sowemustbeverycarefulwhenperformingside-effectoperationsonthedata,asthatwouldaffecteverystreamthatweareforkingtoBack-pressurewillworkout-of-the-box;theflowcomingfrominputStreamwillgoasfastastheslowestbranchofthefork!

MergingstreamsMergingistheoppositeoperationtoforkingandconsistsofpipingasetofReadablestreamsintoasingleWritablestream,asshowninthefollowingfigure:

Page 184: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Mergingmultiplestreamsintooneisingeneralasimpleoperation;however,wehavetopayattentiontothewaywehandletheendevent,aspipingusingtheautoendoptionwillcausethedestinationstreamtobeendedassoonasoneofthesourcesends.Thiscanoftenleadtoanerrorsituation,astheotheractivesourceswillstillcontinuetowritetoanalreadyterminatedstream.Thesolutiontothisproblemistousetheoption{end:false}whenpipingmultiplesourcestoasingledestinationandtheninvokeend()onthedestinationonlywhenallthesourceshavecompletedreading.

CreatingatarballfrommultipledirectoriesTomakeasimpleexample,let'simplementasmallprogramthatcreatesatarballfromthecontentsoftwodifferentdirectories.Forthispurpose,wearegoingtointroducetwonewnpmpackages:

tar(https://npmjs.org/package/tar):astreaminglibrarytocreatetarballsfstream(https://npmjs.org/package/fstream):alibrarytocreateobjectstreamsfromfilesystemfiles

OurnewmoduleisgoingtobecalledmergeTar.js;let'sdefineitscontentsstartingfromsomeinitializationsteps:

vartar=require('tar');

varfstream=require('fstream');

varpath=require('path');

vardestination=path.resolve(process.argv[2]);

varsourceA=path.resolve(process.argv[3]);

varsourceB=path.resolve(process.argv[4]);

Intheprecedingcode,wearejustloadingallthedependenciesandinitializingthevariablesthatcontainthenameofthedestinationfileandthetwosourcedirectories(sourceAandsourceB).

Next,wewillcreatethetarstreamandpipeitintoitsdestination:

Page 185: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varpack=tar.Pack();

pack.pipe(fstream.Writer(destination));

Nowit'stimetoinitializethesourcestreams:

varendCount=0;

functiononEnd(){

if(++endCount===2){

pack.end();

}

}

varsourceStreamA=

fstream.Reader({type:"Directory",path:sourceA})

.on('end',onEnd);

varsourceStreamB=

fstream.Reader({type:"Directory",path:sourceB})

.on('end',onEnd);

Intheprecedingcode,wecreatedthestreamsthatreadfromboththetwosourcedirectories(sourceStreamAandsourceStreamB);thenforeachsourcestreamweattachanendlistener,whichwillterminatethepackstreamonlywhenboththedirectoriesarereadcompletely.

Finally,itistimetoperformtherealmerge:

sourceStreamA.pipe(pack,{end:false});

sourceStreamB.pipe(pack,{end:false});

Wepipeboththesourcesintothepackstreamandtakecaretodisabletheautoendingofthedestinationstreambyprovidingtheoption{end:false}tothetwopipe()invocations.

Withthis,wecompletedoursimpletarutility.Wecantrythisutilitybyprovidingthedestinationfileasthefirstcommandlineargument,followedbythetwosourcedirectories:

nodemergeTardest.tar/path/to/sourceA/path/to/sourceB

Toconcludethissection,it'sworthmentioningthat,onnpm,wecanfindafewmodulesthatcansimplifythemergingofstreams,forexample:

merge-stream(https://npmjs.org/package/merge-stream)multistream-merge(https://npmjs.org/package/multistream-merge)

Page 186: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Asforthelastcommentonthestreammergepattern,it'sworthremindingthatthedatapipedintothedestinationstreamisrandomlyintermingled;thisisapropertythatcanbeacceptableinsometypesofobjectstreams(aswesawinthelastexample)butitisoftenanundesiredeffectwhendealingwithbinarystreams.

However,thereisonevariationofthepatternthatallowsustomergestreamsinorder;itconsistsofconsumingthesourcestreamsoneaftertheother,whenthepreviousoneends,thenextonestartsemittingchunks(itislikeconcatenatingtheoutputofallthesources).Asalways,onnpmwecanfindsomepackagesthatalsodealwiththissituation,oneofthemismultistream(https://npmjs.org/package/multistream).

MultiplexinganddemultiplexingThereisaparticularvariationofthemergestreampatterninwhichwedon'treallywanttojustjoinmultiplestreamstogetherbut,instead,touseasharedchanneltodeliverthedataofasetofstreams.Thisisaconceptuallydifferentoperationbecausethesourcestreamsremainlogicallyseparatedinsidethesharedchannel,whichallowsustosplitthestreamagainoncethedatareachestheotherendofthesharedchannel.Thefollowingfigureclarifiesthesituation:

Theoperationofcombiningmultiplestreamstogether(inthiscasealsoknownaschannels)toallowtransmissionoverasinglestreamiscalledmultiplexing,whiletheoppositeoperation,namelyreconstructingtheoriginalstreamsfromthedatareceivedfromasharedstream,iscalleddemultiplexing.Thedevicesthatperformtheseoperationsarecalledmultiplexer(ormux)anddemultiplexer(ordemux)respectively.ThisisawidelystudiedareainComputerScienceandTelecommunicationsingeneral,asitisoneofthefoundationsofalmostanytypeofcommunicationmediasuchastelephony,radio,TV,andofcoursetheInternetitself.Forthescopeofthisbook,wewillnotgotoofarwiththeexplanations,asthisisavasttopic.

Whatwewanttodemonstrateinthissection,instead,ishowit'spossibletousea

Page 187: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

sharedNode.jsstreaminordertoconveymultiplelogicallyseparatedstreamsthatarethensplitagainattheotherendofthesharedstream.

BuildingaremoteloggerLet'suseanexampletodriveourdiscussion.Wewanttohaveasmallprogramthatstartsachildprocessandredirectsbothitsstandardoutputandstandarderrortoaremoteserver,whichinturnsavesthetwostreamsintotwoseparatefiles.So,inthiscasethesharedmediumisaTCPconnection,whilethetwochannelstobemultiplexedarethestdoutandstderrofachildprocess.Wewillleverageatechniquecalledpacketswitching,thesametechniquethatisusedbyprotocolssuchasIP,TCPorUDPandthatconsistsofwrappingthedataintopacketsallowingustospecifyvariousmetainformation,usefulformutiplexing,routing,controllingtheflow,checkingforcorrupteddata,andsoon.Theprotocolthatwearegoingtoimplementforourexampleisveryminimalist,infact,wewillsimplywrapourdataintopacketshavingthefollowingstructure:

Asshownintheprecedingfigure,thepacketcontainstheactualdata,butalsoaheader(ChannelID+Datalength),whichwillmakeitpossibletodifferentiatethedataofeachstreamandenablethedemultiplexertoroutethepackettotherightchannel.

Clientside–Multiplexing

Let'sstarttobuildourapplicationfromtheclientside.Withalotofcreativity,wewillcallthemoduleclient.js;thisrepresentsthepartoftheapplicationthatisresponsibleforstartingachildprocessandmultiplexingitsstreams.

So,let'sstartbydefiningthemodule.First,weneedsomedependencies:

varchild_process=require('child_process');

varnet=require('net');

varpath=require('path');

Then,let'simplementafunctionthatperformsthemultiplexingofalistofsources:

functionmultiplexChannels(sources,destination){

vartotalChannels=sources.length;

for(vari=0;i<sources.length;i++){

Page 188: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

sources[i]

.on('readable',function(i){//[1]

varchunk;

while((chunk=this.read())!==null){

varoutBuff=newBuffer(1+4+chunk.length);

//[2]

outBuff.writeUInt8(i,0);

outBuff.writeUInt32BE(chunk.length,1);

chunk.copy(outBuff,5);

console.log('Sendingpackettochannel:'+i);

destination.write(outBuff);//[3]

}

}.bind(sources[i],i))

.on('end',function(){//[4]

if(--totalChannels===0){

destination.end();

}

});

}

}

ThemutiplexChannels()functiontakesinasinputthesourcestreamstobemultiplexedandthedestinationchannel,andthenitperformsthefollowingsteps:

1. Foreachsourcestream,itregistersalistenerforthereadableeventwherewereadthedatafromthestreamusingthenon-flowingmode.

2. Whenachunkisread,wewrapitintoapacketthatcontainsinorder:1byte(UInt8)forthechannelID,4bytes(UInt32BE)forthepacketsize,andthentheactualdata.

3. Whenthepacketisready,wewriteitintothedestinationstream.4. Finally,weregisteralistenerfortheendeventsothatwecanterminatethe

destinationstreamwhenallthesourcestreamsareended.

TipOurprotocolistobeabletomultiplexupto8differentsourcestreamsbecauseweonlyhave1bytetoidentifythechannel.

Nowthelastpartofourclientbecomesveryeasy:

varsocket=net.connect(3000,function(){//[1]

varchild=child_process.fork(//[2]

process.argv[2],

process.argv.slice(3),

{silent:true}

);

multiplexChannels([child.stdout,child.stderr],socket);

Page 189: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//[3]

});

Inthislastcodefragment,weperformthefollowingoperations::

1. WecreateanewTCPclientconnectiontotheaddresslocalhost:3000.2. Westartthechildprocessbyusingthefirstcommand-lineargumentasthepath,

whileweprovidetherestoftheprocess.argvarrayasargumentsforthechildprocess.Wespecifytheoption{silent:true},sothatthechildprocessdoesnotinheritstdoutandstderroftheparent.

3. Finally,wetakestdoutandstderrofthechildprocessandwemultiplexthemintosocketusingthemutiplexChannels()function.

Serverside–demultiplexing

Nowwecantakecareofcreatingtheserversideoftheapplication(server.js),wherewedemultiplexthestreamsfromtheremoteconnectionandpipethemintotwodifferentfiles.Let'sstartbycreatingafunctioncalleddemultiplexChannel():

functiondemultiplexChannel(source,destinations){

varcurrentChannel=null;

varcurrentLength=null;

source

.on('readable',function(){//[1]

varchunk;

if(currentChannel===null){//[2]

chunk=this.read(1);

currentChannel=chunk&&chunk.readUInt8(0);

}

if(currentLength===null){//[3]

chunk=this.read(4);

currentLength=chunk&&chunk.readUInt32BE(0);

if(currentLength===null){

return;

}

}

chunk=this.read(currentLength);//[4]

if(chunk===null){

return;

}

console.log('Receivedpacketfrom:'+currentChannel);

destinations[currentChannel].write(chunk);//[5]

currentChannel=null;

currentLength=null;

})

.on('end',function(){//[6]

Page 190: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

destinations.forEach(function(destination){

destination.end();

});

console.log('Sourcechannelclosed');

});

}

Theprecedingcodemightlookcomplicatedbutitisnot;thankstothepullnatureofNode.jsReadablestreams,wecaneasilyimplementthedemultiplexingofourlittleprotocolasfollows:

1. Westartreadingfromthestreamusingthenon-flowingmode.2. First,ifwehavenotreadthechannelIDyet,wetrytoread1bytefromthe

streamandthentransformitintoanumber.3. Thenextstepistoreadthelengthofthedata.Weneed4bytesforthat,soit's

possible(evenifunlikely)thatwedon'thaveenoughdataintheinternalbuffer,whichwillcausethethis.read()invocationtoreturnnull.Insuchacase,wesimplyinterrupttheparsingandretryatthenextreadableevent.

4. Whenwefinallycanalsoreadthedatasize,weknowhowmuchdatatopullfromtheinternalbuffer,sowetrytoreaditall.

5. Whenwereadallthedata,wecanwriteittotherightdestinationchannel,makingsurethatweresetthecurrentChannelandcurrentLengthvariables(thesewillbeusedtoparsethenextpacket).

6. Lastly,wemakesuretoendallthedestinationchannelswhenthesourcechannelends.

Nowthatwecandemultiplexthesourcestream,let'sputournewfunctionatwork:

net.createServer(function(socket){

varstdoutStream=fs.createWriteStream('stdout.log');

varstderrStream=fs.createWriteStream('stderr.log');

demultiplexChannel(socket,[stdoutStream,stderrStream]);

}).listen(3000,function(){

console.log('Serverstarted');

});

Intheprecedingcode,wefirststartaTCPserverontheport3000,thenforeachconnectionthatwereceive,wewillcreatetwoWritablestreamspointingtotwodifferentfiles,oneforthestandardoutputandanotherforthestandarderror;theseareourdestinationchannels.Finally,weusedemultiplexChannel()todemultiplexthesocketstreamintostdoutStreamandstderrStream.

Runningthemux/demuxapplication

Now,wearereadytotryournewmux/demuxapplication,butfirstlet'screateasmall

Page 191: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Node.jsprogramtoproducesomesampleoutput;let'scallitgenerateData.js:

console.log("out1");

console.log("out2");

console.error("err1");

console.log("out3");

console.error("err2");

Okay,nowwearereadytotryourremoteloggingapplication.First,let'sstarttheserver:

nodeserver

Thentheclient,byprovidingthefilethatwewanttostartaschildprocess:

nodeclientgenerateData.js

Theclientwillrunalmostimmediately,butattheendoftheprocessthestandardinputandstandardoutputofthegenerateDataapplicationhavetraveledthroughonesingleTCPconnectionandthen,ontheserver,havebeendemultiplexedintotwoseparatefiles.

NotePleasemakeanotethat,asweareusingchild_process.fork()(http://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options),ourclientwillbeabletolaunchonlyotherNode.jsmodules.

MultiplexinganddemultiplexingobjectstreamsTheexamplethatwehavejustshowndemonstratedhowtomultiplexanddemultiplexabinary/textstream,butit'sworthmentioningthatthesamerulesapplyalsotoobjectstreams.Thegreatestdifferenceisthat,usingobjects,wealreadyhaveawaytotransmitthedatausingatomicmessages(theobjects),somultiplexingwouldbeaseasyassettingapropertychannelIDintoeachobject,whiledemultiplexingwouldsimplyinvolvereadingthechannelIDpropertyandroutingeachobjecttowardstherightdestinationstream.

Anotherpatterninvolvingonlydemultiplexingconsistsinroutingthedatacomingfromasourcedependingonsomecondition.Withthispattern,wecanimplementcomplexflows,suchastheoneshowninthefollowingdiagram:

Page 192: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thedemultiplexerusedinthesystemdescribedbytheprecedingdiagram,takesastreamofobjectsrepresentinganimalsanddistributeseachofthemtotherightdestinationstreambasedontheclassoftheanimal:reptiles,amphibians,andmammals.

Usingthesameprinciple,wecanalsoimplementanif-elsestatementforstreams;forsomeinspiration,takealookattheternary-streampackage(https://npmjs.org/package/ternary-stream)thatallowsustodoexactlythat.

Page 193: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryInthischapter,wehaveshedsomelightonNode.jsstreamsandtheirusecase,butatthesametimethisshouldhavethrownopenadoortoaprogrammingparadigmwithvirtuallyunlimitedpossibilities.WelearnedwhystreamsaresoacclaimedbytheNode.jscommunityandwemasteredtheirbasicfunctionality,enablingustodiscovermoreandnavigatecomfortablyinthisnewworld.Weanalyzedsomeadvancedpatternsandstartedtounderstandhowtoconnectstreamstogetherindifferentconfigurations,graspingtheimportanceofinteroperabilitywhichiswhatmakesstreamssoversatileandpowerful.

Ifwecan'tdosomethingwithonestream,weprobablycandoitbyconnectingotherstreamstogether,andthisworksgreatwiththeonethingpermodulephilosophy.Atthispoint,itshouldbeclearthatstreamsarenotjustagoodtoknowfeatureofNode.js;theyare,instead,anessentialpartofthis,acrucialpatterntohandlebinarydata,strings,andobjects.It'snotbychancethatanentirechapterwasdedicatedtothem.

Inthenextchapter,wewillfocusonthetraditionalobject-orienteddesignpatterns.Butdon'tbefooled;eventhoughJavaScriptisanobject-orientedlanguage,inNode.jsthefunctionalorhybridapproachisoftenpreferred.Getridofeveryprejudicebeforereadingthenextchapter.

Page 194: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter4.DesignPatternsAdesignpatternisareusablesolutiontoarecurringproblem;thetermisreallybroadinitsdefinitionandcanspanmultipledomainsofapplication.However,thetermisoftenassociatedwithawell-knownsetofobject-orientedpatternsthatwerepopularizedinthe90'sbythebook,DesignPatterns:ElementsofReusableObject-OrientedSoftware,PearsonEducationbythealmostlegendaryGangofFour(GoF):ErichGamma,RichardHelm,RalphJohnson,andJohnVlissides.Wewilloftenrefertothesespecificsetofpatternsastraditionaldesignpatterns,orGoFdesignpatterns.

Applyingthissetofobject-orienteddesignpatternsinJavaScriptisnotaslinearandformalasitwouldhappeninaclass-basedobject-orientedlanguage.Asweknow,JavaScriptismulti-paradigm,object-oriented,andprototype-based,andhasdynamictyping;ittreatsfunctionsasfirstclasscitizens,andallowsfunctionalprogrammingstyles.ThesecharacteristicsmakeJavaScriptaveryversatilelanguage,whichgivestremendouspowertothedeveloper,butatthesametime,itcausesafragmentationofprogrammingstyles,conventions,techniques,andultimatelythepatternsofitsecosystem.TherearesomanywaystoachievethesameresultusingJavaScriptthateverybodyhastheirownopiniononwhatthebestwayistoapproachaproblem.AcleardemonstrationofthisphenomenonistheabundanceofframeworksandopinionatedlibrariesintheJavaScriptecosystem;probably,nootherlanguagehaseverseensomany,especiallynowthatNode.jshasgivennewastonishingpossibilitiestoJavaScriptandhascreatedsomanynewscenarios.

Inthiscontext,thetraditionaldesignpatternstooareaffectedbythenatureofJavaScript.Therearesomanywaysinwhichtheycanbeimplementedsothattheirtraditional,stronglyobject-orientedimplementationisnotapatternanymore,andinsomecases,notevenpossiblebecauseJavaScript,asweknow,doesn'thaverealclassesorabstractinterfaces.Whatdoesn'tchangethough,istheoriginalideaatthebaseofeachpattern,theproblemitsolves,andtheconceptsattheheartofthesolution.

Inthischapter,wewillseehowsomeofthemostimportantGoFdesignpatternsapplytoNode.jsanditsphilosophy,thusrediscoveringtheirimportancefromanotherperspective.

Thedesignpatternsexploredinthischapterareasfollows:

FactoryProxyDecorator

Page 195: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AdapterStrategyStateTemplateMiddlewareCommand

NoteThischapterassumesthatthereaderhassomenotionofhowinheritanceworksinJavaScript.PleasealsobeadvisedthatthroughoutthischapterwewilloftenusegenericandmoreintuitivediagramstodescribeapatterninplaceofstandardUML,sincemanypatternscanhaveanimplementationbasednotonlyonclasses,butalsoonobjectsandevenfunctions.

FactoryWebeginourjourneystartingfromwhatprobablyisthemostsimpleandcommondesignpatterninNode.js:Factory.

AgenericinterfaceforcreatingobjectsWealreadystressedthefactthat,inJavaScript,thefunctionalparadigmisoftenpreferredtoapurelyobject-orienteddesign,foritssimplicity,usability,andsmallsurfacearea.Thisisespeciallytruewhencreatingnewobjectinstances.Infact,invokingafactory,insteadofdirectlycreatinganewobjectfromaprototypeusingthenewoperatororObject.create(),issomuchmoreconvenientandflexibleunderseveralaspects.

Firstandforemost,afactoryallowsustoseparatetheobjectcreationfromitsimplementation;essentially,afactorywrapsthecreationofanewinstance,givingusmoreflexibilityandcontrolinthewaywedoit.Insidethefactory,wecancreateanewinstanceleveragingclosures,usingaprototypeandthenewoperator,usingObject.create(),orevenreturningadifferentinstancebasedonaparticularcondition.Theconsumerofthefactoryistotallyagnosticabouthowthecreationoftheinstanceiscarriedout.Thetruthisthat,byusingnew,wearebindingourcodetoonespecificwayofcreatinganobject,whileinJavaScriptwecanhavemuchmoreflexibility,almostforfree.Asaquickexample,let'sconsiderasimplefactorythatcreatesanImageobject:

Page 196: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

functioncreateImage(name){

returnnewImage(name);

}

varimage=createImage('photo.jpeg');

ThecreateImage()factorymightlooktotallyunnecessary,whynotinstantiatetheImageclassbyusingthenewoperatordirectly?Somethinglikethefollowinglineofcode:

varimage=newImage(name);

Aswealreadymentioned,usingnewbindsourcodetooneparticulartypeofobject;intheprecedingcase,toobjectsoftype,Image.Afactoryinstead,givesusmuchmoreflexibility;imaginethatwewanttorefactortheImageclass,splittingitintosmallerclasses,oneforeachimageformatthatwesupport.Ifweexposedafactoryastheonlymeanstocreatenewimages,wecansimplyrewriteitasfollows,withoutbreakinganyoftheexistingcode:

functioncreateImage(name){

if(name.match(/\.jpeg$/)){

returnnewJpegImage(name);

}elseif(name.match(/\.gif$/)){

returnnewGifImage(name);

}elseif(name.match(/\.png$/)){

returnnewPngImage(name);

}else{

thrownewException('Unsupportedformat');

}

}

Ourfactoryalsoallowsustonotexposetheconstructorsoftheobjectsitcreates,andpreventsthemfrombeingextendedormodified(remembertheprincipleofsmallsurfacearea?).InNode.js,thiscanbeachievedbyexportingonlythefactory,whilekeepingeachconstructorprivate.

AmechanismtoenforceencapsulationAfactorycanalsobeusedasanencapsulationmechanism,thankstoclosures.

NoteEncapsulationreferstothetechniqueofcontrollingtheaccesstosomeinternaldetailsofanobjectbypreventingtheexternalcodefrommanipulatingthemdirectly.Theinteractionwiththeobjecthappensonlythroughitspublicinterface,

Page 197: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

isolatingtheexternalcodefromthechangesintheimplementationdetailsoftheobject.Thispracticeisalsoreferredtoasinformationhiding.Encapsulationisalsoafundamentalprincipleofobject-orienteddesign,togetherwithinheritance,polymorphism,andabstraction.

Asweknow,inJavaScript,wedon'thaveaccesslevelmodifiers(forexample,wecan'tdeclareaprivatevariable),sotheonlywaytoenforceencapsulationisthroughfunctionscopesandclosures.Afactorymakesitstraightforwardtoenforceprivatevariables,considerthefollowingcodeforexample:

functioncreatePerson(name){

varprivateProperties={};

varperson={

setName:function(name){

if(!name)thrownewError('Apersonmusthaveaname');

privateProperties.name=name;

},

getName:function(){

returnprivateProperties.name;

}

};

person.setName(name);

returnperson;

}

Intheprecedingcode,weleverageclosurestocreatetwoobjects:apersonobjectwhichrepresentsthepublicinterfacereturnedbythefactoryandagroupofprivatePropertiesthatareinaccessiblefromtheoutside,andthatcanbemanipulatedonlythroughtheinterfaceprovidedbythepersonobject.Forexample,intheprecedingcode,wemakesurethataperson'snameisneverempty,thiswouldnotbepossibletoenforceifnamewasjustapropertyofthepersonobject.

NoteFactoriesareonlyoneofthetechniquesthatwehaveforcreatingprivatemembers;infact,otherpossibleapproachesareasfollows:

Definingprivatevariablesinaconstructor(asrecommendedbyDouglasCrockford:http://javascript.crockford.com/private.html)Usingconventions,forexample,prefixingthenameofapropertywithanunderscore"_"orthedollarsign"$"(thishowever,doesnottechnicallypreventamemberfrombeingaccessedfromtheoutside)UsingES6WeakMaps(http://fitzgeraldnick.com/weblog/53/)

Page 198: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

BuildingasimplecodeprofilerNow,let'sworkonacompleteexampleusingafactory.Let'sbuildasimplecodeprofiler,anobjectwiththefollowingproperties:

Astart()methodthattriggersthestartofaprofilingsessionAnend()methodtoterminatethesessionandlogitsexecutiontimetotheconsole

Let'sstartbycreatingafilenamedprofiler.js,whichwillhavethefollowingcontent:

functionProfiler(label){

this.label=label;

this.lastTime=null;

}

Profiler.prototype.start=function(){

this.lastTime=process.hrtime();

}

Profiler.prototype.end=function(){

vardiff=process.hrtime(this.lastTime);

console.log('Timer"'+this.label+'"took'

+diff[0]+'secondsand'

+diff[1]+'nanoseconds.');

}

Thereisnothingfancyintheprecedingclass;wesimplyusethedefaulthighresolutiontimertosavethecurrenttimewhenstart()isinvoked,andthencalculatetheelapsedtimewhenend()isexecuted,printingtheresulttotheconsole.

Now,ifwearegoingtousesuchaprofilerinareal-worldapplicationtocalculatetheexecutiontimeofthedifferentroutines,wecaneasilyimaginethehugeamountofloggingwewillgeneratetothestandardoutput,especiallyinaproductionenvironment.Whatwemightwanttodoinsteadisredirecttheprofilinginformationtoanothersource,forexample,adatabase,oralternatively,disablingtheprofileraltogetheriftheapplicationisrunninginproductionmode.It'sclearthatifweweretoinstantiateaProfilerobjectdirectlybyusingthenewoperator,wewouldneedsomeextralogicintheclientcodeorintheProfilerobjectitselfinordertoswitchbetweenthedifferentlogics.WecaninsteaduseafactorytoabstractthecreationoftheProfilerobject,sothat,dependingonwhethertheapplicationrunsinproductionordevelopmentmode,wecanreturnafullyworkingProfilerobject,oralternatively,amockobjectwiththesameinterface,butwithemptymethods.Let'sdothisthen,in

Page 199: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

theprofiler.jsmoduleinsteadofexportingtheProfilerconstructor,wewillexportonlyafunction,ourfactory.Thefollowingisitscode:

module.exports=function(label){

if(process.env.NODE_ENV==='development'){

returnnewProfiler(label);//[1]

}elseif(process.env.NODE_ENV==='production'){

return{//[2]

start:function(){},

end:function(){}

}

}else{

thrownewError('MustsetNODE_ENV');

}

}

Thefactorythatwecreatedabstractsthecreationofaprofilerobjectfromitsimplementation:

Iftheapplicationisrunningindevelopmentmode,wereturnanew,fullyfunctionalProfilerobjectIfinsteadtheapplicationisrunninginproductionmode,wereturnamockobjectwherethestart()andstop()methodsareemptyfunctions

Thenicefeaturetohighlightisthat,thankstotheJavaScriptdynamictyping,wewereabletoreturnanobjectinstantiatedwiththenewoperatorinonecircumstanceandasimpleobjectliteralintheother(thisisalsoknownasducktypinghttp://en.wikipedia.org/wiki/Duck_typing).Ourfactoryisdoingitsjobperfectly;wecanreallycreateobjectsinanywaythatwelikeinsidethefactoryfunction,andwecanexecuteadditionalinitializationstepsorreturnadifferenttypeofobjectbasedonparticularconditions,andallofthiswhileisolatingtheconsumeroftheobjectfromallthesedetails.Wecaneasilyunderstandthepowerofthissimplepattern.

Now,wecanplaywithourprofiler;thisisapossibleusecaseforthefactorythatwejustcreatedearlier:

varprofiler=require('./profiler');

functiongetRandomArray(len){

varp=profiler('Generatinga'+len+'itemslong

array');

p.start();

vararr=[];

for(vari=0;i<len;i++){

arr.push(Math.random());

}

p.end();

}

Page 200: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

getRandomArray(1e6);

console.log('Done');

ThepvariablecontainstheinstanceofourProfilerobject,butwedon'tknowhowit'screatedandwhatitsimplementationisatthispointinthecode.

IfweincludetheprecedingcodeinafilenamedprofilerTest.js,wecaneasilytesttheseassumptions.Totrytheprogramwithprofilingenabled,runthefollowingcommand:

exportNODE_ENV=development;nodeprofilerTest

Theprecedingcommandenablestherealprofilerandprintstheprofilinginformationtotheconsole.Ifwewanttotrythemockprofilerinstead,wecanrunthefollowingcommand:

exportNODE_ENV=production;nodeprofilerTest

Theexamplethatwejustpresentedisjustasimpleapplicationofthefactoryfunctionpattern,butitclearlyshowstheadvantagesofseparatinganobject'screationfromitsimplementation.

InthewildAswesaid,factoriesareverypopularinNode.js.Manypackagesofferonlyafactoryforcreatingnewinstances,someexamplesarethefollowing:

Dnode(https://npmjs.org/package/dnode):ThisisanRPCsystemforNode.js.Ifwelookintoitssourcecode,wewillseethatitslogicisimplementedintoaclassnamedD;however,thisisneverexposedtotheoutsideastheonlyexportedinterfaceisafactory,whichallowsustocreatenewinstancesoftheclass.Youcantakealookatitssourcecodeathttps://github.com/substack/dnode/blob/34d1c9aa9696f13bdf8fb99d9d039367ad873f90/index.js#L7-9.Restify(https://npmjs.org/package/restify):ThisisaframeworktobuildRESTAPIthatallowsustocreatenewinstancesofaserverusingtherestify.createServer()factory,whichinternallycreatesanewinstanceoftheServerclass(whichisnotexported).Youcantakealookatitssourcecodeathttps://github.com/mcavage/node-restify/blob/5f31e2334b38361ac7ac1a5e5d852b7206ef7d94/lib/index.js#L91-116.

Page 201: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Othermodulesexposebothaclassandafactory,butdocumentthefactoryasthemainmethod—orthemostconvenientway—tocreatenewinstances;someoftheexamplesareasfollows:

http-proxy(https://npmjs.org/package/http-proxy):Thisisaprogrammableproxyinglibrary,wherenewinstancesarecreatedwithhttpProxy.createProxyServer(options).ThecoreNode.jsHTTPserver:Thisiswherenewinstancesaremostlycreatedusinghttp.createServer(),eventhoughthisisessentiallyashortcutfornewhttp.Server().bunyan(https://npmjs.org/package/bunyan):Thisisapopularlogginglibrary;initsreadmefilethecontributorsproposeafactory,bunyan.createLogger(),asthemainmethodtocreatenewinstances,eventhoughthiswouldbeequivalenttorunningnewbunyan().

Someothermodulesprovideafactorytowrapthecreationofothercomponents.Popularexamplesarethrough2andfrom2(wesawtheminChapter3,CodingwithStreams),whichallowustosimplifythecreationofnewstreamsusingafactoryapproach,freeingthedeveloperfromexplicitlyusinginheritanceandthenewoperator.

Page 202: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ProxyAproxyisanobjectthatcontrolstheaccesstoanotherobjectcalledsubject.Theproxyandthesubjecthaveanidenticalinterfaceandthisallowsustotransparentlyswaponefortheother;infact,thealternativenameforthispatternissurrogate.Aproxyinterceptsallorsomeoftheoperationsthataremeanttobeexecutedonthesubject,augmentingorcomplementingtheirbehavior.Thefollowingfigureshowsthediagrammaticrepresentation:

TheprecedingfigureshowsushowtheProxyandtheSubjecthavethesameinterfaceandhowthisistotallytransparenttotheclient,whocanuseoneortheotherinterchangeably.TheProxyforwardseachoperationtothesubject,enhancingitsbehaviorwithadditionalpreprocessingorpost-processing.

NoteIt'simportanttoobservethatwearenottalkingaboutproxyingbetweenclasses;theProxypatterninvolveswrappingactualinstancesofthesubject,thuspreservingitsstate.

Aproxyisusefulinseveralcircumstances,forexample,considerthefollowingones:

Datavalidation:TheproxyvalidatestheinputbeforeforwardingittothesubjectSecurity:TheproxyverifiesthattheclientisauthorizedtoperformtheoperationanditpassestherequesttothesubjectonlyiftheoutcomeofthecheckispositiveCaching:TheproxykeepsaninternalcachesothattheoperationsareexecutedonthesubjectonlyifthedataisnotyetpresentinthecacheLazyinitialization:Ifthecreationofthesubjectisexpensive,theproxycandelayittowhenit'sreallynecessaryLogging:Theproxyinterceptsthemethodinvocationsandtherelativeparameters,recodingthemastheyhappenRemoteobjects:Aproxycantakeanobjectthatislocatedremotely,andmakeitappearlocal

Page 203: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Ofcourse,therearemanymoreapplicationsfortheProxypattern,buttheseshouldgiveusanideaoftheextentofitspurpose.

TechniquesforimplementingproxiesWhenproxyinganobject,wecandecidetointerceptallitsmethodsoronlypartofthem,whiledelegatingtherestofthemdirectlytothesubject.Thereareseveralwaysinwhichthiscanbeachieved;let'sanalyzesomeofthem.

ObjectcompositionCompositionisthetechniquewherebyanobjectiscombinedwithanotherobjectforthepurposeofextendingorusingitsfunctionality.InthespecificcaseoftheProxypattern,anewobjectwiththesameinterfaceasthesubjectiscreated,andareferencetothesubjectisstoredinternallyintheproxyintheformofaninstancevariableoraclosurevariable.Thesubjectcanbeinjectedfromtheclientatcreationtimeorcreatedbytheproxyitself.

Thefollowingisoneexampleofthistechniqueusingapseudoclassandafactory:

functioncreateProxy(subject){

varproto=Object.getPrototypeOf(subject);

functionProxy(subject){

this.subject=subject;

}

Proxy.prototype=Object.create(proto);

//proxiedmethod

Proxy.prototype.hello=function(){

returnthis.subject.hello()+'world!';

}

//delegatedmethod

Proxy.prototype.goodbye=function(){

returnthis.subject.goodbye

.apply(this.subject,arguments);

}

returnnewProxy(subject);

}

Toimplementaproxyusingcomposition,wehavetointerceptthemethodsthatweareinterestedinmanipulating(suchashello()),whilesimplydelegatingtherestofthemtothesubject(aswedidwithgoodbye()).

Theprecedingcodealsoshowstheparticularcasewherethesubjecthasa

Page 204: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

prototypeandwewanttomaintainthecorrectprototypechain,sothat,executingproxyinstanceofSubjectwillreturntrue;weusedpseudo-classicalinheritancetoachievethis.

Thisisjustanextrastep,requiredonlyifweareinterestedinmaintainingtheprototypechain,whichcanbeusefulinordertoimprovethecompatibilityoftheproxywithcodeinitiallymeanttoworkwiththesubject.

However,asJavaScripthasdynamictyping,mostofthetimewecanavoidusinginheritanceandusemoreimmediateapproaches.Forexample,analternativeimplementationoftheproxypresentedintheprecedingcode,mightjustuseanobjectliteralandafactory:

functioncreateProxy(subject){

return{

//proxiedmethod

hello:function(){

returnsubject.hello()+'world!';

},

//delegatedmethod

goodbye:function(){

returnsubject.goodbye.apply(subject,arguments);

}

};

}

NoteIfwewanttocreateaproxythatdelegatesmostofitsmethods,itwouldbeconvenienttogeneratetheseautomaticallyusingalibrary,suchasdelegates(https://npmjs.org/package/delegates).

ObjectaugmentationObjectaugmentation(ormonkeypatching)isprobablythemostpragmaticwayofproxyingindividualmethodsofanobjectandconsistsofmodifyingthesubjectdirectlybyreplacingamethodwithitsproxiedimplementation;considerthefollowingexample:

functioncreateProxy(subject){

varhelloOrig=subject.hello;

subject.hello=function(){

returnhelloOrig.call(this)+'world!';

}

Page 205: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

returnsubject;

}

Thistechniqueisdefinitelythemostconvenientonewhenweneedtoproxyonlyoneorafewmethods,butithasthedrawbackofmodifyingthesubjectobjectdirectly.

AcomparisonofthedifferenttechniquesCompositioncanbeconsideredthesafestwayofcreatingaproxy,becauseitleavesthesubjectuntouchedwithoutmutatingitsoriginalbehavior.Itsonlydrawbackisthatwehavetomanuallydelegateallthemethods,evenifwewanttoproxyonlyoneofthem.Ifneeded,wemightalsohavetodelegatetheaccesstothepropertiesofthesubject.

TipTheobjectpropertiescanbedelegatedusingObject.defineProperty().Findoutmoreathttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty.

Objectaugmentation,ontheotherhand,modifiesthesubject,whichmightnotalwaysbewhatwewant,butitdoesnotpresentthevariousinconveniencesrelatedtodelegation.Forthisreason,objectaugmentationisdefinitelythemostpragmaticwaytoimplementproxiesinJavaScript,andit'sthepreferredtechniqueinallthosecircumstanceswheremodifyingthesubjectisnotabigconcern.

However,thereisatleastonesituationwherecompositionisalmostnecessary;thisiswhenwewanttocontroltheinitializationofthesubjectasforexample,tocreateitonlywhenneeded(lazyinitialization).

TipItisworthpointingoutthatbyusingafactoryfunction(createProxy()inourexamples),wecanshieldourcodefromthetechniqueusedtogeneratetheproxy.

CreatingaloggingWritablestream

Page 206: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Toseetheproxypatterninarealexample,wewillnowbuildanobjectthatactsasaproxytoaWritablestream,byinterceptingallthecallstothewrite()methodandloggingamessageeverytimethishappens.Wewilluseanobjectcompositiontoimplementourproxy;thisishowtheloggingWritable.jsfilelooks:

functioncreateLoggingWritable(writableOrig){

varproto=Object.getPrototypeOf(writableOrig);

functionLoggingWritable(subject){

this.writableOrig=writableOrig;

}

LoggingWritable.prototype=Object.create(proto);

LoggingWritable.prototype.write=

function(chunk,encoding,callback){

if(!callback&&typeofencoding==='function'){

callback=encoding;

encoding=undefined;

}

console.log('Writing',chunk);

returnthis.writableOrig.write(chunk,encoding,

function(){

console.log('Finishedwriting',chunk);

callback&&callback();

});

};

LoggingWritable.prototype.on=function(){

returnthis.writableOrig.on

.apply(this.writableOrig,arguments);

};

LoggingWritable.prototype.end=function(){

returnthis.writableOrig.end

.apply(this.writableOrig,arguments);

}

returnnewLoggingWritable(this.writableOrig);

}

Intheprecedingcode,wecreatedafactorythatreturnsaproxiedversionofthewritableobjectpassedasanargument.Weprovideanoverrideforthewrite()methodthatlogsamessagetothestandardoutputeverytimeitisinvokedandeverytimetheasynchronousoperationcompletes.Thisisalsoagoodexampletodemonstratetheparticularcaseofcreatingproxiesofasynchronousfunctions,whichmakesnecessarytoproxythecallbackaswell;thisisanimportantdetailtobeconsideredinaplatformlikeNode.js.Theremainingmethods,on()andend(),aresimplydelegatedtotheoriginalwritablestream(tokeepthecodeleanerwearenotconsideringtheothermethodsoftheWritableinterface).

Page 207: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WecannowincludeafewmorelinesofcodeintothelogginWritable.jsmoduletotesttheproxythatwejustcreated:

varfs=require('fs');

varwritable=fs.createWriteStream('test.txt');

varwritableProxy=createProxy(writable);

writableProxy.write('Firstchunk');

writableProxy.write('Secondchunk');

writable.write('Thisisnotlogged');

writableProxy.end();

Theproxydidnotchangetheoriginalinterfaceofthestreamoritsexternalbehavior,butifweruntheprecedingcode,wewillnowseethateverychunkthatiswrittenintothestreamistransparentlyloggedtotheconsole.

Proxyintheecosystem–functionhooksandAOPInitsnumerousforms,ProxyisaquitepopularpatterninNode.jsandintheecosystem.Infact,wecanfindseverallibrariesthatallowustosimplifythecreationofproxies,mostofthetimeleveragingobjectaugmentationasanimplementationapproach.Inthecommunity,thispatterncanbealsoreferredtoasfunctionhookingorsometimesalsoasAspectOrientedProgramming(AOP),whichisactuallyacommonareaofapplicationforproxies.AsithappensinAOP,theselibrariesusuallyallowthedevelopertosetpreorpostexecutionhooksforaspecificmethod(orasetofmethods)thatallowustoexecutesomecustomcodebeforeandaftertheexecutionoftheadvisedmethodrespectively.

Sometimesproxiesarealsocalledmiddleware,because,asithappensinthemiddlewarepattern(whichwewillseelaterinthechapter),theyallowustopreprocessandpost-processtheinput/outputofafunction.Sometimes,theyalsoallowtoregistermultiplehooksforthesamemethodusingamiddleware-likepipeline.

Thereareseverallibrariesonnpmthatallowustoimplementfunctionhookswithlittleeffort.Amongthemtherearehooks(https://npmjs.org/package/hooks),hooker(https://npmjs.org/package/hooker),andmeld(https://npmjs.org/package/meld).

InthewildMongoose(http://mongoosejs.com)isapopularObject-DocumentMapping(ODM)libraryforMongoDB.Internally,itusesthehookspackage(https://npmjs.org/package/hooks)toprovidepreandpostexecutionhooksfortheinit,validate,save,andremovemethodsofitsDocumentobjects.Findoutmoreon

Page 208: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

theofficialdocumentationathttp://mongoosejs.com/docs/middleware.html.

Page 209: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

DecoratorDecoratorisastructuralpatternthatconsistsofdynamicallyaugmentingthebehaviorofanexistingobject.It'sdifferentfromclassicalinheritance,becausethebehaviorisnotaddedtoalltheobjectsofthesameclassbutonlytotheinstancesthatareexplicitlydecorated.

Implementation-wise,itisverysimilartotheProxypattern,butinsteadofenhancingormodifyingthebehavioroftheexistinginterfaceofanobject,itaugmentsitwithnewfunctionalities,asdescribedinthefollowingfigure:

Inthepreviousfigure,theDecoratorobjectisextendingtheComponentobjectbyaddingthemethodC()operation.Theexistingmethodsareusuallydelegatedtothedecoratedobject,withoutfurtherprocessing.Ofcourse,ifnecessarywecaneasilycombinetheProxypattern,sothatalsothecallstotheexistingmethodscanbeinterceptedandmanipulated.

TechniquesforimplementingdecoratorsAlthoughProxyandDecoratorareconceptuallytwodifferentpatterns,withdifferentintents,theypracticallysharethesameimplementationstrategies.Let'srevisethem.

CompositionUsingcomposition,thedecoratedcomponentiswrappedaroundanewobjectthatusuallyinheritsfromit.TheDecoratorinthiscasesimplyneedstodefinethenewmethodswhiledelegatingtheexistingonestotheoriginalcomponent:

functiondecorate(component){

varproto=Object.getPrototypeOf(component);

functionDecorator(component){

this.component=component;

}

Decorator.prototype=Object.create(proto);

//newmethod

Page 210: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Decorator.prototype.greetings=function(){

//...

};

//delegatedmethod

Decorator.prototype.hello=function(){

this.component.hello.apply(this.component,arguments);

};

returnnewDecorator(component);

}

ObjectaugmentationObjectdecorationcanalsobeachievedbysimplyattachingnewmethodsdirectlytothedecoratedobject,asfollows:

functiondecorate(component){

//newmethod

component.greetings=function(){

//...

};

returncomponent;

}

ThesamecaveatsdiscussedduringtheanalysisoftheProxypatternarealsovalidforDecorator.Let'snowpracticethepatternwithaworkingexample!

DecoratingaLevelUPdatabaseBeforewestartcodingwiththenextexample,let'sspendafewwordstointroduceLevelUP,themodulethatwearenowgoingtoworkwith.

IntroducingLevelUPandLevelDBLevelUP(https://npmjs.org/package/levelup)isaNode.jswrapperaroundGoogle'sLevelDB,akey-valuestoreoriginallybuilttoimplementIndexedDBintheChromebrowser,butit'smuchmorethanthat.LevelDBhasbeendefinedbyDominicTarrasthe"Node.jsofdatabases",becauseofitsminimalismandextensibility.LikeNode.js,LevelDBprovidesblazingfastperformancesandonlythemostbasicsetoffeatures,allowingdeveloperstobuildanykindofdatabaseontopofit.

TheNode.jscommunity,andinthiscaseRodVagg,didnotmissthechancetobringthepowerofthisdatabaseintoNode.jsbycreatingLevelUP.BornasawrapperforLevelDB,itthenevolvedtosupportseveralkindsofbackends,fromin-memorystores,tootherNoSQLdatabasessuchasRiakandRedis,towebstorageengines

Page 211: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

suchasIndexedDBandlocalStorage,allowingtousethesameAPIonboththeserverandtheclient,openingupsomereallyinterestingscenarios.

Today,thereisafull-fledgedecosystemaroundLevelUpmadeofpluginsandmodulesthatextendthetinycoretoimplementfeaturessuchasreplication,secondaryindexes,liveupdates,queryengines,andmore.Also,completedatabaseswerebuiltontopofLevelUP,includingCouchDBclonessuchasPouchDB(https://npmjs.org/package/pouchdb)andCouchUP(https://npmjs.org/package/couchup),andevenagraphdatabase,levelgraph(https://npmjs.org/package/levelgraph)thatcanworkbothonNode.jsandthebrowser!

NoteFindoutmoreabouttheLevelUPecosystemathttps://github.com/rvagg/node-levelup/wiki/Modules.

ImplementingaLevelUPpluginInthenextexample,wearegoingtoshowhowwecancreateasimplepluginforLevelUpusingtheDecoratorpattern,andinparticular,theobjectaugmentationtechnique,whichisthesimplestbutnonethelessthemostpragmaticandeffectivewaytodecorateobjectswithadditionalcapabilities.

NoteForconvenience,wearegoingtousethelevelpackage(http://npmjs.org/package/level)thatbundlesbothlevelupandthedefaultadaptercalledleveldown,whichusesLevelDBasthebackend.

WhatwewanttobuildisapluginforLevelUPthatallowstoreceivenotificationseverytimeanobjectwithacertainpatternissavedintothedatabase.Forexample,ifwesubscribetoapatternsuchas{a:1},wewanttoreceiveanotificationwhenobjectssuchas{a:1,b:3}or{a:1,c:'x'}aresavedintothedatabase.

Let'sstarttobuildoursmallpluginbycreatinganewmodulecalledlevelSubscribe.js.Wewilltheninsertthefollowingcode:

module.exports=functionlevelSubscribe(db){

Page 212: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

db.subscribe=function(pattern,listener){//[1]

db.on('put',function(key,val){//[2]

varmatch=Object.keys(pattern).every(function(k){

//[3]

returnpattern[k]===val[k];

});

if(match){

listener(key,val);//[4]

}

});

};

returndb;

}

That'sitforourplugin,andit'sextremelysimple.Let'sseewhathappensintheprecedingcodebriefly:

1. Wedecoratedthedbobjectwithanewmethodnamedsubscribe().Wesimplyattachedthemethoddirectlytotheprovideddbinstance(objectaugmentation).

2. Welistenforanyputoperationperformedonthedatabase.3. Weperformedaverysimplepattern-matchingalgorithm,whichverifiedthatall

thepropertiesintheprovidedpatternarealsoavailableonthedatabeinginserted.

4. Ifwehaveamatch,wenotifythelistener.

Let'snowcreatesomecode—inanewfilenamedlevelSubscribeTest.js—totryoutournewplugin:

varlevel=require('level');//[1]

vardb=level(__dirname+'/db',{valueEncoding:'json'});

varlevelSubscribe=require('./levelSubscribe');//[2]

db=levelSubscribe(db);

db.subscribe({doctype:'tweet',language:'en'},//[3]

function(k,val){

console.log(val);

});

//[4]

db.put('1',{doctype:'tweet',text:'Hi',language:'en'});

db.put('2',{doctype:'company',name:'ACMECo.'});

Thisiswhatwedidintheprecedingcode:

1. First,weinitializeourLevelUPdatabase,choosingthedirectorywherethefileswillbestoredandthedefaultencodingforthevalues.

2. Then,weattachourplugin,whichdecoratestheoriginaldbobject.3. Atthispoint,wearereadytousethenewfeatureprovidedbyourplugin,the

Page 213: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

subscribe()method,wherewespecifythatweareinterestedinalltheobjectswithdoctype:'tweet'andlanguage:'en'.

4. Finally,wesavesomevaluesinthedatabase,sothatwecanseeourplugininaction:

db.put('1',{doctype:'tweet',text:'Hi',language:

'en'});

db.put('2',{doctype:'company',name:'ACMECo.'});

Thisexampleshowsarealapplicationofthedecoratorpatterninitsmostsimpleimplementation:objectaugmentation.Itmightlooklikeatrivialpatternbutithasundoubtedpowerifusedappropriately.

TipForsimplicity,ourpluginwillworkonlyincombinationwiththeputoperations,butitcanbeeasilyexpandedtoworkevenwiththebatchoperations(https://github.com/rvagg/node-levelup#batch).

InthewildFormoreexamplesofhowDecoratorisusedintherealworld,wemightwanttoinspectthecodeofsomemoreLevelUpplugins:

level-inverted-index(https://github.com/dominictarr/level-inverted-index):ThisisapluginthataddsinvertedindexestoaLevelUPdatabase,allowingtoperformsimpletextsearchesacrossthevaluesstoredinthedatabaselevel-plus(https://github.com/eugeneware/levelplus):ThisisapluginthataddsatomicupdatestoaLevelUPdatabase

Page 214: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AdapterTheAdapterpatternallowsustoaccessthefunctionalityofanobjectusingadifferentinterface.Asthenamesuggests,itadaptsanobjectsothatitcanbeusedbycomponentsexpectingadifferentinterface.Thefollowingdiagramclarifiesthesituation:

TheprecedingdiagramshowshowtheAdapterisessentiallyawrapperfortheAdaptee,exposingadifferentinterface.ThediagramalsohighlightthefactthattheoperationsoftheAdaptercanalsobeacompositionofoneormoremethodinvocationsontheAdaptee.Fromanimplementationperspective,themostcommontechniqueiscompositionwherethemethodsoftheAdapterprovidesabridgetothemethodsoftheAdaptee.Thispatternisprettystraightforwardsolet'sworkimmediatelyonanexample.

UsingLevelUPthroughthefilesystemAPIWearenowgoingtobuildanadapteraroundtheLevelUPAPI,transformingitintoaninterfacethatiscompatiblewiththecorefsmodule.Inparticular,wewillmakesurethateverycalltoreadFile()andwriteFile()willtranslateintocallstodb.get()anddb.put();thiswaywewillbeabletouseaLevelUPdatabaseasastoragebackendforsimplefilesystemoperations.

Let'sstartbycreatinganewmodulenamedfsAdapter.js.WewillbeginbyloadingthedependenciesandexportingthecreateFsAdapter()factorythatwearegoingtousetobuildtheadapter:

varpath=require('path');

module.exports=functioncreateFsAdapter(db){

varfs={};

//...continueswiththenextcodefragments

Next,wewillimplementthereadFile()functioninsidethefactoryandensurethatitsinterfaceiscompatiblewiththeoneoftheoriginalfunctionfromthefsmodule:

Page 215: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

fs.readFile=function(filename,options,callback){

if(typeofoptions==='function'){

callback=options;

options={};

}elseif(typeofoptions==='string'){

options={encoding:options};

}

db.get(path.resolve(filename),{//[1]

valueEncoding:options.encoding

},

function(err,value){

if(err){

if(err.type==='NotFoundError'){//[2]

err=newError('ENOENT,open\''+filename+'\'');

err.code='ENOENT';

err.errno=34;

err.path=filename;

}

returncallback&&callback(err);

}

callback&&callback(null,value);//[3]

}

);

};

Intheprecedingcode,wehadtodosomeextraworktomakesurethatthebehaviorofournewfunctionisascloseaspossibletotheoriginalfs.readFile()function.Thestepsperformedbythefunctionaredescribedasfollows:

1. Toretrieveafilefromthedbclass,weinvokedb.get()usingfilenameasakey,bymakingsuretoalwaysuseitsfullpath(usingpath.resolve()).WesetthevalueofvalueEncodingusedbythedatabasetobeequaltoanyeventualencodingoptionreceivedasaninput.

2. Ifthekeyisnotfoundinthedatabase,wecreateanerrorwithENOENTaserrorcode,whichisthecodeusedbytheoriginalfsmoduletoindicateamissingfile.Anyothertypeoferrorisforwardedtocallback(forthescopeofthisexample,weareadaptingonlythemostcommonerrorcondition).

3. Ifthekey-valuepairisretrievedsuccessfullyfromthedatabase,wewillreturnthevaluetothecallerusingthecallback.

Aswesee,thefunctionthatwecreatedisquiterough;itdoesnotwanttobeaperfectreplacementforthefs.readFile()functionbutitdefinitelydoesitsjobinthemostcommonsituations.

Tocompleteoursmalladapter,let'snowseehowtoimplementthewriteFile()function:

Page 216: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

fs.writeFile=function(filename,contents,options,callback)

{

if(typeofoptions==='function'){

callback=options;

options={};

}elseif(typeofoptions==='string'){

options={encoding:options};

}

db.put(path.resolve(filename),contents,{

valueEncoding:options.encoding

},callback);

}

Also,inthiscase,wedon'thaveaperfectwrapper,wewillignoresomeoptionssuchasfilepermissions(options.mode),andwewillforwardanyerrorthatwereceivefromthedatabaseasitis.

Finally,weonlyhavetoreturnthefsobjectandclosethefactoryfunctionusingthefollowinglinesofcode:

returnfs;

}

Ournewadapterisnowready;ifwenowwriteasmalltestmodule,wecantrytouseit:

varfs=require('fs');

fs.writeFile('file.txt','Hello!',function(){

fs.readFile('file.txt',{encoding:'utf8'},function(err,

res){

console.log(res);

});

});

//trytoreadamissingfile

fs.readFile('missing.txt',{encoding:'utf8'},function(err,

res){

console.log(err);

});

TheprecedingcodeusestheoriginalfsAPItoperformafewreadandwriteoperationsonthefilesystemandshouldprintsomethinglikethefollowingtotheconsole:

{[Error:ENOENT,open'missing.txt']errno:34,code:

'ENOENT',path:'missing.txt'}

Page 217: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Hello!

Now,wecantrytoreplacethefsmodulewithouradapter,asfollows:

varlevelup=require('level');

varfsAdapter=require('./fsAdapter');

vardb=levelup('./fsDB',{valueEncoding:'binary'});

varfs=fsAdapter(db);

Runningagainourprogramshouldproducethesameoutput,exceptthefactthatnoneofthefilethatwespecifiedisreadorwrittenusingthefilesystem;instead,anyoperationperformedusingouradapterwillbeconvertedintoanoperationperformedonaLevelUPdatabase.

Theadapterthatwejustcreatedmightlooksilly;what'sthepurposeofusingadatabaseinplaceoftherealfilesystem?However,weshouldrememberthatLevelUPitselfhasadaptersthatenablethedatabasetoalsoruninthebrowser;oneoftheseadaptersislevel.js(https://npmjs.org/package/level-js).Now,ouradaptershouldmakeperfectsense;wecanthinkofusingittosharewiththebrowsercode,whichreliesonthefsmodule!Forexample,thewebspiderthatwecreatedinChapter2,AsynchronousControlFlowPatterns,usesthefsAPItostorethewebpagesdownloadedduringitsoperations;ouradapterwillallowittoruninthebrowser,byapplyingonlyminormodifications!WesoonrealizethatAdapterisanextremelyimportantpatternalsowhenitcomestosharingcodewiththebrowser,aswewillseeinmoredetailinChapter6,Recipes.

InthewildThereareplentyofreal-worldexamplesoftheAdapterpattern:welistsomeofthemostnotableexampleshereforyoutoexploreandanalyze:

WealreadyknowthatLevelUPisabletorunwithdifferentstoragebackends,fromthedefaultLevelDBtoIndexedDBinthebrowser.Thisismadepossiblebythevariousadaptersthatarecreatedtoreplicatetheinternal(private)LevelUPAPI.Takealookatsomeofthemtoseehowtheyareimplemented:https://github.com/rvagg/node-levelup/wiki/Modules#storage-back-ends.jugglingdbisamulti-databaseORMandofcourse,multipleadaptersareusedtomakeitcompatiblewithdifferentdatabases.Takealookatsomeofthemathttps://github.com/1602/jugglingdb/tree/master/lib/adapters.Theperfectcomplementtotheexamplethatwecreatedislevel-filesystem(https://www.npmjs.org/package/level-filesystem),whichistheproperimplementationofthefsAPIontopofLevelUP.

Page 218: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

StrategyTheStrategypatternenablesanobject,calledtheContext,tosupportvariationsinitslogicbyextractingthevariablepartsintoseparate,interchangeableobjectscalledStrategies.Thecontextimplementsthecommonlogicofafamilyofalgorithms,whileastrategyimplementsthemutableparts,allowingthecontexttoadaptitsbehaviordependingondifferentfactorssuchasaninputvalue,asystemconfiguration,oruserpreferences.Thestrategiesareusuallypartofafamilyofsolutionsandallofthemimplementthesameinterface,whichistheonethatisexpectedbythecontext.Thefollowingfigureshowsthesituationwejustdescribed:

Theprecedingfigureshowshowthecontextobjectcanplugdifferentstrategiesintoitsstructure,astheywerereplaceablepartsofapieceofmachinery.Imagineacar,itstirescanbeconsidereditsstrategytoadapttothedifferentroadconditions.Wecanfitthewintertirestogoonsnowyroadsthankstotheirstuds,whilewecandecidetofithigh-performancetirestogomainlyonmotorwaysforalongtrip.Ontheonehand,wedon'twanttochangetheentirecarforthistobepossible,andontheother,wedon'twantacarwitheightwheelssothatitcangooneverypossibleroad.

Wequicklyunderstandhowpowerfulthispatternis;notonlyithelpswithseparatingtheconcernswithinanalgorithmbutitalsoenablesittohaveabetterflexibilityandadapttodifferentvariationsofthesameproblem.

TheStrategypatternisparticularlyusefulinallthosesituationswheresupportingvariationsofanalgorithmrequirescomplexconditionallogic(lotsofif-elseorswitchstatements)ormixingtogetherdifferentalgorithmsofthesamefamily.ImagineanobjectcalledOrderthatrepresentsanonlineorderofane-commercewebsite.Theobjecthasamethodcalledpay()that,asitsays,finalizestheorderandtransfersthefundsfromtheusertotheonlinestore.

Page 219: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Tosupportdifferentpaymentsystems,wehaveacoupleofoptionsasfollows:

Useanif/elsestatementinthepay()methodtocompletetheoperationbasedonthechosenpaymentoptionDelegatethelogicofthepaymenttoastrategyobjectthatimplementsthelogicforthespecificpaymentgatewayselectedbytheuser

Inthefirstsolution,ourOrderobjectcannotsupportotherpaymentmethodsunlessitscodeismodified.Also,thiscanbecomequitecomplexwhenthenumberofpaymentoptionsgrows.Instead,usingtheStrategypatternenablestheOrderobjecttosupportavirtuallyunlimitednumberofpaymentmethodsandkeepsitsscopelimitedtoonlymanagingthedetailsoftheuser,thepurchaseditems,andrelativeprice,whiledelegatingthejobofcompletingthepaymenttoanotherobject.

Let'snowdemonstratethispatternwithasimple,realisticexample.

Multi-formatconfigurationobjectsLet'sconsideranobjectcalledConfigthatholdsasetofconfigurationparametersusedbyanapplication,suchasthedatabaseURL,thelisteningportoftheserver,andsoon.TheConfigobjectshouldbeabletoprovideasimpleinterfacetoaccesstheseparametersbutalsoawaytoimportandexporttheconfigurationusingapersistentstorage,suchasafile.Wewanttobeabletosupportdifferentformatstostoretheconfiguration,asforexample,JSON,INI,orYAML.

ByapplyingwhatwelearnedabouttheStrategypattern,wecanimmediatelyidentifythevariablepartoftheconfigobject,whichisthefunctionalitythatallowsustoserializeanddeserializetheconfiguration.Thisisgoingtobeourstrategy.

Let'screateanewmodulecalledconfig.jsandlet'sdefinethegenericpartofourconfigurationmanager:

varfs=require('fs');

varobjectPath=require('object-path');

functionConfig(strategy){

this.data={};

this.strategy=strategy;

}

Config.prototype.get=function(path){

returnobjectPath.get(this.data,path);

}

Config.prototype.set=function(path,value){

returnobjectPath.set(this.data,path,value);

Page 220: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

Intheprecedingcode,weencapsulatetheconfigurationdataintoaninstancevariableandthenweprovidetheset()andget()methodsthatallowustoaccesstheconfigurationpropertiesusingadottedpathnotation(forexample,property.subProperty)byleveraginganpmlibrarycalledobject-path(https://npmjs.org/package/object-path).Intheconstructor,wealsotakeinastrategyasinput,whichrepresentsanalgorithmforparsingandserializingthedata.

Let'snowseehowwearegoingtousestrategy,bywritingtheremainingpartoftheConfigclass:

Config.prototype.read=function(file){

console.log('Deserializingfrom'+file);

this.data=this.strategy.deserialize(fs.readFileSync(file,

'utf-8'));

}

Config.prototype.save=function(file){

console.log('Serializingto'+file);

fs.writeFileSync(file,this.strategy.serialize(this.data));

}

module.exports=Config;

Inthepreviouscode,whenreadingtheconfigurationfromafile,wedelegatethedeserializationtasktothestrategy;then,whenwewanttosavetheconfigurationintoafile,weusestrategytoserializetheconfiguration.ThissimpledesignallowstheConfigobjecttosupportdifferentfileformatswhenloadingandsavingitsdata.

Todemonstratethis,let'screateacoupleofstrategiesintoafilecalledstrategies.js.Let'sstartwithastrategyforparsingandserializingJSONdata:

module.exports.json={

deserialize:function(data){

returnJSON.parse(data);

},

serialize:function(data){

returnJSON.stringify(data,null,'');

}

}

Nothingreallycomplicated!Ourstrategysimplyimplementstheagreedinterface,sothatitcanbeusedbytheConfigobject.

Similarly,thenextstrategywearegoingtocreateallowsustosupporttheINIfileformat:

Page 221: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varini=require('ini');//->https://npmjs.org/package/ini

module.exports.ini={

deserialize:function(data){

returnini.parse(data);

},

serialize:function(data){

returnini.stringify(data);

}

}

Now,toshowyouhoweverythingcomestogether,let'screateafilenamedconfigTest.jsandlet'strytoloadandsaveasampleconfigurationusingdifferentformats:

varConfig=require('./config');

varstrategies=require('./strategies');

varjsonConfig=newConfig(strategies.json);

jsonConfig.read('samples/conf.json');

jsonConfig.set('book.nodejs','designpatterns');

jsonConfig.save('samples/conf_mod.json');

variniConfig=newConfig(strategies.ini);

iniConfig.read('samples/conf.ini');

iniConfig.set('book.nodejs','designpatterns');

iniConfig.save('samples/conf_mod.ini');

OurtestmodulerevealsthepropertiesoftheStrategypattern.WedefinedonlyoneConfigclass,whichimplementsthecommonpartsofourconfigurationmanager,whilechangingthestrategyusedforserializinganddeserializingallowedustocreatedifferentConfiginstancessupportingdifferentfileformats.

Theprecedingexampleshowsonlyoneofthepossiblealternativesthatwehadforselectingthestrategy.Othervalidapproachesmighthavebeenthefollowing:

Creatingtwodifferentstrategyfamilies:oneforthedeserializationandtheotherfortheserialization.Thiswouldhaveallowedreadingfromaformatandsavingintoanother.Dynamicallyselectingthestrategydependingontheextensionofthefileprovided;theConfigobjectcouldhavemaintainedamapextension->strategyandusedittoselecttherightalgorithmforthegivenextension.

Aswecansee,thereareseveraloptionsforselectingthestrategytouseandtherightoneonlydependsonourrequirementsandthetrade-offintermsoffeatures/simplicitywewanttoobtain.

Also,theimplementationofthepatternitselfcanvaryalot,forexample,inits

Page 222: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

simplestform,thecontextandthestrategycanbothbesimplefunctions:

functioncontext(strategy){...}

Eventhoughtheprecedingsituationmightseeminsignificant,itshouldnotbeunderestimatedinaprogramminglanguage,suchasJavaScript,wherefunctionsarefirst-classcitizensandusedasmuchasfull-fledgedobjects.

Betweenallthesevariationsthough,whatdoesnotchangeistheideabehindthepattern,asalwaystheimplementationcanslightlychangebutthecoreconceptsthatdrivethepatternarealwaysthesame.

InthewildPassport.js(http://passportjs.org)isanauthenticationframeworkforNode.jswhichallowstoaddsupportfordifferentauthenticationschemesintoawebserver.WithPassport,wecanprovideaLoginwithFacebookorLoginwithTwitterfunctionalitytoourwebapplicationwithminimaleffort.PassportusestheStrategypatterntoseparatethecommonlogicrequiredduringanauthenticationprocessfromthepartsthatcanchange,namelytheactualauthenticationstep.Forexample,wemightwanttouseOAuthinordertoobtainanaccessTokentoaccessaFacebookorTwitterprofile,orsimplyusealocaldatabasetoverifyausername/passwordpair.ForPassport,thesearealldifferentstrategiesforcompletingtheauthenticationprocess,andaswecanimagine,thisallowsthelibrarytosupportavirtuallyunlimitednumberofauthenticationservices.Takealookatthenumberofdifferentauthenticationproviderssupportedathttp://passportjs.org/guide/providers,togetanideaofwhattheStrategypatterncando.

Page 223: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

StateStateisavariationoftheStrategypatternwherethestrategychangesdependingonthestateofthecontext.Wehaveseenintheprevioussectionhowastrategycanbeselectedbasedondifferentvariablessuchasuserpreferences,aconfigurationparameter,theinputprovidedandoncethisselectionisdone,thestrategystaysunchangedfortherestofthelifespanofthecontext.

IntheStatepatterninstead,thestrategy(alsocalledStateinthiscircumstance)isdynamicandcanchangeduringthelifetimeofthecontext,thusallowingitsbehaviortoadaptdependingonitsinternalstate,asshowninthefollowingfigure:

ImaginethatwehaveahotelbookingsystemandanobjectcalledReservationthatmodelsaroomreservation.Thisisaclassicalsituationwherewehavetoadaptthebehaviorofanobjectbasedonitsstate.Considerthefollowingseriesofevents:

1. Whenthereservationisinitiallycreated,theusercanconfirm(usingconfirm())thereservation;ofcourse,theycannotcancel(usingcancel())it,becauseit'sstillnotconfirmed.Theycanhoweverdelete(usingdelete())itiftheychangetheirmindbeforebuying.

2. Oncethereservationisconfirmed,usingtheconfirm()functionagaindoesnotmakeanysense;however,nowitshouldbepossibletocancelthereservationbutnottodeleteitanylonger,becauseithastobekeptfortherecord.

3. Onthedaybeforethereservationdate,itshouldnotbepossibletocancelthereservation;it'stoolateforthat.

Now,imaginethatwehavetoimplementthereservationsystemthatwedescribedinonemonolithicobject;wecanalreadypicturealltheif-elseorswitchstatements

Page 224: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thatwewouldhavetowritetoenable/disableeachactiondependingonthestateofthereservation.

TheStatepatterninsteadisperfectinthissituation:therewillbethreestrategiesandallimplementingthethreemethodsdescribed(confirm(),cancel(),delete())andeachoneimplementingonlyonebehavior,theonecorrespondingtothemodeledstate.Byusingthispattern,itshouldbeveryeasyfortheReservationobjecttoswitchfromonebehaviortoanother;thiswillsimplyrequiretheactivationofadifferentstrategyoneachstatechange.

Thestatetransitioncanbeinitiatedandcontrolledbythecontextobject,bytheclientcode,orbytheStateobjectsthemselves.Thislastoptionusuallyprovidesthebestresultsintermsofflexibilityanddecouplingasthecontextdoesnothavetoknowaboutallthepossiblestatesandhowtotransitionbetweenthem.

Implementingabasicfail-safesocketLet'snowworkonaconcreteexamplesothatwecanapplywhatwelearnedabouttheStatepattern.Let'sbuildaclientTCPsocketthatdoesnotfailwhentheconnectionwiththeserverislost;instead,wewanttoqueueallthedatasentduringthetimeinwhichtheserverisofflineandthentrytosenditagainassoonastheconnectionisre-established.Wewanttoleveragethissocketinthecontextofasimplemonitoringsystem,whereasetofmachinessendsomestatisticsabouttheirresourceutilizationatregularintervals;iftheserverthatcollectstheseresourcesgoesdown,oursocketwillcontinuetoqueuethedatalocallyuntiltheservercomesbackonline.

Let'sstartbycreatinganewmodulecalledfailsafeSocket.jsthatrepresentsourcontextobject:

varOfflineState=require('./offlineState');

varOnlineState=require('./onlineState');

functionFailsafeSocket(options){//[1]

this.options=options;

this.queue=[];

this.currentState=null;

this.socket=null;

this.states={

offline:newOfflineState(this),

online:newOnlineState(this)

}

this.changeState('offline');

}

FailsafeSocket.prototype.changeState=function(state){//[2]

Page 225: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

console.log('Activatingstate:'+state);

this.currentState=this.states[state];

this.currentState.activate();

}

FailsafeSocket.prototype.send=function(data){//[3]

this.currentState.send(data);

}

module.exports=function(options){

returnnewFailsafeSocket(options);

};

TheFailsafeSocketpseudoclassismadeofthreemainelements:

1. Theconstructorinitializesvariousdatastructures,includingthequeuethatwillcontainanydatasentwhilethesocketisoffline.Also,itcreatesasetoftwostates,oneforimplementingthebehaviorofthesocketwhileit'sofflineandanotheronewhenthesocketisonline.

2. ThechangeState()methodisresponsiblefortransitioningfromonestatetoanother.ItsimplyupdatesthecurrentStateinstancevariableandcallsactivate()onthetargetstate.

3. Thesend()methodisthefunctionalityofthesocket,thisiswherewewanttohaveadifferentbehaviorbasedontheoffline/onlinestate.Aswecansee,thisisdonebydelegatingtheoperationtothecurrentlyactivestate.

Let'snowseehowthetwostateslooklike,startingfromtheofflineState.jsmodule:

varjot=require('json-over-tcp');//[1]

functionOfflineState(failsafeSocket){

this.failsafeSocket=failsafeSocket;

}

module.exports=OfflineState;

OfflineState.prototype.send=function(data){//[2]

this.failsafeSocket.queue.push(data);

}

OfflineState.prototype.activate=function(){//[3]

varself=this;

functionretry(){

setTimeout(function(){

self.activate();

},500);

}

self.failsafeSocket.socket=jot.connect(

self.failsafeSocket.options,

Page 226: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

function(){

self.failsafeSocket.socket.removeListener('error',

retry);

self.failsafeSocket.changeState('online');

}

);

self.failsafeSocket.socket.once('error',retry);

}

Themodulethatwecreatedisresponsibleformanagingthebehaviorofthesocketwhileit'soffline;thisishowitworks:

1. InsteadofusingarawTCPsocket,wewillusealittlelibrarycalledjson-over-tcp(https://npmjs.org/package/json-over-tcp),whichwillallowustoeasilysendJSONobjectsoveraTCPconnection.

2. Thesend()methodisonlyresponsibleforqueuinganydataitreceives;weareassumingthatweareoffline,sothat'sallweneedtodo.

3. Theactivate()methodtriestoestablishaconnectionwiththeserverusingjson-over-tcp.Iftheoperationfails,ittriesagainafter500milliseconds.Itcontinuestryinguntilavalidconnectionisestablished,inwhichcasethestateoffailsafeSocketistransitionedtoonline.

Next,let'simplementtheonlineState.jsmodule,andthen,let'simplementtheonlineStatestrategyasfollows:

functionOnlineState(failsafeSocket){

this.failsafeSocket=failsafeSocket;

}

module.exports=OnlineState;

OnlineState.prototype.send=function(data){//[1]

this.failsafeSocket.socket.write(data);

};

OnlineState.prototype.activate=function(){//[2]

varself=this;

self.failsafeSocket.queue.forEach(function(data){

self.failsafeSocket.socket.write(data);

});

self.failsafeSocket.queue=[];

self.failsafeSocket.socket.once('error',function(){

self.failsafeSocket.changeState('offline');

});

}

TheOnlineStatestrategyisverysimpleandisexplainedasfollows:

1. Thesend()methodwritesthedatadirectlyintothesocket,asweassumewe

Page 227: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

areonline.2. Theactivate()methodflushesanydatathatwasqueuedwhilethesocketwas

offlineanditalsostartslisteningforanyerrorevent;wewilltakethisasasymptomthatthesocketwentoffline(forsimplicity).Whenthishappens,wetransitiontotheofflinestate.

That'sitforourfailsafeSocket;nowwearereadytobuildasampleclientandaservertotryitout.Let'sputtheservercodeinamodulenamedserver.js:

varjot=require('json-over-tcp');

varserver=jot.createServer(5000);

server.on('connection',function(socket){

socket.on('data',function(data){

console.log('Clientdata',data);

});

});

server.listen(5000,function(){console.log('Started')});

Thentheclientsidecode,whichiswhatwearereallyinterestedin,goesintoclient.js:

varcreateFailsafeSocket=require('./failsafeSocket');

varfailsafeSocket=createFailsafeSocket({port:5000});

setInterval(function(){

//sendcurrentmemoryusage

failsafeSocket.send(process.memoryUsage());

},1000);

OurserversimplyprintsanyJSONmessageitreceivestotheconsole,whileourclientsaresendingameasurementoftheirmemoryutilizationeverysecond,leveragingaFailsafeSocketobject.

Totrythesmallsystemthatwebuilt,weshouldrunboththeclientandtheserver,thenwecantestthefeaturesoffailsafeSocketbystoppingandthenrestartingtheserver.Weshouldseethatthestateoftheclientchangesbetweenonlineandoffline,andthatanymemorymeasurementcollectedwhiletheserverisofflineisqueuedandthenresentassoonastheservergoesbackonline.

ThissampleshouldbeacleardemonstrationofhowtheStatepatterncanhelpincreasethemodularityandreadabilityofacomponentthathastoadaptitsbehaviordependingonitsstate.

Note

Page 228: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheFailsafeSocketclassthatwebuiltinthissectionisonlyfordemonstratingtheStatepatternanddoesn'twanttobeacompleteand100percent-reliablesolutiontohandleconnectivityissueswithinTCPsockets.Forexample,wearenotverifyingthatallthedatawrittenintothesocketstreamisreceivedbytheserver,whichwouldrequiresomemorecodenotstrictlyrelatedtothepatternthatwewantedtodescribe.

Page 229: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TemplateThenextpatternthatwearegoingtoanalyzeiscalledTemplateanditalsohasalotincommonwiththeStrategypattern.Templateconsistsofdefininganabstractpseudoclassthatrepresentstheskeletonofanalgorithmwheresomeofitsstepsareleftundefined.Subclassescanthenfillthegapsinthealgorithmbyimplementingthemissingsteps,calledtemplatemethods.Theintentofthispatternismakingitpossibletodefineafamilyofclassesthatareallvariationsofasimilaralgorithm.ThefollowingUMLdiagramshowsthestructurethatwejustdescribed:

Thethreeconcreteclassesshowninthepreviousdiagram,extendTemplateandprovideanimplementationfortemplateMethod(),whichisabstractorpurevirtual,tousetheC++terminology;inJavaScriptthismeansthatthemethodisleftundefinedorisassignedtoafunctionthatalwaysthrowsanexception,indicatingthefactthatthemethodhastobeimplemented.TheTemplatepatterncanbeconsideredmoreclassicallyobject-orientedthantheotherpatternswehaveseensofar,becauseinheritanceisacorepartofitsimplementation.

ThepurposeofTemplateandStrategyisverysimilar,butthemaindifferencebetweenthetwoliesintheirstructureandimplementation.Bothallowustochangesomepartsofanalgorithmwhilereusingthecommonparts;however,whileStrategyallowsustodoitdynamicallyandpossiblyatruntime,withTemplate,thecompletealgorithmisdeterminedthemomenttheconcreteclassisdefined.Undertheseassumptions,theTemplatepatternmightbemoresuitableinthosecircumstanceswherewewanttocreateprepackagedvariationsofanalgorithm.Asalways,thechoicebetweenonepatternandtheotherisuptothedeveloperwhohastoconsiderthevariousprosandconsforeachusecase.

Aconfigurationmanagertemplate

Page 230: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TohaveabetterideaofthedifferencesbetweenStrategyandTemplate,let'snowre-implementtheConfigobjectthatwedefinedinthesectionabouttheStrategypattern,butthistimeusingTemplate.LikeinthepreviousversionoftheConfigobject,wewanttohavetheabilitytoloadandsaveasetofconfigurationpropertiesusingdifferentfileformats.

Let'sstartbydefiningthetemplateclass;wewillcallitConfigTemplate:

varfs=require('fs');

varobjectPath=require('object-path');

functionConfigTemplate(){}

ConfigTemplate.prototype.read=function(file){

console.log('Deserializingfrom'+file);

this.data=this._deserialize(fs.readFileSync(file,'utf-

8'));

}

ConfigTemplate.prototype.save=function(file){

console.log('Serializingto'+file);

fs.writeFileSync(file,this._serialize(this.data));

}

ConfigTemplate.prototype.get=function(path){

returnobjectPath.get(this.data,path);

}

ConfigTemplate.prototype.set=function(path,value){

returnobjectPath.set(this.data,path,value);

}

ConfigTemplate.prototype._serialize=function(){

thrownewError('_serialize()mustbeimplemented');

}

ConfigTemplate.prototype._deserialize=function(){

thrownewError('_deserialize()mustbeimplemented');

}

module.exports=ConfigTemplate;

ThenewConfigTemplateclassdefinestwotemplatemethods:_deserialize()and_serialize(),thatareneededtocarryouttheloadingandsavingoftheconfiguration.Theunderscoreatthebeginningoftheirnamesindicatesthattheyareforinternaluseonly,aneasywaytoflagprotectedmethods.Since,inJavaScript,wecannotdeclareamethodasabstract,wesimplydefinethemasstubs,throwinganexceptioniftheyareinvoked(inotherwords,iftheyarenotoverriddenbyaconcretesubclass).

Page 231: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Let'snowcreateaconcreteclassusingourtemplate,forexample,onethatallowsustoloadandsavetheconfigurationusingtheJSONformat:

varutil=require('util');

varConfigTemplate=require('./configTemplate');

functionJsonConfig(){}

util.inherits(JsonConfig,ConfigTemplate);

JsonConfig.prototype._deserialize=function(data){

returnJSON.parse(data);

};

JsonConfig.prototype._serialize=function(data){

returnJSON.stringify(data,null,'');

}

module.exports=JsonConfig;

TheJsonConfigclassinheritsfromourtemplate,theConfigTemplateclass,andprovidesaconcreteimplementationforthe_deserialize()and_serialize()methods.

TheJsonConfigclasscannowbeusedasastandaloneconfigurationobject,withouttheneedtospecifyastrategyforserializationanddeserialization,asitisbakedintheclassitself:

varJsonConfig=require('./jsonConfig');

varjsonConfig=newJsonConfig();

jsonConfig.read('samples/conf.json');

jsonConfig.set('nodejs','designpatterns');

jsonConfig.save('samples/conf_mod.json');

Withminimaleffort,theTemplatepatternallowedustoobtainanew,fullyworkingconfigurationmanagerbyreusingthelogicandtheinterfaceinheritedfromtheparenttemplateclassandprovidingonlytheimplementationofafewabstractmethods.

InthewildThispatternshouldnotsoundentirelynewtous.WealreadyencountereditinChapter3,CodingwithStreams,whenwewereextendingthedifferentstreamclassestoimplementourcustomstreams.Inthatcontext,thetemplatemethodswerethe_write(),_read(),_transform(),or_flush()methods,dependingonthestreamclassthatwewantedtoimplement.Tocreateanewcustomstream,weneededtoinheritfromaspecificabstractstreamclass,providinganimplementation

Page 232: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

forthetemplatemethods.

Page 233: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

MiddlewareOneofthemostdistinctivepatternsinNode.jsisdefinitelymiddleware.Unfortunatelyit'salsooneofthemostconfusingfortheinexperienced,especiallyfordeveloperscomingfromtheenterpriseprogrammingworld.Thereasonforthedisorientationisprobablyconnectedwiththemeaningofthetermmiddleware,whichintheenterprisearchitecture'sjargonrepresentsthevarioussoftwaresuitesthathelptoabstractlowerlevelmechanismssuchasOSAPIs,networkcommunications,memorymanagement,andsoon,allowingthedevelopertofocusonlyonthebusinesscaseoftheapplication.Inthiscontext,thetermmiddlewarerecallstopicssuchasCORBA,EnterpriseServiceBus,Spring,JBoss,butinitsmoregenericmeaningitcanalsodefineanykindofsoftwarelayerthatactslikeagluebetweenlowerlevelservicesandtheapplication(literallythesoftwareinthemiddle).

MiddlewareinExpressExpress(http://expressjs.com)popularizedthetermmiddlewareintheNode.jsworld,bindingittoaveryspecificdesignpattern.Inexpress,infact,amiddlewarerepresentsasetofservices,typicallyfunctions,thatareorganizedinapipelineandareresponsibleforprocessingincomingHTTPrequestsandrelativeresponses.Anexpressmiddlewarehasthefollowingsignature:

function(req,res,next){...}

WherereqistheincomingHTTPrequest,resistheresponse,andnextisthecallbacktobeinvokedwhenthecurrentmiddlewarehascompleteditstasksandthatinturntriggersthenextmiddlewareinthepipeline.

Examplesofthetaskscarriedoutbyanexpressmiddlewareareasthefollowing:

ParsingthebodyoftherequestCompressing/decompressingrequestsandresponsesProducingaccesslogsManagingsessionsProvidingCross-siteRequestForgery(CSRF)protection

Ifwethinkaboutit,thesearealltasksthatarenotstrictlyrelatedtothemainfunctionalityofanapplication,rather,theyareaccessories,componentsprovidingsupporttotherestoftheapplicationandallowingtheactualrequesthandlerstofocusonlyontheirmainbusinesslogic.Essentially,thosetasksaresoftwareinthemiddle.

Page 234: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

MiddlewareasapatternThetechniqueusedtoimplementmiddlewareinexpressisnotnew;infact,itcanbeconsideredtheNode.jsincarnationoftheInterceptingFilterpatternandtheChainofResponsibilitypattern.Inmoregenericterms,italsorepresentsaprocessingpipeline,whichremindsusaboutstreams.Today,inNode.js,thewordmiddlewareisusedwellbeyondtheboundariesoftheexpressframework,andindicatesaparticularpatternwherebyasetofprocessingunits,filters,andhandlers,undertheformoffunctionsareconnectedtoformanasynchronoussequenceinordertoperformpreprocessingandpostprocessingofanykindofdata.Themainadvantageofthispatternisflexibility;infact,thispatternallowsustoobtainaplugininfrastructurewithincrediblylittleeffort,providinganunobtrusivewayforextendingasystemwithnewfiltersandhandlers.

TipIfyouwanttoknowmoreabouttheInterceptingFilterpattern,thefollowingarticleisagoodstartingpoint:http://www.oracle.com/technetwork/java/interceptingfilter-142169.html.AniceoverviewoftheChainofResponsibilitypatternisavailableatthisURLhttp://java.dzone.com/articles/design-patterns-uncovered-chain-of-responsibility.

Thefollowingdiagramshowsthecomponentsofthemiddlewarepattern:

TheessentialcomponentofthepatternistheMiddlewareManager,whichisresponsiblefororganizingandexecutingthemiddlewarefunctions.Themostimportantimplementationdetailsofthepatternareasfollows:

Newmiddlewarecanberegisteredbyinvokingtheuse()function(thenameofthisfunctionisacommonconventioninmanyimplementationsofthispattern,butwecanchooseanyname).Usually,newmiddlewarecanonlybeappendedat

Page 235: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

theendofthepipeline,butthisisnotastrictrule.Whennewdatatoprocessisreceived,theregisteredmiddlewareisinvokedinanasynchronoussequentialexecutionflow.Eachunitinthepipelinereceivesininputtheresultoftheexecutionofthepreviousunit.Eachmiddlewarecandecidetostopfurtherprocessingofthedatabysimplynotinvokingitscallbackorbypassinganerrortothecallback.Anerrorsituationusuallytriggerstheexecutionofanothersequenceofmiddlewarethatisspecificallydedicatedtohandlingerrors.

Thereisnostrictruleonhowthedataisprocessedandpropagatedinthepipeline.Thestrategiesinclude:

AugmentingthedatawithadditionalpropertiesorfunctionsReplacingthedatawiththeresultofsomekindofprocessingMaintainingtheimmutabilityofthedataandalwaysreturningfreshcopiesasresultoftheprocessing

TherightapproachthatweneedtotakedependsonthewaytheMiddlewareManagerisimplementedandonthetypeofprocessingcarriedoutbythemiddlewareitself.

Creatingamiddlewareframeworkfor ØMQLet'snowdemonstratethepatternbybuildingamiddlewareframeworkaroundtheØMQ(http://zeromq.org)messaginglibrary.ØMQ(alsoknownasZMQ,orZeroMQ)providesasimpleinterfaceforexchangingatomicmessagesacrossthenetworkusingavarietyofprotocols;itshinesforitsperformances,anditsbasicsetofabstractionsarespecificallybuilttofacilitatetheimplementationofcustommessagingarchitectures.Forthisreason,ØMQisoftenchosentobuildcomplexdistributedsystems.

NoteInChapter8,MessagingandIntegrationPatterns,wewillhavethechancetoanalyzethefeaturesofØMQinmoredetail.

TheinterfaceofØMQisprettylow-level,itonlyallowsustousestringsandbinarybuffersformessages,soanyencodingorcustomformattingofdatahastobeimplementedbytheusersofthelibrary.

Inthenextexample,wearegoingtobuildamiddlewareinfrastructuretoabstractthe

Page 236: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

preprocessingandpostprocessingofthedatapassingthroughaØMQsocket,sothatwecantransparentlyworkwithJSONobjectsbutalsoseamlesslycompressthemessagestravelingoverthewire.

NoteBeforecontinuingwiththeexample,pleasemakesuretoinstalltheØMQnativelibrariesfollowingtheinstructionsatthisURL:http://zeromq.org/intro:get-the-software.Anyversioninthe4.0branchshouldbeenoughforworkingonthisexample.

TheMiddlewareManagerThefirststeptobuildamiddlewareinfrastructurearoundØMQistocreateacomponentthatisresponsibleforexecutingthemiddlewarepipelinewhenanewmessageisreceivedorsent.Forthepurpose,let'screateanewmodulecalledzmqMiddlewareManager.jsandlet'sstartdefiningit:

functionZmqMiddlewareManager(socket){

this.socket=socket;

this.inboundMiddleware=[];//[1]

this.outboundMiddleware=[];

varself=this;

socket.on('message',function(message){//[2]

self.executeMiddleware(self.inboundMiddleware,{

data:message

});

});

}

module.exports=ZmqMiddlewareManager;

Thisfirstcodefragmentdefinesanewconstructorforournewcomponent.ItacceptsaØMQsocketasanargumentand:

1. Createstwoemptyliststhatwillcontainourmiddlewarefunctions,onefortheinboundmessagesandanotheronefortheoutboundmessages.

2. Immediately,itstartslisteningforthenewmessagescomingfromthesocketbyattachinganewlistenertothe'message'event.Inthelistener,weprocesstheinboundmessagebyexecutingtheinboundMiddlewarepipeline.

ThenextmethodoftheZmqMiddlewareManagerprototypeisresponsibleforexecutingthemiddlewarewhenanewmessageissentthroughthesocket:

Page 237: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ZmqMiddlewareManager.prototype.send=function(data){

varself=this;

varmessage={

data:data

};

self.executeMiddleware(self.outboundMiddleware,message,

function(){

self.socket.send(message.data);

}

);

}

ThistimethemessageisprocessedusingthefiltersintheoutboundMiddlewarelistandthenpassedtosocket.send()fortheactualnetworktransmission.

Now,weneedasmallmethodtoappendnewmiddlewarefunctionstoourpipelines;wealreadymentionedthatsuchamethodisconventionallycalleduse():

ZmqMiddlewareManager.prototype.use=function(middleware){

if(middleware.inbound){

this.inboundMiddleware.push(middleware.inbound);

}

if(middleware.outbound){

this.outboundMiddleware.unshift(middleware.outbound);

}

}

Eachmiddlewarecomesinpairs;inourimplementationit'sanobjectthatcontainstwoproperties,inboundandoutbound,thatcontainthemiddlewarefunctionstobeaddedtotherespectivelist.

It'simportanttoobserveherethattheinboundmiddlewareispushedtotheendoftheinboundMiddlewarelist,whiletheoutboundmiddlewareisinsertedatthebeginningoftheoutboundMiddlewarelist.Thisisbecausecomplementaryinbound/outboundmiddlewarefunctionsusuallyneedtobeexecutedinaninvertedorder.Forexample,ifwewanttodecompressandthendeserializeaninboundmessageusingJSON,itmeansthatfortheoutbound,weshouldinsteadfirstserializeandthencompress.

NoteIt'simportanttounderstandthatthisconventionfororganizingthemiddlewareinpairsisnotstrictlypartofthegeneralpattern,butonlyanimplementationdetailofourspecificexample.

Page 238: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,it'stimetodefinethecoreofourcomponent,thefunctionthatisresponsibleforexecutingthemiddleware:

ZmqMiddlewareManager.prototype.executeMiddleware=

function(middleware,arg,finish){

varself=this;

(functioniterator(index){

if(index===middleware.length){

returnfinish&&finish();

}

middleware[index].call(self,arg,function(err){

if(err){

console.log('Therewasanerror:'+err.message);

}

iterator(++index);

});

})(0);

}

Theprecedingcodeshouldlookveryfamiliar;infact,itisasimpleimplementationoftheasynchronoussequentialiterationpatternthatwelearnedinChapter2,AsynchronousControlFlowPatterns.Eachfunctioninthemiddlewarearrayreceivedininputisexecutedoneaftertheother,andthesameargobjectisprovidedasanargumenttoeachmiddlewarefunction;thisisthetrickthatmakesitpossibletopropagatethedatafromonemiddlewaretothenext.Attheendoftheiteration,thefinish()callbackisinvoked.

NotePleasenotethatforbrevitywearenotsupportinganerrormiddlewarepipeline.Normally,whenamiddlewarefunctionpropagatesanerror,anothersetofmiddlewarespecificallydedicatedtohandlingerrorsisexecuted.Thiscanbeeasilyimplementedusingthesametechniquethatwearedemonstratinghere.

AmiddlewaretosupportJSONmessagesNowthatwehaveimplementedourMiddlewareManager,wecancreateapairofmiddlewarefunctionstodemonstratehowtoprocessinboundandoutboundmessages.Aswesaid,oneofthegoalsofourmiddlewareinfrastructureishavingafilterthatserializesanddeserializesJSONmessages,solet'screateanewmiddlewaretotakecareofthis.Inanewmodulecalled'middleware.js'let'sincludethefollowingcode:

module.exports.json=function(){

Page 239: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

return{

inbound:function(message,next){

message.data=JSON.parse(message.data.toString());

next();

},

outbound:

function(message,next){

message.data=newBuffer(JSON.stringify(message.data));

next();

}

}

}

Thejsonmiddlewarethatwejustcreatedisverysimple:

Theinboundmiddlewaredeserializesthemessagereceivedasaninputandassignstheresultbacktothedatapropertyofmessage,sothatitcanbefurtherprocessedalongthepipelineTheoutboundmiddlewareserializesanydatafoundintomessage.data

Pleasenotehowthemiddlewaresupportedbyourframeworkisquitedifferentfromtheoneusedinexpress;thisistotallynormalandaperfectdemonstrationofhowwecanadaptthispatterntofitourspecificneed.

UsingtheØMQmiddlewareframeworkWearenowreadytousethemiddlewareinfrastructurethatwejustcreated.Todothat,wearegoingtobuildaverysimpleapplication,withaclientsendingapingtoaserveratregularintervalsandtheserverechoingbackthemessagereceived.

Fromanimplementationperspective,wearegoingtorelyonarequest/replymessagingpatternusingthereq/repsocketpairprovidedbyØMQ(http://zguide.zeromq.org/page:all#Ask-and-Ye-Shall-Receive).WewillthenwrapthesocketswithourzmqMiddlewareManagertogetalltheadvantagesfromthemiddlewareinfrastructurethatwebuilt,includingthemiddlewareforserializing/deserializingJSONmessages.

Theserver

Let'sstartbycreatingtheserverside(server.js).Inthefirstpartofthemoduleweinitializeourcomponents:

varzmq=require('zmq');

varZmqMiddlewareManager=require('./zmqMiddlewareManager');

varmiddleware=require('./middleware');

varreply=zmq.socket('rep');

reply.bind('tcp://127.0.0.1:5000');

Page 240: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Intheprecedingcode,weloadedtherequireddependenciesandbindaØMQ'rep'(reply)sockettoalocalport.Next,weinitializeourmiddleware:

varzmqm=newZmqMiddlewareManager(reply);

zmqm.use(middleware.zlib());

zmqm.use(middleware.json());

WecreatedanewZmqMiddlewareManagerobjectandthenaddedtwomiddlewares,oneforcompressing/decompressingthemessagesandanotheroneforparsing/serializingJSONmessages.

NoteForbrevity,wedidnotshowtheimplementationofthezlibmiddleware,butyoucanfinditinthesamplecodethatisdistributedwiththebook.

Nowwearereadytohandlearequestcomingfromtheclient,wewilldothisbysimplyaddinganothermiddleware,thistimeusingitasarequesthandler:

zmqm.use({

inbound:function(message,next){

console.log('Received:',message.data);

if(message.data.action==='ping'){

this.send({action:'pong',echo:message.data.echo});

}

next();

}

});

Sincethislastmiddlewareisdefinedafterthezlibandjsonmiddlewares,wecantransparentlyusethedecompressedanddeserializedmessagethatisavailableinthemessage.datavariable.Ontheotherhand,anydatapassedtosend()willbeprocessedbytheoutboundmiddleware,whichinourcasewillserializethencompressthedata.

Theclient

Ontheclientsideofourlittleapplication,'client.js',wewillfirsthavetoinitiateanewØMQ'req'(request)socketconnectedtotheport5000,theoneusedbyourserver:

varzmq=require('zmq');

varZmqMiddlewareManager=require('./zmqMiddlewareManager');

Page 241: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varmiddleware=require('./middleware');

varrequest=zmq.socket('req');

request.connect('tcp://127.0.0.1:5000');

Then,weneedtosetupourmiddlewareframeworkinthesamewaythatwedidfortheserver:

varzmqm=newZmqMiddlewareManager(request);

zmqm.use(middleware.zlib());

zmqm.use(middleware.json());

Next,wecreateaninboundmiddlewaretohandletheresponsescomingfromtheserver:

zmqm.use({

inbound:function(message,next){

console.log('Echoedback:',message.data);

next();

}

});

Intheprecedingcode,wesimplyinterceptanyinboundresponseandprintittotheconsole.

Finally,wesetupatimertosendsomepingrequestsatregularintervals,alwaysusingthezmqMiddlewareManagertogetalltheadvantagesofourmiddleware:

setInterval(function(){

zmqm.send({action:'ping',echo:Date.now()});

},1000);

Wecannowtryourapplicationbyfirststartingtheserver:

nodeserver

Wecanthenstarttheclientwiththefollowingcommand:

nodeclient

Atthispoint,weshouldseetheclientsendingmessagesandtheserverechoingthemback.

Ourmiddlewareframeworkdiditsjob;itallowedustodecompress/compressanddeserialize/serializeourmessagestransparently,leavingthehandlersfreetofocuson

Page 242: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

theirbusinesslogic!

Page 243: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CommandAnotherdesignpatternwithhugeimportanceinNode.jsisCommand.Initsmostgenericdefinition,wecanconsideracommandasanyobjectthatencapsulatesalltheinformationnecessarytoperformanactionatalatertime.So,insteadofinvokingamethodorafunctiondirectly,wecreateanobjectrepresentingtheintentiontoperformsuchaninvocation;itwillthenbetheresponsibilityofanothercomponenttomaterializetheintent,transformingitintoanactualaction.Traditionally,thispatternisbuiltaroundfourmajorcomponents,asshowninthefollowingfigure:

ThetypicalorganizationoftheCommandpatterncanbedescribedasfollows:

Command:Thisistheobjectencapsulatingtheinformationnecessarytoinvokeamethodorfunction.Client:ThiscreatestheCommandandprovidesittotheInvoker.Invoker:ThisisresponsibleforexecutingtheCommandontheTarget.Target(orReceiver):Thisisthesubjectoftheinvocation.Itcanbealonefunctionorthemethodofanobject.

Aswewillsee,thesefourcomponentscanvaryalotdependingonthewaywewanttoimplementthepattern;thisshouldnotsoundnewatthispoint.

UsingtheCommandpattern,insteadofdirectlyexecutinganoperation,hasseveraladvantagesandapplications:

Acommandcanbescheduledforexecutionatalatertime.Acommandcanbeeasilyserializedandsentoverthenetwork.Thissimplepropertyallowsustodistributejobsacrossremotemachines,transmitcommandsfromthebrowsertotheserver,createRPCsystems,andsoon.Commandsmakeiteasytokeepahistoryofalltheoperationsexecutedonasystem.Commandsareanimportantpartofsomealgorithmsfordatasynchronizationandconflictresolution.Acommandscheduledforexecutioncanbecancelledifit'snotyetexecuted.It

Page 244: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

canalsobereverted(undone),bringingthestateoftheapplicationtothepointbeforethecommandwasexecuted.Severalcommandscanbegroupedtogether.Thiscanbeusedtocreateatomictransactionsortoimplementamechanismwherebyalltheoperationsinthegroupareexecutedatonce.Differentkindsoftransformationscanbeperformedonasetofcommandssuchasduplicateremoval,joiningandsplitting,orapplyingmorecomplexalgorithmssuchasOperationalTransformation(OT),whichisthebaseformostoftoday'sreal-timecollaborativesoftwaresuchascollaborativetextediting.

TipAgreatexplanationofhowOperationalTransformationworkscanbefoundatwww.codecommit.com/blog/java/understanding-and-applying-operational-transformation.

Theprecedinglistclearlyshowsushowimportantthispatternis,especiallyinaplatformlikeNode.js,wherenetworkingandasynchronousexecutionareessentialplayers.

AflexiblepatternAswealreadymentioned,thecommandpatterninJavaScriptcanbeimplementedinmanydifferentways;wearenowgoingtodemonstrateonlyafewofthemjusttogiveanideaofitsscope.

ThetaskpatternWecanstartoffwiththemostbasicandtrivialimplementation:thetaskpattern.TheeasiestwayinJavaScripttocreateanobjectrepresentinganinvocationis,ofcourse,creatingaclosure:

functioncreateTask(target,args){

returnfunction(){

target.apply(null,args);

}

}

Thisshouldnotlooknewatall;wehaveusedthispatternalreadysomanytimesalongthebook,andinparticular,inChapter2,AsynchronousControlFlowPatterns.Thistechniqueallowedustouseaseparatecomponenttocontrolandschedulethe

Page 245: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

executionofourtasks,whichisessentiallyequivalenttotheinvokeroftheCommandpattern.Forexample,doyourememberhowweweredefiningtaskstopasstotheasynclibrary?Orevenbetter,doyourememberhowwewereusingthunksincombinationwithgenerators?ThecallbackpatternitselfcanbeconsideredaverysimpleversionoftheCommandpattern.

AmorecomplexcommandLet'snowworkonanexampleofamorecomplexcommand;thistimewewanttosupportundoandserialization.Let'sstartwiththetargetofourcommands,alittleobjectthatisresponsibleforsendingstatusupdatestoaservicelikeTwitter.Weuseamockofsuchaserviceforsimplicity:

varstatusUpdateService={

statusUpdates:{},

sendUpdate:function(status){

console.log('Statussent:'+status);

varid=Math.floor(Math.random()*1000000);

statusUpdateService.statusUpdates[id]=status;

returnid;

},

destroyUpdate:function(id){

console.log('Statusremoved:'+id);

deletestatusUpdateService.statusUpdates[id];

}

}

Now,let'screateacommandtorepresentthepostingofanewstatusupdate:

functioncreateSendStatusCmd(service,status){

varpostId=null;

varcommand=function(){

postId=service.sendUpdate(status);

};

command.undo=function(){

if(postId){

service.destroyUpdate(postId);

postId=null;

}

};

command.serialize=function(){

return{type:'status',action:'post',status:status};

}

returncommand;

}

Page 246: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheprecedingfunctionisafactorythatproducesnewsendStatuscommands.Eachcommandimplementsthefollowingthreefunctionalities:

1. Thecommanditselfisafunctionthatwheninvokedwilltriggertheaction;inotherwords,itimplementsthetaskpatternthatwehaveseenbefore.Thecommandwhenexecutedwillsendanewstatusupdateusingthemethodsofthetargetservice.

2. Anundo()function,attachedtothemaintask,thatrevertstheeffectsoftheoperations.Inourcase,wearesimplyinvokingthedestroyUpdate()methodonthetargetservice.

3. Aserialize()functionthatbuildsaJSONobjectthatcontainsallthenecessaryinformationtoreconstructthesamecommandobject.

Afterthis,wecanbuildanInvoker;wecanstartbyimplementingitsconstructoranditsrun()method:

functionInvoker(){

this.history=[];

}

Invoker.prototype.run=function(cmd){

this.history.push(cmd);

cmd();

console.log('Commandexecuted',cmd.serialize());

};

Therun()methodthatwedefinedearlieristhebasicfunctionalityofourInvoker;itisresponsibleforsavingthecommandintothehistoryinstancevariableandthentriggeringtheexecutionofthecommanditself.Next,wecanaddanewmethodthatdelaystheexecutionofacommand:

Invoker.prototype.delay=function(cmd,delay){

varself=this;

setTimeout(function(){

self.run(cmd);

},delay)

}

Then,wecanimplementanundo()methodthatrevertsthelastcommand:

Invoker.prototype.undo=function(){

varcmd=this.history.pop();

cmd.undo();

console.log('Commandundone',cmd.serialize());

}

Page 247: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Finally,wealsowanttobeabletorunacommandonaremoteserver,byserializingandthentransferringitoverthenetworkusingawebservice:

Invoker.prototype.runRemotely=function(cmd){

varself=this;

request.post('http://localhost:3000/cmd',

{json:cmd.serialize()},function(err){

console.log('Commandexecutedremotely',cmd.serialize());

});

}

NowthatwehavetheCommand,theInvoker,andtheTarget,theonlycomponentmissingistheClient.Let'sstartfrominstantiatingtheInvoker:

varinvoker=newInvoker();

Then,wecancreateacommandusingthefollowinglineofcode:

varcommand=createSendStatusCmd(statusUpdateService,'HI!');

Wenowhaveacommandrepresentingthepostingofastatusmessage;wecanthendecidetodispatchitimmediately:

invoker.run(command);

Oops,wemadeamistake;let'sreverttothestateofourtimeline,asitwasbeforesendingthelastmessage:

invoker.undo();

Wecanalsodecidetoschedulethemessagetobesentinahourfromnow:

invoker.delay(command,1000*60*60);

Alternatively,wecandistributetheloadoftheapplicationbymigratingthetasktoanothermachine:

invoker.runRemotely(command);

Thelittleexamplethatwehavejustcreatedshowshowwrappinganoperationinacommandcanopenaworldofpossibilities,andthat'sjustthetipoftheiceberg.

Asthelastremarks,itisworthnoticingthatafull-fledgedCommandpatternhasbe

Page 248: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

usedonlywhenreallyneeded.Wesaw,infact,howmuchadditionalcodewehadtowritetosimplyinvokeamethodofstatusUpdateService;ifallthatweneedisonlyaninvocation,thenacomplexcommandwouldbeoverkill.Ifhowever,weneedtoscheduletheexecutionofataskorrunanasynchronousoperation,thenthesimplertaskpatternoffersthebestcompromise.Ifinstead,weneedmoreadvancedfeaturessuchasundosupport,transformations,conflictresolution,oroneoftheotherfancyusecasesthatwedescribedpreviously,usingamorecomplexrepresentationforthecommandisalmostnecessary.

Page 249: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryInthischapter,welearnedhowsomeofthetraditionalGoFdesignpatternscanbeappliedtoJavaScriptand,inparticular,totheNode.jsphilosophy.Someofthemweretransformed,someweresimplified,otherrenamedoradaptedaspartoftheirassimilationbythelanguage,theplatform,andthecommunity.WeemphasizedhowsimplepatternssuchasFactorycangreatlyimprovetheflexibilityofourcodeandhowwithProxy,Decorator,andAdapterwecanmanipulate,extend,andadapttheinterfaceofexistingobjects.Strategy,State,andTemplate,instead,haveshownushowtosplitabiggeralgorithmintoitsstaticandvariableparts,allowingustoimprovethecodereuseandextensibilityofourcomponents.BylearningtheMiddlewarepattern,wearenowabletoprocessourdatausingasimple,extensible,andelegantparadigm.Finally,theCommandpatternprovideduswithasimpleabstractiontomakeanyoperationmoreflexibleandpowerful.

WealsoacquiredotherevidenceofhowJavaScriptisaboutgettingthingsdoneandbuildingsoftwarebycomposingdifferentreusableobjectsorfunctionsinsteadofextendingmanylittleclassesorinterfaces.Also,fordeveloperscomingfromotherObject-Orientedlanguages,itmighthavelookedweirdtoseehowdifferentsomedesignpatternsbecomewhenimplementedinJavaScript;somemightfeellostknowingthattheremightbenotjustonebutrathermanydifferentwaysforimplementingadesignpattern.

JavaScriptisapragmaticlanguage,wesaid;itallowsustogetthingsdonequickly;however,withoutanykindofstructureorguideline,weareaskingfortrouble.That'swherethisbookand,inparticular,thischaptercomesuseful.Theytrytoteachtherightbalancebetweencreativityandrigor,byshowingthattherearepatternsthatcanbereusedtoimproveourcodebutalsothattheirimplementationisnotthemostimportantdetail,asitcanvaryalotorevenoverlapwithotherpatterns;whatreallymattersisinsteadtheblueprint,theguideline,theideaatthebaseofthepattern.ThisistherealreusablepieceofinformationthatwecanexploittodesignbetterNode.jsapplicationswithbothfunandmethod.

Inthenextchapter,wewillanalyzesomemoredesignpatternsbyfocusingononeofthemostopinionatedaspectsofprogramming:howtoorganizeandconnectmodulestogether.

Page 250: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter5.WiringModulesTheNode.jsmodulesystembrilliantlyfillsanoldgapintheJavaScriptlanguage:thelackofanativewayoforganizingcodeintodifferentself-containedunits.Oneofitsbiggestadvantagesistheabilitytolinkthesemodulestogetherusingtherequire()function(aswehaveseeninChapter1,Node.jsDesignFundamentals),asimpleyetpowerfulapproach.However,manydevelopersnewtoNode.jsmightfindthisconfusing;oneofthemostaskedquestionsisinfact:what'sthebestwaytopassaninstanceofcomponentXintomoduleY?

Sometimes,thisconfusionresultsinadesperatequestfortheSingletonpatterninthehopeoffindingamorefamiliarwaytolinkourmodulestogether.Ontheotherside,somemightoverusetheDependencyInjectionpattern,leveragingittohandleanytypeofdependency(evenstateless)withoutaparticularreason.ItshouldnotbesurprisingthattheartofmodulewiringisoneofthemostcontroversialandopinionatedtopicsinNode.js.Therearemanyschoolsofthoughtinfluencingthisarea,butnoneofthemcanbeconsideredtopossesstheundisputedtruth.Everyapproach,infact,hasitsprosandconsandtheyoftenendupmixedtogetherinthesameapplication,adapted,customized,orusedindisguiseunderothernames.

Inthischapter,we'regoingtoanalyzethevariousapproachesforwiringmodulesandhighlighttheirstrengthsandweaknesses,sothatwecanrationallychooseandmixthemtogetherdependingonthebalancebetweensimplicity,reusability,andextensibilitythatwewanttoobtain.Inparticular,we'regoingtopresentthemostimportantpatternsrelatedtothistopic,whichareasfollows:

HardcodeddependencyDependencyinjectionServicelocatorDependencyinjectioncontainers

Wewillthenexploreacloselyrelatedproblem,namely,howtowireplugins.Thiscanbeconsideredaspecializationofmodulewiringanditmostlypresentsthesametraits,butthecontextofitsapplicationisslightlydifferentandpresentsitsownchallenges,especiallywhenapluginisdistributedasaseparateNode.jspackage.Wewilllearnthemaintechniquestocreateaplugin-capablearchitectureandwewillthenfocusonhowtointegratethesepluginsintotheflowofthemainapplication.

Attheendofthischapter,theobscureartofNode.jsmodulewiringshouldnotbeamysterytousanymore.

Page 251: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ModulesanddependenciesEverymodernapplicationistheresultoftheaggregationofseveralcomponentsand,astheapplicationgrows,thewayweconnectthesecomponentsbecomesawinorlosefactor.It'snotonlyaproblemrelatedtotechnicalaspectssuchasextensibility,butit'salsoaconcernwiththewayweperceivethesystem.Atangleddependencygraphisaliabilityanditaddstothetechnicaldebtoftheproject;insuchasituation,anychangeinthecodeaimedtoeithermodifyorextenditsfunctionalitycanresultinatremendouseffort.

Intheworstcase,thecomponentsaresotightlyconnectedtogetherthatitbecomesimpossibletoaddorchangeanythingwithoutrefactoringorevencompletelyrewritingentirepartsoftheapplication.This,ofcourse,doesnotmeanthatwehavetoover-engineerourdesignstartingfromtheveryfirstmodule,butsurelyfindingagoodbalancefromtheverybeginningcanmakeahugedifference.

Node.jsprovidesagreattoolfororganizingandwiringthecomponentsofanapplicationtogether:it'stheCommonJSmodulesystem.However,themodulesystemaloneisnotaguaranteeforsuccess;ifononeside,itaddsaconvenientlevelofindirectionbetweentheclientmoduleandthedependency,thenontheother,itmightintroduceatightercouplingifnotusedproperly.Inthissection,wewilldiscusssomefundamentalaspectsofdependencywiringinNode.js.

ThemostcommondependencyinNode.jsInsoftwarearchitecture,wecanconsideranyentity,state,ordataformat,whichinfluencesthebehaviororstructureofacomponent,asadependency.Forexample,acomponentmightusetheservicesofferedbyanothercomponent,relyonaparticularglobalstateofthesystem,orimplementaspecificcommunicationprotocolinordertoexchangeinformationwithothercomponents,andsoon.Theconceptofdependencyisverybroadandsometimeshardtoevaluate.

InNode.js,though,wecanimmediatelyidentifyoneessentialtypeofdependency,whichisthemostcommonandeasytoidentify;ofcourse,wearetalkingaboutthedependencybetweenmodules.Modulesarethefundamentalmechanismatourdisposaltoorganizeandstructureourcode;it'sunreasonabletobuildalargeapplicationwithoutrelyingonthemodulesystematall.Ifusedproperlytogroupthevariouselementsofanapplication,itcanbringalotofadvantages.Infact,thepropertiesofamodulecanbesummedupasfollows:

Amoduleismorereadableandunderstandablebecause(ideally)it'smorefocused

Page 252: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Beingrepresentedasaseparatefile,amoduleiseasiertoidentifyAmodulecanbemoreeasilyreusedacrossdifferentapplications

Amodulerepresentstheperfectlevelofgranularityforperforminginformationhidingandgivesaneffectivemechanismtoexposeonlythepublicinterfaceofacomponent(usingmodule.exports).

However,simplyspreadingthefunctionalityofanapplicationoralibraryacrossdifferentmodulesisnotenoughforasuccessfuldesign;ithastobedoneright.Oneofthefallaciesisendingupinasituationwheretherelationshipbetweenourmodulesbecomessostrongwecreateauniquemonolithicentity,whereremovingorreplacingamodulewouldreverberateacrossmostofthearchitecture.Weareimmediatelyabletorecognizethatthewayweorganizeourcodeintomodulesandthewayweconnectthemtogether,playastrategicrole.Andaswithanyprobleminsoftwaredesign,it'samatteroffindingtherightbalancebetweendifferentmeasures.

CohesionandCouplingThetwomostimportantpropertiestobalancewhenbuildingmodulesarecohesionandcoupling.Theycanbeappliedtoanytypeofacomponentorsubsysteminsoftwarearchitecture,sowecanusethemasguidelineswhenbuildingNode.jsmodulesaswell.Thesetwopropertiescanbedefinedasfollows:

Cohesion:Thisisameasureofthecorrelationbetweenthefunctionalitiesofacomponent.Forexample,amodulethatdoesonlyonething,whereallitspartscontributetothatonesingletaskhasahighcohesion.Amodulethatcontainsfunctionstosaveanytypeofobjectintoadatabase—saveProduct(),saveInvoice(),saveUser(),andsoon—hasalowcohesion.Coupling:Thismeasureshowmuchacomponentisdependentontheothercomponentsofasystem.Forexample,amoduleistightlycoupledtoanothermodulewhenitdirectlyreadsormodifiesthedataoftheothermodule.Also,twomodulesthatinteractviaaglobalorsharedstatearetightlycoupled.Ontheotherside,twomodulesthatcommunicateonlyviathepassingofparametersarelooselycoupled.

Thedesirablescenarioistohaveahighcohesionandaloosecoupling,whichusuallyresultsinmoreunderstandable,reusable,andextensiblemodules.

StatefulmodulesInJavaScript,everythingisanobject.Wedon'thaveabstractconceptssuchaspureinterfacesorclasses;itsdynamictypingalreadyprovidesanaturalmechanismto

Page 253: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

decoupletheinterface(orpolicy)fromtheimplementation(ordetail).That'soneofthereasonswhysomeofthedesignpatternsthatwehaveseeninChapter4,DesignPatterns,lookedsodifferentandsimplifiedcomparedtotheirtraditionalimplementation.

InJavaScript,wehaveminimalproblemsinseparatinginterfacesfromimplementations;however,bysimplyusingtheNode.jsmodulesystem,wearealreadyintroducingahardcodedrelationshipwithoneparticularimplementation.Undernormalconditions,thereisnothingwrongwiththis,butifweuserequire()toloadamodulethatexportsastatefulinstance,suchasadbhandle,anHTTPserverinstance,theinstanceofaservice,oringeneralanyobjectwhichisnotstateless,weareactuallyreferencingsomethingverysimilartoaSingleton,thusinheritingitsprosandcons,withtheadditionofsomecaveats.

TheSingletonpatterninNode.jsAlotofpeoplenewtoNode.jsgetconfusedabouthowtoimplementtheSingletonpatterncorrectly,mostofthetimewiththesimpleintentofsharinganinstanceacrossthevariousmodulesofanapplication.But,theanswerinNode.jsiseasierthanwhatwemightthink;simplyexportinganinstanceusingmodule.exportsisalreadyenoughtoobtainsomethingverysimilartotheSingletonpattern.Consider,forexample,thefollowinglineofcode:

//'db.js'module

module.exports=newDatabase('my-app-db');

Bysimplyexportinganewinstanceofourdatabase,wecanalreadyassumethatwithinthecurrentpackage(whichcaneasilybetheentirecodeofourapplication),wearegoingtohaveonlyoneinstanceofthedbmodule.Thisispossiblebecause,asweknow,Node.jswillcachethemoduleafterthefirstinvocationofrequire(),makingsuretonotexecuteitagainatanysubsequentinvocation,returninginsteadthecachedinstance.Forexample,wecaneasilyobtainasharedinstanceofthedbmodulethatwedefinedearlier,withthefollowinglineofcode:

vardb=require('./db');

Butthereisacaveat;themoduleiscachedusingitsfullpathaslookupkey,thereforeitisguaranteedtobeaSingletononlywithinthecurrentpackage.WesawinChapter1,Node.jsDesignFundamentals,thateachpackagemighthaveitsownsetofprivatedependenciesinsideitsnode_modulesdirectory,whichmightresultinmultipleinstancesofthesamepackageandthereforeofthesamemodule,withtheresultthatourSingletonmightnotbesingleanymore.Consider,forexample,thecasewherethedbmoduleiswrappedintoapackagenamedmydb.Thefollowinglinesofcodewill

Page 254: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

beinitspackage.jsonfile:

{

"name":"mydb",

"main":"db.js"

}

Nowconsiderthefollowingpackagedependencytree:

app/

`--node_modules

|--packageA

|`--node_modules

|`--mydb

`--packageB

`--node_modules

`--mydb

BothpackageAandpackageBhaveadependencyonthemydbpackage;inturn,theapppackage,whichisourmainapplication,dependsonpackageAandpackageB.Thescenariowejustdescribed,willbreaktheassumptionabouttheuniquenessofthedatabaseinstance;infact,bothpackageAandpackageBwillloadthedatabaseinstanceusingacommandsuchasthefollowing:

vardb=require('mydb');

However,packageAandpackageBwillactuallyloadtwodifferentinstancesofourpretendingsingleton,becausethemydbmodulewillresolvetoadifferentdirectorydependingonthepackageitisrequiredfrom.

Atthispoint,wecaneasilysaythattheSingletonpattern,asdescribedintheliterature,doesnotexistinNode.jsunlesswedon'tusearealglobalvariabletostoreit,somethingsuchasthefollowing:

global.db=newDatabase('my-app-db');

Thiswouldguaranteethattheinstancewillbeonlyoneandsharedacrosstheentireapplication,andnotjustthesamepackage.However,thisisapracticetoavoidatallcosts;mostofthetime,wedon'treallyneedapureSingleton,andanyway,aswewillseelater,thereareotherpatternsthatwecanusetoshareaninstanceacrossthedifferentpackages.

Note

Page 255: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Throughoutthisbook,forsimplicity,wewillusethetermSingletontodescribeastatefulobjectexportedbyamodule,evenifthisdoesn'trepresentarealsingletoninthestrictdefinitionoftheterm.Wecansurelysaythough,thatitsharesthesamepracticalintentwiththeoriginalpattern:toeasilyshareastateacrossdifferentcomponents.

Page 256: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

PatternsforwiringmodulesNowthatwehavediscussedsomebasictheoryarounddependenciesandcoupling,wearereadytodiveintosomemorepracticalconcepts.Inthissection,infact,wearegoingtopresentthemainmodulewiringpatterns.Ourfocuswillbemainlypointedtowardsthewiringofstatefulinstances,whichare,withoutanydoubts,themostimportanttypeofdependenciesinanapplication.

HardcodeddependencyWestartouranalysisbylookingatthemostconventionalrelationshipbetweentwomodules,whichisthehardcodeddependency.InNode.js,thisisobtainedwhenaclientmoduleexplicitlyloadsanothermoduleusingrequire().Aswewillseeinthissection,thiswayofestablishingmoduledependenciesissimpleandeffective,butwehavetopayadditionalattentiontohardcodingdependencieswithstatefulinstances,asthiswouldlimitthereusabilityofourmodules.

BuildinganauthenticationserverusinghardcodeddependenciesLet'sstartouranalysisbylookingatthestructurerepresentedbythefollowingfigure:

Theprecedingfigureshowsatypicalexampleoflayeredarchitecture;itdescribesthestructureofasimpleauthenticationsystem.AuthControlleracceptstheinputfromtheclient,extractsthelogininformationfromtherequest,andperformssomepreliminaryvalidation.ItthenreliesonAuthServicetocheckwhethertheprovidedcredentialsmatchwiththeinformationstoredinthedatabase;thisisdonebyexecutingsomespecificqueriesusingaDBhandleasameanstocommunicatewiththedatabase.Thewaythesethreecomponentsareconnectedtogetherwilldeterminetheirlevelofreusability,testability,andmaintainability.

ThemostnaturalwaytowirethesecomponentstogetherisrequiringtheDBmodulefromAuthServiceandthenrequiringAuthServicefromAuthController.Thisisthehardcodeddependencythatwearetalkingabout.

Let'sdemonstratethisinpracticebyactuallyimplementingthesystemthatwejustdescribed.Let'sthendesignasimpleauthenticationserver,whichexposesthe

Page 257: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

followingtwoHTTPAPIs:

POST'/login':ThisreceivesaJSONobjectthatcontainsausernameandpasswordpairtoauthenticate.Onsuccess,itreturnsaJSONWebToken(JWT),whichcanbeusedinsubsequentrequeststoverifytheidentityoftheuser.

NoteJSONWebTokenisaformatforrepresentingandsharingclaimsbetweenparties.ItspopularityisgrowingwiththeexplosionofSinglePageApplicationsandCross-originresourcesharing(CORS),asamoreflexiblealternativetocookie-basedauthentication.ToknowmoreaboutJWT,youcanrefertoitsspecification(currentlyindraft)athttp://self-issued.info/docs/draft-ietf-oauth-json-web-token.html.

GET'/checkToken':ThisreadsatokenfromaGETqueryparameterandverifiesitsvalidity.

Forthisexample,wearegoingtouseseveraltechnologies;someofthemarenotnewtous.Inparticular,wearegoingtouseexpress(https://npmjs.org/package/express)toimplementtheWebAPI,andlevelup(https://npmjs.org/package/levelup)tostoretheuser'sdata.

Thedbmodule

Let'sstartbybuildingourapplicationfromthebottomup;theveryfirstthingweneedisamodulethatexposesalevelUpdatabaseinstance.Let'sdothisbycreatinganewfilenamedlib/db.jsandincludingthefollowingcontent:

varlevel=require('level');

varsublevel=require('level-sublevel');

module.exports=sublevel(

level('example-db',{valueEncoding:'json'})

);

TheprecedingmodulesimplycreatesaconnectiontoaLevelDBdatabasestoredinthe./example-dbdirectory,thenitdecoratestheinstanceusingthesublevelplugin(https://npmjs.org/package/level-sublevel),whichaddsthesupporttocreateandqueryseparatesectionsofthedatabase(itcanbecomparedtoaSQLtableorMongoDBcollection).Theobjectexportedbythemoduleisthedatabasehandle

Page 258: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

itself,whichisastatefulinstance,therefore,wearecreatingasingleton.

TheauthServicemodule

Nowthatwehavethedbsingleton,wecanuseittoimplementthelib/authService.jsmodule,whichisthecomponentresponsibleforcheckingauser'scredentialsagainsttheinformationinthedatabase.Thecodeisasfollows(onlytherelevantpartsareshown):

[...]

vardb=require('./db');

varusers=db.sublevel('users');

vartokenSecret='SHHH!';

exports.login=function(username,password,callback){

users.get(username,function(err,user){

[...]

});

};

exports.checkToken=function(token,callback){

[...]

users.get(userData.username,function(err,user){

[...]

});

};

TheauthServicemoduleimplementsthelogin()service,whichisresponsibleforcheckingausername/passwordpairagainsttheinformationinthedatabase,andthecheckToken()service,whichtakesinatokenandverifiesitsvalidity.

Theprecedingcodealsoshowsthefirstexampleofahardcodeddependencywithastatefulmodule.Wearetalkingaboutthedbmodule,whichweloadbysimplyrequiringit.Theresultingdbvariablecontainsanalreadyinitializeddatabasehandlethatwecanusestraightawaytoperformourqueries.

Atthispoint,wecanseethatallthecodethatwecreatedfortheauthServicemoduledoesnotreallynecessitateoneparticularinstanceofthedbmodule—anyinstancewillsimplywork.However,wehardcodedthedependencytooneparticulardbinstance,andthismeansthatwewillbeunabletoreuseauthServiceincombinationwithanotherdatabaseinstancewithouttouchingitscode.

TheauthControllermodule

Continuingtogoupinthelayersoftheapplication,wearenowgoingtoseewhatthelib/authController.jsmodulelookslike.Thismoduleisresponsibleforhandlingthe

Page 259: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

HTTPrequestsandit'sessentiallyacollectionoftheexpressroutes;themodule'scodeisthefollowing:

varauthService=require('./authService');

exports.login=function(req,res,next){

authService.login(req.body.username,req.body.password,

function(err,result){

[...]

}

);

};

exports.checkToken=function(req,res,next){

authService.checkToken(req.query.token,

function(err,result){

[...]

}

);

};

TheauthControllermoduleimplementstwoexpressroutes:oneforperformingtheloginandreturningthecorrespondingauthenticationtoken(login()),andanotherforcheckingthevalidityofthetoken(checkToken()).BoththeroutesdelegatemostoftheirlogictoauthService,sotheironlyjobistodealwiththeHTTPrequestandresponse.

Wecanseethat,inthiscasetoo,wearehardcodingthedependencywithastatefulmodule,authService.Yes,theauthServicemoduleisstatefulbytransitivity,becauseitdependsdirectlyonthedbmodule.Withthis,weshouldstarttounderstandhowahardcodeddependencycaneasilypropagateacrossthestructureoftheentireapplication:theauthControllermoduledependsontheauthServicemodule,whichinturndependsonthedbmodule;transitively,thismeansthattheauthServicemoduleitselfisindirectlylinkedtooneparticulardbinstance.

Theappmodule

Finally,wecanputallthepiecestogetherbyimplementingtheentrypointoftheapplication.Followingtheconvention,wewillplacethislogicinamodulenamedapp.jssittingintherootofourproject,asfollows:

varexpress=require('express');

varbodyParser=require('body-parser');

varerrorHandler=require('errorhandler');

varhttp=require('http');

varauthController=require('./lib/authController');

Page 260: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varapp=module.exports=express();

app.use(bodyParser.json());

app.post('/login',authController.login);

app.get('/checkToken',authController.checkToken);

app.use(errorHandler());

http.createServer(app).listen(3000,function(){

console.log('Expressserverstarted');

});

Aswecansee,ourappmoduleisreallybasic;itcontainsasimpleexpressserver,whichregisterssomemiddlewareandthetworoutesexportedbyauthController.Ofcourse,themostimportantlineofcodeforusiswherewerequireauthControllertocreateahardcodeddependencywithitsstatefulinstance.

Runningtheauthenticationserver

Beforewecantrytheauthenticationserverthatwejustimplemented,weadviseyoutopopulatethedatabasewithsomesampledatausingthepopulate_db.jsscript,whichisprovidedinthecodesamples.Afterdoingthis,wecanfireuptheserverbyrunningthefollowingcommand:

nodeapp

Wecanthentrytoinvokethetwowebservicesthatwecreated;wecanuseaRESTclienttodothisoralternativelythegoodoldcurlcommand.Forexample,toexecutealogin,wecanrunthefollowingcommand:

curl-XPOST-d'{"username":"alice","password":"secret"}'

http://localhost:3000/login-H"Content-Type:

application/json"

Theprecedingcommandshouldreturnatokenthatwecanusetotestthe/checkLoginwebservice(justreplace<TOKEN>inthefollowingcommand):

curl-XGET-H"Accept:application/json"

http://localhost:3000/checkToken?token=<TOKENHERE>

Theprecedingcommandshouldreturnastringsuchasthefollowing,whichconfirmsthatourserverisworkingasexpected:

{"ok":"true","user":{"username":"alice"

}}

Prosandconsofhardcodeddependencies

Page 261: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thesamplewejustimplemented,demonstratedtheconventionalwayofwiringmodulesinNode.js,leveragingthefullpowerofitsmodulesystemtomanagethedependenciesbetweenthevariouscomponentsoftheapplication.Weexportedstatefulinstancesfromourmodules,lettingNode.jsmanagetheirlifecycle,andthenwerequiredthemdirectlyfromotherpartsoftheapplication.Theresultisanimmediatelyintuitiveorganization,easytounderstandanddebug,whereeachmoduleinitializesandwiresitselfwithoutanyexternalintervention.

Ontheotherside,however,hardcodingthedependencyonastatefulinstancelimitsthepossibilityofwiringthemoduleagainstotherinstances,whichmakesitlessreusableandhardertounittest.Forexample,reusingauthServiceincombinationwithanotherdatabaseinstancewouldbeclosetoimpossible,asitsdependencyishardcodedwithoneparticularinstance.Similarly,testingauthServiceinisolationcanbeadifficulttask,becausewecannoteasilymockthedatabaseusedbythemodule.

Asalastconsideration,it'simportanttoseethatmostofthedisadvantagesofusinghardcodeddependenciesareassociatedwithstatefulinstances.Thismeansthatifweuserequire()toloadastatelessmodule,forexample,afactory,constructor,orasetofstatelessfunctions,wedon'tincurthesamekindofproblems.Wewillstillhaveatightcouplingwithaspecificimplementation,butinNode.js,thisusuallydoesnotimpactthereusabilityofacomponent,asitdoesnotintroduceacouplingwithaparticularstate.

DependencyinjectionThedependencyinjection(DI)patternisprobablyoneofthemostmisunderstoodconceptsinsoftwaredesign.ManyassociatethetermwithframeworksanddependencyinjectioncontainerssuchasSpring(forJavaandC#)orPimple(forPHP),butinrealityitisamuchsimplerconcept.Themainideabehindthedependencyinjectionpatternisthedependenciesofacomponentbeingprovidedasinputbyanexternalentity.

Suchanentitycanbeaclientcomponentoraglobalcontainer,whichcentralizesthewiringofallthemodulesofthesystem.Themainadvantageofthisapproachisanimproveddecoupling,especiallyformodulesdependingonstatefulinstances.UsingDI,eachdependency,insteadofbeinghardcodedintothemodule,isreceivedfromtheoutside.Thismeansthatthemodulecanbeconfiguredtouseanydependencyandthereforecanbereusedindifferentcontexts.

Todemonstratethispatterninpractice,wearenowgoingtorefactortheauthenticationserverthatwebuiltintheprevioussection,usingdependencyinjectiontowireitsmodules.

Page 262: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

RefactoringtheauthenticationservertousedependencyinjectionRefactoringourmodulestousedependencyinjectioninvolvestheuseofaverysimplerecipe:insteadofhardcodingthedependencytoastatefulinstance,wewillinsteadcreateafactory,whichtakesasetofdependenciesasarguments.

Let'sstartimmediatelywiththisrefactoring;let'sworkonthelib/db.jsmodulegivenasfollows:

varlevel=require('level');

varsublevel=require('level-sublevel');

module.exports=function(dbName){

returnsublevel(

level(dbName,{valueEncoding:'json'})

);

};

Thefirststepinourrefactoringprocessistotransformthedbmoduleintoafactory.Theresultisthatwecannowuseittocreateasmanydatabaseinstancesaswewant;thismeansthattheentiremoduleisnowreusableandstateless.

Let'smoveonandimplementthenewversionofthelib/authService.jsmodule:

varjwt=require('jwt-simple');

varbcrypt=require('bcrypt');

module.exports=function(db,tokenSecret){

varusers=db.sublevel('users');

varauthService={};

authService.login=function(username,password,callback){

//...sameasinthepreviousversion

};

authService.checkToken=function(token,callback){

//...sameasinthepreviousversion

};

returnauthService;

};

Also,theauthServicemoduleisnowstateless;itdoesn'texportanyparticularinstanceanymore,justasimplefactory.Themostimportantdetailthoughisthatwemadethedbdependencyinjectableasanargumentofthefactoryfunction,removingwhatpreviouslywasahardcodeddependency.Thissimplechangeenablesusto

Page 263: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

createanewauthServicemodulebywiringittoanydatabaseinstance.

Wecanrefactorthelib/authController.jsmoduleinasimilarwayasfollows:

module.exports=function(authService){

varauthController={};

authController.login=function(req,res,next){

//...sameasinthepreviousversion

};

authController.checkToken=function(req,res,next){

//...sameasinthepreviousversion

};

returnauthController;

};

TheauthControllermoduledoesnothaveanyhardcodeddependencyatall,notevenstateless!Theonlydependency,theauthServicemodule,isprovidedasinputtothefactoryatthemomentofitsinvocation.

Okay,nowit'stimetoseewhereallthesemodulesareactuallycreatedandwiredtogether;theanswerliesintheapp.jsmodule,whichrepresentsthetopmostlayerinourapplication;itscodeisthefollowing:

[...]

vardbFactory=require('./lib/db');//[1]

varauthServiceFactory=require('./lib/authService');

varauthControllerFactory=require('./lib/authController');

vardb=dbFactory('example-db');//[2]

varauthService=authServiceFactory(db,'SHHH!');

varauthController=authControllerFactory(authService);

app.post('/login',authController.login);//[3]

app.get('/checkToken',authController.checkToken);

[...]

Thepreviouscodecanbesummedupasfollows:

1. Firstly,weloadthefactoriesofourservices;atthispoint,theyarestillstatelessobjects.

2. Next,weinstantiateeachservicebyprovidingthedependenciesitrequires.Thisisthephasewhereallthemodulesarecreatedandwired.

3. Finally,weregistertheroutesoftheauthControllermodulewiththeexpressserveraswewouldnormallydo.

Page 264: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Ourauthenticationserverisnowwiredusingdependencyinjectionandreadytobeusedagain.

ThedifferenttypesofdependencyinjectionTheexamplewejustpresenteddemonstratedonlyonetypeofdependencyinjection(factoryinjection),butthereareacouplemoreworthmentioning:

Constructorinjection:InthistypeofDI,thedependenciesarepassedtoaconstructoratthemomentofitscreation;onepossibleexamplecanbethefollowing:

varservice=newService(dependencyA,dependencyB);

Propertyinjection:InthistypeofDI,thedependenciesareattachedtoanobjectafteritscreation,asdemonstratedbythefollowingcode:

varservice=newService();//worksalsowithafactory

service.dependencyA=anInstanceOfDependencyA;

Propertyinjectionimpliesthatanobjectiscreatedinaninconsistentstate,becauseit'snotwiredtoitsdependencies,soit'stheleastrobust,butsometimesitmightbeusefulwhentherearecyclesbetweenthedependencies.Forexample,ifwehavetwocomponents,AandB,bothusingfactoryorconstructorinjectionandbothdependingoneachother,wecannotinstantiateeitherofthembecausebothwouldrequiretheothertoexistinordertobecreated.Let'sconsiderasimpleexample,asfollows:

functionAfactory(b){

return{

foo:function(){

b.say();

},

what:function(){

return'Hello!';

}

}

}

functionBfactory(a){

return{

a:a,

say:function(){

console.log('Isay:'+a.what);

}

}

}

Page 265: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thedependencydeadlockbetweenthetwoprecedingfactoriescanberesolvedonlyusingpropertyinjection,forexample,byfirstcreatinganincompleteinstanceofB,whichthencanbeusedtocreateA.Finally,wewillinjectAintoBbysettingtherelativepropertyasfollows:

varb=Bfactory(null);

vara=Afactory(b);

a.b=b;

NoteInsomerarecircumstances,acycleinthedependencygraphisnoteasilyavoidable;however,itisimportanttorememberthatoftenitisasymptomofbaddesign.

ProsandconsofdependencyinjectionIntheauthenticationserverexample,usingdependencyinjectionwewereabletodecoupleourmodulesfromaparticulardependencyinstance.Theresultisthatwecannowreuseeachmodulewithminimaleffortandwithoutanychangeintheircode.Testingamodulethatusesthedependencyinjectionpatternisalsogreatlysimplified;wecaneasilyprovidemockeddependenciesandtestourmodulesinisolationfromthestateoftherestofthesystem.

Anotherimportantaspecttobehighlightedfromtheexamplewepresentedearlieristhatweshiftedthedependencywiringresponsibilityfromthebottomtothetopofourarchitecture.Theideaisthathigh-levelcomponentsarebynaturelessreusablethanlow-levelcomponents,andthat'sbecausethemorewegoupinthelayersofanapplicationthemoreacomponentbecomesspecific.

Startingfromthisassumptionwecanthenunderstandthattheconventionalwaytoseeanapplicationarchitecture,wherehigh-levelcomponentsowntheirlower-leveldependencies,canbeinverted,sothatthelower-levelcomponentsdependonlyonaninterface(inJavaScript,it'sjusttheinterfacethatweexpectfromadependency),whiletheownershipofdefiningtheimplementationofadependencyisgiventothehigher-levelcomponents.Inourauthenticationserver,infact,allthedependenciesareinstantiatedandwiredinthetopmostcomponent,theappmodule,whichisalsothelessreusableandsoisthemostexpendableintermsofcoupling.

Alltheseadvantagesintermsofdecouplingandreusability,though,comewithapricetopay.Ingeneral,theinabilitytoresolveadependencyatcodingtimemakesitmoredifficulttounderstandtherelationshipbetweenthevariouscomponentsofasystem.

Page 266: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Also,ifwelookatthewayweinstantiatedallthedependenciesintheappmodule,wecanseethatwehadtofollowaspecificorder;wepracticallyhadtomanuallybuildthedependencygraphoftheentireapplication.Thiscanbecomeunmanageablewhenthenumberofmodulestowirebecomeshigh.

Aviablesolutiontothisproblemistosplitthedependencyownershipbetweenmultiplecomponents,insteadofhavingitcentralizedallinoneplace.Thiscanreducethecomplexityinvolvedinmanagingthedependenciesexponentially,aseachcomponentwouldberesponsibleonlyforitsparticulardependencysubgraph.Ofcourse,wecanalsochoosetousedependencyinjectiononlylocally,justwhennecessary,insteadofbuildingtheentireapplicationontopofit.

Wewillseelaterinthechapterthat,anotherpossiblesolutiontosimplifythewiringofmodulesincomplexarchitecturesistouseadependencyinjectioncontainer,acomponentexclusivelyresponsibleforinstantiatingandwiringallthedependenciesofanapplication.

Usingdependencyinjectionsurelyincreasesthecomplexityandverbosityofourmodules,butaswesawearlier,therearemanygoodreasonsfordoingthis.Itisuptoustochoosetherightapproach,dependingonthebalancebetweensimplicityandreusabilitythatwewanttoobtain.

NoteDependencyinjectionisoftenmentionedincombinationwiththeDependencyInversionprincipleandInversionofControl;however,theyallaredifferentconcepts(eventhoughcorrelated).

ServicelocatorIntheprevioussection,welearnedhowdependencyinjectioncanliterallytransformthewaywewireourdependencies,obtainingreusableanddecoupledmodules.AnotherpatternwithaverysimilarintentisServiceLocator.Itscoreprincipleistohaveacentralregistryinordertomanagethecomponentsofthesystemandtoactasamediatorwheneveramoduleneedstoloadadependency.Theideaistoasktheservicelocatorforthedependency,insteadofhardcodingit,asshowninthefollowingimage:

Page 267: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Itisimportanttounderstandthatbyusingaservicelocatorweareintroducingadependencyonit,sothewaywewireittoourmodulesdeterminestheirlevelofcouplingandthereforetheirreusability.InNode.js,wecanidentifythreetypesofservicelocators,dependingonthewaytheyarewiredtothevariouscomponentsofthesystem:

HardcodeddependencyonservicelocatorInjectedservicelocatorGlobalservicelocator

Thefirstisdefinitelytheoneofferingtheleastadvantagesintermsofdecoupling,asitconsistsofdirectlyreferencingtheinstanceoftheservicelocatorusingrequire().InNode.js,thiscanbeconsideredananti-patternbecauseitintroducesatightcouplingwiththecomponentsupposedlymeanttoprovideabetterdecoupling.Inthiscontext,aservicelocatorclearlydoesnotprovideanyvalueintermsofreusability,butitonlyaddsanotherlevelofindirectionandcomplexity.

Ontheotherside,aninjectedservicelocatorisreferencedbyacomponentthroughdependencyinjection.Thiscanbeconsideredamoreconvenientwayforinjectinganentiresetofdependenciesatonce,insteadofprovidingthemonebyone,butaswewillsee,itsadvantagesdonotendhere.

Thethirdwayofreferencingaservicelocatorisdirectlyfromtheglobalscope.Thishasthesamedisadvantagesasthatofthehardcodedservicelocator,butsinceitisglobal,itisarealsingletonandthereforeitcanbeeasilyusedasapatternforsharinginstancesbetweenpackages.Wewillseehowthisworkslaterinthechapter,butfornowwecancertainlysaythatthereareveryfewreasonsforusingaglobalservicelocator.

Tip

Page 268: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheNode.jsmodulesystemalreadyimplementsavariationoftheservicelocatorpattern,withrequire()representingtheglobalinstanceoftheservicelocatoritself.

Alltheconsiderationsdiscussedherewillbemoreclearoncewestartusingtheservicelocatorpatterninarealexample.Let'sthenrefactortheauthenticationserveragaintoapplywhatwelearned.

RefactoringtheauthenticationservertouseaservicelocatorWearenowgoingtoconverttheauthenticationservertouseaninjectedservicelocator.Todothis,thefirststepistoimplementtheservicelocatoritself;wewilluseanewmodule,'lib/serviceLocator.js':

module.exports=function(){

vardependencies={};

varfactories={};

varserviceLocator={};

serviceLocator.factory=function(name,factory){//[1]

factories[name]=factory;

};

serviceLocator.register=function(name,instance){//[2]

dependencies[name]=instance;

};

serviceLocator.get=function(name){//[3]

if(!dependencies[name]){

varfactory=factories[name];

dependencies[name]=factory&&factory(serviceLocator);

if(!dependencies[name]){

thrownewError('Cannotfindmodule:'+name);

}

}

returndependencies[name];

};

returnserviceLocator;

};

OurserviceLocatormoduleisafactoryreturninganobjectwiththreemethods:

factory()isusedtoassociateacomponentnameagainstafactory.register()isusedtoassociateacomponentnamedirectlywithaninstance.get()retrievesacomponentbyitsname.Ifaninstanceisalreadyavailable,it

Page 269: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

simplyreturnsit;otherwise,ittriestoinvoketheregisteredfactorytoobtainanewinstance.Itisveryimportanttoobservethatthemodulefactoriesareinvokedbyinjectingthecurrentinstanceoftheservicelocator(serviceLocator).Thisisthecoremechanismofthepatternthatallowsthedependencygraphforoursystemtobebuiltautomaticallyandon-demand.Wewillseehowthisworksinamoment.

NoteAsimplepattern,closelyresemblingaservicelocator,istouseanobjectasanamespaceforasetofdependencies:

vardependencies={};

vardb=require('./lib/db');

varauthService=require('./lib/authService');

dependencies.db=db();

dependencies.authService=authService(dependencies);

Let'snowconvertthe'lib/db.js'modulestraightawaytodemonstratehowourserviceLocatorworks:

varlevel=require('level');

varsublevel=require('level-sublevel');

module.exports=function(serviceLocator){

vardbName=serviceLocator.get('dbName');

returnsublevel(

level(dbName,{valueEncoding:'json'})

);

}

Thedbmoduleusestheservicelocatorreceivedininputtoretrievethenameofthedatabasetoinstantiate.Thisisaninterestingpointtohighlight;aservicelocatorcanbeusednotonlytoreturncomponentinstancesbutalsotoprovideconfigurationparametersthatdefinethebehavioroftheentiredependencygraphthatwewanttocreate.

Thenextstepistoconvertthe'lib/authService.js'module:

[...]

module.exports=function(serviceLocator){

vardb=serviceLocator.get('db');

vartokenSecret=serviceLocator.get('tokenSecret');

Page 270: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varusers=db.sublevel('users');

varauthService={};

authService.login=function(username,password,callback){

//...sameasinthepreviousversion

}

authService.checkToken=function(token,callback){

//...sameasinthepreviousversion

}

returnauthService;

};

Also,theauthServicemoduleisafactorythattakestheservicelocatorastheinput.Thetwodependenciesofthemodule,thedbhandleandtokenSecret(whichisanotherconfigurationparameter)areretrievedusingtheget()methodoftheservicelocator.

Inasimilarway,wecanconvertthe'lib/authController.js'module:

module.exports=function(serviceLocator){

varauthService=serviceLocator.get('authService');

varauthController={};

authController.login=function(req,res,next){

//...sameasinthepreviousversion

};

authController.checkToken=function(req,res,next){

//...sameasinthepreviousversion

};

returnauthController;

}

Nowwearereadytoseehowtheservicelocatorisinstantiatedandconfigured.Thishappensofcourse,inthe'app.js'module:

[...]

varsvcLoc=require('./lib/serviceLocator')();//[1]

svcLoc.register('dbName','example-db');//[2]

svcLoc.register('tokenSecret','SHHH!');

svcLoc.factory('db',require('./lib/db'));

svcLoc.factory('authService',require('./lib/authService'));

svcLoc.factory('authController',

require('./lib/authController'));

varauthController=svcLoc.get('authController');//[3]

Page 271: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

app.post('/login',authController.login);

app.all('/checkToken',authController.checkToken);

[...]

Thisishowthewiringworksusingournewservicelocator:

1. Weinstantiateanewservicelocatorbyinvokingitsfactory.2. Weregistertheconfigurationparametersandmodulefactoriesagainstthe

servicelocator.Atthispoint,allourdependenciesarenotinstantiatedyet;wejustregisteredtheirfactories.

3. WeloadauthControllerfromtheservicelocator;thisistheentrypointthattriggerstheinstantiationoftheentiredependencygraphofourapplication.WhenweaskfortheinstanceoftheauthControllercomponent,theservicelocatorinvokestheassociatedfactorybyinjectinganinstanceofitself,thentheauthControllerfactorywilltrytoloadtheauthServicemodule,whichinturninstantiatesthedbmodule.

It'sinterestingtoseethelazynatureoftheservicelocator;eachinstanceiscreatedonlywhenneeded.Butthereisanotherimportantimplication;wecansee,infact,thateverydependencyisautomaticallywiredwithouttheneedtomanuallydoitinadvance.Theadvantageisthatwedon'thavetoknowinadvancewhattherightorderforinstantiatingandwiringthemodulesis,itallhappensautomaticallyandon-demand.Thisismuchmoreconvenientcomparedtothesimpledependencyinjectionpattern.

TipAnothercommonpatternistouseanexpressserverinstanceasasimpleservicelocator.ThiscanbeachievedusingexpressApp.set(name,instance)toregisteraserviceandexpressApp.get(name)tothenretrieveit.Theconvenientpartofthispatternisthattheserverinstance,whichactsasaservicelocator,isalreadyinjectedintoeachmiddlewareandisaccessiblethroughtherequest.appproperty.Youcanfindanexampleofthispatterninthesamplesdistributedwiththebook.

ProsandconsofaservicelocatorServicelocatoranddependencyinjectionhavealotincommon;bothshiftthedependencyownershiptoanentityexternaltothecomponent.Butthewaywewiretheservicelocatordeterminestheflexibilityofourentirearchitecture.Itisnotbychancethatwechoseaninjectedservicelocatortoimplementourexample,as

Page 272: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

opposedtoahardcodedorglobalservicelocator.Theselasttwovariationsalmostnullifytheadvantagesofthispattern.Infact,theresultwouldbethat,insteadofcouplingacomponentdirectlytoitsdependenciesusingrequire(),wewouldbecouplingittooneparticularinstanceoftheservicelocator.It'salsotruethatahardcodedservicelocatorwillstillgivemoreflexibilityinconfiguringwhatcomponenttoassociatewithaparticularname,butthisdoesstillnotgiveanybigadvantageintermsofreusability.

Also,likedependencyinjection,usingaservicelocatormakesithardertoidentifytherelationshipbetweenthecomponents,astheyareresolvedatruntime.Butinadditionitalsomakesitmoredifficulttoknowexactlywhatdependencyaparticularcomponentisgoingtorequire.Withdependencyinjectionthisisexpressedinamuchclearerway,bydeclaringthedependenciesinthefactoryorconstructorarguments.Withaservicelocator,thisismuchlessclearandwouldrequireacodeinspectionoranexplicitstatementinthedocumentationexplainingwhatdependenciesaparticularcomponentwilltrytoload.

Asafinalnote,itisimportanttoknowthatoftenaservicelocatorisincorrectlymistakenforadependencyinjectioncontainerbecauseitsharesthesameroleofserviceregistrywithit;however,thereisabigdifferencebetweenthetwo.Withaservicelocator,eachcomponentloadsitsdependenciesexplicitlyfromtheservicelocatoritself,whenusingaDIcontainerinstead,thecomponenthasnoknowledgeofthecontainer.

Thedifferencebetweenthesetwoapproachesisnoticeablefortworeasons:

Reusability:Acomponentrelyingonaservicelocatorislessreusablebecauseitrequiresthataservicelocatorisavailableinthesystem.Readability:Aswehavealreadysaid,aservicelocatorobfuscatesthedependencyrequirementsofacomponent.

Intermsofreusability,wecansaythattheservicelocatorpatternsitsinbetweenhardcodeddependenciesandDI.Intermsofconvenienceandsimplicity,itisdefinitelybetterthanmanualdependencyinjection,aswedon'thavetomanuallytakecareofbuildingtheentiredependencygraph.

Undertheseassumptions,aDIcontainerdefinitelyoffersthebestcompromiseintermsofreusabilityofthecomponentsandconvenience.Wearegoingtobetteranalyzethispatterninthenextsection.

DependencyinjectioncontainerThesteptotransformaservicelocatorintoaDependencyInjectioncontaineris

Page 273: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

notbig,butaswealreadymentioned,itmakesahugedifferenceintermsofdecoupling.Withthispattern,infact,eachmoduledoesn'thavetodependontheservicelocatoranymore,butitcansimplyexpressitsneedintermsofdependenciesandtheDIcontainerwilldotherestseamlessly.Aswewillsee,thebigleapforwardofthismechanismisthateverymodulecanbereusedevenwithoutthecontainer.

DeclaringasetofdependenciestoaDIcontainerADIcontainerisessentiallyaservicelocatorwiththeadditionofonefeature:itidentifiesthedependencyrequirementsofamodulebeforeinstantiatingit.Forthistobepossible,amodulehastodeclareitsdependenciesinsomeway,andaswewillsee,wehavemultipleoptionsfordoingthis.

Thefirstandprobablythemostpopulartechniqueconsistsofinjectingasetofdependenciesbasedonthearguments'namesusedinafactoryorconstructor.Let'stake,forexample,theauthServicemodule:

module.exports=function(db,tokenSecret){

//...

}

Aswedefinedit,theprecedingmodulewillbeinstantiatedbyourDIcontainerusingthedependencieswithnamesdbandtokenSecret,averysimpleandintuitivemechanism.However,tobeabletoreadthenamesoftheargumentsofafunction,it'snecessarytousealittletrick.InJavaScript,wehavethepossibilitytoserializeafunction,obtainingatruntimeitssourcecode;thisisaseasyasinvokingtoString()onthefunctionreference.Then,withsomeregularexpressions,obtainingtheargumentslistiscertainlynotblackmagic.

TipThistechniqueofinjectingasetofdependenciesusingthenamesoftheargumentsofafunctionwaspopularizedbyAngularJS(http://angularjs.org),aclient-sideJavaScriptframeworkdevelopedbyGoogleandentirelybuiltontopofaDIcontainer.

Thebiggestproblemofthisapproachisthatitdoesn'tplaywellwithminification,apracticeusedextensivelyinclient-sideJavaScript,whichconsistsofapplyingparticularcodetransformationstoreducetotheminimumthesizeofthesourcecode.Manyminificatorsapplyatechniqueknownasnamemangling,whichessentiallyrenamesanylocalvariabletoreduceitslength,usuallytoasinglecharacter.Thebadnewsisthatfunctionargumentsarelocalvariablesandareusuallyaffectedbythis

Page 274: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

process,causingthemechanismthatwedescribedfordeclaringdependenciestofallapart.Eventhoughminificationisnotreallynecessaryinserver-sidecode,it'simportanttoconsiderthatoftenNode.jsmodulesaresharedwiththebrowser,andthisisanimportantfactortoconsiderinouranalysis.

Luckily,aDIcontainermightuseothertechniquestoknowwhichdependenciestoinject.Thesetechniquesaregivenasfollows:

Wecanuseaspecialpropertyattachedtothefactoryfunction,forexample,anarrayexplicitlylistingallthedependenciestoinject:

module.exports=function(a,b){};

module.exports._inject=['db','another/dependency'];

Wecanspecifyamoduleasanarrayofdependencynamesfollowedbythefactoryfunction:

module.exports=['db','another/depencency',

function(a,b){}];

Wecanuseacommentannotationthatisappendedtoeachargumentofafunction(however,thisalsodoesn'tplaywellwithminification):

module.exports=function(a/*db*/,

b/*another/depencency*/){};

Allthesetechniquesarequiteopinionated,soforourexample,wearegoingtousethemostsimpleandpopular,whichistoobtainthedependencynamesusingtheargumentsofafunction.

RefactoringtheauthenticationservertouseaDIcontainerTodemonstratehowaDIcontainerismuchlessinvasivethanaservicelocator,wearenowgoingtorefactoragainourauthenticationserver,andtodosowearegoingtouseasstartingpointtheversioninwhichwewereusingtheplaindependencyinjectionpattern.Infact,whatwearegoingtodoisjustleaveuntouchedallthecomponentsoftheapplicationexceptfortheapp.jsmodule,whichisgoingtobethemoduleresponsibleforinitializingthecontainer.

Butfirst,weneedtoimplementourDIcontainer.Let'sdothatbycreatinganewmodulecalleddiContainer.jsunderthelib/directory.Thisisitsinitialpart:

varargsList=require('args-list');

module.exports=function(){

Page 275: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

vardependencies={};

varfactories={};

vardiContainer={};

diContainer.factory=function(name,factory){

factories[name]=factory;

};

diContainer.register=function(name,dep){

dependencies[name]=dep;

};

diContainer.get=function(name){

if(!dependencies[name]){

varfactory=factories[name];

dependencies[name]=factory&&

diContainer.inject(factory);

if(!dependencies[name]){

thrownewError('Cannotfindmodule:'+name);

}

}

returndependencies[name];

};

//...tobecontinued

ThefirstpartofthediContainermoduleisfunctionallyidenticaltotheservicelocatorwehaveseenpreviously.Theonlynotabledifferencesare:

Werequireanewnpmmodulecalledargs-list(https://npmjs.org/package/args-list),whichwewillusetoextractthenamesoftheargumentsofafunction.Thistime,insteadofdirectlyinvokingthemodulefactory,werelyonanothermethodofthediContainermodulecalledinject(),whichwillresolvethedependenciesofamodule,andusethemtoinvokethefactory.

Let'sseehowthediContainer.inject()methodlookslike:

diContainer.inject=function(factory){

varargs=argsList(factory)

.map(function(dependency){

returndiContainer.get(dependency);

});

returnfactory.apply(null,args);

};

};//endofmodule.exports=function(){

TheprecedingmethodiswhatmakestheDIcontainerdifferentfromaservicelocator.Itslogicisverystraightforward:

1. Weextracttheargumentslistfromthefactoryfunctionwereceiveastheinput,

Page 276: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

usingtheargs-listlibrary.2. Wethenmapeachargumentnametothecorrespondentdependencyinstance

retrievedusingtheget()method.3. Attheend,allwehavetodoisjustinvokethefactorybyprovidingthe

dependencylistthatwejustgenerated.

That'sreallyitforourdiContainer.Aswesawit'snotthatmuchdifferentfromaservicelocator,butthesimplestepofinstantiatingamodulebyinjectingitsdependenciesmakesadramaticdifference(ascomparedtoinjectingtheentireservicelocator).

Tocompletetherefactoringoftheauthenticationserver,wealsoneedtotweakthe'app.js'module:

[...]

vardiContainer=require('./lib/diContainer')();

diContainer.register('dbName','example-db');

diContainer.register('tokenSecret','SHHH!');

diContainer.factory('db',require('./lib/db'));

diContainer.factory('authService',

require('./lib/authService'));

diContainer.factory('authController',

require('./lib/authController'));

varauthController=diContainer.get('authController');

app.post('/login',authController.login);

app.get('/checkToken',authController.checkToken);

[...]

Aswecansee,thecodeoftheappmoduleisidenticaltotheonethatweusedtoinitializetheservicelocatorintheprevioussection.WecanalsonoticethattobootstraptheDIcontainer,andthereforetriggertheloadingoftheentiredependencygraph,westillhavetouseitasaservicelocatorbyinvokingdiContainer.get('authController').Fromthatpointon,everymoduleregisteredwiththeDIcontainerwillbeinstantiatedandwiredautomatically.

ProsandconsofaDependencyInjectioncontainerADIcontainerassumesthatourmodulesusethedependencyinjectionpatternandthereforeitinheritsmostofitsprosandcons.Inparticular,wehaveanimproveddecouplingandtestabilitybutontheothersidemorecomplexitybecauseourdependenciesareresolvedatruntime.ADIcontaineralsosharesmanypropertieswiththeservicelocatorpattern,butithasonitssidethefactthatitdoesn'tforcethemodulestodependonanyextraserviceexceptitsactualdependencies.Thisisa

Page 277: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

hugeadvantagebecauseitallowseachmoduletobeusedevenwithouttheDIcontainer,usingasimplemanualinjection.

That'sessentiallywhatwedemonstratedinthissection:wetooktheversionoftheauthenticationserverwhereweusedtheplaindependencyinjectionpatternandthen,withoutmodifyinganyofitscomponents(exceptfortheappmodule),wewereabletoautomatizetheinjectionofeverydependency.

TipOnnpm,youcanfindalotofDIcontainerstoreuseortakeinspirationfromathttps://www.npmjs.org/search?q=dependency%20injection.

Page 278: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

WiringpluginsThedreamarchitectureofasoftwareengineeristheonehavingasmall,minimalcore,extensibleasneededthroughtheuseofplugins.Unfortunately,thisisnotalwayseasytoobtain,sincemostofthetimeithasacostintermsoftime,resources,andcomplexity.Nonetheless,it'salwaysdesirabletosupportsomekindofexternalextensibility,eveniflimitedtojustsomepartsofthesystem.Inthissection,wearegoingtoplungeintothisfascinatingworldandfocusonadualisticproblem:

ExposingtheservicesofanapplicationtoapluginIntegratingapluginintotheflowoftheparentapplication

PluginsaspackagesOfteninNode.js,thepluginsofanapplicationareinstalledaspackagesintothenode_modulesdirectoryofaproject.Therearetwoadvantagesfordoingthis.Firstly,wecanleveragethepowerofnpmtodistributethepluginandmanageitsdependencies.Andsecondly,apackagecanhaveitsownprivatedependencygraph,whichreducesthechancesofhavingconflictsandincompatibilitiesbetweendependencies,asopposedtolettingthepluginusethedependenciesoftheparentproject.

Thefollowingdirectorystructuregivesanexampleofanapplicationwithtwopluginsdistributedaspackages:

application

'--node_modules

|--pluginA

'--pluginB

IntheNode.jsworldthisisaverycommonpractice,somepopularexamplesareexpress(http://expressjs.com)withitsmiddleware,gulp(http://gulpjs.com),grunt(http://gruntjs.com),nodebb(http://nodebb.org),anddocpad(http://docpad.org).

However,thebenefitsofusingpackagesarenotonlylimitedtoexternalplugins.Infact,onepopularpatternistobuildentireapplicationsbywrappingtheircomponentsintopackages,asiftheywereinternalplugins.So,insteadoforganizingthemodulesinthemainpackageoftheapplication,wecancreateaseparatepackageforeachbigchunkoffunctionalityandinstallitintothenode_modulesdirectory.

Tip

Page 279: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Apackagecanbeprivateandnotnecessarilyavailableonthepublicnpmregistry.Wecanalwayssetthe'private'flagintothe'package.json'topreventaccidentalpublicationtonpm.Then,wecancommitthepackagesintoaversioncontrolsystemsuchasgitorleverageaprivatenpmservertosharethemwiththerestoftheteam.

Whyfollowthispattern?Firstofallconvenience:peopleoftenfinditimpracticalortooverbosetoreferencethelocalmodulesofapackageusingtherelativepathnotation.Let's,forexample,considerthefollowingdirectorystructure:

application

|--componentA

|'--subdir

|'--moduleA

'--componentB

'--moduleB

IfwewanttoreferencemoduleBfrommoduleA,wehavetowritesomethinglikethis:

require('../../componentB/moduleB');

Instead,wecanleveragethepropertiesoftheresolvingalgorithmofrequire()(aswehavestudieditinChapter1,Node.jsDesignFundamentals)andputtheentirecomponentBdirectoryintoapackage.Byinstallingitintothenode_modulesdirectory,wecanthenwritesomethingsuchasthefollowing(fromanywhereinthemainpackageoftheapplication):

require('componentB/module');

Thesecondreasonforsplittingaprojectintopackagesisofcoursereusability.Apackagecanhaveitsownprivatedependenciesanditforcesthedevelopertothinkintermsofwhattoexposetothemainapplicationandwhatinsteadtokeepprivate,withbeneficialeffectsonthedecouplingandinformationhidingoftheentireapplication.

TipPattern:usepackagesasameanstoorganizeyourapplication,notjustfordistributingcodeincombinationwithnpm.

Theusecaseswehavejustdescribedmakeuseofapackagenotjustasa

Page 280: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

stateless,reusablelibrary(likemostofthepackagesonnpm),butmoreasanintegralpartofaparticularapplication,providingservices,extendingitsfunctionalityormodifyingitsbehavior.Themaindifferenceisthatthesetypesofpackagesareintegratedinsideanapplicationratherthanjustused.

NoteForsimplicity,wewillusethetermplugintodescribeanypackagemeanttointegratewithaparticularapplication.

Aswewillsee,thecommonproblemthatwearegoingtofacewhendecidingtosupportthistypeofarchitectureisexposingpartsofthemainapplicationtoplugins.Infact,wecannotthinkofonlystatelessplugins—thisisofcourse,theaimforaperfectextensibility—butsometimesthepluginhastousesomeoftheservicesoftheparentapplicationinordertocarryoutitstasks.Thisaspectmightdependalotonthetechniqueusedtowiremodulesintheparentapplication.

ExtensionpointsThereareliterallyinfinitewaystomakeanapplicationextensible.Forexample,someofthedesignpatternswestudiedinChapter4,DesignPatternsaremeantexactlyforthis:usingProxyorDecoratorweareabletochangeoraugmentthefunctionalityofaservice;withStrategywecanswappartsofanalgorithm;withMiddlewarewecaninsertprocessingunitsinanexistingpipeline.Also,Streamscanprovidegreatextensibilitythankstotheircomposablenature.

Ontheotherhand,EventEmittersallowustodecoupleourcomponentsusingeventsandthepublish/subscribepattern.Anotherimportanttechniqueistoexplicitlydefineintheapplicationsomepointswherenewfunctionalitiescanbeattachedortheexistingonesmodified;thesepointsinanapplicationarecommonlyknownashooks.Tosummarize,themostimportantingredienttosupportpluginsisasetofextensionpoints.

Butalsothewaywewireourcomponentsplaysadecisiverolebecauseitcanaffectthewayweexposetheservicesoftheapplicationtotheplugin.Inthissection,wearemainlygoingtofocusonthisaspect.

Plugin-controlledvsApplication-controlledextension

Page 281: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Beforewegoaheadandpresentsomeexamples,itisimportanttounderstandthebackgroundofthetechniquewearegoingtouse.Therearemainlytwoapproachesforextendingthecomponentsofanapplication:

ExplicitextensionExtensionthroughInversionofControl(IoC)

Inthefirstcase,wehaveamorespecificcomponent(theoneprovidingthenewfunctionality)explicitlyextendingtheinfrastructure,whileinthesecondcase,itistheinfrastructuretocontroltheextensionbyloading,installing,orexecutingthenewspecificcomponent.Inthissecondscenario,theflowofcontrolisinverted,asshowninthefollowingimage:

InversionofControlisaverybroadprinciplethatcanbeappliednotonlytotheproblemofapplicationextensibility.Infact,inmoregeneraltermsitcanbesaidthatbyimplementingsomeformofIoC,insteadofthecustomcodecontrollingtheinfrastructure,theinfrastructurecontrolsthecustomcode.WithIoC,thevariouscomponentsofanapplicationtradeofftheirpowerofcontrollingtheflowinexchangeforanimprovedlevelofdecoupling.ThisisalsoknownastheHollywoodprincipleor"don'tcallus,we'llcallyou".

Forexample,adependencyinjectioncontainerisademonstrationoftheInversionofControlprincipleappliedtothespecificcaseofdependencymanagement.TheobserverpatternisanotherexampleofIoCappliedtostatemanagement.Template,Strategy,State,andMiddlewarearealsomorelocalizedmanifestationsofthesameprinciple.ThebrowserimplementstheIoCprinciplewhendispatchingUIeventstotheJavaScriptcode(it'snottheJavaScriptcodeactivelypollingthebrowserforevents).Andguesswhat,Node.jsitselffollowstheIoCprinciplewhencontrollingtheexecutionofthevariouscallbacksforus.

Page 282: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

NoteToknowmoreabouttheInversionofControlprinciple,weadviseyoutostudythetopicdirectlyfromthewordsofitsmaster,MartinFowlerathttp://martinfowler.com/bliki/InversionOfControl.html.

Applyingthisconcepttothespecificcaseofpluginswecanthenidentifytwoformsofextension:

Plugin-controlledextensionApplication-controlledextension(IoC)

Inthefirstcase,itisthepluginthattapsintothecomponentsoftheapplicationtoextendthemasneeded,whileinthesecondcase,thecontrolisinthehandsoftheapplication,whichintegratesthepluginintooneofitsextensionpoints.

Tomakeaquickexample,let'sconsiderapluginthatextendstheexpressapplicationwithanewroute.Byusingaplugin-controlledextensionthiswouldlooklikethefollowing:

//intheapplication:

varapp=express();

require('thePlugin')(app);

//intheplugin:

module.exports=functionplugin(app){

app.get('/newRoute',function(req,res){...})

};

Ifinstead,wewanttouseanapplication-controlledextension(IoC),thesameprecedingexamplewouldlooklikethefollowing:

//intheapplication:

varapp=express();

varplugin=require('thePlugin')();

app[plugin.method](plugin.route,plugin.handler);

//intheplugin:

module.exports=functionplugin(){

return{

method:'get',

route:'/newRoute',

handler:function(req,res){...}

}

}

Page 283: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Inthelastcodefragment,wesawhowthepluginisonlyapassiveplayerintheextensionprocess;thecontrolisinthehandsoftheapplication,whichimplementstheframeworktoreceivetheplugin.

Basedontheprecedingexample,wecanimmediatelyidentifyafewimportantdifferencesbetweenthetwoapproaches:

Plugin-controlledextensionismorepowerfulandflexible,asoftenwehaveaccesstotheinternalsoftheapplicationandwecanmovefreelyasifthepluginwasactuallyapartoftheapplicationitself.However,thissometimescanbemorealiabilitythananadvantage.Infact,anychangeintheapplicationwouldmoreeasilyhaverepercussionsontheplugins,requiringconstantupdatesasthemainapplicationevolves.Application-controlledextensionrequiresaplugininfrastructureinthemainapplication.Withaplugin-controlledextension,theonlyrequirementisthatthecomponentsoftheapplicationareextensibleinsomeway.Withaplugin-controlledextension,itbecomesessentialtosharetheinternalservicesoftheapplicationwiththeplugin(intheprecedingsmallexample,theservicetosharewastheappinstance),otherwisewewouldnotbeabletoextendthem.Withanapplication-controlledextension,itmightstillbenecessarytoaccesssomeoftheservicesoftheapplication,nottoextendbutrathertousethem.Forexample,wemightwanttoquerythedbinstanceinourplugin,orleveragetheloggerofthemainapplication,justtonameafewscenarios.

Thislastpointshouldletusthinkabouttheimportanceofexposingtheservicesofanapplicationtothepluginandthat'swhatwearemainlyinterestedinexploring.Thebestwayofdoingthisistoshowapracticalexampleofaplugin-controlledextension,whichrequiresasmalleffortintermsofinfrastructureandwecanemphasizemoreontheproblemofsharingtheapplication'sstatewiththeplugins.

ImplementingalogoutpluginLet'snowstarttoworkonasmallpluginforourauthenticationserver.Withthewayweoriginallycreatedtheapplication,itisnotpossibletoexplicitlyinvalidateatoken,itsimplybecomesinvalidwhenitexpires.Now,wewanttoaddsupportforthisfeature,namelylogout,andwewanttodothatbynotmodifyingthecodeofthemainapplicationbutratherdelegatingthetasktoanexternalplugin.

Tosupportthisnewfeature,weneedtosaveeachtokeninthedatabaseafteritiscreatedandthencheckforitsexistenceeverytimewewanttovalidateit.Toinvalidateatoken,wesimplyneedtoremoveitfromthedatabase.

Todothis,wearegoingtouseaplugin-controlledextensiontoproxythecallsto

Page 284: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

authService.login()andauthService.checkToken().WethenneedtodecoratetheauthServicewithanewmethodcalledlogout().Afterdoingthis,wealsowanttoregisteranewrouteagainstthemainexpressservertoexposeanewendpoint('/logout'),whichwecanusetoinvalidateatokenusinganHTTPrequest.

Wearegoingtoimplementthepluginwejustdescribedinfourdifferentvariations:

UsinghardcodeddependenciesUsingdependencyinjectionUsingaservicelocatorUsingadependencyinjectioncontainer

UsinghardcodeddependenciesThefirstvariationofthepluginwearenowgoingtoimplementcoversthecaseinwhichourapplicationmainlyuseshardcodeddependenciesforwiringitsstatefulmodules.Inthiscontext,ifourpluginlivesinapackageunderthenode_modulesdirectory,tousetheservicesofthemainapplicationwehavetogainaccesstotheparentpackage.Wecandothisintwoways:

Usingrequire()andnavigatingtotheapplication'srootusingrelativeorabsolutepaths.Usingrequire()byimpersonatingamoduleintheparentapplication—usuallythemoduleinstantiatingtheplugin.Thiswillallowustoeasilygainaccesstoalltheservicesoftheapplicationbyusingrequire(),asifitwasinvokedbytheparentapplicationandnotfromtheplugin.

Thefirsttechniqueislessrobustasitassumesthatthepackageisawareofthepositionofthemainapplication.Themoduleimpersonationpatterninstead,canbeusedregardlessofwherethepackageisrequiredfrom,andforthisreasonthisisthetechniquethatwearegoingtousetoimplementthenextdemo.

Tobuildourplugin,wefirstneedtocreateanewpackageunderthenode_modulesdirectory,namedauthsrv-plugin-logout.Beforewestartcoding,weneedtocreateaminimalpackage.jsontodescribethepackage,fillinginonlytheessentialparameters(thecompletepathtothefileis:node_modules/authsrv-plugin-logout/package.json):

{

"name":"authsrv-plugin-logout",

"version":"0.0.0"

}

Now,wearereadytocreatethemainmoduleofourplugin,wewillusethefile

Page 285: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

'index.js'asitisthedefaultmodulethatNode.jswilltrytoloadwhenrequiringthepackage(ifno'main'propertyisdefinedinthe'package.json').Asalways,theinitiallinesofthemodulearededicatedtoloadingthedependencies;payattentiononhowwearegoingtorequirethem(file'node_modules/authsrv-plugin-logout/index.js'):

varparentRequire=module.parent.require;

varauthService=parentRequire('./lib/authService');

vardb=parentRequire('./lib/db');

varapp=parentRequire('./app');

vartokensDb=db.sublevel('tokens');

Thefirstlineofcodeiswhatmakesthedifference.Weobtainareferencetotherequire()functionoftheparentmodule,whichistheonethatloadstheplugin.Inourcasetheparentisgoingtobetheappmoduleinthemainapplication,andthismeansthateverytimeweuseparentRequire()weareloadingamoduleasifweweredoingitfrom'app.js'.

ThenextstepiscreatingaproxyfortheauthService.login()method.AfterstudyingthispatterninChapter4,DesignPatterns,weshouldalreadyknowhowitworks:

varoldLogin=authService.login;//[1]

authService.login=function(username,password,callback){

oldLogin(username,password,function(err,token){//[2]

if(err)returncallback(err);//[3]

tokensDb.put(token,{username:username},function(){

callback(null,token);

});

});

}

Intheprecedingcode,thestepsperformedareexplainedasfollows:

1. Wefirstsaveareferencetotheoldlogin()methodandthenweoverrideitwithourproxiedversion.

2. Intheproxyfunction,weinvoketheoriginallogin()methodbyprovidingacustomcallbacksothatwecanintercepttheoriginalreturnvalue.

3. Iftheoriginallogin()returnsanerror,wesimplyforwardittothecallback,otherwisewesavethetokenintothedatabase.

Similarly,weneedtointerceptthecallstocheckToken()sothatwecanaddourcustomlogic:

Page 286: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varoldCheckToken=authService.checkToken;

authService.checkToken=function(token,callback){

tokensDb.get(token,function(err,res){

if(err)returncallback(err);

oldCheckToken(token,callback);

});

}

Thistime,wefirstwanttocheckwhetherthetokenexistsinthedatabasebeforegivingthecontroltotheoriginalcheckToken()method.Ifthetokenisnotfound,theget()operationreturnsanerror;thismeansthatourtokenwasinvalidatedandsoweimmediatelyreturntheerrorbacktothecallback.

TofinalizetheextensionoftheauthService,wenowneedtodecorateitwithanewmethod,whichwewillusetoinvalidateatoken:

authService.logout=function(token,callback){

tokensDb.del(token,callback);

}

Thelogout()methodisverysimple:wejustdeletethetokenfromthedatabase.

Finally,wecanattachanewroutetotheexpressservertoexposethenewfunctionalitythroughawebservice:

app.get('/logout',function(req,res,next){

authService.logout(req.query.token,function(){

res.status(200).send({ok:true});

});

});

Now,ourpluginisreadytobeattachedtothemainapplication,sotodothiswejustneedtogobacktothemaindirectoryoftheapplicationandeditthe'app.js'module:

[...]

varapp=module.exports=express();

app.use(bodyParser.json());

require('authsrv-plugin-logout');

app.post('/login',authController.login);

app.all('/checkToken',authController.checkToken);

[...]

Page 287: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Aswecansee,toattachthepluginweonlyneedtorequireit.Assoonasthishappens—duringthestartupoftheapplication—theflowofcontrolisgiventotheplugin,whichinturnwillextendtheauthServiceandtheappmodules,aswesawearlier.

Nowourauthenticationserveralsosupportstheinvalidationofthetoken.Wedidthatinareusableway,thecoreoftheapplicationremainedalmostuntouched,andwewereabletoeasilyapplytheProxyandDecoratorpatternstoextenditsfunctionalities.

Wecannowtrytostarttheapplicationagain:

nodeapp

Then,wecanverifythatthenew/logoutwebserviceactuallyexistsandworksasexpected.Usingcurlwecannowtrytoobtainanewtokenusing/login:

curl-XPOST-d'{"username":"alice","password":"secret"}'

http://localhost:3000/login-H"Content-Type:

application/json"

Then,wecancheckwhetherthetokenisvalidusing/checkToken:

curl-XGET-H"Accept:application/json"

http://localhost:3000/checkToken?token=<TOKENHERE>

Then,wecanpassthetokentothe/logoutendpointtoinvalidateit;withcurlthiscanbedonewithacommandsuchasthis:

curl-XGET-H"Accept:application/json"

http://localhost:3000/logout?token=<TOKENHERE>

Now,ifwetrytocheckagainthevalidityofthetokenweshouldgetanegativeresponse,confirmingthatourpluginisworkingperfectly.

Evenwithasmallpluginliketheonewejustimplementedtheadvantagesofsupportingplugin-basedextensibilityareclear.Wealsolearnedhowtogainaccesstotheservicesofthemainapplicationfromanotherpackageusingthemoduleimpersonation.

NoteThemoduleimpersonationpatternisusedbyquiteafewNodeBBplugins;you

Page 288: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

mightwanttocheckacoupleoftheminordertohaveanideaofhowthisisusedinarealapplication.Thesearethelinkstosomenotableexamples:

nodebb-plugin-poll:https://github.com/Schamper/nodebb-plugin-poll/blob/b4a46561aff279e19c23b7c635fda5037c534b84/lib/nodebb.jsnodebb-plugin-mentions:https://github.com/julianlam/nodebb-plugin-mentions/blob/9638118fa7e06a05ceb24eb521427440abd0dd8a/library.js#L4-13

Moduleimpersonationis,ofcourse,aformofhardcodeddependencyandshareswithitstrengthsandweaknesses.Fromoneside,itallowsustoaccessanyserviceofthemainapplicationwithlittleeffortandminimalinfrastructuralrequirements,butfromtheother,itcreatesatightcoupling,notonlywithaparticularinstanceofaservicebutalsowithitslocation,whichmoreeasilyexposestheplugintochangesandrefactoringsinthemainapplication.

ExposingservicesusingaservicelocatorSimilartomoduleimpersonation,theservicelocatorisalsoagoodchoiceifwewanttoexposeallthecomponentsofanapplicationtoitsplugins,butontopofthat,ithasamajoradvantage,becauseaplugincanusetheservicelocatortoexposeitsownservicestotheapplicationoreventootherplugins.

Let'snowrefactorourlogoutpluginagaintouseaservicelocator.We'llrefactorthemainmoduleoftheplugininthenode_modules/authsrv-plugin-logout/index.jsfile:

module.exports=function(serviceLocator){

varauthService=serviceLocator.get('authService');

vardb=serviceLocator.get('db');

varapp=serviceLocator.get('app');

vartokensDb=db.sublevel('tokens');

varoldLogin=authService.login;

authService.login=function(username,password,callback){

//...sameasinthepreviousversion

}

varoldCheckToken=authService.checkToken;

authService.checkToken=function(token,callback){

//...sameasinthepreviousversion

}

authService.logout=function(token,callback){

//...sameasinthepreviousversion

}

app.get('/logout',function(req,res,next){

Page 289: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

//...sameasinthepreviousversion

});

};

Nowthatourpluginreceivestheservicelocatoroftheparentapplicationastheinput,itcanaccessanyofitsservicesasneeded.Thismeansthattheapplicationdoesnothavetoknowinadvancewhatthepluginisgoingtoneedintermsofdependencies;thisissurelyamajoradvantagewhenimplementingaplugin-controlledextension.

Thenextstepistoexecutethepluginfromthemainapplication,andtodothat,wehavetomodifytheapp.jsmodule.Wewillusetheversionoftheauthenticationserveralreadybasedontheservicelocatorpattern.Therequiredchangesaregiveninthefollowingblockofcode:

[...]

varsvcLoc=require('./lib/serviceLocator')();

svcLoc.register(...);

[...]

svcLoc.register('app',app);

varplugin=require('authsrv-plugin-logout');

plugin(svcLoc);

[...]

Thechangesarehighlightedintheprecedingcode;thosechangesenabledusto:

Registertheappmoduleitselfintheservicelocator,asthepluginmightwanttohaveaccesstoitRequirethepluginInvoketheplugin'smainfunctionbyprovidingtheservicelocatorasanargument

Aswealreadysaid,themainstrengthoftheservicelocatoristhatitprovidesasimplewaytoexposealltheservicesofanapplicationtoitsplugins,butitcanalsobeusedasamechanismforsharingservicesfromthepluginbackintotheparentapplicationorevenotherplugins.Thislastconsiderationisprobablythemainstrengthoftheservicelocatorpatterninthecontextofplugin-basedextensibility.

ExposingservicesusingdependencyinjectionUsingdependencyinjectiontopropagateservicestoapluginisaseasyasusingitintheapplicationitself.Thispatternbecomesalmostarequirementifit'salreadythemainmethodforwiringdependenciesintheparentapplication,butnothingpreventsusfromusingitwhentheprevalentformofdependencymanagementishardcodeddependenciesoraservicelocator.DIisalsoanidealchoicewhenwewantto

Page 290: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

supportanapplication-controlledextension,becauseitprovidesbettercontroloverwhatissharedwiththeplugin.

Totesttheseassumptions,let'simmediatelytrytorefactorthelogoutplugintousedependencyinjection.Thechangesrequiredareminimal,solet'sstartfromthemainmoduleoftheplugin('node_modules/authsrv-plugin-logout/index.js'):

module.exports=function(app,authService,db){

vartokensDb=db.sublevel('tokens');

varoldLogin=authService.login;

authService.login=function(username,password,callback){

//...sameasinthepreviousversion

}

varoldCheckToken=authService.checkToken;

authService.checkToken=function(token,callback){

//...sameasinthepreviousversion

}

authService.logout=function(token,callback){

//...sameasinthepreviousversion

}

app.get('/logout',function(req,res,next){

//...sameasinthepreviousversion

});

};

Allwedidiswraptheplugin'scodeintoafactorythatreceivestheservicesoftheparentapplicationastheinput;therestofitremainsunchanged.

Tocompleteourrefactoring,wealsoneedtochangethewayweattachthepluginfromtheparentapplication;let'sthenchangethatonelinewherewerequirethepluginintheapp.jsmodule:

[...]

varplugin=require('authsrv-plugin-logout');

plugin(app,authService,authController,db);

[...]

Weintentionallydidn'tshowhowthesedependencieswereobtained.Infact,itdoesn'treallymakeanydifference,anymethodwillequallywork;wemightusehardcodeddependenciesorobtaintheinstancesfromfactoriesorfromaservicelocator,itdoesn'treallymatter.Thisprovesthatdependencyinjectionisaflexiblepatternwhenwiringpluginsthatcanbeusedregardlessofthewaywewiretheservicesintheparentapplication.

Page 291: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Butthedifferencesaremuchmoreprofound.Dependencyinjectionisdefinitelythecleanestwayofprovidingasetofservicestoaplugin,butmostimportantly,itoffersthebestlevelofcontroloverwhat'sexposedtoit,resultinginbetterinformationhidingandbetterprotectionagainsttooaggressiveextensions.However,thiscanbealsoconsideredadrawback,becausethemainapplicationcan'talwaysknowwhatservicesthepluginisgoingtoneed,soweendupeitherinjectingeveryservice,whichisimpractical,oronlyasubsetofthem,forexample,onlytheessentialcoreservicesoftheparentapplication.Forthisreason,dependencyinjectionisnottheidealchoiceifwemainlywanttosupportplugin-controlledextensibility;however,theuseofaDIcontainercaneasilysolvetheseissues.

NoteGrunt(http://gruntjs.com),ataskrunnerforNode.js,usesdependencyinjectiontoprovideeachpluginwithaninstanceofthecoregruntservice.Eachplugincanthenextenditbyattachingnewtasks,usingittoretrievetheconfigurationparameters,orrunningothertasks.Agruntpluginlookslikethefollowing:

module.exports=function(grunt){

grunt.registerMultiTask('taskName','description',

function(...){...}

);

};

ExposingservicesusingadependencyinjectioncontainerTakingthepreviousexampleasastartingpoint,wecanuseaDIcontainerincombinationwithourpluginbyapplyingasmallchangetotheappmodule,asshowninthefollowingcode:

[...]

vardiContainer=require('./lib/diContainer')();

diContainer.register(...);

[...]

//initializetheplugin

diContainer.inject(require('authsrv-plugin-logout'));

[...]

Afterregisteringthefactoriesortheinstancesofourapplication,allwehavetodoisinstantiatetheplugin,whichisdonebyinjectingitsdependenciesusingtheDIcontainer.Thisway,eachplugincanrequireitsownsetofdependencieswithoutthe

Page 292: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

parentapplicationneedingtoknow.AllthewiringisagaincarriedoutautomaticallybytheDIcontainer.

UsingaDIcontaineralsomeansthateachplugincanpotentiallyaccessanyserviceoftheapplication,reducingtheinformationhidingandthecontroloverwhatcanbeusedorextended.ApossiblesolutiontothisproblemistocreateaseparateDIcontainerregisteringonlytheservicesthatwewanttoexposetoplugins;thiswaywecancontrolwhateachplugincanseeofthemainapplication.ThisdemonstratesthataDIcontainercanalsobeaverygoodchoiceintermsofencapsulationandinformationhiding.

Thisconcludesourlastrefactoringofthelogoutpluginandtheauthenticationserver.

Page 293: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryThetopicofdependencywiringiscertainlyoneofthemostopinionatedinsoftwareengineering,butinthischapter,wetriedtokeeptheanalysisasfactualaspossibletogiveanobjectiveoverviewofthemostimportantwiringpatterns.WeclearedsomeofthemostcommondoubtsaroundSingletonsandinstancesinNode.js,andwelearnedhowtoconnectmodulesusinghardcodeddependencies,dependencyinjection,andservicelocators.Wepracticedeachtechniqueusingtheauthenticationserverasaplayground,allowingustoidentifytheprosandconsofeachapproach.

Inthesecondpartofthechapter,welearnedhowanapplicationcansupportplugins,butmostimportantly,howwecanwirethosepluginsintothemainapplication.Weappliedthesametechniquespresentedinthefirstpartofthechapter,butanalyzedthemfromanotherperspective.Wediscoveredhowimportantitcanbeforaplugintohaveaccesstotherightservicesofthemainapplication,andhowmuchthiscanimpactitscapabilities.

Bytheendofthischapter,weshouldfeelcomfortableinchoosingthebestapproachforthelevelofdecoupling,reusability,andsimplicitywewanttoobtaininourapplication.Wecanalsoconsiderusingmorethanonepatterninthesameapplication.Forexample,wecanusehardcodeddependenciesasthemaintechniqueandthenuseaservicelocatorwhenitcomestolinkingplugins;therearereallynolimitstowhatwecando,nowthatweknowthebestusecaseforeachapproach.

Sofarinthisbook,wehavefocusedouranalysisonhighlygenericandcustomizablepatterns,butfromthenextchapteronward,wewillshiftourattentiontosolvingmorespecifictechnicalproblems.Whatcomesnextis,infact,acollectionofrecipes,whichcanbeusedtosolvespecificissuesrelatedtoCPU-boundtasks,asynchronouscaching,andsharingcodewiththebrowser.

Page 294: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter6.RecipesAlmostallthedesignpatternswe'veseensofarcanbeconsideredgenericandapplicabletomanydifferentareasofanapplication.Thereis,however,asetofpatternsthataremorespecificandfocusedonsolvingwell-definedproblems;wecancallthesepatternsrecipes.Asinreal-lifecooking,wehaveasetofwell-definedstepstofollowthatwillleadustoanexpectedoutcome.Ofcourse,thisdoesn'tmeanthatwecan'tusesomecreativitytocustomizetherecipestomatchthetasteofourguests,buttheoutlineoftheprocedureisusuallytheonethatmatters.Inthischapter,wearegoingtoprovidesomepopularrecipestosolvesomespecificproblemsweencounterinoureverydayNode.jsdevelopment.Theserecipesinclude:

RequiringmodulesthatareinitializedasynchronouslyBatchingandcachingasynchronousoperationstogetaperformanceboostinbusyapplications,usingonlyaminimaldevelopmenteffortRunningsynchronousCPU-boundoperationsthatcanblocktheeventloopandcrippletheabilityofNode.jstohandleconcurrentrequestsSharingcodewiththebrowser,theHolyGrailofNode.jsdevelopment

RequiringasynchronouslyinitializedmodulesInChapter1,Node.jsDesignFundamentals,whenwediscussedthefundamentalpropertiesoftheNode.jsmodulesystem,wementionedthefactthatrequire()workssynchronouslyandthatmodule.exportscannotbesetasynchronously.

ThisisoneofthemainreasonsfortheexistenceofsynchronousAPIinthecoremodulesandmanynpmpackages,theyareprovidedmoreasaconvenientalternative,tobeusedprimarilyforinitializationtasksratherthanasubstituteforasynchronousAPI.

Unfortunately,thisisnotalwayspossible;asynchronousAPImightnotalwaysbeavailable,especiallyforcomponentsusingthenetworkduringtheirinitializationphase,forexample,toperformhandshakeprotocolsortoretrieveconfigurationparameters.Thisisthecaseformanydatabasedriversandclientsformiddlewaresystemssuchasmessagequeues.

Canonicalsolutions

Page 295: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Let'stakeanexample:amodulecalleddb,whichconnectstoaremotedatabase.Thedbmodulewillbeabletoacceptrequestsonlyaftertheconnectionandthehandshakewiththeserverhavebeencompleted.Inthisscenario,weusuallyhavetwooptions:

Makingsurethatthemoduleisinitializedbeforestartingtouseit,otherwisewaitforitsinitialization.Thisprocesshastobedoneeverytimewewanttoinvokeanoperationontheasynchronousmodule:

vardb=require('aDb');//Theasyncmodule

module.exports=functionfindAll(type,callback){

if(db.connected){//isitinitialized?

runFind();

}else{

db.once('connected',runFind);

}

functionrunFind(){

db.findAll(type,callback);

});

};

UseDependencyInjectioninsteadofdirectlyrequiringtheasynchronousmodule.Bydoingthis,wecandelaytheinitializationofsomemodulesuntiltheirasynchronousdependenciesarefullyinitialized.Thistechniqueshiftsthecomplexityofmanagingthemoduleinitializationtoanothercomponent,usuallytheparentmodule.Inthefollowingexample,thiscomponentisapp.js:

//inthemoduleapp.js

vardb=require('aDb');//Theasyncmodule

varfindAllFactory=require('./findAll');

db.on('connected',function(){

varfindAll=findAllFactory(db);

});

//inthemodulefindAll.js

module.exports=function(db){

//dbisguaranteedtobeinitialized

returnfunctionfindAll(type,callback){

db.findAll(type,callback);

}

}

Wecanimmediatelyseethatthefirstoptioncanbecomehighlyundesirable,consideringtheamountofboilerplatecodeinvolved.

Also,thesecondoption,whichusesDependencyInjection,sometimesisundesirable,aswehaveseeninChapter5,WiringModules.Inbigprojects,itcanquicklybecomeover-complicated,especiallyifdonemanuallyandwithasynchronouslyinitializedmodules.TheseproblemswouldbemitigatedifwewereusingaDIcontainer

Page 296: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

designedtosupportasynchronouslyinitializedmodules.

Aswewillseethough,thereisathirdalternativethatallowsustoeasilyisolatethemodulefromtheinitializationstateofitsdependencies.

PreinitializationqueuesAsimplepatterntodecoupleamodulefromtheinitializationstateofadependencyinvolvestheuseofqueuesandtheCommandpattern.Theideaistosavealltheoperationsreceivedbyamodulewhileit'snotyetinitializedandthenexecutethemassoonasalltheinitializationstepshavebeencompleted.

ImplementingamodulethatinitializesasynchronouslyTodemonstratethissimplebuteffectivetechnique,let'sbuildasmalltestapplication,nothingfancy,justsomethingtoverifyourassumptions.Let'sstartbycreatinganasynchronouslyinitializedmodulecalledasyncModule.js:

varasyncModule=module.exports;

asyncModule.initialized=false;

asyncModule.initialize=function(callback){

setTimeout(function(){

asyncModule.initialized=true;

callback();

},10000);

}

asyncModule.tellMeSomething=function(callback){

process.nextTick(function(){

if(!asyncModule.initialized){

returncallback(

newError('Idon\'thaveanythingtosayrightnow')

);

}

callback(null,'Currenttimeis:'+newDate());

});

}

Intheprecedingcode,asyncModuletriestodemonstratehowanasynchronouslyinitializedmoduleworks.Itexposesaninitialize()method,whichafteradelayof10seconds,setstheinitializedvariabletotrueandnotifiesitscallback(10secondsisalotforarealapplication,butforusit'sgreattohighlightanyracecondition).Theothermethod,tellMeSomething(),returnsthecurrenttime,butifthemoduleisnotyetinitialized,itgeneratesanerror.

Page 297: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thenextstepistocreateanothermoduledependingontheservicewejustcreated.Let'sconsiderasimpleHTTPrequesthandlerimplementedinafilecalledroutes.js:

varasyncModule=require('./asyncModule');

module.exports.say=function(req,res){

asyncModule.tellMeSomething(function(err,something){

if(err){

res.writeHead(500);

returnres.end('Error:'+err.message);

}

res.writeHead(200);

res.end('Isay:'+something);

});

}

ThehandlerinvokesthetellMeSomething()methodofasyncModule,thenitwritestheresultintoanHTTPresponse.Aswecansee,wearenotperforminganycheckontheinitializationstateofasyncModule,andaswecanimagine,thiswilllikelyleadtoproblems.

Now,let'screateaverybasicHTTPserverusingnothingbutthecorehttpmodule(theapp.jsfile):

varhttp=require('http');

varroutes=require('./routes');

varasyncModule=require('./asyncModule');

asyncModule.initialize(function(){

console.log('Asyncmoduleinitialized');

});

http.createServer(function(req,res){

if(req.method==='GET'&&req.url==='/say'){

returnroutes.say(req,res);

}

res.writeHead(404);

res.end('Notfound');

}).listen(8000,function(){console.log('Started')});

Theprecedingsmallmoduleistheentrypointofourapplication,andallitdoesistriggertheinitializationofasyncModuleandcreateanHTTPserverthatmakesuseoftherequesthandlerwecreatedpreviously(routes.say()).

Wecannowtrytofireupourserverbyexecutingtheapp.jsmoduleasusual.Aftertheserverisstarted,wecantrytohittheURL,http://localhost:8000/say,withabrowserandseewhatcomesbackfromourasyncModule.

Page 298: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Asexpected,ifwesendtherequestjustaftertheserverisstarted,theresultwillbeanerrorasfollows:

Error:Idon'thaveanythingtosayrightnow

ThismeansthatasyncModuleisnotyetinitialized,butwestilltriedtouseit.Dependingontheimplementationdetailsoftheasynchronouslyinitializedmodule,wecouldhavereceivedagracefulerror,lostimportantinformation,orevencrashedtheentireapplication.Ingeneral,thesituationwejustdescribedhastoalwaysbeavoided.Mostofthetime,afewfailingrequestsmightnotbeaconcernortheinitializationmightbesofastthat,inpractice,itwouldneverhappen;however,forhighloadapplicationsandcloudserversdesignedtoautoscale,bothoftheseassumptionsmightquicklygetobliterated.

WrappingthemodulewithpreinitializationqueuesToaddrobustnesstoourserver,wearenowgoingtorefactoritbyapplyingthepatternwedescribedatthebeginningofthesection.WewillqueueanyoperationinvokedonasyncModuleduringthetimeit'snotyetinitializedandthenflushthequeueassoonwearereadytoprocessthem.ThislookslikeagreatapplicationfortheStatepattern!Wewillneedtwostates,onethatqueuesalltheoperationswhilethemoduleisnotyetinitialized,andanotherthatsimplydelegateseachmethodtotheoriginalasyncModulemodule,whentheinitializationiscomplete.

Often,wedon'thavethechancetomodifythecodeoftheasynchronousmodule;so,toaddourqueuinglayer,wewillneedtocreateaproxyaroundtheoriginalasyncModulemodule.

Let'sstarttoworkonthecode;let'screateanewfilenamedasyncModuleWrapper.jsandlet'sstartbuildingitpiecebypiece.Thefirstthingthatweneedtodoistocreatetheobjectthatdelegatestheoperationstotheactivestate:

varasyncModule=require('./asyncModule');

varasyncModuleWrapper=module.exports;

asyncModuleWrapper.initialized=false;

asyncModuleWrapper.initialize=function(){

activeState.initialize.apply(activeState,arguments);

};

asyncModuleWrapper.tellMeSomething=function(){

activeState.tellMeSomething.apply(activeState,arguments);

};

Page 299: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Intheprecedingcode,asyncModuleWrappersimplydelegateseachofitsmethodstothecurrentlyactivestate.Let'sthenseewhatthetwostateslooklike,startingfromnotInitializedState:

varpending=[];

varnotInitializedState={

initialize:function(callback){

asyncModule.initialize(function(){

asyncModuleWrapper.initalized=true;

activeState=initializedState;//[1]

pending.forEach(function(req){//[2]

asyncModule[req.method].apply(null,req.args);

});

pending=[];

callback();//[3]

});

},

tellMeSomething:function(callback){

returnpending.push({

method:'tellMeSomething',

args:arguments

});

}

};

Whentheinitialize()methodisinvoked,wetriggertheinitializationoftheoriginalasyncModulemodule,providingacallbackproxy.Thisallowsourwrappertoknowwhentheoriginalmoduleisinitializedandconsequentlytriggersthefollowingoperations:

1. UpdatestheactiveStatevariablewiththenextstateobjectinourflow—initializedState.

2. Executesallthecommandsthatwerepreviouslystoredinthependingqueue.3. Invokestheoriginalcallback.

Asthemoduleatthispointisnotyetinitialized,thetellMeSomething()methodofthisstatesimplycreatesanewCommandobjectandaddsittothequeueofthependingoperations.

Atthispoint,thepatternshouldalreadybeclear:whentheoriginalasyncModulemoduleisnotyetinitialized,ourwrapperwillsimplyqueueallthereceivedrequests.Then,whenwearenotifiedthattheinitializationiscomplete,weexecuteallthequeuedoperationsandthenswitchtheinternalstatetoinitializedState.Let'sthenseewhatthislastpieceofthewrapperlookslike:

Page 300: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varinitializedState=asyncModule;

Without(probably)anysurprise,theinitializedStateobjectissimplyareferencetotheoriginalasyncModule!Infact,whentheinitializationiscomplete,wecansafelyrouteanyrequestdirectlytotheoriginalmodule,nothingmoreisrequired.

Atlast,wehavetosettheinitialactivestate,whichofcoursewillbenotInitializedState:

varactiveState=notInitializedState;

Wecannowtrytolaunchourtestserveragain,butfirst,let'snotforgettoreplacethereferencestotheoriginalasyncModulemodulewithournewasyncModuleWrapperobject;thishastobedoneintheapp.jsandroutes.jsmodules.

Afterdoingthis,ifwetrytosendarequesttotheserveragain,wewillseethatduringthetime,theasyncModulemoduleisnotyetinitialized;therequestswillnotfail;instead,theywillhanguntiltheinitializationiscompletedandwillonlythenbeactuallyexecuted.Wecansurelyaffirmthatthisisamuchmorerobustbehavior.

TipPattern:Ifamoduleisinitializedasynchronously,queueeveryoperationuntilthemoduleisfullyinitialized.

Now,ourservercanstartacceptingrequestsimmediatelyafterit'sstartedanditguaranteesthatnoneoftheserequestswilleverfailbecauseoftheinitializationstateofitsmodules.WewereabletoobtainthisresultwithoutusingDependencyInjectionorrequiringverboseanderror-pronecheckstoverifythestateoftheasynchronousmodule.

InthewildThepatternwejustpresentedisusedbymanydatabasedriversandORMlibraries.ThemostnotableisMongoose(http://mongoosejs.com),whichisanORMforMongoDB.WithMongoose,it'snotnecessarytowaitforthedatabaseconnectiontoopeninordertobeabletosendqueries,becauseeachoperationisqueuedandthenexecutedlaterwhentheconnectionwiththedatabaseisfullyestablished.ThisclearlybooststheusabilityofitsAPI.

Page 301: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TipTakealookatthecodeofMongoosetoseehoweverymethodinthenativedriverisproxiedtoaddthepreinitializationqueue(italsodemonstratesanalternativewayofimplementingthispattern).Youcanfindthecodefragmentresponsibleforimplementingthepatternathttps://github.com/LearnBoost/mongoose/blob/21f16c62e2f3230fe616745a40f22b4385a11b11/lib/drivers/node-mongodb-native/collection.js#L103-138.

Page 302: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AsynchronousbatchingandcachingInhigh-loadapplications,cachingplaysacriticalroleandisusedalmosteverywhereintheweb,fromstaticresourcessuchaswebpages,images,andstylesheets,topuredatasuchastheresultofdatabasequeries.Inthissection,wearegoingtolearnhowcachingappliestoasynchronousoperationsandhowahighrequestthroughputcanbeturnedtoouradvantage.

ImplementingaserverwithnocachingorbatchingBeforewestartdivingintothisnewchallenge,let'simplementasmalldemoserverthatwewilluseasareferencetomeasuretheimpactofthevarioustechniqueswearegoingtoimplement.

Let'sconsiderawebserverthatmanagesthesalesofane-commercecompany,inparticular,wewanttoqueryourserverforthesumofallthetransactionsofaparticulartypeofmerchandise.Forthispurpose,wearegoingtouseLevelUPagainforitssimplicityandflexibility.Thedatamodelthatwearegoingtouseisasimplelistoftransactionsstoredinthesalessublevel(asectionofthedatabase),whichisorganizedinthefollowingformat:

transactionIdà{amount,item}

ThekeyisrepresentedbytransactionIdandthevalueisaJSONobjectthatcontainstheamountofthesale(amount)andtheitemtype.

Thedatatoprocessisreallybasic,solet'simplementtheAPIimmediatelyinafilenamedtotalSales.js,whichwillbeasfollows:

varlevel=require('level');

varsublevel=require('level-sublevel');

vardb=sublevel(level('example-db',{valueEncoding:

'json'}));

varsalesDb=db.sublevel('sales');

module.exports=functiontotalSales(item,callback){

varsum=0;

salesDb.createValueStream()//[1]

.on('data',function(data){

if(!item||data.item===item){//[2]

sum+=data.amount;

}

Page 303: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

})

.on('end',function(){

callback(null,sum);//[3]

});

}

ThecoreofthemoduleisthetotalSalesfunction,whichisalsotheonlyexportedAPI,thisishowitworks:

1. WecreateastreamfromthesalesDbsublevelthatcontainsthesalestransactions.Thestreampullsalltheentriesfromthedatabase.

2. Thedataeventreceiveseachsaletransactionasitisreturnedfromthedatabasestream.Weaddtheamountvalueofthecurrententrytothetotalsumvalue,butonlyiftheitemtypeisequaltotheoneprovidedintheinput(orifnoinputisprovidedatall,allowingustocalculatethesumofallthetransactions,regardlessoftheitemtype).

3. Atlast,whentheendeventisreceived,weinvokethecallback()methodbyprovidingthefinalsumasresult.

Thesimplequerythatwebuiltisdefinitelynotthebestintermsofperformances.Ideally,inareal-worldapplication,wewouldhaveusedanindextoquerythetransactionsbytheitemtype,orevenbetter,anincrementalmap/reducetocalculatethesuminrealtime;however,forourexample,aslowqueryisactuallybetterasitwillhighlighttheadvantagesofthepatternswearegoingtoanalyze.

Tofinalizethetotalsalesapplication,weonlyneedtoexposethetotalSalesAPIfromanHTTPserver;so,thenextstepistobuildone(theapp.jsfile):

varhttp=require('http');

varurl=require('url');

vartotalSales=require('./totalSales');

http.createServer(function(req,res){

varquery=url.parse(req.url,true).query;

totalSales(query.item,function(err,sum){

res.writeHead(200);

res.end('Totalsalesforitem'+

query.item+'is'+sum);

});

}).listen(8000,function(){console.log('Started')});

Theserverwecreatedisveryminimalistic;weonlyneedittoexposethetotalSalesAPI.

Beforewestarttheserverforthefirsttime,weneedtopopulatethedatabasewithsomesampledata;wecandothiswiththepopulate_db.jsscriptthatwecanfindinthecodesamplesdedicatedtothissection.Thescriptwillcreate100Krandomsales

Page 304: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

transactionsinthedatabase.

Okay!Now,everythingisreadyinordertostarttheserver;asusualwecandothisbyexecutingthefollowingcommand:

nodeapp

Toquerytheserver,simplynavigatewithabrowsertothefollowingURL:

http://localhost:8000?item=book

However,tohaveabetterideaoftheperformanceofourserver,wewillneedmorethanonerequest;so,wewilluseasmallscriptnamedloadTest.jswhichsendsrequestsatanintervalof200ms.Thescriptcanbefoundinthecodesamplesofthebookandit'salreadyconfiguredtoconnecttotheURLoftheserver,so,torunit,justexecutethefollowingcommand:

nodeloadTest

Wewillseethatthe20requestswilltakeawhiletocomplete,takenoteofthetotalexecutiontimeofthetest,becausewearenowgoingtoapplyouroptimizationsandmeasurehowmuchtimewecansave.

AsynchronousrequestbatchingWhendealingwithasynchronousoperations,themostbasiclevelofcachingcanbeachievedbybatchingtogetherasetofinvocationstothesameAPI.Theideaisverysimple:ifweareinvokinganasynchronousfunctionwhilethereisstillanotheronepending,wecanattachthecallbacktothealreadyrunningoperation,insteadofcreatingabrandnewrequest.Takealookatthefollowingfigure:

Page 305: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thepreviousimageshowstwoclients(theycanbetwodifferentobjects,ortwodifferentwebrequests)invokingthesameasynchronousoperationwithexactlythesameinput.Ofcourse,thenaturalwaytopicturethissituationiswiththetwoclientsstartingtwoseparateoperationsthatwillcompleteintwodifferentmoments,asshownbytheprecedingimage.Now,considerthenextscenario,depictedinthefollowingfigure:

Page 306: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thissecondimageshowsushowthetworequests—whichinvokethesameAPIwiththesameinput—canbebatched,orinotherwordsappendedtothesamerunningoperation.Bydoingthis,whentheoperationcompletes,boththeclientswillbenotified.Thisrepresentsasimple,yetextremelypowerful,waytooptimizetheloadofanapplicationwhilenothavingtodealwithmorecomplexcachingmechanisms,whichusuallyrequireanadequatememorymanagementandinvalidationstrategy.

BatchingrequestsinthetotalsaleswebserverLet'snowaddabatchinglayerontopofourtotalSalesAPI.Thepatternwearegoingtouseisverysimple:ifthereisalreadyanotheridenticalrequestpendingwhentheAPIisinvoked,wewilladdthecallbacktoaqueue.Whentheasynchronousoperationcompletes,allthecallbacksinitsqueueareinvokedatonce.

Now,let'sseehowthispatterntranslatesincode.Let'screateanewmodulenamedtotalSalesBatch.js.Here,we'regoingtoimplementabatchinglayerontopoftheoriginaltotalSalesAPI:

vartotalSales=require('./totalSales');

varqueues={};

module.exports=functiontotalSalesBatch(item,callback){

Page 307: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

if(queues[item]){//[1]

console.log('Batchingoperation');

returnqueues[item].push(callback);

}

queues[item]=[callback];//[2]

totalSales(item,function(err,res){

varqueue=queues[item];//[3]

queues[item]=null;

queue.forEach(function(cb){

cb(err,res);

});

});

}

ThetotalSalesBatch()functionisaproxyfortheoriginaltotalSales()API,anditworksasfollows:

1. Ifaqueuealreadyexistsfortheitemtypeprovidedastheinput,itmeansthatarequestforthatparticularitemisalreadyrunning.Inthiscase,allwehavetodoissimplyappendthecallbacktotheexistingqueueandreturnfromtheinvocationimmediately.Nothingelseisrequired.

2. Ifnoqueueisdefinedfortheitem,itmeansthatwehavetocreateanewrequest.Todothis,wecreateanewqueueforthatparticularitemandweinitializeitwiththecurrentcallbackfunction.Next,weinvoketheoriginaltotalSales()API.

3. WhentheoriginaltotalSales()requestcompletes,weiterateoverallthecallbacksthatwereaddedinthequeueforthatspecificitemandinvokethemonebyonewiththeresultoftheoperation.

ThebehaviorofthetotalSalesBatch()functionisidenticaltothatoftheoriginaltotalSales()API,withthedifferencethat,now,multiplecallstotheAPIusingthesameinputarebatched,thussavingtimeandresources.

Curioustoknowwhatistheperformanceimprovementcomparedtotheraw,non-batchedversionofthetotalSales()API?Let'sthenreplacethetotalSalesmoduleusedbytheHTTPserverwiththeonewejustcreated(theapp.jsfile):

//vartotalSales=require('./totalSales');

vartotalSales=require('./totalSalesBatch');

http.createServer(function(req,res){

[...]

Ifwenowtrytostarttheserveragainandruntheloadtestagainstit,thefirstthingwewillseeisthattherequestsarereturnedinbatches.Thisistheeffectofthepatternwejustimplementedandit'sagreatpracticaldemonstrationofhowitworks.

Page 308: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Besidesthat,weshouldalsoobserveaconsiderablereductioninthetotaltimeforexecutingthetest;itshouldbeatleastfourtimesfasterthantheoriginaltestperformedagainsttheplaintotalSales()API!

Thisisastunningresult,confirmingthehugeperformanceboostwecanobtainbyjustapplyingasimplebatchinglayer,withoutallthecomplexityofmanagingafull-fledgedcache,andmoreimportantly,withoutworryingaboutinvalidationstrategies.

NoteTherequest-batchingpatternreachesitsbestpotentialinhigh-loadapplicationsandwithslowAPIs,becauseit'sexactlyinthesecircumstancesthatwecanbatchtogetherahighnumberofrequests.

AsynchronousrequestcachingOneoftheproblemswiththerequest-batchingpatternisthatthefastertheAPI,thefewerbatchedrequestsweget.OnecanarguethatifanAPIisalreadyfast,thereisnopointintryingtooptimizeit;however,itstillrepresentsafactorintheresourceloadofanapplicationthat,whensummedup,canstillhaveasubstantialimpact.Also,sometimeswecansafelyassumethattheresultofanAPIinvocationwillnotchangesooften;therefore,asimplerequestbatchingwillnotprovidethebestperformances.Inallthesecircumstances,thebestcandidatetoreducetheloadofanapplicationandincreaseitsresponsivenessisdefinitelyamoreaggressivecachingpattern.

Theideaissimple:assoonasarequestcompletes,westoreitsresultinthecache,whichcanbeavariable,anentryinthedatabase,orinaspecializedcachingserver.Hence,thenexttimetheAPIisinvoked,theresultcanberetrievedimmediatelyfromthecache,insteadofspawninganotherrequest.

Theideaofcachingshouldnotbenewtoanexperienceddeveloper,butwhatmakesthispatterndifferentinasynchronousprogrammingisthatitshouldbecombinedwiththerequestbatching,tobeoptimal.Thereasonisbecausemultiplerequestsmightrunconcurrentlywhilethecacheisnotset,andwhenthoserequestscomplete,thecachewillbesetmultipletimes.

Basedontheseassumptions,thefinalstructureoftheasynchronousrequest-cachingpatternisshowninthefollowingfigure:

Page 309: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theprecedingfigureshowsusthetwophasesofanoptimalasynchronouscachingalgorithm:

Thefirstphaseistotallyidenticaltothebatchingpattern.Anyrequestreceivedwhilethecacheisnotsetwillbebatchedtogether.Whentherequestcompletes,thecacheisset,once.Whenthecacheisfinallyset,anysubsequentrequestwillbeserveddirectlyfromit.

AnothercrucialdetailtoconsideristheunleashingZalgoanti-pattern(wehaveseenitinactioninChapter1,Node.jsDesignFundamentals).AswearedealingwithasynchronousAPIs,wemustbesuretoalwaysreturnthecachedvalueasynchronously,evenifaccessingthecacheinvolvesonlyasynchronousoperation.

CachingrequestsinthetotalsaleswebserverTodemonstrateandmeasuretheadvantagesoftheasynchronouscachingpattern,let'snowapplywhatwe'velearnedtothetotalSales()API.Asintherequest-batchingexample,wehavetocreateaproxyfortheoriginalAPIwiththesolepurposeofaddingacachinglayer.

Let'sthencreateanewmodulenamedtotalSalesCache.jsthatcontainsthefollowingcode:

vartotalSales=require('./totalSales');

Page 310: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varqueues={};

varcache={};

module.exports=functiontotalSalesBatch(item,callback){

varcached=cache[item];//[1]

if(cached){

console.log('Cachehit');

returnprocess.nextTick(callback.bind(null,null,

cached));

}

if(queues[item]){

console.log('Batchingoperation');

returnqueues[item].push(callback);

}

queues[item]=[callback];

totalSales(item,function(err,res){

if(!err){//[2]

cache[item]=res;

setTimeout(function(){

deletecache[item];

},30*1000);//30secondsexpiry

}

varqueue=queues[item];

queues[item]=null;

queue.forEach(function(cb){

cb(err,res);

});

});

}

Weshouldstraightawayseethattheprecedingcodeisinmanypartsidenticaltowhatweusedfortheasynchronousbatching.Infact,theonlydifferencesarethefollowingones:

1. ThefirstthingthatweneedtodowhentheAPIisinvokedistocheckwhetherthecacheissetandifthat'sthecase,wewillimmediatelyreturnthecachedvalueusingcallback(),makingsuretodeferitwithprocess.nextTick().

2. Theexecutioncontinuesinbatchingmode,butthistime,whentheoriginalAPIsuccessfullycompletes,wesavetheresultintothecache.Wealsosetatimeouttoinvalidatethecacheafter30seconds.Asimplebuteffectivetechnique!

Now,wearereadytotrythetotalSaleswrapperwejustcreated;todothat,weonlyneedtoupdatetheapp.jsmoduleasfollows:

//vartotalSales=require('./totalSales');

//vartotalSales=require('./totalSalesBatch');

vartotalSales=require('./totalSalesCache');

http.createServer(function(req,res){

Page 311: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

[...]

Now,theservercanbestartedagainandprofiledusingtheloadTest.jsscriptaswedidinthepreviousexamples.Withthedefaulttestparameters,weshouldseea10-percentreductionintheexecutiontimeascomparedtosimplebatching.Ofcourse,thisishighlydependentonalotoffactors;forexample,thenumberofrequestsreceived,andthedelaybetweenonerequestandtheother.Theadvantagesofusingcachingoverbatchingwillbemuchmoresubstantialwhentheamountofrequestsishigherandspansalongerperiodoftime.

NoteMemoizationisthepracticeofcachingtheresultofafunctioninvocation.Innpm,youcanfindmanypackagestoimplementasynchronousmemoizationwithlittleeffort;oneofthemostcompletepackagesismemoizee(https://npmjs.org/package/memoizee).

NotesaboutimplementingcachingmechanismsWemustrememberthatinreal-lifeapplications,wemightwanttousemoreadvancedinvalidationtechniquesandstoragemechanisms.Thismightbenecessaryforthefollowingreasons:

Alargeamountofcachedvaluesmighteasilyconsumealotofmemory.Inthiscase,aLeastRecentlyUsed(LRU)algorithmcanbeappliedtomaintainconstantmemoryutilization.Whentheapplicationisdistributedacrossmultipleprocesses,usingasimplevariableforthecachemightresultindifferentresultstobereturnedbyeachserverinstance.Ifthat'sundesiredfortheparticularapplicationweareimplementing,thesolutionistouseasharedstoreforthecache.PopularsolutionsareRedis(http://redis.io)andMemcached(http://memcached.org).Amanualcacheinvalidation,asopposedtoatimedexpiry,canenablealonger-livingcacheandatthesametimeprovidemoreup-to-datedata,but,ofcourse,itwouldbealotmorecomplextomanage.

BatchingandcachingwithPromisesInChapter2,AsynchronousControlFlowPatterns,wesawhowPromisescangreatlysimplifyourasynchronouscode,buttheyofferanevenmoreinterestingapplicationwhendealingwithbatchingandcaching.IfwerecallwhatwesaidaboutPromises,therearetwopropertiesthatcanbeexploitedtoouradvantageinthis

Page 312: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

circumstance:

Multiplethen()listenerscanbeattachedtothesamepromise.Thethen()listenerisguaranteedtobeinvokedatmostonceanditworksevenifit'sattachedafterthepromiseisalreadyresolved.Moreover,then()isguaranteedtobeinvokedasynchronously,always.

Inshort,thefirstpropertyisexactlywhatweneedforbatchingrequests,whilethesecondmeansthatapromiseisalreadyacachefortheresolvedvalueandoffersanaturalmechanismforreturningacachedvalueinaconsistentasynchronousway.Inotherwords,thismeansthatbatchingandcachingareextremelysimpleandconcisewithPromises.

Todemonstratethis,wecantrytocreateawrapperforthetotalSales()API,usingPromises,andseewhatittakestoaddabatchingandcachinglayer.Let'sseethenhowthislookslike.Let'screateanewmodulenamedtotalSalesPromises.js:

vartotalSales=require('./totalSales');

varPromise=require('bluebird');//[1]

totalSales=Promise.promisify(totalSales);

varcache={};

module.exports=functiontotalSalesPromises(item){

if(cache[item]){//[2]

returncache[item];

}

cache[item]=totalSales(item)//[3]

.then(function(res){//[4]

setTimeout(function(){

deletecache[item];

},30*1000);//30secondsexpiry

returnres;

})

.catch(function(err){//[5]

deletecache[item];

throwerr;

});

returncache[item];//[6]

}

Thefirstthingthatstrikesusisthesimplicityandeleganceofthesolutionweimplementedintheprecedingcode.Promisesareindeedagreattool,butforthisparticularapplicationtheyofferahuge,out-of-the-boxadvantage.Thisiswhathappensintheprecedingcode:

1. First,werequireourPromiseimplementation(bluebird)andthenapplya

Page 313: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

promisificationtotheoriginaltotalSales()function.Afterdoingthis,totalSales()willreturnaPromiseinsteadofacceptingacallback.

2. WhenthetotalSalesPromises()wrapperisinvoked,wecheckwhetheracachedPromisealreadyexistsforthegivenitemtype.IfwealreadyhavesuchaPromise,wereturnitbacktothecaller.

3. Ifwedon'thaveaPromiseinthecacheforthegivenitemtype,weproceedtocreateonebyinvokingtheoriginal(promisified)totalSales()API.

4. WhenthePromiseresolves,wesetupatimetoclearthecacheafter30secondsandwereturnrestopropagatetheresultoftheoperationtoanyotherthen()listenerattachedtothePromise.

5. IfthePromiserejectswithanerror,weimmediatelyresetthecacheandthrowtheerroragaintopropagateittothepromisechain,soanyotherlistenerattachedtothesamePromisewillreceivetheerroraswell.

6. Atlast,wereturnthecachedpromisewejustcreated.

Verysimpleandintuitive,andmoreimportantly,wewereabletoachievebothbatchingandcaching.

IfwenowwanttotrythetotalSalesPromise()function,wewillhavetoslightlyadapttheapp.jsmoduleaswell,becausenow,theAPIisusingPromisesinsteadofcallbacks.Let'sdoitbycreatingamodifiedversionoftheappmodulenamedappPromises.js:

varhttp=require('http');

varurl=require('url');

vartotalSales=require('./totalSalesPromises');

http.createServer(function(req,res){

varquery=url.parse(req.url,true).query;

totalSales(query.item).then(function(sum){

res.writeHead(200);

res.end('Totalsalesforitem'+

query.item+'is'+sum);

});

}).listen(8000,function(){console.log('Started')});

ItsimplementationisalmostidenticaltotheoriginalappmodulewiththedifferencethatnowweusethePromise-basedversionofthebatching/cachingwrapper;therefore,thewayweinvokeitisalsoslightlydifferent.

That'sit!Wearenowreadytotrythisnewversionoftheserverbyrunningthefollowingcommand:

nodeappPromises

Page 314: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UsingtheloadTestscript,wecanverifythatthenewimplementationisworkingasexpected.TheexecutiontimeshouldbethesameaswhenwetestedtheserverusingthetotalSalesCache()API.

Page 315: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

RunningCPU-boundtasksThetotalSales()API,eventhoughexpensiveintermsofresources,wasnotaffectingtheabilityoftheservertoacceptconcurrentrequests.WhatwelearnedinChapter1,Node.jsDesignFundamentals,abouttheeventloopshouldprovideanexplanationforthisbehavior:invokinganasynchronousoperationcausesthestacktounwindbacktotheeventloop,leavingitfreetohandleotherrequests.

However,whathappenswhenwerunalong,synchronoustaskthatnevergivesbackthecontroltotheeventloop?ThiskindoftaskisalsoknownasCPU-bound,becauseitsmaincharacteristicisthatitisheavyonCPUutilizationratherthanbeingheavyonI/Ooperations.

Let'sworkimmediatelyonanexampletoseehowthesetypesoftasksbehaveinNode.js.

SolvingthesubsetsumproblemLet'snowchooseacomputationallyexpensiveproblemtouseasabaseforourexperiment.Agoodcandidateisthesubsetsumproblemthatconsistsofdecidingwhetheraset(ormultiset)ofintegerscontainsanon-emptysubsetthathasasumequaltozero.Forexample,ifwehadasinputtheset[1,2,-4,5,-3],thesubsetssatisfyingtheproblemare[1,2,-3]and[2,-4,5,-3].

Themostsimplealgorithmistheonethatcheckseverypossiblecombinationofsubsetsofanysize,andithasacomputationalcostofO(2n),orinotherwords,itgrowsexponentiallywiththesizeoftheinput.Thismeansthatasetof20integerswouldrequireupto1,048,576combinationstobechecked,notbadfortestingourassumptions.Ofcourse,thesolutionmightbefoundalotsoonerthanthat;so,tomakethingsharder,wearegoingtoconsiderthefollowingvariationofthesubsetsumproblem:givenasetofintegers,wewanttocalculateallthepossiblecombinationswhosesumisequaltoagivenarbitraryinteger.

Let'sthenworktobuildsuchanalgorithm,let'screateanewmodulecalled'subsetSum.js';wewillstartbycreatingaclasscalledSubsetSum:

varinherits=require('util').inherits;

varEventEmitter=require('events').EventEmitter;

functionSubsetSum(sum,set){

EventEmitter.call(this);

this.sum=sum;

this.set=set;

Page 316: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

this.totalSubsets=0;

}

inherits(SubsetSum,EventEmitter);

module.exports=SubsetSum;

TheSubsetSumclassisinheritingfromtheEventEmitter,thisallowsustoproduceaneventeverytimewefindanewsubsetmatchingthesumreceivedasinput.Aswewillsee,thiswillgiveusalotofflexibility.

Next,let'sseehowwecangenerateallthepossiblecombinationsofsubsets:

SubsetSum.prototype._combine=function(set,subset){

for(vari=0;i<set.length;i++){

varnewSubset=subset.concat(set[i]);

this._combine(set.slice(i+1),newSubset);

this._processSubset(newSubset);

}

}

Wewillnotgointotoomuchdetailaboutthealgorithm,buttherearetwoimportantthingstonotice:

The_combine()methodiscompletelysynchronous;itrecursivelygenerateseverypossiblesubsetwithoutevergivingbackthecontroltotheeventloop.Ifwethinkaboutit,thisisperfectlynormalforanalgorithmnotrequiringanyI/O.Everytimeanewcombinationisgenerated,weprovideittothe_processSubset()methodforfurtherprocessing.

The_processSubset()methodisresponsibleforverifyingthatthesumoftheelementsofthegivensubsetisequaltothenumberwearelookingfor:

SubsetSum.prototype._processSubset=function(subset){

console.log('Subset',++this.totalSubsets,subset);

varres=subset.reduce(function(prev,item){

returnprev+item;

},0);

if(res==this.sum){

this.emit('match',subset);

}

}

Trivially,the_processSubset()methodappliesareduceoperationtothesubsetinordertocalculatethesumofitselements.Then,itemitsaneventoftype'match'whentheresultingsumisequaltotheoneweareinterestedinfinding(this.sum).

Finally,thestart()methodputsalltheprecedingpiecestogether:

Page 317: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SubsetSum.prototype.start=function(){

this._combine(this.set,[]);

this.emit('end');

}

Theprecedingmethodtriggersthegenerationofallthecombinationsbyinvoking_combine(),andlastly,emitsan'end'eventsignalingthatallthecombinationswerecheckedandanypossiblematchalreadyemitted.Thisispossiblebecause_combine()issynchronous;therefore,the'end'eventwillbeemittedassoonasthefunctionreturns,whichmeansthatallthecombinationswerecalculated.

Next,wehavetoexposeoverthenetworkthealgorithmwejustcreated,asalwayswecanuseasimpleHTTPserverforthetask.Inparticular,wewanttocreateanendpointintheformat,'/subsetSum?data=<Array>&sum=<Integer>'thatinvokestheSubsetSumalgorithmwiththegivenarrayofintegersandsumtomatch.

Let'sthenimplementthissimpleserverinamodulenamedapp.js:

varhttp=require('http');

varSubsetSum=require('./subsetSum');

http.createServer(function(req,res){

varurl=require('url').parse(req.url,true);

if(url.pathname==='/subsetSum'){

vardata=JSON.parse(url.query.data);

res.writeHead(200);

varsubsetSum=newSubsetSum(url.query.sum,data);

subsetSum.on('match',function(match){

res.write('Match:'+JSON.stringify(match)+'\n');

});

subsetSum.on('end',function(){

res.end();

});

subsetSum.start();

}else{

res.writeHead(200);

res.end('I\malive!\n');

}

}).listen(8000,function(){console.log('Started')});

ThankstothefactthattheSubsetSumobjectreturnsitsresultsusingevents,wecanstreamthematchingsubsetsassoonastheyaregeneratedbythealgorithm,inrealtime.AnotherdetailtomentionisthatourserverrespondswiththetextI'mAlive!everytimewehitaURLdifferentfrom/subsetSum.Wewillusethisforcheckingtheresponsivenessofourserver,aswewillseeinamoment.

Wearenowreadytotryoursubsetsumalgorithm,curioustoknowhowourserver

Page 318: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

willhandleit?Let'sthenfireitup:

nodeapp

Assoonastheserverstarts,wearereadytosendourfirstrequest,let'strythenwithasetof17randomnumbers,whichwillresultinthegenerationof131,071combinations,aniceamounttokeepourserverbusyforawhile:

curl-Ghttp://localhost:8000/subsetSum--data-urlencode

"data=[116,

119,101,101,-116,109,101,-105,-102,117,-115,-97,119,-116,-104,

-105,115]"--data-urlencode"sum=0"

Wewillstarttoseetheresultsstreaminglivefromtheserver,butifwetrythefollowingcommandinanotherterminalwhilethefirstrequestisstillrunning,wewillspotahugeproblem:

curl-Ghttp://localhost:8000

WewillimmediatelyseethatthislastrequesthangsuntiltheSubsetSumalgorithmofthefirstrequesthasfinished;theserverisunresponsive!Thiswaskindofwhatweexpected,theNode.jseventlooprunsinasinglethread,andifthisthreadisblockedbyalongsynchronouscomputation,itwillbeunabletoexecuteevenonemorecycleinordertorespondwithasimpleI'malive!.

Wequicklyunderstandthatthisbehaviordoesnotworkforanykindofapplicationmeanttoservemultiplerequests.Butdon'tdespair,inNode.js,wecantacklethistypeofsituationinseveralways,let'sanalyzethetwomostimportantones.

InterleavingwithsetImmediateUsually,aCPU-boundalgorithmisbuiltuponasetofsteps,itcanbeasetofrecursiveinvocations,alooporanyvariation/combinationofthose.So,asimplesolutiontoourproblemwouldbetogivebackthecontroltotheeventloopaftereachoneofthesestepscompletes(orafteracertainnumberofthem).Thisway,anypendingI/Ocanstillbeprocessedbytheeventloopinthoseintervalswherethelong-runningalgorithmyieldstheCPU.AsimplewaytoachievethisistoschedulethenextstepofthealgorithmtorunafteranypendingI/Orequests.ThissoundsliketheperfectusecaseforthesetImmediate()function(wealreadyintroducedthisAPIinChapter1,Node.jsDesignFundamentals).

Note

Page 319: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Pattern:Interleavetheexecutionofalong-runningsynchronoustaskwithsetImmediate().

InterleavingthestepsofthesubsetsumalgorithmLet'snowseehowthispatternappliestothesubsetsumalgorithm.AllwehavetodoisslightlymodifythesubsetSum.jsmodule.Forconvenience,wearegoingtocreateanewmodulecalledsubsetSumDefer.js,takingthecodeoftheoriginalsubsetSumclassasastartingpoint.

Thefirstchangewearegoingtomakeistoaddanewmethodcalled_combineInterleaved(),whichisthecoreofthepatternweareimplementing:

SubsetSumDefer.prototype._combineInterleaved=function(set,

subset){

this.runningCombine++;

setImmediate(function(){

this._combine(set,subset);

if(--this.runningCombine===0){

this.emit('end');

}

}.bind(this));

}

Aswecansee,allwehadtodoisdefertheinvocationoftheoriginal(synchronous)_combine()methodwithsetImmediate().However,nowitbecomesmoredifficulttoknowwhenthefunctionhasfinishedgeneratingallthecombinations,becausethealgorithmisnotsynchronousanymore.Tofixthis,wehavetokeeptrackofalltherunninginstancesofthe_combine()methodusingapatternverysimilartotheasynchronousparallelexecutionwehaveseeninChapter2,AsynchronousControlFlowPatterns.Whenalltheinstancesofthe_combine()methodarefinishedrunning,wecanthenemittheendeventnotifyinganylistenerthattheprocesshascompleted.

TofinalizetherefactoringoftheSubsetSumalgorithm,weneedacoupleofmoretweaks.First,weneedtoreplacetherecursivestepinthe_combine()methodwithitsdeferredcounterpart:

SubsetSumDefer.prototype._combine=function(set,subset){

for(vari=0;i<set.length;i++){

varnewSubset=subset.concat(set[i]);

this._combineInterleaved(set.slice(i+1),newSubset);

this._processSubset(newSubset);

}

}

Page 320: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Withtheprecedingchange,wemakesurethateachstepofthealgorithmwillbequeuedintheeventloopusingsetImmediate()andthereforeexecutedafteranypendingI/Orequest,insteadofrunningsynchronously.

Theothersmalltweakisinthestart()method:

SubsetSumDefer.prototype.start=function(){

this.runningCombine=0;

this._combineInterleaved(this.set,[]);

}

Intheprecedingcode,weinitializethenumberofrunninginstancesofthe_combine()methodto0.Wealsoreplacedthecallto_combine()withacallto_combineInterleaved()andremovedtheemissionofthe'end'event,becausenowthisishandledasynchronouslyin_combineInterleaved().

Withthislastchange,oursubsetsumalgorithmshouldnowbeabletorunitsCPU-boundcodeinstepsinterleavedbyintervalswheretheeventloopcanrunandprocessanyotherpendingrequest.

Thelastmissingbitisupdatingtheapp.jsmodulesothatitcanusethenewversionoftheSubsetSumAPI,thisisactuallyatrivialchange:

varhttp=require('http');

//varSubsetSum=require('./subsetSum');

varSubsetSum=require('./subsetSumDefer');

http.createServer(function(req,res){

[...]

Wearenowreadytotrythisnewversionofthesubsetsumserver.Let'sstarttheappmodulebyusingthefollowingcommand:

nodeapp

Then,trytosendarequestagaintocalculateallthesubsetsmatchingagivensum:

curl-Ghttp://localhost:8000/subsetSum--data-urlencode

"data=[116,

119,101,101,-116,109,101,-105,-102,117,-115,-97,119,-116,-104,

-105,115]"--data-urlencode"sum=0"

Whiletherequestisrunning,wemightnowwanttoseewhethertheserverisresponsive:

Page 321: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

curl-Ghttp://localhost:8000

Cool!Thesecondrequestnowshouldreturnimmediately,evenwhileaSubsetSumtaskisrunning,confirmingthatourpatternisworkinggreat.

ConsiderationsontheinterleavingpatternAswesaw,runningaCPU-boundtaskwhilepreservingtheresponsivenessofanapplicationisnotthatcomplicated,itjustrequirestheuseofsetImmediate()toschedulethenextstepofanalgorithmafteranypendingI/O.However,thisisnotthebestpatternintermsofefficiency;infact,deferringataskintroducesasmalloverheadthat,multipliedbyallthestepsthatanalgorithmhastorun,canhaveasignificantimpact.ThisisusuallythelastthingwewantwhenrunningaCPU-boundtask,especiallyifwehavetoreturntheresultdirectlytotheuser,whichshouldhappeninareasonableamountoftime.ApossiblesolutiontomitigatetheproblemwouldbeusingsetImmediate()onlyafteracertainnumberofsteps—insteadofusingitateverysinglestep—butstillthiswouldnotsolvetherootoftheproblem.

Bearinmindthatthisdoesnotmeanthatthepatternwehavejustseenshouldbeavoidedatallcosts,infact,ifwelookatthebiggerpicture,asynchronoustaskdoesnotnecessarilyhavetobeextremelylongandcomplextocreatetroubles.Inabusyserver,evenataskthatblockstheeventloopfor200millisecondscancreateundesirabledelays.Inthosesituations,wherethetaskisexecutedsporadicallyorinthebackgroundanddoesnothavetorunfortoolong,usingsetImmediate()tointerleaveitsexecutionisprobablythesimplestandmosteffectivewaytoavoidblockingtheeventloop.

Noteprocess.nextTick()cannotbeusedtointerleavealong-runningtask.AswesawinChapter1,Node.jsDesignFundamentals,nextTick()schedulesanoperationbeforeanypendingI/O,andthiscaneventuallycauseI/Ostarvationincaseofrepeatedcalls.YoucanverifythatbyyourselfbyreplacingsetImmediate()withprocess.nextTick()intheprevioussample.YoumightalsowanttoknowthatthisbehaviorwasintroducedwithNode.js0.10,infact,withNode.js0.8,process.nextTick()canstillbeusedasaninterleavingmechanism.TakealookatthisGitHubissuetoknowmoreaboutthehistoryandmotivationsofthischangeathttps://github.com/joyent/node/issues/3335

Usingmultipleprocesses

Page 322: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

DeferringthestepsofanalgorithmisnottheonlyoptionwehaveforrunningCPU-boundtasks;anotherpatternforpreventingtheeventloopfromblockingisusingchildprocesses.WealreadyknowthatNode.jsgivesitsbestwhenrunningI/Ointensiveapplicationssuchaswebservers,whichallowsustooptimizeresourceutilization,thankstoitsasynchronousarchitecture.

So,thebestwaywehavetomaintaintheresponsivenessofanapplicationistonotrunexpensiveCPU-boundtasksinthecontextofthemainapplicationanduseinsteadseparateprocesses.Thishasthreemainadvantages:

Thesynchronoustaskcanrunatfullspeed,withouttheneedtointerleavethestepsofitsexecutionWorkingwithprocessesinNode.jsissimple,probablyeasierthanmodifyinganalgorithmtousesetImmediate(),andallowsustoeasilyusemultipleprocessorswithouttheneedtoscalethemainapplicationitselfIfwereallyneedmaximumperformances,theexternalprocessmightbecreatedinlower-levellanguages,suchasthegoodoldC(alwaysusethebesttoolforthejob!)

Node.jshasanampletoolbeltofAPIsforinteractingwithexternalprocesses,wecanfindallweneedinthechild_processmodule.Moreover,whentheexternalprocessisjustanotherNode.jsprogram,connectingittothemainapplicationisextremelyeasyandwedon'tevenfeellikewearerunningsomethingexternaltothelocalapplication.Themagichappensthankstothechild_process.fork()function,whichcreatesanewchildNode.jsprocessandalsoautomaticallycreatesacommunicationchannelwithit,allowingustoexchangeinformationusinganinterfaceverysimilartoanEventEmitter.Let'sseehowthisworksbyrefactoringoursubsetsumserveragain.

DelegatingthesubsetsumtasktootherprocessesThegoalfortherefactoringoftheSubsetSumtaskistocreateaseparatechildprocessresponsibleforhandlingthesynchronousprocessing,leavingtheeventloopoftheserverfreetohandlerequestscomingfromthenetwork.Thisistherecipewearegoingtofollowtomakethispossible:

1. WewillcreateanewmodulenamedprocessPool.jsthatwillallowustocreateapoolofrunningprocesses.Startinganewprocessisexpensiveandrequirestime,sokeepingthemconstantlyrunningandreadytohandlerequestsallowsustosavetimeandCPU.Also,thepoolwillhelpuslimitthenumberofprocessesrunningatthesametimetoavoidexposingtheapplicationtoDenialofService(DoS)attacks.

2. Next,wewillcreateamodulecalled'subsetSumFork.js'responsiblefor

Page 323: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

abstractingaSubsetSumtaskrunninginachildprocess.Itsrolewillbecommunicatingwiththechildprocessandexposingtheresultsofthetaskasiftheywerecomingfromthecurrentapplication.

3. Atlast,weneedaworker(ourchildprocess),anewNode.jsprogramwiththeonlygoalofrunningtheSubsetSumalgorithmandforwardingitsresultstotheparentprocess.

Implementingaprocesspool

Let'sstartbybuildingtheprocessPool.jsmodulepiecebypiece:

varfork=require('child_process').fork;

functionProcessPool(file,poolMax){

this.file=file;

this.poolMax=poolMax;

this.pool=[];

this.active=[];

this.waiting=[];

}

module.exports=ProcessPool;

Inthefirstpartofthemodule,weimportthechild_process.fork()functionthatwewillusetocreatenewprocesses.Then,wedefinetheProcessPoolconstructorthatacceptsafilerepresentingtheNode.jsprogramtorunandthemaximumnumberofrunninginstancesinthepool(poolMax).Wethendefinethreeinstancevariables:

poolisthesetofrunningprocessesreadytobeusedactivecontainsthelistoftheprocessescurrentlybeingusedwaitingcontainsaqueueofcallbacksforallthoserequeststhatcouldnotbefulfilledimmediatelybecauseofthelackofanavailableprocess

ThenextpieceoftheProcessPoolclassistheacquire()method,whichisresponsibleforreturningaprocessreadytobeused:

ProcessPool.prototype.acquire=function(callback){

varworker;

if(this.pool.length>0){//[1]

worker=this.pool.pop();

this.active.push(worker);

returnprocess.nextTick(callback.bind(null,null,

worker));

}

if(this.active.length>=this.poolMax){//[2]

returnthis.waiting.push(callback);

}

Page 324: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

worker=fork(this.file);//[3]

this.active.push(worker);

process.nextTick(callback.bind(null,null,worker));

}

Itslogicisverysimpleandisexplainedasfollows:

1. Ifwehaveaprocessinpoolreadytobeused,wesimplymoveittotheactivelistandthenreturnitbyinvokingcallback(inadeferredfashion,rememberZalgo?).

2. Iftherearenoavailableprocessesinpoolandwealreadyhavereachedthemaximumnumberofrunningprocesses,wehavetowaitforonetobeavailable.Weachievethisbyqueuingthecurrentcallbackinthewaitinglist.

3. Ifwehaven'tyetreachedthemaximumnumberofrunningprocesses,wewillcreateanewoneusingchild_process.fork(),addittotheactivelist,andthenreturnittothecallerusingthecallback.

ThelastmethodoftheProcessPoolclassisrelease(),whosepurposeistoputaprocessbackinthepool:

ProcessPool.prototype.release=function(worker){

if(this.waiting.length>0){//[1]

varwaitingCallback=this.waiting.shift();

waitingCallback(null,worker);

}

this.active=this.active.filter(function(w){//[2]

returnworker!==w;

});

this.pool.push(worker);

}

Theprecedingcodeisalsoverysimpleanditsexplanationisasfollows:

1. Ifthereisarequestinthewaitinglist,wesimplyreassigntheworkerbeingreleasedbypassingittothecallbackattheheadofthewaitingqueue.

2. Otherwise,weremovetheworkerfromtheactivelistandputitbackintopool.

Aswecansee,theprocessesareneverstoppedbutjustreassigned,allowingustosavetimebynotrestartingthemateachrequest.However,it'simportanttoobservethatthismightnotalwaysbethebestchoiceandthisgreatlydependsontherequirementsofourapplication.Possibletweaksforreducinglong-termmemoryusageandaddingrobustnesstoourprocesspoolare:

TerminateidleprocessestofreememoryafteracertaintimeofinactivityAddamechanismtokillnon-responsiveprocesses,orrestartthosethathave

Page 325: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

simplycrashed

Butinthisexample,wewillkeeptheimplementationofourprocesspoolsimple,asthedetailswemightwanttoaddarereallyendless.

Communicatingwithachildprocess

NowthatourProcessPoolclassisready,wecanuseittoimplementtheSubsetSumForkwrapperwhoseroleistocommunicatewiththeworkerandexposetheresultsitproduces.Aswesaid,startingaprocesswithchild_process.fork()alsogivesusasimplemessage-basedcommunicationchannel,solet'sseehowthisworksbyimplementingthesubsetSumFork.jsmodule:

varinherits=require('util').inherits;

varEventEmitter=require('events').EventEmitter;

varProcessPool=require('./processPool');

varworkers=newProcessPool(__dirname+

'/subsetSumWorker.js',2);

functionSubsetSumFork(sum,set){

EventEmitter.call(this);

this.sum=sum;

this.set=set;

}

inherits(SubsetSumFork,EventEmitter);

module.exports=SubsetSumFork;

SubsetSumFork.prototype.start=function(){

workers.acquire(function(worker){//[1]

worker.send({sum:this.sum,set:this.set});

varonMessage=function(msg){

if(msg.event==='end'){//[3]

worker.removeListener('message',onMessage);

workers.release(worker);

}

this.emit(msg.event,msg.data);//[4]

}.bind(this);

worker.on('message',onMessage);//[2]

}.bind(this));

}

ThefirstthingtonoticeisthatweinitializedaProcessPoolobjectusingastargetafilenamedsubsetSumWorker.js,whichrepresentsourchildworker.Wealsosettotwothemaximumcapacityofthepool.

AnotherpointworthmentioningisthatwetriedtomaintainthesamepublicAPIofthe

Page 326: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

originalSubsetSumclass.Infact,SubsetSumForkisanEventEmitterwhoseconstructoracceptsasumandaset,whilethestart()methodtriggerstheexecutionofthealgorithm,whichrunsonaseparateprocessthistime.Thisiswhathappenswhenthestart()methodisinvoked:

1. Wetrytoacquireanewchildprocessfromthepool.Whenthishappens,weimmediatelyusetheworkerhandletosendthechildprocessamessagewiththeinputofthejobtorun.Thesend()APIisprovidedautomaticallybyNode.jstoallprocessesthatstartwithchild_process.fork(),thisisessentiallythecommunicationchannelthatweweretalkingabout.

2. Wethenstartlisteningforanymessagereturnedfromtheworkerprocess,usingtheon()methodtoattachanewlistener(thisisalsoapartofthecommunicationchannelprovidedbyallprocessesthatstartwithchild_process.fork()).

3. Inthelistener,wefirstcheckwhetherwereceivedanendevent,whichmeansthattheSubsetSumtaskhasfinished,inwhichcaseweremovetheonMessagelistenerandreleasetheworker,puttingitbackintothepool.

4. Theworkerproducesmessagesintheformat{event,data}allowingustoseamlesslyre-emitanyeventproducedbythechildprocess.

That'sitfortheSubsetSumForkwrapper;let'snowimplementtheworkerapplication.

NoteItisgoodtoknowthatthesend()methodavailableonachildprocessinstancecanalsobeusedtopropagateasockethandlefromthemainapplicationtoachildprocess(lookatthedocumentationhttp://nodejs.org/api/child_process.html#child_process_child_send_message_sendhandle).ThisisactuallythetechniqueusedbytheclustermoduletodistributetheloadofanHTTPserveracrossmultipleprocesses(asofNode.js0.10).Wewillseethisinmoredetailinthenextchapter.

Communicatingwiththeparentprocess

Let'snowcreatethesubsetSumWorker.jsmodule,ourworkerapplication,theentirecontentofthismodulewillruninaseparateprocess:

varSubsetSum=require('./subsetSum');

process.on('message',function(msg){//[1]

varsubsetSum=newSubsetSum(msg.sum,msg.set);

subsetSum.on('match',function(data){//[2]

process.send({event:'match',data:data});

Page 327: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

});

subsetSum.on('end',function(data){

process.send({event:'end',data:data});

});

subsetSum.start();

});

Wecanimmediatelyseethatwearereusingtheoriginal(andsynchronous)SubsetSumasitis.Nowthatweareinaseparateprocess,wedon'thavetoworrytoblocktheeventloopanymore,alltheHTTPrequestswillcontinuetobehandledbytheeventloopofthemainapplication,withoutdisruptions.

Whentheworkerisstartedasachildprocess,thisiswhathappens:

1. Itimmediatelystartslisteningformessagescomingfromtheparentprocess.Thiscanbeeasilydonewiththeprocess.on()function(also,apartofthecommunicationAPIprovidedwhentheprocessstartsusingchild_process.fork()).TheonlymessageweexpectfromtheparentprocessistheoneprovidingtheinputtoanewSubsetSumtask.Assoonassuchamessageisreceived,wecreateanewinstanceofaSubsetSumclassandregisterthelistenersforthematchandendevents.Lastly,westartthecomputationwithsubsetSum.start().

2. Everytimeaneventisreceivedfromtherunningalgorithm,wewrapitinanobjectwiththeformat,{event,data},andsendittotheparentprocess.ThesemessagesarethenhandledinthesubsetSumFork.jsmodule,aswehaveseenintheprevioussection.

Aswecansee,wejusthadtowrapthealgorithmwealreadybuilt,withoutmodifyingitsinternals.Thisclearlyshowsthatanyportionofanapplicationcanbeeasilyputinanexternalprocessbysimplyusingthepatternwehavejustseen.

TipWhenthechildprocessisnotaNode.jsprogram,thesimplecommunicationchannelwejustdescribedisnotavailable.Inthesesituations,wecanstillestablishaninterfacewiththechildprocessbyimplementingourownprotocolontopofthestandardinputandstandardoutputstreams,whichareexposedtotheparentprocess.

Tofindoutmoreaboutallthecapabilitiesofthechild_processAPI,youcanrefertotheofficialNode.jsdocumentationathttp://nodejs.org/api/child_process.html.

Page 328: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ConsiderationsonthemultiprocesspatternAsalways,totrythisnewversionofthesubsetSumalgorithm,wesimplyhavetoreplacethemoduleusedbytheHTTPserver(fileapp.js):

varhttp=require('http');

//varSubsetSum=require('./subsetSum');

//varSubsetSum=require('./subsetSumDefer');

varSubsetSum=require('./subsetSumFork');

[...]

Wecannowstarttheserveragainandtrytosendasamplerequest:

curl-Ghttp://localhost:8000/subsetSum--data-urlencode

"data=

[116,119,101,101,-116,109,101,-105,-102,117,-115,-97,119,-116,

-104,-105,115]"--data-urlencode"sum=0"

Similartotheinterleavingpatternwehaveseenbefore,alsowiththisnewversionofthesubsetSummoduletheeventloopisnotblockedwhilerunningtheCPU-boundtask.Thiscanbeconfirmedbysendinganotherconcurrentrequestasfollows:

curl-Ghttp://localhost:8000

Theprecedingcommandlineshouldimmediatelyreturnastringasfollows:

I'malive!

Moreinterestingly,wecanalsotrytostarttwosubsetSumtasksconcurrently,wecanseethattheywillusethefullpoweroftwodifferentprocessorsinordertorun(ifoursystemhasmorethanoneprocessor,ofcourse).Instead,ifwetrytorunthreesubsetSumtasksconcurrently,theresultshouldbethatthelastonetostartwillhang.Thisisnotbecausetheeventloopofthemainprocessisblocked,butbecausewesetaconcurrencylimitoftwoprocessesforthesubsetSumtask,whichmeansthatthethirdrequestwillbehandledassoonasatleastoneofthetwoprocessesinthepoolbecomesavailableagain.

Aswesaw,themultiprocesspatternisdefinitelymorepowerfulandflexiblethantheinterleavingpattern;however,it'sstillnotscalable,astheamountofresourcesofferedbyasinglemachineisstillahardlimit.Theanswerinthiscaseistodistributetheloadacrossmultiplemachines,butthisisanotherstoryandfallsunderthecategoryofdistributedarchitecturalpatterns,whichwewillexploreinthenextchapters.

Page 329: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TipItisworthmentioningthatthreadscanbeapossiblealternativetoprocesseswhenrunningCPU-boundtasks.Currently,thereareafewnpmpackagesthatexposeanAPIforworkingwiththreadstouserlandmodules;oneofthemostpopulariswebworker-threads(https://npmjs.org/package/webworker-threads).However,evenifthreadsaremorelightweight,full-fledgedprocessescanoffermoreflexibilityandabetterlevelofisolationinthecaseofproblemssuchasfreezingorcrashing.

Page 330: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SharingcodewiththebrowserOneofthemainsellingpointofNode.jsisthefactthatit'sbasedonJavaScriptandrunsonV8,anenginethatactuallypowersoneofthemostpopularbrowsers:Chrome.Wemightthinkthatthat'senoughtoconcludethatsharingcodebetweenNode.jsandthebrowserisaneasytask;howeveraswewillsee,thisisnotalwaystrue.Unlesswewanttoshareonlysomesmall,self-containedandgenericfragmentsofcode,developingforboththeclientandtheserverrequiresanon-negligiblelevelofeffortinmakingsurethatthesamecodecanrunproperlyintwoenvironmentsthatareintrinsicallydifferent.Forexample,inNode.jswedon'thavetheDOMorlong-livingviews,whileinthebrowserwesurelydon'thavethefilesystemortheabilitytostartnewprocesses.Mostoftheeffortrequiredwhendevelopingforboththeplatformsismakingsuretoreducethosedifferencestotheminimum.Thiscanbedonewiththehelpofabstractionsandpatternsthatenabletheapplicationtoswitch,dynamicallyoratbuildtime,betweenthebrowser-compatiblecodeandtheNode.jscode.

Luckily,withtherisinginterestinthisnewmind-blowingpossibility,manylibrariesandframeworksintheecosystemhavestartedtosupportbothenvironments.Thisevolutionisalsobackedbyagrowingnumberoftoolssupportingthisnewkindofworkflow,whichovertheyearshavebeenrefinedandperfected.ThismeansthatifweareusingannpmpackageonNode.js,thereisagoodprobabilitythatitwillworkseamlesslyonthebrowseraswell.However,thisisoftennotenoughtoguaranteethatourapplicationcanrunwithoutproblemsonboththebrowserandNode.js.Aswewillsee,acarefuldesignisalwaysneededwhendevelopingcross-platformcode.

Inthissection,wearegoingtoexplorethefundamentalproblemswemightencounterwhenwritingcodeforbothNode.jsandthebrowserandwearegoingtoproposesometoolsandpatternsthatcanhelpusintacklingthisnewandexcitingchallenge.

SharingmodulesThefirstwallwehitwhenwewanttosharesomecodebetweenthebrowserandtheserveristhemismatchbetweenthemodulesystemusedbyNode.jsandtheheterogeneouslandscapeofthemodulesystemsusedinthebrowser.Anotherproblemisthatinthebrowserwedon'thavearequire()functionorthefilesystemfromwhichwecanresolvemodules.SoifwewanttowritelargeportionsofcodethatcanworkonboththeplatformsandwewanttocontinuetousetheCommonJSmodulesystem,weneedtotakeanextrastep,weneedatooltohelpusinbundlingallthedependenciestogetheratbuildtimeandabstractingtherequire()mechanismonthebrowser.

Page 331: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UniversalModuleDefinitionInNode.js,weknowperfectlywellthattheCommonJSmodulesarethedefaultmechanismforestablishingdependenciesbetweencomponents.Thesituationinbrowser-spaceisunfortunatelywaymorefragmented:

Wemighthaveanenvironmentwithnomodulesystematall,whichmeansthatglobalsarethemainmechanismtoaccessothermodulesWemighthaveanenvironmentbasedonanAsynchronousModuleDefinition(AMD)loader,asforexample,RequireJS(http://requirejs.org)WemighthaveanenvironmentabstractingtheCommonJSmodulesystem

Luckily,thereisasetofpatternscalledUniversalModuleDefinition(UMD)thatcanhelpusabstractourcodefromthemodulesystemusedintheenvironment.

CreatinganUMDmodule

UMDisnotquitestandardizedyet,sotheremightbemanyvariationsthatdependontheneedsofthecomponentandthemodulesystemsithastosupport.However,thereisoneformthatprobablyisthemostpopularandallowsustosupportthemostcommonmodulesystems,whichareAMD,CommonJS,andbrowserglobals.

Let'sseeasimpleexampleofhowitlookslike.Inanewproject,let'screateanewmodulecalled'umdModule.js':

(function(root,factory){//[1]

if(typeofdefine==='function'&&define.amd){//[2]

define(['mustache'],factory);

}elseif(typeofmodule==='object'&&//[3]

typeofmodule.exports==='object'){

varmustache=require('mustache');

module.exports=factory(mustache);

}else{//[4]

root.UmdModule=factory(root.Mustache);

}

}(this,function(mustache){//[5]

vartemplate='<h1>Hello<i>{{name}}</i></h1>';

mustache.parse(template);

return{

sayHello:function(toWhom){

returnmustache.render(template,{name:toWhom});

}

};

}));

Theprecedingexampledefinesasimplemodulewithoneexternaldependency:

Page 332: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Mustache(http://mustache.github.io),whichisasimpletemplateengine.ThefinalproductoftheprecedingUMDmoduleisanobjectwithonemethodcalledsayHello()thatwillrenderamustachetemplateandreturnittothecaller.ThegoalofUMDisintegratingthemodulewithothermodulesystemsavailableontheenvironment.Thisishowitworks:

1. Allthecodeiswrappedinananonymousself-executingfunction,verysimilartotheRevealingModulepatternwehaveseeninChapter1,Node.jsDesignFundamentals.Thefunctionacceptsarootthatistheglobalnamespaceobjectavailableonthesystem(forexample,windowonthebrowser).Thisisneededmainlyforregisteringthedependencyasaglobalvariable,aswewillseeinamoment.Thesecondargumentisthefactory()ofthemodule,afunctionreturninganinstanceofthemoduleandacceptingitsdependenciesasinput(DependencyInjection).

2. ThefirstthingwedoischeckwhetherAMDisavailableonthesystem.Wedothisbyverifyingtheexistenceofthedefinefunctionanditsamdflag.Iffound,itmeansthatwehaveanAMDloaderonthesystem,soweproceedwithregisteringourmoduleusingdefineandrequiringthedependencymustachetobeinjectedintofactory().

3. WethencheckwhetherweareinaNode.js-flavoredCommonJSenvironmentbycheckingtheexistenceofthemoduleandmodule.exportsobjects.Ifthat'sthecase,weloadthedependenciesofthemoduleusingrequire()andweprovidethemtothefactory().Thereturnvalueofthefactoryisthenassignedtomodule.exports.

4. Lastly,ifwehaveneitherAMDnorCommonJS,weproceedwithassigningthemoduletoaglobalvariable,usingtherootobject,whichinabrowserenvironmentwillusuallybethewindowobject.Also,youcanseehowthedependency,Mustache,isexpectedtobeintheglobalscopeaswell.

5. Asafinalstep,thewrapperfunctionisself-invoked,providingthethisobjectasroot(inthebrowser,itwillbethewindowobject)andprovidingourmodulefactoryasasecondargument.Youcanseehowthefactoryacceptsitsdependenciesasarguments.

TipInthecodedistributedwiththebook,youcanfindasetofexamplesshowinghowtheUMDmodulewejustcreatedcanbeusedincombinationwithanAMDloader,aCommonJSsystem,orsimplywithnoneoftheabove(usingglobals).

ConsiderationsontheUMDpattern

TheUMDpatternisaneffectiveandsimpletechniqueusedforcreatingamodule

Page 333: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

compatiblewiththemostpopularmodulesystemsoutthere.However,wehaveseenthatitrequiresalotofboilerplate,whichcanbedifficulttotestineachenvironmentandisinevitablyerror-prone.ThismeansthatmanuallywritingtheUMDboilerplatecanmakesenseforwrappingasinglemoduleandnotasapracticetouseforeverymodulewecreateinourprojects.Itissimplyunfeasibleandimpractical.Inthesesituations,itwouldbebettertoleavethetasktosometoolthatcanhelpusautomatetheprocess,oneofthosetoolsisBrowserify,whichwewillseeinamoment.

Also,weshouldmentionthatAMD,CommonJSandbrowserglobalsarenottheonlymodulesystemsoutthere.Thepatternwehavepresentedwillcovermostoftheusecases,butitrequiresadaptationstosupportanyothermodulesystem.Forexample,theupcomingES6modulespecificationwillbesomethingthatwemightwanttosupportassoonasitgetsstandardized.

TipYoucanfindabroadlistofformalizedUMDpatternsathttps://github.com/umdjs/umd.

IntroducingBrowserifyWhenwritingaNode.jsapplication,thelastthingwewanttodoistomanuallyaddsupportforamodulesystemdifferentfromtheoneoffered,bydefault,bytheplatform.Theidealsituationwouldbecontinuingtowriteourmodulesaswehavealwaysdone,usingrequire()andmodule.exports,andthenuseatooltotransformourcodeintoabundlethatcaneasilyruninthebrowser.Luckily,thisisaproblemthathasalreadybeensolvedbymanyprojects,amongwhichBrowserify(http://browserify.org)isthemostpopularandbroadlysupported.

BrowserifyallowsustowritemodulesusingtheNode.jsmoduleconventionsandthen,thankstoacompilationstep,itcreatesabundle(asingleJavaScriptfile)thatcontainsallthedependenciesourmodulesneedforworking,includinganabstractionoftherequire()function.Thisbundlecanthenbeeasilyincludedintoawebpageandexecutedinsideabrowser.Browserifyrecursivelyscansoursourceslookingforreferencesoftherequire()function,resolving,andthenincludingthereferencedmodulesintothebundle.

TipBrowserifyisnottheonlytoolwehaveforcreatingbrowserbundlesfromNode.js

Page 334: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

modules.OtherpopularalternativesareWebmake(https://npmjs.org/package/webmake)andWebpack(https://npmjs.org/package/webpack).Also,require.jsallowsustocreatemodulesforboththeclientandNode.jsbutitusesAMDinplaceofCommonJS(http://requirejs.org/docs/node.html).

ExploringthemagicofBrowserify

Toquicklydemonstratehowthismagicworks,let'sseehowtheumdModulewecreatedintheprevioussectionlookslikeifweuseBrowserify.First,weneedtoinstallBrowserifyitself,wecandoitwithasimplecommand:

npminstallbrowserify-g

The-goptionwilltellnpmtoinstallBrowserifyglobally,sothatwecanaccessitusingasimplecommandfromtheconsole,aswewillseeinamoment.

Next,let'screateafreshprojectandlet'strytobuildamoduleequivalenttotheumdModulewecreatedbefore.ThisishowitlookslikeifwehadtoimplementitinNode.js(filesayHello.js):

varmustache=require('mustache');

vartemplate='<h1>Hello<i>{{name}}</i></h1>';

mustache.parse(template);

module.exports.sayHello=function(toWhom){

returnmustache.render(template,{name:toWhom});

};

DefinitelysimplerthanapplyingaUMDpattern,isn'tit?Now,let'screateafilecalledmain.jsthatistheentrypointofourbrowsercode:

window.addEventListener('load',function(){

varsayHello=require('./sayHello').sayHello;

varhello=sayHello('World!');

varbody=document.getElementsByTagName("body")[0];

body.innerHTML=hello;

});

Intheprecedingcode,werequirethesayHellomoduleinexactlythesamewayaswewoulddoinNode.js,sonomoreannoyancesformanagingdependenciesorconfiguringpaths,asimplerequire()doesthejob.

Next,let'smakesuretohavemustacheinstalledintheproject:

Page 335: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

npminstallmustache

Now,comesthemagicalstep.Inaterminal,let'srunthefollowingcommand:

browserifymain.js-obundle.js

Thepreviouscommandwillcompilethemainmoduleandbundlealltherequireddependenciesintoasinglefilecalledbundle.js,whichisnowreadytobeusedinthebrowser!

Toquicklytestthisassumption,let'screateanHTMLpagecalledmagic.htmlthatcontainsthefollowingcode:

<html>

<head>

<title>Browserifymagic</title>

<scriptsrc="bundle.js"></script>

</head>

<body>

</body>

</html>

Thisisenoughforrunningourcodeinthebrowser.Trytoopenthepageandseeitwithyoureyes.Boom!

TipDuringdevelopment,wesurelydon'twanttomanuallyrunbrowserifyateverychangewemaketooursources.Whatwewantinsteadisanautomaticmechanismtoregeneratethebundlewhenoursourceschange.Todothat,wecanusewatchify(https://npmjs.org/package/watchify),acompaniontoolthatwecaninstallbyrunningthefollowingcommand:

npminstallwatchify-g

Watchifycanbeusedintheexactsamewayasbrowserify,thetwotoolshaveasimilarpurposeandcommandlineoptions.Thedifferencebetweenthetwoisthatwatchify,aftercompilingthebundleforthefirsttime,willcontinuetowatchthesourcesoftheprojectsforanychangeandwillthenrebuildthebundleautomaticallybyprocessingonlythechangedfilesformaximumspeed.

TheadvantagesofusingBrowserify

Page 336: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ThemagicofBrowserifydoesn'tstophere.Thisisa(incomplete)listoffeaturesthatmakesharingcodewiththebrowserasimpleandseamlessexperience:

BrowserifyautomaticallyprovidesaversionofthecoreNode.jsmodulesthatarecompatiblewiththebrowser.Thismeansthatwecanusestreams,HTTPclients,Buffers,EventEmitter,andmanymoreinthebrowser!

TipNotethatthefsmoduleisamongthosenotsupported.

Ifwehaveamodulethatisincompatiblewiththebrowser,wecanexcludeitfromthebuild(--excludeoption),replaceitwithanemptyobject(--ignoreoption),orreplaceitwithanothermoduleprovidinganalternativeandbrowser-compatibleimplementation(byusingthe'browser'sectioninthepackage.json).Thisisacrucialfeatureandwewillhavethechancetouseitintheexamplewearegoingtoseeinawhile.BrowserifycangenerateanUMDbundlethatiscompatiblewithothermoduleloaders(--standaloneoption).Browserifyallowsustoperformadditionalprocessingofthesourcefilesusingthird-partytransforms.Thereisatransformforalmosteverythingonemightneed,fromCoffeeScriptcompilation,tosupportforloadingAMD,Bower(http://bower.io),andComponent(http://component.github.io)packagesusingrequire(),fromminificationtothecompilationandbundlingofotherassetssuchastemplatesandstylesheets.

TipYoucanfindalistofalltheavailabletransformsontheproject'swikipageathttps://github.com/substack/node-browserify/wiki/list-of-transforms.

WecaneasilyinvokeBrowserifyfromtaskmanagerssuchasGulp(https://npmjs.org/package/gulp-browserify)andGrunt(https://npmjs.org/package/grunt-browserify).

ThepowerandflexibilityofBrowserifyaresocaptivatingthatmanydevelopersstartedtouseiteventomanageonlyclient-sidecode,inplaceofmorepopularmodulesystemssuchasAMD.Thisisalsomadepossiblebythefactthatmanyclient-sidelibrariesarestartingtosupportCommonJSandnpmbydefault,opening

Page 337: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

newandinterestingscenarios.Forexample,wecaninstallJQueryasfollows:

npminstalljquery

Andthenloaditintoourcodewithasimplelineofcode:

var$=require('jquery');

Youwillbesurprisedathowmanyclient-sidelibrariesalreadysupportCommonJSandBrowserify.

TipAgreatresourceforknowingmoreaboutBrowserifyanditscapabilitiesisitsofficialhandbookthatyoucanfindonGitHubathttps://github.com/substack/browserify-handbook.

Fundamentalsofcross-platformdevelopmentWhendevelopingfordifferentplatforms,themostcommonproblemwehavetofaceissharingthecommonpartsofacomponent,whileprovidingdifferentimplementationsforthedetailsthatareplatform-specific.Wewillnowexploresomeoftheprinciplesandthepatternstousewhenfacingthischallenge.

RuntimecodebranchingThemostsimpleandintuitivetechniqueforprovidingdifferentimplementationsbasedonthehostplatformistodynamicallybranchourcode.Thisrequiresthatwehaveamechanismtorecognizeatruntimethehostplatformandthenswitchdynamicallytheimplementationwithanif-elsestatement.IfweareusingBrowserify,thisisassimpleascheckingthevariableprocess.browser,whichisautomaticallysettotruebyBrowserifywhenbundlingourmodules:

if(process.browser){

//clientsidecode

}else{

//Node.jscode

}

SomemoregenericapproachesinvolvecheckingglobalsthatareavailableonlyonNode.jsoronlyinthebrowser.Forexample,wecanchecktheexistenceofthe

Page 338: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

windowglobal:

if(window&&window.document){

//clientsidecode

}else{

//Node.jscode

}

UsingaruntimebranchingforswitchingbetweenNode.jsandbrowserimplementationisdefinitelythemostintuitiveandsimplepatternwecanuseforthepurpose;howeverithassomeinconveniences:

Thecodeforboththeplatformsisincludedinthesamemoduleandthereforealsointhefinalbundle,increasingitssizewithunreachablecode.Ifusedtooextensively,itcanconsiderablyreducethereadabilityofthecode,asbusinesslogicwouldbemixedwithlogicmeantonlytoaddcross-platformcompatibility.Usingdynamicbranchingtoloadadifferentmoduledependingontheplatformwillresultinallthemodulestobeaddedtothefinalbundleregardlessoftheirtargetplatform.Forexample,ifweconsiderthenextcodefragment,bothclientModuleandserverModulewillbeincludedinabundlegeneratedwithBrowserify,unlesswedon'texplicitlyexcludeoneofthemfromthebuild:

if(window&&window.document){

require('clientModule');

}else{

require('serverModule');

}

Thislastinconvenienceisduetothefactthatbundlershavenowayofknowingthevalueofaruntimevariableatbuild-time(unlessthevariableisaconstant),sotheyincludeanymoduleregardlessofwhetherit'srequiredfromreachableorunreachablecode.

NoteAconsequenceofthislastpropertyisthatmodulesrequireddynamicallyusingvariablesarenotincludedinthebundle.Forexample,fromthefollowingcode,nomodulewillbebundled:

moduleList.forEach(function(module){

require(module);

});

Page 339: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Build-timecodebranchingMostofthetime,wealreadyknowatbuild-timewhatcodehastobeincludedintheclientbundleandwhatshouldn't.Thismeansthatwecantakethisdecisionupfrontandinstructthebundlertoreplacetheimplementationofamoduleatbuild-time.Thisoftenresultsinaleanerbundle,asweareexcludingunnecessarymodules,andamorereadablecodebecausewedon'thavealltheif-elsestatementsrequiredbyaruntimebranching.

InBrowserify,thismoduleswappingmechanismcanbeeasilyconfiguredinaspecialsectionofthepackage.json.Forexample,considerthefollowingthreemodules:

//moduleA.js

varshowAlert=require('./alert');

//alert.js

module.exports=console.error;

//clientAlert.js

module.exports=alert;

InNode.js,moduleAisusingthedefaultimplementationofthealertmodule,whichwilllogamessagetotheconsole,inthebrowserthoughwewantaproperalertpopuptoshow.Todothat,wecaninstructBrowserifytoswapatbuildtime,theimplementationofthealert.jsmodulewithclientAlert.js.Allweneedtodoistoaddasectionsuchasthefollowingintothepackage.jsonofaproject:

"browser":{

"./alert.js":"./clientAlert.js"

}

Thiswillresultineveryreferencetothealert.jsmodulebeingreplacedwithareferencetotheclientAlert.jsmodule.Thefirstmodulewillnotevenbeincludedinthebundle.

Werealizehowbuild-timebranchingismuchmoreelegantandpowerfulthanruntimebranching.Ononeside,itallowsustocreatemodulesthatarefocusedononlyoneplatform,andontheother,itprovidesasimplemechanismtoexcludeNode.js-onlymodulesfromthefinalbundle.

Designpatternsforcross-platformdevelopmentNowthatweknowhowtoswitchbetweenNode.jsandbrowsercode,theremainingpieceofthepuzzleishowtointegratethiswithinourdesignandhowwecancreate

Page 340: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

ourcomponentsinsuchawaythatsomeoftheirpartsareinterchangeable.Thesechallengesshouldnotsoundnewtousatall,infact,allthroughoutthebookwehaveseen,analyzed,andusedpatternstoachievethisverypurpose.

Let'sremindsomeofthemanddescribehowtheyapplytocross-platformdevelopment:

StrategyandTemplate:Thesetwoareprobablythemostusefulpatternswhensharingcodewiththebrowser.Theirintentis,infact,todefinethecommonstepsofanalgorithm,allowingsomeofitspartstobereplaced,whichisexactlywhatweneed!Incross-platformdevelopment,thesepatternsallowustosharetheplatform-agnosticpartofourcomponents,whileallowingtheirplatform-specificpartstobechangedusingadifferentstrategyortemplatemethod(whichcanbechangedusingruntimeorcompile-timebranching).Adapter:Thispatternisprobablythemostusefulwhenweneedtoswapanentirecomponent.InChapter4,DesignPatterns,wehavealreadyseenanexampleofhowanentiremodule,incompatiblewiththebrowser,canbereplacedwithanadapterbuiltontopofabrowser-compatibleinterface.DoyouremembertheLevelUPadapterforthefsinterface?Proxy:Whencodemeanttorunintheserverrunsinthebrowser,weoftenexpectthingsthatliveontheservertobeavailableinthebrowseraswell.ThisiswheretheremoteProxypatterncomesintoplace.Imagineifwewantedtoaccessthefilesystemoftheserverfromthebrowser,wecouldthinkofcreatinganfsobjectontheclientthatproxieseverycalltothefsmodulelivingontheserver,usingAjaxorWebSocketsasawayofexchangingcommandsandreturnvalues.Observer:Theobserverpatternprovidesanaturalabstractionbetweenthecomponentthatemitstheeventandthosethatreceiveit.Incross-platformdevelopment,thismeansthatwecanreplacetheemitterwithitsbrowser-specificimplementationwithoutaffectingthelistenersandviceversa.DependencyInjectionandServicelocator:BothDIandservicelocatorcanbeusefultoreplacetheimplementationofamoduleatthemomentofitsinjection.

Aswecansee,thearsenalofpatternsatourdisposalisquitepowerful,butthemostpowerfulweaponisstilltheabilityofthedevelopertochoosethebestapproachandadaptittothespecificproblemathand.Inthenextsection,wearegoingtoputwhatwelearnedintoaction,leveragingsomeoftheconceptsandpatternswehaveseensofar.

SharingbusinesslogicanddatavalidationusingBackboneModels

Page 341: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Asaperfectconclusionforthissectionandchapter,wearenowgoingtoworkonanapplicationmorecomplexthanusualtodemonstratehowtoperformcodesharingbetweenNode.jsandthebrowser.Wewilltakeasexampleapersonalcontactmanagerapplicationwithverybasicfunctionalities.

Inparticular,weareonlyinterestedinsomebasicCRUDoperationssuchaslisting,creating,andremovingcontacts.Butthemainfeatureofourapplication,theonethatwearereallyinterestedinexploring,isthesharingofthemodelsbetweentheserverandtheclient.Thisisactuallyoneofthemostsoughtaftercapabilitieswhendevelopinganapplicationthathastovalidateandprocessdatabothontheclientandontheserver,whichiswhatmostofthemodernapplicationsactuallyneedtodo.

Togiveyouanidea,thisishowourapplicationshouldlooklikeonceit'scompleted:

Theplanistouseafamiliarstackontheserverwithexpressandlevelup,BackboneViews(http://backbonejs.org)ontheclient,andasetofBackboneModelssharedbetweenNode.jsandthebrowser,toimplementpersistenceandvalidation.Browserifyisourchoiceforbundlingthemodulesforthebrowser.Ifyoudon'tknowBackbone,don'tworry,theconceptswearegoingtodemonstrateherearegeneric

Page 342: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

enoughandcanbeunderstoodalsowithoutanyknowledgeofthisframework.

NoteTheprojectwearegoingtoexplorenowisprettylargetobedescribedandwritteninfullonthesepages.Pleasebeadvisedthatonlytherelevantpartswillbeshownhere.Forthefullcode,pleaserefertothesamplesdistributedwiththebook.

ImplementingthesharedmodelsLet'sstartfromthefocalcenterofourapplication,theBackbonemodelswewanttosharewiththebrowser.Inourapplication,wehavetwomodels:Contact,aBackboneModel,andContacts,aBackboneCollection.Let'sseehowtheContactmodulelookslike(themodels/Contact.jsfile):

varBackbone=require('backbone');

varvalidator=require('validator');

module.exports=Backbone.Model.extend({

defaults:{

name:'',

email:'',

phone:''

},

validate:function(attrs,options){

varerrors=[];

if(!validator.isLength(attrs.name,2)){

errors.push('Mustspecifyaname');

}

if(attrs.email&&!validator.isEmail(attrs.email)){

errors.push('Notavalidemail');

}

if(attrs.phone&&!validator.isNumeric(attrs.phone)){

errors.push('Notavalidphone');

}

if(!attrs.phone&&!attrs.email){

errors.push('Mustspecifyatleastonecontact

information');

}

returnerrors.length?errors:undefined;

},

collectionName:'contacts',

url:function(){

if(this.isNew())returnthis.collectionName;

returnthis.collectionName+'/'+this.id;

},

Page 343: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

sync:require('./modelSync')

});

Mostoftheprecedingcodeissharedbetweenthebrowserandtheserver,namely,thelogicforsettingthedefaultattributesvaluesandtheirvalidation.Boththedefaults()andvalidate()methodsarepartoftheBackboneframeworkandareoverriddentoprovidethecustomlogicforourmodel.Wealsoaddedanextrafieldtotheobject,calledcollectionName,thatwillbeusedbytheserverforpersistingthemodelintherightsublevel(wewillseethislater)andbytheclientinordertocalculatetheURLoftheRESTAPIendpoint(theurlfield).

Now,comesthebestpart:whenaBackbonemodelissaved,deleted,orfetched(usingsave(),remove(),andfetch()respectively),Backboneinternallydelegatesthetasktothesync()methodofthemodel.Soundsfamiliar?ThisisactuallyaTemplatepatternandit'sperfectforustoperformabuild-timebranchingofourcode.That'sinfactwherethemodelsmusthaveadifferentbehaviorbasedonthetargetenvironment:

Ontheserver,whensave()isinvoked,wewanttopersistamodelinthedatabase;similarly,withfetch(),wewanttoretrievethemodel'sdatafromthedatabase,andwithremove(),wewanttodeleteitOntheclientinstead,wewanteachoneofsave(),fetch(),andremove()totriggeranAJAXcalltotheserver,whichinturnexecutestherequiredoperationandreturnstheresultbacktotheclient

Implementingtheplatform-specificcodeInthecodefragmentgivenearlier,thesyncattributeisafunctionloadedfromthemodelSyncmodule,whichrepresentsourserver-sideimplementationofthemethod.Thisishowitlookslike(themodels/modelSync.jsfile):

vardb=require('../db');

varBackbone=require('backbone');

varuuid=require('node-uuid');

varself=module.exports=function(method,model,options){

switch(method){

case'create':

returnself.saveModel(model,options);

[...]

}

};

self.saveModel=function(model,options){

varcollection=db.sublevel(model.collectionName);

varresults=[];

if(!model.id)model.set('id',uuid.v4());

Page 344: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

collection.put(model.id,model.toJSON(),function(err){

if(err)returnoptions.error();

options.success(model.toJSON());

});

}

[...]

WhentheinternalsoftheBackboneModelinvokethesync()method,threeparametersareprovided,asfollows:

Themethodparameterrepresentingtheactionbeingperformed(whichcanbeoneofthefollowing:'create','read','update'or'delete')Themodelparameter,whichistheobjectoftheoperationAsetofoptionsthatcontains,amongotherthings,asuccesscallbacktobeinvokedwhentheoperationcompletesandanerrorcallbacktoinvokeifitfails

Intheprecedingcode,weareshowingwhathappenswhenwereceivea'create'request.Aswecansee,thesaveModel()functionisinvoked,whichsavesthemodelintothedatabase.

Thesync()implementationwehavejustseen,ismeanttobeexecutedonlyontheserver,wherewewanttopersistthedata.Ideally,itcouldalsoworkonthebrowser,becauseLevelUPhasadaptersforIndexedDBandLocalStorage,butthat'snotwhatwewantinthisexample.

Whatwewantinsteadistopersistthedataontheserver,andtodothiswehavetoinvokeawebservicewhenanoperationisperformedonthemodel.ThismeansthatthemodelSyncmoduleisnotgoodforustouseonthebrowser,soweneedadifferentimplementation.Luckily,Backbonealreadyprovidesadefaultimplementationforthesync()methodthatdoesexactlywhatweneed.Sothat'swhatwearegoingtouseontheclient-sideimplementationofthemodelSyncmodule(file:models/clientSync.js):

module.exports=require('backbone').sync;

That'sit,thenextstepistoinstructBrowserifytousethemodulewejustcreatedinplaceofmodelSyncwhencreatingtheclient-sidebundle.Aswehaveseen,thiscanbedoneinthepackage.jsonfile:

[...]

"browser":{

"./models/modelSync.js":"./models/clientSync.js"

[...]

}

Page 345: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theprecedingfewlinescreateabuild-timebranchingtellingBrowserifytoreplaceanyreferencetothemodule"./models/modelSync.js"withareferenceto"./models/clientSync.js".ThemodulemodelSyncwillnotbeincludedinthefinalbundle.

UsingtheisomorphicmodelsAtthispoint,theContactmoduleshouldbeisomorphic,whichmeansthatitcanruntransparentlybothontheclientandonNode.js.Toshowhowthislookslike,let'sseehowthemodelisusedintheserverroutes(fileroutes.js):

varContact=require('./models/Contact');

[...]

module.exports.createContact=function(req,res,next){

varcontact=newContact(req.body);

contact.once('invalid',function(model,errors){

res.status(400).json({error:errors});

});

contact.save({},{success:function(contact){

res.status(200).json(contact);

}});

}

[...]

ThecreateContact()handlerbuildsanewcontact(Contact)fromtheJSONdatareceivedinthebodyoftherequest(aPOSTtothe'/contacts'URL).Then,weattachtothemodelalistenerfortheinvalidevent,whichtriggerswhenitsattributesdonotpassthevalidationtestswehavedefined.Finally,contact.save()willpersistthedatainthedatabase.

Aswewillsee,thisisexactlywhatwedointhebrowser-sideoftheapplicationaswell.ThishappensintheBackboneViewresponsibleforhandlingthedatasubmittedinaform(fileclient/ContactsView.js):

varBackbone=require('backbone');

varContact=require('../models/Contact');

var$=require('jquery');

[...]

module.exports=Backbone.View.extend({

[...]

createContact:function(evt){

evt.preventDefault();

varcontactJson={

name:$('#newContactForminput[name=name]').val(),

email:$('#newContactForminput[name=email]').val(),

Page 346: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

phone:$('#newContactForminput[name=phone]').val()

};

varcontact=newContact(contactJson);//[1]

$('.error-container',this.$el).empty();

contact.once('invalid',this.invalid,this);//[2]

contact.save({},{success:function(){//[3]

this.contacts.add(contact);

}.bind(this)});

}

[...]

});

Aswecansee,whenthecreateContact()functionisinvoked(afterthe"newcontact"formissubmitted),weissuetheexactsamecommandsweusedontheserver:

1. WecreateanewContactmodelfromtheformdata.2. Weregisteralistenerfortheinvalideventsothatwecanimmediatelydisplaya

messagetotheuserifthedatadoesnotpassthevalidation.3. Finally,wesavethemodel,whichthistimewillresultinanHTTPPOSTrequestto

the/contactsURL.

Aswewantedtodemonstrate,ourContactmodelisisomorphicandenablesustoshareitsbusinesslogicandvalidationbetweenthebrowserandtheserver!

RunningtheapplicationTorunthefullsampledistributedwiththebook,don'tforgettoinstallallthemoduleswith:

npminstall

Then,runBrowserifyonthemainmoduleoftheclient-sideapplicationtogeneratethebundleusedonthebrowser:

browserifyclient/main.js-owww/bundle.js

Thenfinally,fireuptheserverwith:

nodeapp

Now,wecanopenourbrowseratthefollowingURLtoaccesstheapplication

Page 347: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

http://localhost:8080.

Wecannowverifythatthevalidationisactuallyperformedidenticallyonthebrowserasitisontheserver.Tocheckthisonthebrowser,wecansimplytrytocreateacontactwithaphonenumberthatcontainsletters,whichwillfailthevalidation.Then,totesttheserver-sidevalidation,wecantrytoinvoketheRESTAPIdirectlywithcurl:

curl-XPOSThttp://localhost:8000/contacts–data

'{"name":"John","phone":"wrong"}'--header"Content-

Type:application/json"

Theprecedingcommandshouldreturnanerrorindicatingthatthedatawearetryingtosaveisinvalid.

ThisconcludesourexplorationofthefundamentalprinciplesforsharingcodebetweenNode.jsandthebrowser.Aswehaveseen,thechallengesaremanyandtheefforttodesignisomorphiccodecanbesubstantial.Inthiscontext,it'sworthmentioningthatonebigchallengerelatedtothisareaissharedrendering,whichistheabilitytorenderaviewontheserveraswellasdynamicallyontheclient.Thisrequiresamuchmorecomplexdesigneffortthateasilyaffectstheentirearchitectureoftheapplicationonboththeserverandthebrowser.Manyframeworkstriedtosolvethisultimatechallenge,whichusuallyisthemostcomplexintheareaofcross-platformJavaScriptdevelopment.Amongthoseprojects,wecanfindDerby(http://derbyjs.com),Meteor(https://www.meteor.com),React(http://facebook.github.io/react),andthenRendr(https://github.com/rendrjs/rendr)andEzel(http://ezeljs.com),whicharebasedonBackbone,similartowhatwedidinourexample.

Page 348: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryThischapteraddedsomegreatnewweaponstoourtoolbelt,andaswecannotice,ourjourneyisgettingmorefocusedonspecificproblemsandwehavestartedtodelvedeeplyintomoreadvancedsolutions.Often,wereusedsomeofthepatternswehaveanalyzedinthepreviouschapters:State,Command,andProxytoprovideaneffectiveabstractionforasynchronouslyinitializedmodules,asynchronouscontrolflowpatternstoaddbatchingandcachingtoourAPIs,deferredexecutionandeventstohelpusrunCPU-boundtasks,andfinallyamixofvariousdesignpatternstoenableourmodulestorunseamlesslyinbothNode.jsandthebrowser.

Thischaptergaveusnotonlyasetofrecipestoreuseandcustomizeforourneeds,butalsosomegreatdemonstrationsofhowthemasteringofafewprinciplesandpatternscanhelpustacklethemostcomplexproblemsinNode.jsdevelopment.

Thenexttwochaptersrepresentthepeakofourjourney.Afterstudyingthevarioustactics,wearenowreadytomovetothestrategies,andexplorethepatternsforscalinganddistributingourNode.jsapplications.

Page 349: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter7.ScalabilityandArchitecturalPatternsInitsearlydays,Node.jswasmainlyanon-blockingwebserver;itsoriginalnamewasinfactweb.js.Itscreator,RyanDahl,soonrealizedthepotentialoftheplatformandstartedextendingitwithtoolstoenablethecreationofanytypeofserver-sideapplicationontopoftheduoJavaScript/non-blockingparadigm.ThecharacteristicsofNode.jswereperfectfortheimplementationofdistributedsystems,madeofnodesorchestratingtheiroperationsthroughthenetwork.Node.jswasborntobedistributed.Unlikeotherwebplatforms,thewordscalabilityentersthevocabularyofaNode.jsdeveloperveryearlyinthelifeofanapplication,mainlybecauseofitssingle-threadednature,incapableofexploitingalltheresourcesofamachine,butoftentherearemoreprofoundreasons.Aswewillseeinthischapter,scalinganapplicationdoesnotonlymeanincreasingitscapacity,enablingittohandlemorerequestsfaster;it'salsoacrucialpathtoachievinghighavailabilityandtolerancetoerrors.Amazingly,itcanalsobeawaytosplitthecomplexityofanapplicationintomoremanageablepieces.Scalabilityisaconceptwithmultiplefaces,sixtobeprecise,asmanyasthefacesofacube—thescalecube.

Inthischapter,wewilllearnthefollowingtopics:

WhatthescalecubeisHowtoscalebyrunningmultipleinstancesofthesameapplicationHowtoleveragealoadbalancerwhenscalinganapplicationWhataServiceRegistryisandhowitcanbeusedHowtodesignaMicroservicearchitectureoutofaMonolithicapplicationHowtointegratealargenumberofservicesthroughtheuseofsomesimplearchitecturalpatterns

AnintroductiontoapplicationscalingBeforewediveintosomepracticalpatternsandexamples,itisworthsayingafewwordsaboutthereasonsforscalinganapplicationandhowitcanbeachieved.

ScalingNode.jsapplicationsWealreadyknowthatmostofthetasksofatypicalNode.jsapplicationruninthecontextofasinglethread.InChapter1,Node.jsDesignFundamentals,welearnedthatthisisnotreallyalimitationbutratheranadvantage,becauseitallowsthe

Page 350: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

applicationtooptimizetheusageoftheresourcesnecessarytohandleconcurrentrequests,thankstothenon-blockingI/Oparadigm.Asinglethreadfullyexploitedbynon-blockingI/Oworkswonderfullyforapplicationshandlingamoderatenumberofrequestspersecond,usuallyafewhundredpersecond(thisgreatlydependsontheapplication).Assumingweareusingcommodityhardware,thecapacitythatasinglethreadcansupportislimitednomatterhowpowerfulaservercanbe,therefore,ifwewanttouseNode.jsforhigh-loadapplications,theonlywayistoscaleitacrossmultipleprocessesandmachines.

However,workloadisnottheonlyreasontoscaleaNode.jsapplication;infact,withthesametechniques,wecanobtainotherdesirablepropertiessuchasavailabilityandtolerancetofailures.Scalabilityisalsoaconceptapplicabletothesizeandthecomplexityofanapplication;infact,buildingarchitecturesthatcangrowbigisanotherimportantfactorwhendesigningsoftware.JavaScriptisatooltobeusedwithcaution,thelackoftypecheckinganditsmanygotchascanbeanobstacletothegrowthofanapplication,butwithdisciplineandanaccuratedesign,wecanturnthisintoanadvantage.WithJavaScript,weareoftenpushedtokeeptheapplicationsimpleandsplititintomanageablepieces,makingiteasiertoscaleanddistribute.

ThethreedimensionsofscalabilityWhentalkingaboutscalability,thefirstfundamentalprincipletounderstandisloaddistribution,thescienceofsplittingtheloadofanapplicationacrossseveralprocessesandmachines.Therearemanywaystoachievethis,andthebookTheArtofScalabilitybyMartinL.AbbottandMichaelT.Fisher,proposesaningeniousmodeltorepresentthem,calledthescalecube.Thismodeldescribesscalabilityintermsofthefollowingthreedimensions:

XAxis:CloningYAxis:Decomposingbyservice/functionalityZAxis:Splittingbydatapartition

Thesethreedimensionscanberepresentedasacube,asshowninthefollowingfigure:

Page 351: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thebottom-leftcornerofthecuberepresentstheapplicationshavingalltheirfunctionalitiesandservicesinasinglecodebase(monolithicapplications)andrunningonasingleinstance.Thisisacommonsituationforapplicationshandlingsmallworkloadsorattheearlystagesofdevelopment.

Themostintuitiveevolutionofamonolithic,unscaledapplicationismovingrightalongtheXaxis,whichissimple,mostofthetimeinexpensive(intermsofdevelopmentcost),andhighlyeffective.Theprinciplebehindthistechniqueiselementary—thatis,cloningthesameapplicationntimesandlettingeachinstancehandle1/nthoftheworkload.

ScalingalongtheYaxismeansdecomposingtheapplicationbasedonitsfunctionalities,services,orusecases.Inthisinstance,decomposingmeanscreatingdifferent,standaloneapplications,eachwithitsowncodebase,sometimeswithitsowndedicateddatabase,orevenwithaseparateUI.Forexample,acommonsituationisseparatingthepartofanapplicationresponsiblefortheadministrationfromthepublic-facingproduct.Anotherexampleisextractingtheservicesresponsiblefortheuserauthentication,creatingadedicatedauthenticationserver.Thecriteriatosplitanapplicationbyitsfunctionalitiesdependmostlyonitsbusinessrequirements,theusecases,thedata,andmanyotherfactors,aswewillseelaterinthischapter.Interestingly,thisisthescalingdimensionwiththebiggestrepercussions,notonlyonthearchitectureofanapplication,butalsoonthewayitismanagedfromadevelopmentperspective.Aswewillsee,microservicesisatermthatatthe

Page 352: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

momentismostcommonlyassociatedwithafine-grainedYaxisscaling.

ThelastscalingdimensionistheZaxis,wheretheapplicationissplitinsuchawaythateachinstanceisresponsibleforonlyaportionofthewholedata.Thisisatechniquemainlyusedindatabasesandalsotakesthenameofhorizontalpartitioningorsharding.Inthissetup,therearemultipleinstancesofthesameapplication,eachofthemoperatingonapartitionofthedata,whichisdeterminedusingdifferentcriteria.Forexample,wecouldpartitiontheusersofanapplicationbasedontheircountry(listpartitioning),orbasedonthestartingletteroftheirsurname(rangepartitioning),orbylettingahashfunctiondecidethepartitioneachuserbelongsto(hashpartitioning).Eachpartitioncanthenbeassignedtoaparticularinstanceofourapplication.Theuseofdatapartitionsrequireseachoperationtobeprecededbyalookupsteptodeterminewhichinstanceoftheapplicationisresponsibleforagivendatum.Aswesaid,datapartitioningisusuallyappliedandhandledatdatabaselevelbecauseitsmainpurposeisovercomingtheproblemsrelatedtohandlinglargemonolithicdatasets(limiteddiskspace,memory,andnetworkcapacity).Applyingitattheapplicationlevelisworthconsideringonlyforcomplex,distributedarchitecturesorforveryparticularusecasesas,forexample,whenbuildingapplicationsrelyingoncustomsolutionsfordatapersistence,whenusingdatabasesnotsupportingpartitioning,orwhenbuildingapplicationsatGooglescale.Consideringitscomplexity,scalinganapplicationalongtheZaxisshouldbetakeninconsiderationonlyaftertheXandYaxesofthescalecubehavebeenfullyexploited.

Inthenextsections,wewillfocusonthetwomostcommonandeffectivetechniquestoscaleNode.jsapplications,namely,cloninganddecomposingbyfunctionality/service.

Page 353: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CloningandloadbalancingTraditional,multithreadedwebserversareusuallyscaledonlywhentheresourcesassignedtoamachinecannotbeupgradedanymoreorwhendoingsowouldinvolveahighercostthansimplylaunchinganothermachine.Byusingmultiplethreads,traditionalwebserverscantakeadvantageofalltheprocessingpowerofaserver,usingalltheavailableprocessorsandmemory.However,asingleNode.jsprocessisunabletodothat,beingsingle-threadedandhavingamemorylimitof1GB(on64-bitmachines,whichcanbeincreasedtoamaximumof1.7GB).ThismeansthatNode.jsapplicationsareusuallyscaledmuchsoonercomparedtotraditionalwebservers,eveninthecontextofasinglemachine,tobeabletotakeadvantageofallitsresources.

NoteInNode.js,verticalscaling(addingmoreresourcestoasinglemachine)andhorizontalscaling(addingmoremachinestotheinfrastructure)arealmostequivalentconcepts;bothinfactinvolvesimilartechniquestoleveragealltheavailableprocessingpower.

Don'tbefooledintothinkingaboutthisasadisadvantage.Onthecontrary,beingalmostforcedtoscalehasbeneficialeffectsonotherattributesofanapplication,inparticularavailabilityandfault-tolerance.Infact,scalingaNode.jsapplicationbycloningisrelativelysimpleandit'softenimplementedevenifthereisnoneedtoharvestmoreresources,justforthepurposeofhavingaredundant,fail-tolerantsetup.

Thisalsopushesthedevelopertotakeintoaccountscalabilityfromtheearlystagesofanapplication,makingsuretheapplicationdoesnotrelyonanyresourcethatcannotbesharedacrossmultipleprocessesormachines.Infact,anabsoluteprerequisitetoscalinganapplicationisthateachinstancedoesnothavetostorecommoninformationonresourcesthatcannotbeshared,usuallyhardware,suchasmemoryordisk.Forexample,inawebserver,storingthesessiondatainmemoryorondiskisapracticethatdoesnotworkwellwithscaling;instead,usingashareddatabasewillassurethateachinstancewillhaveaccesstothesamesessioninformation,whereveritisdeployed.

Let'snowintroducethemostbasicmechanismforscalingNode.jsapplications:theclustermodule.

Page 354: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheclustermoduleInNode.js,thesimplestpatterntodistributetheloadofanapplicationacrossdifferentinstancesrunningonasinglemachineisbyusingtheclustermodule,whichispartofthecorelibraries.Theclustermodulesimplifiestheforkingofnewinstancesofthesameapplicationandautomaticallydistributesincomingconnectionsacrossthem,asshowninthefollowingfigure:

Themasterprocessisresponsibleforspawninganumberofprocesses(workers),eachrepresentinganinstanceoftheapplicationwewanttoscale.Eachincomingconnectionisthendistributedacrosstheclonedworkers,spreadingtheloadacrossthem.

NotesonthebehavioroftheclustermoduleInNode.js0.8and0.10,theclustermodulesharesthesameserversocketacrosstheworkersandleavestotheoperatingsystemthejobofload-balancingincomingconnectionsacrosstheavailableworkers.However,thereisaproblemwiththisapproach;infact,thealgorithmsusedbytheoperatingsystemtodistributetheloadacrosstheworkersarenotmeanttoload-balancenetworkrequests,butrathertoscheduletheexecutionofprocesses.Asaresult,thedistributionisnotalwaysuniformacrossalltheinstances;often,afractionofworkersreceivemostoftheload.Thistypeofbehaviorcanmakesensefortheoperatingsystemschedulerbecauseit

Page 355: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

focusesonminimizingthecontextswitchesbetweendifferentprocesses.TheshortstoryisthattheclustermoduledoesnotworkatitsfullpotentialinNode.js<=0.10.

However,thesituationchangesstartingfromversion0.11.2,whereanexplicitroundrobinload-balancingalgorithmisincludedinsidethemasterprocess,whichmakessuretherequestsareevenlydistributedacrossalltheworkers.Thenewload-balancingalgorithmisenabledbydefaultonallplatformsexceptWindows,anditcanbegloballymodifiedbysettingthevariablecluster.schedulingPolicy,usingtheconstantscluster.SCHED_RR(roundrobin)orcluster.SCHED_NONE(handledbytheoperatingsystem).

NoteTheroundrobinalgorithmdistributestheloadevenlyacrosstheavailableserversonarotationalbasis.Thefirstrequestisforwardedtothefirstserver,thesecondtothenextserverinthelist,andsoon.Whentheendofthelistisreached,theiterationstartsagainfromthebeginning.Thisisoneofthesimplestandmostusedload-balancingalgorithms;however,it'snottheonlyone.Moresophisticatedalgorithmsallowassigningpriorities,selectingtheleastloadedserverortheonewiththefastestresponsetime.

TipYoucanfindmoredetailsabouttheevolutionoftheclustermoduleinthesetwoNode.jsissues:

https://github.com/joyent/node/issues/3241https://github.com/joyent/node/issues/4435

BuildingasimpleHTTPserverLet'snowstartworkingonanexample.Let'sbuildasmallHTTPserver,clonedandload-balancedusingtheclustermodule.Firstofall,weneedanapplicationtoscale;forthisexamplewedon'tneedtoomuch,justaverybasicHTTPserver.

Let'screateafilecalledapp.jscontainingthefollowingcode:

varhttp=require('http');

varpid=process.pid;

Page 356: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

http.createServer(function(req,res){

for(vari=1e7;i>0;i--){}

console.log('Handlingrequestfrom'+pid);

res.end('Hellofrom'+pid+'\n');

}).listen(8080,function(){

console.log('Started'+pid);

});

TheHTTPserverwejustbuiltrespondstoanyrequestbysendingbackamessagecontainingitsPID;thiswillbeusefultoidentifywhichinstanceoftheapplicationishandlingtherequest.Also,tosimulatesomeactualCPUwork,weperformanemptyloop10milliontimes;withoutthis,theserverloadwouldbealmostnothingconsideringthesmallscaleofthetestswearegoingtorunforthisexample.

TipTheappmodulewewanttoscalecanbeanythingandcanalsobeimplementedusingawebframework,forexample,express.

Wecannowcheckifallworksasexpectedbyrunningtheapplicationasusualandsendingarequesttohttp://localhost:8080usingeitherabrowserorcurl.

Wecanalsotrytomeasuretherequestspersecondthattheserverisabletohandleusingonlyoneprocess,forthispurpose,wecanuseanetworkbenchmarkingtoolsuchassiege(http://www.joedog.org/siege-home)orApacheab(http://httpd.apache.org/docs/2.4/programs/ab.html):

siege-c200-t10Shttp://localhost:8080

Withab,thecommandlinewouldbeverysimilar:

ab-c200-t10http://localhost:8080/

Theprecedingcommandswillloadtheserverwith200concurrentconnectionsfor10seconds.Asareference,theresultforasystemwithfourprocessorsisintheorderof90transactionspersecond,withanaverageCPUutilizationofonly20percent.

NotePleaserememberthattheloadtestswewillperforminthischapterareintentionallysimpleandminimalandareprovidedonlyforreferenceandlearning

Page 357: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

purposes.Theirresultscannotprovidea100percentaccurateevaluationoftheperformanceofthevarioustechniquesweareanalyzing.

ScalingwiththeclustermoduleLet'snowtrytoscaleourapplicationusingtheclustermodule.Let'screateanewmodulecalledclusteredApp.js:

varcluster=require('cluster');

varos=require('os');

if(cluster.isMaster){

varcpus=os.cpus().length;

//startasmanychildrenasthenumberofCPUs

for(vari=0;i<cpus;i++){//[1]

cluster.fork();

}

}else{

require('./app');//[2]

}

Aswecansee,usingtheclustermodulerequiresverylittleeffort.Let'sanalyzewhatishappening:

1. WhenwelaunchclusteredAppfromthecommandline,weareactuallyexecutingthemasterprocess.Thecluster.isMastervariableissettotrueandtheonlyworkwearerequiredtodoisforkingthecurrentprocessusingcluster.fork().Intheprecedingexample,wearestartingasmanyworkersasthenumberofCPUsinthesystemtotakeadvantageofalltheavailableprocessingpower.

2. Whencluster.fork()isexecutedfromthemasterprocess,thecurrentmainmodule(clusteredApp)isrunagain,butthistimeinworkermode(cluster.isWorkerissettotrue,whilecluster.isMasterisfalse).Whentheapplicationrunsasaworker,itcanstartdoingsomeactualwork.Inourexample,weloadtheappmodule,whichactuallystartsanewHTTPserver.

NoteIt'simportanttorememberthateachworkerisadifferentNode.jsprocesswithitsowneventloop,memoryspace,andloadedmodules.

It'sinterestingtonoticethattheusageoftheclustermoduleisbasedonarecurringpattern,whichmakesitveryeasytorunmultipleinstancesofanapplication:

Page 358: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

if(cluster.isMaster){

//fork()

}else{

//dowork

}

TipUnderthehood,theclustermoduleusesthechild_process.fork()API(wealreadymetthisAPIinChapter6,Recipes),therefore,wealsohaveacommunicationchannelavailablebetweenthemasterandtheworkers.Theinstancesoftheworkerscanbeaccessedfromthevariable,cluster.workers,sobroadcastingamessagetoallofthemwouldbeaseasyasrunningthefollowinglinesofcode:

Object.keys(cluster.workers).forEach(function(id){

cluster.workers[id].send('Hellofromthemaster');

});

Now,let'strytorunourHTTPserverinclustermode.WecandothatbystartingtheclusteredAppmoduleasusual:

nodeclusteredApp

Ifourmachinehasmorethanoneprocessor,weshouldseeanumberofworkersbeingstartedbythemasterprocess,oneaftertheother.Forexample,inasystemwithfourprocessors,theterminalshouldlooklikethis:

Started14107

Started14099

Started14102

Started14101

IfwenowtrytohitourserveragainusingtheURLhttp://localhost:8080,weshouldnoticethateachrequestwillreturnamessagewithadifferentPID,whichmeansthattheserequestshavebeenhandledbydifferentworkers,confirmingthattheloadisbeingdistributedamongthem.

Nowwecantrytoloadtestourserveragain:

siege-c200-t10Shttp://localhost:8080

Thisway,weshouldbeabletodiscovertheperformanceincreaseobtainedby

Page 359: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

scalingourapplicationacrossmultipleprocesses.Asareference,byusingNode.js0.10inaLinuxsystemwithfourprocessors,theperformanceincreaseshouldbearound3x(270trans/secversus90trans/sec)withanaverageCPUloadof90percent.

ResiliencyandavailabilitywiththeclustermoduleAswealreadymentioned,scalinganapplicationalsobringsotheradvantages,inparticular,theabilitytomaintainacertainlevelofserviceeveninthepresenceofmalfunctionsorcrashes.Thispropertyisalsoknownasresiliencyanditcontributestowardstheavailabilityofasystem.

Bystartingmultipleinstancesofthesameapplication,wearecreatingaredundantsystem,whichmeansthatifoneinstancegoesdownforwhateverreason,westillhaveotherinstancesreadytoserverequests.Thispatternisprettystraightforwardtoimplementusingtheclustermodule.Let'sseehowitworks!

Let'stakethecodefromtheprevioussectionasthestartingpoint.Inparticular,let'smodifytheapp.jsmodulesothatitcrashesafterarandomintervaloftime:

//[...]

//Attheendofapp.js

setTimeout(function(){

thrownewError('Ooops');

},Math.ceil(Math.random()*3)*1000);

Withthischangeinplace,ourserverexitswithanerrorafterarandomnumberofsecondsbetween1and3.Inareal-lifesituation,thiswouldcauseourapplicationtostopworking,andofcourse,serverequests,unlessweusesomeexternaltooltomonitoritsstatusandrestartitautomatically.However,ifweonlyhaveoneinstance,theremaybeanon-negligibledelaybetweenrestartscausedbythestartuptimeoftheapplication.Thismeansthatduringthoserestarts,theapplicationisnotavailable.Havingmultipleinstancesinsteadwillmakesurewealwayshaveabackupsystemtoserveanincomingrequestevenwhenoneoftheworkersfails.

Withtheclustermodule,allwehavetodoisspawnanewworkerassoonaswedetectthatoneisterminatedwithanerrorcode.Let'sthenmodifythe'clusteredApp.js'moduletotakethisintoaccount:

if(cluster.isMaster){

//[...]

cluster.on('exit',function(worker,code){

if(code!=0&&!worker.suicide){

console.log('Workercrashed.Startinganewworker');

Page 360: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

cluster.fork();

}

});

}else{

require('./app');

}

Intheprecedingcode,assoonasthemasterprocessreceivesan'exit'event,wecheckwhethertheprocessisterminatedintentionallyorastheresultofanerror;wedothisbycheckingthestatuscodeandtheflagworker.suicide,whichindicateswhethertheworkerwasterminatedexplicitlybythemaster.Ifweconfirmthattheprocesswasterminatedbecauseofanerror,westartanewworker.It'sinterestingtonoticethatwhilethecrashedworkerrestarts,theotherworkerscanstillserverequests,thusnotaffectingtheavailabilityoftheapplication.

Totestthisassumption,wecantrytostressourserveragainusingsiege.Whenthestresstestcompletes,wenoticethatamongthevariousmetricsproducedbysiege,thereisalsoanindicatorthatmeasurestheavailabilityoftheapplication.Theexpectedresultwouldbesomethingsimilartothis:

Transactions:3027hits

Availability:99.31%

[...]

Failedtransactions:21

Pleasebearinmindthatthisresultcanvaryalot;itgreatlydependsonthenumberofrunninginstancesandhowmanytimestheycrashduringthetest,butitshouldgiveagoodindicatorofhowoursolutionworks.Theprecedingnumberstellusthatdespiteourapplicationisconstantlycrashing,weonlyhad21failedrequestsover3,027hits.Intheexamplescenariowebuilt,mostofthefailingrequestswillbecausedbytheinterruptionofalreadyestablishedconnectionsduringacrash.Infact,whenthishappens,siegewillprintanerrorlikethefollowing:

[error]socket:readerrorConnectionresetbypeer

sock.c:479:Connectionresetbypeer

Unfortunately,thereisverylittlewecandotopreventthesetypesoffailures,especiallywhentheapplicationterminatesbecauseofacrash.Nonetheless,oursolutionprovestobeworkinganditsavailabilityisnotbadatallforanapplicationthatcrashessooften!

Zero-downtimerestartANode.jsapplicationmightneedtoberestartedalsowhenitscodeneedstobe

Page 361: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

updated.Soalsointhisscenario,havingmultipleinstancescanhelpmaintaintheavailabilityofourapplication.

Whenwehavetointentionallyrestartanapplicationtoupdateit,thereisasmallwindowinwhichtheapplicationrestartsandisunabletoserverequests.Thiscanbeacceptableifweareupdatingourpersonalblog,butit'snotevenanoptionforaprofessionalapplicationwithanSLA(ServiceLevelAgreement)oronethatisupdatedveryoftenaspartofacontinuousdeliveryprocess.Thesolutionistoimplementazero-downtimerestartwherethecodeofanapplicationisupdatedwithoutaffectingitsavailability.

Withtheclustermodule,thisisagainaprettyeasytask,thepatternconsistsinrestartingtheworkersoneatatime.Thisway,theremainingworkerscancontinuetooperateandmaintaintheservicesoftheapplicationavailable.

Let'sthenaddthisnewfeaturetoourclusteredserver;allwehavetodoisaddsomenewcodetobeexecutedbythemasterprocess(theclusteredApp.jsfile):

if(cluster.isMaster){

//[...]

process.on('SIGUSR2',function(){//[1]

console.log('Restartingworkers');

varworkers=Object.keys(cluster.workers);

functionrestartWorker(i){//[2]

if(i>=workers.length)return;

varworker=cluster.workers[workers[i]];

console.log('Stoppingworker:'+worker.process.pid);

worker.disconnect();//[3]

worker.on('exit',function(){

if(!worker.suicide)return;

varnewWorker=cluster.fork();//[4]

newWorker.on('listening',function(){

restartWorker(i+1);//[5]

});

});

}

restartWorker(0);

});

}else{

require('./app');

}

Thisishowtheprecedingblockofcodeworks:

1. TherestartingoftheworkersistriggeredonreceivingtheSIGUSR2signal.2. WedefineaniteratorfunctioncalledrestartWorker().Thisimplementsan

Page 362: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

asynchronoussequentialiterationpatternovertheitemsofthecluster.workersobject.

3. ThefirsttaskoftherestartWorker()functionisstoppingaworkergracefullybyinvokingworker.disconnect().

4. Whentheterminatedprocessexits,wecanspawnanewworker.5. Onlywhenthenewworkerisreadyandlisteningfornewconnectionswecan

proceedwithrestartingthenextworkerbyinvokingthenextstepoftheiteration.

NoteAsourprogrammakesuseofUNIXsignals,itwillnotworkproperlyonWindowssystems.Signalsarethesimplestmechanismtoimplementoursolution.However,thisisn'ttheonlyone;infact,otherapproachesincludelisteningforacommandcomingfromasocket,apipe,orthestandardinput.

Nowwecantestourzero-downtimerestartbyrunningtheclusteredAppmoduleandthensendingaSIGUSR2signal.However,firstweneedtoobtainthePIDofthemasterprocess;thefollowingcommandcanbeusefultoidentifyitfromthelistofalltherunningprocesses:

psaf

Themasterprocessshouldbetheparentofasetofnodeprocesses.OncewehavethePIDwearelookingfor,wecansendthesignaltoit:

kill-SIGUSR2<PID>

NowtheoutputoftheclusteredAppapplicationshoulddisplaysomethinglikethis:

Restartingworkers

Stoppingworker:19389

Started19407

Stoppingworker:19390

Started19409

Wecantryagaintousesiegetoverifythatwedon'thaveanyconsiderableimpactontheavailabilityofourapplicationduringtherestartoftheworkers.

Tippm2(https://github.com/Unitech/pm2)isasmallutility,basedoncluster,which

Page 363: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

offersloadbalancing,processmonitoring,zero-downtimerestarts,andothergoodies.

DealingwithstatefulcommunicationsTheclustermoduledoesnotworkwellwithstatefulcommunicationswherethestatemaintainedbytheapplicationisnotsharedbetweenthevariousinstances.Thisisbecausedifferentrequestsbelongingtothesamestatefulsessionmaypotentiallybehandledbyadifferentinstanceoftheapplication.Thisisnotaproblemlimitedonlytotheclustermodule,butingeneralitappliestoanykindofstateless,loadbalancingalgorithm.Consider,forexample,thesituationdescribedbythefollowingfigure:

Theuserjohninitiallysendsarequesttoourapplicationtoauthenticatehimself,buttheresultoftheoperationisregisteredlocally(forexample,inmemory),soonlytheinstanceoftheapplicationthatreceivestheauthenticationrequest(InstanceA)knowsthatJohnissuccessfullyauthenticated.WhenJohnsendsanewrequest,theloadbalancermightforwardittoadifferentinstanceoftheapplication,whichactuallydoesn'tpossesstheauthenticationdetailsofjohn,hencerefusingtoperformtheoperation.Theapplicationwejustdescribedcannotbescaledasitis,butluckily,therearetwoeasysolutionswecanapplytosolvetheproblem.

Page 364: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SharingthestateacrossmultipleinstancesThefirstoptionwehavetoscaleanapplicationusingstatefulcommunicationsissharingthestateacrossalltheinstances.Thiscanbeeasilyachievedwithashareddatastore,as,forexample,adatabasesuchasPostgreSQL(http://www.postgresql.org),MongoDB(http://www.mongodb.org),orCouchDB(http://couchdb.apache.org),orevenbetter,wecanuseanin-memorystoresuchasRedis(http://redis.io)orMemcached(http://memcached.org).

Thefollowingdiagramoutlinesthissimpleandeffectivesolution:

Theonlydrawbackofusingasharedstoreforthecommunicationstateisthatit'snotalwayspossible,forexample,wemightbeusinganexistinglibrarythatkeepsthecommunicationstateinmemory;anyway,ifwehaveanexistingapplication,applyingthissolutionrequiresachangeinthecodeoftheapplication(ifit'snotalreadysupported).Aswewillseenext,thereisalessinvasivesolution.

StickyloadbalancingTheotheralternativewehavetosupportstatefulcommunicationsishavingtheloadbalancerroutingalltherequestsassociatedwithasessionalwaystothesameinstanceoftheapplication.Thistechniqueisalsocalledstickyloadbalancing.Thefollowingfigureillustratesasimplifiedscenarioinvolvingthistechnique:

Page 365: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Aswecanseefromtheprecedingfigure,whentheloadbalancerreceivesarequestassociatedtoanewsession,itcreatesamappingwithoneparticularinstanceselectedbytheload-balancingalgorithm.Thenexttimetheloadbalancerreceivesarequestfromthatsamesession,itbypassestheload-balancingalgorithm,selectingtheapplicationinstancethatwaspreviouslyassociatedtothesession.TheparticulartechniquewejustdescribedinvolvestheinspectionofthesessionIDassociatedwiththerequests(usuallyincludedinacookiebytheapplicationortheloadbalanceritself).

AsimpleralternativetoassociateastatefulconnectiontoasingleserverisbyusingtheIPaddressoftheclientperformingtherequest.Usually,theIPisprovidedtoahashfunctionthatgeneratesanIDrepresentingtheapplicationinstancedesignatedtoreceivetherequest.Thistechniquehastheadvantageofnotrequiringtheassociationtoberememberedbytheloadbalancer.Howeveritdoesn'tworkwellwithdeviceschangingtheIPfrequently,as,forexample,whenroamingondifferentnetworks.

Tip

Page 366: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Stickyloadbalancingisnotsupportedbydefaultbytheclustermodule;however,itcanbeaddedwithannpmlibrarycalledsticky-session(https://www.npmjs.org/package/sticky-session).

Onebigproblemwithstickyloadbalancingisthefactthatitnullifiesmostoftheadvantagesofhavingaredundantsystem,wherealltheinstancesoftheapplicationarethesame,andwhereaninstancecaneventuallyreplaceanotheronethatstoppedworking.Forthesereasons,therecommendationistoalwaystrytoavoidstickyloadbalancing,preferringtobuildapplicationsthatmaintainanysessionstateinasharedstoreorthatdon'trequirestatefulcommunicationsatall(forexample,byincludingthestateintherequestitself).

TipForarealexampleofalibraryrequiringastickyloadbalancing,wecanmentionSocket.io(http://socket.io/blog/introducing-socket-io-1-0/#scalability).

ScalingwithareverseproxyTheclustermoduleisnottheonlyoptionwehavetoscaleaNode.jswebapplication.Infact,moretraditionaltechniquesareoftenpreferredbecausetheyoffermorecontrolandpowerinhighlyavailableproductionenvironments.

Thealternativetousingclusteristostartmultiplestandaloneinstancesofthesameapplicationrunningondifferentportsormachines,andthenuseareverseproxy(orgateway)toprovideaccesstothoseinstances,distributingthetrafficacrossthem.Inthisconfiguration,wedon'thaveanymasterprocessdistributingrequeststoasetofworkers,butasetofdistinctprocessesrunningonthesamemachine(usingdifferentports)orscatteredacrossdifferentmachinesinsideanetwork.Toprovideasingleaccesspointtoourapplication,wecanthenuseareverseproxy,aspecialdeviceorserviceplacedbetweentheclientsandtheinstancesofourapplication,whichtakesanyrequestandforwardsittoadestinationserver,returningtheresulttotheclientasifitwasitselftheorigin.Inthisscenario,thereverseproxyisalsousedasaloadbalancer,distributingtherequestsamongtheinstancesoftheapplication.

TipForaclearexplanationofthedifferencesbetweenareverseproxyandaforward

Page 367: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

proxy,youcanrefertotheApacheHTTPserverdocumentationathttp://httpd.apache.org/docs/2.4/mod/mod_proxy.html#forwardreverse.

Thenextfigureshowsatypicalmultiprocess,multimachineconfigurationwithareverseproxyactingasaloadbalanceronthefront:

ForaNode.jsapplication,therearemanyreasonstochoosethisapproachinplaceoftheclustermodule:

Areverseproxycandistributetheloadacrossseveralmachines,notjustseveralprocessesThemostpopularreverseproxiesonthemarketsupportstickyloadbalancingAreverseproxycanroutearequesttoanyavailableserver,regardlessofitsprogramminglanguageorplatformWecanchoosemorepowerfulload-balancingalgorithmsManyreverseproxiesalsoofferotherservicessuchasURLrewrites,caching,SSLterminationpoint,oreventhefunctionalityoffull-fledgedwebserversthatcanbeused,forexample,toservestaticfiles

Thatsaid,theclustermodulecouldalsobeeasilycombinedwithareverseproxyifnecessary;forexample,usingclustertoscaleverticallyinsideasinglemachineand

Page 368: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thenusingthereverseproxytoscalehorizontallyacrossdifferentnodes.

NotePattern:useareverseproxytobalancetheloadofanapplicationacrossmultipleinstancesrunningondifferentportsormachines.

Wehavemanyoptionstoimplementaloadbalancerusingareverseproxy;popularsolutionsare:

Nginx(http://nginx.org):Thisisawebserver,reverseproxy,andloadbalancer,builtuponthenon-blockingI/Omodel.HAProxy(http://www.haproxy.org):ThisisafastloadbalancerforTCP/HTTPtraffic.Node.js-basedproxies:TherearemanysolutionsfortheimplementationofreverseproxiesandloadbalancersdirectlyinNode.js.Thismighthaveadvantagesanddisadvantages,aswewillseelater.Cloud-basedproxies:IntheeraofCloudComputing,it'snotraretoutilizealoadbalanceras-a-service.Thiscanbeconvenientbecauseitrequiresminimalmaintenance,it'susuallyhighlyscalable,andsometimes,itcansupportdynamicconfigurationstoenableon-demandscalability.

Inthenextfewsectionsofthischapter,wewillanalyzeasampleconfigurationusingNginxandlateron,wewillalsoworkonbuildingourveryownloadbalancerusingnothingbutNode.js!

LoadbalancingwithNginxTogiveanideaofhowadedicatedreverseproxieswork,wewillnowbuildascalablearchitecturebasedonNginx(http://nginx.org);butfirstweneedtoinstallit.Wecandothatbyfollowingtheinstructionsathttp://nginx.org/en/docs/install.html.

TipOnalatestUbuntusystem,youcanquicklyinstallNginxwiththecommand:

sudoapt-getinstallnginx

OnMacOSX,youcanusebrew(http://brew.sh):

Page 369: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

brewinstallnginx

Aswearenotgoingtouseclustertostartmultipleinstancesofourserver,weneedtoslightlymodifythecodeofourapplicationsothatwecanspecifythelisteningportusingacommandlineargument.Thiswillallowustolaunchmultipleinstancesondifferentports.Let'sthenconsideragainthemainmoduleofourexampleapplication(app.js):

varhttp=require('http');

varpid=process.pid;

http.createServer(function(req,res){

for(vari=1e7;i>0;i--){}

console.log('Handlingrequestfrom'+pid);

res.end('Hellofrom'+pid+'\n');

}).listen(process.env.PORT||process.argv[2]||8080,

function(){

console.log('Started'+pid);

});

Thetinychangeishighlightedintheprecedingcode.

Anotherimportantfeaturewelackbynotusingclusteristheautomaticrestartincaseofacrash.Luckilythisiseasytofixbyusingadedicatedsupervisor,whichisanexternalprocessmonitoringourapplicationandrestartingitifnecessary.Possiblechoicesare:

Node.js-basedsupervisorssuchasforever(https://npmjs.org/package/forever)orpm2(https://npmjs.org/package/pm2)OS-basedmonitorssuchasUpstart(http://upstart.ubuntu.com)orSystemd(http://freedesktop.org/wiki/Software/systemd)MoreadvancedmonitoringsolutionssuchasMonit(http://mmonit.com/monit)

Forthisexample,wearegoingtouseforever,whichisthesimplestandmostimmediateforustouse.Wecaninstallitgloballybyrunningthefollowingcommand:

npminstallforever-g

Thenextstepistostartthefourinstancesofourapplication,allondifferentportsandsupervisedbyforever:

foreverstartapp.js8081

foreverstartapp.js8082

foreverstartapp.js8083

foreverstartapp.js8084

Page 370: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Wecancheckthelistofthestartedprocessesusingthecommand:

foreverlist

Nowit'stimetoconfiguretheNginxserverasaloadbalancer.

First,weneedtoidentifythelocationofthenginx.conffilethatcanbefoundinoneofthefollowinglocations,dependingonyoursystem/usr/local/nginx/conf,/etc/nginx,or/usr/local/etc/nginx.

Next,let'sopenthenginx.conffileandapplythefollowingconfiguration,whichistheveryminimumrequiredtogetaworkingloadbalancer:

http{

#[...]

upstreamnodejs_design_patterns_app{

server127.0.0.1:8081;

server127.0.0.1:8082;

server127.0.0.1:8083;

server127.0.0.1:8084;

}

#[...]

server{

listen80;

location/{

proxy_passhttp://nodejs_design_patterns_app;

}

}

#[...]

}

Theconfigurationneedsverylittleexplanation.Intheupstreamnodejs_design_patterns_appsection,wearedefiningalistofthebackendserversusedtohandlethenetworkrequests,andthen,intheserversection,wespecifytheproxy_passdirective,whichessentiallytellsNginxtoforwardanyrequesttotheservergroupwedefinedbefore(nodejs_design_patterns_app).That'sit,nowweonlyneedtoreloadtheNginxconfigurationwiththecommand:

nginx-sreload

Oursystemshouldnowbeup-and-running,readytoacceptrequestsandbalancethetrafficacrossthefourinstancesofourNode.jsapplication.Simplypointyourbrowsertotheaddresshttp://localhosttoseehowthetrafficisbalancedbyourNginxserver.

Page 371: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UsingaServiceRegistryOneimportantadvantageofmoderncloud-basedinfrastructuresistheabilitytodynamicallyadjustthecapacityofanapplicationbasedonthecurrentorpredictedtraffic;thisisalsoknownasdynamicscaling.Ifimplementedproperly,thispracticecanreducethecostoftheITinfrastructureenormouslywhilestillmaintainingtheapplicationhighlyavailableandresponsive.

Theideaissimple;ifourapplicationisexperiencingaperformancedegradationcausedbyapeakinthetraffic,weautomaticallyspawnnewserverstocopewiththeincreasedload.Wecouldalsodecidetoshutdownsomeserversduringcertainhours,as,forexample,atnight,whenweknowthatthetrafficwillbeless,andrestartingthemagaininthemorning.Thismechanismrequirestheloadbalancertoalwaysbeup-to-datewiththecurrentnetworktopology,knowingatanytimewhichserverisup.

AcommonpatterntosolvethisproblemistouseacentralrepositorycalledServiceRegistry,whichkeepstrackoftherunningserversandtheservicestheyprovide.Thenextfigureshowsamultiservicearchitecturewithaloadbalanceronthefront,dynamicallyconfiguredusingaServiceRegistry:

Page 372: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theprecedingarchitectureassumesthepresenceoftwoservices,APIandWebApp.Theloadbalancerdistributestherequestsarrivingonthe/apiendpointtoalltheserversimplementingtheAPIservice,whiletherestoftherequestsarespreadacrosstheserversimplementingtheWebAppservice.Theloadbalancerobtainsthelistofserversusingtheserviceregistry.

Forthistoworkincompleteautomation,eachapplicationinstancehastoregisteritselftotheserviceregistrythemomentitcomesuponlineandunregisteritselfwhenitstops.Thisway,theloadbalancercanalwayshaveanup-to-dateviewoftheserversandtheservicesavailableinthenetwork.

NotePattern(serviceregistry):useacentralrepositorytostoreanalwaysup-to-dateviewoftheserversandtheservicesavailableinasystem.

Page 373: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thispatterncanbeappliednotonlytoloadbalancing,butalsomoregenerallyasawaytodecoupleaservicetypefromtheserversprovidingit.WecanlookatitasaServiceLocatordesignpatternappliedtonetworkservices.

Implementingadynamicloadbalancerwithhttp-proxyandseaportTosupportadynamicnetworkinfrastructure,wecanuseareverseproxysuchasNginxorHAProxy;allweneedtodoisupdatetheirconfigurationusinganautomatedserviceandthenforcetheloadbalancertopickthechanges.ForNginx,thiscanbedoneusingthefollowingcommandline:

nginx-sreload

Thesameresultcanbeachievedwithacloud-basedsolution,butwehaveathirdandmorefamiliaralternativethatmakesuseofourfavoriteplatform.

WeallknowthatNode.jsisagreattooltobuildanysortofnetworkapplication;aswesaid,thisisexactlyoneofitsmaindesigngoals.So,whynotbuildaloadbalancerusingnothingbutNode.js?Thiswouldgiveusmuchmorefreedomandpower,andwouldallowustoimplementanysortofpatternoralgorithmstraightintoourcustom-builtloadbalancer,includingtheonewearenowgoingtoexplore,dynamicloadbalancingusingaServiceRegistry.

Forthisexample,wewanttoreplicatethemultiservicearchitecturewesawinthefigureoftheprevioussection,andtodothat,wearegoingtomainlyusetwonpmpackages:

http-proxy(https://npmjs.org/package/http-proxy):ThisisalibrarytosimplifythecreationofproxiesandloadbalancersinNode.jsseaport(https://npmjs.org/package/seaport):ThisisaminimalistServiceRegistrywritteninNode.js

Let'sstartbyimplementingourservices.TheyaresimpleHTTPserversastheoneswehaveusedsofartotestclusterandNginx,butthistimewewanteachservertoregisteritselfintotheServiceRegistrythemomentitstarts.

Let'sseehowthislooks(fileapp.js):

varhttp=require('http');

varpid=process.pid;

varseaport=require('seaport').connect('localhost',9090);

varserviceType=process.argv[2];

varport=seaport.register(serviceType);

Page 374: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

http.createServer(function(req,res){

for(vari=1e7;i>0;i--){}

console.log('Handlingrequestfrom'+pid);

res.end(serviceType+'responsefrom'+pid+'\n');

}).listen(port,function(){

console.log('Started'+pid);

});

Intheprecedingcode,therearethreelinesofcodethatdeserveourattention:

1. First,weinitializetheseaportclientandconnectittotheregistryserver,listeningonport9090.

2. Next,wereadfromthecommandlineaserviceType,sowecanstartaserverbychoosingtheserviceitprovides.Thisisonlyforourconvenience,toallowustosimulateamultiservicesetupwithoutimplementingmultipleservers.

3. Finally,weregistertheserviceusingseaport.register().ThisalsoreturnsaportnumberthatweusetobindtheHTTPserver.

TheregistrywillautomaticallyunregistertheservicewhenitlosestheconnectiontotheHTTPserver.Thismeansthatwedon'tneedtomanuallydoit;theserverwillsimplydisappearfromtheregistryassoonasitstops.

Nowit'stimetoimplementtheloadbalancer.Let'sdothatbycreatinganewmodulecalled'loadBalancer.js'.First,weneedtodefinearoutingtabletomapURLpathstoservices:

varrouting=[{

path:'/api',

service:'api-service',

index:0

},{

path:'/',

service:'webapp-service',

index:0

}];

Eachitemintheroutingarraycontainstheserviceusedtohandletherequestsarrivingonthemappedpath.Theindexpropertywillbeusedtoroundrobintherequestsofagivenservice.

Let'sseehowthisworksbyimplementingthesecondpartofloadbalancer.js:

varhttpProxy=require('http-proxy');

varseaport=require('seaport').connect('localhost',9090);

//[1]

Page 375: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

varproxy=httpProxy.createProxyServer({});

require('http').createServer(function(req,res){

varroute;

routing.some(function(entry){//[2]

route=entry;

//Startswiththeroutepath?

returnreq.url.indexOf(route.path)===0;

});

varservers=seaport.query(route.service);//[3]

if(!servers.length){

res.writeHead(502);

returnres.end('Badgateway');

}

route.index=(route.index+1)%servers.length;//[4]

proxy.web(req,res,{target:servers[route.index]});

}).listen(8080,function(){console.log('Started');});

ThisishowweimplementedourNode.js-basedloadbalancer:

1. First,weneedtoconnecttotheseaportserversothatwecanhaveaccesstotheregistry.Next,weinstantiateanhttp-proxyobjectandstartanormalwebserver.

2. Intherequesthandleroftheserver,thefirstthingwedoismatchtheURLagainstourroutingtable.Theresultwillbeadescriptorcontainingtheservicename.

3. Weobtainfromseaportthelistofserversimplementingtherequiredservice.Ifthislistisempty,wereturnanerrortotheclient.Formaximumspeed,seaportcachestheregistrylocally,andkeepsitup-to-datebysynchronizingitwiththemainregistryserver.That'swhyseaport.query()isasynchronouscall.

4. Atlast,wecanroutetherequesttoitsdestination.Weupdateroute.indextopointtothenextserverinthelist,followingaroundrobinapproach.Wethenusetheindextoselectaserverfromthelist,passingittoproxy.web()alongwiththerequest(req)andtheresponse(res)objects.Thiswillsimplyforwardtherequesttotheserverwechose.

ItisnowclearhowsimpleitistoimplementaloadbalancerusingonlyNode.jsandhowmuchflexibilitywecanhavebydoingso.Nowweshouldbereadytogiveitago,butfirst,let'sinstalltheseaportserverbyrunningthefollowingcommand:

npminstallseaport-g

Thisallowsustostarttheseaportserviceregistrywiththissimplecommandline:

seaportlisten9090

Page 376: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Nowwearereadytostarttheloadbalancer:

nodeloadBalancer

Nowifwetrytoaccesssomeoftheservicesexposedbytheloadbalancer,wewillnoticethatitreturnsanHTTP502error,becausewedidn'tstartanyserveryet.Tryityourself:

curllocalhost:8080/api

Theprecedingcommandshouldreturnthefollowingoutput:

BadGateway

Thesituationwillchangeifwespawnsomeinstancesofourservices,forexample,twoapi-serviceandonewebapp-service:

foreverstartapp.jsapi-service

foreverstartapp.jsapi-service

foreverstartapp.jswebapp-service

Nowtheloadbalancershouldautomaticallyseethenewserversandstartdistributingrequestsacrossthem.Let'stryagainwiththefollowingcommand:

curllocalhost:8080/api

Theprecedingcommandshouldnowreturnthis:

api-serviceresponsefrom6972

Byrunningitagain,weshouldnowreceiveamessagefromanotherserver,confirmingthattherequestsarebeingdistributedevenlyamongthedifferentservers:

api-serviceresponsefrom6979

Theadvantagesofthispatternareimmediate.Wecannowscaleourinfrastructuredynamically,on-demand,orbasedonaschedule,andourloadbalancerwillautomaticallyadjustwiththenewconfigurationwithoutanyextraeffort!

Peer-to-peerloadbalancing

Page 377: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

UsingareverseproxyisalmostanecessitywhenwewanttoexposeacomplexinternalnetworkarchitecturetoapublicnetworksuchastheInternet.Ithelpshidethecomplexity,providingasingleaccesspointthatexternalapplicationscaneasilyuseandrelyon.However,ifweneedtoscaleaservicethatisforinternaluseonly,wecanhavemuchmoreflexibilityandcontrol.

Let'simaginehavingaServiceAwhichreliesonaServiceBtoimplementitsfunctionality.ServiceBisscaledacrossmultiplemachinesandit'savailableonlyintheinternalnetwork.WhatwehavelearnedsofaristhatServiceAwillconnecttoServiceBusingareverseproxy,whichwilldistributethetraffictoalltheserversimplementingServiceB.

However,thereisanalternative.Wecanremovethereverseproxyfromthepictureanddistributetherequestsdirectlyfromtheclient(ServiceA),whichnowbecomesdirectlyresponsibleforloadbalancingitsconnectionsacrossthevariousinstancesofServiceB.ThisispossibleonlyifServerAknowsthedetailsabouttheserversexposingServiceB,andinaninternalnetwork,thisisusuallyknowninformation.Withthisapproachweareessentiallyimplementingpeer-to-peerloadbalancing.

Thefollowingdiagramcomparesthetwoalternativeswejustdescribed:

Page 378: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thisisanextremelysimpleandeffectivepatternthatenablestrulydistributedcommunicationswithoutbottlenecksorsinglepointsoffailure.Besidesthat,italsodoesthefollowing:

ReducestheinfrastructurecomplexitybyremovinganetworknodeAllowsfastercommunications,becausemessageswilltravelthroughonefewernodeScalesbetter,becauseperformancesarenotlimitedbywhattheloadbalancercanhandle

Ontheotherside,byremovingthereverseproxy,weareactuallyexposingthecomplexityofitsunderlyinginfrastructure.Also,eachclienthastobesmarterbyimplementingaload-balancingalgorithmandpossibly,alsoawaytokeepitsknowledgeoftheinfrastructureup-to-date.

TipPeer-to-peerloadbalancingisapatternusedextensivelyintheØMQ(http://zeromq.org)library.

ImplementinganHTTPclientthatcanbalancerequestsacrossmultipleserversWealreadyknowhowtoimplementaloadbalancerusingonlyNode.jsanddistributeincomingrequestsacrosstheavailableservers,soimplementingthesamemechanismontheclientsideshouldnotbethatdifferent.AllwehavetodoinfactiswraptheclientAPIandaugmentitwithaload-balancingmechanism.Takealookatthefollowingmodule(balancedRequest.js):

varhttp=require('http');

varservers=[

{host:'localhost',port:'8081'},

{host:'localhost',port:'8082'}

];

vari=0;

module.exports=function(options,callback){

i=(i+1)%servers.length;

options.hostname=servers[i].host;

options.port=servers[i].port;

returnhttp.request(options,callback);

};

Page 379: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theprecedingcodeisverysimpleandneedslittleexplanation.Wewrappedtheoriginalhttp.requestAPIsothatitoverrideshostnameandportoftherequestwiththoseselectedfromthelistofavailableserversusingaroundrobinalgorithm.

ThenewwrappedAPIcanthenbeusedseamlessly(client.js):

varrequest=require('./balancedRequest');

for(vari=10;i>=0;i--){

request({method:'GET',path:'/'},function(res){

varstr='';

res.on('data',function(chunk){

str+=chunk;

}).on('end',function(){

console.log(str);

});

}).end();

}

Totrytheprecedingcode,wehavetostarttwoinstancesofthesampleserverprovided:

nodeapp8081

nodeapp8082

Followedbytheclientapplicationwejustbuilt:

nodeclient

Weshouldnoticehoweachrequestissenttoadifferentserver,confirmingthatwearenowabletobalancetheloadwithoutadedicatedreverseproxy!

TipAnimprovementtothewrapperwecreatedbeforewouldbetointegrateaServiceRegistrydirectlyintotheclientandobtaintheserverlistdynamically.Youcanfindanexampleofthistechniqueinthecodedistributedwiththebook.

Page 380: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

DecomposingcomplexapplicationsSofarinthechapter,wehavemainlyfocusedouranalysisontheXaxisofthescalecube.Wesawhowitrepresentstheeasiestandmostimmediatewaytodistributetheloadofanapplication,alsoimprovingitsavailability.Inthefollowingsection,wearenowgoingtofocusontheYaxisofthescalecube,whereapplicationsarescaledbydecomposingthembyfunctionalityandservice.Aswewilllearn,thistechniqueallowstoscalenotonlythecapacityofanapplication,butalso,andmostimportantly,itscomplexity.

MonolithicarchitectureThetermmonolithicmightmakeusthinkofasystemwithoutmodularity,wherealltheservicesofanapplicationareinterconnectedtogetherandalmostindistinguishable.However,thisisnotalwaysthecase.Often,monolithicsystemshaveahighlymodulararchitectureandagooddecouplingbetweentheirinternalcomponents.

AperfectexampleistheLinuxOperatingSystemkernel,whichispartofacategorycalledmonolithickernels(inperfectoppositionwithitsecosystemandtheUnixphilosophy).Linuxhasthousandsofservicesandmodulesthatwecanloadandunloaddynamicallyevenwhilethesystemisrunning.However,theyallruninkernelmode,whichmeansthatafailureinanyofthemmightbringtheentireOSdown(haveyoueverseenakernelpanic?).Thisapproachisopposedtothemicrokernelarchitecture,whereonlythecoreservicesoftheoperatingsystemruninkernelmode,whiletherestisrunninginusermode,usuallyeachonewithitsownprocess.Themainadvantageofthisapproachisthataprobleminanyoftheseserviceswouldmorelikelycauseittocrashinisolationinsteadofaffectingthestabilityoftheentiresystem.

NoteTheTorvalds-Tanenbaumdebateonkerneldesignisprobablyoneofthemostfamousflamewarsinthehistoryofcomputerscience,whereoneofthemainpointsofdisputewasexactlymonolithicversusmicrokerneldesign.Youcanfindawebversionofthediscussion(itoriginallyappearedonUsenet)athttps://groups.google.com/d/msg/comp.os.minix/wlhw16QWltI/P8isWhZ8PJ8J.

It'sremarkablehowthesedesignprinciples,morethan30yearsold,canstillbeappliedtodayandintotallydifferentenvironments.Modernmonolithicapplicationsare

Page 381: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

comparabletomonolithickernels;ifanyoftheircomponentsfail,theentiresystemisaffected,whichtranslatedintoNode.jstermsmeansthatalltheservicesarepartofthesamecodebaseandruninasingleprocess(whennotcloned).

Tomakeanexampleofamonolithicarchitecture,let'stakealookatthefollowingfigure:

Theprecedingfigureshowsthearchitectureofatypicale-commerceapplication.Itsstructureismodular;wehavetwodifferentfrontends,oneforthemainstoreandanotherfortheadministrationinterface.Internally,wehaveaclearseparationoftheservicesimplementedbytheapplication,eachoneresponsibleforaspecificportionofitsbusinesslogic:Products,Cart,Checkout,Search,andAuthenticationandUsers.However,theprecedingarchitectureismonolithic,everymodule,infact,ispartofthesamecodebaseandrunsaspartofasingleapplication.Afailureinanyofitscomponents,forexample,anuncaughtexception,canpotentiallyteardowntheentireonlinestore.

Anotherproblemwiththistypeofarchitectureistheinterconnectionbetweenitsmodules;thefactthattheyallliveinsidethesameapplicationmakesitveryeasyforadevelopertobuildinteractionsandcouplingbetweenmodules.Forexample,considertheusecasewhenaproductisbeingpurchased;theCheckoutmodulehastoupdatetheavailabilityoftheProductobject,andifthosetwomodulesareinthesameapplication,it'stooeasyforadevelopertojustobtainareferencetoaProductobjectandupdateitsavailabilitydirectly.Maintainingalowcouplingbetweeninternalmodulesisveryhardinmonolithicapplication,partlybecausetheboundariesbetween

Page 382: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

themarenotalwaysclearorproperlyenforced.

Ahighcouplingisoftenoneofthemainobstaclestothegrowthofanapplicationandpreventsitsscalabilityintermsofcomplexity.Infact,anintricatedependencygraphmeansthateverypartofthesystemisaliability;ithastobemaintainedfortheentirelifeoftheproduct,andanychangeshouldbecarefullyevaluatedbecauseeverycomponentislikeawoodenblockinaJengatower,movingorremovingoneofthemcancausetheentiretowertocollapse.Thisoftenresultsinthebuildingofconventionsanddevelopmentprocessestocopewiththeincreasingcomplexityoftheproject.

TheMicroservicearchitectureNowwearegoingtorevealthemostimportantpatterninNode.jstowritebigapplications:avoidwritingbigapplications.Thisseemslikeatrivialstatement,butit'sanincrediblyeffectivestrategytoscaleboththecomplexityandthecapacityofasoftwaresystem.Sowhat'sthealternativetowritingbigapplications?TheanswerisintheYaxisofthescalecube,decompositionandsplittingbyserviceandfunctionality.Theideaistobreakdownanapplicationintoitsessentialcomponents,creatingseparate,independentapplications.Itispracticallytheoppositeofthemonolithicarchitecture.ThisfitsperfectlywiththeUnixphilosophy,andtheNode.jsprincipleswediscussedinthebeginningofthebook,inparticular"makeeachprogramdoonethingwell".

TheMicroservicearchitectureisprobablytodaythereferencepatternforthistypeofapproach,whereasetofself-sufficientservicesreplacesbigmonolithicapplications.Theprefixmicromeansthattheservicesshouldbeassmallaspossible,butalwayswithinreasonablelimits.Don'tbemisledbythinkingthatcreatinganarchitecturewithahundreddifferentapplicationsexposingonlyonewebserviceisnecessarilyagoodchoice.Inreality,thereisnostrictruleonhowsmallorbigaserviceshouldbe,it'snotthesizethatmattersinthedesignofaMicroservicearchitecture;instead,it'sacombinationofdifferentfactors,mainlyloosecoupling,highcohesion,andintegrationcomplexity.

AnexampleoftheMicroservicearchitectureLet'snowseehowthemonolithice-commerceapplicationwouldlooklike,usingaMicroservicearchitecture:

Page 383: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Aswecanseefromthepreviousfigure,eachfundamentalcomponentofthee-commerceapplicationisnowaself-sustainingandindependententity,livinginitsowncontext,withitsowndatabase.Inpractice,theyareallindependentapplicationsexposingasetofrelatedservices(highcohesion).

ThedataownershipofaserviceisanimportantcharacteristicoftheMicroservicearchitecture.Thisiswhythedatabasealsohastobesplittomaintaintheproperlevelofisolationandindependence.Ifauniqueshareddatabaseisused,itwouldbecomemucheasierfortheservicestoworktogether;however,thiswouldalsointroduceacouplingbetweentheservices(basedondata),nullifyingsomeoftheadvantagesofhavingdifferentapplications.

Thedashedlineconnectingallthenodestellsusthat,insomeway,theyhavetocommunicateandexchangeinformationfortheentiresystemtobefullyfunctional.Astheservicesdonotsharethesamedatabase,thereismorecommunicationinvolvedtomaintaintheconsistencyofthewholesystem.Forexample,theCheckoutapplicationneedstoknowsomeinformationaboutProducts,suchasthepriceandrestrictionsonshipping,andatthesametime,itneedstoupdatethedatastoredin

Page 384: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

theProductsservice,forexample,theproductavailabilitywhenthecheckoutiscomplete.Intheprecedingfigure,wetriedtokeepthewaythenodescommunicateabstract.Surely,themostpopularstrategyisusingwebservices,butaswewillseelater,thisisnottheonlyoption.

NotePattern(microservicearchitecture):splitacomplexapplicationbycreatingseveralsmall,self-containedservices.

ProsandconsofmicroservicesInthissectionwearegoingtohighlightsomeoftheadvantagesanddisadvantagesofimplementingtheMicroservicearchitecture.Aswewillsee,thisapproachpromisestobringaradicalchangeinthewaywedevelopourapplications,revolutionizingthewayweseescalabilityandcomplexity,butontheotherside,itintroducesnewnontrivialchallengesaswell.

NoteMartinFowlerwroteagreatarticleaboutmicroservicesthatyoucanfindathttp://martinfowler.com/articles/microservices.html.

Everyserviceisexpendable

Themaintechnicaladvantageofhavingeachservicelivinginitsownapplicationcontextisthatcrashes,bugs,andbreakingchangesdonotpropagatetotheentiresystem.Thegoalistobuildtrulyindependentservicesthataresmaller,easiertochange,orevenrebuildfromscratch.If,forexample,theCheckoutserviceofoure-commerceapplicationsuddenlycrashesbecauseofaseriousbug,therestofthesystemwouldcontinuetoworkasnormal.Somefunctionalitymaybeaffected,forexample,theabilitytopurchaseaproduct,buttherestofthesystemwouldcontinuetowork.

Also,imagineifwesuddenlyrealizedthatthedatabaseortheprogramminglanguageweusedtoimplementacomponentwasnotagooddesigndecision.Inamonolithicapplication,therewouldbeverylittlewecoulddotochangethingswithoutaffectingtheentiresystem;instead,inaMicroservicearchitecture,wecouldmoreeasilyre-implementtheentireservicefromscratch,usingadifferentdatabaseorplatform,andtherestofthesystemwouldnotevennoticeit.

Page 385: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Reusabilityacrossplatformsandlanguages

Splittingabigmonolithicapplicationintomanysmallservicesallowsustocreateindependentunitsthatcanbere-usedmuchmoreeasily.Elasticsearch(http://www.elasticsearch.org)isagreatexampleofare-usablesearchservice,alsotheauthenticationserverwebuiltinChapter5,WiringModules,isanotherexampleofaservicethatcanbeeasilyre-usedinanyapplication,regardlessoftheprogramminglanguageit'sbuiltin.

Themainadvantageisthatthelevelofinformationhidingisusuallymuchhighercomparedtomonolithicapplications.Thisispossiblebecausetheinteractionsusuallyhappenthrougharemoteinterfacesuchasawebserviceoramessagebroker,whichmakesitmucheasiertohidetheimplementationdetailsandshieldtheclientfromchangesinthewaytheserviceisimplementedordeployed.Forexample,ifallwehavetodoisinvokeawebservice,weareshieldedfromthewaytheinfrastructurebehindisscaled,fromwhatprogramminglanguageituses,fromwhatdatabaseitusestostoreitsdata,andsoon.

Awaytoscaletheapplication

Goingbacktothescalecube,it'sclearthatmicroservicesareequivalenttoscalinganapplicationalongtheYaxis,soit'salreadyameansforthedistributionoftheloadacrossmultiplemachines.Also,weshouldnotforgetthatwecancombinemicroserviceswiththeothertwodimensionsofthecubetoscaletheapplicationevenfurther.Forexample,eachservicecouldbeclonedtohandlemoretraffic,andtheinterestingaspectisthattheycanbescaledindependently,allowingbetterresourcemanagement.

Thechallengesofmicroservices

Atthispoint,itwouldlooklikemicroservicesarethesolutiontoallourproblems;however,thisisfarfrombeingtrue.Infact,havingmorenodestomanageintroducesahighercomplexityintermsofintegration,deployment,andcodesharing;itfixessomeofthepainsoftraditionalarchitecturesbutitalsoopensmanynewquestions.Howdowemaketheservicesinteract?Howcanwedeploy,scale,andmonitorsuchahighnumberofapplications?Howcanweshareandreusecodebetweenservices?Fortunately,cloudservicesandmodernDevOpsmethodologiescanprovidesomeanswerstothosequestions,andalso,Node.jscanhelpalot.Itsmodulesystemisaperfectcompaniontosharecodebetweendifferentprojects.Node.jswasmadetobeanodeinadistributedsystemsuchasthoseimplementedusingtheMicroservicearchitecture.

Tip

Page 386: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Althoughmicroservicescanbebuiltusinganyframework(orevenjustthecoreNode.jsmodules),thereareafewsolutionsspecializedforthispurpose,amongthemostnotablewehaveSeneca(https://npmjs.org/package/seneca).Ausefultooltomanagethedeploymentofmicroservicesisnscale(https://github.com/nearform/nscale).

IntegrationpatternsinaMicroservicearchitectureOneofthetoughestchallengesofmicroservicesisconnectingallthenodestogethertomakethemcollaborate.Forexample,theCartserviceofoure-commerceapplicationwouldmakelittlesensewithoutsomeProductstoadd,andtheCheckoutservicewouldbeuselesswithoutalistofproductstobuy(acart).Aswealreadymentioned,therearealsootherfactorsthatnecessitateaninteractionbetweenthevariousservices.Forexample,theSearchservicehastoknowwhichProductsareavailableandmustalsoensuretokeepitsinformationup-to-date.ThesamecanbesaidabouttheCheckoutservicethathastoupdatetheinformationaboutProductavailabilitywhenapurchaseiscompleted.

Whendesigninganintegrationstrategy,it'salsoimportanttoconsiderthecouplingthatit'sgoingtointroducebetweentheservicesinthesystem.Weshouldnotforgetthatdesigningadistributedarchitectureinvolvesthesamepracticesandprinciplesthatweuselocallywhendesigningamoduleorsubsystem,therefore,wealsoneedtotakeintoconsiderationpropertiessuchasthereusabilityandextensibilityoftheservice.

TheAPIproxyThefirstpatternwearegoingtoshowmakesuseofanAPIproxy,aserverthatproxiesthecommunicationsbetweenaclientandasetofremoteAPI.IntheMicroservicearchitecture,itsmainpurposeistoprovideasingleaccesspointformultipleAPIendpoints,butitcanalsoofferloadbalancing,caching,authentication,andtrafficlimiting,allfeaturesthatproveouttobeveryusefultoimplementasolidAPIsolution.

Thispatternshouldnotbenewtous;wealreadysawitinactionwhenwebuiltthecustomloadbalancerwithhttp-proxyandseaport.Forthatexample,ourloadbalancerwasexposingonlytwoservices,andthen,thankstoaServiceRegistry,itwasabletomapaURLpathtoaserviceandhencetoalistofservers.AnAPIproxyworksinthesameway;itisessentiallyareverseproxyandoftenalsoaloadbalancer,specificallyconfiguredtohandleAPIrequests.Thenextfigureshowshow

Page 387: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

wecanapplysuchasolutiontooure-commerceapplication:

Fromtheprecedingfigure,itshouldbeclearhowanAPIproxycanhidethecomplexityofitsunderlyinginfrastructure.ThisisreallyhandyinaMicroserviceinfrastructure,asthenumberofnodesmaybehigh,especiallyifeachserviceisscaledacrossmultiplemachines.TheintegrationachievedbyanAPIProxyisthereforeonlystructural;thereisnosemanticmechanism.ItsimplyprovidesafamiliarmonolithicviewofacomplexMicroserviceinfrastructure.Thisisopposedtothenextpatternwearegoingtolearn,wheretheintegrationissemanticinstead.

APIorchestrationThepatternwearegoingtodescribenextisprobablythemostnaturalandexplicitwaytointegrateandcomposeasetofservices,andit'scalledAPIorchestration.DanielJacobson,VPofengineeringfortheNetflixAPI,inoneofhisblogposts(http://thenextweb.com/dd/2013/12/17/future-api-design-orchestration-layer),definesAPIOrchestrationasfollows:

AnAPIOrchestrationLayer(OL)isanabstractionlayerthattakesgenerically-modeleddataelementsand/orfeaturesandpreparestheminamorespecificway

foratargeteddeveloperorapplication.

Page 388: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thegenericallymodeledelementsand/orfeaturesfitthedescriptionofaserviceinaMicroservicearchitectureperfectly.Theideaistocreateanabstractiontoconnectthosebitsandpiecestoimplementnewservicesspecifictotheapplication.

Let'smakeanexampleusingthee-commerceapplication.Refertothefollowingfigure:

TheprecedingfigureshowshowtheStorefront-endapplicationusesanOrchestrationlayertobuildmorecomplexandspecificfeaturesbycomposingandorchestratingexistingservices.ThedescribedscenariotakesasexampleahypotheticalcompleteCheckout()servicethatisinvokedthemomentacustomerclicksthePaybuttonattheendofthecheckout.ThefigureshowshowcompleteCheckout()isacompositeoperationmadeofthreedifferentsteps:

1. First,wecompletethetransactionbyinvokingcheckoutService/pay.2. Then,whenthepaymentissuccessfullyprocessed,weneedtotellthecart

servicethattheitemswerepurchasedandtheycanberemovedfromthecart.WedothatbyinvokingcartService/delete.

3. Also,whenthepaymentiscomplete,weneedtoupdatetheavailabilityofthe

Page 389: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

productsthatwerejustpurchased.ThisisdonethroughproductsService/update.

Aswecansee,wetookthreeoperationsfromthreedifferentservicesandwebuiltanewAPIthatcoordinatestheservicestomaintaintheentiresysteminaconsistentstate.

AnothercommonoperationperformedbytheAPIOrchestrationLayerisdataaggregation,inotherwords,combiningdatafromdifferentservicesintoasingleresponse.Imagineifwewantedtolistalltheproductscontainedinacart.Inthiscase,theOrchestrationwouldneedtoretrievethelistofproductIDsfromtheCartserviceandthenretrievethecompleteinformationabouttheproductsfromtheProductsservice.Thewaysbywhichwecancombineandcoordinateservicesarereallyinfinite,buttheimportantpatterntorememberistheroleoftheOrchestrationlayer,whichactsasanabstractionbetweenanumberofservicesandaspecificapplication.

TheOrchestrationlayerisagreatcandidateforafurtherfunctionalsplitting.Itisinfactverycommontohaveitimplementedasadedicated,independentservice,inwhichcaseittakesthenameofAPIOrchestrator.ThispracticeisperfectlyinlinewiththeMicroservicephilosophy.

Thenextfigureshowsthisfurtherimprovementofourarchitecture:

Page 390: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CreatingastandaloneOrchestrator,asshowninthepreviousfigure,canhelpindecouplingtheclientapplication(inourcase,theStorefront-end)fromthecomplexityoftheMicroserviceinfrastructure.ThisremindsusabouttheAPIProxy;however,thereisacrucialdifference;anOrchestratorperformsasemanticintegrationofthevariousservices—it'snotjustanaïveproxy—anditoftenexposesanAPIthatisdifferentfromtheoneexposedbytheunderlyingservices.

IntegrationwithamessagebrokerTheOrchestratorpatterngaveusamechanismtointegratethevariousservicesinanexplicitway.Thishasbothadvantagesanddisadvantages.Itiseasytodesign,easytodebug,andeasytoscale,butunfortunately,ithastohaveacompleteknowledgeoftheunderlyingarchitectureandhoweachserviceworks.Ifweweretalkingaboutobjectsinsteadofarchitecturalnodes,theOrchestratorwouldbeananti-patterncalledGodObject,whichdefinesanobjectthatknowsanddoestoomuch,whichusuallyresultsinhighcoupling,lowcohesion,butmostimportantly,highcomplexity.

Page 391: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thepatternwearenowgoingtoshowtriestodistributeacrosstheservicestheresponsibilityofsynchronizingtheinformationoftheentiresystem.However,thelastthingwewanttodoiscreatedirectrelationshipsbetweenservices,whichwouldresultinhighcouplingandafurtherincreaseinthecomplexityofthesystem,duetotheincreasingnumberofinterconnectionsbetweennodes.Thegoalistohaveeachservicemaintainitsisolation;theyshouldbeabletoworkevenwithouttherestoftheservicesinthesystemorincombinationwithnewservicesandnodes.

Thesolutionistouseamessagebroker,asystemcapableofdecouplingthesenderfromthereceiverofamessage,allowingtoimplementacentralizedpublish/subscribepattern,inpracticeanobserverpatternfordistributedsystems(wewilltalkmoreaboutthispatternlaterinthebook).Thefollowingdiagramshowsanexampleofhowthisappliestothee-commerceapplication:

Aswecansee,theclientoftheCheckoutservice,whichisthefront-endapplication,doesnotneedtocarryoutanyexplicitintegrationwiththeotherservices.AllithastodoisinvokecheckoutService/paytocompletethecheckoutandtakethemoneyfromthecustomer;alltheintegrationworkhappensinthebackground:

Page 392: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

1. TheStorefront-endinvokesthecheckoutService/payoperationontheCheckoutservice.

2. Whentheoperationcompletes,theCheckoutservicegeneratesanevent,attachingthedetailsoftheoperation,thatis,thecartIdandthelistofproductsthatwerejustpurchased.Theeventispublishedintothemessagebroker.Atthispoint,theCheckoutservicedoesnotknowwhoisgoingtoreceivethemessage.

3. TheCartserviceissubscribedtothebroker,soit'sgoingtoreceivethepurchasedeventthatwasjustpublishedbytheCheckoutservice.TheCartservicereactsbyremovingfromitsdatabasethecartidentifiedwiththeIDcontainedinthemessage.

4. TheProductsservicewassubscribedtothemessagebrokeraswell,soitreceivesthesamepurchasedevent.Itthenupdatesitsdatabasebasedonthisnewinformation,adjustingtheavailabilityoftheproductsincludedinthemessage.

ThewholeprocesshappenswithoutanyexplicitinterventionfromexternalentitiessuchasanOrchestrator.Theresponsibilityforspreadingtheknowledgeandkeepinginformationinsyncisdistributedacrosstheservicesthemselves.ThereisnoGodservicethathastoknowhowtomovethegearsoftheentiresystem,eachserviceisinchargeofitsownpartoftheintegration.

Themessagebrokerisafundamentalelementtodecoupletheservicesandreducethecomplexityoftheirinteraction.Itmightalsoofferotherinterestingfeaturessuchaspersistentmessagequeuesandguaranteedorderingofthemessages.Wewilltalkmoreaboutthisinthenextchapter.

Page 393: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryInthischapter,welearnedhowtodesignNode.jsarchitecturesthatscalebothincapacityandcomplexity.Wesawhowscalinganapplicationisnotonlyabouthandlingmoretrafficorreducingtheresponsetime,butit'salsoapracticetoapplywhenwewantbetteravailabilityandtolerancetofailures.Wesawhowthesepropertiesoftenareonthesamewavelengthandweunderstoodthatscalingearlyisnotabadpractice,especiallyinNode.js,whichallowsustodoiteasilyandwithfewresources.

Thescalecubetaughtusthatapplicationscanbescaledacrossthreedimensions.Wedivedintothetwomostimportantofthem,theXandYaxes,allowingustodiscovertwoessentialarchitecturalpatterns,namely,loadbalancingandmicroservices.WeshouldknowbynowhowtostartmultipleinstancesofthesameNode.jsapplication,howtodistributethetrafficacrossthem,andhowtoexploitthissetupforotherpurposessuchasfailtoleranceandzero-downtimerestarts.Wealsoanalyzedhowtohandletheproblemofdynamicandautoscaledinfrastructures,wesawthataServiceRegistrycancomereallyusefulinthosesituations.However,cloningandloadbalancingcoveronlyonedimensionofthescalecube,sowemovedouranalysistoanotherdimension,studyinginmoredetailwhatitmeanstosplitanapplicationbyitsconstituentservices,bybuildingaMicroservicearchitecture.Wesawhowmicroservicesenableacompleterevolutioninhowaprojectisdevelopedandmanaged,providinganaturalwaytodistributetheloadofanapplicationandsplititscomplexity.However,welearnedthatthisalsomeansshiftingthecomplexityfromhowtobuildabigmonolithicapplicationtohowtointegrateasetofservices.Thislastaspectiswherewefocusedthelastpartofouranalysis,showingsomeofthearchitecturalsolutionstointegrateasetofindependentservices.

Inthenextchapter,wewillhavethechancetoanalyzeinmoredetailthemessagingpatternswediscussedinthischapterinadditiontomoreadvancedintegrationtechniques,usefulwhenimplementingcomplexdistributedarchitectures.

Page 394: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Chapter8.MessagingandIntegrationPatternsIfscalabilityisaboutsplitting,systemsintegrationisaboutrejoining.Inthepreviouschapter,welearnedhowtodistributeanapplication,fragmentingitacrossseveralmachines.Inorderforittoworkproperly,allthosepieceshavetocommunicateinsomeway,andhence,theyhavetobeintegrated.

Therearetwomaintechniquestointegrateadistributedapplication:oneistouseasharedstoreasacentralcoordinatorandkeeperofalltheinformation,theotheroneistousemessagestodisseminatedata,events,andcommandsacrossthenodesofthesystem.Thislastoptioniswhatreallymakesthedifferencewhenscalingdistributedsystems,andit'salsowhatmakesthistopicsofascinatingandsometimescomplex.

Messagesareusedineverylayerofasoftwaresystem.WeexchangemessagestocommunicateontheInternet,wecanusemessagestosendinformationtootherprocessesusingpipes,wecanusemessageswithinanapplicationasanalternativetodirectfunctioninvocation(Commandpattern),andalsodevicedriversusemessagestocommunicatewiththehardware.Anydiscreteandstructureddatathatisusedasawaytoexchangeinformationbetweencomponentsandsystemscanbeseenasamessage.However,whendealingwithdistributedarchitectures,thetermmessagingsystemisusedtodescribeaspecificclassofsolutions,patterns,andarchitecturesthataremeanttofacilitatetheexchangeofinformationoverthenetwork.

Aswewillsee,thereareseveraltraitsthatcharacterizethesetypesofsystems.Wemightchoosetouseabrokerversusapeer-to-peerstructure,wemightusearequest/replyorone-waycommunication,orwemightusequeuestodeliverourmessagesmorereliably;thescopeofthetopicisreallybroad.Thebook,EnterpriseIntegrationPatterns,byGregorHohpeandBobbyWoolf,givesyouanideaaboutthevastnessofthetopic.ItisconsideredtheBibleofmessagingandintegrationpatternsthathasmorethan700pagesdescribing65differentintegrationpatterns.Thischapterexploresthemostimportantofthosewell-knownpatterns,consideringthemfromtheperspectiveofNode.jsanditsecosystem.

Tosumup,inthischapter,wewilllearnaboutthefollowingtopics:

ThefundamentalsofamessagingsystemThepublish/subscribepatternPipelinesandtaskdistributionpatterns

Page 395: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Request/replypatterns

FundamentalsofamessagingsystemWhentalkingaboutmessagesandmessagingsystems,therearefourfundamentalelementstotakeinconsideration,theseareasfollows:

Thedirectionofthecommunication,whichcanbeone-wayonlyorarequest/replyexchangeThepurposeofthemessage,whichalsodeterminesitscontentThetimingofthemessage,whichcanbesentandreceivedimmediatelyoratalatertime(asynchronously)Thedeliveryofthemessage,whichcanhappendirectlyorviaabroker

Inthesectionsthatwillfollow,wearegoingtoformalizetheseaspectsinordertoprovideabaseforourlaterdiscussions.

One-wayandrequest/replypatternsThemostfundamentalaspectinamessagingsystemisthedirectionofthecommunication,whichoftenalsodeterminesitssemantics.

Themostsimplecommunicationpatterniswhenthemessageispushed,one-wayfromasourcetoadestination,thisisatrivialsituation,anditdoesn'tneedmanyexplanations.

Atypicalexampleofone-waycommunicationisthee-mail,orawebserverthatsendsamessagetoaconnectedbrowserusingWebSockets,orasystemthatdistributestaskstoasetofworkers.

Therequest/replypatternis,however,farmorepopularthantheone-wayonlycommunication,atypicalexampleistheinvocationofawebservice.Thefollowingfigureshowsthissimpleandwell-knownscenario:

Page 396: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Therequest/replypatternmightseematrivialpatterntoimplement;however,wewillseethatitbecomesmorecomplicatedwhenthecommunicationisasynchronousorinvolvesmultiplenodes.Takealookattheexampleinthefollowingfigure:

Withthesetupshownintheprecedingdiagram,wecanappreciatethecomplexityofsomerequest/replypatterns.Ifweconsiderthedirectionofthecommunicationbetweenanytwonodes,wecansurelysaythatitisone-way.However,fromaglobalpointofview,theinitiatorsendsarequestandinturnreceivesanassociatedresponse,eveniffromadifferentnode.Inthesesituations,whatreallydifferentiatesarequest/replypatternfromabareone-wayloopistherelationshipbetweentherequestandthereply,whichiskeptintheinitiator.Thereplyisusuallyhandledinthesamecontextoftherequest.

MessagetypesAmessageisessentiallyameanstoconnectdifferentsoftwarecomponentsandtherearedifferentreasonsfordoingso:itmightbebecausewewanttoobtainsomeinformationheldbyanothersystemoracomponent,toexecuteoperationsremotely,ortonotifysomepeersthatsomethinghasjusthappened.Themessagecontentwillalsovarydependingonthereasonofthecommunication.Ingeneral,wecanidentifythreetypesofmessages,dependingontheirpurpose:

CommandMessage

Page 397: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

EventMessageDocumentMessage

TheCommandMessageisalreadyfamiliartous;it'sessentiallyaserializedCommandObjectaswedescribeditinChapter4,DesignPatterns.Thepurposeofthistypeofmessageistotriggertheexecutionofanactionorataskonthereceiver.Forthistobepossible,ourmessagehastocontaintheessentialinformationtorunthetask,whichisusuallythenameoftheoperationandalistofargumentstoprovidewhenit'sexecuted.TheCommandMessagecanbeusedtoimplementRemoteProcedureCall(RPC)systems,distributedcomputations,ormoresimplyusedtorequestsomedata.RESTfulHTTPcallsaresimpleexamplesofcommands;eachHTTPverbhasaspecificmeaningandisassociatedwithapreciseoperation:GET,toretrievetheresource;POST,tocreateanewone;PUT,toupdateit;andDELETE,todestroyit.

AnEventMessageisusedtonotifyanothercomponentthatsomethinghasoccurred.Itusuallycontainsthetypeoftheeventandsometimesalsosomedetailssuchasthecontext,thesubjectoractorinvolved.Inwebdevelopment,weareusinganEventmessageinthebrowserwhenusinglong-pollingorWebSocketstoreceivenotificationsfromtheserverthatsomethinghasjusthappened,asforexample,changesinthedataoringeneral,thestateofthesystem.Theuseofeventsisaveryimportantintegrationmechanismindistributedapplications,asitenablesustokeepallthenodesofthesystemonthesamepage.

TheDocumentMessageisprimarilymeanttotransferdatabetweencomponentsandmachines.ThemaincharacteristicthatdifferentiatesaDocumentfromaCommand(whichmightalsocontaindata)isthatthemessagedoesnotcontainanyinformationthattellsthereceiverwhattodowiththedata.Ontheotherside,themaindifferencefromanEventmessageismainlytheabsenceofanassociationwithaparticularoccurrence,withsomethingthathappened.Often,therepliestotheCommandmessagesareDocumentmessages,astheyusuallycontainonlythedatathatwasrequestedortheresultofanoperation.

AsynchronousmessagingandqueuesAsNode.jsdevelopers,weshouldalreadyknowtheadvantagesofexecutingasynchronousoperations.Formessagingandcommunications,it'sthesamestory.

Wecancompareasynchronouscommunicationtoaphonecall:thetwopeersmustbeconnectedtothesamechannelatthesametimeandtheyshouldexchangemessagesinrealtime.Normally,ifwewanttocallsomeoneelse,weeitherneedanotherphoneorclosetheongoingcommunicationinordertostartanewone.

Page 398: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AnasynchronouscommunicationissimilartoanSMS,itdoesn'trequiretherecipienttobeconnectedtothenetworkthemomentwesendit,wemightreceivearesponseimmediatelyorafteranunknowndelay,orwemightnotreceivearesponseatall.WemightsendmultipleSMStomultiplerecipientsoneaftertheother,andreceivetheirresponses(ifany)inanyorder.Inshort,wehaveabetterparallelismwiththeuseoffewerresources.

Anotherimportantadvantageofasynchronouscommunicationsisthatthemessagescanbestoredandthendeliveredassoonaspossibleoratalatertime.Thismightbeusefulwhenthereceiveristoobusytohandlenewmessagesorwhenwewanttoguaranteethedelivery.Inmessagingsystems,thisismadepossibleusingamessagequeue,acomponentthatmediatesthecommunicationbetweenthesenderandthereceiver,storinganymessagebeforeitgetsdeliveredtoitsdestination,asshowninthefollowingfigure:

Ifforanyreasonthereceivercrashes,disconnectsfromthenetwork,orexperiencesaslowdown,themessagesareaccumulatedinthequeueanddispatchedassoonasthereceivercomesonlineandisfullyworking.Thequeuecanbelocatedinthesender,orsplitbetweenthesenderandreceiver,orlivinginadedicatedexternalsystemactingasamiddlewareforthecommunication.

Peer-to-peerorbroker-basedmessagingMessagescanbedelivereddirectlytothereceiver,inapeer-to-peerfashionorthroughacentralizedintermediarysystemcalledMessageBroker.Themainroleofthebrokeristodecouplethereceiverofthemessagefromthesender.Thefollowingfigureshowsthearchitecturaldifferencebetweenthetwoapproaches:

Page 399: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Inapeer-to-peerarchitecture,everynodeisdirectlyresponsibleforthedeliveryofthemessagetothereceiver.Thisimpliesthatthenodeshavetoknowtheaddressandportofthereceiverandtheyhavetoagreeonaprotocolandmessageformat.Thebrokereliminatesthesecomplexitiesfromtheequation:eachnodecanbetotallyindependentandcancommunicatewithanundefinednumberofpeerswithoutdirectlyknowingtheirdetails.Abrokercanalsoactasabridgebetweenthedifferentcommunicationprotocols,forexample,thepopularRabbitMQbroker(http://www.rabbitmq.com)supportsAdvancedMessageQueuingProtocol(AMQP),MessageQueueTelemetryTransport(MQTT),andSimple/StreamingTextOrientatedMessagingProtocol(STOMP),enablingmultipleapplicationssupportingdifferentmessagingprotocolstointeract.

NoteMQTT(http://mqtt.org)isalightweightmessagingprotocol,specificallydesignedformachine-to-machinecommunications(InternetofThings).AMQP(http://www.amqp.org)isamorecomplexprotocol,whichisdesignedtobeanopensourcealternativetoproprietarymessagingmiddlewares.STOMP(http://stomp.github.io)isalightweighttext-basedprotocol,whichcomesfromtheHTTPschoolofdesign.AllthreeareApplicationlayerprotocols,andbasedonTCP/IP.

Besidesthedecouplingandtheinteroperability,abrokercanoffermoreadvancedfeaturessuchaspersistentqueues,routing,messagetransformations,andmonitoring,withoutmentioningthebroadrangeofmessagingpatternsthatmanybrokerssupportoutofthebox.Ofcourse,nothingcanstopusfromimplementingall

Page 400: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thesefeaturesusingapeer-to-peerarchitecture,butunfortunatelythereismuchmoreeffortinvolved.Nonetheless,theremightbedifferentreasonstoavoidabroker:

RemovingasinglepointoffailureAbrokerhastobescaled,whileinapeer-to-peerarchitectureweonlyneedtoscalethesinglenodesExchangingmessageswithoutintermediariescangreatlyreducethelatencyofthetransmission

Ifwewanttoimplementapeer-to-peermessagingsystem,wecanalsohavemuchmoreflexibilityandpower,becausewearenotboundtoanyparticulartechnology,protocol,orarchitecture.ThepopularityofØMQ(http://zeromq.org),whichisalow-levellibraryforbuildingmessagingsystems,isagreatdemonstrationoftheflexibilitythatwecanhavebybuildingcustompeer-to-peerorhybridarchitectures.

Page 401: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Publish/subscribepatternPublish/subscribe(oftenabbreviatedPub/Sub)isprobablythebestknownone-waymessagingpattern.Weshouldalreadybefamiliarwithit,asit'snothingmorethanadistributedobserverpattern.Asinthecaseofobserver,wehaveasetofsubscribersregisteringtheirinterestinreceivingaspecificcategoryofmessages.Ontheotherside,thepublisherproducesmessagesthataredistributedacrossalltherelevantsubscribers.Thefollowingfigureshowsthetwomainvariationsofthepub/subpattern,thefirstpeer-to-peer,thesecondusingabrokertomediatethecommunication:

Whatmakespub/subsospecialisthefactthatthepublisherdoesn'tknowwhotherecipientsofthemessagesareinadvance.Aswesaid,it'sthesubscriberwhichhastoregisteritsinteresttoreceiveaparticularmessage,allowingthepublishertoworkwithanunknownnumberofreceivers.Inotherwords,thetwosidesofthepub/subpatternarelooselycoupled,whichmakesthisanidealpatterntointegratethenodesofanevolvingdistributedsystem.

Thepresenceofabrokerfurtherimprovesthedecouplingbetweenthenodesofthesystembecausethesubscribersinteractonlywiththebroker,notknowingwhichnodeisthepublisherofamessage.Aswewillseelater,abrokercanalsoprovideamessagequeuingsystem,allowingareliabledeliveryeveninthepresenceofconnectivityproblemsbetweenthenodes.

Now,let'sworkonanexampletodemonstratethispattern.

Page 402: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Buildingaminimalistreal-timechatapplicationToshowarealexampleofhowthepub/subpatterncanhelpusintegrateadistributedarchitecture,wearenowgoingtobuildaverybasicreal-timechatapplicationusingpureWebSockets.Then,wewilltrytoscaleitbyrunningmultipleinstancesandusingamessagingsystemtoputthemincommunication.

ImplementingtheserversideNow,let'stakeonestepatatime.Let'sfirstbuildourchatapplication;todothis,wewillrelyonthewspackage(https://npmjs.org/package/ws),whichisapureWebSocketimplementationforNode.js.Asweknow,implementingreal-timeapplicationsinNode.jsisprettysimple,andourcodewillconfirmthisassumption.Let'sthencreatetheserversideofourchat;itscontentisasfollows(intheapp.jsfile):

varWebSocketServer=require('ws').Server;

//staticfileserver

varserver=require('http').createServer(//[1]

require('ecstatic')({root:__dirname+'/www'})

);

varwss=newWebSocketServer({server:server});//[2]

wss.on('connection',function(ws){

console.log('Clientconnected');

ws.on('message',function(msg){//[3]

console.log('Message:'+msg);

broadcast(msg);

});

});

functionbroadcast(msg){//[4]

wss.clients.forEach(function(client){

client.send(msg);

});

}

server.listen(process.argv[2]||8080);

That'sit!That'sallweneedtoimplementourchatapplicationontheserver.Thisisthewayitworks:

1. WefirstcreateanHTTPserverandattachamiddlewarecalledecstatic(https://npmjs.org/package/ecstatic)toservestaticfiles.Thisisneededtoservetheclient-sideresourcesofourapplication(JavaScriptandCSS).

2. WecreateanewinstanceoftheWebSocketserverandweattachittoour

Page 403: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

existingHTTPserver.WethenstartlisteningforincomingWebSocketconnections,byattachinganeventlistenerfortheconnectionevent.

3. Eachtimeanewclientconnectstoourserver,westartlisteningforincomingmessages.Whenanewmessagearrives,webroadcastittoalltheconnectedclients.

4. Thebroadcast()functionisasimpleiterationoveralltheconnectedclients,wherethesend()functionisinvokedoneachoneofthem.

ThisisthemagicofNode.js!Ofcourse,theserverthatweimplementedisveryminimalandbasic,butaswewillsee,itdoesitsjob.

ImplementingtheclientsideNext,it'stimetoimplementtheclientsideofourchat;thisisalsoaverysmallandsimplefragmentofcode,essentiallyaminimalHTMLpagewithsomebasicJavaScriptcode.Let'screatethispageinafilenamedwww/index.htmlasfollows:

<html>

<head>

<script>

varws=newWebSocket('ws://'+

window.document.location.host);

ws.onmessage=function(message){

varmsgDiv=document.createElement('div');

msgDiv.innerHTML=message.data;

document.getElementById('messages').appendChild(msgDiv);

};

functionsendMessage(){

varmessage=document.getElementById('msgBox').value;

ws.send(message);

}

</script>

</head>

<body>

Messages:

<divid='messages'></div>

<inputtype='text'placeholder='Sendamessage'

id='msgBox'>

<inputtype='button'onclick='sendMessage()'value='Send'>

</body>

</html>

TheHTMLpagewecreateddoesn'treallyneedmanycomments;itisjustapieceofstraightforwardwebdevelopment.WeusethenativeWebSocketobjecttoinitializeaconnectiontoourNode.jsserver,andthenstartlisteningformessagesfromtheserverdisplayingtheminnewdivelementsastheyarrive.Forsendingmessages,

Page 404: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

instead,weuseasimpletextboxandabutton.

NoteWhenstoppingorrestartingthechatserver,theWebSocketconnectionisclosedanditwillnotreconnectautomatically(asitwouldusinghigh-levellibrariessuchasSocket.io).Thismeansthatitisnecessarytorefreshthebrowserafteraserverrestart,tore-establishtheconnection(orimplementareconnectionmechanism,whichwewillnotcoverhere).

RunningandscalingthechatapplicationWecantryrunningourapplicationimmediately,justlaunchtheserverwithacommandsuchasthefollowing:

nodeapp8080

NoteTorunthisdemo,youwillneedarecentbrowser,whichsupportsnativeWebSockets.Hereisalistofcompatiblebrowsers:http://caniuse.com/#feat=websockets

Pointingabrowsertohttp://localhost:8080shouldpresentaninterfacesimilartothefollowing:

Whatwewanttoshownow,iswhathappenswhenwetrytoscaleourapplicationbylaunchingmultipleinstances.Let'strytodothis,let'sstartanotherserveronanotherport:

Page 405: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

nodeapp8081

Thedesiredoutcomeofscalingourchatapplicationshouldbethatthetwoclientsconnectingtothetwodifferentserversshouldbeabletoexchangechatmessages.Unfortunately,thisisnotwhathappenswithourcurrentimplementation,wecantrythatbyopeninganotherbrowsertabtohttp://localhost:8081.

Whensendingachatmessageononeinstance,webroadcastamessagelocally,distributingittoonlytheclientsconnectedtothatparticularserver.Inpractice,thetwoserversdon'ttalktoeachother.Weneedtointegratethem.

NoteInarealapplication,wewillusealoadbalancertodistributetheloadacrossourinstances,butforthisdemowewillnotuseone.Thisallowsustoaccesseachserverinadeterministicwaytoverifyhowitinteractswiththeotherinstances.

UsingRedisasamessagebrokerWestartouranalysisofthemostimportantpub/subimplementationsbyintroducingRedis(http://redis.io),whichisaveryfastandflexiblekey-valuestore,alsodefinedbymanyasadatastructureserver.Redisismoreadatabasethanamessagebroker,howeveramongitsmanyfeaturesthereisapairofcommandsspecificallydesignedtoimplementacentralizedpublish/subscribepattern.Ofcourse,thisimplementationisverysimpleandbasic,comparedtomoreadvancedmessage-orientedmiddleware,butthisisoneofthemainreasonsforitspopularity.Often,infact,Redisisalreadyavailableinanexistinginfrastructure,forexample,asacachingserverorsessionstore;itsspeedandflexibilitymakeitaverypopularchoiceforsharingdatainadistributedsystem.So,assoonastheneedforapublish/subscribebrokerarisesinaproject,themostsimpleandimmediatechoiceistoreuseRedisitself,avoidingtoinstallandmaintainadedicatedmessagebroker.Let'sworkonanexampletodemonstrateitssimplicityandpower.

NoteThisexamplerequiresaworkinginstallationofRedis,listeningonitsdefaultport.Youcanfindmoredetailsathttp://redis.io/topics/quickstart.

OurplanofactionistointegrateourchatserversusingRedisasamessagebroker.

Page 406: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Eachinstancepublishesanymessagereceivedfromitsclientstothebroker,andatthesametimeitsubscribesforanymessagecomingfromotherserverinstances.Aswecansee,eachserverinourarchitectureisbothasubscriberandapublisher.Thefollowingfigureshowsarepresentationofthearchitecturethatwewanttoobtain:

Bylookingattheprecedingfigure,wecansumupthejourneyofamessageasfollows:

1. Themessageistypedintothetextboxofthewebpageandsenttotheconnectedinstanceofourchatserver.

2. Themessageisthenpublishedtothebroker.3. Thebrokerdispatchesthemessagetoallthesubscribers,whichinour

architecturearealltheinstancesofthechatserver.4. Ineachinstance,themessageisdistributedtoalltheconnectedclients.

NoteRedisallowspublishingandsubscribingtochannels,whichareidentifiedbyastring,forexample,chat.nodejs.Italsoallowsustouseglob-stylepatternstodefinesubscriptionsthatcanpotentiallymatchmultiplechannels,forexample,chat.*.

Page 407: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Let'sseeinpracticehowthisworks.Let'smodifytheservercodebyaddingthepublish/subscribelogic:

varWebSocketServer=require('ws').Server;

varredis=require("redis");//[1]

varredisSub=redis.createClient();

varredisPub=redis.createClient();

//staticfileserver

varserver=require('http').createServer(

require('ecstatic')({root:__dirname+'/www'})

);

varwss=newWebSocketServer({server:server});

wss.on('connection',function(ws){

console.log('Clientconnected');

ws.on('message',function(msg){

console.log('Message:'+msg);

redisPub.publish('chat_messages',msg);//[2]

});

});

redisSub.subscribe('chat_messages');//[3]

redisSub.on('message',function(channel,msg){

wss.clients.forEach(function(client){

client.send(msg);

});

});

server.listen(process.argv[2]||8080);

Thechangesthatwemadetoouroriginalchatserverarehighlightedintheprecedingcode;thisishowitworks:

1. ToconnectourNode.jsapplicationtotheRedisserver,weusetheredispackage(https://npmjs.org/package/redis),whichisacompleteclientthatsupportsalltheavailableRediscommands.Next,weinstantiatetwodifferentconnections,oneusedtosubscribetoachannel,theothertopublishmessages.ThisisnecessaryinRedis,becauseonceaconnectionisputinsubscribermodeonlycommandsrelatedtothesubscriptioncanbeused.Thismeansthatweneedasecondconnectionforpublishingmessages.

2. Whenanewmessageisreceivedfromaconnectedclient,wepublishamessageinthechat_messageschannel.Wedon'tdirectlybroadcastthemessagetoourclientsbecauseourserverissubscribedtothesamechannel(aswewillseeinamoment),soitwillcomebacktousthroughRedis.Forthescopeofthisexample,thisisasimpleandeffectivemechanism.

3. Aswesaid,ourserveralsohastosubscribetothechat_messageschannel,soweregisteralistenertoreceiveallthemessagespublishedintothatchannel(eitherbythecurrentserveroranyotherchatserver).Whenamessageis

Page 408: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

received,wesimplybroadcastittoalltheclientsconnectedtothecurrentWebSocketserver.

Thesefewchangesareenoughtointegrateallthechatserversthatwemightdecidetostart.Toprovethis,wecantrystartingmultipleinstancesofourapplication:

nodeapp8080

nodeapp8081

nodeapp8082

Wecanthenconnectmultiplebrowsers'tabstoeachinstanceandverifythatthemessageswesendtooneserveraresuccessfullyreceivedbyalltheotherclientsconnectedtodifferentservers.Congratulations!Wejustintegratedadistributedreal-timeapplicationusingthepublish/subscribepattern.

Peer-to-peerpublish/subscribewithØMQThepresenceofabrokercanconsiderablysimplifythearchitectureofamessagingsystem;however,therearecircumstanceswhereitisnotanoptimalsolution,asforexample,whenlatencyiscritical,whenscalingcomplexdistributedsystems,orwhenthepresenceofasinglepointoffailureisnotanoption.

IntroducingØMQIfourprojectfallsinthecategoryofpossiblecandidatesforapeer-to-peermessageexchange,thebestsolutiontoevaluateiscertainlyØMQ(http://zeromq.org,alsoknownaszmq,ZeroMQ,or0MQ);wealreadymentionedthislibraryearlierinthebook.ØMQisanetworkinglibrarythatprovidesthebasictoolstobuildalargevarietyofmessagingpatterns.Itislowlevel,extremelyfast,andhasaminimalisticAPIbutitoffersallthebasicbuildingblocksofamessagingsystemsuchasatomicmessages,loadbalancing,queues,andmanymore.Itsupportsmanytypesoftransportssuchasin-processchannels(inproc://),inter-processcommunication(ipc://),multicastusingthePGMprotocol(pgm://orepgm://),andofcourse,theclassicTCP(tcp://).

AmongthefeaturesofØMQ,wecanalsofindtoolstoimplementapublish/subscribepattern,exactlywhatweneedforourexample.So,whatwearegoingtodonowis,removethebroker(Redis)fromthearchitectureofourchatapplicationandletthevariousnodescommunicateinapeer-to-peerfashionleveragingthepublish/subscribesocketsofØMQ.

Note

Page 409: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

AØMQsocketcanbeconsideredanetworksocketonsteroids,whichprovidesadditionalabstractionstohelpimplementthemostcommonmessagingpatterns.Forexample,wecanfindsocketsdesignedtoimplementpublish/subscribe,request/reply,orone-waycommunications.

Designingapeer-to-peerarchitectureforthechatserverWhenweremovethebrokerfromourarchitecture,eachinstanceofthechatapplicationhastodirectlyconnecttotheotheravailableinstancesinordertoreceivethemessagestheypublish.InØMQ,wehavetwotypesofsocketsspecificallydesignedforthispurpose:PUBandSUB.ThetypicalpatternistobindaPUBsockettoaportthatwillstartlisteningforsubscriptionscomingfromtheotherSUBsockets.

AsubscriptioncanhaveafilterthatspecifieswhatmessageswillbedeliveredtotheSUBsockets.Thefilterisasimplebinarybuffer(soitcanalsobeastring),whichwillbematchedagainstthebeginningofthemessage(whichisalsoabinarybuffer).WhenamessageissentthroughthePUBsocketitisbroadcasttoalltheconnectedSUBsockets,butonlyaftertheirsubscriptionfiltersareapplied.Thefilterswillbeappliedtothepublishersideonlyifaconnectedprotocolisused,asforexample,TCP.

Thefollowingfigureshowsusthepatternappliedtoourdistributedchatserverarchitecture(withonlytwoinstances,forsimplicity):

Theprecedingfigureshowsustheflowofinformationwhenwehavetwoinstancesof

Page 410: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

thechatapplication,butthesameconceptcanbeappliedforNinstances.Thearchitecturetellsusthateachnodemustbeawareoftheothernodesinthesystem,tobeabletoestablishallthenecessaryconnections.ItalsoshowsushowthesubscriptionsgofromaSUBsockettoaPUBsocket,whilemessagestravelintheoppositedirection.

NoteToruntheexampleinthissection,youneedtoinstallthenativeØMQbinariesonyoursystem.Youcanfindmoreinformationathttp://zeromq.org/intro:get-the-software.Note:thisexamplewastestedagainstthe4.0branchofØMQ.

UsingtheØMQPUB/SUBsocketsLet'sseehowthisworksinpracticebymodifyingourchatserver(wewillshowyouonlythechangedparts):

[...]

varargs=require('minimist')(process.argv.slice(2));//[1]

varzmq=require('zmq');

varpubSocket=zmq.socket('pub');//[2]

pubSocket.bind('tcp://127.0.0.1:'+args['pub']);

varsubSocket=zmq.socket('sub');//[3]

varsubPorts=[].concat(args['sub']);

subPorts.forEach(function(p){

console.log('Subscribingto'+p);

subSocket.connect('tcp://127.0.0.1:'+p);

});

subSocket.subscribe('chat');

[...]

ws.on('message',function(msg){//[4]

console.log('Message:'+msg);

broadcast(msg);

pubSocket.send('chat'+msg);

});

[...]

subSocket.on('message',function(msg){//[5]

console.log('Fromotherserver:'+msg);

broadcast(msg.toString().split('')[1]);

});

[...]

server.listen(args['http']||8080);

Page 411: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Theprecedingcodeclearlyshowsthatthelogicofourapplicationbecameslightlymorecomplicated,howeverit'sstillstraightforwardconsideringthatweareimplementingadistributedandpeer-to-peerpublish/subscribepattern.Let'sseehowallthepiecescometogether:

1. Werequirethezmqpackage(https://npmjs.org/package/zmq),whichisessentiallytheNode.jsbindingfortheØMQnativelibrary.Wealsorequireminimist(https://npmjs.org/package/minimist),whichisacommand-lineargumentparser;weneedthistobeabletoeasilyacceptnamedarguments.

2. WeimmediatelycreateourPUBsocketandbindittotheportprovidedinthe--pubcommand-lineargument.

3. WecreatetheSUBsocketandweconnectittothePUBsocketsoftheotherinstancesofourapplication.TheportsofthetargetPUBsocketsareprovidedinthe--subcommand-linearguments(theremightbemorethanone).Wethencreatetheactualsubscription,byprovidingchatasafilter,whichmeansthatwewillreceiveonlythemessagesbeginningwithchat.

4. WhenanewmessageisreceivedbyourWebSocket,webroadcastittoalltheconnectedclientsbutwealsopublishitthroughourPUBsocket.Weusechatasaprefixfollowedbyaspace,sothemessagewillbepublishedtoallthesubscriptionsusingchatasafilter.

5. WestartlisteningformessagesthatarriveatourSUBsocket,wedosomesimpleparsingofthemessagetoremovethechatprefix,andthenwebroadcastittoalltheclientsconnectedtothecurrentWebSocketserver.

Wehavenowbuiltasimpledistributedsystem,integratedusingapeer-to-peerpublish/subscribepattern!

Let'sfireitup,let'sstartthreeinstancesofourapplicationbymakingsuretoconnecttheirPUBandSUBsocketsproperly:

nodeapp--http8080--pub5000--sub5001--sub5002

nodeapp--http8081--pub5001--sub5000--sub5002

nodeapp--http8082--pub5002--sub5000--sub5001

ThefirstcommandwillstartaninstancewithanHTTPserverlisteningontheport8080,whilebindingaPUBsocketonport5000andconnectingtheSUBsockettotheports5001and5002,whichiswherethePUBsocketsoftheothertwoinstancesshouldbelisteningat.Theothertwocommandsworkinasimilarway.

Now,thefirstthingwecansee,isthatØMQwillnotcomplainifaportcorrespondingtoaPUBsocketisnotavailable.Forexample,atthetimeofthefirstcommand,thereisnobodylisteningontheports5001and5002,howeverØMQisnotthrowinganyerror.ThisisbecauseØMQhasareconnectionmechanismthatwillautomaticallytrytoestablishaconnectiontotheseportsatregulartimeintervals.Thisfeaturealso

Page 412: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

comesparticularlyhandyifanynodegoesdownorisrestarted.ThesameforgivinglogicappliestothePUBsocket:iftherearenosubscriptions,itwillsimplydropallthemessages,butitwillcontinueworking.

Atthispoint,wecantrytonavigatewithabrowsertoanyoftheserverinstancesthatwestartedandverifythatthemessagesareproperlybroadcasttoallthechatservers.

TipInthepreviousexample,weassumedastaticarchitecture,wherethenumberofinstancesandtheiraddressesareknowninadvance.WecanintroduceaServiceRegistry,asexplainedinthepreviouschapter,toconnectourinstancesdynamically.ItisalsoimportanttopointoutthatØMQcanbeusedtoimplementabrokerusingthesameprimitiveswedemonstratedhere.

DurablesubscribersAnimportantabstractioninamessagingsystemisthemessagequeue(MQ).Withamessagequeue,thesenderandthereceiver(s)ofthemessagedon'tnecessarilyneedtobeactiveandconnectedatthesametimetoestablishacommunication,becausethequeuingsystemtakescareofstoringthemessagesuntilthedestinationisabletoreceivethem.Thisbehaviorisopposedtothesetandforgetparadigm,whereasubscribercanreceivemessagesonlyduringthetimeitisconnectedtothemessagingsystem.

Asubscriberthatisabletoalwaysreliablyreceiveallthemessages,eventhosesentwhenit'snotlisteningforthem,iscalledadurablesubscriber.

TipTheMQTTprotocoldefinesalevelofQualityofService(QoS)forthemessagesexchangedbetweenthesenderandreceiver.Theselevelsarealsoveryusefultodescribethereliabilityofanyothermessagingsystem(notonlyMQTT).Theseareasfollows:

QoS0,atmostonce:Alsoknownassetandforget,themessageisnotpersisted,andthedeliveryisnotacknowledged.Thismeansthatthemessagecanbelostincasesofcrashesordisconnectionsofthereceiver.QoS1,atleastonce:Themessageisguaranteedtobereceivedatleastonce,

Page 413: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

butduplicatesmightoccurif,forexample,thereceivercrashesbeforenotifyingthesender.Thisimpliesthatthemessagehastobepersistedintheeventualityithastobesentagain.QoS2,exactlyonce:ThisisthemostreliableQoS,itguaranteesthatthemessageisreceivedonceandonlyonce.Thiscomesattheexpenseofaslowerandmoredataintensivemechanismforacknowledgingthedeliveryofmessages.

FindoutmoreintheMQTTspecificationsathttp://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#qos-flows.

Aswesaid,toallowdurablesubscribers,oursystemhastouseamessagequeuetoaccumulatethemessageswhilethesubscriberisdisconnected.Thequeuecanbestoredinmemoryorpersistedondisktoallowtherecoveryofitsmessagesevenifthebrokerrestartsorcrashes.Thefollowingfigureshowsagraphicalrepresentationofadurablesubscriberbackedbyamessagequeue:

Thedurablesubscriberisprobablythemostimportantpatternenabledbyamessagequeue,butit'scertainlynottheonlyone,aswewillseelaterinthechapter.

Page 414: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TipTheRedispublish/subscribecommandsimplementasetandforgetmechanism(QoS0).However,Rediscanstillbeusedtoimplementadurablesubscriberusingacombinationofothercommands(withoutrelyingdirectlyonitspublish/subscribeimplementation).Youcanfindadescriptionofthistechniqueinthefollowingblogposts:

http://davidmarquis.wordpress.com/2013/01/03/reliable-delivery-message-queues-with-redishttp://www.ericjperry.com/redis-message-queue

ØMQdefinessomepatternstosupportdurablesubscribersaswell,butit'smostlyuptoustoimplementthismechanism.

IntroducingAMQPAmessagequeueisnormallyusedinallthosesituationswherenomessagecanbelost,whichincludesmission-criticalapplicationssuchasbankingorfinancialsystems.Thisusuallymeansthatthetypicalenterprise-grademessagequeueisaverycomplexpieceofsoftware,whichutilizesbulletproofprotocolsandpersistentstoragetoguaranteethedeliveryofthemessageeveninthepresenceofmalfunctions.Forthisreason,enterprisemessagingmiddlewarehavebeenformanyyearsaprerogativeofgiantssuchasOracleandIBM,eachoneofthemusuallyimplementingitsownproprietaryprotocolresultinginastrongcustomerlock-in.Fortunately,it'sbeenafewyearsnowthatmessagingsystemshaveenteredthemainstream,thankstothegrowthofopenprotocolssuchasAMQP,STOMP,andMQTT.Tounderstandhowamessagequeuingsystemworks,wearenowgoingtogiveanoverviewofAMQP;thisisfundamentaltounderstandhowtouseatypicalAPIbasedonthisprotocol.

AMQP(AdvancedMessageQueuingProtocol)isanopenstandardprotocolsupportedbymanymessagequeuingsystems.Besidesdefiningacommoncommunicationprotocol,italsoprovidesamodelfordescribingrouting,filtering,queuing,reliability,andsecurity.InAMQP,therearethreeessentialcomponents:

Queue:Thedatastructureresponsibleforstoringthemessagesconsumedbytheclients.Themessagesfromaqueuearepushed(orpulled)tooneormoreconsumers—essentiallyourapplications.Ifmultipleconsumersareattachedtothesamequeue,themessagesareloadbalancedacrossthem.Aqueuecanbeoneofthefollowing:

Page 415: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Durable:Thismeansthatthequeueisautomaticallyrecreatedifthebrokerrestarts.Adurablequeuedoesnotimplythatitscontentsarepreservedaswell;infact,onlymessagesthataremarkedaspersistentaresavedtothediskandrestoredincaseofarestart.Exclusive:Thismeansthatthequeueisboundtoonlyoneparticularsubscriberconnection.Whentheconnectionisclosed,thequeueisdestroyed.Auto-delete:Thiswillcausethequeuetobedeletedwhenthelastsubscriberdisconnects.

Exchange:Thisiswhereamessageispublished.AnExchangeroutesthemessagestooneormorequeuesdependingonthealgorithmitimplements:

Directexchange:Itroutesthemessagesbymatchinganentireroutingkey(forexample,chat.msg).Topicexchange:Itdistributesthemessagesusingaglob-likepatternmatchedagainsttheroutingkey(forexample,chat.#matchesalltheroutingkeysstartingwithchat).Fanoutexchange:Itbroadcastsamessagetoalltheconnectedqueues,ignoringanyroutingkeyprovided.

Binding:Thisisthelinkbetweenexchangesandqueues.Italsodefinestheroutingkeyorthepatternusedtofilterthemessagesthatarrivefromtheexchange.

Thesecomponentsaremanagedbyabroker,whichexposesanAPIforcreatingandmanipulatingthem.Whenconnectingtoabroker,aclientcreatesachannel—anabstractionofaconnection—whichisresponsibleformaintainingthestateofthecommunicationwiththebroker.

NoteInAMQP,thedurablesubscriberpatterncanbeobtainedbycreatinganytypeofqueuethatisnotexclusiveorauto-delete.

Thefollowingfigureshowsusallthesecomponentsputtogether:

Page 416: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheAMQPmodeliswaymorecomplexthanthemessagingsystemswehaveusedsofar(RedisandØMQ);however,itoffersasetoffeaturesandareliabilitythatwouldbeveryhardtoobtainusingonlyprimitivepublish/subscribemechanisms.

TipYoucanfindadetailedintroductiontotheAMQPmodelontheRabbitMQwebsite:https://www.rabbitmq.com/tutorials/amqp-concepts.html.

DurablesubscriberswithAMQPandRabbitMQLet'snowpracticewhatwelearnedaboutdurablesubscribersandAMQPandworkonasmallexample.Atypicalscenariowhereit'simportanttonotloseanymessageiswhenwewanttokeepthedifferentservicesofaMicroservicearchitectureinsync;wealreadydescribedthisintegrationpatterninthepreviouschapter.Ifwewanttouseabrokertokeepallourservicesonthesamepage,it'simportantthatwedon'tloseanyinformation,otherwisewemightendupinaninconsistentstate.

Designingahistoryserviceforthechatapplication

Let'snowextendoursmallchatapplicationusingaMicroserviceapproach.Let'saddahistoryservicethatpersistsourchatmessagesinsideadatabase,sothatwhenaclientconnects,wecanquerytheserviceandretrievetheentirechathistory.WearegoingtointegratethehistoryservicewiththechatserverusingtheRabbitMQbroker(https://www.rabbitmq.com)andAMQP.

Page 417: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Thenextfigureshowsourplannedarchitecture:

Asdescribedintheprecedingarchitecture,wearegoingtouseasinglefanoutexchange;wedon'tneedanyparticularrouting,soourscenariodoesnotrequireanyexchangemorecomplexthanthat.Next,wewillcreateonequeueforeachinstanceofthechatserver;thesequeuesareexclusive,wearenotinterestedinreceivinganymissedmessagewhenachatserverisoffline,that'sthejobofourhistoryservice,whicheventuallycanalsoimplementmorecomplicatedqueriesagainstthestoredmessages.Inpractice,thismeansthatourchatserversarenotdurablesubscribers,andtheirqueueswillbedestroyedassoonastheconnectionisclosed.

Onthecontrary,thehistoryservicecannotaffordtoloseanymessage;otherwise,itwouldnotfulfillitsverypurpose.Thequeuewearegoingtocreateforithastobedurable,sothatanymessagethatispublishedwhilethehistoryserviceisdisconnectedwillbekeptinthequeueanddeliveredwhenitcomesbackonline.

WearegoingtousethefamiliarLevelUPasstorageengineforthehistoryservice,whilewewillusetheamqplibpackage(https://npmjs.org/package/amqplib)toconnecttoRabbitMQusingtheAMQPprotocol.

NoteThefollowingexamplerequiresaworkingRabbitMQserver,listeningonitsdefaultport.Formoreinformation,pleaserefertoitsofficialinstallationguideathttp://www.rabbitmq.com/download.html.

ImplementingareliablehistoryserviceusingAMQP

Let'snowimplementourhistoryservice!Wearegoingtocreateastandalone

Page 418: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

application(atypicalMicroservice),whichisimplementedinthemodulehistorySvc.js.Themoduleismadeupoftwoparts:anHTTPservertoexposethechathistorytoclients,andanAMQPconsumerwhichisresponsibleforcapturingthechatmessagesandstoringtheminalocaldatabase.

Let'sseehowthislookslikeinthecodethatfollows:

varlevel=require('level');

vartimestamp=require('monotonic-timestamp');

varJSONStream=require('JSONStream');

vardb=level('./msgHistory');

varamqp=require('amqplib');

//HTTPserverforqueryingthechathistory

require('http').createServer(function(req,res){

res.writeHead(200);

db.createValueStream()

.pipe(JSONStream.stringify())

.pipe(res);

}).listen(8090);

varchannel,queue;

amqp

.connect('amqp://localhost')//[1]

.then(function(conn){

returnconn.createChannel();

})

.then(function(ch){

channel=ch;

returnchannel.assertExchange('chat','fanout');//[2]

})

.then(function(){

returnchannel.assertQueue('chat_history');//[3]

})

.then(function(q){

queue=q.queue;

returnchannel.bindQueue(queue,'chat');//[4]

})

.then(function(){

returnchannel.consume(queue,function(msg){//[5]

varcontent=msg.content.toString();

console.log('Savingmessage:'+content);

db.put(timestamp(),content,function(err){

if(!err)channel.ack(msg);

});

});

})

.catch(function(err){

console.log(err);

});

WecanimmediatelyseethatAMQPrequiresalittlebitofsetup,whichisnecessary

Page 419: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

tocreateandconnectallthecomponentsofthemodel.It'salsointerestingtoobservethatamqplibsupportsPromisesbydefault,soweleveragedthemheavilytostreamlinetheasynchronousstepsoftheapplication.Let'sseeindetailhowitworks:

1. WefirstestablishaconnectionwiththeAMQPbroker,whichisRabbitMQinourcase.Then,wecreateachannel—whichissimilartoasession—thatwillmaintainthestateofourcommunications.

2. Next,wesetupourexchange,namedchat.Aswealreadymentioned,itisafanoutexchange.TheassertExchange()commandwillmakesurethattheexchangeexistsonthebroker,otherwiseitwillcreateit.

3. Wealsocreateourqueue,calledchat_history.Bydefault,thequeueisdurable—notexclusiveandnotauto-delete—sowedon'tneedtopassanyextraoptionstosupportdurablesubscribers.

4. Next,webindthequeuetotheexchangewepreviouslycreated.Here,wedon'tneedanyotherparticularoption,forexample,aroutingkeyorpattern,astheexchangeisofthetype,fanout,soitdoesn'tperformanyfiltering.

5. Finally,wecanbegintolistenformessagescomingfromthequeuewejustcreated.WesaveeverymessagethatwereceiveinaLevelDBdatabaseusingamonotonictimestampaskey(https://npmjs.org/package/monotonic-timestamp),tokeepthemessagessortedbydate.It'salsointerestingtoseethatweareacknowledgingeverymessageusingchannel.ack(msg),andonlyafterthemessageissuccessfullysavedintothedatabase.IftheACK(acknowledgment)isnotreceivedbythebroker,themessageiskeptinthequeueforbeingprocessedagain.ThisisanothergreatfeatureofAMQPforbringingthereliabilityofourservicetoawholenewlevel.Ifwearenotinterestedinsendingexplicitacknowledgments,wecanpasstheoption{noAck:true}tothechannel.consume()API.

IntegratingthechatapplicationwithAMQP

TointegratethechatserversusingAMQP,wehavetouseasetupverysimilartotheoneweimplementedinthehistoryservice,sowearenotgoingtorepeatithereinfull.However,it'sstillinterestingtoseehowthequeueiscreatedandhowanewmessageispublishedintotheexchange.Therelevantpartsofthenewapp.jsfile,arethefollowing:

[...]

.then(function(){

returnchannel.assertQueue('chat_srv_'+httpPort,

{exclusive:true});

})

[...]

ws.on('message',function(msg){

console.log('Message:'+msg);

channel.publish('chat','',newBuffer(msg));

Page 420: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

});

[...]

Aswementioned,ourchatserverdoesnotneedtobeadurablesubscriber,asetandforgetparadigmisenough.Sowhenwecreateourqueue,wepasstheoption{exclusive:true}indicatingthatthequeueisscopedtothecurrentconnectionandthereforeitwillbedestroyedassoonasthechatservershutsdown.

Publishinganewmessageisalsoveryeasy,wesimplyhavetospecifythetargetexchange(chat)andaroutingkey,whichinourcaseisempty('')becauseweareusingafanoutexchange.

Wecannowrunourimprovedchatarchitecture,todothatlet'sstarttwochatserversandthehistoryservice:

nodeapp8080

nodeapp8081

nodehistorySvc

Itisnowinterestingtoseehowoursystem,andinparticularthehistoryservice,behavesincaseofdowntime.IfwestopthehistoryserverandcontinuetosendmessagesusingthewebUIofthechatapplication,wewillseethatwhenthehistoryserverisrestarted,itwillimmediatelyreceiveallthemessagesitmissed.Thisisaperfectdemonstrationofhowthedurablesubscriberpatternworks!

TipItisnicetoseehowtheMicroserviceapproachallowsoursystemtosurviveevenwithoutoneofitscomponents—thehistoryservice.Therewouldbeatemporaryreductionoffunctionality(nochathistoryavailable)butpeoplewouldstillbeabletoexchangechatmessagesinrealtime.Awesome!

Page 421: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

PipelinesandtaskdistributionpatternsInChapter6,Recipes,welearnedhowtodelegatecostlytaskstomultiplelocalprocesses,buteventhoughthiswasaneffectiveapproach,itcannotbescaledbeyondtheboundariesofasinglemachine.Inthissection,wearegoingtoseehowit'spossibletouseasimilarpatterninadistributedarchitecture,usingremoteworkers,locatedanywhereinanetwork.

Theideaistohaveamessagingpatternthatallowsustospreadtasksacrossmultiplemachines.Thesetasksmightbeindividualchunksofworkorpiecesofabiggertasksplitusingadivideandconquertechnique.

Ifwelookatthelogicalarchitecturerepresentedinthefollowingfigure,weshouldbeabletorecognizeafamiliarpattern:

Aswecanseefromtheprecedingdiagram,thepublish/subscribepatternisnotsuitableforthistypeofapplication,asweabsolutelydon'twantatasktobereceivedbymultipleworkers.Whatweneedinstead,isamessagedistributionpatternsimilartoaloadbalancer,thatdispatcheseachmessagetoadifferentconsumer(alsocalledworker,inthiscase).Inthemessagingsystemsterminology,thispatternis

Page 422: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

knownascompetingconsumers,fan-outdistribution,orventilator.

OneimportantdifferencewiththeHTTPloadbalancerswehaveseeninthepreviouschapter,isthat,here,theconsumershaveamoreactiverole.Infact,aswewillseelater,mostofthetimeit'snottheproducerthatconnectstotheconsumers,buttheyaretheconsumersthemselvesthatconnecttothetaskproducerorthetaskqueueinordertoreceivenewjobs.Thisisagreatadvantageinascalablesystemasitallowsustoseamlesslyincreasethenumberofworkerswithoutmodifyingtheproduceroradoptingaserviceregistry.

Also,inagenericmessagingsystem,wedon'tnecessarilyhavearequest/replycommunicationbetweentheproducerandworkers.Instead,mostofthetime,thepreferredapproachistouseaone-wayasynchronouscommunication,whichenablesabetterparallelismandscalability.Insuchanarchitecture,messagescanpotentiallytravelalwaysinonedirection,creatingpipelines,asshowninthefollowingfigure:

Pipelinesallowustobuildverycomplexprocessingarchitectureswithouttheburdenofasynchronousrequest/replycommunication,oftenresultinginlowerlatencyandhigherthroughput.Intheprecedingfigure,wecanseehowmessagescanbedistributedacrossasetofworkers(fan-out),forwardedtootherprocessingunits,andthenaggregatedintoasinglenode(fan-in),usuallycalledsink.

Inthissection,wearegoingtofocusonthebuildingblocksofthesekindsofarchitectures,byanalyzingthetwomostimportantvariations:peer-to-peerandbroker-based.

TipThecombinationofapipelinewithataskdistributionpatternisalsocalledparallel

Page 423: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

pipeline.

TheØMQfan-out/fan-inpatternWehavealreadydiscoveredsomeofthecapabilitiesofØMQforbuildingpeer-to-peerdistributedarchitectures.Intheprevioussection,weusedPUBandSUBsocketstodisseminateasinglemessagetomultipleconsumers;nowwearegoingtoseehowit'spossibletobuildparallelpipelinesusinganotherpairofsocketscalledPUSHandPULL.

PUSH/PULLsocketsIntuitively,wecansaythatthePUSHsocketsaremadeforsendingmessages,whilethePULLsocketsaremeantforreceiving.Itmightseematrivialcombination;however,theyhavesomenicecharacteristicsthatmakethemperfectforbuildingone-waycommunicationsystems:

Bothcanworkinconnectmodeorbindmode.Inotherwords,wecanbuildaPUSHsocketandbindittoalocalportlisteningfortheincomingconnectionsfromaPULLsocket,orviceversa,aPULLsocketmightlistenforconnectionsfromaPUSHsocket.Themessagesalwaystravelinthesamedirection,fromPUSHtoPULL,it'sonlytheinitiatoroftheconnectionthatcanbedifferent.Thebindmodeisthebestsolutionfordurablenodes,asforexample,thetaskproducerandthesink,whiletheconnectmodeisperfectfortransientnodes,asforexample,thetaskworkers.Thisallowsthenumberoftransientnodestovaryarbitrarilywithoutaffectingthemoredurablenodes.IftherearemultiplePULLsocketsconnectedtoasinglePUSHsocket,themessagesareevenlydistributedacrossallthePULLsockets,inpractice,theyareloadbalanced(peer-to-peerloadbalancing!).Ontheotherhand,aPULLsocketthatreceivesmessagesfrommultiplePUSHsocketswillprocessthemessagesusingafairqueuingsystem,whichmeansthattheyareconsumedevenlyfromallthesources—around-robinappliedtoinboundmessages.ThemessagessentoveraPUSHsocketthatdoesn'thaveanyconnectedPULLsocket,donotgetlost;theyareinsteadqueuedupontheproduceruntilanodecomesonlineandstartspullingthemessages.

WearenowstartingtounderstandhowØMQisdifferentfromtraditionalWebservicesandwhyit'saperfecttoolforbuildinganykindofmessagingsystem.

BuildingadistributedhashsumcrackerwithØMQ

Page 424: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,it'stimetobuildasampleapplicationtoseeinactionthepropertiesofthePUSH/PULLsocketswejustdescribed.

Asimpleandfascinatingapplicationtoworkwithwouldbeahashsumcracker,asystemthatusesabruteforcetechniquetotrytomatchagivenhashsum(MD5,SHA1,andsoon)toeverypossiblevariationofcharactersofagivenalphabet.Thisisanembarrassinglyparallelworkload(http://en.wikipedia.org/wiki/Embarrassingly_parallel),whichisperfectforbuildinganexampledemonstratingthepowerofparallelpipelines.

Forourapplication,wewanttoimplementatypicalparallelpipelinewithanodetocreateanddistributetasksacrossmultipleworkers,plusanodetocollectalltheresults.ThesystemwejustdescribedcanbeimplementedinØMQusingthefollowingarchitecture:

Inourarchitecture,wehaveaventilatorgeneratingallthepossiblevariationsofcharactersinagivenalphabetanddistributingthemtoasetofworkers,whichinturncalculatethehashsumofeverygivenvariationandtrytomatchitagainstthehashsumgivenastheinput.Ifamatchisfound,theresultissenttoaresultscollectornode(sink).

Thedurablenodesofourarchitecturearetheventilatorandthesink,whilethetransientnodesaretheworkers.ThismeansthateachworkerconnectsitsPULLsockettotheventilatoranditsPUSHsockettothesink,thiswaywecanstartandstophowmanyworkerswewantwithoutchanginganyparameterintheventilatororthesink.

Implementingtheventilator

Page 425: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,let'sstarttoimplementoursystembycreatinganewmodulefortheventilator,inafilenamedventilator.js:

varzmq=require('zmq');

varvariationsStream=require('variations-stream');

varalphabet='abcdefghijklmnopqrstuvwxyz';

varbatchSize=10000;

varmaxLength=process.argv[2];

varsearchHash=process.argv[3];

varventilator=zmq.socket('push');//[1]

ventilator.bindSync("tcp://*:5000");

varbatch=[];

variationsStream(alphabet,maxLength)

.on('data',function(combination){

batch.push(combination);

if(batch.length===batchSize){//[2]

varmsg={searchHash:searchHash,variations:batch};

ventilator.send(JSON.stringify(msg));

batch=[];

}

})

.on('end',function(){

//sendremainingcombinations

varmsg={searchHash:searchHash,variations:batch};

ventilator.send(JSON.stringify(msg));

});

Toavoidgeneratingtoomanyvariations,ourgeneratorusesonlythelowercaselettersoftheEnglishalphabetandsetsalimitonthesizeofthewordsgenerated.Thislimitisprovidedininputasacommandlineargument(maxLength)togetherwiththehashsumtomatch(searchHash).Weusealibrarycalledvariations-stream(https://npmjs.org/package/variations-stream)togenerateallthevariationsusingastreaminginterface.

Butthepartthatwearemostinterestedinanalyzing,ishowwedistributethetasksacrosstheworkers:

1. WefirstcreateaPUSHsocketandwebindittothelocalport5000,thisiswherethePULLsocketoftheworkerswillconnecttoreceivetheirtasks.

2. Wegroupthegeneratedvariationsinbatchesof10,000itemseachandthenwecraftamessagethatcontainsthehashtomatchandthebatchofwordstocheck.Thisisessentiallythetaskobjectthattheworkerswillreceive.Whenweinvokesend()overtheventilatorsocket,themessagewillbepassedtothenextavailableworker,followingaround-robindistribution.

Implementingtheworker

Page 426: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Now,it'stimetoimplementtheworker(worker.js):

varzmq=require('zmq');

varcrypto=require('crypto');

varfromVentilator=zmq.socket('pull');

vartoSink=zmq.socket('push');

fromVentilator.connect('tcp://localhost:5000');

toSink.connect('tcp://localhost:5001');

fromVentilator.on('message',function(buffer){

varmsg=JSON.parse(buffer);

varvariations=msg.variations;

variations.forEach(function(word){

console.log('Processing:'+word);

varshasum=crypto.createHash('sha1');

shasum.update(word);

vardigest=shasum.digest('hex');

if(digest===msg.searchHash){

console.log('Found!=>'+word);

toSink.send('Found!'+digest+'=>'+word);

}

});

});

Aswesaid,ourworkerrepresentsatransientnodeinourarchitecture,thereforeitssocketsshouldconnecttoaremotenodeinsteadoflisteningfortheincomingconnections.That'sexactlywhatwedoinourworker,wecreatetwosockets:

APULLsocketthatconnectstotheventilator,forreceivingthetasks.APUSHsocketthatconnectstothesink,forpropagatingtheresults.

Besidesthis,thejobdonebyourworkerisverysimple:foreachmessagereceivedweiterateoverthebatchofwordsitcontains,thenforeachwordwecalculatetheSHA1checksumandwetrytomatchitagainstthesearchHashpassedwiththemessage.Whenamatchisfound,theresultisforwardedtothesink.

Implementingthesink

Forourexample,thesinkisaverybasicresultcollector,whichsimplyprintsthemessagesreceivedbytheworkerstotheconsole.Thecontentsofthefilesink.jsareasfollows:

varzmq=require('zmq')

varsink=zmq.socket('pull');

sink.bindSync("tcp://*:5001");

sink.on('message',function(buffer){

console.log('Messagefromworker:',buffer.toString());

Page 427: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

});

It'sinterestingtoseethatthesink(astheventilator)isalsoadurablenodeofourarchitectureandthereforewebinditsPULLsocketinsteadofconnectingitexplicitlytothePUSHsocketoftheworkers.

Runningtheapplication

Wearenowreadytolaunchourapplication,let'sstartacoupleofworkersandthesink:

nodeworker

nodeworker

nodesink

Then,it'stimetostarttheventilator,specifyingthemaximumlengthofthewordstogenerateandtheSHA1checksumthatwewanttomatch.Thefollowingisasamplelistofarguments:

nodeventilator4f8e966d1e207d02c44511a58dccff2f5429e9a3b

Whentheprecedingcommandisrun,theventilatorwillstartgeneratingallthepossiblewordsthathavealengthofatmostfourcharacters,distributingthemtothesetofworkerswestarted,alongwiththechecksumweprovided.Theresultsofthecomputation,ifany,willappearintheterminalofthesinkapplication.

PipelinesandcompetingconsumersinAMQPIntheprevioussection,wesawhowaparallelpipelinecanbeimplementedinapeer-to-peercontext.Nowwearegoingtoexplorethispatternwhenappliedtoafully-fledgedmessagebroker,suchasRabbitMQ.

Point-to-pointcommunicationsandcompetingconsumersInapeer-to-peerconfiguration,apipelineisaverystraightforwardconcepttopictureinmind.Withamessagebrokerinthemiddlethough,therelationshipbetweenthevariousnodesofthesystemarealittlebithardertounderstand;thebrokeritselfactsasanintermediaryforourcommunicationsandoften,wedon'treallyknowwhoisontheothersidelisteningformessages.Forexample,whenwesendamessageusingAMQP,wedon'tdeliveritdirectlytoitsdestination,butinsteadtoanexchangeandthentoaqueue.Finally,itwillbeforthebrokertodecidewheretoroutethe

Page 428: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

message,basedontherulesdefinedintheexchange,thebindings,andthedestinationqueues.

IfwewanttoimplementapipelineandataskdistributionpatternusingasystemlikeAMQP,wehavetomakesurethateachmessageisreceivedbyonlyoneconsumer,butthisisimpossibletoguaranteeifanexchangecanpotentiallybeboundtomorethanonequeue.Thesolutionthen,istosendamessagedirectlytothedestinationqueue,bypassingtheexchangealtogether,thiswaywecanmakesurethatonlyonequeuewillreceivethemessage.Thiscommunicationpatterniscalledpoint-to-point.

Onceweareabletosendasetofmessagesdirectlytoasinglequeue,wearealreadyhalf-waytoimplementingourtaskdistributionpattern.Infact,thenextstepcomesnaturally:whenmultipleconsumersarelisteningonthesamequeue,themessageswillbedistributedevenlyacrossthem,implementingafan-outdistribution.Inthecontextofmessagebrokers,thisisbetterknownastheCompetingConsumerspattern.

ImplementingthehashsumcrackerusingAMQPWejustlearnedthatexchangesarethepointinabrokerwhereamessageismulticasttoasetofconsumers,whilequeuesaretheplacewheremessagesareloadbalanced.Withthisknowledgeinmind,let'snowimplementourbruteforcehashsumcrackerontopofanAMQPbroker(asforexample,RabbitMQ).Thefollowingfiguregivesanoverviewofthesystemwewanttoobtain:

Aswediscussed,todistributeasetoftasksacrossmultipleworkersweneedtouse

Page 429: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

asinglequeue.Intheprecedingfigure,wecalledthisthejobsqueue.Ontheothersideofthejobsqueue,wehaveasetofworkers,whicharecompetingconsumers,inotherwords,eachonewillpulladifferentmessagefromthequeue.Theresultisthatmultipletaskswillexecuteinparallelondifferentworkers.

Anyresultgeneratedbytheworkersispublishedintoanotherqueue,whichwecalledresultsqueue,andthenconsumedbytheresultscollector;thisisactuallyequivalenttoasink,orfan-indistribution.Intheentirearchitecture,wedon'tmakeuseofanyexchange,weonlysendmessagesdirectlytotheirdestinationqueue,implementingapoint-to-pointcommunication.

Implementingtheproducer

Let'sseehowtoimplementsuchasystem,startingfromtheproducer(thevariationsgenerator).Itscodeisidenticaltothesamplewehaveseenintheprevioussectionexceptforthepartsconcerningthemessageexchange.Theproducer.jsfilewilllookasfollows:

varamqp=require('amqplib');

[...]

varconnection,channel;

amqp

.connect('amqp://localhost')

.then(function(conn){

connection=conn;

returnconn.createChannel();

})

.then(function(ch){

channel=ch;

produce();

})

.catch(function(err){

console.log(err);

});

functionproduce(){

[...]

variationsStream(alphabet,maxLength)

.on('data',function(combination){

[...]

varmsg={searchHash:searchHash,variations:batch};

channel.sendToQueue('jobs_queue',

newBuf

fer(JSON.stringify(msg)));

[...]

}

})

[...]

Page 430: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

}

Aswecansee,theabsenceofanyexchangeorbindingmakesthesetupofanAMQPcommunicationmuchsimpler.Intheprecedingcode,wedidn'tevenneedaqueue,asweareinterestedonlyinpublishingamessage.

Themostimportantdetailthough,isthechannel.sendToQueue()API,whichisactuallynewtous.Asitsnamesays,that'stheAPIresponsiblefordeliveringamessagestraighttoaqueue—jobs_queueinourexample—bypassinganyexchangeorrouting.

Implementingtheworker

Ontheothersideofthejobs_queuewehavetheworkerslisteningfortheincomingtasks.Let'simplementtheircodeinafilecalledworker.js,asfollows:

varamqp=require('amqplib');

[...]

varchannel,queue;

amqp

.connect('amqp://localhost')

.then(function(conn){

returnconn.createChannel();

})

.then(function(ch){

channel=ch;

returnchannel.assertQueue('jobs_queue');

})

.then(function(q){

queue=q.queue;

consume();

})

[...]

functionconsume(){

channel.consume(queue,function(msg){

[...]

variations.forEach(function(word){

[...]

if(digest===data.searchHash){

console.log('Found!=>'+word);

channel.sendToQueue('results_queue',

newBuffer('Found!'+digest+'=>'+word));

}

[...]

});

channel.ack(msg);

});

};

Page 431: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

OurnewworkerisalsoverysimilartotheoneweimplementedintheprevioussectionusingØMQ,exceptforthepartrelatedtothemessageexchange.Intheprecedingcode,wecanseehowwefirstmakesurethatjobs_queueexistsandthenwestartlisteningforincomingtasksusingchannel.consume().Then,everytimeamatchisfound,wesendtheresulttothecollectorviaresults_queue,usingagainapoint-to-pointcommunication.

Ifmultipleworkersarestarted,theywillalllistenonthesamequeue,resultinginthemessagestobeloadbalancedbetweenthem.

Implementingtheresultcollector

Theresultscollectorisagainatrivialmodule,simplyprintinganymessagereceivedtotheconsole.Thisisimplementedinthecollector.jsfile,asfollows:

[...]

.then(function(ch){

channel=ch;

returnchannel.assertQueue('results_queue');

})

.then(function(q){

queue=q.queue;

channel.consume(queue,function(msg){

console.log('Messagefromworker:',

msg.content.toString());

});

})

[...]

Runningtheapplication

Noweverythingisreadytogiveournewsystematry,wecanstartbyrunningacoupleofworkers,whichwillbothconnecttothesamequeue(jobs_queue),sothateverymessagewillbeloadbalancedbetweenthem:

nodeworker

nodeworker

Then,wecanrunthecollectormoduleandthenproducer(byprovidingthemaximumwordlengthandthehashtocrack):

nodecollector

nodeproducer4f8e966d1e207d02c44511a58dccff2f5429e9a3b

Withthis,weimplementedamessagepipelineandthecompetingconsumerspatternusingnothingbutAMQP.

Page 432: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Request/replypatternsDealingwithamessagingsystemoftenmeansusingaone-wayasynchronouscommunication;publish/subscribeisaperfectexample.

One-waycommunicationscangiveusgreatadvantagesintermsofparallelismandefficiency,butalonetheyarenotabletosolveallourintegrationandcommunicationproblems.Sometimes,agoodoldrequest/replypatternmightjustbetheperfecttoolforthejob.Therefore,inallthosesituationswhereanasynchronousone-waychannelisallthatwehave,it'simportanttoknowhowtobuildanabstractionthatallowsustoexchangemessagesinarequest/replyfashion.That'sexactlywhatwearegoingtolearnnext.

CorrelationidentifierThefirstrequest/replypatternwearegoingtolearniscalledcorrelationidentifieranditrepresentsthebasicblockforbuildingarequest/replyabstractionontopofaone-waychannel.

Thepatternconsistsinmarkingeachrequestwithanidentifier,whichisthenattachedtotheresponsebythereceiver;thiswaythesenderoftherequestcancorrelatethetwomessagesandreturntheresponsetotherighthandler.Thiselegantlysolvestheprobleminpresenceofaone-wayasynchronouschannelwheremessagescantravelinanydirectionatanytime.Let'stakealookattheexampleinthefollowingfigure:

Page 433: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

TheprecedingscenarioshowshowusingacorrelationIDallowsustomatcheachresponsewiththerightrequest,evenifthosearesentandthenreceivedinadifferentorder.

Implementingarequest/replyabstractionusingcorrelationidentifiersLet'snowstartworkingonanexamplebychoosingthemostsimpletypeoftheone-waychannels,onethatispoint-to-point(whichdirectlyconnectstwonodesofthesystem)andfull-duplex(messagescantravelinbothdirections).

Inthesimplechannelcategory,wecanfind,forexample,WebSockets:theyestablishapoint-to-pointconnectionbetweentheserverandbrowser,andthemessagescantravelinanydirection.Anotherexample,isthecommunicationchannelthatiscreatedwhenachildprocessisspawnedusingchild_process.fork(),weshouldalreadyknowaboutit,wesawthisAPIinChapter6,Recipes.Thischanneltooisasynchronous,itconnectstheparentonlywiththechildprocessanditallowsmessagestotravelinanydirection.Thisisprobablythemostbasicchannelofthiscategory,sothat'swhatwearegoingtouseinournextexample.

Theplanforthenextapplicationistobuildanabstractioninordertowrapthechannelcreatedbetweentheparentandchildprocesses.Thisabstractionshouldprovidearequest/replycommunicationbyautomaticallymarkingeachrequestwithacorrelation

Page 434: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

identifierandthenmatchingtheIDofanyincomingreplyagainstthelistofrequesthandlersawaitingaresponse.

FromChapter6,Recipes,weshouldrememberthattheparentprocesscanaccessthechannelwiththechildusingtwoprimitives:

child.send(message)

child.on('message',callback)

Inasimilarway,thechildcanaccessthechanneltotheparentprocessusing:

process.send(message)

process.on('message',callback)

Thismeansthattheinterfaceofthechannelavailableintheparentisidenticaltotheoneavailableinthechild;thiswillallowustobuildacommonabstraction,sothattherequestscanbesentfromboththeendsofthechannel.

Abstractingtherequest

Let'sstartbuildingthisabstractionbyconsideringthepartresponsibleforsendingnewrequests,let'screateanewfilecalledrequest.js:

varuuid=require('node-uuid');

module.exports=function(channel){

varidToCallbackMap={};//[1]

channel.on('message',function(message){//[2]

varhandler=idToCallbackMap[message.inReplyTo];

if(handler){

handler(message.data);

}

});

returnfunctionsendRequest(req,callback){//[3]

varcorrelationId=uuid.v4();

idToCallbackMap[correlationId]=callback;

channel.send({

type:'request',

data:req,

id:correlationId

});

};

}

Thisishowourrequestabstractionworks:

Page 435: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

1. Theonethatfollowsisaclosurecreatedaroundourrequestfunction.ThemagicofthepatternliesintheidToCallbackMapvariable,whichstorestheassociationbetweentheoutgoingrequestsandtheirreplyhandlers.

2. Assoonasthefactoryisinvoked,thefirstthingwedoisstartlisteningforincomingmessages.IfthecorrelationIDofthemessage(containedintheinReplyToproperty)matchesanyoftheIDscontainedintheidToCallbackMapvariable,weknowthatwejustreceivedareply,soweobtainthereferencetotheassociatedresponsehandlerandweinvokeitwiththedatacontainedinthemessage.

3. Finally,wereturnthefunctionwewillusetosendnewrequests.ItsjobistogenerateacorrelationIDusingthenode-uuidpackage(https://npmjs.org/package/node-uuid)andthenwraptherequestdatainanenvelopethatallowsustospecifythecorrelationIDandthetypeofthemessage.

That'sitfortherequestmodule;let'smovetothenextpart.

Abstractingthereply

Wearejustastepawayfromimplementingthefullpattern,solet'sseehowthecounterpartoftherequest.jsmoduleworks.Let'screateanotherfilecalledreply.js,whichwillcontaintheabstractionforwrappingthereplyhandler:

module.exports=function(channel){

returnfunctionregisterHandler(handler){

channel.on('message',function(message){//[1]

if(message.type!=='request')return;

handler(message.data,function(reply){

channel.send({//[2]

type:'response',

data:reply,

inReplyTo:message.id

});

});

});

};

}

Ourreplymoduleisagainafactorythatreturnsafunctiontoregisternewreplyhandlers.Thisiswhathappenswhenanewhandlerisregistered:

1. Westartlisteningforincomingrequestsandwhenwereceiveone,weimmediatelyinvokehandlerbypassingthedataofthemessageandacallbackfunctiontocollectthereplyfromthehandler.

Page 436: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

2. Oncethehandlerhasdoneitswork,itwillinvokethecallbackthatweprovidedreturningbackitsreply.WethenbuildanenvelopebyattachingthecorrelationIDoftherequest(theinReplyToproperty),thenweputeverythingbackintothechannel.

TheamazingthingofthispatternisthatinNode.jsitcomesveryeasy,everythingforusisalreadyasynchronous,soanasynchronousrequest/replycommunicationbuiltontopofaone-waychannelisnotverydifferentfromanyotherasynchronousoperation,especiallyifwebuildanabstractiontohideitsimplementationdetails.

Tryingthefullrequest/replycycle

Now,wearereadytotryournewasynchronousrequest/replyabstraction.Let'screateasamplereplierinafilenamedreplier.js:

varreply=require('./reply')(process);

reply(function(req,callback){

setTimeout(function(){

callback({sum:req.a+req.b});

},req.delay);

});

Ourrepliersimplycalculatesthesumbetweenthetwonumbersreceivedandreturnstheresultafteracertaindelay(whichisalsospecifiedintherequest).Thiswillallowustoverifythattheorderoftheresponsescanalsobedifferentfromtheorderinwhichwesenttherequests,toconfirmthatourpatternisworking.

Thefinalsteptocompletethesampleistocreatetherequestorinafilenamedrequestor.js,whichalsohasthetaskofstartingthereplierusingchild_process.fork():

varreplier=require('child_process')

.fork(__dirname+'/replier.js');

varrequest=require('./request')(replier);

request({a:1,b:2,delay:500},function(res){

console.log('1+2=',res.sum);

replier.disconnect();

});

request({a:6,b:1,delay:100},function(res){

console.log('6+1=',res.sum);

});

Therequestorstartsthereplierandthenpassesitsreferencetoourrequestabstraction.Wethenrunacoupleofsamplerequestsandverifythatthecorrelation

Page 437: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

withtheresponsetheyreceiveisright.

Totryoutthesample,simplylaunchtherequestor.jsmodule,theoutputshouldbesomethingsimilartothefollowing:

6+1=7

1+2=3

Thisconfirmsthatourpatternworksperfectlyfineandthattherepliesarecorrectlyassociatedwiththeirownrequests,nomatterinwhatordertheyaresentorreceived.

ReturnaddressThecorrelationidentifieristhefundamentalpatternforcreatingarequest/replycommunicationontopofaone-waychannel;however,it'snotenoughwhenourmessagingarchitecturehasmorethanonechannelorqueue,orwhentherecanbepotentiallymorethanonerequestor.Inthesesituations,inadditiontoacorrelationID,wealsoneedtoknowthereturnaddress,apieceofinformationwhichallowstherepliertosendtheresponsebacktotheoriginalsenderoftherequest.

ImplementingthereturnaddresspatterninAMQPInAMQP,thereturnaddressisthequeuewheretherequestorislisteningforincomingreplies.Becausetheresponseismeanttobereceivedbyonlyonerequestor,it'simportantthatthequeueisprivateandnotsharedacrossdifferentconsumers.Fromtheseproperties,wecaninferthatwearegoingtoneedatransientqueue,scopedtotheconnectionoftherequestorandthatthereplierhastoestablishapoint-to-pointcommunicationwiththereturnqueue,tobeabletodeliveritsresponses.

Thefollowingimagegivesusanexampleofthisscenario:

Page 438: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Tocreatearequest/replypatternontopofAMQP,allthatweneedtodoistospecifythenameoftheresponsequeueinthemessageproperties,thiswaythereplierknowswheretheresponsemessagehastobedelivered.Thetheoryseemsverystraightforward,solet'sseehowtoimplementthisinarealapplication.

Implementingtherequestabstraction

Let'snowbuildarequest/replyabstractionontopofAMQP.WewilluseRabbitMQasabroker,butanycompatibleAMQPbrokershoulddothejob.Let'sstartwiththerequestabstraction(implementedintheamqpRequest.jsmodule),wewillshowhereonlytherelevantparts.

Thefirstinterestingthingtoobserve,ishowwecreatethequeuetoholdtheresponses;thisisthecoderesponsibleforthat:

channel.assertQueue('',{exclusive:true});

Whenwecreatethequeue,wedon'tspecifyanyname,whichmeansthatarandomonewillbechosenforus;inadditiontothis,thequeueisexclusive,whichmeansthatit'sboundtotheactiveAMQPconnectionanditwillbedestroyedwhentheconnectioncloses.Thereisnoneedtobindthequeuetoanexchange,aswedon'tneedanyroutingordistributiontothemultiplequeues,thismeansthatthemessageshavetobedeliveredstraightintoourresponsequeue.

Page 439: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Next,let'sseehowwecangenerateanewrequest:

AMQPRequest.prototype.request=function(queue,message,

callback){

varid=uuid.v4();

this.idToCallbackMap[id]=callback;

this.channel.sendToQueue(queue,

newBuffer(JSON.stringify(message)),

{correlationId:id,replyTo:this.replyQueue}

);

}

Therequest()methodacceptsasinputthenameoftherequestsqueueandthemessagetosend.Aswelearnedintheprevioussection,weneedtogenerateacorrelationIDandassociateittothecallbackfunction.Finally,wesendthemessage,specifyingthecorrelationIdandthereplyTopropertyasmetadata.

It'sinterestingtoseethatforsendingthemessageweareusingthechannel.sentToQueue()APIinsteadofchannel.publish();thisisbecausewearenotinterestedinimplementinganypublish/subscribedistributionusingexchanges,butamorebasicpoint-to-pointdeliverystraightintothedestinationqueue.

TipInAMQP,wecanspecifyasetofproperties(ormetadata),tobepassedtotheconsumertogetherwiththemainmessage.

ThelastimportantpieceofouramqpRequestprototypeiswherewelistenforincomingresponses:

AMQPRequest.prototype._listenForResponses=function(){

varself=this;

returnthis.channel.consume(this.replyQueue,function(msg){

varcorrelationId=msg.properties.correlationId;

varhandler=self.idToCallbackMap[correlationId];

if(handler){

handler(JSON.parse(msg.content.toString()));

}

});

}

Intheprecedingcode,welistenformessagesonthequeuewecreatedexplicitlyforreceivingresponses,thenforeachincomingmessagewereadthecorrelationIDandwematchitagainstthelistofhandlersawaitingareply.Oncewehavethehandler,weonlyneedtoinvokeitbypassingthereplymessage.

Page 440: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Implementingthereplyabstraction

That'sitfortheamqpRequestmodule,nowit'stimetoimplementtheresponseabstractioninanewmodulenamedamqpReply.js.

Here,wehavetocreatethequeuethatwillreceivetheincomingrequests;wecanuseasimpledurablequeueforthispurpose.Wewon'tshowthispart,sinceit'sagainallAMQPboilerplate.Whatweareinterestedinseeinginstead,ishowwehandlearequestandthensenditbacktotherightqueue:

AMQPReply.prototype.handleRequest=function(handler){

varself=this;

returnself.channel.consume(self.queue,function(msg){

varcontent=JSON.parse(msg.content.toString());

handler(content,function(reply){

self.channel.sendToQueue(

msg.properties.replyTo,

newBuffer(JSON.stringify(reply)),

{correlationId:msg.properties.correlationId}

);

self.channel.ack(msg);

});

});

}

Whensendingbackareply,weusechannel.sendToQueue()topublishthemessagestraightintothequeuespecifiedinthereplyTopropertyofthemessage(ourreturnaddress).AnotherimportanttaskofouramqpReplyobjectistosetacorrelationIdinthereply,sothatthereceivercanmatchthemessagewiththelistofpendingrequests.

Implementingtherequestorandthereplier

Everythingisnowreadytogiveoursystematry,butfirst,let'sbuildasamplerequestorandrepliertoseehowtouseournewabstraction.

Let'sstartfromthemodulereplier.js:

varReply=require('./amqpReply');

varreply=Reply('requests_queue');

reply.initialize().then(function(){

reply.handleRequest(function(req,callback){

console.log('Requestreceived',req);

callback({sum:req.a+req.b});

});

});

Page 441: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

It'snicetoseehowtheabstractionwebuiltallowsustohideallthemechanismsthathandlethecorrelationIDandthereturnaddress;allweneedtodoistoinitializeanewreplyobject,specifyingthenameofthequeuewherewewanttoreceiveourrequests('requests_queue').Therestofthecodeisjusttrivial;oursamplerepliersimplycalculatesthesumofthetwonumbersreceivedasinputandsendsbacktheresultusingtheprovidedcallback.

Ontheotherside,wehaveasamplerequestorimplementedintherequestor.jsfile:

varreq=require('./amqpRequest')();

req.initialize().then(function(){

for(vari=100;i>0;i--){

sendRandomRequest();

}

});

functionsendRandomRequest(){

vara=Math.round(Math.random()*100);

varb=Math.round(Math.random()*100);

req.request('requests_queue',{a:a,b:b},

function(res){

console.log(a+'+'+b+'='+res.sum);

}

);

}

Oursamplerequestorsends100randomrequeststothe'requests_queue'queue.Inthiscasetoo,it'sinterestingtoseethatourabstractionisdoingitsjobperfectly,hidingallthedetailsoftheasynchronousrequest/replypattern.

Now,totryoutthesystem,simplyruntherepliermodulefollowedbyrequestormodule:

nodereplier

noderequestor

Wewillseeasetofoperationspublishedbytherequestorandthenreceivedbythereplier,whichinturnwillsendbacktheresponses.

Now,wecantryotherexperiments.Oncethereplierisstartedforthefirsttime,itcreatesadurablequeue;thismeansthat,ifwenowstopitandthenruntherequestoragain,thennorequestwillbelost.Allthemessageswillbestoredinthequeueuntilthereplierisstartedagain!

AnothernicefeaturethatwegetforfreeusingAMQPisthefactthatourreplierisscalableout-of-the-box.Totestthisassumption,wecantrytostarttwoormore

Page 442: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

instancesofthereplier,andwatchtherequestsbeingloadbalancedbetweenthem.Thisworksbecause,everytimearequestorstarts,itattachesitselfasalistenertothesamedurablequeue,andasaresult,thebrokerwillloadbalancethemessagesacrossalltheconsumersofthequeue(competingconsumerspattern).Sweet!

TipØMQhasapairofsocketsspecificallymeantforimplementingrequest/replypatterns(REQ/REP),howevertheyaresynchronous(onlyonerequest/responseatatime).Morecomplexrequest/replypatternsarepossiblewithmoresophisticatedtechniques.Formoreinformation,youcanreadtheofficialguideathttp://zguide.zeromq.org/page:all#advanced-request-reply.

Page 443: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

SummaryWereachedtheendofthischapter.Here,welearnedthemostimportantmessagingandintegrationpatternsandtheroletheyplayinthedesignofdistributedsystems.Wemadeouracquaintancewiththethreemajortypesofmessageexchangepatterns:publish/subscribe,pipelines,andrequest/reply,andwesawhowtheycanbeimplementedusingapeer-to-peerarchitectureoramessagebroker.Weanalyzedtheirprosandcons,andwesawthatbyusingAMQPandafull-fledgedmessagebroker,wecanimplementreliableandscalableapplicationswithlittledevelopmenteffortbutatacostofhavingonemoresystemtomaintainandscale.Also,wesawhowØMQallowsustobuilddistributedsystemswherewecanhavetotalcontrolovereveryaspectofthearchitecture,finetuningitspropertiesaroundourveryownrequirements.

Thischapteralsoclosesthebook,bynowweshouldhaveatoolbeltfullofpatternsandtechniqueswecangoandapplyinourprojects.WeshouldalsohaveamoredeepunderstandingofhowNode.jsdevelopmentworksandwhatareitsstrengthsandweaknesses.Throughoutthebook,wealsohadthechancetoworkwithamyriadofpackagesandsolutionsdevelopedbymanyextraordinarydevelopers.Attheend,thisisthemostbeautifulaspectofNode.js,itspeople,acommunitywhereeverybodyplaysitspartingivingbacksomething.

Ihopeyouenjoyedmysmallcontribution.

Page 444: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

IndexA

Adaptee/AdapterAdapterpattern

about/AdapterLevelUP,usingthroughfilesystemAPI/UsingLevelUPthroughthefilesystemAPIexamples/Inthewild

add()function

about/Synchronouscontinuation-passingstyle

AdvancedMessageQueuingProtocol(AMQP)

about/Peer-to-peerorbroker-basedmessagingURL/Peer-to-peerorbroker-basedmessaging

advisedmethod/Proxyintheecosystem–functionhooksandAOPaggressivecachingpattern/AsynchronousrequestcachingAMQP

about/IntroducingAMQPqueue/IntroducingAMQPexchange/IntroducingAMQPbinding/IntroducingAMQPusing/DurablesubscriberswithAMQPandRabbitMQreal-timechatapplication,integrating/IntegratingthechatapplicationwithAMQP

amqplibpackage

URL/Designingahistoryserviceforthechatapplication

AngularJS

URL/DeclaringasetofdependenciestoaDIcontainer

Apacheab

Page 445: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

URL/BuildingasimpleHTTPserver

APIorchestration

about/APIorchestrationdataaggregation/APIorchestration

APIOrchestrator/APIorchestrationAPIproxy/TheAPIproxyAPIs,ES6promises

constructor/Promises/A+implementationsstaticmethods,ofPromiseobject/Promises/A+implementationsmethods,ofPromiseinstance/Promises/A+implementations

Application-controlledextension

versusPlugin-controlled/Plugin-controlledvsApplication-controlledextension

appmodule

about/Theappmodule

args-listmodule

URL/RefactoringtheauthenticationservertouseaDIcontainer

arguments,constructor

resolve(obj)/Promises/A+implementationsreject(err)/Promises/A+implementations

AspectOrientedProgramming(AOP)/Proxyintheecosystem–functionhooksandAOPasync.queue()function

about/Limitedparallelexecution

asynchronousbatching

about/Asynchronousbatchingandcaching

asynchronouscaching

Page 446: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Asynchronousbatchingandcaching

asynchronouscallbackpattern

about/Synchronousorasynchronous?unpredictablefunction/AnunpredictablefunctionZalgo,unleashing/UnleashingZalgounpredictablefunction,using/UnleashingZalgosynchronousAPIs,using/UsingsynchronousAPIsexecution,deferring/Deferredexecution

asynchronouscontrolflow,streams

about/Asynchronouscontrolflowwithstreamssequentialexecution/Sequentialexecutionunorderedparallelexecution/Unorderedparallelexecutionunorderedlimitedparallelexecution/Unorderedlimitedparallelexecution

asynchronouscontrolflow,withgenerators

about/Asynchronouscontrolflowwithgeneratorsgenerator-basedcontrolflow,usingco/Generator-basedcontrolflowusingco

asynchronousfunction

about/Asynchronouscontinuation-passingstyle

asynchronouslyinitializedmodules

requiring/Requiringasynchronouslyinitializedmodulescanonicalsolutions/Canonicalsolutionspreinitializationqueues/Preinitializationqueuesimplementing/Implementingamodulethatinitializesasynchronously

asynchronousmessaging

about/Asynchronousmessagingandqueues

AsynchronousModuleDefinition(AMD)/UniversalModuleDefinitionasynchronousprogramming

challenges/Thedifficultiesofasynchronousprogramming

asynchronousrequestbatching

Page 447: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Asynchronousrequestbatchingbatchingrequests,intotalsaleswebserver/Batchingrequestsinthetotalsaleswebserver

asynchronousrequestcaching

about/Asynchronousrequestcachingcachingrequests,intotalsaleswebserver/Cachingrequestsinthetotalsaleswebserver

asynclibrary

about/Theasynclibraryreferencelink/Theasynclibrarysequentialexecution/Sequentialexecutionparallelexecution/Parallelexecutionlimitedparallelexecution/Limitedparallelexecutionpros/Comparisoncons/Comparison

authControllermodule

about/TheauthControllermodule,Refactoringtheauthenticationservertousedependencyinjection

authenticationserver

building,hardcodeddependenciesused/Buildinganauthenticationserverusinghardcodeddependenciesabout/Buildinganauthenticationserverusinghardcodeddependenciesrunning/Runningtheauthenticationserverrefactoring,fordependencyinjectionusage/Refactoringtheauthenticationservertousedependencyinjectionrefactoring,forservicelocatorusage/Refactoringtheauthenticationservertouseaservicelocatorrefactoring,forDIcontainerusage/RefactoringtheauthenticationservertouseaDIcontainer

authServicemodule

about/TheauthServicemodule,Refactoringtheauthenticationservertouseaservicelocator

availability,clustermodule/Resiliencyandavailabilitywiththeclustermodule

Page 448: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Bback-pressure

about/Back-pressure

back-pressure,writablestreams/Back-pressureBackboneModels

used,forsharingbusinesslogic/SharingbusinesslogicanddatavalidationusingBackboneModelsused,forsharingdatavalidation/SharingbusinesslogicanddatavalidationusingBackboneModelssharedmodels,implementing/Implementingthesharedmodelsplatform-specificcode,implementing/Implementingtheplatform-specificcodeisomorphicmodels,using/Usingtheisomorphicmodelsapplication,running/Runningtheapplication

BackboneViews

URL/SharingbusinesslogicanddatavalidationusingBackboneModels

basicconcepts,generators

about/Thebasicssimpleexample/Asimpleexamplegenerators,asiterators/Generatorsasiteratorsvalues,returningtogenerator/Passingvaluesbacktoagenerator

batching

about/Asynchronousrequestbatching

batching,withPromises/BatchingandcachingwithPromisesbatchoperations

URL/ImplementingaLevelUPplugin

binding,AMQP

about/IntroducingAMQP

Bluebird

Page 449: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

URL/Promises/A+implementations

Bower

URL/TheadvantagesofusingBrowserify

brew

URL/LoadbalancingwithNginx

browser

code,sharingwith/Sharingcodewiththebrowser

Browserify

about/ConsiderationsontheUMDpattern,IntroducingBrowserifyURL/IntroducingBrowserify,TheadvantagesofusingBrowserifydemonstrating/ExploringthemagicofBrowserifyadvantages/TheadvantagesofusingBrowserify

Browserify,invokingfromGrunt

URL/TheadvantagesofusingBrowserify

Browserify,invokingfromGulp

URL/TheadvantagesofusingBrowserify

buffering

versusstreaming/BufferingvsStreaming

bundle

about/IntroducingBrowserify

bunyan

URL/Inthewild

businesslogic

sharing,BackboneModelsused/SharingbusinesslogicanddatavalidationusingBackboneModels

Page 450: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

busy-waiting

about/Non-blockingI/O

Ccaching

about/Asynchronousbatchingandcaching

caching,withPromises/BatchingandcachingwithPromisescachingmechanisms

implementing/Notesaboutimplementingcachingmechanisms

callbackfunction

about/Thereactorpattern

callbackhell

about/Thecallbackhell

callbackpattern

about/Thecallbackpatterncontinuation-passingstyle/Thecontinuation-passingstyleasynchronous/Synchronousorasynchronous?synchronous/Synchronousorasynchronous?Node.jscallbackconventions/Node.jscallbackconventions

canonicalsolutions

about/Canonicalsolutions

centralregistry

about/Servicelocator

ChainofResponsibilitypattern/Middlewareasapatternchallenges,asynchronousprogramming

simplewebspider,creating/Creatingasimplewebspidercallbackhell/Thecallbackhell

Page 451: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

chancemodule

URL/ImplementingReadablestreams

channels

about/Multiplexinganddemultiplexing,UsingRedisasamessagebroker,IntroducingAMQP

Checkoutservice/Integrationwithamessagebrokerchildprocess

URL/Communicatingwithachildprocess

childprocesses

about/Usingmultipleprocesses

child_process.fork()

URL/Runningthemux/demuxapplication

child_processAPI

URL/Communicatingwiththeparentprocess

cloning/Cloningandloadbalancingclosures

about/Thecallbackpatternreferencelink/Thecallbackpattern

Cloud-basedproxies/Scalingwithareverseproxyclustermodule

about/Theclustermodulebehavior/NotesonthebehavioroftheclustermoduleURL/NotesonthebehavioroftheclustermodulesimpleHTTPserver,building/BuildingasimpleHTTPserversimpleHTTPserver,profiling/BuildingasimpleHTTPserverscaling/Scalingwiththeclustermoduleresiliency/Resiliencyandavailabilitywiththeclustermoduleavailability/Resiliencyandavailabilitywiththeclustermodulezero-downtimerestart/Zero-downtimerestart

Page 452: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

co

referencelink/Generator-basedcontrolflowusingcoabout/Generator-basedcontrolflowusingcoyieldables/Generator-basedcontrolflowusingco

co-limiter

referencelink/Limitedparallelexecution

code

sharing,withbrowser/Sharingcodewiththebrowser

codeprofiler,Factorypattern

about/Buildingasimplecodeprofiler

cohesion

about/CohesionandCoupling

combine-streampackage

URL/Combiningstreams

combinedstream

advantages/Combiningstreamsimplementing/Implementingacombinedstream

Command-lineInterface(CLI)/GzippingusingabufferedAPICommandMessage

about/Messagetypes

Commandpattern

about/Commandadvantages/Commandflexiblepattern/Aflexiblepattern

CommonJSmodules

about/Node.jsmodulesexplained

Page 453: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

CommonJSmodulesystem

about/Modulesanddependencies

competingconsumers

about/Point-to-pointcommunicationsandcompetingconsumers

competingconsumerspattern

about/Point-to-pointcommunicationsandcompetingconsumers

competingconsumerspattern

about/Pipelinesandtaskdistributionpatterns

competitiverace

about/Thepattern

complexapplications

decomposing/Decomposingcomplexapplicationsmonolithicarchitecture/Monolithicarchitecturemicroservicearchitecture/TheMicroservicearchitecturemicroservicearchitecture,integrationpatterns/IntegrationpatternsinaMicroservicearchitecture

Component

URL/TheadvantagesofusingBrowserify

composabilityapproach,streams

about/Gzippingusingstreams

/Composabilityconfigurationmanagertemplate/AconfigurationmanagertemplateconsistentReadSync()function

about/UsingsynchronousAPIs

constructor

about/Promises/A+implementations

Page 454: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

arguments/Promises/A+implementations

constructorinjection

about/Thedifferenttypesofdependencyinjection

continuation-passingstyle

about/Thecontinuation-passingstylesynchronouscontinuation-passingstyle/Synchronouscontinuation-passingstyleasynchronouscontinuation-passingstyle/Asynchronouscontinuation-passingstylenoncontinuation-passingstylecallbacks/Noncontinuation-passingstylecallbacks

continuousdeliveryprocess/Zero-downtimerestartcorrelationidentifier

about/Correlationidentifierused,forimplementingrequest/replyabstraction/Implementingarequest/replyabstractionusingcorrelationidentifiers

CouchDB

URL/Sharingthestateacrossmultipleinstances

CouchUP

URL/IntroducingLevelUPandLevelDB

coupling

about/Modulesanddependencies,CohesionandCouplingtightlycoupled/CohesionandCouplinglooselycoupled/CohesionandCoupling

CPU-bound

about/RunningCPU-boundtasksinterleavingpattern,withsetImmediate/InterleavingwithsetImmediatemultipleprocesses,using/Usingmultipleprocesses

CPU-boundtasks

Page 455: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

running/RunningCPU-boundtaskssubsetsumproblem,solving/Solvingthesubsetsumproblem

Cross-originresourcesharing(CORS)/Buildinganauthenticationserverusinghardcodeddependenciescross-platformcode/Sharingcodewiththebrowsercross-platformdevelopment

fundamentals/Fundamentalsofcross-platformdevelopmentdesignpatterns/Designpatternsforcross-platformdevelopment

Ddataownership/AnexampleoftheMicroservicearchitecturedatavalidation

sharing,BackboneModelsused/SharingbusinesslogicanddatavalidationusingBackboneModels

dbmodule

about/Thedbmodule,Refactoringtheauthenticationservertouseaservicelocator,Canonicalsolutions

Decoratorpattern

about/Decoratorimplementing,techniques/Techniquesforimplementingdecorators

deferreds

about/Promises/A+implementations

demultiplexer(demux)/Multiplexinganddemultiplexingdemultiplexing

about/Multiplexinganddemultiplexingserversideapplication,creating/Serverside–demultiplexing

DenialofService(DoS)/LimitedparallelexecutionDenialofService(DoS)attacks

about/Delegatingthesubsetsumtasktootherprocesses

Page 456: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

dependencies

about/Modulesanddependencies

dependency

about/ThemostcommondependencyinNode.jsbetweenmodules/ThemostcommondependencyinNode.js

dependencygraph

about/Modulesanddependencies

dependencyhellproblem/Smallmodulesdependencyinjection(DI)

about/Dependencyinjectionpros/Prosandconsofdependencyinjectioncons/Prosandconsofdependencyinjectionused,forexposingservices/Exposingservicesusingdependencyinjection

dependencyinjection(DI),types

constructorinjection/Thedifferenttypesofdependencyinjectionpropertyinjection/Thedifferenttypesofdependencyinjection

dependencyInjectioncontainers

URL/ProsandconsofaDependencyInjectioncontainer

dependencyinjectioncontainers

about/Dependencyinjection,Prosandconsofdependencyinjection,Dependencyinjectioncontainerpros/ProsandconsofaDependencyInjectioncontainercons/ProsandconsofaDependencyInjectioncontainerused,forexposingservices/Exposingservicesusingadependencyinjectioncontainer

Derby

URL/Runningtheapplication

designpattern

Page 457: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Factory/FactoryProxy/ProxyDecorator/DecoratorAdapter/AdapterStrategy/StrategyState/StateTemplate/TemplateMiddleware/MiddlewareCommand/Command

designpatterns,cross-platformdevelopment

about/Designpatternsforcross-platformdevelopmentStrategy/Designpatternsforcross-platformdevelopmentTemplate/Designpatternsforcross-platformdevelopmentAdapter/Designpatternsforcross-platformdevelopmentProxy/Designpatternsforcross-platformdevelopmentObserver/Designpatternsforcross-platformdevelopmentDependencyInjection/Designpatternsforcross-platformdevelopmentServicelocator/Designpatternsforcross-platformdevelopment

DIcontainer

setofdependencies,declaringto/DeclaringasetofdependenciestoaDIcontainer

dimensions,scalability

XAxis/ThethreedimensionsofscalabilityYAxis/ThethreedimensionsofscalabilityZAxis/Thethreedimensionsofscalability

directstyle

about/Synchronouscontinuation-passingstyle

distributedhashsumcracker,withØMQ

building/BuildingadistributedhashsumcrackerwithØMQventilator,implementing/Implementingtheventilatorworker,implementing/Implementingtheworkersink,implementing/Implementingthesinkexecuting/Runningtheapplication

Page 458: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Dnode

URL/Inthewild

docpad

URL/Pluginsaspackages

DocumentMessage

about/Messagetypes

Don'tRepeatYourself(DRY)principle

about/Smallmodules

ducktyping

URL/Buildingasimplecodeprofiler

duplexer2module

URL/Combiningstreams

Duplexstream

about/Duplexstreamscreating/Duplexstreams

durablesubscribers

about/DurablesubscribersAMQP/IntroducingAMQPwithAMQP/DurablesubscriberswithAMQPandRabbitMQwithRabbitMQ/DurablesubscriberswithAMQPandRabbitMQreal-timechatapplication/Designingahistoryserviceforthechatapplication

dynamicloadbalancer

implementing,withhttp-proxy/Implementingadynamicloadbalancerwithhttp-proxyandseaportimplementing,withseaport/Implementingadynamicloadbalancerwithhttp-proxyandseaport

Page 459: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

dynamicscaling/UsingaServiceRegistry

Eecstaticmiddleware

URL/Implementingtheserversideabout/Implementingtheserverside

elasticsearch

URL/Reusabilityacrossplatformsandlanguages

embarrassinglyparallelworkload

about/BuildingadistributedhashsumcrackerwithØMQreferencelink/BuildingadistributedhashsumcrackerwithØMQ

encapsulationmechanism/Amechanismtoenforceencapsulationenvelope

about/Abstractingtherequest

ES6

about/Promises

ES6promises

about/Promises/A+implementationsAPIs/Promises/A+implementations

ES6WeakMaps

URL/Amechanismtoenforceencapsulation

EventEmitterfunction

about/TheEventEmitteron(event,listener)method/TheEventEmitteronce(event,listener)method/TheEventEmitteremit(event,[arg1],[])method/TheEventEmitterremoveListener(event,listener)method/TheEventEmitterusing/CreateanduseanEventEmitter

Page 460: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

creating/CreateanduseanEventEmitterfilereadevent/CreateanduseanEventEmitterfoundevent/CreateanduseanEventEmittererrorevent/CreateanduseanEventEmitter

EventEmitters

about/Extensionpoints

eventloop

about/Eventdemultiplexing

EventMessage

about/Messagetypes

eventnotificationinterface

about/Eventdemultiplexing

exchange,AMQP

about/IntroducingAMQPdirectexchange/IntroducingAMQPtopicexchange/IntroducingAMQPfanoutexchange/IntroducingAMQP

Express

URL/MiddlewareinExpressMiddleware/MiddlewareinExpress

express

URL/Buildinganauthenticationserverusinghardcodeddependencies,Pluginsaspackages

expressmiddleware

about/MiddlewareinExpresstasks/MiddlewareinExpress

extensionpoints

Page 461: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Extensionpoints

Ezel

URL/Runningtheapplication

Ffactoryinjection/ThedifferenttypesofdependencyinjectionFactorypattern

about/Factorygenericinterface,forcreatingobjects/Agenericinterfaceforcreatingobjectsencapsulationmechanism/Amechanismtoenforceencapsulationsimplecodeprofiler,building/BuildingasimplecodeprofilerDnode/InthewildRestify/Inthewild

fail-safesocket

implementing/Implementingabasicfail-safesocket

fairqueuingsystem

about/PUSH/PULLsockets

fan-outdistribution

about/Pipelinesandtaskdistributionpatterns

Fibers

referencelink/Comparison

flexiblepattern

taskpattern/Thetaskpatterncomplexcommand/Amorecomplexcommand

flowingmode,readablestreams/Theflowingmodeforever

URL/LoadbalancingwithNginx

Page 462: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

forwardproxy

URL/Scalingwithareverseproxy

framework/Plugin-controlledvsApplication-controlledextensionfrom2package

URL/throughandfrom

frompackage/throughandfrom

URL/throughandfrom

fsmodule

createReadStream()method/GettingstartedwithstreamscreateWriteStream()method/Gettingstartedwithstreams

fstreampackage

URL/Creatingatarballfrommultipledirectories

functionhooking/Proxyintheecosystem–functionhooksandAOPfundamentals,cross-platformdevelopment

about/Fundamentalsofcross-platformdevelopmentruntimecodebranching/Runtimecodebranchingbuild-timecodebranching/Build-timecodebranching

Ggateway

about/Scalingwithareverseproxy

generators

about/Generatorsbasics/Thebasicsasynchronouscontrolflow,withgenerators/Asynchronouscontrolflowwithgeneratorssequentialexecution/Sequentialexecutionparallelexecution/Parallelexecutionlimitedparallelexecution/Limitedparallelexecution

Page 463: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

pros/Comparisoncons/Comparison

GodObject/IntegrationwithamessagebrokerGrunt

URL/Exposingservicesusingdependencyinjectionabout/Exposingservicesusingdependencyinjection

gulp

URL/Pluginsaspackages

Hhandler

about/Thereactorpattern

HAProxy

URL/Scalingwithareverseproxy

hardcodeddependencies

used,forbuildingauthenticationserver/Buildinganauthenticationserverusinghardcodeddependenciespros/Prosandconsofhardcodeddependenciescons/Prosandconsofhardcodeddependenciesusing/Usinghardcodeddependencies

hardcodeddependency

about/Hardcodeddependency

Harmony

about/Promises

hashsumcracker,withAMQP

implementing/ImplementingthehashsumcrackerusingAMQPproducer,implementing/Implementingtheproducerworker,implementing/Implementingtheworker

Page 464: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

resultcollector,implementing/Implementingtheresultcollectorexecuting/Runningtheapplication

Hollywoodprinciple

about/Plugin-controlledvsApplication-controlledextension

hooks

URL/Proxyintheecosystem–functionhooksandAOPabout/Extensionpoints

horizontalpartitioning/Thethreedimensionsofscalabilityhorizontalscaling/Cloningandloadbalancinghttp-proxy

URL/Inthewild,Implementingadynamicloadbalancerwithhttp-proxyandseaport

HTTPserver

building/BuildingasimpleHTTPserverprofiling/BuildingasimpleHTTPserver

II/OCompletionPortAPI(IOCP)/Thenon-blockingI/OengineofNode.js–libuvimplementation/Statefulmodulesimplementationtechniques,Decoratorpattern

composing,using/Compositionobjectaugmentation/ObjectaugmentationLevelUPdatabase,decorating/DecoratingaLevelUPdatabase

implementationtechniques,Proxypattern

objectcomposition/Objectcompositionobjectaugmentation(monkeypatching)/Objectaugmentation

inconsistentRead()function

about/UnleashingZalgo

IndexedDB/IntroducingLevelUPandLevelDB

Page 465: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

informationhiding/ThemostcommondependencyinNode.jsinjectedservicelocator

about/Servicelocator

integrationpatterns,microservicearchitecture

about/IntegrationpatternsinaMicroservicearchitectureAPIproxy/TheAPIproxyAPIorchestration/APIorchestrationmessagebroker/Integrationwithamessagebroker

InterceptingFilterpattern

about/MiddlewareasapatternURL/Middlewareasapattern

interface/Statefulmodulesinterleavingpattern

considerations/Considerationsontheinterleavingpattern

interleavingpattern,withsetImmediate

about/InterleavingwithsetImmediatestepsofsubsetsumalgorithm,interleaving/Interleavingthestepsofthesubsetsumalgorithm

InversionofControl(IoC)

about/Plugin-controlledvsApplication-controlledextensionURL/Plugin-controlledvsApplication-controlledextension

isomorphic

about/Usingtheisomorphicmodels

Jjson-over-tcplibrary

URL/Implementingabasicfail-safesocket

JSONmessages

Page 466: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

supporting,withmiddleware/AmiddlewaretosupportJSONmessages

JSONWebToken(JWT)

about/BuildinganauthenticationserverusinghardcodeddependenciesURL/Buildinganauthenticationserverusinghardcodeddependencies

jugglingdb

URL/Inthewild

KKeepItSimple,Stupid(KISS)principle/Simplicityandpragmatismkernelpanic/MonolithicarchitectureKISSprinciple/Thedifficultiesofasynchronousprogrammingkoa

referencelink/Generator-basedcontrolflowusingco

LLeastRecentlyUsed(LRU)algorithm/Notesaboutimplementingcachingmechanismslevel-filesystem

URL/Inthewild

level-inverted-index

URL/Inthewild

level-plus

URL/Inthewild

level.js

URL/UsingLevelUPthroughthefilesystemAPI

LevelDB

about/IntroducingLevelUPandLevelDB

Page 467: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

levelgraph

URL/IntroducingLevelUPandLevelDB

levelofindirection

about/Modulesanddependencies

levelpackage

URL/ImplementingaLevelUPplugin

levelup

URL/Buildinganauthenticationserverusinghardcodeddependencies

LevelUPdatabase

decorating/DecoratingaLevelUPdatabaseURL/IntroducingLevelUPandLevelDBabout/IntroducingLevelUPandLevelDB

LevelUPecosystem

URL/IntroducingLevelUPandLevelDB

LevelUPplugin

implementing/ImplementingaLevelUPplugin

libuv

about/Thenon-blockingI/OengineofNode.js–libuvreferencelink/Thenon-blockingI/OengineofNode.js–libuv

limitedparallelexecution,asynclibrary

about/Limitedparallelexecution

limitedparallelexecution,generators

about/Limitedparallelexecutionoptions/Limitedparallelexecutionproducer-consumerpattern/Producer-consumerpatterndownloadtasksconcurrency,limiting/Limitingthedownloadtasks

Page 468: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

concurrency

limitedparallelexecution,plainJavaScript

about/Limitedparallelexecutionconcurrency,limiting/Limitingtheconcurrencyconcurrency,limitingglobally/Globallylimitingtheconcurrencyqueues/Queuestotherescuewebspiderversion4/Webspiderversion4

limitedparallelexecution,promise

about/Limitedparallelexecution

loadbalancer/Scalingwithareverseproxyloadbalancing/Cloningandloadbalancing,Notesonthebehavioroftheclustermoduleloaddistribution/ThethreedimensionsofscalabilityloggingWritablestream

creating/CreatingaloggingWritablestream

logoutplugin

implementing/Implementingalogoutplugin

lookup/Thethreedimensionsofscalability

MMainfunction

about/Parallelexecution

makeGenerator()function/Thebasicsmasterprocess/Theclustermodulemeld

URL/Proxyintheecosystem–functionhooksandAOP

Memcached

URL/Notesaboutimplementingcachingmechanisms,Sharingthestateacrossmultipleinstances

Page 469: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

memoization

about/Cachingrequestsinthetotalsaleswebserver

memoizeepackage

URL/Cachingrequestsinthetotalsaleswebserver

merge-streammodule

URL/Creatingatarballfrommultipledirectories

messagebroker

integration/Integrationwithamessagebrokerabout/Integrationwithamessagebroker

MessageBroker

about/Peer-to-peerorbroker-basedmessaging

messagequeue

about/Asynchronousmessagingandqueues

messagequeue(MQ)

about/Durablesubscribers

MessageQueueTelemetryTransport(MQTT)

about/Peer-to-peerorbroker-basedmessagingURL/Peer-to-peerorbroker-basedmessaging

messages

about/Messagetypes

messages,types

CommandMessage/MessagetypesEventMessage/MessagetypesDocumentMessage/Messagetypes

messagingsystem

Page 470: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

fundamentals/Fundamentalsofamessagingsystemone-waypattern/One-wayandrequest/replypatternsrequest/replypattern/One-wayandrequest/replypatternsasynchronousmessaging/Asynchronousmessagingandqueuesqueues/Asynchronousmessagingandqueuespeer-to-peermessaging/Peer-to-peerorbroker-basedmessaging

Meteor

URL/Runningtheapplication

methods,Promiseinstance

promise.then(onFulfilled,onRejected)/Promises/A+implementationspromise.catch(onRejected)method/Promises/A+implementations

microservicearchitecture

about/TheMicroservicearchitectureloosecoupling/TheMicroservicearchitecturehighcohesion/TheMicroservicearchitectureintegrationcomplexity/TheMicroservicearchitectureexample/AnexampleoftheMicroservicearchitecturedataownership/AnexampleoftheMicroservicearchitectureintegrationpatterns/IntegrationpatternsinaMicroservicearchitecture

microservices

about/Thethreedimensionsofscalabilitypros/Prosandconsofmicroservices,Reusabilityacrossplatformsandlanguagescons/Prosandconsofmicroservices,AwaytoscaletheapplicationURL/Prosandconsofmicroservicesexpendableservice/Everyserviceisexpendableplatformreusability/Reusabilityacrossplatformsandlanguagesapplication,scaling/Awaytoscaletheapplicationchallenges/Thechallengesofmicroservices

Middleware

about/MiddlewareinExpress/MiddlewareinExpressaspattern/Middlewareasapatternusing,aspattern/Middlewareasapattern

Page 471: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

middlewareframework,forØMQ/Creatingamiddlewareframeworkfor ØMQ

middleware

about/Proxyintheecosystem–functionhooksandAOP,Asynchronousmessagingandqueues

Middleware,usingaspattern

about/Middlewareasapatternsteps/Middlewareasapattern

middlewareframework

creating,forØMQ/Creatingamiddlewareframeworkfor ØMQMiddlewareManager/TheMiddlewareManager

middlewareframework,creatingforØMQ

MiddlewareManager/TheMiddlewareManagerjsonmiddleware/AmiddlewaretosupportJSONmessagesØMQmiddlewareframework,using/UsingtheØMQmiddlewareframework

MiddlewareManager

about/Middlewareasapattern

minification/DeclaringasetofdependenciestoaDIcontainerminificators/DeclaringasetofdependenciestoaDIcontainerminimistpackage

URL/UsingtheØMQPUB/SUBsockets

Mkdirpdependency

about/Creatingasimplewebspider

models

about/SharingbusinesslogicanddatavalidationusingBackboneModels

module

Page 472: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Smallmoduleswrapping,withpreinitializationqueues/Wrappingthemodulewithpreinitializationqueues

moduledefinitionpatterns

about/Moduledefinitionpatternsnamedexports/Namedexportsfunction,exporting/Exportingafunctionconstructor,exporting/Exportingaconstructorinstance,exporting/Exportinganinstancemodules,modifyingonglobalscope/Modifyingothermodulesortheglobalscope

moduleimpersonationpattern/Usinghardcodeddependenciesmodules

about/Modulesanddependenciesproperties/ThemostcommondependencyinNode.jssharing/Sharingmodules

modulesystem

about/Themodulesystemanditspatternspatterns/Themodulesystemanditspatternsrevealingmodulepattern/Therevealingmodulepattern

modulewiringpatterns

about/Patternsforwiringmoduleshardcodeddependency/Hardcodeddependencydependencyinjection(DI)/Dependencyinjectionservicelocator/Servicelocatordependencyinjectioncontainer/Dependencyinjectioncontainer

MongoDB

URL/Sharingthestateacrossmultipleinstances

Mongoose

about/InthewildURL/Inthewildreferences/Inthewild

Page 473: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Monit

URL/LoadbalancingwithNginx

monkeypatching

about/Modifyingothermodulesortheglobalscope

monkeypatching(Objectaugmentation)/Objectaugmentationmonolithicapplications

about/Thethreedimensionsofscalability

monolithicarchitecture

about/Monolithicarchitecture

monolithickernels/Monolithicarchitecturemonotonictimestamp

using/ImplementingareliablehistoryserviceusingAMQPURL/ImplementingareliablehistoryserviceusingAMQP

multipipepackage

URL/Combiningstreams

multipledirectories

tarball,creatingfrom/Creatingatarballfrommultipledirectories

multiplexer(mux)/Multiplexinganddemultiplexingmultiplexing

about/Multiplexinganddemultiplexingclientsideapplication,creating/Clientside–Multiplexing

multiprocesspattern

considerations/Considerationsonthemultiprocesspattern

multistream-mergemodule

URL/Creatingatarballfrommultipledirectories

multistreampackage

Page 474: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

URL/Creatingatarballfrommultipledirectories

Mustache

URL/CreatinganUMDmoduleabout/CreatinganUMDmodule

mux/demuxapplication

running/Runningthemux/demuxapplication

ØMQ

middlewareframework,creatingfor/Creatingamiddlewareframeworkfor ØMQURL/UsingtheØMQmiddlewareframework,Peer-to-peerloadbalancing,Peer-to-peerorbroker-basedmessaging,IntroducingØMQabout/IntroducingØMQURL,forinstallation/Designingapeer-to-peerarchitectureforthechatserver

ØMQfan-out/fan-inpattern

about/TheØMQfan-out/fan-inpatternPUSH/PULLsockets/PUSH/PULLsocketsdistributedhashsumcracker,building/BuildingadistributedhashsumcrackerwithØMQ

ØMQmiddlewareframework

using/UsingtheØMQmiddlewareframeworkserver/Theserverclient/Theclient

ØMQPUB/SUBsockets

using/UsingtheØMQPUB/SUBsockets

Nnamedexports

about/Namedexports

Page 475: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

namemangling/DeclaringasetofdependenciestoaDIcontainerNginx

URL/Scalingwithareverseproxy,LoadbalancingwithNginxinstalling,URL/LoadbalancingwithNginxloadbalancing/LoadbalancingwithNginx

node-core

about/TherecipeforNode.js

node-globmodule

referencelink/CombinecallbacksandEventEmitter

node-uuidpackage

using/AbstractingtherequestURL/Abstractingtherequest

Node.js

URL,forblog/Anatomyofstreams

Node.js-basedproxies/ScalingwithareverseproxyNode.jsapplications

scaling/ScalingNode.jsapplications

Node.jscallbackconventions

about/Node.jscallbackconventionscallbacks,passingaslastargument/Callbackscomelasterror,passingasfirstargument/Errorcomesfirsterrorpropagation/Propagatingerrorsuncaughtexceptions/Uncaughtexceptions

Node.jsmodules

about/Node.jsmodulesexplainedhomemademoduleloader/Ahomemademoduleloaderdefining/Definingamoduleglobals,defining/Definingglobalsmodule.exports,versusexports/module.exportsvsexportsrequirefunction/requireissynchronous

Page 476: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

resolvingalgorithm/Theresolvingalgorithmmodulecache/Themodulecachecycles/Cycles

Node.jsphilosophy

about/TheNode.jsphilosophysimplicity/Simplicityandpragmatismpragmatism/Simplicityandpragmatism

Node.jsstylefunction

promisifying/PromisifyingaNode.jsstylefunction

nodebb

URL/Pluginsaspackages

nodebb-plugin-mentions

URL/Usinghardcodeddependencies

nodebb-plugin-poll

URL/Usinghardcodeddependencies

non-flowingmode,readablestreams/Thenon-flowingmodenormalfunction

about/Generators

npmdependencies

request/CreatingasimplewebspiderMkdirp/Creatingasimplewebspider

nscale

URL/Thechallengesofmicroservices

OObject-DocumentMapping(ODM)/Inthewildobject-pathlibrary

Page 477: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

URL/Multi-formatconfigurationobjects

Object.defineProperty()

URL/Acomparisonofthedifferenttechniques

objectstreams

multiplexing/Multiplexinganddemultiplexingobjectstreamsdemultiplexing/Multiplexinganddemultiplexingobjectstreams

Observerpattern

about/Publish/subscribepattern

observerpattern

about/TheobserverpatternEventEmitter/TheEventEmitterEventEmitter,using/CreateanduseanEventEmitterEventEmitter,creating/CreateanduseanEventEmittererrors,propagating/Propagatingerrorsobservableobject,creating/Makeanyobjectobservablesynchronousevents/Synchronousandasynchronouseventsasynchronousevents/SynchronousandasynchronouseventsEventEmitter,versuscallbacks/EventEmittervsCallbackscallbacks,combiningwithEventEmitter/CombinecallbacksandEventEmitter

one-waypattern

about/One-wayandrequest/replypatterns

operatingmodes,streams

binary/Anatomyofstreamsobject/Anatomyofstreams

OperationalTransformation(OT)/Command

URL/Command

options,stream.Writable

highWaterMark/ImplementingWritablestreams

Page 478: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

decodeStrings/ImplementingWritablestreams

orderedparallelexecution,streams/Orderedparallelexecutionorganization,Commandpattern

Command/CommandClient/CommandInvoker/CommandTarget(orReceiver)/Command

Ppackages

about/Smallmodules

packages,streams

about/Usefulpackagesforworkingwithstreamsreadable-stream/readable-streamthrough/throughandfromfrom/throughandfrom

packetswitching

about/Buildingaremotelogger

parallelexecution,asynclibrary

about/Parallelexecution

parallelexecution,generators

about/Parallelexecution

parallelexecution,plainJavaScript

about/Parallelexecutionwebspiderversion3/Webspiderversion3pattern/Thepatternraceconditions,fixing/Fixingraceconditionsinthepresenceofconcurrenttasks

parallelexecution,promise

Page 479: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Parallelexecution

parallelpipeline

about/PipelinesandtaskdistributionpatternsØMQfan-out/fan-inpattern/TheØMQfan-out/fan-inpattern

Passport.js

URL/Inthewild

peer-to-peerloadbalancing

about/Peer-to-peerloadbalancingHTTPclient,implementingformultipleserverrequestbalancing/ImplementinganHTTPclientthatcanbalancerequestsacrossmultipleservers

peer-to-peermessaging

about/Peer-to-peerorbroker-basedmessaging

peer-to-peerpublish/subscribepattern

withØMQ/Peer-to-peerpublish/subscribewithØMQarchitecture,designing/Designingapeer-to-peerarchitectureforthechatserverØMQPUB/SUBsockets,using/UsingtheØMQPUB/SUBsockets

pipe()method/Composabilitypipeline/Middlewareasapatternpipelines

about/Pipelinesandtaskdistributionpatternscreating/Pipelinesandtaskdistributionpatternscompetingconsumers/PipelinesandcompetingconsumersinAMQPpoint-to-pointcommunications/Point-to-pointcommunicationsandcompetingconsumers

pipes

used,forconnectingstreams/Connectingstreamsusingpipes

pipingpatterns

Page 480: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Pipingpatterns

plainJavaScript,using

about/UsingplainJavaScriptcallbackdiscipline/Callbackdisciplinecallbackdiscipline,applying/Applyingthecallbackdisciplinesequentialexecution/Sequentialexecutionparallelexecution/Parallelexecutionlimitedparallelexecution/Limitedparallelexecutionpros/Comparisoncons/Comparison

Plugin-controlled

versusApplication-controlledextension/Plugin-controlledvsApplication-controlledextension

plugininfrastructure/Plugin-controlledvsApplication-controlledextensionplugins

about/Wiringplugins

plugins,aspackages

example/Pluginsaspackages

pm2

URL/Zero-downtimerestart,LoadbalancingwithNginx

point-to-pointcommunications

about/Point-to-pointcommunicationsandcompetingconsumershashsumcracker,implementing/ImplementingthehashsumcrackerusingAMQP

PostgreSQL

URL/Sharingthestateacrossmultipleinstances

PouchDB

URL/IntroducingLevelUPandLevelDB

Page 481: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

preinitializationqueues

about/Preinitializationqueuesasynchronouslyinitializedmodules,implementing/Implementingamodulethatinitializesasynchronouslymodule,wrappingwith/Wrappingthemodulewithpreinitializationqueues

process.nextTick()

about/Deferredexecution

process.nextTick()function/Considerationsontheinterleavingpatternproducer-consumerpattern/Limitedparallelexecutionpromise

about/Promises,Whatisapromise?working/Whatisapromise?Promises/A+implementations/Promises/A+implementationsNode.jsstylefunction,promisifying/PromisifyingaNode.jsstylefunctionsequentialexecution/Sequentialexecutionparallelexecution/Parallelexecutionlimitedparallelexecution/Limitedparallelexecutionpros/Comparisoncons/Comparison

Promise.all(array)method

about/Promises/A+implementations

promise.catch(onRejected)method

about/Promises/A+implementations

Promise.reject(err)method

about/Promises/A+implementations

Promise.resolve(obj)method

about/Promises/A+implementations

promise.then(onFulfilled,onRejected)method

about/Promises/A+implementations

Page 482: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Promises

dealing,withcaching/BatchingandcachingwithPromisesdealing,withbatching/BatchingandcachingwithPromises

Promises/A+specification

URL/Promises,Whatisapromise?implementing/Promises/A+implementations

promisification

about/PromisifyingaNode.jsstylefunction

promisifiedversion

about/PromisifyingaNode.jsstylefunction

propertyinjection

about/Thedifferenttypesofdependencyinjection

Proxypattern

about/Proxyimplementing,techniques/Techniquesforimplementingproxiestechniques,comparing/AcomparisonofthedifferenttechniquesloggingWritablestream,creating/CreatingaloggingWritablestreamfunctionhooking/Proxyintheecosystem–functionhooksandAOPAspectOrientedProgramming(AOP)/Proxyintheecosystem–functionhooksandAOP

pseudo-classicalinheritance/Objectcompositionpublish/subscribepattern/Integrationwithamessagebroker

about/Publish/subscribepatternreal-timechatapplication,building/Buildingaminimalistreal-timechatapplicationRedis,using/UsingRedisasamessagebrokerpeer-to-peerpublish/subscribe,withØMQ/Peer-to-peerpublish/subscribewithØMQdurablesubscribers/Durablesubscribers

publisher

Page 483: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Publish/subscribepattern

QQlibrary

URL/Promises/A+implementationsreferencelink/Promises/A+implementations

QualityofService(QoS)

about/DurablesubscribersQoS0,atmostonce/DurablesubscribersQoS1,atleastonce/DurablesubscribersQoS2,exactlyonce/Durablesubscribers

queue,AMQP

about/IntroducingAMQPdurable/IntroducingAMQPexclusive/IntroducingAMQPauto-delete/IntroducingAMQP

queues

about/Queuestotherescue,Asynchronousmessagingandqueues

RRabbitMQ

URL/Peer-to-peerorbroker-basedmessaging,Designingahistoryserviceforthechatapplicationusing/DurablesubscriberswithAMQPandRabbitMQURL,forinstallation/Designingahistoryserviceforthechatapplication

React

URL/Runningtheapplication

reactorpattern

core/Smallcoremodules/Smallmodules

Page 484: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

surfacearea/Smallsurfaceareaabout/Thereactorpattern,ThereactorpatternI/O/I/OisslowblockingI/O/BlockingI/Onon-blockingI/O/Non-blockingI/Oeventdemultiplexing/Eventdemultiplexingstructure/ThereactorpatternEventDemultiplexer/ThereactorpatternEventQueue/Thereactorpatternlibuv/Thenon-blockingI/OengineofNode.js–libuvcomponents/TherecipeforNode.jsrecipe,forNode.js/TherecipeforNode.js

read()method/Thenon-flowingmodereadable-streampackage

about/readable-streamURL/readable-stream

Readablestream

implementing/ImplementingReadablestreams

readablestreams

about/Readablestreamsnon-flowingmode/Thenon-flowingmodeflowingmode/Theflowingmode

real-timechatapplication

building/Buildingaminimalistreal-timechatapplicationserverside,implementing/Implementingtheserversideclientside,implementing/Implementingtheclientsidescaling/Runningandscalingthechatapplicationexecuting/Runningandscalingthechatapplicationhistoryservice,designing/Designingahistoryserviceforthechatapplicationhistoryservice,implementingwithAMQP/ImplementingareliablehistoryserviceusingAMQPintegrating,withAMQP/IntegratingthechatapplicationwithAMQP

realsingleton

Page 485: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Servicelocator

Redis

URL/Notesaboutimplementingcachingmechanisms,Sharingthestateacrossmultipleinstances,UsingRedisasamessagebrokerabout/UsingRedisasamessagebrokerusing,asmessagebroker/UsingRedisasamessagebrokerURL,forinstallation/UsingRedisasamessagebroker

redispackage

URL/UsingRedisasamessagebrokerabout/UsingRedisasamessagebroker

remotelogger

building/Buildingaremotelogger

RemoteProcedureCall(RPC)

about/Messagetypes

Rendr

URL/Runningtheapplication

request/replyabstraction

implementing,correlationidentifierused/Implementingarequest/replyabstractionusingcorrelationidentifiersrequest,abstracting/Abstractingtherequestreply,abstracting/Abstractingthereplyreplier,creating/Tryingthefullrequest/replycycle

request/replypattern

about/One-wayandrequest/replypatterns,Request/replypatternscorrelationidentifier/Correlationidentifierreturnaddress/Returnaddressreferencelink/Implementingtherequestorandthereplier

requestdependency

about/Creatingasimplewebspider

Page 486: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

require()function

about/Ahomemademoduleloader

RequireJS

URL/UniversalModuleDefinition

resiliency,clustermodule/Resiliencyandavailabilitywiththeclustermoduleresolvingalgorithm

about/Theresolvingalgorithmfilemodules/Theresolvingalgorithmcoremodules/Theresolvingalgorithmpackagemodules/Theresolvingalgorithmreferencelink/Theresolvingalgorithm

Restify

URL/Inthewild

returnaddress

about/Returnaddressimplementing,inAMQP/ImplementingthereturnaddresspatterninAMQPrequestabstraction,implementing/Implementingtherequestabstractionreplyabstraction,implementing/Implementingthereplyabstractionrequestor,implementing/Implementingtherequestorandthereplierreplier,implementing/Implementingtherequestorandthereplier

revealingmodulepattern

about/Therevealingmodulepattern

RevealingModulepattern/CreatinganUMDmodulereverseproxy

scaling/Scalingwithareverseproxyabout/ScalingwithareverseproxyURL/Scalingwithareverseproxyloadbalancing,withNginx/LoadbalancingwithNginx

roundrobin/NotesonthebehavioroftheclustermoduleRSVP

Page 487: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

URL/Promises/A+implementations

Sscalability

about/Anintroductiontoapplicationscalingdimensions/Thethreedimensionsofscalability

scalecube/Thethreedimensionsofscalabilityseaport

URL/Implementingadynamicloadbalancerwithhttp-proxyandseaport

semi-coroutines

about/Generators

Seneca

URL/Thechallengesofmicroservices

sequentialexecution,asynchronouscontrolflowofstreams

about/Sequentialexecution

sequentialexecution,asynclibrary

about/Sequentialexecutionfunctions/Sequentialexecutionknowntasks,executinginsequence/Sequentialexecutionofaknownsetoftaskssequentialiteration/Sequentialiteration

sequentialexecution,generators

about/Sequentialexecution

sequentialexecution,plainJavaScript

about/Sequentialexecutionanalyzing/Sequentialexecutionvariations/Sequentialexecutionknowntasks,executinginsequence/Executingaknownsetoftasksin

Page 488: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

sequencesequentialiteration/Sequentialiterationwebspiderversion2,sequentialiteration/Webspiderversion2sequentialcrawlingoflinks,sequentialiteration/Sequentialcrawlingoflinkspattern,sequentialiteration/Thepattern

sequentialexecution,promise

about/Sequentialexecutionsequentialiteration/Sequentialiterationpattern,sequentialiteration/Sequentialiteration–thepattern

server

implementing,withoutcaching/Implementingaserverwithnocachingorbatchingimplementing,withoutbatching/Implementingaserverwithnocachingorbatching

servicelocator

about/Servicelocatorpros/Prosandconsofaservicelocatorcons/Prosandconsofaservicelocatorreusabilityapproach/Prosandconsofaservicelocatorreadabilityapproach/Prosandconsofaservicelocatorused,forexposingservices/Exposingservicesusingaservicelocator

serviceLocatormodule

factory()method/Refactoringtheauthenticationservertouseaservicelocatorregister()method/Refactoringtheauthenticationservertouseaservicelocatorget()method/Refactoringtheauthenticationservertouseaservicelocator

ServiceRegistry

using/UsingaServiceRegistrydynamicloadbalancer,implementingwithhttp-proxy/Implementingadynamicloadbalancerwithhttp-proxyandseaportdynamicloadbalancer,implementingwithseaport/Implementingadynamicloadbalancerwithhttp-proxyandseaport

Page 489: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

services

exposing,servicelocatorused/Exposingservicesusingaservicelocatorexposing,dependencyinjectionused/Exposingservicesusingdependencyinjectionexposing,dependencyinjectioncontainerused/Exposingservicesusingadependencyinjectioncontainer

setandforgetparadigm

about/Durablesubscribers

sharding/Thethreedimensionsofscalabilitysharedrendering

about/Runningtheapplication

siege

URL/BuildingasimpleHTTPserver

Simple/StreamingTextOrientatedMessagingProtocol(STOMP)

about/Peer-to-peerorbroker-basedmessagingURL/Peer-to-peerorbroker-basedmessaging

SinglePageApplications/BuildinganauthenticationserverusinghardcodeddependenciesSingleton/ExportinganinstanceSingletonpattern

about/Statefulmodules,TheSingletonpatterninNode.js

sink

about/Pipelinesandtaskdistributionpatterns

SLA(ServiceLevelAgreement)/Zero-downtimerestartSocket.io

URL/Stickyloadbalancing

softwaredevelopmentphilosophies

referencelink/TheNode.jsphilosophy

Page 490: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

spatialefficiencyapproach,streams

about/Spatialefficiencygzipping,withbufferedAPI/GzippingusingabufferedAPIgzipping,withstreams/Gzippingusingstreams

splitpackage

URL/ImplementingaURLstatusmonitoringapplication

SSLterminationpoint/Scalingwithareverseproxystandaloneinstances/Scalingwithareverseproxystatefulcommunications

dealingwith/Dealingwithstatefulcommunicationsstate,sharingacrossmultipleinstances/Sharingthestateacrossmultipleinstancesstickyloadbalancing/Stickyloadbalancing

statefulmodules

about/StatefulmodulesSingletonpattern/TheSingletonpatterninNode.js

Statepattern

about/Statefail-safesocket,implementing/Implementingabasicfail-safesocket

staticmethods,Promiseobject

Promise.resolve(obj)/Promises/A+implementationsPromise.reject(err)/Promises/A+implementationsPromise.all(array)/Promises/A+implementations

sticky-session

URL/Stickyloadbalancing

stickyloadbalancing/StickyloadbalancingStorefront-end/APIorchestrationStrategypattern

about/Strategymultiformatconfigurationobject/Multi-formatconfigurationobjects

Page 491: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Passport.js/Inthewild

Streamclass

URL,forAPIs/Writingtoastream

streaming

versusbuffering/BufferingvsStreaming

Streamline

referencelink/Comparison

streams

needfor/Discoveringtheimportanceofstreamsspatialefficiencyapproach/Spatialefficiencytimeefficiencyapproach/Timeefficiencycomposabilityapproach/Composabilityabout/Gettingstartedwithstreamsanatomy/Anatomyofstreamsreadingfrom/Readingfromastreamwritingto/Writingtoastreamconnecting,pipesused/Connectingstreamsusingpipespackages/Usefulpackagesforworkingwithstreamsasynchronouscontrolflow/Asynchronouscontrolflowwithstreamscombining/Combiningstreamsmerging/Mergingstreams

streams,forking

about/Forkingstreamsmultiplechecksumgenerator,implementing/Implementingamultiplechecksumgenerator

Streams1/readable-streamStreams2/readable-streamStreams3/readable-streamsubject/Proxysublevelplugin

URL/Thedbmodule

subscribers

Page 492: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Publish/subscribepattern

subsetsumalgorithm

steps,interleavingof/Interleavingthestepsofthesubsetsumalgorithm

subsetsumproblem

solving/Solvingthesubsetsumproblem

subsetsumtask,delegating

about/Delegatingthesubsetsumtasktootherprocessesprocesspool,implementing/Implementingaprocesspoolchildprocesscommunication/Communicatingwithachildprocessparentprocesscommunication/Communicatingwiththeparentprocess

supervisor/LoadbalancingwithNginxsuspend

referencelink/Generator-basedcontrolflowusingco

synchronouseventdemultiplexer

about/Eventdemultiplexing

synchronousfunction

about/Asynchronouscontinuation-passingstyle

Systemd/LoadbalancingwithNginx

Ttarball

creating,frommultipledirectories/Creatingatarballfrommultipledirectories

tarpackage

URL/Creatingatarballfrommultipledirectories

taskdistributionpattern

Page 493: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Pipelinesandtaskdistributionpatterns

taskpattern

about/Thetaskpattern

TaskQueueclass/Queuestotherescuetechnicaldebt

about/Modulesanddependencies

templatemethods/TemplateTemplatepattern

about/Templateconfigurationmanagertemplate/Aconfigurationmanagertemplate

ternary-streampackage

URL/Multiplexinganddemultiplexingobjectstreams

thenable

about/Whatisapromise?

threads

about/Considerationsonthemultiprocesspattern

through2package

URL/throughandfrom

through2-parallelpackage

URL/Orderedparallelexecution

throughpackage/throughandfrom

URL/throughandfrom

thunk

about/Asynchronouscontrolflowwithgenerators

thunkify

Page 494: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

referencelink/Generator-basedcontrolflowusingco

timeefficiencyapproach,streams

about/Timeefficiency

tolerancetofailures/ScalingNode.jsapplicationstransforms,onproject'swikipage

URL/TheadvantagesofusingBrowserify

Transformstreams

about/Transformstreamsimplementing/ImplementingTransformstreams

UUMDmodule

creating/CreatinganUMDmodule

UMDpattern

considerations/ConsiderationsontheUMDpatternURL/ConsiderationsontheUMDpattern

unorderedlimitedparallelexecution,asynchronouscontrolflowofstreams

about/Unorderedlimitedparallelexecution

unorderedparallelexecution,asynchronouscontrolflowofstreams

about/Unorderedparallelexecutionunorderedparallelstream,implementing/ImplementinganunorderedparallelstreamURLstatusmonitoring,implementing/ImplementingaURLstatusmonitoringapplication

Upstart

URL/LoadbalancingwithNginx

usemultipleprocessors

Page 495: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

about/Usingmultipleprocesses

userland

about/Smallcore

userspace

about/Smallcore

uses,Proxypattern

datavalidation/Proxysecurity/Proxycaching/Proxylazyinitialization/Proxylogging/Proxyremoteobjects/Proxy

VV8

referencelink/Thecallbackhell

variations-streamlibrary

using/ImplementingtheventilatorURL/Implementingtheventilator

ventilator

about/Pipelinesandtaskdistributionpatterns

verticalscaling/Cloningandloadbalancingvmmodule

referencelink/Ahomemademoduleloader

Vow

URL/Promises/A+implementations

W

Page 496: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

watchify

URL/ExploringthemagicofBrowserify

WebSockets

about/RunningandscalingthechatapplicationURL/Runningandscalingthechatapplication

webspider/Creatingasimplewebspiderwebworker-threads

URL/Considerationsonthemultiprocesspattern

When.js

URL/Promises/A+implementations

wiringplugins

about/Wiringplugins

worker

about/Delegatingthesubsetsumtasktootherprocesses

Writablestream

implementing/ImplementingWritablestreams

writablestreams

about/Writablestreamsback-pressure/Back-pressure

wspackage

URL/Implementingtheserverside

Yyieldstatement

about/Generators

Page 497: Table of Contents · An introduction to application scaling Scaling Node.js applications The three dimensions of scalability Cloning and load balancing ... The ØMQ fan-out/fan-in

Zzero-downtimerestart/Zero-downtimerestartzmqpackage

URL/UsingtheØMQPUB/SUBsockets