-
Notifications
You must be signed in to change notification settings - Fork 191
Plugin Support for Java SDK #2761
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
base: master
Are you sure you want to change the base?
Conversation
temporal-sdk/src/main/java/io/temporal/common/plugin/ClientPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/client/ClientPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/client/ClientPlugin.java
Outdated
Show resolved
Hide resolved
temporal-serviceclient/src/main/java/io/temporal/serviceclient/WorkflowServiceStubs.java
Outdated
Show resolved
Hide resolved
temporal-serviceclient/src/main/java/io/temporal/serviceclient/WorkflowServiceStubs.java
Outdated
Show resolved
Hide resolved
temporal-serviceclient/src/main/java/io/temporal/serviceclient/WorkflowServiceStubs.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/SimplePluginBuilder.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/plugin/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/plugin/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/plugin/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/client/ClientPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/plugin/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/common/SimplePlugin.java
Outdated
Show resolved
Hide resolved
7be817f to
19fb453
Compare
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
|
@cretz I think i've addressed everything. Could you do a re-review of the code? |
|
FYI, I also took a first attempt at replayer support. |
…tdownWorkerFactory to SimplePlugin.
…ient to WorkflowClientPlugin.configureWorkflowClient
6ef6ecb to
76d0482
Compare
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/worker/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-testing/src/main/java/io/temporal/testing/WorkflowReplayer.java
Show resolved
Hide resolved
cretz
left a comment
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.
Sorry, a few more things I noticed as I got into the impl (thanks for allowing this back and forth)
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/worker/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/worker/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
temporal-sdk/src/main/java/io/temporal/worker/WorkerPlugin.java
Outdated
Show resolved
Hide resolved
|
@cretz I've addressed all items in this round. |
cretz
left a comment
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.
Looks great, love the reduction in complication (also helps plugin authors when what the plugin does is more obvious). Only one more request on that same aspect, then LGTM.
temporal-sdk/src/main/java/io/temporal/worker/WorkerFactory.java
Outdated
Show resolved
Hide resolved
cretz
left a comment
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.
Looks great, thanks! Approved from my POV, but not marking approved because you'll want @maciejdudko or @tconley1428 approval to merge (owners of this SDK).
temporal-sdk/src/main/java/io/temporal/client/WorkflowClientInternalImpl.java
Outdated
Show resolved
Hide resolved
| @Override | ||
| public void configureWorkflowClient(@Nonnull WorkflowClientOptions.Builder builder) { | ||
| // Apply customizers | ||
| for (Consumer<WorkflowClientOptions.Builder> customizer : clientCustomizers) { |
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 sure I would bother with the customizers versus just letting them subclass simpleplugin if they want to, but I don't object strenuously.
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.
IIUC, in all of our SDKs we decided to have such a concept where they don't have to subclass to provide a lambda to customize existing values. While I disagreed with this (and disagreed with the general aversion to requiring subclassing to do advanced customization), it was overruled.
Granted we could change this to have "customizers" for the specific fields we offer instead of the options as a whole, that'd make sense to me.
| * @see WorkerPlugin | ||
| */ | ||
| @Experimental | ||
| public abstract class SimplePlugin |
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.
The usability of this seems limited compared to at least the other languages I wrote given the lack of ability to see and modify existing configs. For instance, if I want my plugin to add a codec to a dataconverter or change the converter, I can't do that. In other languages I allow you to provide a function as well as a raw value. Not sure how easy that is in Java though.
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 guess some of the builder stuff makes that tricky. DataConverter in particular is common to need to modify, so maybe just something special for that.
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.
For instance, if I want my plugin to add a codec to a dataconverter or change the converter, I can't do that
Why not? You have an options customizer you can do whatever. But if you're saying you want a data converter customizer instead of an options customizer, that can make sense and more aligns with what other SDKs do.
In other languages I allow you to provide a function as well as a raw value. Not sure how easy that is in Java though.
.NET you couldn't either, but we allowed callables as alternatives for fixed values for every specific simple-plugin option we offered, I think we can do the same here and get rid of the options-level customizers.
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.
The current support for customizers (customizeClient) sort of let's you do this, but it's a bit janky:
SimplePlugin.newBuilder("my-plugin")
.customizeClient(builder -> {
DataConverter existing = builder.build().getDataConverter();
builder.setDataConverter(wrapWithCodec(existing, myCodec));
})
.build();Which isn't a whole lot better than subclassing SimplePlugin with this override:
@Override
public void configureWorkflowClient(WorkflowClientOptions.Builder builder) {
// Call super first to apply any base configuration
super.configureWorkflowClient(builder);
// Now see what's configured and wrap it
DataConverter existing = builder.build().getDataConverter();
builder.setDataConverter(
new CodecDataConverter(existing, List.of(myCodec)));
}Here's couple of options:
Option 0: Remove the customizers
Just remove these customizer things like customizeClient. If they want to customize, just have them override configureWorkflowClient. Builder-style supports full replacement only.
Option 1: Add customizeX methods alongside setX methods
// Existing - replaces
public Builder setDataConverter(DataConverter dataConverter)
// New - transforms existing
public Builder customizeDataConverter(UnaryOperator<DataConverter> customizer)
// Usage:
.setDataConverter(myConverter) // Replace
.customizeDataConverter(existing -> wrapWithCodec(existing, myCodec)) // Transform Option 2: Only accept functions
public Builder setDataConverter(UnaryOperator<DataConverter> customizer)
// Usage:
.setDataConverter(existing -> myConverter) // Replace (ignores existing)
.setDataConverter(existing -> wrapWithCodec(existing, myCodec)) // Transform I don't really like this, I think it makes call sites hard/weird to read for full replacements.
Option 3: Custom PluginValue<T> wrapper (closer to Python)
@FunctionalInterface
public interface PluginValue<T> {
T resolve(@Nullable T existing);
static <T> PluginValue<T> set(T value) { return existing -> value; }
static <T> PluginValue<T> customize(UnaryOperator<T> fn) { return fn::apply; }
}
// Usage
.dataConverter(PluginValue.set(myConverter))
.dataConverter(PluginValue.customize(existing -> wrap(existing)))
.dataConverter(existing -> wrap(existing)) // Works since it's a functional interface Seems like a more complicated version of Option 1.
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.
(FYI i just renamed customizeClient -> customizeWorkflowClient, so its consistent withconfigureWorkflowClient).
|
@cretz @tconley1428 In 2f20274 I tried removing the options customizers and adding in a DataConverter customizer. LMK how that looks. |
Plugin System for Java Temporal SDK
What was changed
Added a plugin system for the Java Temporal SDK, modeled after the Python SDK's plugin architecture but adapted to Java idioms.
New Plugin Interfaces
WorkflowServiceStubsPlugin(temporal-serviceclient) - Configuration and connection lifecycle for service stubsWorkflowClientPlugin(temporal-sdk) - Configuration for workflow clientsScheduleClientPlugin(temporal-sdk) - Configuration for schedule clientsWorkerPlugin(temporal-sdk) - Configuration and lifecycle for worker factories and workersWorkerPlugin Lifecycle Methods
WorkerPluginincludes lifecycle methods that use a chain pattern wherenextaccepts the same parameters as the method:startWorkerFactory(factory, next)- Wraps factory startup,nextisConsumer<WorkerFactory>shutdownWorkerFactory(factory, next)- Wraps factory shutdown,nextisConsumer<WorkerFactory>startWorker(taskQueue, worker, next)- Wraps individual worker startup,nextisBiConsumer<String, Worker>shutdownWorker(taskQueue, worker, next)- Wraps individual worker shutdown,nextisBiConsumer<String, Worker>replayWorkflowExecution(worker, history, next)- Wraps workflow replay,nextisReplayCallback(custom interface that throwsException)Configuration methods are called in forward (registration) order. Lifecycle methods are called in reverse order for proper nesting (first plugin wraps all others)
SimplePlugin
SimplePlugin(temporal-sdk) - Abstract convenience class implementing all plugin interfacesSimplePlugin.newBuilder("name").build()Modified Files
WorkflowServiceStubsOptions- AddedpluginsfieldWorkflowServiceStubs- Applies plugin configuration and connection lifecycleWorkflowClientOptions- AddedpluginsfieldWorkflowClientInternalImpl- Applies plugin configuration, propagates from service stubsScheduleClientOptions- AddedpluginsfieldScheduleClientImpl- Applies plugin configuration, propagates from service stubsWorkerFactoryOptions- AddedpluginsfieldWorkerFactory- Full plugin lifecycle (configuration, worker init, start/shutdown)Worker- Added plugins field and replay plugin chain supportTest Files
PluginTest.java- Tests subclassing pattern and lifecycle orderingSimplePluginBuilderTest.java- Tests builder API comprehensivelyPluginPropagationTest.java- Integration tests for plugin propagation chainWorkflowClientOptionsPluginTest.java- Tests options class plugin field handlingWhy?
The plugin system provides a higher-level abstraction over the existing interceptor infrastructure, enabling users to:
Checklist
Closes Plugin support #2626, tracked in Plugins to support controlling multiple configuration points at once features#652
How was this tested:
Any docs updates needed?
Design
Plugin Interfaces
Each level in the creation chain has its own plugin interface:
Plugins set at higher levels propagate down automatically. For example, a plugin set on
WorkflowServiceStubsOptionsthat also implementsWorkflowClientPluginwill have itsconfigureWorkflowClient()method called when creating aWorkflowClient.SimplePlugin
SimplePluginis an abstract class that implements all plugin interfaces with sensible defaults. It can be used two ways:Builder pattern for declarative configuration:
Subclassing for custom behavior:
Usage Example