-
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
Add methods to obtain named per-host clients #2192
Conversation
Generate changelog in
|
|
||
import com.palantir.dialogue.core.TargetUri; | ||
|
||
public final class PerHostTarget { |
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.
We had originally discussed designing PerHostTarget
to look like:
class PerHostTarget {
// An opaque hash of the uri and resolvedAddress
String name;
boolean isSelf;
}
The problem with this approach is that the same logical target may produce different names across Dialogue versions. For example, if we add a new field to TargetUri
that gets incorporated into the hash. This is bad for a couple reasons:
- Consumers using different Dialogue version will disagree on the sorted order of these channels.
- Consumers may attempt to persist these names and later compare them against names produced by a different Dialogue version.
The only capabilities we require from PerHostTarget
are that it has a consistent ordering for the same logical target across Dialogue version.
This PR instead accomplishes this by making TargetUri
implement Comparable<TargetUri>
. If we add new fields to TargetUri
, we can implement compareTo
such that the order remains consistent.
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 wanted to avoid leaking uri-info to callers, as dialogue aims to provide an abstraction that makes such info unnecessary. However, I don't think it will be possible to provide enough precision for our users without making this data available in some form. TargetUri
is used to direct requests to individual upstreams (with greater specificity than a URI), so any future additions to abstract away something like nodes behind a reverse proxy (no plans to do this, just speculating about potential future changes) would have to be represented in TargetUri
, which would be similarly helpful here -- so there shouldn't be any need to expose similar fields from a new type here. I think retaining the PerHostTarget
wrapper around TargetUri
buys us some freedom to add new metadata for per-host-channels in the future if needs be later on.
The isSelf
accessor was an attempt to avoid avoid leaking these details, but I think if we do expose the TargetUri
, we needn't expose isSelf
(that sort of check could become library functionality elsewhere, but it isn't required for the dialogue feature imo). The TargetUri will allow consumers to be more accurate in their own isSelf
check, for instance when N services are running on the same host, the caller is likely to know which port it's listening on, in order to provide further differentiation from other services on the same host.
a570412
to
18bd034
Compare
} | ||
return list.build(); | ||
return map.buildKeepingLast(); |
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 want this to fail loudly/catastrophically if something goes wrong
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 safe? There's no guarantee that the TargetUri
values are unique.
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.
Hmm, I suppose an example might look something like this:
uris:
- https://target/api
- https://target/api
In such a case, I think I'd prefer if we pruned out duplicates earlier on, otherwise we risk effectively doubling the concurrency limits on repeated values. I think we can improve things across the board by updating ReloadingClientFactory.getTargetUris
with something like this to retain order, but filter duplicates:
-return ImmutableList.copyOf(targetUris);
+return ImmutableSet.copyOf(targetUris).toList();
@@ -223,12 +227,12 @@ public PerHostClientFactory perHost(String serviceName) { | |||
Preconditions.checkNotNull(serviceName, "serviceName"); | |||
String channelName = ChannelNames.reloading(serviceName, params); | |||
|
|||
Refreshable<List<DialogueChannel>> perHostDialogueChannels = configurationForService(serviceName) | |||
Refreshable<Map<PerHostTarget, Channel>> perHostChannels = configurationForService(serviceName) |
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.
Slight preference to internally represent things like this using the immutables type for clarity
Refreshable<Map<PerHostTarget, Channel>> perHostChannels = configurationForService(serviceName) | |
Refreshable<ImmutableMap<PerHostTarget, Channel>> perHostChannels = configurationForService(serviceName) |
This also has the minor upside of making code like 288 a bit simpler:
-ImmutableList.copyOf(channels.values())
+channels.values().asList()
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 runs into the problem mentioned below in #2192 (comment).
You can't return a Refreshable<ImmutableMap>
from a method that returns Refreshable<Map>
.
return perHostChannels.map(channels -> channels.entrySet().stream() | ||
.collect(ImmutableMap.toImmutableMap( | ||
Map.Entry::getKey, | ||
entry -> Reflection.callStaticFactoryMethod( | ||
clientInterface, entry.getValue(), params.runtime())))); |
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.
slight preference to avoid stream()
where possible
ImmutableMap.copyOf(Maps.transformValues(
channels,
channel -> Reflection.callStaticFactoryMethod(clientInterface, entry.getValue(), params.runtime())));
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 is this code better? We have to allocate some temporary variables in either case.
We're already using streams in a number of places in this class.
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.
Streams are difficult for hotspot to optimize, and I find to-map stream collectors a bit more difficult to read at a glance (this makes it more clear to devs that the keys aren't being modified, and the transformValues call makes it clear to the internals that the resulting ImmutableMap can be pre-sized).
Almost certainly won't make a measurable difference in practice, my goal in the comment is to share how I'm thinking about this, it's entirely up to you whether or not you'd like to change the snippet :-)
return getPerHostChannels().map(channels -> channels.stream() | ||
.map(chan -> Reflection.callStaticFactoryMethod(clientInterface, chan, params.runtime())) | ||
return perHostChannels.map(channels -> channels.values().stream() | ||
.map(channel -> Reflection.callStaticFactoryMethod(clientInterface, channel, params.runtime())) |
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.
Thoughts on delegation here?
return getNamedPerHost(clientInterface).map(result -> result.values().asList());
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.
Should we make the existing methods default methods on PerHostClientFactory
? Or would you prefer to keep the implementation 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.
Delegation would require me to change the return type declared in PerHostClientFactory
to be ImmutableMap
, which probably isn't what we want to do here.
It's also nice to have one less refreshable subscriber 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.
Delegation would require me to change the return type declared in PerHostClientFactory to be ImmutableMap, which probably isn't what we want to do here.
Ah, we didn't handle PECS (Producer Extends Consumer Super) very well on the PerHostClientFactory
existing interface methods -- ideally we may have declared something like Refreshable<? extends List<? extends Channel>> getPerHostChannels();
for greater flexibility in the implementation, however that does force callers who don't use the var
keyword to declare a fair bit more cruft.
Happy to leave this as is
0df1d90
to
5878c99
Compare
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.
lgtm, thanks!
Invalidated by push of 5878c99
Released 3.123.0 |
Before this PR
There are some useful capabilities not currently supported by the per-host channels:
After this PR
The
PerHostClientFactory
now provides two additional methods,getNamedPerHostChannels
andgetNamedPerHost
, that return channels keyed byPerHostTarget
.PerHostTarget
allows callers to introspect the target and determine whether that target refers to themselves.