Three Productive Go Patterns to Put on Your Radar | Blog

文章推薦指數: 80 %
投票人數:10人

Three Productive Go Patterns to Put on Your Radar · The Dependency Injection Pattern · The Client-Side Interface Pattern · The Actor Pattern · Your ... Blog>Engineering>ThreeProductiveGoPatternstoPutonYourRadarThreeProductiveGoPatternstoPutonYourRadarEngineeringThreeProductiveGoPatternstoPutonYourRadarByPeterBourgon|Sep13,2017|10minreadTaggo,golangSummaryThedependencyinjection,client-sideinterface,andactorpatternscanimproveyourproductivity,offeryoumorefreedom,andoptimizeyourprogramming.Bymostdefinitions,withjust26keywords,atersespec,andacommitmenttoorthogonalfeatures,Goisasimplelanguage.Goprogrammersareexpectedtousethebasicbuildingblocksofferedbythelanguagetocomposemorecomplexabstractions.Overtime,best-in-classsolutionstofrequentlyencounteredproblemstendtobediscovered,shared,andreplicated.Thesedesignpatternsdrawheritagefromotherlanguages,butoftenlookandfeeldistinctinGo.I’dliketocastaspotlightonthreepatternsIuseoverandoveragain.TheDependencyInjectionPatternDependencyinjection(DI)isactuallyanumbrellatermthatcanmeanvastlydifferentthingsdependingoncontext.Thecoreideaisthis:Giveorinjectdependenciestoacomponent,ratherthanhavethecomponenttakedependenciesfromtheenvironment.Beyondthat,thingscangetalittlecomplicated.SomepeopleuseDItorefertodependencyinjectionframeworks,typicallyapackageorobjectintowhichyouregisterdependenciesandlaterinjectthemintocomponentsthatusethem,usuallybysomekeyschema.ButthisstyleofDIisn’tagoodmatchforGo,primarilybecauseGolacksthedynamictypingrequiredtoservealiterateAPI.MostDIframeworksinGoresorttostringlytypedkeys(meaningvariablesareoftentypedasstrings),andrelyonreflectiontoreifythedependenciestoconcretetypesorinterfaces,whichisalwaysaredflag.Instead,amorebasicversionofDIisparticularlywell-suitedtoGoprograms.Theinspirationcomesfromfunctionalprogramming,specifically,theideaofclosurescoping.Andit’snothingspecial,really:Justprovideallthedependenciestoacomponentaslineitemsinthecomponent’sconstructor.//NewHandlerconstructsandreturnsauseablerequesthandler. funcNewHandler( db*sql.DB, requestDuration*metrics.Histogram, logger*log.Logger, )*Handler{ return&Handler{ db:    db, dur:   requestDuration, logger:logger, } }Theclearconsequenceofthispatternisthatconstructorsbegintogetverylong,especiallyasbusinesscapabilitygrows.That’sacost.Butthere’salsoanotablebenefit.Namely,there’sgreatvirtueinmakingdependenciesexplicitatthecallsite,especiallytofuturereadersandmaintainersofyourcode.It’simmediatelyobviousthattheHandlertakesandusesadatabase,ahistogram,andalogger.There’snoneedtohuntdowndependencyrelationshipsfarfromthesiteofconstruction.Thewriterpaysacostofkeystrokes,butthereaderreceivesthebenefitofcomprehension.Outsideofhobbyprojects,weknowthatcodeisreadfarmoreoftenthanitiswritten.It’sreasonable,then,tooptimizeforthebenefitofreader—evenifitcomesatsomeexpensetothewriter.Butwehavesometricksupoursleevetomakelongconstructorsforlargecomponentsmoretractable.Oneapproachistouseatightlyscopedconfigstruct,containingonlythosedependenciesusedbythespecificcomponent.It’stypicaltoomitindividualfieldswhenbuildingastruct,soconstructorsshoulddetectnils,whenappropriate,andprovidesanedefaultalternatives.//NewHandlerconstructsandreturnsauseablerequesthandler. funcNewHandler(cHandlerConfig)*Handler{ ifc.RequestDuration==nil{ c.RequestDuration=metrics.NewNopHistogram() } ifc.Logger==nil{ c.Logger=log.NewNopLogger() } return&Handler{ db:    c.DB, dur:   c.RequestDuration, logger:c.Logger, } } //HandlerConfigcapturesthedependenciesusedbytheHandler. typeHandlerConfigstruct{ //DBisthebackingSQLdatastore.Required. DB*sql.DB //RequestDurationwillreceiveobservationsinseconds. //Optional;ifnil,ano-ophistogramwillbeused. RequestDuration*metrics.Histogram //Loggerisusedtologwarningsunsuitableforclients. //Optional;ifnil,ano-oploggerwillbeused. Logger*log.Logger } Ifacomponenthasafewrequireddependenciesandmanyoptionaldependencies,thefunctionaloptionsidiommaybeagoodfit. //NewHandlerconstructsandreturnsauseablerequesthandler. funcNewHandler(db*sql.DB,options...HandlerOption)*Handler{ h:=&Handler{ db:    c.DB, dur:   metrics.NewNopHistogram(), logger:log.NewNopLogger(), } for_,option:=rangeoptions{ option(h) } returnh } //HandlerOptionsetsanoptionontheHandler. typeHandlerOptionfunc(*Handler) //WithRequestDurationinjectsahistogramtoreceiveobservationsinseconds. //Bydefault,ano-ophistogramwillbeused. funcWithRequestDuration(dur*metrics.Histogram)HandlerOption{ returnfunc(h*Handler){h.dur=dur} } //WithLoggerinjectsaloggertologwarningsunsuitableforclients. //Bydefault,ano-oploggerwillbeused. funcWithLogger(logger*log.Logger)HandlerOption{ returnfunc(h*Handler){h.logger=logger} }ByusingthissimplifiedDIpattern,we’vemadethedependencygraphexplicitandavoidedhidingdependenciesinglobalstate.It’salsoworthusingasimplifieddefinitionofdependency—thatis,nothingmorethansomethingthatacomponentusestodoitswork.Bythisdefinition,loggersandmetricsareclearlydependencies.Sobyextension,theyshouldbetreatedidenticallytootherdependencies.Thiscanseemabitawkwardatfirst,especiallywhenwe’reusedtothinkingofe.g.loggersasincidentalorubiquitous.ButbyliftingthemuptotheregularDImechanism,wedomorethanestablishaconsistentlanguageforexpressingneeds-arelationships.We’vemadeourcomponentsmoretestablebyisolatingthemfromthesharedglobalenvironment.Gooddesignpatternstendtohavethiseffect—notonlyimprovingthethingthey’redesignedtoimprove,butalsohavingpositiveknock-oneffectsthroughouttheprogram.TheClient-SideInterfacePatternConcretely,interfacesarenothingmorethanacollectionofmethodsthattypescanchoosetoimplement.Butsemantically,interfacesaremuchmore.Theydefinebehavioralcontractsbetweencomponentsinasystem.Understandinginterfacesascontractshelpsusdecidewhereandhowtodefinethem.Andjustascontracttestinginmicroservicearchitecturesteachesusthattherightplacetowriteacontractisoftenwiththeconsumer.Considerapackagewithatype.Goprogrammersfrequentlymodelthattypeanditsconstructorlikethefollowing.packagefoo //widgetisanunexportedconcretetype. typewidgetstruct{/*...*/} func(w*widget)Bop(int)int                    {/*...*/} func(w*widget)Twist(string)([]float64,error){/*...*/} func(w*widget)Pull()(string,error)          {/*...*/} //Widgetisanexportedinterface. typeWidgetinterface{ Bop(int)int Twist(string)([]float64,error) Pull()(string,error) } //NewWidgetconstructorreturnstheinterface. funcNewWidget()Widget{/*...*/}Inourcontractmodelofinterfaces,thisestablishestheWidgetcontractalongsidethetypethatimplementsit.Buthowcanwepredictwhichmethodsconsumersactuallywanttouse?Especiallyasthetypegrowsfunctionalityandourinterfacegrowsmethods,weloseutility.Thebiggertheinterface,theweakertheabstraction.Instead,considerhavingyourconstructorsreturnconcretetypesandlettingpackageconsumersdefinetheirowninterfacesasrequired.Forexample,consideraclientthatonlyneedstoBopaWidget.funcmain(){ w:=foo.NewWidget()//returnsaconcrete*foo.Widget process(w)          //takesabopper,which*foo.Widgetsatisfies } //boppermodelspartoffoo.Widget. typebopperinterface{ Bop(int)int } funcprocess(bbopper){ println(b.Bop(123)) }Thereturnedtypeisconcrete,soallofitsmethodsareavailabletothecaller.ThecallerisfreetonarrowitsscopeofinterestbycapturingtheinterestingmethodsofWidgetinaninterfaceandusingthatinterfacelocally.Insodoingthecallerdefinesacontractbetweenitselfandpackagefoo:NewWidgetshouldalwaysproducesomethingthatIcanBop.Andevenbetter,thatcontractisenforcedbythecompiler.IfNewWidgeteverstopsbeingBoppable,I’llseeerrorsatbuildtime.Moreicingonthecake:Testingtheprocessfunctionisnowaloteasier,aswedon’tneedtoconstructarealWidget.WejustneedtopassitsomethingthatcanbeBopped—probablyafakeormockstruct,withpredictablebehavior.AndthisallalignswiththeCodeReviewCommentsguidelinesaroundinterfaces,whichstatethat:Gointerfacesgenerallybelonginthepackagethatusesvaluesoftheinterfacetype,notthepackagethatimplementsthosevalues.Theimplementingpackageshouldreturnconcrete(usuallypointerorstruct)types:thatway,newmethodscanbeaddedtoimplementationswithoutrequiringextensiverefactoring.Sometimesthereisacasefordefininginterfacesneartoconcretetypes.Forexample,inthestandardlibrary,packagehashdefinesaHashinterface,whichtypesinsubsidiarypackagesimplement.Althoughtheinterfaceisdefinedintheproducer,thesemanticsarethesame:atightlyscopedcontractthatthepackagecommitstosupporting.Ifyourcontractissimilarlytightlyscopedandsatisfiedbydifferenttypeswithdifferentimplementations,thenitmaymakesensetoincludeitalongsidethoseimplementationsasasignalofintenttoyourconsumers.TheActorPatternLikedependencyinjection,theideaoftheactorpatterncanmeanwildlydifferentthingstodifferentpeople.Butatthecore,it’snotmuchmorethananautonomouscomponentthatreceivesinputandprobablyproducesoutput.InGo,welearnedprettyearlyonthatagreatwaytomodelanactorisasaninfinitelyloopingfunctionselectingonablockofchannels.Thegoroutineactsasasynchronizationpointforstatemutationsintheactor,ineffectmakingtheloopbodysinglethreaded—ahugewinforclarityandcomprehensibility.Wetypicallynamethisfunction`run`or`loop`anddefineitasamethodonastructtypethatholdsthechannels.typeActorstruct{ eventc  chanEvent requestcchanreqRes quitc   chanstruct{} } func(a*Actor)loop(){ for{ select{ casee:=



請為這篇文章評分?