-
Notifications
You must be signed in to change notification settings - Fork 343
Introduce carrier format identifiers for parity with other platforms #31
Comments
(Note that this will be |
FWIW, I really like this direction. I'm planning to do an RFC for a |
Alright, I sent out #32 ... comments welcome. |
I can see the discussion and project has evolved to SpanContext, and i'm a lot more comfortable with it now. I'm not sold on the ideas of Formats though (not yet at least). I disagree with (1) being either ugly and inefficient. And (2) just doesn't make sense.
This is a nonsensical statement. Sorry for being blunt. But how does one re-use carriers when the notion of different formats doesn't explicitly exist? And what is preventing you from re-using carriers for different formats undefined but implicitly existing? Can you explain this a bit better for me please. |
public <T> Injector<T> getInjector(Class<T> carrierType) {
Class<?> c = carrierType;
// match first on concrete classes
do {
if (injectors.containsKey(c)) {
return injectors.get(c);
}
c = c.getSuperclass();
} while (c != null);
// match second on interfaces
for (Class<?> iface : carrierType.getInterfaces()) {
if (injectors.containsKey(iface)) {
return injectors.get(iface);
}
}
throw new AssertionError("no registered injector for " + carrierType.getName());
} vs. public <W> Injector<W> getInjector(Format<?, W> format) {
return injectors.get(format);
} I stand by my qualification that the former is "ugly and inefficient", and not due to inexperience of the author, but due to restrictions imposed by the API.
What I meant was "It is currently impossible to reuse the same carrier type for different formats." Given the above example, if I defined an interface for a carrier, e.g. public interface TextMapWriter {
void put(String key, String value)
} then class-based injector lookup will always return the same injector when passed an instance of Basically, the carrier type defines an API for how data is injected into it. The Format defines what data can be injected. |
(1) This is not code we expose to the user or client implementors. I'm in favour of making the users life simpler for the sake of 9 more lines of code internally. Simplicity here boils down to a trade-off between adding the Format contruct and the caveat that concrete classes will be matched before interfaces. (2)That's for the clarification. And that argument is valid. |
Agreed on (1). On (2), what we changing for the users is asking them to pass one extra parameter. Not really "lines of code", but extra typing for sure. What they are getting in return is:
|
(3) Sounds like enough good reasoning has been provided. If the numbers are behind going with this then do it. (@bensigelman i'm still in favour of two separate PRs, since this and the SpanContext deserve to be separate atomic commits.) |
@michaelsembwever noted, I'm working on it :) |
Ok, #32 has the SpanContext stuff without the Format changes. I will try (?) to get another PR built off of that base that reintroduces the Format stuff later this evening; hopefully I'll have time. @adriancole curious to hear what you think about this. I'm mainly interested in Yuri's point (2) above... namely that inject/extract can be subtle, and self-documenting code is preferable to "maximally concise" code. The concerns of the Tracer implementor are perhaps a tie-breaking vote if we need one, but as long as it's not going to show up in a profiler I don't really care about the verbosity/awkwardness there... I'm much more interested in how it looks to the caller. I'm a +0.5 vote in favor of @yurishkuro's proposal (after normalizing for my lack of recent experience in Java). |
@bensigelman as made in the other PR, If a user "chooses" to pass the same format object to both read and write calls, then they have accomplished their goal. There's nothing about Since this is up to the user anyway, if we must expose an implementation detail like this generically, I'd suggest we focus on the type that's in use by the method, in this case here's one example, but these things abound as many libraries have a need to qualify a type and/or capture a generic type. https://google.github.io/guice/api-docs/latest/javadoc/index.html?com/google/inject/Key.html |
More concretely to this use-case, there's no generic type resolution problem, as the standard types have no generic type parameters. To solve the problem of having two different formats (or versions) of the same type, we minimally need a parameter for the qualifier. Ex. // I don't care.. let the system decide
<T> Injector<T> getInjector(Class<T> carrierType);
// I expect to use generic parameters, but don't want to define how this is implemented
<T> Injector<T> getInjector(Type carrierType, Annotation/Object/String/etc format);
// I expect to use generic parameters, and I want to define how this is implemented
<T> Injector<T> getInjector(Format<W> format) We lost the battle on defining was
<W> void inject(Span span, W carrier);
now
<W> void inject(Span span, Format<?, W> format, W carrier);
suggestion to drop the racing stripes and keep the old api since it is less complex
<W> void inject(Span span, W carrier, Format<W> format); |
ps |
For me it's the (3) that is a show stopped, it creates a dependency that is impossible to break without either introducing a 3rd library purely for a unique interface class, or making the user code dependent on specific tracing implementation classes (even thought the the same behavior can be supported by multiple tracers).
The reason the format is generically typed is to prevent wrong combinations like
But generics are just a candy, for type safety. The main points are (2) and (3) in my earlier comment today, which are not addressed by a carrier-only syntax of |
So there are basically four options on the table IIUC (other ideas welcome):
Am I missing anything? Anybody want to propose a strong endorsement and/or a veto (and ideally "why" if you didn't already make it clear in the discussion above). Thanks. |
It's worth mentioning that options 2-4 are only different in the method signature. From the end-user point of view, the invocations of the API look exactly the same. My preference is 4, 3, 2, and a strong No to 1, because it disallows reuse of common types like Map or ByteBuffer for different formats, forcing instrumentation and tracing implementation to agree on some carrier interface that needs to be released as an external dependency for both. |
PTAL at #33. |
Hello there, I just started looking at the OpenTracing project and got interested in it. I wonder why Putting the logic inside A potential problem of concrete One solution is to define a standard set of formats that must be provided by all tracer implementations (basically the TextMap and Binary formats stated in the spec). The OpenTracing API could then define some methods (e.g. |
@maxwindiff thanks for the note, and this is a good question. TL;DR, supporting things like Also, imagine that some new IPC technology called BetteRPC takes the world by storm, and thus gets its own (externally-defined) OpenTracing format... we would have the same situation as with TextMap or Binary, except that we can't add an abstract method to the Tracer interface called |
I think it's wrong to have different formats with the same type signature and no other difference. This now means that HttpHeaders and TextMap are only distinguishable by its object reference. Given a format object, the only way to detect the actual format is to do a reference comparison with the static variables. If (for whatever reason) you don't have access to this variable or someone created the object manually, you are lost. The format interface should implement a public name string and equals() should use it for comparison. This way, implementations can depend on the string instead of an object reference. In general, I'm not yet convinced by this typed concept but that might be my bad. 😀 |
This is driven by several concerns:
opentracing.TextMap
in Go, makes the implementation as simple as a map lookup, and remove ambiguity of dealing with potentially multiple superclasses and interfaces implemented by a carrierTextMapReader/Writer
interfaces in Go can be used for both plain TextMap format as well as for upcoming HTTPHeader format. It is much easier to define another constant for the format than to duplicate the reader/writer interfaces.The text was updated successfully, but these errors were encountered: