Component and service calls

An Akka service comprises many components. Such components might depend on one another, on other Akka services or even external services. This section describes how to call other components and services from within an Akka service.

Akka components

Since Akka is an auto-scaling solution, components can be distributed across many nodes within the same service. That’s why calls between Akka components is done via a client rather than through normal method calls, the receiving component instance may be on the same node, but it may also be on a different node.

Requests and responses are always serialized to JSON between the client and the component.

Component Client

The akka.javasdk.client.ComponentClient is a utility for making calls between components in a type-safe way. To use the ComponentClient you need to inject it into your component via the constructor:

@Acl(allow = @Acl.Matcher(principal = Acl.Principal.INTERNET))
@HttpEndpoint("/carts") (1)
public class ShoppingCartEndpoint {

  private final ComponentClient componentClient;

  private static final Logger logger = LoggerFactory.getLogger(ShoppingCartEndpoint.class);

  public ShoppingCartEndpoint(ComponentClient componentClient) { (2)
    this.componentClient = componentClient;
  }


  @Get("/{cartId}") (3)
  public CompletionStage<ShoppingCart> get(String cartId) {
    logger.info("Get cart id={}", cartId);
    return componentClient.forEventSourcedEntity(cartId) (4)
        .method(ShoppingCartEntity::getCart)
        .invokeAsync(); (5)
  }


  @Put("/{cartId}/item") (6)
  public CompletionStage<HttpResponse> addItem(String cartId, ShoppingCart.LineItem item) {
    logger.info("Adding item to cart id={} item={}", cartId, item);
    return componentClient.forEventSourcedEntity(cartId)
      .method(ShoppingCartEntity::addItem)
      .invokeAsync(item)
      .thenApply(__ -> HttpResponses.ok()); (7)
  }
1 Accept the ComponentClient as a constructor argument, keep it in a field.
2 Use a specific request builder for the component you want to call
3 Invoking the method returns a CompletionStage<T> where T is what the component eventually returns.
4 Adapt the response rather than returning it as is. In this case discarding the response value, and respond OK without a response body.

The component client can call command handlers on Event Sourced Entities, Key Value Entities, Workflows, Timed Actions, and query methods on Views.

The component client is available for injection only in Service Setup, Endpoints, Consumers, Timed Actions, and Workflows. For more information, see dependency injection.

Akka services

Calling other Akka services in the same project is done by invoking them using an HTTP client. The service is identified by the name it has been deployed. Akka takes care of routing requests to the service and keeping the data safe by encrypting the connection and handling authentication for you.

In this sample we will make an action that does a call to the Key Value Entity Counter service, deployed with the service name counter.

The SDK provides akka.javasdk.http.HttpClientProvider which provides HTTP client instances for calling other services.

In our delegating service implementation:

@Acl(allow = @Acl.Matcher(service = "*"))
@HttpEndpoint
public class DelegatingServiceEndpoint {

  private final HttpClient httpClient;

  public DelegatingServiceEndpoint(HttpClientProvider componentClient) { (1)
    this.httpClient = componentClient.httpClientFor("counter"); (2)
  }

  // model for the JSON we accept
  record IncreaseRequest(int increaseBy) {}

  // model for the JSON the upstream service responds with
  record Counter(int value) {}

  @Post("/delegate/counter/{counter_id}/increase")
  public CompletionStage<String> addAndReturn(String counterId, IncreaseRequest request) {
    CompletionStage<String> result =
        httpClient.POST("/counter/" + counterId + "/increase") (3)
            .withRequestBody(request)
            .responseBodyAs(Counter.class)
            .invokeAsync() (4)
            .thenApply(response -> { (5)
              if (response.status().isSuccess()) {
                return "New counter vaue: " + response.body().value;
              } else {
                throw new RuntimeException("Counter returned unexpected status: " + response.status());
              }
            });

    return result;
  }
}
1 Accept a HttpClientProvider parameter for the constructor
2 Use it to look up a client for the counter service
3 Use the HttpClient to prepare a REST call to the counter service
4 Invoking the call will return a CompletionStage<StrictResponse<T>> with details about the result as well as the deserialized response body.
5 Handle the response, which may be successful, or an error

The HTTP client provider client is only available for injection in the following types of components: HTTP Endpoints, Workflows, Consumers and Timed Actions.

External services

Calling HTTP services deployed on different Akka projects or any other external HTTP server is also done with the HttpClientProvider. Instead of a service name, the protocol and full server name is used when calling httpClientFor. For example https://example.com or http://example.com.

package com.example.callanotherservice;

import akka.javasdk.annotations.Acl;
import akka.javasdk.annotations.http.Get;
import akka.javasdk.annotations.http.HttpEndpoint;
import akka.javasdk.http.HttpClient;
import akka.javasdk.http.HttpClientProvider;
import akka.javasdk.http.StrictResponse;

import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;

@HttpEndpoint
@Acl(allow = @Acl.Matcher(principal = Acl.Principal.ALL))
public class CallExternalServiceEndpoint {

  private final HttpClient httpClient;

  public record PeopleInSpace(List<Astronaut> people, int number, String message) {}
  public record Astronaut(String craft, String name) {}

  public record AstronautsResponse(List<String> astronautNames) {}

  public CallExternalServiceEndpoint(HttpClientProvider httpClient) { (1)
    this.httpClient = httpClient.httpClientFor("http://api.open-notify.org"); (2)
  }

  @Get("/iss-astronauts")
  public CompletionStage<AstronautsResponse> issAstronauts() {
    CompletionStage<StrictResponse<PeopleInSpace>> asyncResponse =
      httpClient.GET("/astros.json")(3)
        .responseBodyAs(PeopleInSpace.class) (4)
        .invokeAsync();

    return asyncResponse.thenApply(peopleInSpaceResponse -> { (5)
      var astronautNames = peopleInSpaceResponse.body().people.stream()
          .filter(astronaut -> astronaut.craft.equals("ISS"))
          .map(astronaut -> astronaut.name)
          .collect(Collectors.toList());
      return new AstronautsResponse(astronautNames); (6)
    });
  }

}
1 Accept a HttpClientProvider parameter for the constructor
2 Look up a HttpClient for a service using http protocol and server name.
3 Issue a GET call to the path /astros.json on the server
4 Specify a class to parse the response body into
5 Once the call completes, handle the response.
6 Return an adapted result object which will be turned into a JSON response.