Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add config to allow surpressing notification on launch (flag cache load) #2534

Merged
merged 20 commits into from
Oct 24, 2024

Conversation

8ma10s
Copy link
Contributor

@8ma10s 8ma10s commented Oct 17, 2024

Description

This pull request introduces a new feature to disable notifications on initialization and refactors the caching mechanism to support this feature. It also includes updates to the test suite to ensure the new functionality works as expected.

Problem

  • go-feature-flag always sent notification of diff to all Notifierson ffClient initialization because it detects the difference between the initial cache (= always empty) and the flags retrieved.

Resolution

  • introduce DisableNotificationOnInit configuration key. If set to true, avoid sending the notification on cache initialization (it will still send notification when the change is detected AFTER initialization)
    • Since the behavior (should) stays the same if the new config is NOT set, it should not introduce any breaking change.

How to test

  • I've added some tests to make sure the behavior stays the same when the new config is not explicitly set. You can run go test ./...
  • Or, simply initialize ffClient should do the trick too.

Closes issue(s)

Resolve #2532

Checklist

  • I have tested this code
  • I have added unit test to cover this code
  • I have updated the documentation (README.md and /website/docs)
  • I have followed the contributing guide

Copy link

netlify bot commented Oct 17, 2024

Deploy Preview for go-feature-flag-doc-preview canceled.

Name Link
🔨 Latest commit ce61272
🔍 Latest deploy log https://app.netlify.com/sites/go-feature-flag-doc-preview/deploys/671a3df485f8e30008413b7a

@8ma10s 8ma10s changed the title Skip notification on launch feat: Add config to allow surpressing notification on launch (flag cache load) Oct 17, 2024
Copy link

codecov bot commented Oct 17, 2024

Codecov Report

Attention: Patch coverage is 94.73684% with 1 line in your changes missing coverage. Please review.

Project coverage is 85.48%. Comparing base (a1955f0) to head (ce61272).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/relayproxy/service/gofeatureflag.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2534      +/-   ##
==========================================
+ Coverage   85.39%   85.48%   +0.09%     
==========================================
  Files         100      100              
  Lines        4436     4444       +8     
==========================================
+ Hits         3788     3799      +11     
+ Misses        518      516       -2     
+ Partials      130      129       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

This also aligns the interval with rest of the test code
@8ma10s 8ma10s marked this pull request as ready for review October 17, 2024 09:09
@8ma10s 8ma10s marked this pull request as draft October 17, 2024 22:36
@8ma10s 8ma10s marked this pull request as ready for review October 18, 2024 08:51
@8ma10s
Copy link
Contributor Author

8ma10s commented Oct 22, 2024

Sorry about the repeated commits. I think I've fixed the issue, and hopefully it works now.

Copy link
Owner

@thomaspoignant thomaspoignant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this PR @8ma10s this is really great.

I like your approach but I will tend to simplify it a bit by just adding some parameters rather than creating new functions that can be hard to understand what is happening.
Would it work for you too?

Comment on lines 178 to 184
// DisableNotificationOnInit (optional) set to true if you do not want to
// send a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotificationOnInit bool `mapstructure:"disableNotificationOnInit" koanf:"disablenotificationoninit"`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I understand why you've put notification here, but I think it will be more explicit to use the name notifier which is what we are using everywhere.

Suggested change
// DisableNotificationOnInit (optional) set to true if you do not want to
// send a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotificationOnInit bool `mapstructure:"disableNotificationOnInit" koanf:"disablenotificationoninit"`
// DisableNotifierOnInit (optional) set to true if you do not want to
// send a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotifierOnInit bool `mapstructure:"disableNotificationOnInit" koanf:"disablenotificationoninit"`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense, thank you. Yes, "notification" does seem a bit vague (as there can be other kinds of notifications), but "notifier" in this repo's context is pretty clear. Let me change that 👍

config.go Outdated
Comment on lines 31 to 37
// DisableNotificationOnInit (optional) set to true if you do not want to send
// a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotificationOnInit bool
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before

Suggested change
// DisableNotificationOnInit (optional) set to true if you do not want to send
// a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotificationOnInit bool
// DisableNotifierOnInit (optional) set to true if you do not want to send
// a notification when the flags are loaded.
// This is useful if you do not want a Slack/Webhook notification saying that
// the flags have been added every time you start the application.
// Default is set to false for backward compatibility.
// Default: false
DisableNotifierOnInit bool

feature_flag.go Show resolved Hide resolved
feature_flag.go Outdated
}

// retrieveFlagsAndInitializeCache is called when the feature flag is initialized
func retrieveFlagsAndInitializeCache(config Config, cache cache.Manager, retrieverManager *retriever.Manager) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather having almost the same function for retrieveFlagsAndInitializeCache and retrieveFlagsAndUpdateCache I would have prefer to add a parameter to the previous function retrieveFlagsAndUpdateCache and use the same code on both side.

Something like

func retrieveFlagsAndUpdateCache(config Config, cache cache.Manager, retrieverManager *retriever.Manager, isInitialization bool) error {

// ...

    if isInitialization && config.DisableNotificationOnInit {
		err = cache.UpdateCache(newFlags, config.internalLogger)
	} else {
		err = cache.UpdateCacheAndNotify(newFlags, config.internalLogger)
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same deal as here: #2534 (comment)

I think I just tend to prefer having explicit function/method names just to make it a bit more readable on the caller code and avoid introducing too many parameters in a single method, but I think either works. To make it consistent with the code on the above comment, let me change this one as well 👍

Comment on lines 64 to 70
func (c *cacheManagerImpl) UpdateCache(newFlags map[string]dto.DTO, log *fflog.FFLogger) error {
return c.updateCache(newFlags, log, false)
}

func (c *cacheManagerImpl) UpdateCacheAndNotify(newFlags map[string]dto.DTO, log *fflog.FFLogger) error {
return c.updateCache(newFlags, log, true)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need those functions or should we just keep UpdateCache with the new parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion

Sure, I think I can change the code to do that. Let me change my code.
Given that it's an internal interface that's not meant to be used by anything outside of this repo's code, I think we don't need to care so much for readability (or method clarity) as much as I initially thought.

My initial thought

I just thought that, given that CacheManager is (technically) defined as an interface, it would be beneficial to have a separate method to clearly indicate what the method is doing on the inside, rather than having an argument that doesn't really convey anything to the reader of the code.

What I mean is, if I have a code that calls this method defined in the interface, it would be (slightly) easier for the readers to understand what's going on when written like

err = cache.UpdateCacheAndNotify(newFlags, config.internalLogger)
// do other stuff...

rather than

err = cache.UpdateCache(newFlags, config.internalLogger, true)
// do other stuff...

because the value of true doesn't really convey anything just by reading it, whereas the method name UpdateCacheAndNotify clearly tells that it's gonna "notify something".


err := cache.UpdateCache(newFlags, config.internalLogger)
err = cache.UpdateCache(newFlags, config.internalLogger, isInit && !config.DisableNotifierOnInit)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't want to introduce breaking changes, we have to make false to be the original behavior, so that the default behavior would be same as the behavior before this PR. This is why we're making the config value name to be disableNotiferOnInit, and it's making this boolean logic slightly more difficult to read by introducing double negation.

Hopefully at some point, this repo makes the decision to make disabling the notifier the default behavior.
At that point, we can make the config value name to be enableNotifierOnInit (without negation, and that should make this code much easier to read:

err = cache.UpdateCache(newFlags, config.internalLogger, isInit && config.EnableNotifierOnInit)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverting the logic is something I am open with but we will need to explicitly guide GOFF users about this change in the future.

I'll keep it in mind in the potential breaking changes for the future.

Copy link
Owner

@thomaspoignant thomaspoignant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again, we are almost ready to go and to merge this PR.

I have just put some comments on the mocks because we can probably reuse the existing one instead of creating new ones.


err := cache.UpdateCache(newFlags, config.internalLogger)
err = cache.UpdateCache(newFlags, config.internalLogger, isInit && !config.DisableNotifierOnInit)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inverting the logic is something I am open with but we will need to explicitly guide GOFF users about this change in the future.

I'll keep it in mind in the potential breaking changes for the future.

feature_flag_test.go Outdated Show resolved Hide resolved
internal/cache/cache_manager_test.go Outdated Show resolved Hide resolved
variation_test.go Outdated Show resolved Hide resolved
website/docs/go_module/configuration.md Outdated Show resolved Hide resolved
website/docs/relay_proxy/configure_relay_proxy.md Outdated Show resolved Hide resolved
@thomaspoignant
Copy link
Owner

Thanks a lot for your contribution @8ma10s this is really great.

Copy link

sonarcloud bot commented Oct 24, 2024

@kodiakhq kodiakhq bot merged commit 1351a3f into thomaspoignant:main Oct 24, 2024
22 checks passed
@8ma10s
Copy link
Contributor Author

8ma10s commented Oct 24, 2024

@thomaspoignant Thank you for reviewing and giving me suggestions! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(change) Don't send "new flag added" notification upon ffClient initialization
2 participants