Integrating Lagom with Play Framework and Akka Actors

Table of Contents

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.

Command PATH Security in Go

Command PATH Security in Go

In the realm of software development, security is paramount. Whether you’re building a small utility or a large-scale application, ensuring that your code is robust

Read More »
Undefined vs Null in JavaScript

Undefined vs Null in JavaScript

JavaScript, as a dynamically-typed language, provides two distinct primitive values to represent the absence of a meaningful value: undefined and null. Although they might seem

Read More »