We know that Lagom doesn’t prescribe any particular environment, although Lagom provide out of the box ConductR, an integrated environment that provide several services to deploy and manage your services. It would be interesting, in some cases, to be able to deploy a service in standalone fashion.

This article start with idea that you know how write and run a Lagom service in dev environment.

We know that one microservice cannot live without service locator. In development environment, Lagom’s plugin provided one service locator out of the box. It exclusively dedicated to the dev mode. The unique thing required to run a Lagom microservice in standalone mode is a service Locator.

Here we will see how run a Lagom service in standalone mode.

To follow this guide, this imply you have one Zookeeper instance accessible. Please look at here to learn how running zookeeper instance

As service locator registry, we will used Zookeeper. For that, we will use the James Boner’s project that implements service Locator interface of Lagom for Zookeeper.

This guide will explain how to :

  • integrate the zookeeper service locator in your Lagom’s project
  • package a service to run it in standalone manner

We will use the following service implementation :

Service interface

import com.lightbend.lagom.javadsl.api.Descriptor;
import com.lightbend.lagom.javadsl.api.Service;
import com.lightbend.lagom.javadsl.api.ServiceCall;

import static com.lightbend.lagom.javadsl.api.Service.named;
import static com.lightbend.lagom.javadsl.api.transport.Method.POST;

public interface HelloWorldService extends Service {

    ServiceCall<String, String> sayHello();

    @Override
    default Descriptor descriptor() {

        return named("helloWorld").withCalls(Service.restCall(POST, "/msg&amp", this::sayHello)).withAutoAcl(Boolean.TRUE);
    }
}

Service implementation

import com.lightbend.lagom.javadsl.api.ServiceCall;

import static java.util.concurrent.CompletableFuture.completedFuture;

public class HelloWorldServiceImpl implements HelloWorldService {

    @Override
    public ServiceCall<String, String> sayHello() {
        return request -> completedFuture("Hello" + request );
    }
}

 

How integrate the zookeeper service locator in your Lagom’s project

First step will be to add an implementation of the zookeeper service locator into your Lagom service.
For that, we have to build locally the lagom-zookeeper-service-locator project.
We have to do that because the project is under snapshot version.
Well, clone the project with the command below :

git clone https://github.com/jboner/lagom-service-locator-zookeeper.git

then publish locally the project as below :

 > sbt publishLocal

Once is published, we have to add the project as dependencies in your service, open your build.sbt file et add line as below :

 libraryDependencies in ThisBuild += "com.lightbend.lagom" % "lagom-service-locator-zookeeper_2.11" % "1.0.0-SNAPSHOT"

Now the service locator must be activate. We have to declare the module’s class in the service configuration file (by default, application.conf). Informations below indicated to the service Locator how reach zookeeper instance :

lagom {
  discovery {
    zookeeper {
      server-hostname = "127.0.0.1"   # hostname or IP-address for the ZooKeeper server
      server-port     = 2181          # port for the ZooKeeper server
      uri-scheme      = "http"        # for example: http or https
      routing-policy  = "round-robin" # valid routing policies: first, random, round-robin
    }
  }
}

Here, your service is able to work with the service locator but it is not yet localizable. We going to modify the service module class to ensure that the service becomes localizable.

public class HelloWorldServiceModule extends AbstractModule implements ServiceGuiceSupport {

    private Environment environment;

    @Inject
    public HelloWorldServiceModule(Environment environment, Configuration configuration) {
        this.environment = environment;
    }

    @Override
    protected void configure() {

        bindServices(serviceBinding(HelloWorldService.class, HelloWorldServiceImpl.class));

        if (environment.mode() == Mode.Prod()) {

            try {
                ZooKeeperServiceRegistry registry = new ZooKeeperServiceRegistry(
                        ZooKeeperServiceLocator.zkUri(),
                        ZooKeeperServiceLocator.zkServicesPath());
                registry.start();

                // create the service instance for the service discovery
                // needs to be held on to to be able to unregister the service on shutdown
                ServiceInstance<String> serviceInstance = ServiceInstance.<String>builder()
                        .name("helloWorld")
                        .id("helloWorldId")
                        .address("localhost")
                        .port(8080)
                        .uriSpec(new UriSpec("{scheme}://{serviceAddress}:{servicePort}"))
                        .build();

                // register the service
                registry.register(serviceInstance);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

The code above, record the service reference into zookeeper. You noticed that the code will be enabled only in production environment to avoid any conflict with service locator from dev environment. name and id parameters need a little explanation. Name parameter is the name os the microservice while id parameter is the reference of the instance of the microservice.

Now your service is ready to be used in standalone manner. Next, we will see how packaged and run the service.

How package a service to run in standalone manner

This step is very simple. To package your service, from sbt console you should select service implementation project by the command :

> project helloWorld-imp

Once, it is selected, simply run the following command :

 > dist

Then to start your service in standalone manner, go into the following path:

/helloworld/helloWorld-impl/target/universal/

Here, you will find an archive named by the service implementation name, in our case helloworldimpl-[current_version].zip
In our case, name should be

helloworldimpl-1.0-SNAPSHOT.zip

This archive contains all elements require to run in standalone manner.

Unzip archive :

unzip helloworldimpl-1.0-SNAPSHOT.zip

and finally, run the script localized in bin directory :

 ./helloworldimpl-1.0-SNAPSHOT/bin/helloworldimpl

Now, your Lagom service started in standalone manner. It can be interact with all your others services.

If you connect to your zookeeper instance by the zookeeper client (zkCli.sh). With the following command you will get all service’s information (path corresponds to the configuration that we put into class’ module) :

get /lagom/services/helloWorld/helloWorldId

Result that you will obtain :

{"name":"helloWorld","id":"helloWorldId","address":"localhost",
"port":8080,"sslPort":null,"payload":null,
"registrationTimeUTC":1474744298139,"serviceType":"DYNAMIC",
"uriSpec":{"parts":[{"value":"scheme","variable":true},
{"value":"://","variable":false},
{"value":"serviceAddress","variable":true},
{"value":":","variable":false},{"value":"servicePort","variable":true}]}}

This configuration should be add in all yours services that compose your microservices system. After, your services can localize all others services through the service locator.

I hope this article will give you a clear understanding of relation between Lagom service and service Locator.