æ¬èšäºã¯ BASEã¢ããã³ãã«ã¬ã³ããŒ2024 ã®13æ¥ç®ã®èšäºã§ãã ã¯ããã« Pay IDã®ããã¯ãªãŒãã®å€§æš( @roothybrid7 )ã§ãã 9/30ã«ãPAYæ ªåŒäŒç€Ÿ(以äžPAY瀟)ãä¿æããŠãããPay IDãã®ã·ã¹ãã 移管ãè¡ãããªãã¥ãŒã¢ã«ããŸããã ã«ãŒããã·ã§ãã管çç»é¢ãPay IDã¢ããªãšã¯å¥ã®ç¬ç«ããã·ã¹ãã ãšãªã£ãŠãããã¢ããªã±ãŒã·ã§ã³ã®æè¡éžå®ãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ã®èšèšã«æºãããŸããã®ã§ããã®èŸºã®è©±ãããããšæããŸãã 移管ã®èæ¯ 2021幎㫠賌å
¥è
åãã·ã§ããã³ã°ãµãŒãã¹ãšããŠãPay IDãã®æäŸãéå§ ããŸãããããµãŒãã¹ã®éçšã¯åŒãç¶ãã äžæ¹ã§ãæ±ºæžæ©èœã¯ãPAY瀟ãã·ã¹ãã ã®éçºã»éçšç®¡çãè¡ããšãããé·ããåæãããç¶æ
ã«ãããŸããã ãŸããã¯ã¬ãžããã«ãŒãäŒå¡æ
å ±ãæ±ã£ãŠããããšããã移管ããã«ããŠããã©ã®ããŒã¿ã移管ããã°ããã粟æ»ããªããšãããªããšãã課é¡ããããŸããã 詳现ã¯çããŸãããã¯ã¬ãžããã«ãŒããšäŒå¡ã®æã€ãã°ã€ã³ã»äœææ
å ±ãåé¢ããããšã«ããããã®èª²é¡ã解決ã§ãããã ãšããããšãšã人å¡ç¢ºä¿ã§ããããšããç®åŠãç«ã£ããããç§»ç®¡ãæ±ºè¡ããããšãšãªããŸããã æè¡éžå®ã«ã€ã㊠ã·ã¹ãã 移管ã«ããããå
ã®æè¡ã¹ã¿ãã¯ããã®ãŸãŸèžè¥²ããå¿
èŠã¯ãªãã£ããããæ¹ããŠãããèããããšã«ãªããŸããã ãŸããããã°ã©ãã³ã°èšèªãšããŠã¯ã go ãæ¡çšããããšã«ããŸãããæ¡çšçç±ãšããŠã¯ä»¥äžã®éãã§ãã BASE BANKã«éçšããŠããŠãã éçåä»ãã®å®å¿æãšææ³ã®ã·ã³ãã«ã gofmtçéçºè
äœéšãåäžãããããŒã«ãæã£ãŠãã Viewåšããããã³ããšã³ãã«å¯ããAPIãµãŒããšããŠã ãå®è£
移管ã®ããã³ããšã³ãã«é¢ããå
容ã¯ã ã·ã¹ãã ãªãã¥ãŒã¢ã«ã§Next.jsã®App Router/Server Actionã䜿ã£ãŠäŸ¿å©ã ãšæã£ããšãã - BASEãããã¯ãããŒã ããã° ãã芧ãã ããã goã䜿ã£ãã¢ããªã±ãŒã·ã§ã³éçºã¯åããŠã§ã¯ãªãã§ãããè§Šã£ãŠããã®ã¯ go1.7 ã®é ã§ãããmodule管çããµããŒãããããããžã§ããªã¯ã¹ããµããŒããããããgoã®é²åãæããŸããã æ¬¡ã«ãã·ã¹ãã ã«é¢ããŠãæ©èœãšããŠã¯å€§ããã¢ã«ãŠã³ã管çãã¯ã¬ãžããã«ãŒãæ
å ±ãæ±ãããã«PAY.JP APIãå©çšãããããã·ãšããŠã®æ©èœããããŸããããã«ãPay IDãå©çšããã¯ã©ã€ã¢ã³ãã®çš®é¡ãå€ããããèªèšŒæ¹åŒãå©çšããããŒã¿ã®éãã§ãããããã®APIãšã³ããã€ã³ããå®è£
ããªããã°ãªããŸããã ãŸãããã©ãã£ãã¯éã«ãéãããããäŸãã°ãã«ãŒãã§ã¯æ±ºæžããéã«ã¢ã«ãŠã³ããã«ãŒãæ
å ±ãæ¯ååç
§ããã®ã§å€ããšãããã以å€ã ãšããã°ã€ã³ããæãã¢ã«ãŠã³ãç·šéããæã«ããã¢ã¯ã»ã¹ããªããšããããããããããèæ
®ããŠAPIãµãŒããããããçšæããŸããã APIãµãŒããåãããšããŠããèªèšŒæ¹åŒãç°ãªãã ãã§ãçµå±å
šãåãåŠçãããŒã蟿ãããšããããŸãããŸãã賌å
¥è
ã®ããã®åŠçãšç€Ÿå
ç®¡çæ¥åãšç°ãªãåŠçã§ãã£ãŠããã¢ã«ãŠã³ãã®èšå®å€æŽãéäŒåŠçãªã©ãåãæ©èœãå©çšãããšãã£ãããšããããŸãã ãã®ãããã³ãŒãåå©çšå¯èœãªããã«ãåŠçåäœãé©åã«åããããã¬ã€ã€ãŒåããããšããããšãæèããŸããã ãããå®çŸããããã®æè¡ã¹ã¿ãã¯ã¯ä»¥äžã®éãã§ãã Go AWS(ECS/Fargate, ALB, CloudFront, WAF, RDS, ElastiCache, SES) oapi-codegen(OpenAPI) gqlgen(GraphQL) SQLBoiler(ORM) Uber Fx(DI) chi(Router) oso(RBAC, ACL) ä»åã¯ãPoC(Proof of Valueã䟡å€å®èšŒ)ãPoV(Proof of ConceptãæŠå¿µå®èšŒ)ã®ãããªå®èšŒå®éšãã§ãŒãºã§ã¯ãªãã·ã¹ãã 移管ãªããããã§ã«éçšãããŠããã·ã¹ãã ããäºææ§ãã§ããéãä¿ã¡ã€ã€åæšéžæããªããå®è£
ããå¿
èŠããããŸãã æ§ã
ãªè°è«ããããšã¯æããŸããããããã£ãããçšåºŠã®èŠæš¡ã®ã¢ããªã±ãŒã·ã§ã³ã®ã³ãŒãããŒã¹ãæŽçããç¶æ
ãšããã«ã¯ãã¬ã€ã€åãããã¢ãŒããã¯ãã£ã®å°å
¥ã¯å¿
èŠããªãšå人çã«ã¯æããŸãã ä»åãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ãšããŠã¯ãã¯ãªãŒã³ã¢ãŒããã¯ãã£ãããŒã¹ã«æ¡çšããŠããŸãã ã¯ãªãŒã³ã¢ãŒããã¯ã㣠ã¯ãªãŒã³ã¢ãŒããã¯ãã£ã¯ã Clean Coder Blog - The Clean Architecture ã äžè¬ç㪠Web ã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ã㣠- ã¯ãªãŒã³ ã¢ãŒããã¯ã㣠ãåèã«ããŠããã ããšããŠãäž»ã«ãããžãã¹ããžãã¯ãã¢ãã«ãäžå¿ã«é
眮ããç¹å®ã®ãã¬ãŒã ã¯ãŒã¯ãããŒã¿ããŒã¹ãå€éšAPIã«äŸåãããªãããã«ã€ã³ã¿ãã§ãŒã¹ãå®çŸ©ãããããéããŠå©çšã§ããããã«ããŸãããã¬ãŒã ã¯ãŒã¯ãå©çšããã€ã³ã¿ãã§ãŒã¹ã®å®è£
ã¯ã以äžã®Interface AdaptersãšåŒã°ããå±€ã«é
眮ããŸããããã«ããåäœãã¹ãã®ã¢ãã¯ãå®è£
ããããšãã§ããŸãããORM(Object-Relational Mapping)ãå¥ã®ãã®ã«å·®ãæ¿ããããšãå¯èœãšãªããŸãã ããã«ãéèŠãªã®ãå³ã®å³äžã§ãInterface AdaptersãšUse Casesã¬ã€ã€ãŒéã®éä¿¡å¶åŸ¡ãããŒã瀺ããŠããŸãããäŸåæ§é転ã®ååã«ãããæœè±¡ã¯è©³çްã«äŸåããŠã¯ãªããªããè©³çŽ°ãæœè±¡ã«äŸåãã¹ãã§ãããšãããšããã衚çŸããŠããŸãã The Clean Code Blog ããåŒçš ä»åã¯ãAPIãµãŒãè€æ°å®è£
ããå¿
èŠãããããã¢ã«ãŠã³ãç»é²çã®å€ãã®ãŠãŒã¹ã±ãŒã¹ã®ãããŒã«å·®ç°ã¯ãªãåãã¢ããªã±ãŒã·ã§ã³ããžãã¯ãå©çšã§ããŸãããã®ãããå³ã«ãããšããã® ControllerãPresenterã®å®è£
ã¯ãAPIãµãŒãæ¯ã«å¿
èŠã ããUse Caseã¯ã²ãšã€ã§ãããããã¯ãã®å³ã®ã«ãŒã«ã«æºæ ããã°å®çŸã§ããŸãã äŸåé¢ä¿ãæ°Žå¹³æ¹åã®ã¬ã€ã€ãŒå³ã§è¡šããšä»¥äžã®ããã«ãªããŸãã 以äžã®Interfaceã®äŸã ãšãInputPortã«é©å¿ãããŠãŒã¹ã±ãŒã¹åŠçãå®çŸããã¢ããªã±ãŒã·ã§ã³ããžãã¯ãå®è£
ããOutputPortã«é©å¿ããPresenterãå®è£
ããŸããOutputPortã®å®è£
詳现ã¯ãAPIãµãŒãæ¯ã«å¥ã®å®è£
ã«ãªããŸãã type InputPort[I any, O any] interface { Exec(ctx context.Context, input I, outputPort O) error } type OutputPort[T any] interface { Write(ctx context.Context, value T) error } ãŸããOutputPortã¯å€ãæ»ããã ãããµãŽãã«ã¢ãŒããã¯ãã£ ãæšå¥šããããŒãã¢ããã¿ãŒæ¹åŒãå©çšããŠããŸããããŒãã§ write() ãå®è¡ãããããå®è£
ã§ããã¢ããã¿ãŒã§ãã®åºåãäœåºŠã§ãåãåãããšãã§ããŸãããšã©ãŒãçºçããªããã°ãoutputPortã®åºåããŒã¿ã®åãåãæã§ããPresenterããããŒã¿ãåæå€æããoapi-codegençå©çšããŠãããã¬ãŒã ã¯ãŒã¯ã«åã圢ã®ããŒã¿æ§é ã«å€æããŸãã func Interactor(ctx context.Context, input Input, outputPort OutputPort[Result]) error { account := AccountFromContext(ctx) if account == nil { return outputPort.Write(ctx, Result{Notice: "signupRequired" } } outputPort.Write(ctx, Result{Account: convertPublicFields(account)}) token, err := IssueToken(ctx, input) if err != nil { return err } return outputPort.Write(ctx, Result{Token: token}) } ã¡ãªã¿ã«ãããã¯è³Œå
¥è
åãã®APIã®è©±ã§ã瀟å
æ¥åã§ã®å©çšãç®çãšããadmin-apiã¯ããŠãŒã¹ã±ãŒã¹ãç°ãªãã®ãšã GraphQL ãæ¡çšããŠããããã®ãã¬ãŒã ã¯ãŒã¯ãæå€§éã«æŽ»çšããŠãããããããã«ã¯æºæ ããŠããŸããããªãªãŒã¹ã«ããé害ãçºçãããšããŠãããžãã¹çãªæå€±ã¯ããŸããªãããšãšãæè»ã«èŠä»¶ã«å¯Ÿå¿ããããçæéã§å®è£
ããªãªãŒã¹ããããšããç®çã®ããã§ãã ãã ãEntitieså±€ã«ããã³ã¢ããžãã¯ã¯ãç¹å®ã®ãã¬ãŒã ã¯ãŒã¯ã«ã¯äŸåããŠããããåå©çšå¯èœãªããã«å®çŸ©ããŠãããããäŸãã°ãã¢ã«ãŠã³ãæ
å ±ã®æŽæ°ãéäŒåŠçãªã©ã¯å
±æããããšãã§ããŸãã Uber Fx äŸåé¢ä¿é転ã®ååãå®çŸããããã«ãDIã³ã³ããã® Uber Fx ãå©çšããŸããã ããã¥ã¡ã³ãããããããããäŸåé¢ä¿ãäžè¶³ããŠããå Žåããã°ã«åºåããŠãããããããªã䜿ããããã£ãã§ãã å®è£
ãã€ã³ã¿ãã§ãŒã¹ãšããŠæäŸããéã As() ãå©çšããŠæç€ºçã«ã€ã³ã¿ãã§ãŒã¹ãæå®ããå¿
èŠããããŸããäž»ã«é¢æ°åã®å®è£
ã远å ããæãã€ã³ã¿ãã§ãŒã¹ãšããŠæäŸã§ããŠãªãããšããããããŸããã fx.Annotate( PasswordUpdaterFunc, fx.As( new (PasswordUpdater)), ) ãŸãã As() ã®éã®åœ¹å²ããã From() ãšãããã®ããããããã¯Interfaceã«å®è£
ãæ³šå
¥ããéã«å©çšããŸãããšããå®è£
ã§ãdatabase/sqlã®DBãšåäœãã¹ãã§å©çšããgo-txdbãäž¡æ¹åããšãå¯èœã«ãããããå
±éã®ã€ã³ã¿ãã§ãŒã¹ãå®çŸ©ããéã«å©çšããããšãã§ããŸããã type Executor interface { Exec(query string , args ... interface {}) (sql.Result, error ) //[...snip...] } fx.Annotate( func (executor Executor) AFunc { return func (ctx context.Context, id ID, opts ...Option) error { //[...snip...] }, }, fx.ParamTags( `name:"ro"` ), fx.From( new (*sql.DB)), fx.As( new (A)), ) å€ãã£ããšããã§èšããšããã¡ã€ã³ã€ãã³ããšã€ãã³ããã³ãã©ãŒã®ç»é²ã§ãå©çšããŸããã var EventModule = fx.Module( "Event" , fx.Provide( fx.Annotate( func () []event.Event { events := make ([]event.Event, 0 , len (event.EventAllTypes)) for _, e := range event.EventAllTypes { events = append (events, e) } return events }, fx.ResultTags( `group:"listenableEvent,flatten"` ), ), fx.Annotate( eventhandler.MakeActivityRecorder, fx.ResultTags( `name:"recordActivity"` ), fx.As( new (event.Handler)), ), fx.Annotate( eventhandler.NewVerificationMailHandler, fx.ResultTags( `name:"verificationMail"` ), fx.As( new (event.Handler)), ), ), fx.Decorate( fx.Annotate( func (events []event.Event) []event.Event { return event.FilterEvents(events, func (event event.Event) bool { switch event.Name() { case "account.created" , "account.recovery" : return true default : return false } }) }, fx.ParamTags( `group:"listenableEvent"` ), fx.ResultTags( `group:"verificationMail"` ), ), ), fx.Invoke( asSubscribeHandler( "recordActivity" , "listenableEvent" ), asSubscribeHandler( "verificationMail" , "verificationMail" ), ), ) OpenAPI APIã¯è€æ°ãããšãã話ãããŸããããå®éã©ã®ãããªé
ç®ãAPIã§ãããšãããŠãããã¯ã移管å
ã®ã¢ããªã±ãŒã·ã§ã³ãšå©çšåŽã®ã¢ããªã±ãŒã·ã§ã³ã®ã³ãŒããå
šãŠç¢ºèªããOpenAPIã®ããã¥ã¡ã³ããšããŠèšèŒããŠãããŸããããŸããå©çšåŽã§å
šã䜿ãããŠããªãé
ç®ã¯ããã®éãŸãšããŠåé€ããŠããããšãå¿ããã«ããŠãããŸããã ããã«ã Redocly CLI ãå©çšãã瀟å
ãããã¯ãŒã¯ã«ããã¥ã¡ã³ããå
¬éãããã€ã§ãé²èЧã§ããããã«ãããŸããã GoãµãŒãã®ã³ãŒãã¯ããã®OpenAPIã®ã¹ããŒããããšã«ã oapi-codegen ã䜿ã£ãŠçæããŠããŸãã ORM ORMã¯ãDB Schemaã«äœµããŠã³ãŒãçæã§ããSQLBoilerãå©çšããŠããŸãã ã³ãŒãçæãããŠããããããšãã£ã¿ã§ã³ãŒãè£å®ãå¹ããŸãããEager Loadingãã¯ãšãªãã«ããŒã®ãããªæ©èœããããŸãã äžç¹å©çšããŠããŠé£ããã£ãã®ã¯ãLEFT JOINããçµæãbindããstructã®å®çŸ©ã§ããã ã¿ã°ã§ããŒãã«åãæå®ããå¿
èŠããããŸããããã³ãŒãçæãããstructãåå©çšããããšãããšããŸãå©çšã§ãããèªåã§å®çŸ©ããªããã°ãªããªãã£ãããnullã«ãªãããæ¹ã¯ããã€ã³ã¿ã§æå®ããªããã°ãããŸããã§ããã type JoinedStruct struct { Account Account `boil:"accounts,bind"` Extra *ExtraAttr `boil:"extra_attributes,bind"` } ãã ãåè¿°ã®éããã¯ãªãŒã³ã¢ãŒããã¯ãã£ãæ¡çšããŠãããã¢ããªã±ãŒã·ã§ã³ããžãã¯ã§ã¯SQLBoilerãçæããã¢ãã«ãçŽæ¥äœ¿ã£ãŠããªããããããŒã¿ããŒã¹ã®æäœãããå ŽåãEntitiesã®ã¢ãã«çãšçžäºå€æããŠããŸããèªåã§æŽæ°ãã¹ãã«ã©ã ãæšæž¬ã§ããªããããã¢ãã«äžã§äœããã®å€æŽãè¡ã£ãéããã¡ã€ã³ã€ãã³ããçºè¡ããã©ã®ã«ã©ã ãæŽæ°ãã¹ããæç€ºçã«Whitelistã§æå®ããŠããŸãã ã¡ãªã¿ã«ãå
šãŠã®æŽæ°åŠçãEntitiesã®ã¢ãã«ãéããŠè¡ã£ãŠããããã§ã¯ãªããåã«å€éšAPIããååŸããã¬ã¹ãã³ã¹ãDBã«åæããå Žåã¯ãã¢ããªã±ãŒã·ã§ã³ããžãã¯ãšã¯é¢ä¿ãªããInterface Adapterå±€å
éšã§ã®åŠçã®ããçŽæ¥importãã圢ã«ããŠããŸãã åæ§ã«ãã¯ã©ã€ã¢ã³ããèŠæ±ããããŒã¿ãåã«è¿ãã ãã®å ŽåããEntitiesã®ã¢ãã«ã«å€æããæå³ã¯ãªãããããŠãŒã¹ã±ãŒã¹ã«ç¹åããããŒã¿æ§é ã«å€æããŠè¿ããŠããŸãã â»çŸåšãSQLBoilerã¯ã¡ã³ããã³ã¹ã¢ãŒãã§ãä»åŸæ°æ©èœã远å ãããããšã¯ãªããããå¥ã®ãã®ã«ç§»è¡ããæ¹ãè¯ãããã§ããã¯ãªãŒã³ã¢ãŒããã¯ãã£ã ãšãInterface Adapterå±€ãæžãçŽãå¿
èŠã¯ãããŸãããä»ã®å±€ã¯ç¹ã«å€æŽããããšãªãç§»è¡ã§ããŸãã ãã¡ã€ã³ã€ãã³ããå©çšããå®è£
ã¡ã€ã³ããžãã¯ã®åŠçãããŒãæ£åžžã«å®äºãããšããŠãã¡ãŒã«éä¿¡ãè¡åå±¥æŽçãå¯äœçšçãªåŠçãå¿
èŠã«ãªãããšããããŸãã ãã®ããã«å©çšãããã®ãããã¡ã€ã³ã€ãã³ãã§ãã ãã¡ã€ã³ ã€ãã³ã: èšèšãšå®è£
- .NET ããåŒçš ãã®ä»çµã¿ã䜿ãã°ããã©ã³ã¶ã¯ã·ã§ã³ãã³ãããåŸããã¡ã€ã³ã€ãã³ããéä¿¡ããããšã§ãã€ãã³ãã賌èªããŠããåã€ãã³ããã³ãã©ãŒãããããã®å¯äœçšãå®è¡ããããšãå¯èœã§ãã äŸãã°ãã¢ã«ãŠã³ãç»é²ãæåããåŸãèªèšŒã¡ãŒã«ãéä¿¡ããããè¡åå±¥æŽãèšé²ããããšãã£ãããšãã§ããŸãã ãã®ä» å®è£
ã¯ããªãã¹ãAPIã®äºææ§ãä¿ã€ããã«åªåããŸããããå
šãŠã®ããŒã¿ãããžãã¯ã移管ã§ããªããšãããšãããšãgoã§ã¯éåžžã¬ã¹ãã³ã¹ã«nullãå«ããããšãã§ããªããšããããšã§ãã¯ã©ã€ã¢ã³ãåŽã®å®è£
ã倿ŽããããåŸãªãã£ãããã远å ã§ãã®å®è£
ã䜵ããŠè¡ãå¿
èŠããããŸãããä»ããŒã ãšã®å®è£
æ¹éã®èª¿æŽçã䜵ããŠ1ã¶æåŒ±ã¯è¿œå ã§ããã£ãŠããŸãããªãªãŒã¹ãå»¶æããããåŸãªãç¶æ³ãšããªããŸããã ãŸããéçºããããããã«æ¬äœãšã¯å¥ã®ã¢ããªã±ãŒã·ã§ã³ã ã£ãããã©ã€ãã©ãªãäœæããããããŸããã OAuthèªå¯ãããŒã詊ããããWebã¢ã㪠Pay IDã¢ããªã®ã¢ã«ãŠã³ãç·šéãWebã¢ããªã«çµ±åããããã®èªèšŒã¯ã©ã€ã¢ã³ãã©ã€ãã©ãª( Kotlin Multiplatform project ) ãããã« ãããžã§ã¯ãã§äœ¿ãããŠããæè¡ã¹ã¿ãã¯ãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ã«ã€ããŠãç°¡åã«äžéšã玹ä»ãããŠããã ããŸããã ã·ã¹ãã 移管ã¯ãäºææ§ãã§ããã ãå£ããªãããã«ããå¿
èŠããããæ°èŠãµãŒãã¹ç«ã¡äžããšã¯ãŸãéã£ãé£ããããããåœåæ³å®ããŠãªãã£ã課é¡ãå¢ãããããŠããªããªããªãªãŒã¹ãŸã§æŒãçããããªããã©ãããããããŸããã Pay IDã§ã¯ãä»åŸããããã¯ãã®æ¹åãæè¡çæ¹åãç©æ¥µçã«è¡ã£ãŠãããŸããçŸåšãšã³ãžãã¢ãåéããŠããã®ã§ãèå³ã®ããæ¹ã¯ãã²æ¡çšæ
å ±ãªã©ãã芧ãã ããïŒ binc.jp ææ¥ã¯ãoliverãããštandenããã®èšäºã§ãããæ¥œãã¿ã«ã