Tutorial: Child Services

../../_images/example-multi-store.png

List Service with Baratine store:

Prerequisites

This tutorial will show you how to build a RESTful service with multiple child instances living at unique URLs off of the parent.

Introduction

Baratine uses URLs throughout so that services can be easily locatable and shardable.

URLs allow your service to either use a local JVM version of a service, or one on another machine. In a clustered environment, Baratine hashes the URL to determine which machine to send the request to. A request to service /foo will always go to the same machine, but how do you have multiple instances of a service running? To implement sharding of services, Baratine uses child services.

The Child Service

A child service is a service that uses the same Inbox queue as the parent service. Its URL is the parent’s URL suffixed with a path. For example:

/counter     - parent
/counter/aaa - child
/counter/bbb - child

Child services are separate instances from the parent. For all intents and purposes, they are separate services. Whenever a client sends a request to /counter/aaa for the very first time, Baratine will ask the parent for a child service by calling the @OnLookup method:

@OnLookup
public CounterService onLookup(String url)
{
  return new CounterServiceImpl(url);
}

Subsequent requests to the same URL will use the same child instance. Baratine keeps child instances in memory in an LRU.

Child services support the same lifecyle annotations as the parent service. When the child service is first initialized, Baratine will call its @OnLoad. When the child is going away, Baratine will call its @OnSave.

Note

Your @OnLookup should not call out to their services. Initialization and loading should be done in your child’s @OnInit and @OnLoad respectively.

CounterManagerImpl.java - the Parent Service

package tutorial.children;

@Service("/counter")
public class CounterManagerImpl
{
  @OnLookup
  public CounterService onLookup(String url)
  {
    return new CounterServiceImpl(url);
  }
}

Note

The parent service is just an ordinary Baratine service. Clients may call methods on the parent using the parent’s URL.

CounterServiceImpl.java - the Child Service

package tutorial.children;

import io.baratine.core.Modify;
import io.baratine.core.OnLoad;
import io.baratine.core.OnSave;
import io.baratine.core.Result;
import io.baratine.core.Service;
import io.baratine.store.Store;

public class CounterServiceImpl
{
  private String _url;
  private long _count;

  @Inject @Lookup("store:///counter")
  private void Store<Long> _store;

  public CounterServiceImpl(String url)
  {
    _url = url;
  }

  @OnInit
  public void onInit(Result<Void> result)
  {
    _store.lookup(url, result.from(store -> _store = store));
  }

  @Modify
  public void incrementAndGet(long value, Result<Long> result)
  {
    _count += value;

    result.complete(_count);
  }

  @OnLoad
  public void onLoad(Result<Void> result)
  {
    _store.get("count", result.from(value -> onLoadComplete(value)));
  }

  private void onLoadComplete(Long value)
  {
    if (value == null) {
      _count = 0;
    }
    else {
      _count = value;
    }
  }

  @OnSave
  public void onSave(Result<Void> result)
  {
    _store.put("count", _count, result.from(Void -> onSaveComplete()));
  }

  private void onSaveComplete()
  {
    // do nothing
  }
}

This CounterServiceImpl.java is mostly the same as the last tutorial’s. The difference is that this one needs to look up its Store dynamically so that its Store URL matches the child’s URL. If the URL matches, then Baratine guarantees that requests to the Store from the child will always go on the same JVM.

CounterService.java

package tutorial.persistence;

import io.baratine.core.Result;

public interface CounterService
{
  void incrementAndGet(long value, Result<Long> result);
}

Deployment

Deployment is the same as before. Just deploy the parent service:

$ bin/baratine.sh deploy child-services.jar

Or you can deploy it as an embedded service programmatically:

ServerBaratine server = Baratine.newServer()
                                .port(8085)
                                .root("file:/tmp/caucho/qa")
                                .build()

ServiceManager manager = server.newPod("pod")
                               .build()
                               .manager();

ServiceRef ref = manager.newService()
                        .address("public:///counter")
                        .service(new CounterManagerImpl())
                        .build();

Thread.currentThread().join();

The Client

String id = args[0];
long value = Long.parseLong(args[1]);

ServiceClient client = ServiceClient.newClient("http://127.0.0.1:8085/s/pod").build();

CounterService service = client.lookup("remote:///counter" + id).as(CounterService.class);

service.incrementAndGet(value, value -> System.out.println("incremented counter " + id + ": " + value));

Run the client on the command-line and you’ll get:

$ java -cp child-services-jar-with-dependencies.jar tutorial.children.CounterClient /aaa 111
Received response: 111

$ java -cp child-services-jar-with-dependencies.jar tutorial.children.CounterClient /aaa 111
Received response: 222

$ java -cp child-services-jar-with-dependencies.jar tutorial.children.CounterClient /bbb 444
Received response: 444

Conclusion

You have just learned how to implement a service with multiple child instances. The next tutorial will go over the concept of facade services for remote clients, which will be important once we deploy to a clustered environment.