Forwarding and effects in Java

A command can only act on one component at a time—​the component that received the command. For certain use cases, you might want a different component to handle the command. Rather than sending an immediate reply to a command, the receiving component can forward the command to another component. In cases where you want other components to be aware that a component processed a command, you can emit an effect.

Handle forwarding and emit effects by wrapping the return value into a reply. The reply holds a regular return message or a forward to another component. Both can carry effects that notify other components.

The forwarding or effect can target any component within the service—​whether it is an instance of the same or different type of component.

Transactional limitations

It’s important to note that forwarded commands, and commands emitted as side effects, are non-atomic—​there is no guarantee that any one sent transactions will succeeded. If the service, or the data store, fails, while a forwarded command is executing, the triggering command responds with an error (so the client can retry), but there is no automatic rollback.

If partial updates will cause problems, do not use forwarding and effects to update multiple entities at once. In this case emit an event that is (eventually) processed by all subscribers.

See the documentation on Publishing and subscribing.

Forwarding control to another component

To forward a command return a forward reply that includes the call to invoke, and the message to invoke it with. The command is not forwarded until any state actions requested by the command handler are successfully completed. It is the responsibility of the component receiving the forward to return a reply that matches the type of the original command. Forwards can be chained arbitrarily long.

Use case: processing messages and triggering commands

An action might listen to a journal or an external topic and trigger commands on entities based on the incoming messages.

Looking up service call references

To issue forwards or emit effects you need to look up the destination component and operation from the service call factory. The context of the component or operation allows access to the service call factory. The lookup requires the fully-qualified service name and the operation name (as stated in the protobuf definition of the service), and you pass in the parameter type of the target Java method.

You might want to look up the service call reference in the originating entity’s constructor. That way, you only have to look it up once.

    private final ServiceCallRef<Hotitems.Item> itemAddedToCartRef;

    public ShoppingCartEntity(Context ctx) {
        itemAddedToCartRef =
            ctx.serviceCallFactory()
                .lookup("example.shoppingcart.ShoppingCartService", "ItemAddedToCart", Hotitems.Item.class);
    }

Forwarding a command

To allow forwarding a request, change the service implementation method to return com.akkaserverless.javadsk.Reply<T> where T corresponds to the return type as specified in the protobuf definition.

To create the forwarding reply, create a service call based to pass data to the component and use the Reply.forward(…​) factory method to construct the reply instance.

import com.akkaserverless.javasdk.Reply;
import com.akkaserverless.javasdk.ServiceCallRef;
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 {
    private final String serviceName = "shopping.product.api.ProductPopularityService";

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

        ServiceCallRef<ProductApi.IncreasePopularity> increaseCall =
            ctx.serviceCallFactory()
                .lookup(serviceName, "Increase", ProductApi.IncreasePopularity.class);

        ProductApi.IncreasePopularity increase =
            ProductApi.IncreasePopularity.newBuilder()
                .setProductId(in.getItem().getProductId())
                .setQuantity(in.getItem().getQuantity())
                .build();

        return Reply.forward(increaseCall.createCall(increase));
    }
}

Emitting effects on another component

An entity may also emit one or more effects. An effect is something whose result has no impact on the result of the current command—​if it fails, the current command still succeeds. The result of the effect is therefore ignored. Effects are only performed after the successful completion of any state actions requested by the command handler.

There is no guarantee that an effect will be executed successfully. If a failure occurs after the command is fully handled, effects might not be executed. Effects are not retried in case of failures.

Effects may be declared as synchronous or asynchronous. Asynchronous commands run in a "fire and forget" fashion. The code flow of the caller (the command handler of the entity which emitted the asynchronous command) continues while the command is being asynchronously processed. Meanwhile, synchronous commands run sequentially, that is, the commands are processed in order, one at a time. The final result of the command handler, either a reply or a forward, is not sent until all synchronous commands are completed.

Use case: mobile notification

You might want to emit effects to notify interested parties of a change in state. For example, after a withdrawal is made from a bank account, an account entity could send a notification to the account owner’s mobile phone.

Emitting an effect

To allow emitting effects after processing a request, change the service implementation method to return com.akkaserverless.javadsk.Reply<T> where T corresponds to the return type as specified in the protobuf definition.

For example, upon successful completion of the addItem command by ShoppingCartEntity, if you also want to emit an effect on the HotItems entity, you would invoke the effect service call as:

@CommandHandler
public Reply<Empty> addItem(Shoppingcart.AddLineItem item, CommandContext ctx) {
  // ... Validate and emit event

  // prepare effect
  Hotitems.Item item =
      Hotitems.Item.newBuilder()
          .setProductId(item.getProductId())
          .setName(item.getName())
          .setQuantity(item.getQuantity())
          .build();

  return Reply
      .message(Empty.getDefaultInstance())
      .addEffects(Effect.of(itemAddedToCartRef.createCall(item)));
}

Please note that, contrary to command forwarding, the result of the effect is ignored by the current command addItem.