Lagom is an open source highly opinionated framework for building systems of reactive microservices in Java and Scala. It is a n open-source framework maintained by Lightbend. It has emerged as a powerful tool facilitating developers with a streamlined approach to building flexible, resilient and responsive systems.
Lagom supports multiple aspects from development to deployment by leveraging other reactive frameworks like Play and Akka. The Lagom integrated development environment allows us to focus on solving the business problem. With a single command, the project is built, initiating the supporting components, microservices, and Lagom infrastructure. The build automatically reloads when it identifies changes in the source code.
Right-sized services created with Lagom enable:
clearly defined development responsibilities—to increase agility
more frequent releases with less risk—to improve time to market
systems with reactive qualities: responsiveness, resilience, scalability, and elasticity—to make best use of modern computing environments and meet demanding user expectations.
Microservices are designed to be isolated and autonomous with loosely coupled communication between them. This facilitates synchronous and asynchronous communication through HTTP. It also offers message-based communication through brokers like kafka. Microservices follow the principles of Bounded Context that is, they own their data and have direct control over them, which facilitates data persistence through Event Sourcing and CQRS. Lagom persisted the event stream in the database through asynchronous APIs, Cassandra is the default database in Lagom.
For services to communicate with each other, Lagom’s development environment is embedded with a service locator. This allows services to get discovered and to communicate with each other. To allow external clients to connect to the lagom services, the service gateway is embedded as well.
Understanding Lagom with Code
To understand Lagom with either Play or Akka, we will take an example of the microservice that is mentioned in the official documentation of Lagom. Lagom offers Java or Scala for APIs and Marven or sbt. Here in the example Scala with sbt is used.
Prerequisite for Lagom is
Java Development Kit
Installing SBT
Proxy setup
To set up -
Simply define a microservice without any persistence. Here starter tools provided by Lagom are used. Define the project structure and let the sbt generate the bootstrap.
organization in ThisBuild := "com.baeldung"
version in ThisBuild := "1.0-SNAPSHOT"
scalaVersion in ThisBuild := "2.13.0"
val macwire = "com.softwaremill.macwire" %% "macros" % "2.3.3" % "provided"
val scalaTest = "org.scalatest" %% "scalatest" % "3.1.1" % Test
lazy val `hello` = (project in file(".")).aggregate(`hello-api`, `hello-impl`)
lazy val `hello-api` = (project in file("hello-api"))
.settings(
libraryDependencies ++= Seq(
lagomScaladslApi
)
)
lazy val `hello-impl` = (project in file("hello-impl"))
.enablePlugins(LagomScala)
.settings(
libraryDependencies ++= Seq(
lagomScaladslTestKit,
macwire,
scalaTest
)
)
.settings(lagomForkedTestSettings)
.dependsOn(`hello-api`)
In lagom we have defined separate projects for service interface(i.e “hello-api”) and implementation(i.e “hello-impl”). The service implementation depends on the service interface.
Defining Messages
Define the messages that the services will consume, for that use either implicit or custom message serializer, that lagom used to serialize and deserialize request and response messages.
Defining the messages:
case class Job(jobId: String, task: String, payload: String)
object Job {
implicit val format: Format[Job] = Json.format
}
case class JobAccepted(jobId: String)
object JobAccepted {
implicit val format: Format[JobAccepted] = Json.format
}
The above defined messages are two case classes - Job and JobStatus and their companion objects. The implicit JSON serialization is added (by default Lagom uses Play JSON). The messages represent the request and the response for the service.
Defining the Service
Lagom splits a microservice into a service interface and its implementation.
Defining the service interface:
trait HelloService extends Service {
def submit(): ServiceCall[Job, JobAccepted]
override final def descriptor: Descriptor = {
import Service._
named("hello")
.withCalls(
pathCall("/api/submit", submit _)
).withAutoAcl(true)
}
}
The service descriptor defines how to implement and invoke a service.
Understanding the above code snippet -
The declined call “/api/submit’ maps to the “submit” function
The function returns a handle to a ServiceCall that takes parameters Job and JobAccepted.
These parameters are message types which can be strict or streamed
This handle can be used to invoke the call to execute the work
Here path based identifiers are used for path and query string to route the calls.
Implementing the Service :
It includes an implementation for each call specified by the descriptor:
class HelloServiceImpl()(implicit ec: ExecutionContext)
extends HelloService {
override def submit(): ServiceCall[Job, JobAccepted] = ServiceCall {
job =>
Future[String] {JobAccepted(job.jobId)}
}
}
This is a fundamental implementation for the function that is defined as a service descriptor.
Understanding the above code snippet :
The method does not execute the call directly instead it returns the call to be executed as a lambda
This offers a convenient way to compose such calls in a function-based composition
The call itself does not return a value immediately but a Future which is a promise
This gives us a powerful way to develop asynchronous and non-blocking, reactive applications
Creating a Lagom Application
To bring the services and their implementations together in an application, Lagom uses compile-time dependency injection to wire together this Lagom application. Lagom prefers Macwire, which gives lightweight macros that locate dependencies for the components.
To create the Lagom Application:
abstract class HelloApplication(context: LagomApplicationContext)
extends LagomApplication(context)
with AhcWSComponents {
override lazy val lagomServer: LagomServer =
serverFor[HelloService](wire[HelloServiceImpl])
}
Understanding the above code snippet :
HelloApplication gets integrated with AhcWSComponents through Macwire
Also, we can integrate various other components from Scala or third parties
We implement the method - lagomServer which Lagom utilizes to identify the service bindings
Also Macwire’s wire macro can be used to inject other dependencies into HelloServiceImpl
This class remains abstract as it requires the implementation of the method serviceLocator
Finally, write an application loader such that the application can bootstrap itself. This can be done conveniently in Lagom by extending the LagomApplicationLoader:
class HelloLoader extends LagomApplicationLoader {
override def load(context: LagomApplicationContext): LagomApplication =
new HelloApplication(context) {
override def serviceLocator: ServiceLocator = NoServiceLocator
}
override def loadDevMode(context: LagomApplicationContext): LagomApplication =
new HelloApplication(context) with LagomDevModeComponents
override def describeService = Some(readDescriptor[HelloService])
}
Understanding the above code snippet :
The code implements two methods- load and loadDevMode
In the load method the appropriate serviceLocator is mixed with the HelloApplication
The describeService method aids in configure components like service gateways (this is optional)
Configurations
Lagom services offers configuration options through values specified in the file- application.conf.
However, for this example, the only thing that requires configure is our application loader:
play.application.loader = com.baeldung.hello.impl.HelloLoader
Running the Example
With all the required steps completed to create a simple working application in Lagom, the next step is to run the application.
To run the entire Lagom application use a command prompt and sbt tool.
sbt runAll
Once the server bootstraps successfully, we should be able to post our jobs to this service using a tool like postman with POST method:
http://localhost:9000/api/submit
{
"jobId":"jobId",
"task":"task",
"Payload":"payload"
}
If you are looking to get support with Lagom, Please get in touch with hello@fusionpact.com
Comments