-
Notifications
You must be signed in to change notification settings - Fork 597
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
Extending functional options #746
Comments
Approach 1 -
|
Approach 2 -
|
Approach 3 -
|
Approach 4 - type embedding + type assertionUse type embedding and type assertion in vendor-specific repo.
It does not require any change. Pros:
Cons:
|
I may be very biased and subjective. Feel free to point out other pros/cons and also feel free to argue if anything I described is not true. I think that approach 3 is the cleanest solution. However approach 4 can be used to almost any functional option API. Personally, I would go for approach 4. However maybe after looking at this analysis we would still like to make some changes in the current OTel functional options design. |
otelhttp.Option is a function that applies to an inner structure with inner options - a black box. Here you embed otelhttp's functionality, so you should embed their options, too - even better, hide them! |
@tgulacsi First off all thanks for your comment 👍
No sure if you are talking about Splunk or OTel package. However take notice that it is not possible to "not allow" external options (there is one possibility but it would result in very unfriendly API - unexport the Option interface). We can only make sure that external options are not processed. All described approaches achieve it (for OTel at least). Maybe for approach 2 it would be possible to change the config using reflection. |
otelhttp exposes several Option, but as a black box. Only those are usable os configuration-altering options. splunkhttp SHOULD NOT expose otelhttp options, MUST hide them behind a wrapper. For me it's weird to allow any otelhttp option through splunkhttp. Esp. as they're in a constant flux... |
So a bit of logistics before we get into any technical discussion. It seems like you have done a lot of work to explore these options, but you need a bit more to make it presentable to this audience. What would go a long way to achieve this is:
Remember you are asking for a review from others that don't know what you know and have limited time to review. Try to make it easy for them to understand what you are changing and why. As for the technical discussion: Returning errors: |
@tgulacsi Thanks for clarification
Returning an unexported type is highly discouraged. You cannot make a field, function, array variable (the last is critical for functional options) it the type is unexported. This is also why I have written "it would result in very unfriendly API". More: golang/lint#210
Thanks 👍 PRs fixed
Good remark. It is designed that way so that:
The whole idea of https://github.com/signalfx/splunk-otel-go is to create convenient and easy to use helpers that are working on top of OTel libraries. |
@MadVikingGod I highly appreciate your comment.
Great guidance 👍 Done. Please review and feel free to tell me if I can make it more approachable (without making too much noise).
Currently, I only think that approach 3 could make the extensibility easier. While still keeping the options as black boxes. But there is a drawback from OTel perspective (requires type assertion). Therefore, I guess approach 4 will be the favoured one. Also based on your feedback I have created: #750
Not sure if I follow. But I believe it would be better to create a separate issue 😉 |
While I can understand the desire to have these properties, I think as it is currently structured it may prove overly limiting and lead to users to being unable to use it at all. By having If, instead, the splunkHandler := splunkhttp.NewHandler(handler, splunkhttp.WithFoo())
h := otelhttp.NewHandler(splunkHandler, otelhttp.WithBar())
h2 := otelmux.NewHandler(splunkHandler)
h3 := otelgin.NewHandler(splunkHandler, otelgin.WithBaz()) |
@Aneurysm9 Thanks for your insight 🙇 I think that what you presented can be done in Splunk in addition to one of the presented approaches. And it can be also reused internally. 👍 What you presented is more flexible and composable. However, it requires the client to write more code. Especially if we would like to set some default options (TBH I am not sure if there is a use case for it, but maybe someone might want to add some default filter?). splunkHandler := splunkhttp.NewHandler(handler, splunkhttp.WithFoo())
opts := splunkhttp.DefaultHttpOpts()
opts = append(otelHttpOpts, otelhttp.WithBar())
instrHandler := otelhttp.NewHandler(splunkHandler, opts...) Compare it to: instrHandler := otelhttp.NewHandler(handler, splunkhttp.WithFoo(), otelhttp.WithBar()) Correct, the presented approach has to be implemented per each instrumentation. To achieve the same coding experience for Besides I guess that your comment would also be valid for extending instrumentations done on top of PS. nit: Your code snippett probably should have |
If you are really looking to make programing this easier, then you should entirely encapsulate the otelhttp package. Users of your package shouldn't know or have to find out what otelhttp configuration options there are, they should be using splunkhttp options. This lets you keep up with changes to otelhttp at your pace, and if you want to keep in lockstep I would look at code generation. It would also mean users have only one location they have to search to understand what knobs they can configure. I would also point out in what you linked:
If these are orthogonal features you should make the component orthogonal. But you look to want to encapsulate, so you should make your component encapsulate entirely the API. |
Approach 5 - encapsulate the
|
I am getting convinced that your proposal is the most pragmatic one (and probably optimal as well).
At the same time, I start to feel that each of my proposed approaches is overcomplicated compared to the benefits they bring (and the benefits are still very questionable). |
Approach 6 - do NOT extend, but create another middlewareby @Aneurysm9 Create
Pros:
Cons:
|
@Aneurysm9 @MrAlias Feel free to close the issue. I am not closing it, because maybe you want to have some follow up (e.g. docs) 😉 🤷 |
Why
It would be good to provide guidance on how to extend on top of functional options API.
What
Make vendor-specific (which may be not OTel compliant) extensibility "easy" and user-friendly.
The whole idea behind https://github.com/signalfx/splunk-otel-go is to create convenient and easy to use helpers that are working on top of OTel libraries. For example:
splunkhttp.NewHandler
may set default otelhttp optionsotelhttp.NewHandler
I have experimented with 4 approaches. I will post them as separate comments so you can comment/upvote/downvote each one individually.
All of these approaches allow achieving the following usage code:
The
splunkhttp.WithOTelOpts(....)
call is no longer needed.All of the approaches are not making any breaking change.
I posted this issue because I think we should strive to use similar extensibility approaches across different vendors.
Other remarks
Blocking extensibility by design?
Is there any reason to do such things? I would say it is blocking extensibility on purpose, but why? BTW even if I would like to do so I would rather unexport this method: https://github.com/open-telemetry/opentelemetry-go/blob/b09df84a24b35cb5f287a501e31d9dec324be5a9/trace/config.go#L41.
Answered here.
Why functional options cannot return an error?
One of the main advantages of functional options pattern is that it can be also used to encapsulate validation. How we would notify the client that his configuration passed to
opentelemetry-go-contrib/instrumentation/net/http/otelhttp/handler.go
Line 60 in e1a7c47
Answered here.
The text was updated successfully, but these errors were encountered: