-
Notifications
You must be signed in to change notification settings - Fork 16
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
New entry-point: Dialogue.newClientPool() #398
Conversation
* Facilitates creating many clients which all share the same connection pool. Should only create one of these per | ||
* server. Close it when your server shuts down to release resources. | ||
*/ | ||
public interface ClientPool extends Closeable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably a reasonable place to start reading this PR if you want
import com.google.errorprone.annotations.MustBeClosed; | ||
import com.palantir.dialogue.ConjureRuntime; | ||
|
||
public final class Dialogue { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My dream is if we can make all the things people care about accessible from one top-level starting point, so they can just remember "the method is on Dialogue somewhere".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if that's the case it'd be better to return a ClientFactory that uses the provided ClientPool so that no user of Dialogue has to know anything about something called a ClientPool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not so clear to me that this code should live in dialogue land rather than in wc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@uschi2000 are you referring to just this one entrypoint or the whole PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea of a "singleton client factory", i.e., something that holds on to resources for the lifetime of a process, is something I'd expect in a server library, not a client library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we try to make all our libraries good enough to use on their own, then when we pull them into WC they'll still be good. I actually think a client library that doesn't give you a convenient way to release resources when you want to seems broken/incomplete.
To be clear this API doesn't know anything about the lifetime of a process, and there are no static singletons here. it would still be up to witchcraft to make sure there's one client pool and witchcrafts responsibility to close it at shutdown time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dialogue
is designed to be a singleton, no?
Yes, it's a bug today that we don't close channels, but then why isn't it sufficient for this library to add Channel#close
? I prefer hierarchical ownership (with delegating .close()
) over complex "inside-out" resource management; is that not an option here?
Is this actually desirable? |
* will always fail if the specified uri is not listed in the latest version of the config. Somewhat dangerous | ||
* because this has no limits / failover. | ||
*/ | ||
Channel rawHttpChannel(String uri, Listenable<DialogueConfig> config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this actually something that should be exposed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not to be honest - I started with this 3-member interface just to hash out in my head what the key building blocks were. Happy to push it down to package private.
* Returns a channel for interacting with the given abstract upstream service, which routes traffic | ||
* appropriately to the various available nodes. Live-reloaded every time the urls change. | ||
*/ | ||
Channel smartChannel(Listenable<DialogueConfig> config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when does one use this instead of get
?
I think so, because the average WC server has 38 clients according to datadog, but I think some have much fewer (like maybe 2) and some have even more. This means any limits are gonna seem to apply quite differently. It also tries to provide similar-ish behaviour to our old c-j-r okhttp world, where there was just one static instance.
|
.getOrComputeIfAbsent( | ||
"one-off-client-construction", | ||
unused -> { | ||
return constructLiveReloadingClient(config); // we'll re-use this instance every time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a unique CloseableHttpClient instance for each ssl configuration -- each service can have different trust data. A few other prefs from the configuration are applied directly to the CloseableHttpClient so they will require unique instances
* Returns a channel for interacting with the given abstract upstream service, which routes traffic | ||
* appropriately to the various available nodes. Live-reloaded every time the urls change. | ||
*/ | ||
Channel smartChannel(Listenable<DialogueConfig> config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should have some API separation between channel creation and using a channel to create a generated client (which would delegate to the first component)
return factory.construct(smartChannel, runtime); | ||
} | ||
|
||
private static <F> F getOnlyEnumConstant(Class<F> factoryClass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static <F> F getOnlyEnumConstant(Class<F> factoryClass) { | |
private static <F extends Enum<F>> F getOnlyEnumConstant(Class<F> factoryClass) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
Class<? extends HttpChannelFactory> getFactoryClass() { | ||
try { | ||
return (Class<? extends HttpChannelFactory>) Class.forName(className); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need reflection here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e., why not Supplier<Channel>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want people who depend on dialogue-core to transitively get dialogue-apache, dialogue-java, dialogue-okhttp etc.
Might be able to doing this with a compileOnly dependency if you'd prefer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of runtime dependencies like this. might indicate that the decomp is a little off, and maybe also that this should live in the server repo?
Closing out as I'm resuscitating my PR to do this in WC |
Before this PR
After this PR
==COMMIT_MSG==
A new
Dialogue.newClientPool()
method gives people a single clear entrypoint to the library.==COMMIT_MSG==
It people build clients in a declarative way that people are familiar with from feign:
By getting conjure to generate enums, we can actually do this without using any sketchy reflection (no InvocationTargetExceptions anywhere!)
Obviously this doesn't have enough tests to merge yet, just wanted to see what you guys think of the idea before I sink too much time into it!
Possible downsides?