Guide to Reactive Microservices Using Lagom Framework

1. Overview

In this article, we’ll explore the
Lagom
framework
and implement an example application using a reactive
microservices driven architecture.

Simply put, reactive software applications rely on message-driven
asynchronous communication and are highly Responsive, Resilient and
Elastic in nature.

By microservice-driven architecture, we meant splitting the system into
boundaries between collaborative services for achieving goals of
Isolation, Autonomy, Single Responsibility, Mobility, etc. For
further reading on these two concepts, refer
The Reactive Manifesto and
Reactive
Microservices Architecture
.

2. Why Lagom?

Lagom is an open source framework built with the shifting from monoliths
to microservices-driven application architecture in mind. It abstracts
the complexity of building, running and monitoring microservices driven
applications.

Behind the scenes, Lagom framework uses the
Play Framework, an Akka
message-driven runtime, Kafka for decoupling
services, Event
Sourcing
, and CQRS patterns,
and ConductR support for monitoring and
scaling microservices in the container environment.

*3. Hello World in Lagom

*

We’ll be creating a Lagom application to handle a greeting request from
the user and reply back with a greeting message along with weather
statistics for the day.

And we’ll be developing two separate microservices: Greeting and
Weather.

Greeting will focus on handling a greeting request, interacting with
weather service to reply back to the user. The Weather microservice
will service the request for weather statistics for today.

In the case of existing user interacting with Greeting microservice,
the different greeting message will be shown to the user.

3.1. Prerequisites

  1. Install Scala (we are currently using 2.11.8 version) from
    here

  2. Install sbt build tool (we are currently using 0.13.11) from
    here

*4. Project Setup

*

Let’s now have a quick look at the steps to set up a working Lagom
system.

4.1. SBT Build

Create a project folder lagom-hello-world followed by the build file
build.sbt. A Lagom system is typically made up of a set of sbt
builds with each build corresponding to a group of related services:

organization in ThisBuild := "org.baeldung"

scalaVersion in ThisBuild := "2.11.8"

lagomKafkaEnabled in ThisBuild := false

lazy val greetingApi = project("greeting-api")
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslApi
    )
  )

lazy val greetingImpl = project("greeting-impl")
  .enablePlugins(LagomJava)
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslPersistenceCassandra
    )
  )
  .dependsOn(greetingApi, weatherApi)

lazy val weatherApi = project("weather-api")
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslApi
    )
  )

lazy val weatherImpl = project("weather-impl")
  .enablePlugins(LagomJava)
  .settings(
    version := "1.0-SNAPSHOT"
  )
  .dependsOn(weatherApi)

def project(id: String) = Project(id, base = file(id))

To start with, we’ve specified the organization details, scala
version, and disabled Kafka for the current project. Lagom follows a
convention of two separate projects for each microservice
: API project
and an implementation project.

The API project contains the service interface on which the
implementation depends.

We’ve added dependencies to the relevant Lagom modules like
lagomJavadslApi, lagomJavadslPersistenceCassandra for using the
Lagom Java API in our microservices and storing events related to the
persistent entity in Cassandra, respectively.

Also, the greeting-impl project depends on the weather-api project
to fetch and serve weather stats while greeting a user.

Support for the Lagom plugin is added by creating a plugin folder with
plugins.sbt file, having an entry for Lagom plugin. It provides all
the necessary support for building, running, and deploying our
application.

Also, the sbteclipse plugin will be handy if we use Eclipse IDE for
this project. The code below shows the contents for both plugins:

addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")

Create project/build.properties file and specify sbt version to
use:

sbt.version=0.13.11

*4.2. Project Generation

*

Running sbt command from the project root will generate the following
project templates:

  1. greeting-api

  2. greeting-impl

  3. weather-api

  4. weather-impl

Before we start implementing the microservices, let’s add the
src/main/java and src/main/java/resources folders inside each of the
projects, to follow Maven-like project directory layout.

Also, two dynamic projects are generated inside
project-root/target/lagom-dynamic-projects:

  1. lagom-internal-meta-project-cassandra

  2. lagom-internal-meta-project-service-locator

These projects are used internally by Lagom.

*5. Service Interface

*

In the greeting-api project, we specify the following interface:

public interface GreetingService extends Service {

    public ServiceCall<NotUsed, String> handleGreetFrom(String user);

    @Override
    default Descriptor descriptor() {
        return named("greetingservice")
          .withCalls(restCall(Method.GET, "/api/greeting/:fromUser",
            this::handleGreetFrom))
          .withAutoAcl(true);
    }
}

GreetingService exposes handleGreetFrom() to handle greet request
from the user. A ServiceCall API is used as the return type of these
methods. ServiceCall takes two type parameters Request and
Response.

The Request parameter is the type of the incoming request message, and
the Response parameter is the type of the outgoing response message.

In the example above, we’re not using request payload, request type is
NotUsed, and Response type is a String greeting message.

GreetingService also specifies a mapping to the actual transport used
during the invocation, by providing a default implementation of the
Service.descriptor() method. A service named greetingservice is
returned.

handleGreetFrom() service call is mapped using a Rest identifier:
GET method type and path identifier /api/greeting/:fromUser mapped
to handleGreetFrom() method. Check
this
link
out for more details on service identifiers.

On the same lines, we define WeatherService interface in the
weather-api project. weatherStatsForToday() method and
descriptor() method are pretty much self explanatory:

public interface WeatherService extends Service {

    public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday();

    @Override
    default Descriptor descriptor() {
        return named("weatherservice")
          .withCalls(
            restCall(Method.GET, "/api/weather",
              this::weatherStatsForToday))
          .withAutoAcl(true);
    }
};

WeatherStats is defined as an enum with sample values for different
weather and random lookup to return weather forecast for the day:

public enum WeatherStats {

    STATS_RAINY("Going to Rain, Take Umbrella"),
    STATS_HUMID("Going to be very humid, Take Water");

    public static WeatherStats forToday() {
        return VALUES.get(RANDOM.nextInt(SIZE));
    }
}

6. Lagom Persistence – Event Sourcing

Simply put, in a system making use of Event Sourcing, we’ll be able to
capture all changes as immutable domain events appended one after the
other. The current state is derived by replaying and processing
events.
This operation is essentially a foldLeft operation known from
the Functional Programming paradigm.

Event sourcing helps to achieve high write performance by appending the
events and avoiding updates and deletes of existing events.

Let’s now look at our persistent entity in the greeting-impl project,
GreetingEntity:

public class GreetingEntity extends
  PersistentEntity<GreetingCommand, GreetingEvent, GreetingState> {

      @Override
      public Behavior initialBehavior(
        Optional<GreetingState> snapshotState) {
            BehaviorBuilder b
              = newBehaviorBuilder(new GreetingState("Hello "));

            b.setCommandHandler(
              ReceivedGreetingCommand.class,
              (cmd, ctx) -> {
                  String fromUser = cmd.getFromUser();
                  String currentGreeting = state().getMessage();
                  return ctx.thenPersist(
                    new ReceivedGreetingEvent(fromUser),
                    evt -> ctx.reply(
                      currentGreeting + fromUser + "!"));
              });

            b.setEventHandler(
              ReceivedGreetingEvent.class,
              evt -> state().withMessage("Hello Again "));

            return b.build();
      }
}

Lagom provides
PersistentEntity<Command,
Entity, Event>
API for processing incoming events of type Command
via setCommandHandler() methods and persist state changes as events of
type Event. The domain object state is updated by applying the event
to the current state using the setEventHandler() method. The
initialBehavior()
abstract method defines the Behavior of the entity.

In initialBehavior(), we build original GreetingState “Hello” text.
Then we can define a ReceivedGreetingCommand command handler – which
produces a ReceivedGreetingEvent Event and gets persisted in the event
log.

GreetingState is recalculated to “Hello Again” by the
ReceivedGreetingEvent event handler method. As mentioned earlier,
we’re not invoking setters – instead, we are creating a new instance of
State from the current event being processed
.

Lagom follows the convention of GreetingCommand and GreetingEvent
interfaces for holding together all the supported commands and events:

public interface GreetingCommand extends Jsonable {

    @JsonDeserialize
    public class ReceivedGreetingCommand implements
      GreetingCommand,
      CompressedJsonable,
      PersistentEntity.ReplyType<String> {
          @JsonCreator
          public ReceivedGreetingCommand(String fromUser) {
              this.fromUser = Preconditions.checkNotNull(
                fromUser, "fromUser");
          }
    }
}
public interface GreetingEvent extends Jsonable {
    class ReceivedGreetingEvent implements GreetingEvent {

        @JsonCreator
        public ReceivedGreetingEvent(String fromUser) {
            this.fromUser = fromUser;
        }
    }
}

*7. Service Implementation

*

7.1. Greeting Service

public class GreetingServiceImpl implements GreetingService {

    @Inject
    public GreetingServiceImpl(
      PersistentEntityRegistry persistentEntityRegistry,
      WeatherService weatherService) {
          this.persistentEntityRegistry = persistentEntityRegistry;
          this.weatherService = weatherService;
          persistentEntityRegistry.register(GreetingEntity.class);
      }

    @Override
    public ServiceCall<NotUsed, String> handleGreetFrom(String user) {
        return request -> {
            PersistentEntityRef<GreetingCommand> ref
              = persistentEntityRegistry.refFor(
                GreetingEntity.class, user);
            CompletableFuture<String> greetingResponse
              = ref.ask(new ReceivedGreetingCommand(user))
                .toCompletableFuture();
            CompletableFuture<WeatherStats> todaysWeatherInfo
              = (CompletableFuture<WeatherStats>) weatherService
                .weatherStatsForToday().invoke();

            try {
                return CompletableFuture.completedFuture(
                  greetingResponse.get() + " Today's weather stats: "
                    + todaysWeatherInfo.get().getMessage());
            } catch (InterruptedException | ExecutionException e) {
                return CompletableFuture.completedFuture(
                  "Sorry Some Error at our end, working on it");
            }
        };
    }
}

Simply put, we inject the PersistentEntityRegistry and
WeatherService dependencies using @Inject (provided by Guice
framework), and we register the persistent GreetingEntity.

The handleGreetFrom() implementation is sending
ReceivedGreetingCommand to the GreetingEntity to process and return
greeting string asynchronously using CompletableFuture implementation
of CompletionStage API.

Similarly, we make an async call to Weather microservice to fetch
weather stats for today.

Finally, we concatenate both outputs and return the final result to the
user.

To register an implementation of the service descriptor interface
GreetingService with Lagom, let’s create GreetingServiceModule class
which extends AbstractModule and implements ServiceGuiceSupport:

public class GreetingServiceModule extends AbstractModule
  implements ServiceGuiceSupport {

      @Override
      protected void configure() {
          bindServices(
            serviceBinding(GreetingService.class, GreetingServiceImpl.class));
          bindClient(WeatherService.class);
    }
}

Also, Lagom internally uses the Play Framework. And so, we can add our
module to Play’s list of enabled modules in
src/main/resources/application.conf file:

play.modules.enabled
  += org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule

7.2. Weather Service

After looking at the GreetingServiceImpl, WeatherServiceImpl is
pretty much straightforward and self-explanatory:

public class WeatherServiceImpl implements WeatherService {

    @Override
    public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday() {
        return req ->
          CompletableFuture.completedFuture(WeatherStats.forToday());
    }
}

We follow the same steps as we did above for greeting module to register
the weather module with Lagom:

public class WeatherServiceModule
  extends AbstractModule
  implements ServiceGuiceSupport {

      @Override
      protected void configure() {
          bindServices(serviceBinding(
            WeatherService.class,
            WeatherServiceImpl.class));
      }
}

Also, register the weather module to Play’s framework list of enabled
modules:

play.modules.enabled
  += org.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule

*8. Running the Project

*

Lagom allows running any number of services together with a single
command
.

We can start our project by hitting the below command:

sbt lagom:runAll

This will start the embedded Service Locator, embedded Cassandra and
then start microservices in parallel. The same command also reloads our
individual microservice when the code changes so that we don’t have to
restart them manually
.

We can be focused on our logic and Lagom handle the compilation and
reloading. Once started successfully, we will see the following output:

................
[info] Cassandra server running at 127.0.0.1:4000
[info] Service locator is running at http://localhost:8000
[info] Service gateway is running at http://localhost:9000
[info] Service weather-impl listening for HTTP on 0:0:0:0:0:0:0:0:56231 and how the services interact via
[info] Service greeting-impl listening for HTTP on 0:0:0:0:0:0:0:0:49356
[info] (Services started, press enter to stop and go back to the console...)

Once started successfully we can make a curl request for greeting:

curl http://localhost:9000/api/greeting/Amit

We will see following output on the console:

Hello Amit! Today's weather stats: Going to Rain, Take Umbrella

Running the same curl request for an existing user will change the
greeting message:

Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella

*9. Conclusion

*

In this article, we have covered how to use Lagom framework to create
two micro services that interact asynchronously.

The complete source code and all code snippets for this article are
available in the
GitHub project
.

Leave a Reply

Your email address will not be published.