Writing gRPC descriptors

Akka Serverless SDKs use protobuf descriptors in the Proto3 new tab Protocol Buffers language. You define command messages, data associated with Entities, and Entity events in .proto files. From these definitions, the gRPC compiler creates client and server side code that saves work for you and enables Akka Serverless to serialize message data.

Your service will omit events and can send state snapshots to be persisted. You can choose the way they are serialized for JavaScript or for Java implementations.

Descriptor elements

The examples in the following sections specify the service API for a shopping cart Entity.

Syntax

The first line of our .proto file defines the version of Protocol Buffer syntax:

syntax = "proto3";

Imports

The following imports provide additional Google and Akka Serverless functionality. The entity_key.proto is required to get the Entity key notation. The rest are optional, depending on the features you need to use.

import "akkaserverless/annotations.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";

Package

The package specifies a namespace for this proto file and its imports—​names must be unique within each namespace:

package ecommerce;

Messages

You define messages that can be sent to or returned from the service. Each message that is input to a rpc command, must contain an Entity key. In the example, this includes AddLineItem, RemoveLineItem, and GetShoppingCart where the user_id is the (akkaserverless.field).entity_key. Akka Serverless uses the entity_key to know which instance of an Entity a message is for. If more than one field is specified as an Entity key, the fields are concatenated together. Akka Serverless serializes Entity keys to strings.

message AddLineItem {
    string user_id = 1 [(akkaserverless.field).entity_key = true];
    string product_id = 2;
    string name = 3;
    int32 quantity = 4;
}

message RemoveLineItem {
    string user_id = 1 [(akkaserverless.field).entity_key = true];
    string product_id = 2;
}

message GetShoppingCart {
    string user_id = 1 [(akkaserverless.field).entity_key = true];
}

message LineItem {
    string product_id = 1;
    string name = 2;
    int32 quantity = 3;
}

message Cart {
    repeated LineItem items = 1;
}

Service

This section of the .proto file declares the API of the service itself, along with each function or method and their parameters and return types. When a command is received for a given Entity key, Akka Serverless will establish a gRPC streamed call to the service implementation using that Entity’s type’s protocol if one isn’t already established. Any commands received for the Entity key will be sent through that call.

The AddItem and RemoveItem methods have no return value (the Empty type).

See Transcoding HTTP for an explanation of the HTTP annotations.

service CartService {
    option (akkaserverless.service).entity.type = ".persistence.Cart";

    rpc AddItem(AddLineItem) returns (google.protobuf.Empty) {
        option (google.api.http) = {
            post: "/cart/{user_id}/items/add"
            body: "*"
        };
    }

    rpc RemoveItem(RemoveLineItem) returns (google.protobuf.Empty) {
        option (google.api.http).post = "/cart/{user_id}/items/{product_id}/remove";
    }

    rpc GetCart(GetShoppingCart) returns (Cart) {
        option (google.api.http) = {
            get: "/carts/{user_id}"
            additional_bindings: {
                get: "/carts/{user_id}/items"
                response_body: "items"
            }
        };
    }
}

Transcoding HTTP

Akka Serverless supports transcoding gRPC to HTTP/JSON, using the Google transcoding annotations described here. You can use transcoding to consume your Entities' gRPC interfaces using HTTP/JSON.

Below the optional transcoding of the service to bind the various endpoints to HTTP is highlighted with annotations.

service ShoppingCart {
    rpc AddItem(AddLineItem) returns (google.protobuf.Empty) {
        option (google.api.http) = {
           (1)
           post: "/cart/{user_id}/items/add",
           body: "*",
        };
    }

    rpc RemoveItem(RemoveLineItem) returns (google.protobuf.Empty) {
        option (google.api.http) = {
          (2)
          post: "/cart/{user_id}/items/{product_id}/remove"
        };
    }

    rpc GetCart(GetShoppingCart) returns (Cart) {
        option (google.api.http) = {
          (3)
          get: "/carts/{user_id}",
          additional_bindings: {
            get: "/carts/{user_id}/items",
            response_body: "items"
          }
        };
    }
}
1 This extra annotation specifies that we can call this endpoint using the POST method with the URI /cart/{user_id}/items/add, where {user_id} is the actual user id we want the cart for.
2 A URL that we can POST to which will remove a line item.
3 A more complex example where the entire Cart can be retrieved (on the first URI) or the items in the cart using the one ending in "/items".