Enhanced CDI Contexts and Bulkheads With MicroProfile Context Propagation
Learn more about how MicroProfile Context Propagation can provide enhanced CDI contexts and bulkheads.
Join the DZone community and get the full member experience.
Join For FreeWhen using CDI with asynchronous execution methods, such as a ManagedExecutorService
, it's traditionally not possible to access all of CDI's scopes that were active in the originating thread. MicroProfile Context Propagation enables us to define and pass thread execution contexts to completion stages where our code can access various CDI contexts despite being executed asynchronously. Additionally, Context Propagation allows us to create managed executor services that can be injected and used inside our beans, for example, to realize bulkheads.
Let's create and use a request-scoped bean that is being used during the handling of a request. With plain CDI, we would not be able to access and lookup the bean within an asynchronous execution.
Have a look at the following code:
@ApplicationScoped
@Path("contexts/example")
public class ThreadContextExampleResource {
@Inject
ExampleStore exampleStore;
@Inject
ThreadContext threadContext;
@Resource
ManagedExecutorService mes;
@Inject
Notifier notifier;
@PUT
public void setExample(String example) {
exampleStore.setExample(example);
mes.execute(threadContext.contextualRunnable(notifier::notifyAbout));
}
}
@RequestScoped
public class ExampleStore {
private String example;
public String getExample() {
return example;
}
public void setExample(String example) {
this.example = example;
}
}
public class Notifier {
@Inject
ExampleStore exampleStore;
public void notifyAbout() {
System.out.println("New example: " + exampleStore.getExample());
}
}
If a client PUT
s some content to the contexts/example
resource, the method will update the request-scoped ExampleStore
bean and execute the notification asynchronously, using the ManagedExecutorService
. In order to enable the asynchronous execution to lookup the request-scoped store, we are using the ThreadContext
to wrap the runnable with a context captured from the originating thread. This makes sure that the executed runnable can use the corresponding context.
We have to configure and produce a ThreadContext
depending on which type of contexts (e.g. CDI, transaction, security) we want to propagate:
public class ThreadContextProducer {
@Produces
ThreadContext threadContext() {
return ThreadContext.builder()
.propagated(ThreadContext.ALL_REMAINING)
.build();
}
}
This example will propagate all context types to the wrapped execution. Our bean then injects and uses the produced ThreadContext
.
Defining Bulkheads Using Executors
MicroProfile Context Propagation allows us to create and configure ManagedExecutor
s, a container-managed executor service similar to ManagedExecutorService
. We can create a ManagedExecutor
programmatically, set constraints on the allowed concurrency, and define a context propagation, as well.
Let's define the following asynchronous JAX-RS resources:
@ApplicationScoped
@Path("bulkheads")
public class BulkheadExampleResource {
@Inject
ExampleStore exampleStore;
@Inject
Notifier notifier;
@Inject
ManagedExecutor writeExecutor;
@Inject
ManagedExecutor readExecutor;
@GET
public CompletionStage<String> example() {
return readExecutor.supplyAsync(exampleStore::getExample);
}
@PUT
public CompletionStage<Void> setExample(String example) {
return writeExecutor.runAsync(() -> {
exampleStore.setExample(example);
writeExecutor.execute(notifier::notifyAbout);
});
}
}
We're injecting two dedicated executors that are used to run the corresponding functionalities. The executors are created using a producer:
public class ManagedExecutorProducer {
@Produces
ManagedExecutor managedExecutor() {
return ManagedExecutor.builder()
.propagated(ThreadContext.CDI, ThreadContext.APPLICATION)
.maxAsync(4)
.maxQueued(4)
.build();
}
public void disposeManagedExecutor(@Disposes ManagedExecutor managedExecutor) {
managedExecutor.shutdownNow();
}
}
Our executors will have upper bounds of four concurrently executed completion stages, and four tasks in the queue. The contexts of the CDI and application context types will be propagated to the executing threads.
When injecting the executors, be aware of the scope of the injection point; here, we're using an application-scoped resource. Otherwise, we might create more than two executors, which would defeat the purpose of the bulkhead pattern. Since we're using CDI, it's, of course, possible to define additional qualifies if the created executors should be configured differently.
You can try out MicroProfile Context Propagation, for example, using the latest builds of Open Liberty. I've published an example repository on GitHub.
When we're running our applications on Open Liberty, MicroProfile Context Propagation executors are backed by the automatically-tuned global thread pool. You can have a look at the default thread pool metrics provided by Liberty, as shown here.
Published at DZone with permission of Sebastian Daschner. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments