-
Notifications
You must be signed in to change notification settings - Fork 108
Why not use interfaces instead of classes? #128
Comments
Can’t your SpanContext extend the OpentTracing SpanContext? |
In this case yes. But another example would be if I wanted to offer additional SpanOptions: opentracing Tracer:
custom Tracer:
Is the idea that these things shouldn't be touched so the public interface stays exactly the same? |
Hmm it should work as long as everything is assignable? Could you give a minimal reproducible example? |
Does that help? |
The error you are getting is that the function returns void instead of Span. It needs to return a Span (that’s the same with interfaces or classes) |
Sorry, you're right of course. In eagerness to demonstrate my problem I didn't take time to read the error message properly. I updated my example again though. I'm probably just not seeing it - but how do I work around this issue? Since |
Not sure if it's the best way but the way we made it work was to extend all classes, so then our You can see the definition here. The only downside is that this breaks TypeDoc, but that's a bug in TypeDoc. |
First, the premise is unclear to me - there are many tracers that implement this API by extending the base classes (data dog, Jaeger, Lightstep,etc) Second, start span options is a value object that defines the signature of the method. Changing that signature is not "extending" it, it's "breaking" it - you are introducing behavior not specified by OpenTracing API. You can certainly do it in your company if all you instrumentation is 100pct internal, but then you must realize that you're no longer using OpenTracing API, but a clone that looks like it, because you break the basic compatibility guarantee. Eg you won't be able to replace your tracer implementation with another off the shelf, should you decide to do so in the future. |
@chlab you are not calling -import {
- Tracer as OpenTracer,
- SpanOptions,
- Span as OpenSpan,
- SpanContext as OpenSpanContext
-} from "opentracing";
-
-class SpanContext extends OpenSpanContext {
+import * as ot from "opentracing";
+
+class SpanContext extends ot.SpanContext {
spanId: string;
}
-class Span extends OpenSpan {
+class Span extends ot.Span {
__context: SpanContext;
context(): SpanContext {
@@ -22,13 +17,20 @@ class Span extends OpenSpan {
}
}
-class Tracer extends OpenTracer {
+interface SpanOptions extends ot.SpanOptions {
+ childOf?: Span | SpanContext
+}
+
+class Tracer extends ot.Tracer {
startSpan(operationName: string, options: SpanOptions = {}): Span {
- let parent: SpanContext;
+ let parentSpanId: string;
// results in error:
// Property 'spanId' does not exist on type '() => SpanContext'
- parent = (options.childOf as Span).context.spanId;
+ if (options.childOf) {
+ const context = options.childOf instanceof Span ? options.childOf.context() : options.childOf
+ parentSpanId = context.spanId;
+ }
return new Span();
} See below why I think you may have a point, but this is unrelated to whether interfaces or base classes are used. Although, I agree that interfaces would be cleaner (base classes are an issue when multiple versions are involved). The only reason I didn't do that in the past was cause I couldn't think of good names for the interfaces while keeping the names of the current classes. I think one way around that would be using something like @yurishkuro is it expected though that within one runspace, you can pass a |
But for the jaeger node client, you guys aren't extending the opentracing classes? If changing (adding to) the options is breaking the API, isn't adding any public method to your custom implementation breaking the API? (I'm not trying to be difficult here btw, just trying to make sure I'm not missing something.)
My goal is for our implementation is to be able to do just that. I'm having a bit of trouble in finding out where to put the logic that knows how to map our various specific fields to opentracing. @felixfbecker @rochdev |
@felixfbecker it is not explicitly prohibited by the API, but no, it's not the expected situation. Most tracers wouldn't know what to do if they receive "foreign" objects.
@chlab why would it be breaking? We're talking about Javascript here, but in a "normal" language (sorry, couldn't help myself), the user code would be interacting with an interface, without knowledge of the underlying implementation, so even if that implementation has additional public methods, it does not affect compatibility with the interface. Also, does Javascript even have such thing as "interface"? To my knowledge this lib provides a noop implementation that serves to define the API, but the implementations are not required to extend the classes. It might be different in Typescript though, so yes, if TS has clear distinction between classes and interfaces, then this library should ideally define interfaces first, and then classes as no-op impl. |
TypeScript has a distinction, but interfaces can actually extend classes, so we could say that a class is simply an interface with an implementation in a single construct. For the purpose of this discussion, this is my understanding (@yurishkuro please correct me if I'm wrong):
@chlab Since you said your goal is to preserve interoperability, then you should not alter any existing API and instead rely on constructors or |
Probably not all vendor specific properties, only the things you want to propagate from system to system. According to the docs on opentracing.io:
But I agree - I guess the best place to handle implementation specific things is by passing options to the constructors. @rochdev @yurishkuro I am browsing DD's documentation and this (see Scope Manager) is what I mean by adding new public methods to the opentracing classes breaking the API. If someone uses @felixfbecker I updated my example again. If a client uses the custom implementation he will get errors that the custom method on Tracer and custom field on SpanContext aren't defined. I guess in this case it's correct, seeing as, as mentioned above, this would void interoperability. However, I'm still stumped on how I would e.g. handle custom fields on the SpanContext the way it is now. Update: after more thought and discussions, I think the way to go for me is by passing a custom reporter to the tracer and handling certain custom properties there and handling the rest via tags. |
I am working on an implementation of a tracer that handles our organizations specific needs but ideally would use the interfaces defined by opentracing. Maybe I am missing something but I don't see how that would work the way the types are defined now, with classes instead of interfaces.
E.g. if I define my own
Tracer
class that expects certain properties on theSpanContext
, TypeScript will tell me that these properties don't exist.If the opentracing types were interfaces, custom implementations could
implement
them and this wouldn't be a problem.A consumer using my tracing implementation could not expect a span returned by my tracer's
startSpan()
to be of type opentracingSpan
.Could you provide a few pointers on how you propose to create a custom tracer implementation that still uses the opentracing types?
Thanks 👍
The text was updated successfully, but these errors were encountered: