Hands-on with Akka Serverless – Part 1

In our previous blog-post, we wrote about the launch of Akka-serverless, a stateful serverless framework, and why it is an important evolution.

But what does it mean to actually use it? Let’s get our hands dirty and find out.

As an exercise, let’s build a stateful reactive application with AkkaServerless, which will allow us to have a view on the climate change of our planet. We’ll keep the application simple enough so that we don’t lose focus on the goal of this exercise (checking out the potential of Akka-Serverless), but not too simple so that it is representative for a real-life application.

Requirements of our reactive application

Imagine that every weather station on the planet is connected to the internet and as such, is able to send data about the weather to the cloud.

We want to build a cloud-application that does exactly that: gathering the data of those IOT-weather stations and processing it in a way that allows for flexible reporting on that data. Some metrics we want to follow are:

    • average temperature per weather station
    • global extremes: maximum and minimum ever recorded
    • average temperatures per region (let’s say, per country)

These are just a few examples. What we really want is to be able to slice-and-dice the raw data however we want.

We also want to make sure that our service is elastic: we don’t want the application to become unresponsive when (hundreds of) thousands of weather stations are added to the system and all of them start reporting measurements. At the same time, if no weather stations are sending data, we don’t want our service to burn more resources than necessary: we don’t want our application to heat up the planet even more. That wouldn’t be right.

Another important requirement is that the processing of the data (sent by weather stations) cannot be disrupted if the application is generating reports. In addition, the generation of the reports should be resilient for failure in any other part of the system. In other words, The separate parts should be nicely isolated from each other.

Overview

In this blog, we’ll explore how we could leverage Akka Serverless to:

    • A. Design the system
    • B. Develop the individual components
    • C. Deploy the full application to the cloud

A. Designing a stateful serverless application

Akka-Serverless is a framework for building stateful reactive applications. The goal of such applications is to be highly elastic and resilient. The framework is designed around 3 types of components. Those components can be mapped to the basics of Command Query Responsibility Segregation (CQRS). In a previous post, we zoomed in on CQRS. For even more info on that: this free course is a great introduction to the concept.

Every type of component in Akka-Serverless has a well-defined purpose. A component is either an Entity, Action or View:

    • The Entities contain state and provide a way to deal with state-changes in a elastic and resilient way.
    • Actions are stateless and are the ‘glue’ between different components.
    • Views are the ‘Query’ side of CQRS: They expose the internal state of the application, typically in a format that is optimized for querying.

We can combine the 3 types of components into a small system:

    • WeatherStationEntity represents a single weather station. This is the core of the application and it contains all the things related to the management of the state of a weather station. There can be thousands of instances of those in our system: one for every physical weather station.
    • API-action: this is a stateless action that implements the public REST-API. It converts inbound http-requests into commands and forwards them to the appropriate entity (the one that represents the physical weather station that sent the request). Since it is a stateless service, there can be multiple instances of it.
    • AverageView: This view consumes the events that are generated by the WeatherStationEntity and projects them into a database exactly as we want to read them: averages per weather station.

This design with only 3 components is still relatively simple. Nevertheless, it already has a number of powerful features:

    • Separation of concerns: the (public) REST-API is decoupled from the (internal) API of our entity: our internal entity can evolve without impacting our ‘clients’. This in important because in this example the clients are scattered across the globe. Making changes to our public API is to be avoided as much as possible.
    • Isolation: the read-side (view) is hosted from a dedicated database, which is not directly impacted by anything that can happen on the write-side (ie. excessive load or failure). The inverse is true as well. This makes the system elastic (different components can be scaled independently) and resilient (the read-side can work even if the rest of the system has crashed, and the other way round).

As you understand, it already is a full-fledged stateful reactive application. Even though this does not yet satisfy all the requirements, it allows to further build on.

B. Developing a stateful serverless application

For each of the components in the framework, the workflow is the same:

    1. describe the component in a language agnostic file (protobuf)
    2. the Akka-Serverless sdk generates boilerplate code and placeholders, for which only the business-logic needs to be filled in.
    3. integrating the components in the application is done in a type-safe way. It means that whenever something relevant changes in the definition of a component (step 1), the compiler will highlight which parts of the code need to be updated accordingly.

If the protobuf-files describe the (yellow, green and blue) boxes from our diagram, then the code-snippet below is making sure that the arrows between the boxes are correctly implemented, with respect to the interfaces and types that were declared in the protobuf-files. This is a big help during development: the compiler actually tells the developer what needs to be changed in order to comply to the defined interfaces.

public static AkkaServerless createAkkaServerless() {
    return AkkaServerlessFactory.withComponents( 
            WeatherStation::new,
            WeatherStationApiAction::new,
            WeatherStationOverallAverageView::new);
}
public static void main(String[] args) throws Exception {
        LOG.info("starting the Akka Serverless service");
        createAkkaServerless().start().toCompletableFuture().get();
}

Main.java

The above piece of code shows how the 3 components of our application are combined. Let’s take a closer look at how they are created.

B.1 Creating an EventSourced entity

Let’s start with the core of the application: the WeatherStationEntity. This entity is responsible for dealing with the reported measurements. It is basically a virtual representation of a physical weather station (think of it as a sort-of digital twin). We are using an event-sourced entity, which doesn’t actually store the current state of an entity, but rather the changes to the state, in the form of events. The current state is derived from those stored events (cfr. EventSourcing) and kept in memory.

The entities contain the domain-logic to manage the state of the application. This example is fairly simple, so there isn’t all that much going on. The only state that is maintained for a weather station is its id, name and location.There are 2 parts to it: The service-description and the implementation. First the service description:

Entity Description

service WeatherStationEntityService {
    option (akkaserverless.service) = {
        type : SERVICE_TYPE_ENTITY
        component : "be.reaktika.weatherstation.domain.WeatherStation"
    };
    rpc RegisterStation(StationRegistrationCommand) returns (google.protobuf.Empty);
    rpc PublishTemperatureReport(StationTemperatureCommand) returns (google.protobuf.Empty);
}

weatherstation_domain.proto

This file describes the model (of the state), 2 commands, 2 events and a service with 2 methods:

    • StationRegistrationCommand and StationTemperatureCommand. The processing of those commands result in 2 types of events, which are being stored in the event-journal of this entity.
    • The events StationRegistered and TemperaturesCelciusAdded. They represent the state-changes of the entity: the facts that a station was registered, and that temperature-measurements for a station were recorded, respectively.
    • The methods RegisterStation() and PublishTemperatureReport(): ‘external’ clients can use this component by means of these 2 methods. In our example, it will be our API-Action that will invoke this entity based on inbound requests.
    • The annotation type : SERVICE_TYPE_ENTITY makes that the akka-serverless SDK will generate placeholder code for which we only need to fill in the blanks: the actual logic of the entity

Entity Implementation

As the name (EventSourcedEntity) suggests, we are using EventSourcing here. It means the internal state is not stored. What happens instead is that all state-manipulations (in the form of events) are stored . The state of the entity at any point in time, is the cumulative result of all events that ‘have happened’ to the entity (until that point in time).

For those readers who are new to event-sourcing, this might feel a bit counter-intuitive. But in reality, it makes perfect sense. For example, think about your bank account: The balance of it is the result of all the debit and credit transactions that were done on it. This exact same concept is used on the state of an event-sourced entity. It is done by implementing command-handlers (which generate events), and event-handlers (which apply events to the current state).

This is how:
When compiling the project with the entity-definition from above, the SDK generates a stub-implementation for the entity. The only thing we need to do here is implement the command handling and event handling in the stub methods generated by the SDK.

Handling commands

Handling a command, in a event-sourced entity, results in an event. The command can be validated and checked for any kind of domain-constraints based on the current state. The current state is injected into the CommandHandler method, together with the command. If the command doesn’t make sense, the processing can fail by returning an error. A command that does make sense results in generating and emitting an event.

This is how we deal with StationRegistrationCommand in the entity:

@Override
public Effect<Empty> registerStation(WeatherStationState currentState, StationRegistrationCommand command) {
    if (Math.abs(command.getLatitude()) > MAX_LAT_ABS || Math.abs(command.getLongitude()) > MAX_LON_ABS){
      return effects().error(String.format("latitude or longitude are invalid: %f, %f",command.getLongitude(), command.getLongitude()));
    }
    var event = StationRegistered.newBuilder()
            .setStationName(command.getStationName())
            .setStationId(command.getStationId())
            .setLongitude(command.getLongitude())
            .setLatitude(command.getLatitude()).build();
    return effects()
            .emitEvent(event)
            .thenReply(newState -> Empty.getDefaultInstance());
}

WeatherStation.java

The generated event represents the fact that a business-action has happened. Once it is stored, it is immutable and undisputable. It is a fact. As you can see, every generated event-class (ie. StationRegistered) comes with a nice builder, which helps with assuring that messages are immutable.

Handling events

Every generated event is stored in the event-store and represents a fact that has happened. Only events that have succesfully been stored, will be processed by the EventHandler. An eventhandler accepts an event and applies it to the internal state. This results in the new state. For example, if a weather station was registered (cfr. the receiving of a StationRegistered event), the internal state of that weather station will be updated to reflect that.

@Override
public WeatherStationState stationRegistered(WeatherStationState currentState, StationRegistered event) {
  var newState = WeatherStationState.newBuilder(currentState)
          .setStationName(event.getStationName())
          .setLatitude(event.getLatitude())
          .setLongitude(event.getLongitude());
  return newState.build();
}

Putting the state in stateful serverless

It might have gone unnoticed, but the method-signature of the command-handlers and event-handlers show what Akka-Serverless is all about: The business-logic of an entity is implemented in a method that is injected with the current state, together with the command/event that needs to be processed.

This seemingly small detail is the essence of the framework: as a developer you don’t have to care where this state is coming from. You only need to worry about writing the correct logic to apply the latest event to the current state, which results in the new state (where the next event will be applied to). People who have worked with other serverless applications that have to deal with state, must surely appreciate this. In a ‘normal’ (stateless) serverless solution, dealing with state is not trivial at all. You are confronted with:

    • deciding how and where to (de)serialize and store the state, making sure that the datastore/database is available
    • for every event: load the correct current state for that event (by reading from the database), execute the business logic, store the new state (by writing to the database)
    • latency at the startup of the serverless application because of the setup of the database-connection
    • latency during the processing of the event because of the reading/writing of the state from/to the database, putting a limit on the throughput of the system
    • what if 2 parallel instances of the application deal with the same state? How to guarantee consistency?

All of these problems (and more) are solved by Akka-Serverless. It is solved in a way which makes it extremely easy for developers use the solution. (For the curious, a lot of those problems are solved by building on Akka-Cluster-Sharding)

B.2 Creating the View

Now that we have our entity, which is extremely elastic and can deal with lots of weather stations, we need a way to expose the state of the application to the outside world. Creating a view happens in the same 3 steps. First, the view is described in a protubuf file. The view is defined by Subscribing to events that are emitted by an entity:

option (akkaserverless.method).eventing.in = {
  event_sourced_entity: "weatherstation"
};

weatherstation_average_view.proto

For the implementation of the view, the SDK generates a placeholder for which we, again, only need to fill in the details, similar as with the Entity. For every event emitted by the observed entity, we need to have method that accepts the event, and returns the new value for the view (in our case: the new average for that weather station). The method has 2 arguments: the emitted event, and the previous (view)state. Based on that, the new state can be calculated.

@Override
public UpdateEffect<WeatherStationOverallAverageState> processStationRegistered(WeatherStationOverallAverageState state, WeatherStationDomain.StationRegistered event) {
  WeatherStationOverallAverageState.Builder stateBuilder = WeatherStationOverallAverageState.newBuilder(state);
  //...calculating the new version of the view
  return effects().updateState(stateBuilder.build());
}

WeatherStationOverallAverageView.java

The state that is build up like this, can be queries through a http-endpoint, by using an SQL-like query langage

rpc GetStationState(StationByIdRequest) returns (WeatherStationOverallAverageState) {
  option (akkaserverless.method).view.query = {
    query: "SELECT * FROM weatherstationsOveralAverage WHERE station_id = :station_id"
  };
  option (google.api.http) = {
    get: "/weather/station/{station_id}"
  };
}

Now that we have entities, and a view which exposes the state in a read-friendly format, we only need to create a public API so that weather stations can start reporting their measurements.

B.3 Creating the API

Thy public API is an endpoint implemented by an Action. It is in essence a (MVC-)Controller.

service WeatherStationApi {
    option (akkaserverless.service) = {
        type : SERVICE_TYPE_ACTION
    };
    rpc RegisterStation(StationRegistrationRequest) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/weather/station/register"
            body: "*"
        };
    }
}

weatherstation_api.proto

That is all we need to give the SDK enough to go with: The annotation  type : SERVICE_TYPE_ACTION makes that the akka-serverless SDK will, again, generate placeholder code for which we only need to fill in the blanks: the actual logic that needs to handle the inbound requests. The endpoints will be made available trough gRPC and HTTP (thanks to the google.api.http-annotation).

API implementation

Implementing the API is easy:  it is a passthrough to the entity-service. We need to convert requests and forward them to another component. Communication with other components is documented here. Eventually, the implementation looks like this:

@Override
public Effect<Empty> registerStation(WeatherStationService.StationRegistrationRequest request) {
  var command = WeatherStationDomain.StationRegistrationCommand.newBuilder()
            .setStationName(request.getStationName())
            .setStationId(request.getStationId())
            .setLongitude(request.getLongitude())
            .setLatitude(request.getLatitude()).build();
  return effects().forward(stationRegistationService.createCall(command));
}

WeatherStationApiAction.java

That’s all there is to it. In this example, the requests maps 1-to-1 onto a command, but the fact that this mapping is here, means than both sides (the request-API and the internal command) are decoupled. It makes the application more flexible to change.

C. Deploying the Stateful Serverless application

When everything is setup correctly (as described in the documentation), deploying this application to the cloud is as simple as:

 > mvn deploy

This will give you an elastic, resilient and stateful serverless application that is publicly available. The Akka-Serverless console gives a nice overview of the deployed services and how they are behaving. It is possible to capture the logs of the application and send them to an external party. In this example the logs are forwarded to google-cloud operations suite.

Conclusion

We covered a lot of ground here. Akka-Serverless is a opinionated framework in the sense that it dictates how the application should be structured. That isn’t necessarily a bad thing, but it does mean that the structure and intend of the framework should be clear, before you want to start using the framework. So there is some learning involved. The documentation is an exellent starting point for that.

Fortunately, the structure of the framework makes a lot of sense and the intend of the components (Entity/View/Action) is clear. It is completely grafted on being reactive: read and write is separated in actions and views (CQRS). The entities keep state and (business) logic about changing the state. State-changes are propagated by means of event, which is the only possible way if the goal is a reactive system.

Provided it is well understood what the intent of Views, Actions and Entities is, this framework allows to create reactive (elastic and resilient) applications with relatively little effort (relative compared to alternatives).

Reactive applications can be build with other tools too, but as we described in our previous post , it’s all about choosing the right tool for the job at the right level of abstraction. Dealing with state in an application that is supposed to be elastic is not trivial at all. There are solutions such as akka-cluster-sharding and frameworks like Lagom and Cloudstate, (not by chance all Lightbend technology), but they all solve only a part of the problem.
Akka-Serverless, thanks to the managed cloud-runtime, integrates all of this nicely and makes it a lot easier to approach. The integrated managed runtime, together with the provided tooling, allows to have your application, up and running in very little time. This includes a fully automated CI/CD pipeline.

What’s next?

This example demonstrates some capabilities of the framework and how the different types of components can be combined. But the functionalities are still very basic: it only allows to show the average values per weather station.

In the next part of this series, things get more exciting: as we said in the introduction, we want to be able to slice-and-dice the data as we see fit. For example, since every weather station has a location, we want to be able to group the data per country, and make reports based on that. Having a view on the extremes could be interesting too: what’s the minimum/maximum of all weather stations?

That is exactly what we’ll do in the next blog-post: we’ll build on this solution to see how far we can take it.

CURIOUS FOR MORE?

Eager to join Reaktika and help us build the databaseless future? Let’s grab a coffee or check out our open job positions.