BASE ADVENT CALENDAR 2025 DAY.20 ã¯ããã« ãã®èšäºã¯BASE ã¢ããã³ãã«ã¬ã³ã㌠2025ã®20æ¥ç®ã®èšäºã§ãã Pay ID Platform Group 㮠倧æšã§ãã æ¬èšäºã§ã¯ãFeature Flag(aka Feature Toggles)ã®æšæºå仿§åã³SDKã§ãã OpenFeature ãšãFeature Flag As A Service(以äžFFaaS)ã§ãã AWS AppConfig ãå©çšãããµãŒãã¹ãçŽ1幎ééçšããŠãããããOpenFeatureãäžå¿ã«Feature Flagã®çŸåšãšAppConfigã®éçšã«é¢ããŠãã話ãããŸããã·ã¹ãã ã®éçºèšèªã¯Goãå©çšããŠãããããäž»ã«ãããããŒã¹ã«ã話ãããŸãã Feature FlagæŠèŠ Feature FlagãšèããŠäœãæãæµ®ãã¹ãã§ãããã? On/Offã®ã¹ã€ããã®ãããªãã®ã§ã段éçæ©èœå
¬éãè¡ãã«ããªã¢ãªãªãŒã¹ã§å©çšããããåé¡ããã£ãå Žåã«ããŒã«ããã¯ã§ãã A/B ãã¹ãã§ããŠãŒã¶ã»ã°ã¡ã³ãããšã«ç°ãªãæ©èœãUIãåçã«æ¯ãåããããããªã©ã«äœ¿ã ã³ãŒãå·®åãé »ç¹ã«mainãã©ã³ããžããŒãžããã³ãŒãã®ãã¬ãã·ã¥ããä¿ã€ãã©ã³ã¯ããŒã¹éçº å
·äœçãªã±ãŒã¹ãæãæµ®ãã¹ããšãè€æ°ã®æ©èœãæã£ãŠããããã«æããŸãã ãç°¡åã«èšãã°ã Feature Flagãšã¯ãã³ãŒãã倿Žããããšãªãã·ã¹ãã ã®åäœã倿Žã§ããææ³ ã§ãã·ã¹ãã ãžã®ã³ãŒãåæ (ãããã€)ãšæ©èœå
¬é(ãªãªãŒã¹)ãåé¢ããããšãã§ãããã®ã§ãã ãã®æŽ»çšæ¹æ³ãšããŠã倧ãã4ã€ã®ã«ããŽãªã«åé¡ããããšãã§ããèšèšäžã®å¶çŽãç®¡çæ¹æ³ãããããç°ãªããŸããã«ããŽãªã®åé¡ç®å®ãšããŠããã©ã°ãã©ã®ãããã®æéå©çšããããšããåç¶æéãšããã©ã°ã®è©äŸ¡ããžãã¯ãå€ã®æ±ºå®ãã©ã®çšåºŠåçã«è¡ããã«ãã£ãŠåé¡ã§ããããã§ãã Release Flag: éçºäžã®æªå®æãªæ©èœãæ¬çªç°å¢ããé ãããã®ãæ¯èŒççåœãªãã©ã°ã Ops Flag: ã·ã¹ãã ã®éçšæ
åœè
ããããã©ãŒãã³ã¹ã®æé©åãæ©èœã®ç·æ¥åæ¢ã®ããã«äœ¿çšãããã©ã°ãæ¯èŒçé·åœã«ãªãããšããããŸãã Experiment Flag: A/Bãã¹ããªã©ããŠãŒã¶ãŒã®æ¯ãèããæ¯èŒããããã«äœ¿çšãããã©ã°ãå®éšæéäžã®ã¿ååšããŸãã Permission Flag: ç¹å®ã®ãŠãŒã¶ãŒã°ã«ãŒãã«æ©èœãžã®ã¢ã¯ã»ã¹æš©ãäžããããã®ãæ°žç¶çãŸãã¯éåžžã«é·åœãªãã©ã°ã åŒçš: https://martinfowler.com/articles/feature-toggles.html æã
ã¯äž»ã«ãäžé·æéçšããOps FlagãPermission Flagã«é¢ããŠãOpenFeature SDKãšAppConfigãå©çšããéçšç®¡çãè¡ãªã£ãŠãããOpenFeatureã®äž»èŠæ©èœãäºåç¥èã亀ããªãã説æããããšæããŸãã OpenFeatureã«ã€ã㊠OpenFeature ãšã¯ãã³ãã¥ããã£äž»å°ã®Feature Flagæšæºåãããžã§ã¯ãã§ãããæšæºå仿§ã®çå®ãšSDKãæäŸããŠããŸããLaunchDarklyãAppConfigçãã©ã°ç®¡çãè¡ããã³ããŒãæäŸããAPIãç¬èªSDKãçŽæ¥å©çšããã®ã§ã¯ãªããOpenFeatureãå©çšããå©ç¹ã¯ã©ã®ãããªãšããã«ããã®ã§ãããã? äŸãã°ã以äžã®ãããªç¹ãæãããããšæããŸãã ã©ã®ãã©ã°ç®¡çããã¯ãšã³ããéžæããŠããã¢ããªã±ãŒã·ã§ã³ããã¯ãOpenFeature SDKã®è©äŸ¡APIã«ãããçµ±äžçãªã€ã³ã¿ãã§ãŒã¹ã§ãã©ã°è©äŸ¡ããžãã¯ãå®è£
å¯èœ FFaaSãDBãããŒã«ã«ãã¡ã€ã«ãªã©ããã©ã°ç®¡çããããã¯ãšã³ãã¯ãããããã«å¯Ÿå¿ããOpenFeature Providerãå©çšããããšã§ãã¢ããªã±ãŒã·ã§ã³åŽã®ãã©ã°è©äŸ¡ããžãã¯ããã»ãŒå€æŽããã«å·®ãæ¿ããå¯èœ Hooksãå©çšãããã©ã°åçè©äŸ¡ãè¡ãããã®è©äŸ¡ã³ã³ããã¹ã(Evaluate Context)ãç·šéãããããã°åºåãTelemetryã®å®æœãªã©æ©èœæ¡åŒµãè¡ãããšãå¯èœ 以äžã®å³ã¯ãOpenFeatureããä»»æã®ä»®æ³çãªãã©ã°ç®¡çã·ã¹ãã ãFlags-R-usããšçµ±åãããããšã瀺ããŠããŸããããããã¯ã©ãŠãäžã®ã·ã¹ãã ãšçµ±åãããããªå°è±¡ãåããŸããã3rd partyãã³ããŒãæäŸããç¬èªSDKããDBãããŒã«ã«ãã¡ã€ã«ãªã©ãã©ã°ç®¡çãè¡ããä»çµã¿ãšãããã«å¯Ÿå¿ããProviderãçšæã§ããã°ãå
±æã®æšæºåããããã©ã°ã¯ã©ã€ã¢ã³ã(SDK)ãå©çšããäžè²«æ§ã®ããçµ±åAPIãå©çšããŠå®è£
ããããšãå¯èœã§ãã åŒçš: https://openfeature.dev/docs/reference/intro ã¢ããªã±ãŒã·ã§ã³ã§ãOpenFeature SDKãå©çšããŠãFeature FlagãGoã§æ±ãã«ã¯ä»¥äžã®ãããªå®è£
ãšãªããŸãããããã©ã°ç®¡çããã¯ãšã³ããå·®ãæ¿ãããå Žåã¯ã1.ã§ãã©ã°ç®¡çããã¯ãšã³ãã«å¯Ÿå¿ããProviderã«å·®ãæ¿ããã°ããã©ã°è©äŸ¡ãå©çšããç®æã®ä¿®æ£ã¯äžèŠã§ãã package main import ( "fmt" "context" "github.com/open-feature/go-sdk/openfeature" ) func main() { // 1. ãã©ã°ç®¡çããã¯ãšã³ããæ±ãProviderãç»é² openfeature.SetProviderAndWait(openfeature.NoopProvider{}) // 2. ãã®Providerããã¢ããªã±ãŒã·ã§ã³ããå©çšããããã®ã¯ã©ã€ã¢ã³ããäœæ client := openfeature.NewClient( "app" ) // 3. ãã©ã°ã®è©äŸ¡å®è¡ v2Enabled := client.Boolean( context.TODO(), "v2_enabled" , true , openfeature.EvaluationContext{}, ) // 4. è©äŸ¡ããããã©ã°å€ã䜿ã if v2Enabled { fmt.Println( "v2 is enabled" ) } } ããã§çµãããªããçŽæ¥ãã©ã°ç®¡çã·ã¹ãã ãæäŸããAPIãSDKã䜿ãã°è¯ãããšæããŸãããä»ã«ããããã仿§ãèããããŠããŸãã®ã§ãããã€ã玹ä»ããŸãã 1. å
±éã®I/Fã§å®è£
ãããããã¢ããªå¶åŸ¡ãšA/Bãã¹ãã¯ãå¥ã
ã®ãã©ã°ç®¡çã·ã¹ãã ãå©çšããã A/Bãã¹ããè¡ãéã«ãFeature Flagãå©çšããããšãã§ããŸããæã
ã¯AWSã®AppConfigããã©ã°ç®¡çã·ã¹ãã ãšããŠå©çšããŠããŸãããA/Bãã¹ãã§ã¯ãåæåºç€ãçšæãããŠãããã©ããããšãŠãéèŠã§ããçŸåšã Amazon CloudWatch Evidently ãšããã¢ãã¿ãªã³ã°ãåæãè¡ããæ©èœã¯ãµããŒããçµäºããŠãããåæåºç€ãå¥éçšæããã®ã倧å€ã§ãããããã£ãå Žåã以äžã®ããã«è€æ°Providerãç»é²ããããšãå¯èœã§ããããããã®Providerã«å¯Ÿå¿ããã¯ã©ã€ã¢ã³ããäœæããããšã§è€æ°ã®ãã©ã°ç®¡çã·ã¹ãã ããçšéããšã«äœ¿ãåããããšãã§ããŸãã import "github.com/open-feature/go-sdk/openfeature" // ã¢ããªå¶åŸ¡çšProviderãç»é² openfeature.SetProviderAndWait(NewLocalProvider()) // ååä»ãã§Providerãç»é² openfeature.SetNamedProvider( "abtesting" , NewABProvider()) // ããã©ã«ãã®Providerãå©çšããã¯ã©ã€ã¢ã³ããäœæ clientWithDefault := openfeature.NewDefaultClient() // ååä»ãã§ç»é²ããProviderãå©çšããã¯ã©ã€ã¢ã³ããäœæ clientForABTesting := openfeature.NewClient( "abtesting" ) 2. ãã©ã°ç®¡çã·ã¹ãã ãå¥ã·ã¹ãã ã«ç§»è¡ããã äŸãã°ããµãŒãã¹çµäºã§å¥ã·ã¹ãã ã«ç§»è¡ããªããã°ãªããªããšããã±ãŒã¹ãèããŠã¿ãŸãããã å®éšçæ©èœãšãªããŸããã Multi-Provider ãšãããã®ãå©çšããã°ãè€æ°ã®ProviderããŸãšããŠç»é²ããäžã€ã®ã¯ã©ã€ã¢ã³ãã䜿ã䞊è¡ããŠå®è¡ã§ããŸããããã«ãããå
ã«æ°ã·ã¹ãã ã«å¯Ÿå¿ããProviderã远å ç»é²ããŠãããŠãåŸãããã£ãããšæ°ããã·ã¹ãã ã«ãã©ã°ãç»é²ãããšãã£ãããšãå¯èœã«ãªããŸãã import ( "github.com/open-feature/go-sdk/openfeature" "github.com/open-feature/go-sdk/openfeature/multi" "github.com/open-feature/go-sdk/openfeature/memprovider" ) mprovider, err := multi.NewProvider( multi.StrategyFirstMatch, multi.WithProvider( "providerA" , memprovider.NewInMemoryProvider( /*...*/ )), multi.WithProvider( "providerB" , myCustomProvider), ) if err != nil { return err } openfeature.SetNamedProviderAndWait( "multiprovider" , mprovider) 3. è€æ°ãã©ã°ç®¡çã·ã¹ãã ãçµã¿åãããŠã·ãŒã ã¬ã¹ã«å©çšããã 2.ã®ã±ãŒã¹ãšåãããã«è€æ°ã®Providerãå©çšããã±ãŒã¹ãªã®ã§ãããçµã¿åãããŠäœ¿ãã±ãŒã¹ãèããŠã¿ãŸããããFFaaSãå©çšããå Žåããããã¯ãŒã¯è¶ãã«ãã©ã°ããŒã¿ã»ãããååŸããŠããªããã°ãªããªããããã·ã¹ãã ãããŠã³ããŠããå Žåãã¢ã¯ã»ã¹ã§ããªãããšããããŸããããã§ãããã¯ã¢ãããšããŠãç°å¢å€æ°ãããŒã«ã«ãã¡ã€ã«ãå©çšããProviderã䜵ããŠç»é²ããŠããã°ããã®éã¯ããã©ãŒã«ããã¯ããŠãã©ã°ãå©çšãããšãã£ãããšãå¯èœã«ãªããŸãã以äžã®äžããProviderå©çšæŠç¥ãæå®ããããšã§ãæè»ã«çµæãå©çšããããšãã§ããŸãã First Match: Providerãé çªã«åŒã³åºããæåã«ãã©ã°ãååšãããã®ãè¿ã First Success: Providerãé çªã«åŒã³åºãããšã©ãŒãçºçããªãã£ãProviderã®çµæãè¿ã Comparison: 䞊åã«ProviderãåŒã³åºããçµæãæ¯èŒãäžèŽããå Žåã¯ãã®çµæããããã§ãªãå Žåã¯Fallback Providerã®çµæãŸãã¯ããã©ã«ãå€ãè¿ã Custom: èªåã§å©çšããžãã¯ãå®è£
OpenFeatureã§ã¯ãã¿ãŒã²ãã£ã³ã°ãšåŒã°ããä»çµã¿ããããã¢ããªã±ãŒã·ã§ã³ããŠãŒã¶ãŒã«é¢ããæ
å ±ãå©çšããŠãåçã«è©äŸ¡ããã«ãŒã«èšå®ãå®çŸ©ãããã©ã°ã®ã¹ããŒã¿ã¹ãå¶åŸ¡ããŸãã AppConfigã®å Žåã ãã«ãããªã¢ã³ããã©ã° ãšãããã®ãå©çšããå¿
èŠããããŸãããã®å ŽåãAppConfigããAPIã§çŽæ¥ååŸããæ¹åŒã¯å©çšã§ãããAppConfig AgentãSidecarã³ã³ããã§ç«ãŠãŠå©çšããã®ãå¿
é ãšãªã£ãŠããŸããAgentãAppConfigãããèšå®å€ãã¿ãŒã²ãŒãã£ã³ã°ã«å©çšããã«ãŒã«ã»ãããããŒãªã³ã°ã«ãã宿çã«ããŠã³ããŒããããããã¢ããªã±ãŒã·ã§ã³ããã¯ãããŒã«ã«ãã¹ããžã®éä¿¡ã«ãããAgentã§ãã©ã°è©äŸ¡ãå®è¡ã§ããŸãã ãããã Agentã®ã€ã¡ãŒãž ãå£ããå Žåãæ£ãããã©ã°ãååŸã§ããªãã£ããããªãœãŒã¹æ¯æžãéè² è·ã«ãªã£ãå ŽåãAgentãžã®æ¥ç¶ãšã©ãŒã«ãªãããšã極ãŸãã«çºçããŸããããããå Žåãããã¯ã¢ãããšããŠãããŒã«ã«èšå®ãããã©ã°ã®çµæãè¿ããProviderã䜵ããŠç»é²ããŠããããšã§ãåé¡ãèµ·ãã«ãããªãããªãšæããŸãã åŒçš: https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/appconfig-agent.html 4. ã¿ãŒã²ãã£ã³ã°ããµããŒãããè©äŸ¡ã³ã³ããã¹ããæè»ã«æ§ç¯ã§ãã OpenFeatureã¯ãã¿ãŒã²ãã£ã³ã°ãè¡ãå
¥åå€ãšããŠãè©äŸ¡ã³ã³ããã¹ããšããã³ã³ãããå©çšããŸããããã«ããŠãŒã¶ã®IPãã¡ãŒã«ã¢ãã¬ã¹ããµãŒãã®äœçœ®æ
å ±(AWS Regionãªã©)ãèŒããŠãªã¯ãšã¹ãããããšã§ãæè»ã«ãã©ã°è©äŸ¡ãè¡ããŸãã ãã©ã°ç®¡çã·ã¹ãã ããã©ã°ãåçã«è©äŸ¡ãçµæãè¿ãä»çµã¿ãæäŸããŠããã°ã®è©±ã§ãããããã«ãã£ãŠãæè»ã«çµæãå€åãããããšãã§ããŸããããã¯ã°ããŒãã«ã«èšå®ããããç¹å®ã¯ã©ã€ã¢ã³ããå®è¡æã«ãèšå®ã§ããŸããæ§ã
ãªãã§ãŒãºã§èšå®ãããã³ã³ããã¹ãã®ããŒã¿ã¯ããŒãžãããŠãè©äŸ¡ãªã¯ãšã¹ãã«ä¹ããŸãã ããã䜿ã£ãå®è£
ãšããŠãã«ãŒãã®CheckoutæãšPay IDã¢ããªã®éCheckoutæã«ãªã¯ãšã¹ãããåãHTTP APIããããããã§ãã®ã¿ãŒã²ãã£ã³ã°ãå©çšãããã©ã°ãå©çšããŠããŸããPermission Flagã®äžçš®ã®äœ¿ãæ¹ã§ããã Checkoutæã«ã¯ãåæ®µã§ç¬èªã«å¯©æ»ããžãã¯ãåäœããŠãããããç¡é§ã«å¯©æ»ãæéãçºçããªãããã«å¯©æ»ããžãã¯ãã¹ãããããããPay IDã¢ããªã®éCheckoutæã«ã¯äœã審æ»ããŠããªããããAPIã§å¿
ã審æ»ããžãã¯ãå®è¡ããå¿
èŠããããŸãããã®å¶åŸ¡ã«ãã®ä»çµã¿ã䜿ã£ãŠããŸãã // OpenFeatureå
šäœã§å©çšããè©äŸ¡ã³ã³ããã¹ããèšå® openfeature.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map [ string ]any{ "region" : "us-east-1-iah-1a" , }, )) // ã¯ã©ã€ã¢ã³ãã«èšå®(ããã«ãã£ãŠãå©çšããProviderã®ã¿ã«äŒæ¬ããã) client := openfeature.NewClient( "my-app" ) client.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext( map [ string ]any{ "version" : "1.4.6" , }, )) // å®è¡æã«è©äŸ¡ã³ã³ããã¹ããèšå® evalCtx := openfeature.NewEvaluationContext( "user-123" , map [ string ]any{ "company" : "Initech" , }, ) boolValue, err := client.BooleanValue( "boolFlag" , false , evalCtx) ãŸããéçºèšèªã«ãã£ãŠã¯ããã©ã³ã¶ã¯ã·ã§ã³ã³ã³ããã¹ããå©çšã§ããå Žåãããããªã¯ãšã¹ãã¹ã³ãŒããªããŒã¿ãããã«èŒããŠäŒæ¬ããããšãå¯èœã§ãã goã®å Žåã¯ã context packag e ã䜿ã£ãŠå®çŸããŠããŸããããã«ãã£ãŠãHTTPãªã¯ãšã¹ãåä¿¡æã«ããã«ãŠã§ã¢ã§IPããŠãŒã¶IDãåŸããããããšãããããã©ã³ã¶ã¯ã·ã§ã³ã³ã³ããã¹ããå©çšããè©äŸ¡ã³ã³ããã¹ãã«ããŒãžãããšãã£ãäœ¿ãæ¹ãå¯èœã§ãã import "github.com/open-feature/go-sdk/openfeature" // set the TransactionContext ctx := openfeature.WithTransactionContext(context.TODO(), openfeature.EvaluationContext{}) // get the TransactionContext from a context ec := openfeature.TransactionContext(ctx) // merge an EvaluationContext with the existing TransactionContext, preferring // the context that is passed to MergeTransactionContext tCtx := openfeature.MergeTransactionContext(ctx, openfeature.EvaluationContext{}) // use TransactionContext in a flag evaluation client.BooleanValue(tCtx, ....) 5. Hooksã§æ©èœæ¡åŒµã§ãã Hooksã¯ãã¢ããªã±ãŒã·ã§ã³éçºè
ãããã©ã°è©äŸ¡ã«ä»»æã®åäœã远å ã§ããæ©èœã§ãã 以äžã®4ã€ã®ã¹ããŒãžã§ããžãã¯ã远å ã§ããŸãã before: ãã©ã°è©äŸ¡ã®çŽå after: ãã©ã°è©äŸ¡ãæåããçŽåŸ error: ãã©ã°è©äŸ¡ã倱æããçŽåŸ finally: ãã©ã°è©äŸ¡åŸã«ç¡æ¡ä»¶ã« ããã¯ãè©äŸ¡ã³ã³ããã¹ããšåæ§ãã°ããŒãã«ãç¹å®ã¯ã©ã€ã¢ã³ãæ¯ããè©äŸ¡å®è¡æã®ããããã§å®è¡ããããã«èšå®å¯èœã§ãã ãŠãŒã¹ã±ãŒã¹ãšããŠã¯ãè©äŸ¡ã³ã³ããã¹ããžã®ç·šéã远å ãè©äŸ¡åŸã®ãã©ã°å€ã®æ€èšŒã TelemetryããŒã¿èšæž¬ ããã°èšé²ã«å©çšã§ããŸãã OpenFeature Hooks LifeCycle è©äŸ¡ã³ã³ããã¹ããžã®ç·šéã«å©çšããããã¯ã®äŸãšããŠã¯ã以äžã®ãããªãã®ã§ãã AppConfigã®å ŽåãContextã¯ãªã¯ãšã¹ããããã§éä¿¡ããŸããæäŸãããŠããProviderã§ãè©äŸ¡ãªã¯ãšã¹ãã®ãã©ãŒãããã«åãããã«å€§äœã¯å€æããããšæããŸãããäžéšããŒã¿åã®å€æããè©äŸ¡ã«ãŒã«ãæ³å®ããŠããããŒã¿åã«åããªãã±ãŒã¹ããçºçããããšããããŸãã 倿ããžãã¯ããã¢ããªã±ãŒã·ã§ã³åŽã®ã³ãŒãã§å®è£
ããããšãå¯èœã§ããããã©ã°ç®¡çã·ã¹ãã ãå·®ãæ¿ããå Žåããã©ãŒããããäžèŽããªãå¯èœæ§ãããããããã®éšåãæžãçŽãå¿
èŠãçãããããããŸãããããã¯ãå®è£
ãOpenFeature SDKã«ç»é²ããã°ãã¢ããªã±ãŒã·ã§ã³ããžãã¯ã«åœ±é¿ãäžãããããŒã¿å€æããžãã¯ãå®è¡ããããšãå¯èœãšãªããŸãã package featureflag import ( "context" "maps" "strings" "time" "github.com/open-feature/go-sdk/openfeature" ) // AppConfigã®Contextã«åãããã«å€æããHookãå®è£
ããŸãã type AlterAppConfigContextHook struct { openfeature.UnimplementedHook } // AppConfigã§ã¯ãHTTP Header: Contextã«è€æ°å€ãè©°ã蟌ããããã«ã³ãåºåãã¯ãããã®å€ãåããããã«äœ¿çšãããŸãã // ãã®ãããã«ã³ãåºåãã®å€ãæã€é
åå€çã®å Žåã¯ãå¥ã®åºåãæåã«çœ®ãæããå¿
èŠããããŸãã // ããããé
åããµããŒãããŠããªãããªãããã¹ããŒã¹åºåãã«å€æããããã«ããŸãã // // See https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/appconfig-creating-multi-variant-feature-flags-rules-operators.html // // Example: // // HTTP Header: Context: "targetingKey=1qaz2wsx3edc,machineId=1qaz2wsx3edc,region=us-east-1-iah-1a,tenant=e1,service=payid-api,scope=payid-app account,accessDate=2025-03-31T04:00:16" func (h *AlterAppConfigContextHook) Before(ctx context.Context, hookContext openfeature.HookContext, hookHints openfeature.HookHints) (*openfeature.EvaluationContext, error ) { oldECtx := hookContext.EvaluationContext() newAttrs := map [ string ]any{} maps.Copy(newAttrs, oldECtx.Attributes()) // èšèŒãããŠããè©äŸ¡ã«ãŒã«ãšããŠå©çšå¯èœãªãªãã©ã³ãã®ãã©ãŒãããã«åããã for k, v := range maps.All(newAttrs) { if t, ok := v.([] string ); ok { newAttrs[k] = strings.Join(t, " " ) } if t, ok := v.(time.Time); ok { newAttrs[k] = t.Format(time.RFC3339) } } newECtx := openfeature.NewTargetlessEvaluationContext(newAttrs) return &newECtx, nil } func NewAlterAppConfigContextHook() *AlterAppConfigContextHook { return &AlterAppConfigContextHook{} } ãµãŒããµã€ãã«ãããFeature Flagè©äŸ¡ãšååŸã«é¢ããèª²é¡ ãããŸã§ããã©ã°ç®¡çã·ã¹ãã ãã©ãã«ããããæèããã«è©±ããŠããŸãããããããFFaasã®å Žåãã¯ã©ãŠãäžã®ã©ããã«ååšãããããã©ããã®æ®µéã§ãã©ã°ããŒã¿ã»ããããããã¯ãŒã¯è¶ãã«éä¿¡ããŠãååŸããå¿
èŠããããŸãã ãã®éã«èããããšã¯ããã€ãããããšæããŸãããã©ã°ãå¿
èŠã«ãªããã³ã«ããªã¢ãŒãã«éä¿¡ããŠãã®éœåºŠè©äŸ¡ããŠãããã®ããããŒã¿ã»ãããååŸãããŒã«ã«ã§è©äŸ¡ããã®ããããããã§ãã Different approaches for server-side SDK architectures ãšããèšäºã«ãããšããµãŒããŒãµã€ãSDKãäžè¬çã«æ¡çšããŠããã¢ãŒããã¯ãã£äžã®ã¢ãããŒãã¯3ã€ãããšãããŸãã Different approaches for server-side SDK architecture ã¢ãŒããã¯ã㣠ä»çµã¿ å©ç¹ æ¬ ç¹ "Direct" API Bridge ãã©ã°è©äŸ¡ã®ãã³ã«ãSDKããã©ã°ç®¡çãµãŒãã¹ã®APIïŒREST/gRPCïŒãçŽæ¥åŒã³åºãã SDKã®å®è£
ãã·ã³ãã«ãè©äŸ¡ããžãã¯ããµãŒãã¹åŽã«éçŽã§ããã ãããã¯ãŒã¯ã®ãªãŒããŒãããã倧ãããããã©ãŒãã³ã¹ãäœãããããã¯ãŒã¯é害ã®åœ±é¿ãåããããã API with Cache APIããã®ã¬ã¹ãã³ã¹ãSDKããã£ãã·ã¥ããåŸç¶ã®åããªã¯ãšã¹ãã«ã¯ãã£ãã·ã¥ããå¿çããã ãããã¯ãŒã¯ãã©ãã£ãã¯ãåæžã§ããã ååãªã¯ãšã¹ãã¯é
ãããã£ãã·ã¥ãããŠããªãåçãªè©äŸ¡ã«ã¯äžåãããã£ãã·ã¥ã®æŽæ°ã©ã°ãããã Local Evaluation SDKããã©ã°ã®å
šèšå®ããŒã¿ãããŒã«ã«ã«ä¿æããè©äŸ¡ãã¡ã¢ãªäžã§å®çµããããèšå®ã®æŽæ°ã¯ã¹ããªãŒãã³ã°ïŒSSE, WebSocketsïŒã宿çãªããŒãªã³ã°ã§è¡ãã éåžžã«é«éã§ãããã¯ãŒã¯ã®åœ±é¿ãåããªããèé害æ§ãé«ãã SDKã®å®è£
ãè€éãå
šèšå®ããŒã¿ãä¿æãããããã¡ã¢ãªäœ¿çšéãå¢å ããå¯èœæ§ãããã ãããã®ãã¡ãLocal Evaluationãæãé«éãã€å¹ççã§èé害æ§ã«åªããŠããŸãããããããã©ã°ç®¡çã·ã¹ãã ããåçŽã«APIããæäŸããŠããªãå ŽåãOpenFeature Providerã§ãããã®ä»çµã¿ãé 匵ã£ãŠå®è£
ããå¿
èŠããããŸãã ãŸããç¬èªSDKãæäŸããŠããå Žåããã®SDKã«å¯ŸããŠåŒã³åºããè¡ãOpenFeature Providerãå©çšãããã¯äœæããã°ãå®è£
ã³ã¹ããäžããããšãå¯èœã§ãã ããŠãAppConfigã®å Žåãã©ããªãã®ã§ãããã? å
ã»ã©ãèŠãããAppConfigã®å³ãåæ²ããŸãã åŒçš: https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/appconfig-agent.html Agentãšã¢ããªã±ãŒã·ã§ã³ãäžã€ã®ã·ã¹ãã ãšãããªããLocal Evaluationãšèšãããã§ãããã ãã1ã®éšåã§ããŒã«ã«ãã¹ãå®ã«éä¿¡ãçºçããŠããããã®éä¿¡ãéè² è·æãæ¥µãŸãã«å€±æããããšããããŸãããŸããAppConfigã§ã¿ãŒã²ãã£ã³ã°ãè¡ãå Žåããã®ã¢ãŒããã¯ãã£ã¯å€æŽã§ããŸããã ãã®AppConfig Agentãå©çšããProviderãå®è£
ããå ŽåãæãåçŽãªä»çµã¿ãšãããªãã察Agentåãã«éä¿¡ãè¡ãããã¡ãã§ãã©ã°è©äŸ¡ãå®è¡ãã"Direct" API Bridge ã®ã¢ãããŒããšãªããŸããæã
ã¯ãOpenFeature ProviderãšããŠã https://github.com/Arthur1/openfeature-provider-go-aws-appconfig ãå©çšããŠããŸãã AppConfigã«ãã©ã°ãè¿œå æŽæ°ããããã€ããã«ã¯? AppConfigã§ã¯ããããŒãžã¡ã³ãã³ã³ãœãŒã«ã§ãçŽæ¥èšå®ããŒã¿ãäœæãããããã€ãå®è¡ããã±ãŒã¹ã®ã»ãã https://docs.aws.amazon.com/ja_jp/appconfig/latest/userguide/appconfig-type-reference-feature-flags.html ã®JSONã¹ããŒãã«åºã¥ããèšå®ããŒã¿ãäœæããAWS SDKãCLIã䜿ã£ãŠãã©ã°ããŒã¿ã»ããã®æŽæ°ãšãããã€ã«ããèšå®åæ ãè¡ããŸãããããã€ã®éããããã€æŠç¥ã䜿ãããšã«ãããæ®µéçãªãªãŒã¹ãè¡ãããšãå¯èœã§ããããŒã¿ã»ããã®å®çŸ©ã¯ãã³ãŒãããŒã¹ã§Git管çããŠãããCI/CDã«ãããAWSç°å¢ã«åæ ããŠããŸãã 以äžã®å³ã®éããGitHub Actionsã§ããŒãžæã«å·®åãæ€ç¥ããããŒã¿ã»ãããS3ã«ã¢ããããŒããããšãStep functionsã®åã¹ãããã§Lambdaãå®è¡ãããAppConfigãžã®èšå®åæ ãšãããã€ãå®è¡ãããŸãã ãã®ã¯ãŒã¯ãããŒã§ã¯ãå®çŸ©æžã¿ãããã€æŠç¥: AppConfig.AllAtOnceãšåæ§ã®æŠç¥ã䜿çšããŠãããããæ®µéçã§ã¯ãªããããã€å®äºåŸå³æãªãªãŒã¹ãšãªããŸãããŸããæåããªã¬ãŒã§ã®ã¯ãŒã¯ãããŒãçšæããŠããŸãã appconfig workflow ãã©ã°ããŒã¿ã»ããã«ã€ã㊠ã³ãŒãããŒã¹ã«ããèšå®ãã¡ã€ã«ã®åœ¢åŒã¯ãJSONã§ã¯ãªãYAMLã«ããŠããŸããããã«ã¯ã©ãŒãã®ãšã¹ã±ãŒããªã©è²ã
èæ
®ããªããšãããªãããã§ãã AWS.AppConfig.FeatureFlags ã®JSONã¹ããŒãã«æºæ ããããŒã¿æ§é ã§ãYAMLãã¡ã€ã«ã«èšè¿°ããŠç®¡çããŠããŸãããããAppConfigãžåæ ããéã«ã¯ãJSONã«å€æããŸãã version: "1" flags: sampleflag: description: 説æ attributes: attribute_name: description: 屿§èª¬æ constraints: type : number minimum: 1 values: sampleflag: enabled: false # ãŸã㯠_variants ãŸããã¿ãŒã²ãã£ã³ã°ã§ãã«ãããªã¢ã³ããã©ã°ãå©çšããããã«ã¯ã以äžã®ããã« _variants ã«ããããã®è©äŸ¡ã«ãŒã«ãå®çŸ©ããããŒã¿ã»ãããå®çŸ©ããå¿
èŠããããŸãã OpenFeatureã®è©äŸ¡ã³ã³ããã¹ãã«æ ŒçŽãããå€ãå©çšããruleã«äžèŽããããå€å®ããŸãã以äžã®èšå®äŸã ãšãè©äŸ¡ã³ã³ããã¹ãã«ã environment=dev ãšæ ŒçŽãããå Žåã¯ãäžçªç®ãéžæãããŸãã values: sign: _variants: - name: High cache enabled: true rule: (eq $environment "dev" ) attributeValues: cache_interval: 3600 expires_in: 3600 - name: default enabled: true attributeValues: cache_interval: 300 expires_in: 3600 attributeValues ã䜵ããŠå®çŸ©ããããšã«ããããã®ãã©ã°ã«é¢é£ã¥ãããã屿§ã远å ããããšãã§ããŸããOpenFeatureã§ã¯ããããMetadataãšããŠååŸããããšãå¯èœã§ãã AppConfigã®ãã©ã°å±æ§ã¯ãBoolean圢åŒã®FeatureFlagã®å Žåãenableã®æã®ã¿å©çšã§ãããŸãããŒã¿ã»ããå®çŸ©ã§ã _variants ã䜿ã£ãå Žåã§ãªããšå©çšã§ããŸããã ããã«ãããã¢ããªã±ãŒã·ã§ã³ã«æ§ã
ãªç¹æ§ãä»äžããããšãã§ããŸãã äžèšã®ããŒã¿ã»ããäŸã¯ãAWS KMSãå©çšãã眲åãªã¯ãšã¹ããå¶åŸ¡ãããã©ã°ãæ³å®ããŠããã®ã§ãããæ¯å眲åããã®ã¯ã³ã¹ãããããããã眲åããã£ãã·ã¥ãããæå¹æéãèšå®ããã®ã«å©çšãããããŠããŸãã ã¡ãªã¿ã«ãOpenFeature Go SDKã ãšãMetadataã¯å map[string]any ãšãªã£ãŠããããã©ã°ç®¡çã·ã¹ãã ã®å®çŸ©ãŸãã¯ãProviderã®ããŒã¹åŠçã«ãã£ãŠã¯ãåæ
å ±ã倱ãããŠããŸããŸãã 以äžã®ããã«ããã«ããŒé¢æ°ãå®è£
ããã®ãè¯ãã§ãããã func GetInt(metadata openfeature.FlagMetadata, key string ) (result int64 , err error ) { // map[string]any ãªã®ã§ãint64, float64 ã®äž¡æ¹ã詊ã intVal, err := metadata.GetInt(key) if err == nil { result = intVal return } floatVal, err := metadata.GetFloat(key) if err == nil { result = int64 (floatVal) return } return } func GetStringArray(metadata openfeature.FlagMetadata, key string ) ([] string , error ) { v, ok := metadata[key] if !ok { return nil , fmt.Errorf( "key %s does not exist in FlagMetadata" , key) } s, ok := v.([]any) if !ok { return nil , fmt.Errorf( "key %s is not an array" , key) } var strArr [] string for _, v := range s { if str, ok := v.( string ); ok { strArr = append (strArr, str) } } return strArr, nil } 泚æç¹ FeatureFlagãFFaaSãå©çšãããšãè²ã
ããããšã¥ããã®ããã«èŠããŸãããå¿
ãããäžèœã§ã¯ãããŸããã 1. åŸæ¹äºææ§ãä¿ãŠãªããªãªãŒã¹ãããå Žåãåé¡ãèŠã€ãã£ãåŸã«ãã£ãšåã®ç¶æ
ã«æ»ãã®ã¯é£ãã ã³ãŒãããŒã¹ãã§ããã ãææ°ã«ä¿ã€ãã©ã³ã¯ããŒã¹éçºã«ã¯äŸ¿å©ã§ãããæã«ã¯ãåŸæ¹äºææ§ãæãªãæ©èœããªãªãŒã¹ããªããšãããªãå ŽåããããŸãã åé¡ãèŠã€ãã£ãå Žåããã©ã°ã®åãæ¿ãã§ãã³ãŒããã¹ã®åãæ¿ãã¯ã§ãããããããŸããããããŒã¿æ§é ãå€ãã£ãŠããŸã£ãå Žåãç·æ¥ã¡ã³ããã³ã¹ã宿œããããŒã¿ã®æŽæ°ãå¿
èŠã«ãªããããããŸããã ãã®ãããæã«ã¯Featureãã©ã³ãã§ãmainãã©ã³ããšã¯é·æéåãé¢ããç¶æ
ã§ãéçºããæ¹ãå®å
šãªå¯èœæ§ã¯ãããŸãããã ãããã®æŠç¥ããšã£ãå Žåãåæµæã«ããããããã¯ãã³ãªãªãŒã¹ãšãªããäœæ¥ã¯å€§å€ã«ãªãããšæããŸãã 2. äžç¹å®å€æ°ã®ã¿ãŒã²ãã£ã³ã°ã«ã¯åããŠããããç¹å®ãŠãŒã¶ã®èªå¯ã®ä»£ããã«ã¯ãªããªã èªå¯ã¯ããŠãŒã¶ãŒãç¹å®ã®ã¢ã¯ã·ã§ã³ãå®è¡ãããããªãœãŒã¹ã«ã¢ã¯ã»ã¹ããããã æš©éããããã©ãã ãæ€èšŒããã»ãã¥ãªãã£ã¡ã«ããºã ã®ãããç®çãç°ãªããŸãã äœã§ãããã§ããFeatureFlagã§å¶åŸ¡ããã®ã¯ãããããªã ã§ãã 3. äžèŠã«ãªã£ãRelease Flagã®ãæé€ãé¢å ãªãªãŒã¹åŸå®å®éçšã§ããããŒã«ããã¯ããªããšããã£ãå Žåããã®ãã©ã°ã¯äžèŠãšãªããŸãããã ãFFaaSã®èšå®ãæ°è»œã«è§Šããããªããããã®ãŸãŸã«ããå Žåããããã©ã°èšå®ãåç
§ããå¿
èŠããªãã®ã«ãäžèŠãªã¢ã¯ã»ã¹ã§èª²éãããŠããŸããŸãã åé€ããã«ããŠãã䜵ããŠã³ãŒããæžãæããå¿
èŠããããŸãããã©ã°ã䜿ããã«æžãã®ãªãã°ããã®æ¹ããªãªãŒã¹åŸã«ä¿®æ£ãäžèŠãªãããç°¡æœã«å®è£
ã§ããŸãããŸãããªãªãŒã¹åŸã«ããç¡é§ã«èª²éãããããšãèãããšãå Žåã«ãã£ãŠã¯ãç°å¢å€æ°ãããŒã«ã«èšå®çµç±ã®å®çŸ©ã§æžãŸããæ¹ãè¯ããšããããšããããŸãã äžæ¹ã§ãé«è² è·æãã¢ã©ãŒããããªã¬ãŒã«æ©èœãªãã«ãããã«ã¹ã€ãããšããŠããªãªãŒã¹åŸã«ãOps Flagçã«å©çšããã®ãªããFFaasãå©çšãã䟡å€ã¯ãããŸãã ãããã« OpenFeatureãäžå¿ã«ãFeatureFlagã®çŸåšã«ã€ããŠã話ãããŸãããåèã«ãªãã°å¹žãã§ãã BASEã§ã¯ãä»åŸã®äºæ¥æé·ãæ¯ãããšã³ãžãã¢ãåéããŠããŸãã ãèå³ãããã°ããã²æ¡çšæ
å ±ãã芧ãã ããã æ¡çšæ
å ± | BASE, Inc. - BASE, Inc. ææ¥ã®BASEã¢ããã³ãã«ã¬ã³ããŒã¯å°ç¬ åããã®èšäºã§ãããæ¥œãã¿ã«ïŒ