Local Microservice

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

Local Microservice

Introduction

Baratine can wrap an existing module into a local microservice. Like external microservices, local microservices are encapsulated to create a clean boundary. A local microservice can be a useful tool to organize an application without needing to start and manage multiple servers.

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
  • Existing deployment (in JVM services)

Benefits:

  • Context Boundary
  • Loose coupling
  • Async callers
  • Testability
  • Isolation
  • Some CPU affinity
  • Worker limits

Examples:

  • internal modules in a monolithic application
  • lucene library
  • DAO using Hibernate

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);
}

Service 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.

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

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

@Service("local:///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. 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);
}

Client Code

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

ServiceManager manager = ServiceManager.current();

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

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

Web-app deployment

The web-app deployment is basically:

  • add WEB-INF/lib/baratine.jar
  • add service implementation classes to WEB-INF
  • modify uses of the module to use the service proxy

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.

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.

Non Web-App Programmatic Deployment

The service module pattern can be used outside of a web-app (and outside of a Baratine container.) The ServiceManager interface includes a newManager method, which creates a new ServiceManager that can be used to create and lookup the service module.

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

MyModule module = new MyModule();

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

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