Actions

Actions are stateless functions that can be triggered in multiple ways. For example, by

  • a gRPC service call

  • an HTTP service call

  • a new item in an Event Sourced Entity’s journal

  • a forwarded call from another component

Use case: request conversion

You can use Actions to convert incoming data into a different format before forwarding a call to a different component.

A service might need to offer a request data format that does not correspond directly with the commands of an Event Sourced Entity. By exposing a service implemented by an Action, the Action implementation can adapt the data format to the command (or commands) expected by the Event Sourced Entity. The incoming requests get forwarded to the target component.

Use case: listening to a journal

To listen to an Event Sourced Entity’s journal, an Action can be set up for Eventing (see this section for details).

An Event Sourced Entity’s journal contains events that capture all state changes. By subscribing to the journal with the Event Sourced Entity name, another component can receive all events of that type emitted by an Event Sourced Entity.

Together with Topic publishing, this may be used to inform other services asynchronously about certain events.

Implementing Actions

An Action may implement any service method defined in a Protobuf definition, these examples illustrate the definition of a ShoppingCartAnalyticsService:

syntax = "proto3";

import "google/protobuf/empty.proto";

package shopping.cart.actions;

message ItemAdded {}

message ItemRemoved {}

service ToProductPopularityService {
    rpc ForwardAdded(ItemAdded) returns (google.protobuf.Empty);
    rpc ForwardRemoved(ItemRemoved) returns (google.protobuf.Empty);
}

The class implementing an Action needs to be annotated with the @Action annotation.

import com.akkaserverless.javasdk.action.Action;
import com.akkaserverless.javasdk.action.ActionContext;
import com.akkaserverless.javasdk.action.Handler;
import com.google.protobuf.Empty;

@Action
public class ToProductPopularityAction {

Action methods implementing services require the @CommandHandler annotation and may have ActionContext as their second parameter.

  @Handler
  public Empty forwardAdded(ItemAdded in, ActionContext ctx) { ... }

  @Handler
  public Empty forwardRemoved(ItemRemoved in, ActionContext ctx) { ... }

To connect the Protobuf service definition with the implementation as an Action, register the implementing class with Akka Serverless:

public final class Main {
  public static final void main(String[] args) throws Exception {
    new AkkaServerless()
        .registerAction(
            new ShoppingCartAnalyticsAction(),
            ShoppingCartAnalytics.getDescriptor().findServiceByName("ShoppingCartAnalyticsService"))
        .start()
        .toCompletableFuture()
        .get();
  }
}

Multiple replies / reply streaming

An Action may return data conditionally by marking the return type as stream in Protobuf. The Java method implementing that service must return a org.reactivestreams.Publisher implementation, or an Akka Streams Source to fulfill that contract.

The publisher (or source) may publish an arbitrary number of replies.

Forwarding a call from an Action

Action methods that want to forward calls to other service methods use ActionReply<T> as return type. With ActionReply<T> the method can choose to

  • reply with a value (ActionReply.message)

  • send no reply (ActionReply.noReply)

  • forward the call (ActionReply.forward)

To forward to a different method, look up the target method via the ActionContext by specifying the full Protobuf service name, method name and passing the parameter type’s class.

  @CommandHandler
  public ActionReply<Empty> forwardRemoved(ItemRemoved in, ActionContext ctx) { (1)
    ProductApi.DecreasePopularity decrease = ...; (2)

    ServiceCallRef<DecreasePopularity> call =
        ctx.serviceCallFactory()
            .lookup(
                "shopping.product.api.ProductPopularityService", (3)
                "Decrease", (4)
                DecreasePopularity.class (5)
            );

    return ActionReply.forward(call.createCall(decrease)); (6)
  }
1 Use ActionReply<Empty> as return type and accept an ActionContext as second parameter
2 Create the parameter the target method accepts
3 Use the fully-qualified gRPC service name of the target method
4 Specify the Protobuf rpc method name
5 Pass the target methods expected parameter type
6 Return a forward to the call with the required parameter