Calling other services

Kalix services

In some cases it is useful to call a component in another service, for example interacting with multiple entities or actions, aggregating their return values or performing a multi-step workflow. Forwards and side-effects allow us to trigger other services but can’t compose or transform the results.

Calling other Kalix services in the same project from an Action is done by invoking them over gRPC much like how an external client would. The service is however identified only by the name it has been deployed as, Kalix takes care of routing requests to the service and keeping the data safe by encrypting the connection for us.

In this sample we will show an action that does two sequential calls to the Value Entity Counter service, deployed with the service name "counter."

We start by adding the public API of the counter to the src/main/proto directory of our project.

Since the proto file of a Kalix service contains annotations that cause the SDK code generation to generate services, and we only want to consume the service, we need to start by removing the annotations.

Copy the API definition proto file from the other service into the proto directory but remove all kalix.service option blocks as well as all other Kalix annotations and the import "kalix/annotations.proto" from it.

This is how the counter descriptor looks with all annotations removed:

proto/com/example/counter_api.proto
syntax = "proto3";

package com.example;

import "google/protobuf/empty.proto";

message IncreaseValue {
  string counter_id = 1;
  int32 value = 2;
}

message DecreaseValue {
  string counter_id = 1;
  int32 value = 2;
}

message ResetValue {
  string counter_id = 1;
}

message GetCounter {
  string counter_id = 1;
}

message CurrentCounter {
  int32 value = 1;
}

service CounterService {
  rpc Increase (IncreaseValue) returns (google.protobuf.Empty);
  rpc Decrease (DecreaseValue) returns (google.protobuf.Empty);
  rpc Reset (ResetValue) returns (google.protobuf.Empty);
  rpc GetCurrentCounter (GetCounter) returns (CurrentCounter);
}

The proto-js tool will now generate a client for the service in lib/generated/proto.js when we compile the project.

Creating an instance of the generated client is done through the .clients object on the action. In our delegating service implementation:

src/delegatingservice.js
import { Action } from "@kalix-io/kalix-javascript-sdk";
import { replies } from '@kalix-io/kalix-javascript-sdk';

/**
 * Type definitions.
 * These types have been generated based on your proto source.
 * A TypeScript aware editor such as VS Code will be able to leverage them to provide hinting and validation.
 *
 * DelegatingService; a strongly typed extension of Action derived from your proto source
 * @typedef { import("../lib/generated/delegatingservice").DelegatingService } DelegatingService
 */

/**
 * @type DelegatingService
 */
const action = new Action(
  [
    "com/example/delegating_service.proto",
    "com/example/counter_api.proto" (1)
  ],
  "com.example.DelegatingService",
  {
    includeDirs: ["./proto"]
  }
);

action.commandHandlers = {
  async AddAndReturn(request) {
    const counterClient = action.clients.com.example.CounterService.createClient( (2)
      "counter" (3)
    );
    await counterClient.increase({counterId: request.counterId, value: 1}); (4)
    const currentCounter = await counterClient.getCurrentCounter({counterId: request.counterId});
    return replies.message({value: currentCounter.value});

  }
};

export default action;
1 Include the proto file of the other service in the action service descriptors.
2 Create the client using the provided creators to get async methods. Defaults to a clear text connection as TLS is handled transparently for us.
3 Use the name that the other Kalix service was deployed as, in this case counter.
4 We can now call the various methods on the other service with their method names directly on the client. The calls return promises so we can use await/async to interact with them.
The client can only be created once the service has been started by Kalix, for example triggered by a call, not from the constructor.

External gRPC services

Calling a Kalix service in another project, or an arbitrary external gRPC service is done the same way as described above, with the difference that it will use the full public hostname of the service and TLS/https to encrypt the connection.

src/main/java/com/example/delegatingservice.js
import * as grpc from '@grpc/grpc-js'; (1)

      const counterClient = action.clients.com.example.CounterService.createClient( (2)
        "still-queen-1447.us-east1.apps.kalix.dev", (3)
        grpc.credentials.createSsl() (4)
      );
1 Import grpc-js, to provide credentials when creating the client.
2 Create the client using the provided creators to get async methods.
3 Use the full public hostname of the service.
4 Enable TLS/https to encrypt the connection.