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.

The Event Sourced Entity 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";

package shopping.product.actions;

import "akkaserverless/annotations.proto";
import "cart/shopping_cart_domain.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/any.proto";

service ToProductPopularityService {

  rpc ForwardAdded(shopping.cart.domain.ItemAdded) returns (google.protobuf.Empty) {
  }

  rpc ForwardRemoved(shopping.cart.domain.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.ActionCreationContext;
import com.akkaserverless.javasdk.action.Handler;
import com.google.protobuf.Any;
import com.google.protobuf.Empty;

@Action
public class ToProductPopularityAction {

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

  @Handler
  public Reply<Empty> forwardAdded(ShoppingCartDomain.ItemAdded in, ActionContext ctx) {
  }

  @Handler
  public Reply<Empty> forwardRemoved(ShoppingCartDomain.ItemRemoved in, ActionContext ctx) { (1)
  }

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

package shopping;

import com.akkaserverless.javasdk.AkkaServerless;

public final class Main {
  private static final Logger LOG = LoggerFactory.getLogger(Main.class);

  public static final AkkaServerless SERVICE =
      new AkkaServerless()
          .registerAction(
              ToProductPopularityAction.class,
              ToProductPopularity.getDescriptor().findServiceByName("ToProductPopularityService"))

  public static final void main(String[] args) throws Exception {
    LOG.info("started");
    SERVICE.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 Reply<T> as return type. With Reply<T> the method can choose to

  • reply with a value (Reply.message)

  • send no reply (Reply.noReply)

  • forward the call (Reply.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.

  private static final String POPULARITY_SERVICE = "shopping.product.api.ProductPopularityService";

  @Handler
  public Reply<Empty> forwardRemoved(ShoppingCartDomain.ItemRemoved in, ActionContext ctx) { (1)

    ProductPopularityApi.DecreasePopularity decrease = // ... (2)
    ServiceCallRef<ProductPopularityApi.DecreasePopularity> decreaseRef =
        ctx.serviceCallFactory()
            .lookup(
                POPULARITY_SERVICE, (3)
                "Decrease", (4)
                ProductPopularityApi.DecreasePopularity.class (5)
                );
    return Reply.forward(decreaseRef.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