Protocol for Existing Module

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

Protocol for a Module

Introduction

The protocol pattern gives an existing module a HTTP protocol interface that supportsa HTTP request-response using JAMP and a WebSocket async protocol using JAMP.

This pattern is for a stateless protocol and a legacy module. For stateful protocols, see the session pattern.

The module pattern can be used in an existing deployment, for example in a servlet web-app. Baratine’s service creates a loosely-coupled boundary around the service.

Used for:

  • Existing multithreaded modules

Benefits:

  • Standard HTTP/JSON protocol
  • HTTP RPC for simple clients
  • WebSockets when available
  • Fallback to HTTP long-polling when not available
  • Testability
  • Worker limits

Limitations:

  • Fixed JSON-ARPC (JAMP) encoding

Baratine’s remoting is designed around a standard mapping from Java Service interface and classes. Since it is not designed as a customizable protocol generator, applications that need to support existing remote APIs will need to use other remoting packages in combination with Baratine.

Source code

For this example, we’ll show how to deploy a service module in a servlet web-app.

Existing module (MyModule)

For this example, we’ll assume that the existing module has a singleton interface. This is not a requirement of the service pattern, but illustrates integration with the new service workers.

@javax.inject.Singleton
public class MyModule
{
  String myMethod(String arg1);
}

Protocol workers (MyModuleServiceImpl)

The module service uses multiple worker instances defined by @Workers. Because each worker is instantiated independently, the worker will need to obtain the shared legacy module. In this example, we use an @Inject of a @Singleton module to obtain the shared instance.

The multi-worker design is required if the module itself is designed around a blocking, multi-threaded pattern. If the module is already non-blocking, the service implementation can remove the @Workers which will improve its cpu affinity and performance.

Each worker gets its own implementation instance. Since that instance is single threaded, the worker can save per-thread objects.

Annotate the service with @Workers and inject the shared instance:

@Service("public:///my-module")
@Workers(64)
public class MyModuleServiceImpl implements MyModuleService
{
  private @Inject MyModule _myModule;
  ...

  public void myMethod(String arg, Result<String> result)
  {
    result.complete(_myModule.myMethod(arg));
  }
}

The application can also use an @OnInit to initialize the MyModule instance, giving it more flexibility to obtain the module.

public class MyModuleServiceImpl implements MyModuleService
{
  private MyModule _myModule;
  ...
  @OnInit
  private void onInit()
  {
    _myModule = myModuleLookup();
  }

Service Proxy API (MyModuleService)

The MyModuleService interface defines the proxy API and can serve as documentation even for non-Java clients. It can also be used for testing the service.

By convention,``MyModuleService`` defines the non-blocking calls and MyModuleServiceSync defines the blocking versions. The non-blocking version can be used by callers that want an async response.

MyModuleService.java:

import io.baratine.core.Result;

public interface MyModuleService
{
  void myMethod(String arg, Result<String> result);
}

The synchronous version MyModuleServiceSync is used for existing Java clients of the module and for unit testing.

MyModuleServiceSync.java:

import io.baratine.core.Result;

public interface MyModuleServiceSync extends MyModuleService
{
  String myMethod(String arg);
}

Java Client Code

The ServiceClient can provide a proxy to the module service. The code looks like:

String url = "http://localhost:8080/jamp";
ServiceClient client = ServiceClient.newClient(url).build();

MyModuleServiceSync myModule = client.lookup("/my-module")
                                     .as(MyModuleServiceSync.class);

Since the proxy is thread-safe, it can be looked up once during initialization and used for deployment.

See below for more detail on non-Java clients.

Web-app deployment

The web-app deployment is basically:

  • add WEB-INF/lib/baratine.jar
  • add service implementation classes to WEB-INF

When the web-app starts, Baratine will start a new ServiceManager and scan classes in WEB-INF looking for @Service classes. Each @Service class will be deployed in the ServiceManager.

If the proxy API is matches the existing module API, the clients changes can be minimal, only requiring a lookup of the proxy, and using existing code once the proxy is obtained.

JAMP Servlet Deployment

The JAMP servlet is:

com.caucho.v5.jamp.JampServlet

It should be deployed with async-supported set to true.

See below for more details on the JAMP protocol, or HTTP/WebSocket Clients for the main remoting documentation.

Web-App Programmatic Deployment

The module service can also be deployed without relying on the @Service scanning by using the ServiceManager.newService method. For example, an application ServletContextListener could create an register the module.

Programmatic registration might look like:

MyModule module = new MyModule();
ServiceManager manager = ServiceManager.current();

manager.newService()
       .service(()->new MyModuleServiceImpl(module))
       .address("/my-module")
       .workers(100)
       .build();

The .service lambda expression is a Supplier that creates a new service instance for each worker.

JAMP Remoting

The full documentation is at HTTP/WebSocket Clients. Baratine includes client support for JavaScript, PHP, and Python.

This section is informational. Most developers may not need to know the protocol itself for developing, it may be useful for debugging.

JAMP Sync RPC: HTTP request/response

The x-application/jamp-rpc content-type is Baratine’s main HTTP request/response protocol. It consists of four main message types:

["send", {}, "/my-module", "myMethod", arg1]

["query", {}, "/my-client", 1721, "/my-module", "myMethod", arg1]

["reply", {}, "/my-client", 1721, myValue]

["error", {}, "/my-client", 1721, myDetail]

The query’s return address and correlation id (“/my-client” and 1721) are more important when JAMP is used with WebSockets and HTTP long-polling.

JAMP Async RPC: WebSocket and HTTP Long-polling

When available, the Baratine clients can use WebSockets or HTTP long polling.

The JAMP protocol is asynchronous; responses can return out-of-order. The out-of-order is required so slow requests won’t block fast requests when multiple calls are pending.

HAMP - binary Async RPC

When Baratine’s Java client talks to a Baratine server or the JAMP servlet, it will use HTMP, a binary version of JAMP. Instead of using JSON, it uses Hessian, which is like an efficient binary JSON with classes and other object-oriented features added.