Skip to content
This repository has been archived by the owner on Jul 1, 2022. It is now read-only.

Remove dependency from jaeger-core to jaeger-thrift #449

Conversation

jpkrohling
Copy link
Collaborator

Resolves #448 - This PR removes the dependency from jaeger-core to jaeger-thrift by loading
the appropriate Sender via either environment variables or based on existing service providers
on the classpath, loaded via ServiceLoader.

Signed-off-by: Juraci Paixão Kröhling [email protected]

Resolves jaegertracing#448 - This PR removes the dependency from jaeger-core to jaeger-thrift by loading
the appropriate Sender via either environment variables or based on existing service providers
on the classpath, loaded via ServiceLoader.

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
@ghost ghost assigned jpkrohling Jun 13, 2018
@ghost ghost added the review label Jun 13, 2018
@codecov
Copy link

codecov bot commented Jun 13, 2018

Codecov Report

Merging #449 into master will increase coverage by 0.8%.
The diff coverage is 97.22%.

Impacted file tree graph

@@             Coverage Diff             @@
##             master     #449     +/-   ##
===========================================
+ Coverage     87.55%   88.35%   +0.8%     
+ Complexity      517      489     -28     
===========================================
  Files            65       63      -2     
  Lines          2000     1846    -154     
  Branches        263      240     -23     
===========================================
- Hits           1751     1631    -120     
+ Misses          160      137     -23     
+ Partials         89       78     -11
Impacted Files Coverage Δ Complexity Δ
...ava/io/jaegertracing/reporters/RemoteReporter.java 83.33% <100%> (ø) 7 <0> (ø) ⬇️
...main/java/io/jaegertracing/senders/NoopSender.java 100% <100%> (ø) 5 <5> (?)
.../src/main/java/io/jaegertracing/Configuration.java 94.14% <100%> (+2.83%) 39 <0> (ø) ⬇️
.../java/io/jaegertracing/senders/SenderResolver.java 96.55% <96.55%> (ø) 9 <9> (?)
...er-core/src/main/java/io/jaegertracing/Tracer.java 89.66% <0%> (+2.47%) 23% <0%> (ø) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 808d6cc...4831dfe. Read the comment docs.

@@ -137,6 +136,11 @@
*/
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION";

/**
* The fully qualified class name for the sender factory
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wondering if we could select the factory based on a simpler name, which may be maps onto a predefined class name - so the configuration by the user requires less implementation knowledge?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This would be required only if there are multiple factories in the classpath. Do you think it would be a common use case to justify a map of "common" factories?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessary common, but just thinking of the detail the user would need to know (i.e. the fully qualified class name) to select a transport.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the usual case, they just need to have the transport on the classpath, there's no need to know the class name. If they end up somehow with two transports, the class name is used to disambiguate.

How about we go without this map for now, and add later if the case is more common than we thought?

Copy link
Contributor

Choose a reason for hiding this comment

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

Two other suggestions - (1) we treat multiple factories as an error - so if the case comes up to justify having two in the classpath, we can explore it further then, (2) add a type property on the factory, and that is what is specified in the environment variable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

add a type property on the factory, and that is what is specified in the environment variable?

I like this idea, as it's easily extensible.

Copy link
Member

Choose a reason for hiding this comment

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

I think FQN is better than just a class name.

Copy link
Member

Choose a reason for hiding this comment

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

add a type property on the factory,

is the type known when calling the factory?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think FQN is better than just a class name.

This is also available: if the env var contains a class name, we try to load it. Otherwise, we try to get a single factory from the service loader. If there are multiple, we try to match the type with the env var. If none matches, NoopSender is returned.

return sender;
}

return new NoopSender();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should also log a warning that no sender has been found.

@@ -43,6 +48,11 @@ shadowJar {
classifier 'thrift92'
}

shadowJar {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we just want the single shaded jar, with the dependencies relocated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should I just remove the one from jaeger-core and have then jaeger-thrift-okhttp381?

http://central.maven.org/maven2/io/jaegertracing/jaeger-core/0.28.0/

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I think I get a better understanding now. Did you mean to have the shadowed okhttp on the main artifact as well? Or did you mean to have the optional okhttp shadow artifact for thrift, instead of for jaeger-core?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I just changed this PR to include both shadowed thrift and okhttp on the same "shadow" artifact


assertTrue(Configuration.SenderConfiguration.fromEnv().getSender() instanceof HttpSender);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't there be some tests for getting the UdpSender?

@@ -8,6 +8,7 @@ dependencies {
compile group: 'io.zipkin.reporter2', name: 'zipkin-reporter', version: '2.6.0'

compile project(path: ':jaeger-core', configuration: 'shadow')
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a shadow configuration still required here? It might need to be on the jaeger-thrift dependency?

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
@jpkrohling jpkrohling force-pushed the 448-RemoveDependencyFromCoreToThrift branch from b21feed to 043a4b0 Compare June 13, 2018 14:32
@@ -34,33 +32,32 @@
private Semaphore semaphore = new Semaphore(Integer.MAX_VALUE);

public InMemorySender() {
Copy link
Member

Choose a reason for hiding this comment

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

we used to have inMemoryReporter. What's the point of InMemorySender?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The InMemorySender was in the reporters package. I moved it to this package, as it seems more appropriate.

Copy link
Member

Choose a reason for hiding this comment

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

ok, just for tests - nvm

}

public List<Span> getAppended() {
return new ArrayList<Span>(appended);
return new ArrayList<>(appended);
Copy link
Member

Choose a reason for hiding this comment

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

what are the threading expectations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Given that it's used only for tests, I would say that we don't guarantee anything. Concurrent modification will cause a ConcurrentModificationException, for instance.

@@ -6,9 +6,14 @@ apply plugin: 'com.github.johnrengelman.shadow'
description = 'Generated thrift code'
Copy link
Member

Choose a reason for hiding this comment

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

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This description wasn't changed in this PR. Would you like this description changed?

Copy link
Member

Choose a reason for hiding this comment

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

it's no longer accurate since you're extending the purpose and footprint of this module.

* @param senderConfiguration the sender configuration
* @return an appropriate sender based on the configuration, or {@link NoopSender}.
*/
Sender getSender(Configuration.SenderConfiguration senderConfiguration);
Copy link
Member

Choose a reason for hiding this comment

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

passing the configuration is kind of an abstraction leak in this case - the configuration must know about the needs of every possible sender factory.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree. This would require a bigger refactor than I'd like to focus on at this point. For consistency's sake, other configuration objects would also need such a change :/

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Signed-off-by: Juraci Paixão Kröhling <[email protected]>
/**
* Builds and/or selects the appropriate sender based on the given {@link Configuration.SenderConfiguration}
* @param senderConfiguration the sender configuration
* @return an appropriate sender based on the configuration, or {@link NoopSender}.
Copy link
Member

Choose a reason for hiding this comment

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

The NoopSender is an implementation detail of SenderResolver

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not really -- I expect concrete SenderFactory implementations to never return null. The contract is: either a real Sender, or NoopSender.

Copy link
Member

Choose a reason for hiding this comment

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

What can't we return null? It's invoked only in SenderFactory which takes care about null/noop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a contract between the implementation and the consumer, meaning that this documentation is a reference to both sides. By stating that it might return NoopSender, we are ruling out a null object, which makes it far easier for consuming code to not break (no null checks, no null pointer exceptions).

Copy link
Member

Choose a reason for hiding this comment

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

I still think the Noop here is just redundant. This API does not need it and it's just creating a dependency on specific sender impl


/**
* Represents a class that knows how to select and build the appropriate {@link Sender} based on the given
* {@link Configuration.SenderConfiguration}
Copy link
Member

Choose a reason for hiding this comment

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

I would expect some comments that this class is resolved via service loader. Also some explanation about meta-inf/services

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The resolver loads this via the service loader, but there's nothing preventing a consumer from calling a concrete implementation directly. Like: SenderFactory f = new InMemorySenderFactory()

import lombok.extern.slf4j.Slf4j;

/**
* Provides a way to resolve an appropriate {@link Sender}
Copy link
Member

Choose a reason for hiding this comment

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

I am missing some comments about how the sender is resolved. Is it using service loader or what approach? What should I do to provide my own sender?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is implementation-specific. The ThriftSender, for instance, builds an UdpSender or HttpSender based on the contents of the SenderConfiguration. More information is provided at the method-level javadoc.

Copy link
Member

Choose a reason for hiding this comment

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

Then how consumers of this API know how to provide their own sender?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Consumer of the API != Service Provider.

The Service Provider will know how to instantiate a new Sender and will return it as a result of this method call (see jaeger-thrift).

The consumer of the API (target application) will, at most, set the appropriate configuration options on SenderConfiguration and will get an appropriate sender in return.

Copy link
Member

Choose a reason for hiding this comment

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

I have my own sender impl. How do I use jaeger-core to load it from classpath? Where can I found more info about it? I just want to use Configuration.fromEnv()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You implement the SenderFactory, which returns your own Sender on the resolve() method. See the unit tests and the jaeger-thrift for an example on how to do that.

Copy link
Member

Choose a reason for hiding this comment

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

I implement SenderFactory in my app, add jaeger-core. then I use Configuration.fromEnv() without specifying any env var. Would be my sender factory used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@@ -137,6 +136,11 @@
*/
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION";

/**
* The fully qualified class name for the sender factory
Copy link
Member

Choose a reason for hiding this comment

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

I think FQN is better than just a class name.

@@ -137,6 +136,11 @@
*/
public static final String JAEGER_PROPAGATION = JAEGER_PREFIX + "PROPAGATION";

/**
* The fully qualified class name for the sender factory
Copy link
Member

Choose a reason for hiding this comment

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

add a type property on the factory,

is the type known when calling the factory?


@Override
public int flush() {
return 1;
Copy link
Member

Choose a reason for hiding this comment

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

I think 0 is more appropriate - also for close.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thought so too. But not for the append(Span), right?

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
Sender getSender(Configuration.SenderConfiguration senderConfiguration);

/**
* The Factory's name. Can specified via {@link Configuration#JAEGER_SENDER_FACTORY} to disambiguate
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "Can be specified"

* @return the resolved Sender, or NoopSender
*/
public static Sender resolve(Configuration.SenderConfiguration senderConfiguration) {
String senderFactoryClass = System.getProperty(Configuration.JAEGER_SENDER_FACTORY);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: senderFactoryClass -> senderFactoryType ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this still using the old mechanism - where the env var specifies a fq class name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It can be both. At this point in the code, we assume it's a class (we try to use it on a Class.forName later on).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Isn't this still using the old mechanism - where the env var specifies a fq class name?

Yes. I thought the type is only used to disambiguate in case there are multiple factories available? The previous resolution was:

  • FQCN
  • Service Loader (first service available)
  • NoopSender

The current is:

  • FQCN
  • Service Loader (when only one service is available)
  • Service Loader + Type (when multiple services are available)
  • NoopSender

Copy link
Member

Choose a reason for hiding this comment

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

Type in SenderFactory seems redundant as you can still specify the wanted impl as FQCN or wouldn't it work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would, and it's how it was being done before. Looks at this thread: #449 (comment)

@@ -40,7 +43,8 @@ checkstyleTest.enabled = false
shadowJar {
baseName = 'jaeger-thrift'
relocate 'org.apache.thrift', 'org.shadow.apache.thrift92'
classifier 'thrift92'
relocate 'okhttp', 'jaeger.okhttp'
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we should maybe have a consistent relocate pattern - so possibly prefix jaeger as here, so update the thrift one to jaeger.org.apache.thrift (without the 92)?
btw - might also be worth looking at the other dependencies included in this shaded jar, as they may be should be relocated aswell to avoid any potential clashes - or possibly this should be a separate PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

or possibly this should be a separate PR

That would be my preference.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@objectiser objectiser left a comment

Choose a reason for hiding this comment

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

LGTM - subject to resolving the factory env var issue. Couple of nits to look at.

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
/**
* Resolves a sender by passing the given {@link Configuration.SenderConfiguration} down to the
* {@link SenderFactory}. The factory is loaded either based on the value from the environment variable
* JAEGER_SENDER_FACTORY or, in its absence or failure to deliver a {@link Sender}, via the {@link ServiceLoader}.
Copy link
Member

Choose a reason for hiding this comment

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

JAEGER_SENDER_FACTORY make it a javadoc link.

* @return the resolved Sender, or NoopSender
*/
public static Sender resolve(Configuration.SenderConfiguration senderConfiguration) {
String senderFactoryClass = System.getProperty(Configuration.JAEGER_SENDER_FACTORY);
Copy link
Member

Choose a reason for hiding this comment

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

Type in SenderFactory seems redundant as you can still specify the wanted impl as FQCN or wouldn't it work?

import lombok.extern.slf4j.Slf4j;

/**
* Provides a way to resolve an appropriate {@link Sender}
Copy link
Member

Choose a reason for hiding this comment

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

I implement SenderFactory in my app, add jaeger-core. then I use Configuration.fromEnv() without specifying any env var. Would be my sender factory used?

@jpkrohling jpkrohling force-pushed the 448-RemoveDependencyFromCoreToThrift branch from 9c8cec0 to 57f53ec Compare June 14, 2018 13:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants