The Lagom Framework for reactive microservices
Microservice architecture is amongst the latest in best practices in software architecture. Essentially, microservices are small, independent applications that provide REST APIs for communication. These applications are akin to the Unix philosophy of small programs: each program does one thing and does it well. Each application should work on its own and be independently deployable, thus making each microservice independently maintainable. One simple way to check if a service is a microservice is if it is easily described in a single, simple sentence.
So why would you want to use microservices? There are a few advantages to this style of architecture:
- Individual services are easier to maintain and deploy than a monolithic application. This makes the path towards continuous delivery much more attainable.
- Each service can be written in the most appropriate programming language for its domain along with its related libraries and frameworks.
- Services are far more modular and replaceable over time without requiring tremendous refactoring efforts.
- Services own their own data, thus not only allowing data stores to be changed more easily, but this also makes it easier to horizontally scale large amounts of data.
Along with this concept of microservices, there is also the trend of making applications reactive. Reactive applications are responsive, resilient, elastic, and message driven, the combination of which allows them to scale more easily and deal with the deluge of data generated by users and devices. An application that follows reactive principles tends to provide for a better user experience, especially as the application gains a critical number of concurrent users.
Combining these two concepts, we come to the latest in backend software engineering patterns to make reactive microservices. The Lagom framework is a framework that provides an opinionated starting point for implementing some of your reactive microservices. Lagom is built on top of various open source Lightbend products like Akka and Play, managed by a Guice container, along with great integration with Cassandra and Kafka. Lagom requires Java 8 and uses many of its language and library features.
Background
Before delving into specifics on Lagom, there are some things to note about how microservices should generally be architected regardless of framework. Lagom does indeed follow these particular ideas, but it is not the only framework out there that does.
As mentioned earlier, microservices should have a specific function to serve. For example, a blog microservice might be one that allows for creating, updating, and reading blog posts. A related comments microservice could manage the comments attached to blog posts (or anything else). A users microservice could manage user details which could be relevant to both the blog and comments microservices. Note, however, that microservices should not create hard dependencies between each other. Instead, microservices should be able to fall back to local data or default data when a related microservice is unable to provide its functionality. For example, if the users microservice is down, then a page displaying a blog post may fall back to only displaying usernames rather than the additional information the users service may provide such as an avatar, email address, real name, etc. If the comments service is down, a blog post can still be displayed without comments.
Another important architectural concern is that microservices should own their data. In that regard, each service should have its own database that only it owns. Other microservices should use the public API of the owner service in order to access that data. Services are completely allowed to cache or store data from other microservices for various reasons. While this does lead to duplication of data and goes against all normalization practices common to RDBMSs, the duplication of data in microservices is done in order to provide faster services and fallbacks when microservices may be down or otherwise unavailable.
Note that not all microservices need to be stateful. For example, while refactoring a large monolithic application into a microservice architecture, the first steps tend to be creating small, stateless microservice APIs such as for sending email, pushing notifications, etc. In general, though, an application should first be refactored to separate the front end user interface from the back end API before said API could ever be split into proper reactive microservices.
When it comes to implementing reactive microservices, following event sourcing and messaging patterns can help meet a lot of the technical challenges in following the reactive manifesto. In that regard, it is typical to use far more asynchronous communication and APIs. For example, instead of using typical SQL RDBMSs, NoSQL databases tend to be used far more often. Asynchronous communication between services can use messaging systems like Kafka or ActiveMQ. These types of systems also tend to be far more scalable and resilient, thus helping maintain reactivity. However, these types of systems do come at the cost of eventual consistency, the concept that your data will not maintain transactional consistency, but it will eventually be up to date as the events from your system propogate. As we’ll see later, Lagom does not hide this fact from the developer and instead embraces the CQRS paradigm.
Lagom
Lagom is a Java framework (with future support for Scala) that provides an opinionated set of libraries for implementing reactive microservices. Note that although Lagom is a rather interesting framework to use for implementing these services, it should not be the only tool in your box. Lagom works best when combined with microservices written in other languages and frameworks.
Microservices written in Lagom are exposed over HTTP as either REST APIs for synchronous request/reply services, or as WebSocket APIs for asynchronous or streaming services. Data is encoded in standard JSON. Using these web standards makes integration with other microservices much easier.
One important concept to follow when using Lagom is immutability. The standard JDK provides a lot of mutable classes and concepts, so third party libraries are useful here. For example, the recommended library for immutable collections is PCollections, although Guava also provides implementations of immutable collections as well. Immutable data structures can be written by hand (or more likely generated by an IDE), but using an annotation processor to generate that code such as Immutables or Lombok make this much easier. The use of immutable classes is pervasive throughout Lagom to the point where mutable classes can potentially cause fun to debug concurrency bugs.
Usage
Lagom sounds pretty cool, but what does it look like? You can follow along with sample code. What we’ll cover in this overview is a very simple blog microservice. First, we’ll create a service API and then the implementation. Note that these concerns should be separated so that other microservices can use Lagom as a client as well. In order to run the code from these examples, you’ll need to follow the installation guide.
Note that in this project, we will use Lombok instead of Immutables due to incomplete IntelliJ support. Immutables works without any need for workarounds in Eclipse, but there are workarounds required for IntelliJ that make using it inconvenient currently.
Blog API
In our API, we have two main things to provide that can be used by both clients and servers: the service interface and the data model. Our data model will be a simple blog post, and our service will provide APIs to create, update, and read these posts.
@Immutable
@JsonDeserialize
@Value
@Builder
@Wither
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
public final class PostContent {
@NonNull
String title;
@NonNull
String body;
@NonNull
String author;
}
Wow, there are literally more lines of annotations than code here! Let’s go over them all.
@Immutable
is from thejavax.annotation.concurrent
package which is a sort of marker annotation that indicates to developers that the annotated class is completely immutable and threadsafe. To that regard, all publicly accessible data in that class should also be immutable.@JsonDeserialize
is a Jackson annotation that indicates that the annotated class should be used for deserialized JSON data. This can be used to configure some details of that deserialization, but in our case, the default settings are fine.- The next four annotations all come from Lombok. Note that these annotations
are similar to the functionality provided by the Immutables
@Value.Immutable
annotation used in the official Lagom documentation.@Value
makes all fieldsprivate final
, generates getter methods for them, generates standardequals()
,hashCode()
, andtoString()
methods, and generates a constructor taking all fields as arguments. Essentially, this annotation makes creating an immutable value class far less verbose.@Builder
generates a builder class and method for constructing an object through the builder pattern. In this example, this generates a method calledbuilder()
which returns a newPostContentBuilder
, and that class provides methods such astitle()
,body()
, andauthor()
to set these values, and abuild()
method to construct aPostContent
object. For classes with only one or two fields, including a builder is not very useful, but for classes with many fields, Java’s lack of named parameters in constructors makes the builder pattern the only way to readably construct an immutable object.@Wither
is an experimental feature of Lombok right now, but it generateswithFoo()
methods for each field. These methods construct a copy of the current object but with the specified field modified. This feature allows for more efficient “modification” of immutable objects rather than having to manually copy the objects by hand.@AllArgsConstructor
does what it sounds like: it generates a constructor that contains all the fields of the class as parameters. In this case, this constructor replaces the one that would’ve been generated by the@Value
annotation. The reason we use this constructor is in order to decorate that constructor with the@JsonCreator
annotation. This annotation is another Jackson annotation that indicates how to construct new instances of this class. By default, Jackson likes to use JavaBean style classes, but JavaBeans normally follow a mutable pattern, so we specify here our desired behavior.
- Finally, the
@NonNull
annotation on the fields is another Lombok feature that generates null checks in generated constructors and setters for that field. This annotation can also be used in a parameter to a constructor or method and Lombok will generate the null check as well. This null check is implemented as throwing aNullPointerException
if the value is null which is the pattern established by the JDK. One neat aspect of using this annotation is that it can also be configured in your IDE or static code checks as a non-null annotation that can aid in development.
That’s a lot of functionality! Thankfully, we can rely on these annotation processors to provide a lot of otherwise tedious functionality that we’ll come to rely on later when manipulating our data.
Our other class we need to specify is our service contract.
public interface BlogService extends Service {
ServiceCall<NotUsed, Optional<PostContent>> getPost(String id);
ServiceCall<PostContent, String> addPost();
ServiceCall<PostContent, Done> updatePost(String id);
@Override
default Descriptor descriptor() {
return named("blog").withCalls(
restCall(Method.GET, "/api/blog/:id", this::getPost),
restCall(Method.POST, "/api/blog/", this::addPost),
restCall(Method.PUT, "/api/blog/:id", this::updatePost)
).withAutoAcl(true);
}
}
First we note that all service APIs are simply interfaces that inherit from the
Service
interface. Next, we note that service calls are all specified by
abstract methods that return a ServiceCall<Request, Response>
object.
There is also a descriptor()
method that returns metadata about the REST API
that these methods implement.
What is this ServiceCall
class? It’s a functional interface that takes a
Request
object and returns a CompleteableFuture<Response>
object. Note that
Request
and Response
are generic parameters. There are two standard classes
used above: NotUsed
and Done
, both of which are from the Akka API. The
NotUsed
class is similar to Void
in the sense that it’s to be ignored. The
Done
class is essentially an “OK” response.
Thus, our API will implement three REST APIs: get a post, add a post, and update a post. Simple, right?
Blog Implementation
Now we delve into the actual implementation of this API. In this module, we’ll
not only be implementing the above BlogService
interface, but we’ll also be
providing persistent entity classes for storing and retrieving blog posts.
Let’s start with the entity itself.
@SuppressWarnings("unchecked")
public class BlogEntity
extends PersistentEntity<BlogCommand, BlogEvent, BlogState> {
@Override
public Behavior initialBehavior(final Optional<BlogState> snapshotState) {
final BehaviorBuilder b =
newBehaviorBuilder(snapshotState.orElse(BlogState.EMPTY));
addBehaviorForGetPost(b);
addBehaviorForAddPost(b);
addBehaviorForUpdatePost(b);
return b.build();
}
private void addBehaviorForGetPost(final BehaviorBuilder b) {
b.setReadOnlyCommandHandler(BlogCommand.GetPost.class,
(cmd, ctx) -> ctx.reply(state().getContent()));
}
private void addBehaviorForAddPost(final BehaviorBuilder b) {
b.setCommandHandler(BlogCommand.AddPost.class,
(cmd, ctx) -> ctx.thenPersist(
new BlogEvent.PostAdded(entityId(), cmd.getContent()),
evt -> ctx.reply(entityId())
)
);
b.setEventHandler(BlogEvent.PostAdded.class,
evt -> new BlogState(Optional.of(evt.getContent()))
);
}
private void addBehaviorForUpdatePost(final BehaviorBuilder b) {
b.setCommandHandler(BlogCommand.UpdatePost.class,
(cmd, ctx) -> ctx.thenPersist(
new BlogEvent.PostUpdated(entityId(), cmd.getContent()),
evt -> ctx.reply(Done.getInstance())
)
);
b.setEventHandler(BlogEvent.PostUpdated.class,
evt -> new BlogState(Optional.of(evt.getContent()))
);
}
}
Look at all the lambdas! Let’s break this down. A persistent entity in Lagom is the root class for manipulating data. The default implementation of this is via Cassandra, but there is also support for a few JDBC data stores such as Postgres, MySQL, and Oracle. In this API, we use what is known as event sourcing or CQRS as the pattern for reading and writing data.
A persistent entity relates to three classes: commands, events, and state. The command classes are used to provide a command to the entity to do something. This can be something like reading or writing the data of that entity. In our entity class, we specify how to interpret these commands via command handlers. These commands generally create events from the data provided in the command which can be persisted in the event log. An event log is a sort of write-only database structure where data updates are simply saved as new events, and the current state of a particular piece of data can be constructed by combining all the events that affected that state. Events are handled by event handlers which tend to handle the state updates. Finally, state objects are used to store the current accumulated state of an entity.
An entity begins its life with an optional snapshot state which is saved from time to time over the life of an entity. From this possible snapshot state, the entity describes its behavior which is essentially a set of command and event handlers for that entity. Using this snapshot, the initial behavior of the entity is specified, and this behavior can be changed at any time in response to an event. This allows for implementation of finite state machines as well which is pretty neat.
Delving into more detail in the types of behavior we’re giving this entity, the first one is a read-only command handler used for fetching posts. This command is simple to implement by replying with the current state of the entity.
Next, we implement a command handler for adding posts. When this command is
received, we chain a persist call to save a PostAdded
event to our event
log. That is also chained with returning the entity ID from the event. We
also add an event handler to update the current state when receiving a
PostAdded
event.
Finally, we implement a command handler for updating posts. This works almost
exactly like creating posts, but instead we respond with Done
rather than
the entity ID. Our event handler also works very similarly to the post added
event handler.
We could change the behavior of the entity after creating or updating posts (for instance, adding restrictions on editting certain attributes of the post).
So far, we know that commands are used to manipulate data, and events are used to react to that data by updating its state and optionally modifying its behavior. Let’s take a look at our commands.
public interface BlogCommand extends Jsonable {
enum GetPost implements BlogCommand,
PersistentEntity.ReplyType<Optional<PostContent>> {
INSTANCE
}
@Immutable
@JsonDeserialize
@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
final class AddPost implements BlogCommand, CompressedJsonable,
PersistentEntity.ReplyType<String> {
@NonNull
PostContent content;
}
@Immutable
@JsonDeserialize
@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
final class UpdatePost implements BlogCommand, CompressedJsonable,
PersistentEntity.ReplyType<Done> {
@NonNull
PostContent content;
}
}
First we’ll note the Jsonable
interface. This is a marker interface to Lagom
that indicates that the class should be serializable via JSON. There is also
the CompressedJsonable
interface which further specifies that if the JSON
data exceeds a certain size threshold that it can be compressed.
Next, note that the three commands here all implement the BlogCommand
interface
which makes them available as command types in our BlogEntity
class. Our
commands are similar to the PostContent
class in our annotation usage as they
are also value objects.
The other important interface that all these commands implement is the
ReplyType<T>
interface which specifies the reply type returned by executing
this command. In the first command, GetPost
returns an Optional<PostContent>
,
showing that it is possible to attempt to get a nonexistent post. This class is
implemented as an enum because it has no state, and enums provide a very
convenient way to implement a proper singleton object in Java. The second
command, AddPost
, returns a String
which in our implementation will be the
ID of the newly created entity so that it can be retrieved afterwards. The
third command, UpdatePost
, replies with a Done
to indicate that the operation
succeeded. All our commands revolve around the PostContent
model, but this
could be anything really.
Next, let’s take a look at our events.
public interface BlogEvent extends Jsonable, AggregateEvent<BlogEvent> {
@Override
default AggregateEventTagger<BlogEvent> aggregateTag() {
return AggregateEventTag.of(BlogEvent.class);
}
@Immutable
@JsonDeserialize
@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
final class PostAdded implements BlogEvent, CompressedJsonable {
@NonNull
String id;
@NonNull
PostContent content;
}
@Immutable
@JsonDeserialize
@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
final class PostUpdated implements BlogEvent, CompressedJsonable {
@NonNull
String id;
@NonNull
PostContent content;
}
}
Our first interface of note is AggregateEvent<E>
which is used for sharding
entities across database instances. In our case, we use the name of the class
as our sharding key which would not be very efficient in a high throughput
production scenario, but it is pretty simple. A more realistic aggregate
event tag would probably include the author information as blog posts from a
specific author may appear together.
All our event classes implement BlogEvent
so that they can be used in our
BlogEntity
class as events. Other than the aggregate event tagging method,
events are pretty straightforward data objects. The importance of events is
found in the functions that response to the events.
Now we look at our state class.
@Immutable
@JsonDeserialize
@Value
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
public class BlogState implements CompressedJsonable {
public static final BlogState EMPTY = new BlogState(Optional.empty());
Optional<PostContent> content;
}
Not much to this class. We use an Optional<PostContent>
because the initial
state of an entity contains no post data, and abusing null
here to mean
“empty” is a bit of a code smell to me.
Let’s put it all together now and implement the BlogService
interface.
public class BlogServiceImpl implements BlogService {
private final PersistentEntityRegistry registry;
@Inject
public BlogServiceImpl(final PersistentEntityRegistry registry) {
this.registry = registry;
registry.register(BlogEntity.class);
}
@Override
public ServiceCall<NotUsed, Optional<PostContent>> getPost(final String id) {
return request -> registry.refFor(BlogEntity.class, id)
.ask(BlogCommand.GetPost.INSTANCE);
}
@Override
public ServiceCall<PostContent, String> addPost() {
return content -> registry.refFor(BlogEntity.class, UUID.randomUUID().toString())
.ask(new BlogCommand.AddPost(content));
}
@Override
public ServiceCall<PostContent, Done> updatePost(final String id) {
return content -> registry.refFor(BlogEntity.class, id)
.ask(new BlogCommand.UpdatePost(content));
}
}
Our main point of contact with our entity class will be through the
PersistentEntityRegistry
which is injected via Guice. We need to
register our entity class before being able to use the entities. The service
calls are all relatively simple now that we’ve abstracted out our data
access through the persistent entity API. Essentially, we can do simple
lookups of entities by ID, then we “ask” it a command.
The only remaining class in this module is the class used for configuring Guice.
public class BlogModule extends AbstractModule implements ServiceGuiceSupport {
@Override
protected void configure() {
bindServices(serviceBinding(BlogService.class, BlogServiceImpl.class));
}
}
This adds our BlogServiceImpl
class as an implementation of BlogService
which
allows it to be injected into other classes as well as makes itself manageable
by Guice.
There are also various configuration files included in the project that are available on GitHub in order to get a complete project.
Writing Tests
Entities are complex sets of classes, and Lagom provides a test class used for
simulating in-memory entities that can be used in unit tests. Let’s write some
tests for various scenarios involving our BlogEntity
example.
public class BlogEntityTest {
private static ActorSystem system;
@BeforeClass
public static void beforeClass() {
system = ActorSystem.create("BlogEntityTest");
}
@AfterClass
public static void afterClass() {
JavaTestKit.shutdownActorSystem(system);
system = null;
}
@Rule
public TestName testName = new TestName();
private PersistentEntityTestDriver<BlogCommand, BlogEvent, BlogState> driver;
@Before
public void setUp() throws Exception {
// given a default BlogEntity
driver = new PersistentEntityTestDriver<>(
system, new BlogEntity(), testName.getMethodName());
}
@Test
public void initialStateShouldBeEmpty() throws Exception {
// when we send a GetPost command
final Outcome<BlogEvent, BlogState> getPostOutcome =
driver.run(GetPost.INSTANCE);
// then no events should have been created
assertThat(getPostOutcome.events()).isEmpty();
// and the state should still be empty
assertThat(getPostOutcome.state().getContent()).isNotPresent();
// and we should get back an empty Optional to indicate that
// no post was found
final Optional<PostContent> actual = getFirstReply(getPostOutcome);
assertThat(actual).isNotPresent();
}
@Test
public void addPost() throws Exception {
// given entity ID of test name
final String expectedEntityId = testName.getMethodName();
// when we send an AddPost command
final Outcome<BlogEvent, BlogState> addPostOutcome =
driver.run(new AddPost(newPostContent()));
// then a PostAdded event should be persisted
final List<BlogEvent> events = addPostOutcome.events();
assertThat(events).containsExactly(
new PostAdded(expectedEntityId, newPostContent()));
// and the state should contain that post content
assertThat(addPostOutcome.state().getContent()).hasValue(
newPostContent());
// and the reply should give us the entity ID
final String entityId = getFirstReply(addPostOutcome);
assertThat(entityId).isEqualTo(expectedEntityId);
// when we send a subsequent GetPost command
final Outcome<BlogEvent, BlogState> getPostOutcome =
driver.run(GetPost.INSTANCE);
// then the reply should be our post content we added earlier
final Optional<PostContent> content = getFirstReply(getPostOutcome);
assertThat(content).hasValue(newPostContent());
}
@Test
public void updatePost() throws Exception {
// given entity ID of test name
final String expectedEntityId = testName.getMethodName();
// when we send an UpdatePost command
final Outcome<BlogEvent, BlogState> updatePostOutcome =
driver.run(new UpdatePost(newPostContent()));
// then a PostUpdated event should be persisted
final List<BlogEvent> events = updatePostOutcome.events();
assertThat(events).containsExactly(
new PostUpdated(expectedEntityId, newPostContent()));
// and the state should contain the post content
assertThat(updatePostOutcome.state().getContent()).hasValue(
newPostContent());
// and the reply should be Done
final Done reply = getFirstReply(updatePostOutcome);
assertThat(reply).isEqualTo(Done.getInstance());
}
@SuppressWarnings("unchecked")
private static <T> T getFirstReply(final Outcome<?, ?> outcome) {
return (T) outcome.getReplies().get(0);
}
private static PostContent newPostContent() {
return new PostContent("z", "y", "x");
}
}
The ActorSystem
is essentially the entry point class to Akka which is how a
lot of Lagom is built. The next key class here is the
PersistentEntityTestDriver
which allows you to simulate sending commands to
an entity and inspecting the results of sending that command. The only other
possibly strange thing here is the assertThat()
method used here; this is from
the fantastic AssertJ testing library.
As for writing service tests, Lagom’s test kit provides a way to run an embedded
server that contains Cassandra, Kafka, and any other dependent applications of
your service. These tests can interact directly with the service API and perform
tests on the results. For a more complete example, see
BlogServiceTest
.
Finally, integration tests between multiple microservices can be implemented by
setting up both an ActorSystem
and a LagomClientFactory
to run all your
microservices together. Streaming microservices requires also setting up a
Materializer
instance from the Akka streams API. Included in the GitHub
project are the sample integration tests from the Lagom Maven
archetype sample project.