Introduction
Lagom is a framework for building reactive microservices in Java or Scala. It is designed to simplify the development of microservices by providing a set of tools and abstractions for building scalable and resilient systems. One of the strengths of Lagom is its integration with other popular technologies, such as Play Framework and Akka Actors. In this article, we will explore how to integrate Lagom with Play Framework and Akka Actors, taking advantage of the best of all three worlds.
Prerequisites
Before we dive into the integration, make sure you have the following prerequisites installed:
- Java JDK (8 or higher)
- SBT (Scala Build Tool)
- Lagom (latest version)
- Play Framework (latest version)
Creating a Lagom Service
Let’s start by creating a Lagom service. Lagom uses a code-first approach, where you define your service interface and implementation in code. Here’s a simple example of a Lagom service interface:
import com.lightbend.lagom.scaladsl.api._
import akka.NotUsed
trait HelloService extends Service {
def sayHello(): ServiceCall[NotUsed, String]
override def descriptor: Descriptor = {
named("hello")
.withCalls(
pathCall("/api/hello", sayHello _)
)
.withAutoAcl(true)
}
}
This service defines a single sayHello
method, which returns a ServiceCall
that takes no input and returns a string.
Implementing the Lagom Service
Next, let’s implement the HelloService
. This is where we can integrate with Play Framework and Akka Actors. For this example, we’ll create a simple Play Framework controller that interacts with an Akka Actor and returns a response. First, define the Play controller:
import javax.inject.Inject
import play.api.mvc._
class HelloController @Inject()(cc: ControllerComponents, helloService: HelloService) extends AbstractController(cc) {
def hello = Action.async {
helloService.sayHello().invoke().map { message =>
Ok(message)
}
}
}
In this controller, we inject the HelloService
, and the hello
action invokes the sayHello
method from the Lagom service.
Creating an Akka Actor
Now, let’s create an Akka Actor that the Lagom service will interact with. Define an Akka Actor as follows:
import akka.actor._
class HelloActor extends Actor {
def receive = {
case "hello" =>
sender() ! "Hello, Lagom!"
}
}
object HelloActor {
def props: Props = Props[HelloActor]
}
This simple actor responds with a greeting when it receives a “hello” message.
Wiring it All Together
To wire everything together, we need to configure our Lagom service to use Akka and Play. In the application.conf
file of your Lagom project, include the following configurations:
play.modules.enabled += "com.lightbend.lagom.scaladsl.playjson.LagomPlayJsonModule"
akka {
actor {
provider = "akka.cluster.ClusterActorRefProvider"
}
}
Here, we enable the LagomPlayJsonModule
to integrate Lagom’s JSON serialization with Play Framework. We also configure Akka to use the ClusterActorRefProvider.
Testing the Integration
Now that we have integrated Lagom with Play Framework and Akka Actors, it’s time to test our setup. Start your Lagom service and Play application, and then make a GET request to the /api/hello
endpoint.
You should receive a response like this:
{
"message": "Hello, Lagom!"
}
Congratulations! You’ve successfully integrated Lagom with Play Framework and Akka Actors.
Integrating Lagom with Play Framework and Akka Actors
In the previous sections, we successfully integrated Lagom with Play Framework and Akka Actors, demonstrating a simple example of how these technologies can work together. Now, let’s explore more advanced features and scenarios to deepen our understanding.
Leveraging Lagom’s Persistence API
Lagom provides a powerful persistence API for event sourcing and CQRS (Command Query Responsibility Segregation). You can easily integrate this API into your Lagom service, allowing you to store and retrieve data efficiently. To demonstrate this, let’s modify our HelloService
to use Lagom’s persistence API.
First, create a new entity for storing greetings:
import com.lightbend.lagom.scaladsl.persistence._
case class GreetingState(message: String)
object GreetingState {
implicit val format: Format[GreetingState] = Json.format
}
class GreetingEntity extends PersistentEntity {
override type Command = GreetingCommand
override type Event = GreetingEvent
override type State = GreetingState
override def initialState: GreetingState = GreetingState("")
override def behavior: Behavior = {
case GreetingCommand.UpdateMessage(message) =>
Effect.persist(GreetingEvent.MessageUpdated(message)).thenReply(_ => Done)
case GreetingCommand.GetMessage =>
Effect.none.thenReply(state => state.message)
}
}
In this code, we define a GreetingEntity
that handles commands to update and retrieve greeting messages. We also specify the events that can be emitted. Now, let’s update the HelloService
to interact with this entity:
trait HelloService extends Service {
def updateMessage(): ServiceCall[UpdateMessageRequest, Done]
def getMessage(): ServiceCall[NotUsed, String]
override def descriptor: Descriptor = {
named("hello")
.withCalls(
pathCall("/api/hello/update", updateMessage _),
pathCall("/api/hello", getMessage _)
)
.withAutoAcl(true)
}
}
With this change, our service now supports two new operations: updating the greeting message and retrieving the current message.
Implementing the Lagom Service with Persistence
Next, let’s implement the HelloService
with persistence:
class HelloServiceImpl(persistentEntityRegistry: PersistentEntityRegistry) extends HelloService {
override def updateMessage(): ServiceCall[UpdateMessageRequest, Done] = ServiceCall { request =>
val ref = persistentEntityRegistry.refFor[GreetingEntity](GreetingEntityId)
ref.ask(GreetingCommand.UpdateMessage(request.message))
}
override def getMessage(): ServiceCall[NotUsed, String] = ServiceCall { _ =>
val ref = persistentEntityRegistry.refFor[GreetingEntity](GreetingEntityId)
ref.ask(GreetingCommand.GetMessage)
}
}
Here, we inject the PersistentEntityRegistry
and use it to interact with the GreetingEntity
.
Enhancing the Play Controller
To complete our integration, we need to update the Play controller to call these new service methods:
class HelloController @Inject()(cc: ControllerComponents, helloService: HelloService) extends AbstractController(cc) {
def updateMessage = Action.async(parse.json[UpdateMessageRequest]) { request =>
helloService.updateMessage().invoke(request.body).map { _ =>
Ok("Message updated.")
}
}
def getMessage = Action.async {
helloService.getMessage().invoke().map { message =>
Ok(Json.obj("message" -> message))
}
}
}
In this controller, we have two new actions: updateMessage
to update the greeting message and getMessage
to retrieve the message. We use the invoke
method to call the Lagom service methods.
Testing the Updated Service
Now, you can test the updated service by making POST requests to /api/hello/update
to update the message and GET requests to /api/hello
to retrieve the message.
This advanced example demonstrates how to integrate Lagom, Play Framework, and Akka Actors while leveraging Lagom’s powerful persistence capabilities. It showcases a common scenario where event sourcing and CQRS can be beneficial in a microservices architecture.
Conclusion
Integrating Lagom with Play Framework and Akka Actors provides a robust foundation for building reactive microservices. We’ve covered the basics of integration and explored more advanced features, including Lagom’s persistence API. With these tools at your disposal, you can build scalable, resilient, and event-driven systems that meet the demands of modern application development.