The CallbackBuilder is used to create callbacks. Callbacks have error handlers, timeout handlers and return handlers.
Setting up error handlers, timeout handlers and callback handlers with a callback builder.
callbackBuilder
.setCallback(ResultSet.class, resultSet ->
statusCallback.accept(resultSet!=null))
.setOnTimeout(() -> statusCallback.accept(false))
.setOnError(error -> statusCallback.onError(error))
.build(ResultSet.class);
this.addEventStorageRecordAsync(callbackBuilder.build(), storageRec);
The
CallbackBuilder
has many helper methods to help you with dealing with common Java types like Optional
, Map
, List
, Collection
, Set
, String
, primitive types and wrappers.This allows you to quickly build callbacks without navigating the complexity of Generics. Let's cover a small example.
First let's define a basic service that uses lists, maps and optional.
Basic service to drive the example
packageio.advantageous.qbit.example.callback;
importio.advantageous.boon.core.Lists;
importio.advantageous.boon.core.Maps;
importio.advantageous.qbit.reactive.Callback;
importjava.util.List;
importjava.util.Map;
importjava.util.Optional;
publicclassEmployeeServiceImplimplementsEmployeeService {
@Override
publicvoidgetEmployeesAsMap(finalCallback<Map<String, Employee>>empMapCallback) {
empMapCallback.returnThis(Maps.map("rick", newEmployee("Rick")));
}
@Override
publicvoidgetEmployeesAsList(finalCallback<List<Employee>>empListCallback) {
empListCallback.returnThis(Lists.list(newEmployee("Rick")));
}
@Override
publicvoidfindEmployeeByName(finalCallback<Optional<Employee>>employeeCallback,
finalStringname) {
if (name.equals("Rick")) {
employeeCallback.returnThis(Optional.of(newEmployee("Rick")));
} else {
employeeCallback.returnThis(Optional.empty());
}
}
}
The interface for the above looks like this:
Basic interface to drive the example
packageio.advantageous.qbit.example.callback;
importio.advantageous.qbit.reactive.Callback;
importjava.util.List;
importjava.util.Map;
importjava.util.Optional;
publicinterfaceEmployeeService {
voidgetEmployeesAsMap(Callback<Map<String, Employee>>empMapCallback);
voidgetEmployeesAsList(Callback<List<Employee>>empListCallback);
voidfindEmployeeByName(Callback<Optional<Employee>>employeeCallback,
Stringname);
}
If you are familiar with QBit, all of the above should already make sense. If not, I suggest going through the home page of the WIKI and coming back here after you skim it.
To show how to use the CallbackBuilder we will define a basic Rest service called
CompanyRestService
.CompanyRestService to demonstrate CallbackBuilder
/**
* To access this service
* curl http://localhost:8080/emap
{"rick":{"name":"Rick"}}
*/
@RequestMapping("/")
publicclassCompanyRestService {
privatefinalLogger logger =LoggerFactory.getLogger(CompanyRestService.class);
privatefinalEmployeeService employeeService;
publicCompanyRestService(EmployeeServiceemployeeService) {
this.employeeService = employeeService;
}
...
@QueueCallback({QueueCallbackType.EMPTY, QueueCallbackType.LIMIT})
publicvoidprocess(){
ServiceProxyUtils.flushServiceProxy(employeeService);
}
QBit uses micro-batching which helps optimize message passing between service queues (service actors). The
QueueCallback
annotation allows us to capture when our request queue is empty or when it has hit its limit. The limit is usually the batch size but could be other things like hitting an important message. Whenever we hit our limit or when are request queue is empty, we go ahead and flush things to the downstream service by callingServiceProxyUtils.flushServiceProxy
. This should be mostly review.As you can see, the
EmployeeService
has a lot of methods that take Generic types likeOptional
, List
and Map
. When we want to call a downstream service that is going to return a map, list or optional, we have helper methods to make the construction of the callback easier.CompanyRestService Calling getEmployeesAsMap using CallbackBuilder.wrap
@RequestMapping("/")
publicclassCompanyRestService {
...
@RequestMapping("/emap")
publicvoidemployeeMap(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =CallbackBuilder.newCallbackBuilder();
callbackBuilder.wrap(empMapCallback); //Forward to error handling, timeout, and callback defined in empMapCallback
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
In this case, we use the wrap method. This will forward errors, timeouts and the callback return to the
empMapCallback
.To run this, we need to start up the application.
Starting up the REST application.
publicstaticvoid main(finalString... args) throws Exception {
/** Create a ManagedServiceBuilder which simplifies QBit wiring. */
finalManagedServiceBuilder managedServiceBuilder =ManagedServiceBuilder.managedServiceBuilder().setRootURI("/");
managedServiceBuilder.enableLoggingMappedDiagnosticContext();
/** Create a service queue for the employee service. */
finalServiceQueue employeeServiceQueue = managedServiceBuilder.createServiceBuilderForServiceObject(
newEmployeeServiceImpl()).buildAndStartAll();
/** Add a CompanyRestService passing it a client proxy to the employee service. */
managedServiceBuilder.addEndpointService(
newCompanyRestService(employeeServiceQueue.createProxy(EmployeeService.class)));
/** Start the server. */
managedServiceBuilder.startApplication();
}
If we wanted to copy and mutate the map before we serialized it, we could use the withMapCallback to capture the async return, i.e.,
Callback
from the employeeService
.CompanyRestService using withMapCallback and delegate
@RequestMapping("/")
publicclassCompanyRestService {
...
@RequestMapping("/emap2")
publicvoidemployeeMap2(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =CallbackBuilder.newCallbackBuilder();
callbackBuilder.delegate(empMapCallback); //Forward to error handling and timeout defined in empMapCallback
callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
logger.info("GET MAP {}", employeeMap);
empMapCallback.returnThis(employeeMap);
});
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
In this case we forward just the error handling and timeout handling to the callback that we are creating, and then we create a custom return handler using
withMapCallback
.CompanyRestService using withMapCallback and delegateWithLogging
@RequestMapping("/")
publicclassCompanyRestService {
...
@RequestMapping("/emap3")
publicvoidemployeeMap3(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =CallbackBuilder.newCallbackBuilder();
// Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(empMapCallback, logger, "employeeMap3");
callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
logger.info("GET MAP {}", employeeMap);
empMapCallback.returnThis(employeeMap);
});
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
If you want to handle error logging and timeout logging in the context of this services log, you can simply use the
delegateWithLogging
method. This will setup some basic logging for error handing and timeouts.We of course also have methods that work with List can Collections and Sets and....
Working with list by using withListCallback
@RequestMapping("/")
publicclassCompanyRestService {
...
@RequestMapping("/elist")
publicvoidemployeeList(finalCallback<List<Employee>>empListCallback) {
finalCallbackBuilder callbackBuilder =CallbackBuilder.newCallbackBuilder();
// Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(empListCallback, logger, "employeeList");
callbackBuilder.withListCallback(Employee.class, employeeList -> {
logger.info("GET List {}", employeeList);
empListCallback.returnThis(employeeList);
});
employeeService.getEmployeesAsList(callbackBuilder.build());
}
The above works as you would expect. Let's mix things up a bit. We will call
findEmployeeByName
which may or many not return an employee.Working with optional by using withOptionalCallback
@RequestMapping("/")
publicclassCompanyRestService {
...
@RequestMapping("/find")
publicvoidfindEmployee(finalCallback<Employee>employeeCallback,
@RequestParam("name") finalStringname) {
finalCallbackBuilder callbackBuilder =CallbackBuilder.newCallbackBuilder();
// Forward to error handling and timeout defined in empMapCallback,
// but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {
if (employeeOptional.isPresent()) {
employeeCallback.returnThis(employeeOptional.get());
} else {
employeeCallback.onError(newException("Employee not found"));
}
});
employeeService.findEmployeeByName(callbackBuilder.build(), name);
}
To work with Optional's we use
withOptionalCallback
. Here we return an error if the employee is not found and we return the Employee
object if he is found./**
* You need this is you want to do error handling (Exception) from a callback.
* Callback Builder
* created by rhightower on 3/23/15.
*/
@SuppressWarnings("UnusedReturnValue")
publicclassCallbackBuilder {
/**
* Builder method to set callback handler that takes a list
* @param componentClass componentClass
* @param callback callback
* @param <T> T
* @return this
*/
public<T>CallbackBuilderwithListCallback(finalClass<T>componentClass,
finalCallback<List<T>>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a set
* @param componentClass componentClass
* @param callback callback
* @param <T> T
* @return this
*/
public<T>CallbackBuilderwithSetCallback(finalClass<T>componentClass,
finalCallback<Set<T>>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a collection
* @param componentClass componentClass
* @param callback callback
* @param <T> T
* @return this
*/
public<T>CallbackBuilderwithCollectionCallback(finalClass<T>componentClass,
finalCallback<Collection<T>>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a map
* @param keyClass keyClass
* @param valueClass valueClass
* @param callback callback
* @param <K> key type
* @param <V> value type
* @return this
*/
public<K, V>CallbackBuilderwithMapCallback(finalClass<K>keyClass,
finalClass<V>valueClass,
finalCallback<Map<K, V>>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a boolean
* @param callback callback
* @return this
*/
publicCallbackBuilderwithBooleanCallback(finalCallback<Boolean>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a integer
* @param callback callback
* @return this
*/
publicCallbackBuilderwithIntCallback(finalCallback<Integer>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a long
* @param callback callback
* @return this
*/
publicCallbackBuilderwithLongCallback(finalCallback<Long>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes a string
* @param callback callback
* @return this
*/
publicCallbackBuilderwithStringCallback(finalCallback<String>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes an optional string
* @param callback callback
* @return this
*/
publicCallbackBuilderwithOptionalStringCallback(finalCallback<Optional<String>>callback) {
this.callback = callback;
returnthis;
}
/**
* Builder method to set callback handler that takes an optional string
* @param callback callback
* @return this
*/
public<T>CallbackBuilderwithOptionalCallback(finalClass<T>cls, finalCallback<Optional<T>>callback) {
this.callback = callback;
returnthis;
}
Read more about callback builders and how to handle errors, timeouts and downstream calls.
Reactor
Let's say that EmployeeService was really talking to some downstream remote services or perhaps to Cassandra and/or Redis. Let's also say that you want to add some timeout for this downstream system. Let's say 10 seconds.
Then our example will use the QBit
Reactor
and the easiest way to do that would be to subclass the BaseService
.Using QBit Reactor from the BaseService
packageio.advantageous.qbit.example.callback;
importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.annotation.RequestParam;
importio.advantageous.qbit.reactive.Callback;
importio.advantageous.qbit.reactive.CallbackBuilder;
importio.advantageous.qbit.reactive.Reactor;
importio.advantageous.qbit.reactive.ReactorBuilder;
importio.advantageous.qbit.service.BaseService;
importio.advantageous.qbit.service.ServiceQueue;
importio.advantageous.qbit.service.stats.StatsCollector;
importio.advantageous.qbit.util.Timer;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importjava.util.List;
importjava.util.Map;
importjava.util.concurrent.TimeUnit;
@RequestMapping("/")
publicclassCompanyRestServiceUsingReactorextendsBaseService {
privatefinalLogger logger =LoggerFactory.getLogger(CompanyRestService.class);
privatefinalEmployeeService employeeService;
publicCompanyRestServiceUsingReactor(Reactorreactor,
Timertimer,
StatsCollectorstatsCollector,
EmployeeServiceemployeeService) {
super(reactor, timer, statsCollector);
this.employeeService = employeeService;
reactor.addServiceToFlush(employeeService);
}
@RequestMapping("/emap")
publicvoidemployeeMap(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
callbackBuilder.wrap(empMapCallback); //Forward to error handling, timeout, and callback defined in empMapCallback
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
@RequestMapping("/emap2")
publicvoidemployeeMap2(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
callbackBuilder.delegate(empMapCallback); //Forward to error handling and timeout defined in empMapCallback
callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
logger.info("GET MAP {}", employeeMap);
empMapCallback.returnThis(employeeMap);
});
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
@RequestMapping("/emap3")
publicvoidemployeeMap3(finalCallback<Map<String, Employee>>empMapCallback) {
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
// Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(empMapCallback, logger, "employeeMap3");
callbackBuilder.withMapCallback(String.class, Employee.class, employeeMap -> {
logger.info("GET MAP {}", employeeMap);
empMapCallback.returnThis(employeeMap);
});
employeeService.getEmployeesAsMap(callbackBuilder.build());
}
@RequestMapping("/elist")
publicvoidemployeeList(finalCallback<List<Employee>>empListCallback) {
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
// Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(empListCallback, logger, "employeeList");
callbackBuilder.withListCallback(Employee.class, employeeList -> {
logger.info("GET List {}", employeeList);
empListCallback.returnThis(employeeList);
});
employeeService.getEmployeesAsList(callbackBuilder.build());
}
@RequestMapping("/find")
publicvoidfindEmployee(finalCallback<Employee>employeeCallback,
@RequestParam("name") finalStringname) {
finallong startTime =super.time;
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
// Forward to error handling and timeout defined in empMapCallback, but install some additional logging for
// timeout and error handling that associates the error and timeout handling with this call.
callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {
super.recordTiming("findEmployee", time - startTime);
if (employeeOptional.isPresent()) {
employeeCallback.returnThis(employeeOptional.get());
} else {
employeeCallback.onError(newException("Employee not found"));
}
});
employeeService.findEmployeeByName(callbackBuilder.build(), name);
}
publicstaticvoidmain(finalString... args) throwsException {
/** Create a ManagedServiceBuilder which simplifies QBit wiring. */
finalManagedServiceBuilder managedServiceBuilder =ManagedServiceBuilder.managedServiceBuilder().setRootURI("/");
managedServiceBuilder.enableLoggingMappedDiagnosticContext();
/** Create a service queue for the employee service. */
finalServiceQueue employeeServiceQueue = managedServiceBuilder.createServiceBuilderForServiceObject(
newEmployeeServiceImpl()).buildAndStartAll();
/** Add a CompanyRestService passing it a client proxy to the employee service. */
managedServiceBuilder.addEndpointService(
newCompanyRestServiceUsingReactor(
ReactorBuilder.reactorBuilder().setDefaultTimeOut(10).setTimeUnit(TimeUnit.SECONDS).build(),
Timer.timer(),
managedServiceBuilder.getStatServiceBuilder().buildStatsCollector(),
employeeServiceQueue.createProxy(EmployeeService.class)));
/** Start the server. */
managedServiceBuilder.startApplication();
}
}
Notice that the
callbackBuilder
is now constructed from the reactor (final CallbackBuilder callbackBuilder = super.reactor.callbackBuilder();
).To learn more about the Reactor, please read Reactively handling async calls with QBit Reactive Microservices.
Stats
When you use the
BaseService
, you also have access to the stats system.Stats from BaseService
@RequestMapping("/find")
publicvoid findEmployee(finalCallback<Employee> employeeCallback,
@RequestParam("name") finalString name) {
finallong startTime =super.time;
finalCallbackBuilder callbackBuilder =super.reactor.callbackBuilder();
callbackBuilder.delegateWithLogging(employeeCallback, logger, "employeeMap3");
callbackBuilder.withOptionalCallback(Employee.class, employeeOptional -> {
/** Record timing. */
super.recordTiming("findEmployee", time - startTime);
if (employeeOptional.isPresent()) {
/* Increment count of employees found. */
super.incrementCount("employeeFound");
employeeCallback.returnThis(employeeOptional.get());
} else {
/* Increment count of employees not found. */
super.incrementCount("employeeNotFound");
employeeCallback.onError(newException("Employee not found"));
}
});
employeeService.findEmployeeByName(callbackBuilder.build(), name);
}
To learn more about stats and the ManagedServiceBuilder read this.