Using Spring to consume Kalix Services

This guide provides a simple example of how a Spring client can be built to interact with a Kalix Service.

What You Will Build

You will build a Kalix Service along with a Spring client to interact with that Service.

The Kalix Service will create, read, update and persist simple counters.

The Spring client will expose the functionality of the Kalix Service as a REST API.

What You Need

How to get started

To get started, do the following:

  • Download and unzip the source repository for this guide, or clone it using Git:

  • cd into kalix-jvm-sdk/samples/java-valueentity-counter

Build and Run the Kalix Service Locally

With the source repository downloaded, you can build and run the Kalix Service locally.

Run the Kalix Service using Maven:

mvn compile exec:exec

You should see output similar to the following:

[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ valueentity-counter ---
{"timestamp":"2022-03-02T23:38:12.174Z","thread":"main","logger":"com.example.Main","message":"starting the Kalix service","context":"default","severity":"INFO"}
{"timestamp":"2022-03-02T23:38:12.829Z","thread":"kalix-akka.actor.default-dispatcher-2","logger":"akka.event.slf4j.Slf4jLogger","message":"Slf4jLogger started","context":"default","severity":"INFO"}

Build and Run the Kalix proxy locally

In another terminal window, run the Kalix proxy using Docker:

docker compose up

You should see output similar to the following:app-name:

kalix-proxy_1   | {"timestamp":"2022-03-02T23:40:40.766Z","mdc":{"akkaAddress":"akka://kalix-proxy@172.21.0.2:25520","akkaSource":"akka://kalix-proxy/user/discovery-manager","sourceActorSystem":"kalix-proxy"},"logger":"io.kalix.proxy.DiscoveryManager","message":"gRPC proxy started at 0.0.0.0:9000","severity":"INFO","thread":"kalix-proxy-akka.actor.default-dispatcher-8"}

Building Spring REST Client

The Spring REST Client would enable Spring application to integrate with REST endpoints exposed via Kalix Service.

Create a Spring Boot Project

Starting with Spring Initializr

Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

Choose Maven and the Java language.

Click Dependencies and select Spring Reactive Web.

Click Generate.

Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.

Create a Spring WebClient

Creating a Spring REST WebClient would allow Spring application to communicate with the REST Endpoints exposed via Kalix Service.

cd into kalix-jvm-sdk/samples/java-valueentity-counter-spring-client.

Now you can create a WebClient. You need to configure the WebClient to use the Kalix Service’s host and port. The following listing shows how to do so:

src/main/java/com/example/client/spring/rest/config/WebClientConfig.java
@Configuration
public class WebClientConfig {

  @Value("${as.host}")
  String host;

  @Value("${as.port}")
  int port;

  @Bean
  public WebClient createWebClient() {
    return WebClient.builder().baseUrl("http://" + host + ":" + port + "")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .build();
  }
}

Create a Spring Service

The Service methods interact with endpoints defined in Kalix Service.

The CounterServiceImpl shown in the following listing uses the Spring REST WebClient to interact with the Kalix Service and return the response received from called endpoint:

src/main/java/com/example/client/spring/rest/service/CounterServiceImpl.java
@Service
public class CounterServiceImpl implements CounterService {

  @Autowired
  WebClient webClient;

  @Override
  public Mono<String> getCounter(String counterId) {

    CounterRequest counterRequest = new CounterRequest();
    counterRequest.setCounterId(counterId);

    return webClient.post()
        .uri("/com.example.CounterService/GetCurrentCounter")
        .bodyValue(counterRequest)
        .retrieve()
        .bodyToMono(String.class);
  }

Create a Spring Controller

The Controller defines the Spring application REST endpoints which will be exposed to the user for interaction.

The CounterController shown in the following listing uses the Spring service to get actual processing done:

/src/main/java/com/example/client/spring/rest/controller/CounterController.java
@RestController
@RequestMapping("/counter")
public class CounterController {

  @Autowired
  CounterService counterService;

  @Autowired
  GrpcClientService grpcClientService;

  @GetMapping(path = "/{counterId}")
  public Mono<String> getCounter(@PathVariable String counterId){
    return counterService.getCounter(counterId);
  }

Create a Spring Main Application

The Main Application class defines the starting point for spring boot application.

The CounterClientApplication shown in the following listing starts spring application:

src/main/java/com/example/client/spring/rest/CounterClientApplication.java
@SpringBootApplication
public class CounterClientApplication {
  public static void main(String[] args) {
    SpringApplication.run(CounterClientApplication.class, args);
  }
}

Build and Run the Spring Application Locally

In project root directory, run the following command mvn spring-boot:run

Building Spring gRPC Client

The Spring gRPC Client would enable Spring application to integrate with gRPC services exposed via Kalix Service.

Create a Spring Boot Project

Starting with Spring Initializr

Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.

Choose Maven and the Java language.

Click Dependencies and select Spring Reactive Web.

Click Generate.

Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.

  • Add following Maven Dependencies to pom.xml

  <properties>
        <kalix-sdk.version>1.0.2</kalix-sdk.version>
        <akka-grpc.version>2.1.4</akka-grpc.version>
        <protobuf.version>3.19.2</protobuf.version>
        <protobuf-plugin.version>0.6.1</protobuf-plugin.version>
        <grpc.version>1.44.0</grpc.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>1.3.5</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-client-spring-boot-starter</artifactId>
            <version>2.13.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.kalix</groupId>
            <artifactId>kalix-java-sdk</artifactId>
            <version>${kalix-sdk.version}</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.0</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
                    </pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Generate gRPC stubs

  • Get all the proto files from relevant Kalix service and copy under src/main/proto folder. Remove all the Kalix annotations from the proto files. For instance [(kalix.field).entity_key = true] and kalix.codegen etc. For instance, copying proto files from to java-valueentity-counter/src/main/proto/com/example.

  • Run mvn compile to generate stubs.

Create a Spring gRPC Client

Creating a Spring gRPC would allow Spring application to communicate with the gRPC Services exposed via Kalix Service.

cd into kalix-jvm-sdk/samples/java-valueentity-counter-spring-client.

Now you can create a GrpcClientConfig. You need to configure the gRPC Client to use the Kalix Service’s host and port. The following listing shows how to do so:

src/main/java/com/example/client/spring/rest/config/GrpcClientConfig.java
@Configuration
public class GrpcClientConfig {

  @Value("${as.host}")
  String host;

  @Value("${as.port}")
  int port;

  @Bean
  public ManagedChannel createGrpcClient() {
    return ManagedChannelBuilder.forAddress(host, port)
        .usePlaintext()
        .build();
  }
}

Create a Spring Service

The Service methods interact with gRPC services defined in Kalix Service.

The GrpcClientService shown in the following listing uses the Spring REST WebClient to interact with the Kalix Service and return the response received from called endpoint:

src/main/java/com/example/client/spring/rest/service/GrpcClientService.java
@Service
public class GrpcClientService {

  @Autowired
  ManagedChannel channel;

  public String decreaseCounter(String counterId, ValueRequest valueRequest) {

    CounterServiceGrpc.CounterServiceBlockingStub counterServiceBlockingStub =
        CounterServiceGrpc.newBlockingStub(channel);

    Empty decreaseResponse = counterServiceBlockingStub.decrease(CounterApi.DecreaseValue.newBuilder()
        .setCounterId(counterId)
        .setValue(valueRequest.getValue())
        .build());

    return decreaseResponse.toString();
  }
}

Create a Spring Controller

The Controller defines the Spring application REST endpoints which will be exposed to the user for interaction.

The CounterController shown in the following listing uses the Spring service to get actual processing done:

/src/main/java/com/example/client/spring/rest/controller/CounterController.java
@RestController
@RequestMapping("/counter")
public class CounterController {

  @Autowired
  CounterService counterService;

  @Autowired
  GrpcClientService grpcClientService;

  @GetMapping(path = "/{counterId}")
  public Mono<String> getCounter(@PathVariable String counterId){
    return counterService.getCounter(counterId);
  }

Create a Spring Main Application

The Main Application class defines the starting point for spring boot application.

The CounterClientApplication shown in the following listing starts spring application:

src/main/java/com/example/client/spring/rest/CounterClientApplication.java
@SpringBootApplication
public class CounterClientApplication {
  public static void main(String[] args) {
    SpringApplication.run(CounterClientApplication.class, args);
  }
}

Build and Run the Spring Application Locally

In project root directory, run the following command mvn spring-boot:run