Hello World Tour

../../_images/example-hello.png

Basic Service

This Baratine service is cornerstone of a network of loosely-coupled POJO microservices. This hello, world example works through the fundamental service pattern, shows how to call it from remote clients, and shows how it fits into a network of Baratine services.

Introduction

Baratine service pattern examples:

  • Singleton services for resources and queuing
  • Actives queues with Java APIs
  • Existing modules as services for isolation
  • HTTP/WebSocket protocols for existing applications
  • Microservices
  • In-memory services with failover and sharding

Baratine is a flexible service framework that can be used for many patterns. Many of the patterns work in existing applications, such as servlet web-apps or independent JVM applications. So an application can take advantage of Baratine’s benefits without changing their deployment.

This hello tutorial shows quick examples for common service patterns. Patterns for existing implementations, API and deployments are first. Patterns that take advantage of Baratine’s asynchronous APIs follow. And Baratine’s in-memory services and databases are last.

Applications can use services for some quick benefits, because Baratine is based on flexible patterns. If you need to solve a synchronization contention around a resource, the singleton resource pattern can help. If you need an active queue to decouple producers from a consumer, use the queue pattern. An existing module that needs more isolation to improve team boundaries can use the module pattern. And if you need a simple HTTP and WebSocket web API for interactive clients, the protocol pattern is available.

Local Singleton Service for Existing Application

../../_images/example-hello.png

Benefits:

  • context boundary
  • synchronization
  • cpu affinity
  • existing APIs
  • existing deployment (web-app or embedded in JVM)

See Singleton Resource for the pattern.

Servlet Web-App with Programmatic Deployment

Hello.java

package tutorial.hello;

public interface Hello
{
  String hello();
}

HelloImpl.java

package tutorial.hello;

public class HelloImpl implements Hello
{
  public String hello()
  {
    return "hello";
  }
}

MyServlet.java

package tutorial.hello;

import javax.servlet.*;
import javax.servlet.http.*;
import io.baratine.core.*;

public class MyServlet extends HttpServlet
{
  private Hello _hello;

  @Override
  public void init()
  {
    super.init();

    ServiceManager manager = ServiceManager.current();

    _hello = manager.newService()
                    .service(new HelloImpl())
                    .build()
                    .as(Hello.class);
  }

  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
    throws IOException
  {
    PrintWriter out = response.getWriter();

    out.println("Hello: " + _hello.hello());
  }
}

Servlet .war deployment

To deploy in a .war, add baratine.jar to the WEB-INF/lib and build the servlet as usual. When the web-app starts, Baratine will initialize its ServiceManager and make it available with the current() method.

  • WEB-INF/lib/baratine.jar
  • WEB-INF/lib/hello.jar
  • WEB-INF/lib/baratine.cf (optional, used for remoting)

@Service Address: Looking up by URL

A non-trivial application will have services bound to addresses using URL syntax. A client can look up a service by address without needing to know how it’s created. During testing, the address can use a mock service without changing the client.

Addresses use URL syntax with a default scheme of “local:”, meaning the service is to be used in its JVM. Some other schemes:

  • “public:” makes the service available to clients
  • “session:” creates a session-based stateful instance
  • “local:” restricts to local access
  • “event:” is for API-based publish-subscribe events
  • “pod:” is for remote service calls
  • “bfs:” is a distributed filesystem
  • “store:” is a simple key-value store
  • “db:” is an embedded asynchronous database

Services can be registered by address either programmatically or by class scanning for the @Service annotation. We’ll show programmatic registration first.

MyServlet.java (Programmatic Deploy and Lookup)

When a service is created, it is bound to an address when the optional address(addr) method is called during the newService() builder in ServiceManager.

Use lookup(addr) in ServiceManager to look up a service.

package tutorial.hello;

import javax.servlet.*;
import javax.servlet.http.*;
import io.baratine.core.ServiceManager;

public MyServlet extends HttpServlet
{
  private Hello _hello;

  public void init()
  {
    super.init();

    ServiceManager manager = ServiceManager.current();

    manager.newService()
           .address("/hello")
           .service(new HelloImpl())
           .build();

    _hello = manager.lookup("/hello")
                    .as(Hello.class);
  }
  ...
}

A full application would build its services in a different class than the client of the service.

While programmatic registration is useful, Baratine can discover services on startup by scanning the classpath for @Service annotations. When Baratine discovers a @Service class, it will build a new service with its address.

HelloImpl.java (@Service scanning)

package tutorial.hello;

import io.baratine.core.Result;
import io.baratine.core.Service;

@Service("/hello")
public class HelloImpl implements HelloAsync
{
  public void hello(Result<String> result)
  {
    result.complete("hello");
  }
}

Service Explanation

../../_images/service-inbox-outbox.png

Because Baratine’s service mechanism is intended to be invisible to existing applications, the hello example might seem simple. In actuality, that application API is hiding significant capabilities.

A Baratine service is a queue (inbox) with a service thread that processes method calls from the inbox. The internal mechanism is generally invisible because services use application APIs. By design, it’s possible for a service implementation and its clients to ignore that they’re using Baratine, because Baratine is designed to work with existing applications.

While the application code can ignore that it’s using Baratine, developers should know a bit more. The important things to know are:

  • Client proxies are thread-safe, intended to be used by multiple threads.
  • Services are single-threaded (see @Workers for a multi-threaded model)
  • The client thread is isolated from the service thread
  • An inbox queue is the main thread-decoupling mechanism
  • Methods are processed in order
  • Calls and results are asynchronous
  • Blocking calls internally use futures to wait for the result.

The order of a method call looks like the following:

  1. Client calls Hello API proxy with hello()
  2. Proxy creates query message and adds to the service inbox (queue)
  3. If service is idle, wake a thread for the service.
  4. — an async client can move on at this point
  5. Service thread reads request from inbox and executes the method.
  6. On return, the service drops the reply into the outbox
  • If the client is a service, put the reply into the client’s inbox.
  • If the client is blocking, wake the caller.
  • If the client is a non-service async, spawn a thread for the callback.

See @Service Guide for more details.

Installing Baratine

Download Baratine from http://baratine.io/download and extract it to any directory.

Maven repository

Baratine is also available via Maven for developing Baratine services and embedding:

<dependencies>
  <dependency>
    <groupId>io.baratine</groupId>
    <artifactId>baratine</artifactId>
    <version>0.10.2</version>
  </dependency>

  <dependency>
    <groupId>io.baratine</groupId>
    <artifactId>baratine-api</artifactId>
    <version>0.10.2</version>
  </dependency>
</dependencies>

git source

Baratine’s source is available in git as:

$ git clone git://git.caucho.com/baratine.git
$ ant -f baratine/build.xml
$ baratine/bin/baratine start

Queue Service with Application API

../../_images/example-queue.png

See Queue with Application API for the full pattern.

A user of a queue service sends a request and continues processing without waiting for a result. Because Baratine’s services are built around an inbox queue, this fire-and-forget pattern fits naturally.

Benefits:

  • frees calling thread
  • cpu affinity
  • synchronization
  • can improve performance under load
  • can batch under load
  • can have an asynchronous Result

The queue pattern can be used for small, dynamic services like a TCP writer or a file log, or for larger services like PDF generation, or an email queue. The dynamic services are created programmatically. A WebSocket chat implementation might create a Baratine queue for each new connection.

  • TCP protocol writer
  • WebSocket
  • Printer
  • Database batching writer
  • Email service
  • Report generation

If a method has no return value, Baratine treats it as a queue method and lets the calling thread continue after adding the request to the inbox queue. The hello interface is therefore exactly like the original method but without a result.

HelloQueue.java:

public interface HelloQueue
{
  void hello(String arg);
}

The implementation is similar to the plain hello but does not return a result. Queue services might use blocking resources. Ahough Baratine is designed to support async development, it’s designed to support blocking services. For example, a WebSocket writer might block if the TCP is overloaded. A report writer might use blocking database calls.

HelloQueueImpl.java:

@Service("/hello-queue")
public class HelloQueueImpl implements HelloQueue
{
  public void hello(String arg)
  {
    processRequest(arg);
  }
}

A dynamic queue is created for each new resource, such as WebSocket connections, database connections, logging files. These dynamic queues don’t need fixed addresses.

Dynamic Queue:

{
  manager = ServiceManager.current();

  HelloQueue queue;

  queue = manager.newService()
                 .service(new HelloImpl())
                 .build()
                 .as(HelloQueue.class);
}

An bound queue has a fixed address, which adds flexibility to the clients, loosening the dependency.

Bound Queue with URL address:

manager = ServiceManager.current();

manager.newService()
       .address("/hello-queue")
       .service(new HelloImpl())
       .build();

HelloQueue queue = manager.lookup("/hello-queue")
                          .as(HelloQueue.class);

queue.hello("hello");

Queues Supporting Async Results

While queues are natually fire-and-forget, a result can be useful to tell when the operation is complete, and if it’s completed successfully. The Result interface returns an asynchronous value or an exception if the method fails.

HelloQueueAsync.java:

public interface HelloQueueAsync extends HelloQueue
{
  void hello(String arg, Result<Void> result);
}

An client can receive the result using four basic patterns:

  • Ignore the result using Result.ignore()
  • Simple callback with a lambda function..
  • Value and exception handling using Result.from()
  • Chaining results using result.from()

How the result callback is called depends on the caller context.

  • If the caller is another Baratine service, the result is dropped into the caller’s inbox. Results are processed on the service’s own thread.
  • If the caller is in the JVM context (or servlet), a thread is allocated for the callback.
  • If the caller is in a embedded framework like vert.x, the result can be placed in the frameworks own processing stack.

In this example, since we’re assuming a servlet context, the result callback would be on an allocated thread. This does mean that the servlet application would need to deal with synchronization. If you want to avoid synchronization issues, use a Baratine service to handle the callbacks.

{
  HelloQueueAsync queueAsync = manager.lookup("/hello-queue")
                                      .as(HelloQueueAsync.class);

  queueAsync.hello(Result.ignore());

  queueAsync.hello(x->System.out.println("Complete"));

  queueAsync.hello(Result.from(x->System.out.println("Complete"),
                               exn->exn.printStackTrace()));
}

Local Module Service for Existing Application

../../_images/example-module.png

Multiple @Worker Threads for Existing Modules

HelloImpl.java

package tutorial.hello;

import io.baratine.core.Result;
import io.baratine.core.Service;

@Service("/hello")
@Worker(10)
public class HelloImpl implements Hello
{
  public String hello()
  {
    String value = doBlockingCall();

    return value;
  }
}

Workers client

The client is exactly the same for multiple workers as for a single-worker service. From the client’s perspective there is no difference; it’s an implementation detail.

Programmatic Deployment of Workers

The non-scanning creation of a multi-worker service needs to specify the number of workers and also a supplier of worker instances. For multi-workers, each worker gets its own instance of HelloImpl.

package tutorial.hello;

import javax.servlet.*;
import javax.servlet.http.*;
import io.baratine.core.*;

ServiceManager manager = ServiceManager.current();

manager.newService()
       .address("/hello")
       .workers(10)
       .service(()->new HelloImpl())
       .build();

Embedded Baratine outside of Web-app

package tutorial.hello;

import io.baratine.core.ServiceManager;

public class MyMain
{
  public static void main(String []args)
  {
    ServiceManager manager = ServiceManager.newManager().build();

    manager.newService()
           .address("/hello")
           .service(new HelloImpl())
           .build();

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

HTTP and WebSocket from Servlet

../../_images/example-hello-server.png

See HTTP/WebSocket Clients for details on remote clients.

Applications that want a JSON-ARPC based HTTP protocol that supports asynchronous RPC, and WebSockets can use Baratine as a protocol framework. The protocol will need an API service as a facade to the existing application. The application itself does not need to change.

Benefits:

  • Supports HTTP-RPC, and WebSocket
  • Clients for JavaScript, PHP, Python
  • Simple, standard encoding
  • Remote API based on Service API

HelloImpl.java:

package tutorial.hello;

import io.baratine.core.Service;

@Service("public:///hello")
public class HelloImpl implements HelloAsync
{
  ...
}

JSON-ARPC/REST servlet (web.xml)

The JSON-RPC protocol that Baratine uses is called JAMP and is implemented by JampServlet in com.caucho.v5.jamp.JampServlet. It should have async-supported set true.

  • jamp-rpc (HTTP RPC call)
  • jamp websocket
  • jamp-pull (HTTP long polling)
$ curl http://localhost:8080/webapp/hello?m=hello
{"status":"ok","value":"hello"}

Microservice on Baratine Server

../../_images/example-microservice.png

See Microservice for the microservice pattern.

Services can be deployed on Baratine JVMs as separate microservices. HTTP clients can use the JSON-RPC HTTP or WebSocket interfaces to call service methods. Java clients can use Baratine proxies with a “pod:” address scheme to call with a fast binary protocol (HAMP).

When calling from a servlet web-app, the client needs a baratine.cf configuration for the IP addresses of the Baratine seed servers. The client will connect to the seed servers and obtain the current network configuation, including the location of the deployed hello service.

Benefits:

  • JVM isolation
  • HTTP and WebSocket
  • Java clients with binary protocol
  • HTTP, JavaScript, PHP, Pythons clients with JSON-RPC
  • Failover and Sharding

Source

The source code for hello deployed on a Baratine server is the same as the initial hello example.

The address uses the “public:” scheme to expose it to HTTP clients.

HelloImpl.java:

@Service("public:///hello")
public class HelloImpl
{
  public String hello()
  {
    return "hello";
  }
}

Baratine Server Start and Deployment

$ bin/baratine start
Baratine/0.10-SNAPSHOT start with watchdog at 127.0.0.1:6700
Baratine/0.10-SNAPSHOT launching watchdog at 127.0.0.1:6700
  starting *:8085 (cluster-8085)

Build the service jar and deploy to a running Baratine instance.

$ bin/baratine deploy hello.jar
  deployed hello.jar to bfs:///config/pods/50-pod.cf

The service is deployed to a pod named “pod”, which is a virtual cluster. It uses Baratine’s distributed filesystem (BFS) for the application jars and any configuration. All servers in the cluster have access to the application, which enables replication, failover and sharding.

Web-App Client to Baratine Server

The web-app client needs to know the IP address of the server, which is configured in the WEB-INF/baratine.cf file. The configuration specifies the seed servers and the port for the web-app itself to use. In the following, the seed server is localhost:8085 and the web-app’s Baratine port is 8084.

WEB-INF/baratine.cf:

cluster {
  server localhost 8085;
  server 8084;
}

Client Lookup and call:

{
  ServiceManager manager = ServiceManager.current();

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

  System.out.println("Hello: " + hello.hello());
}

Programmatic Deployment in Baratine Server

Some Baratine applications may want programmatic deployment. On startup, Baratine uses the JDK’s ServiceLoader mechanism to load ServiceInitializer classes, where an application can initialize its services.

HelloInit.java:

package tutorial.hello;

import io.baratine.core.ServiceManager;
import io.baratine.core.ServiceInitializer;

public class HelloInit implements ServiceInitializer
{
  public void init(ServiceManager manager)
  {
    manager.newService()
           .address("public:///hello")
           .service(new HelloImpl())
           .build();
  }
}

META-INF/services/io.baratine.core.ServiceInitializer:

tutorial.hello.HelloInit

Failover

../../_images/example-microservice-failover.png

baratine.cf

baratine.cf:

cluster {
  pod pod {
    server 10.0.0.10 8085;
    server 10.0.0.11 8085;
  }

  server 8085;
}

start:

$ baratine start --conf baratine.conf

command-line

$ baratine start --seed 10.0.0.10:8085 --seed 10.0.0.11:8085

Or if you want to specify alternative ports:

$ baratine start -p 8086 --seed 10.0.0.10:8085 --seed 10.0.0.11:8085

Java Web-App Client

The Java web-app client has no changes. When the servers start, they will communicate the network information through the internal bartender heartbeat.

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

System.out.println("hello: " + hello.hello());

The server will send the request to the appropriate server.

In-Memory Services and Databases

Single Item In-Memory Service With Database

../../_images/example-solo-ext-db.png

See In Memory with External Database for the in-memory pattern.

Baratine’s in-memory service uses the same principles as a sharded cache like memory, except Baratine’s services are active: they load themselves from the database and offer sophisticated actions. The services encapsulate their data more completely.

Benefits:

  • in-memory
  • single owner
  • single load; multiple write
  • synchronization
  • isolation
  • cpu affinity
  • failover
  • journalling

Limitations:

  • requires Baratine Server
  • single owner
  • service requires async database API
  • service must not block
  • single threaded (but can be sharded)

This section shows a single database item for simplicity. The following section will show multiple items, such as a user service or an auction service.

Overview

The in-memory service is a strict encapsulation of its data. Only the service itself is allowed to modify the data. This single-owner/single-writer assumption is required for the pattern.

  1. An @OnLoad method loads the service data from an async database. The method does not block. This is the only load.
  2. Read methods that do not modify the data operate entirely in-memory.
  3. @Modify methods that change the data will trigger an @OnSave method.
  4. The @OnSave method writes updates to the database. Like the @OnLoad, the @OnSave must not block.
  5. Under heavy load, the @OnSave writes are batched, reducing database load.

Because of the async database requirement, this pattern uses a gateway pattern to the database for the async API. In the diagram at top, the “W” are multiple workers that provide an async API for a blocking database.

HelloImpl.java

@Service("/hello")
@Journal
public class HelloImpl
{
  @Inject @Lookup("/hello-data")
  HelloDataService _helloData;

  private String _greeting;

  public void hello(Result<String> result)
  {
    result.complete(_greeting);
  }

  @Modify
  public void put(String value)
  {
    _greeting = value;
  }

  @OnLoad
  private void onLoad(Result<Void> result)
  {
    _helloData.loadHello(result.from(v->onLoadHello(v)));
  }

  private Void onLoadHello(String value)
  {
    if (value != null) {
      _greeting = value;
    }
    else {
      _greeting = "hello";
    }
  }

  @OnSave
  private void onSave()
  {
    _helloData.saveHello(_greeting);
  }
}

Hello client

The hello client is exactly the same as before. Because of the loose coupling between the client and the service, the client doesn’t depend on the service’s implementation. The client calls look like microservice calls.

ServiceManager manager = ServiceManager.current();

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

System.out.println("hello: " + hello.hello());

HelloDataService - Async Gateway to Blocking Database

HelloDataService.java:

public interface HelloDataService
{
  void loadHello(String id, Result<String> result);

  void saveHello(String id, String value);
}

HelloDataImpl.java:

@Service("/hello-data")
@Workers(10)
public class HelloDataService implements HelloDataService
{
  public void loadHello(String id, Result<String> result)
  {
    String value = doSlowDatabaseLookup(id);

    result.complete(value);
  }

  public void saveHello(String id, String value)
  {
    doDatabaseSave(id, value);
  }
}

Deployment

$ baratine start
$ baratine deploy hello.jar

If the client is deployed in a servlet web-app, the following must be deployed

  • WEB-INF/lib/hello-client.jar
  • WEB-INF/lib/baratine.jar
  • WEB-INF/baratine.cf

The baratine.cf tells the client the TCP address of the Baratine servers.

Failover for In-Memory Service

../../_images/example-solo-ext-db-failover.png

Benefits:

  • reliability
  • maintenance

Failover in Baratine is done by configuration. No code changes are necessary because the Baratine architecture is designed to support backups.

Failover is configured when more than one server is in the cluster. It is possible to configure the servers explicitly in the “pod” section of the configuration file. The default behavior is to assign servers automatically to pods. Baratine servers send heartbeat messages to each other to maintain network information.

When the primary server for a pod goes down, all servers are notified and switch to use the backup server. This failover is invisible to the application, because the Hello proxy switches servers.

baratine.cf:

cluster {
  port 8085;                # default port

  pod pod {                 # configures the "pod" pod
    server 10.0.0.10 8085;  # primary server for "pod"
    server 10.0.0.11 8085;  # backup server for "pod"
  }
}

On a failover, the new hello service loads its data in the @OnLoad method. Once the load completes, any pending @Journal messages are replayed. Once the replay completes, the @OnActive method is called and the service starts processing new requests.

Multi-Item In-Memory Service With Database

../../_images/example-multi-ext-db.png

Data services like a user service or an auction service will have many items. In Baratine, these items are created with an @OnLookup when a client calls a method on a child address. In this example, each Hello item is for a new language. “/hello/en” is the English translation.

Benefits:

  • in-memory
  • single owner
  • synchronization
  • cpu affinity
  • failover
  • sharding
  • journalling

The in-memory service is single-owner and loads its item only once. After the load the service operates in memory.

Limitations:

  • requires Baratine Server
  • single owner
  • must not block

The single owner and non-blocking limitations are tied together because each multi-item service uses a single thread. The service must not block because blocking the “/hello/en” item would delay processing of the “/hello/jp” item. Scalability is handled by sharding, discussed below.

The non-blocking requirement means that the multi-item service must use asynchronous Result calls to other services to support blocking resources. A traditional database would need a data-access service with an async Result load, probably using multiple @Worker services as in the gateway pattern.

HelloManager.java

Items are created by an application manager in an @OnLookup, but are managed by Baratine. The HelloManager below is an example. The @OnLookup must only instantiate the instance, not load or validate it.

The item instance validates itself and loads itself because of the single-owner principle. The item is the single owner. The manager only owns itself, not the child items it creates. The address may help keep this clear. The manager’s address is “/hello”. The English child’s address is “/hello/en”. Each address has a different owner.

@Service("/hello")
@Journal
public class HelloManager
{
  @Inject @Lookup("/hello-data")
  HelloDataService _dataService;

  @OnLookup
  private HelloImpl onLookup(String path)
  {
    return new HelloImpl(_dataService, path);
  }
}

HelloImpl.java

The item is responsible for its own initialization in its @OnInit method, and its loading in its @OnLoad method. Any additional validation can occur in an @OnActive method.

The constructor must not do anything except set the id field, and possibly shared resources from the manager.

public class HelloImpl
{
  private final HelloDataService _dataService;
  private final String _id;

  private String _greeting;

  HelloImpl(HelloDataService dataService,
            String id)
  {
    _dataService = dataService;
    _id = id;

    // Note: the constructor does not load or validate
  }

  public String hello()
  {
    return _greeting;
  }

  @Modify
  public void put(String value)
  {
    _greeting = value;
  }

  @OnLoad
  private void onLoad(Result<Void> result)
  {
    _dataService.loadHello(_id, result.from(v->afterLoad(v)));
  }

  private Void afterLoad(String value)
  {
    _greeting = value != null ? value : "hello";

    return null;
  }

  @OnSave
  private void onSave()
  {
    _dataService.saveHello(_id, _greeting);
  }
}

Java Client

The Java client for a multi-item service will lookup the item by its address, which may be dynamic.

ServiceManager manager = ServiceManager.current();

String lang = "en";

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

System.out.println("hello: " + hello.hello());

Scaling with Load Balancing

../../_images/example-sharding-db.png

Benefits:

  • in-memory
  • scales for increased load
  • reduce database load
  • isolates database from application servers
  • synchronization
  • active updates
  • replication and failover

You can imagine Baratine’s in-memory scaling as an active cache, like a memcache where the cache fills itself on a data miss and also operates directly on its data.

Baratine Configuration for Sharding

baratine.cf:

cluster {
  port 8085;                # default port

  pod pod type=cluster {    # "pod" is a sharding cluster
    server 10.0.0.10 8085;  # sharding server
    server 10.0.0.11 8085;  # sharding server
    server 10.0.0.12 8085;  # sharding server
  }
}

Java Client and Server are Unchanged

Because Baratine is designed for the sharded in-memory pattern, the client and server source code does not change for sharding. Only the configuration changes to specify which servers will handle the load.

Conclusion

Baratine is a platform for building a network of loosely-coupled POJO microservices.

Some quick wins for an existing servlet applications:

  • Encapsulate singleton resources
  • Active queues with application APIs
  • Encapsulate existing modules as local microservices
  • Improve testability with loose coupling

Longer-term wins with Baratine servers:

  • Microservices deployed in Baratine servers with failover
  • In-memory services with failover and load balancing (sharding)

This tutorial works through the core concepts.

  • Services encapsulate code, data, and threading with a Java API.
  • The Client API is a Java interface.
  • Service implementation is a Java class.
  • Asynchronous method calls are supported with Result.
  • Remote Clients can use HTTP or WebSockets and a JSON-based method call protocol. Support for JavaScript, Java, and PHP clients is provided.
  • In-memory services can act as fast, smart caches.

Baratine features a high-level abstraction model. You do not deal with messages nor forced into a weird model for your application. Baratine lets you build your services around plain-old-Java-objects (POJOs). It is true object-oriented programming where the encapsulation also includes the executing thread. This allows you to model your services in lock-step with your business application logic, while being reactive at the same time.

Definitions

Inbox: a queue where incoming service requests are serialized into

POJO: plain-old-Java-object, Baratine’s first-class abstraction object

Service: a POJO where its interface methods are invokable by clients

Result: asynchronous callback to be executed in the caller’s context

GitHub Repository

The code for this example is in GitHub.