Classes and Interfaces

Overview

Key classes:

io.baratine.core

CancelHandle

CancelHandle is returned as a result handle for subscriptions, streams, and services like the timer service to unregister or cancel the service.

public interface CancelHandle
{
  void cancel();
}

MethodRef

Used for programmatic calls to a service.

Result

Use Result for asynchronous results. It is the core asynchronous result callback for method calls. Result is a JDK8 functional interface. It supports exceptions, chaining, and lambda expressions.

@FunctionalInterface
public interface Result<V>
{
  void complete(V value);

  default void fail(Throwable exn) {}

  <W> static Result<W> ignore();
  <W> static Result<W> from(Consumer<W> complete,
                            Consumer<Throwable> fail);
  <W> Result<W> from(Function<W,V> fun);
  <W> Result<W> from(BiConsumer<W,Result<V>> consumer);
}

Typical declaration in an API:

public interface Hello
{
  void hello(Result<String> result);
}

Result thread/service context

The Result will be called in the caller’s service context. If the caller is a service, the Result is queued in its service inbox.

If service “Hello” calls a “Database” load, its Result will be queued to the “Hello” service inbox.

If Result is used outside of a service, its Result will be in a spawned system context thread. The system context can be configured using ServiceManager setSystemExecutorFactory.

Result.ignore()

Use Result.ignore() when a service method with a Result argument does not care about its return value.

hello.hello(Result.ignore());

Result.from() - chaining

Use Result.from when a service calls another service. For example, an auction service might call a database to load its data. Result.from is required to properly propagate exceptions.

In the following example, the onLoad method calls a database to load the data asynchronously. The completed load will call afterLoad with the intermediate result. afterLoad will return the final result.

@OnLoad
public void onLoad(Result<Void> result)
{
  _store.load(_id, result.from(v->afterLoad(v)));
}

private Void afterLoad(AuctionData data)
{
  _data = data;

  return null;
}

Result.newFork() - fork/join

Use Result.newFork() when a service needs to call multiple services in parallel. When all the branches are complete, fork will call a join function to calculate the final result.

public void myMethod(Result<String> result)
{
  Result.Fork<String,String> fork = result.newFork();

  _serviceA.doStuffA("argA", fork.fork());
  _serviceB.doStuffB("argB", fork.fork());

  fork.join(values->processValues(values));
}

private String processValues(List<String> values)
{
  return "Join" + values;
}

ResultFuture

ResultFuture is a future specific to Baratine’s Result.

In general, it’s preferred to use synchronous methods that return Java values instead of using the future class directly.

public class ResultFuture<V> implements Result<V>
{
  V get();
  V get(long timeout, TimeUnit unit);
}

ResultStream

ResultStream is a streaming result. It is used for publish/subscribe patterns as well as map/reduce queries.

public class ResultStream<V>
{
  void accept(V value);

  default void complete() {}
  default void fail(Throwable exn) {}
}

ServiceClient

ServiceClient is a remote client for standalone Java applications.

public interface ServiceClient
  extends ServiceManager, AutoCloseable
{
  static Builder newClient(String url);

  void close();
}

A simple client call might look like:

try (ServiceClient client = ServiceClient.newClient(url).build()) {
  Hello hello = client.lookup("remote:///hello").as(Hello.class);

  hello.hello();
}

The proxy in the above example is thread-safe. As long as the ServiceClient remains open, the multiple threads can call the proxy and the proxy can be used across the application.

Clients should be reused for multiple requests and used for multiple threads because the client is thread-safe, reconnects automatically, and batches calls. ServiceClient is more efficient when used as a multi-threaded connection.

Servlet Web-App Clients

Java clients inside a servlet web-app should use a “pod://” address with a WEB-INF/baratine.cf instead of using ServiceClient.

ServiceException

ServiceException is the base RuntimeException for service exceptions.

public class ServiceException extends RuntimeException {
  ...
}

ServiceManager

ServiceManager is used to

  • create services programmatically
  • lookup bound services by their URL address
public interface ServiceManager {
  static ServiceManager current();

  static Builder newManager();

  ServiceRef lookup(String address);

  ServiceRef.Builder newService();

  ServiceNode getNode();

  void setSystemExecutorFactory(Supplier<Executor> factory);
}

ServiceManager.lookup

A typical programmatic lookup inside a Baratine service:

ServiceManager manager = ServiceManager.current();

Hello hello = manager.lookup("pod://hello/hello")
                     .as(Hello.class);

ServiceManager.newService

An embedded service creation:

ServiceManager manager = ServiceManager.current();

Hello hello = manager.newService()
                     .address("/hello")
                     .service(new HelloImpl())
                     .build()
                     .as(Hello.class);

ServiceManager.newManager

An embedded manager creation:

ServiceManager manager = ServiceManager.newManager().build();

ServiceManager.setSystemExecutorFactory

setSystemExecutorFactory is for integration with external frameworks and for calls outside of Baratine, because Result callbacks need a thread and a context. When an external framework has its own thread and context management and the Result is executed in that framework context, the setSystemExecutorFactory can launch the Result in the proper context.

ServiceManager.getNode

ServiceNode and getNode is useful in multi-pod environments when a service needs to know which node it’s running in.

ServiceNode

public interface ServiceNode {
  String getPodName();
  int getNodeCount();
  int getNodeIndex();
}

ServiceInitializer

Loaded from META-INF/services/io.baratine.core.ServiceInitializer

public interface ServiceInitializer
{
  void init(ServiceManager manager);
}

ServiceRef

ServiceRef is a reference to a service. It is used to

  • Create a service proxy
  • Convert a proxy to a ServiceRef
  • Pin a callback or listener to a service
  • Subscribe to a publishing service
public interface ServiceRef {
  static ServiceRef current();

  <T> T as(Class<T> api);
  static ServiceRef toServiceRef(Object proxy);

  ServiceRef pin(Object callback);

  CancelHandle subscribe(Object subscriber);
  CancelHandle consume(Object consumer);

  ServiceRef node(int hash);
  int getNodeCount();

  ServiceRef save(Result<Void> result);
}

as()

as() returns a proxy for a ServiceRef. toServiceRef returns the ServiceRef for a proxy.

ServiceManager manager = ServiceManager.current();

ServiceRef helloRef = manager.lookup("/hello");
Hello hello = helloRef.as(Hello.class);
ServiceRef helloRef2 = ServiceRef.toServiceRef(hello);

The proxy class is typically an interface where return values are represented as a Result arguments. This convention encourages non-blocking code. For integration with blocking clients, query methods can also return values, though it will block the method call until it returns.

subscribe()

subscribe() registers a subscription callback with an event service. The event service will implement @OnSubscribe, which will be called for each subscribe.

To subscribe to a service:
  • create an @OnInit method in the subscribing service.
  • obtain the ServiceRef of the broker with ServiceManager.lookup
  • call subscribe() with the callback, or a lambda if the event is a functional interface.
  • save the returned CancelHandle if an unsubscribe is necessary

Subscribing to a target service will call its @OnSubscribe method with a service proxy to the callback, not the callback itself. The proxy ensures the subscriber’s service context is properly enforced.

subscribe() differs from consume() in that multiple subscribers all receive all events, while only one consumer will receive an event.

SessionContext

SessionContext is available for “session:” services. Each instance can inject a copy of the HTTP calling context.

public interface SessionContext
{
  Map<String,List<String>> getHeaders();
  boolean isSecure();
}

io.baratine.stream

ResultStreamBuilder

ResultStreamBuilder is used by clients to construct stream queries. The constructed stream will call the service’s method with a ResultStream argument.

Terminal methods

Terminal methods start the ResultStream and send the request to the target service.

public interface ResultStreamBuilder<T>
{
  CancelHandle exec();

  void result(Result<? super T> result);

  void to(ResultStream<? super T> nextStream);

  ...
}
public interface ResultStreamBuilderSync<T>
  extends ResultStreamBuilder<T>
{
  T result();
}
exec()

ResultStreamBuilder.exec() sends the stream request to the service, returning a CancelHandle which can cancel the stream. The exec() call is asynchronous; it returns immediately. Pub/Sub patterns will often use the exec().

result()

ResultStreamBuilder.result() starts the stream request by calling the service. The Result argument captures the result of the stream. result() is typically used by queries. It can also be used for pub/sub to indicate the end of the stream.

A synchronous result() is available in ResultStreamBuilderSync. That result call will block until the query completes.

to()

ResultStreamBuilder.to() is used to chain streams. Typically used by web-tier facades that receive requests from remote clients. The facade will chain the stream to the proper internal stream.

For example, a chat application will have a Session service to manage the client. If that session uses a stream to subscribe to a chat room, the session service will use to() to chain the client’s stream to the chat room’s stream.