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

Why do policies use builders? #12

Open
jhalterman opened this issue Sep 12, 2023 · 0 comments
Open

Why do policies use builders? #12

jhalterman opened this issue Sep 12, 2023 · 0 comments
Labels
question Further information is requested

Comments

@jhalterman
Copy link
Collaborator

jhalterman commented Sep 12, 2023

This may end up becoming a FAQ, so I wanted to write a few notes on why policies use builders. There were two main options with how we could design an API to configure policies:

  1. Use structs to directly set configuration
  2. Use methods

Since Failsafe-go configurations can be complex internally, and since we want to potentially validate settings in the future, it makes sense to configure policies using methods rather than setting fields directly on structs. That being the case, it also makes sense to just define policies as interfaces rather than structs, since no fields will be visible and using factory methods to construct them allows us to define good defaults.

That leaves us with the question of how we want our configuration methods to be designed. A few options include:

  1. Make policies mutable, via configuration methods
  2. Make policies immutable, where each configuration method returns a new copy
  3. Make policies immutable, but built via mutable builders
  4. Make policies immutable, but built via functional options
  • A drawback of option 1 is it would add more overhead when making policies concurrency safe, since we'd have to do an atomic operation or take a lock just to read simple variables like a RetryPolicy's MaxRetries.
  • A drawback of option 2 is that we'd add more overhead when constructing policies, which for high volume usage could be a problem. Another drawback with option 2 (and 1) is that placing policy configuration directly on the policies themselves would result in a larger API surface area in policies like CircuitBreaker, where configuring and operating the policy already offer several methods.
  • A drawback of option 3 is that while some Go libraries use builders (like the k8s REST client) it's not common. It also creates an additional object allocation to create the builder, though fewer than option 2. On the positive side, option 3 allows us to avoid concurrency safety problems when frequently reading policy settings internally.
  • A drawback of option 4 is that functional options don't have the same easy discoverability of builder methods, which benefit from code completion. They're also more verbose to use than builders, since each option has to be referenced by the package name. The only benefit that functional options offer, the ability for users to define their own options, isn't relevant here.

That all being the case, option 3 seemed to have the most positives and least negatives, leaning heavily towards an API that is user friendly.

@jhalterman jhalterman added the question Further information is requested label Sep 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant