Understanding the programming model

The Akka Serverless programming model encourages a state-first design paradigm. You can create services that contain stateful and stateless components. For stateful components (Entities), you choose a state model and Akka Serverless handles the data management for you.

The remaining sections on this page answer these questions:

What is an Akka Serverless service?

The term microservice has different meanings in different contexts—​so we use the more generic term, "service", to describe the unit of functionality that you will implement and package to run on Akka Serverless. An Akka Serverless service can contain one or more components, which include: stateless Actions, stateful Entities, and Views:

  • Entities are domain objects that encapsulate data and business logic. For example, in a weather monitoring IoT application, an entity might be a digital twin with ID and temperature data items. For each entity, you choose a state model, which determines how Akka Serverless manages the data.

  • Actions contain logic that does not need to preserve state. For example, an Action can transform incoming data if required, or listen to published events.

  • Views allow you to retrieve data from multiple entities. Create Views using a basic query language that is similar to SQL.

In Akka Serverless development we use the term entity as it is used in domain-driven design, specifically an aggregate root. An understanding of domain-driven design is beneficial but not necessary to work with Akka Serverless.

A service can have multiple components, but only one entity type and its associated state model. Services can only be invoked by messages. Services can receive those messages over gRPC, over HTTP, or by subscribing to topics. Clients, like user interfaces, other services, or external systems that send messages to a service can be built using any framework that you choose.

How does Akka Serverless manage state?

You may be accustomed to programming for a traditional architecture where an application tier invokes a database tier to retrieve and manipulate state. This approach requires that you:

  • Be aware of the datastore—​its location and its behavior—​and develop connection and communication logic.

  • Be responsible for handling errors associated with managing state—​infrastructure level failures, as well as domain level errors such as concurrent updates and transactions.

With Akka Serverless, data access and error handling are the domain of the state management system. Your code does not manage database connections or transactional concerns. Functionality for concurrent updates, caching, sharding (distributing across nodes), and routing is handled by the Akka Serverless proxy. It also manages state. This simplifies your code. When the service is deployed Akka Serverless handles state, avoids contention for resources, and supports scaling and failover, resulting in an elastic and resilient system.

The following diagram illustrates the Akka Serverless architecture at runtime:

client service components

How do you create a service?

Akka Serverless uses gRPC as the main communication protocol. gRPC is a language-agnostic open-source framework for communication between services using Protocol Buffers. To create and deploy a service on Akka Serverless, you first define APIs and Entity data structures in a .proto descriptor file. Use of gRPC saves you time by generating communication logic and integrations necessary in distributed systems. It is not necessary for you to learn the details of gRPC, but you might find it helpful to understand the general concepts.

We recommend separating the descriptors that define the external APIs from those that define the domain state model, so each can evolve independently. You define each API as a gRPC service. The gRPC compiler creates the endpoints. The following example API definition shows an AddCustomerLocation endpoint:

service WirelessMeshService {
    rpc AddCustomerLocation(AddCustomerLocationCommand) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/wirelessmesh/add-customer-location"
            body: "*"
        };
    }
...

The corresponding state that will be stored is defined in the domain .proto file:

message CustomerLocationAdded {
    string customerLocationId = 1;
    string accessToken = 2;
    string email = 3;
}
...

The implementation ties these two .proto files together as shown in the following illustration:

proto tie

What are you responsible for implementing?

While Akka Serverless manages data storage and retrieval, you are responsible for writing the code that encapsulates state and domain logic. After gRPC generates stub implementation files for you, you provide the implementation for Entities, Actions, and Views. Typically, a service manages a single entity type—​a domain object with data items. Each entity instance has an identifying key and Akka Serverless instantiates them based on the key value. For example, an entity might represent a device in an IOT application, a customer in a banking application, or a shopping cart in a retail application.

Entities have the following attributes:

  • A state model that determines how Akka Serverless stores data. Value Entities update in place, similar to the CRUD model. Event Sourced Entities persist each change in state as an event that Akka Serverless writes to a journal. The journal can be used to replay events to reconstruct state at a particular time, debug, or provide an audit.

  • At runtime, a key distinguishes each Entity instance from all others. The key can be multi-part, consisting of multiple items. Similar to an address, the key must be unique:

    Entity key
  • Data items store the Entity’s state:

    Entity state
  • Entities encapsulate domain logic:

    Domain logic
  • Operations on Entities, which come in the form of commands, can change state.

  • When an Entity receives a command, it already has everything it needs to handle that request.

For example, you might create a Home service that manages a House entity. It needs a unique identifier such as a house id, which might include address, and phone number. It will have have other data items such as furniture, appliances, temperature, residents, and visitors. It has operations that can change its state such as adding or removing a resident. It can refer to other Entities. And it can contain sets of other data, such as when a resident has multiple visitors.

Using any Entity data item, you can retrieve multiple instances in a View. The following illustration shows how you define Views in the descriptor files, one of which contains the relevant query, and tie them together in the implementation:

width-800