Customer Registry with Views in Java

In this section, you will learn how to create a customer registry with the Java SDK, package it into a container, and run it on Kalix.

Before you begin

If you want to bypass writing code and jump straight to the deployment:

  1. Download the source code using the Kalix CLI: kalix quickstart download customer-registry-views-java

  2. Skip to Package and deploy your service.

Start from the Customer Registry Entity

Start by downloading the Customer Registry sample source code using the Kalix CLI:

kalix quickstart download customer-registry-java

You can access the Customer Entity with its Entity id. In this guide we will describe how to retrieve customers by email or name instead.

Define the CustomerByEmail View

Create a class named CustomerByEmailView in package customer.view.

You implement a View by extending kalix.javasdk.view.View and subscribing to changes from an entity. You specify how to query it by providing a method annotated with @Query, which is then made accessible via REST annotations.

src/main/java/customer/view/CustomerByEmailView.java
import customer.domain.Customer;
import customer.api.CustomerEntity;
import kalix.javasdk.view.View;
import kalix.javasdk.annotations.Query;
import kalix.javasdk.annotations.Subscribe;
import kalix.javasdk.annotations.Table;
import kalix.javasdk.annotations.ViewId;
import org.springframework.web.bind.annotation.GetMapping;

@ViewId("view_customers_by_email") (1)
@Table("customers_by_email") (2)
@Subscribe.ValueEntity(CustomerEntity.class)(3)
public class CustomerByEmailView extends View<Customer> { //  (4)

  @GetMapping("/customer/by_email/{email}") (5)
  @Query("SELECT * FROM customers_by_email WHERE email = :email") (6)
  public Customer getCustomer(String email) {
    return null; (7)
  }
}
1 Defining view ID.
2 Defining table name.
3 Subscribing to CustomerEntity.
4 Extending from View.
5 Defining endpoint.
6 Defining the query.
7 Note that no return is needed. This method is used to declare in Kalix how the query index should be configured. The query execution is carried by Kalix and therefore the method can simply return null.
In this sample, we use the internal domain.Customer as the state of the view. This is convenient since it allows automatic updates of the view without any logic but has the drawback that it implicitly makes the domain.Customer type a part of the public service API. Transforming the state to another type than the incoming update will be illustrated in the CustomersByName example.

Package and deploy your service

To build and publish the container image and then deploy the service, follow these steps:

  1. If you haven’t done so yet, sign in to your Kalix account. If this is your first time using Kalix, this will let you register an account, create your first project, and set this project as the default.

    kalix auth login
  2. Use the deploy target to build the container image, publish it to the container registry as configured in the pom.xml file, and the target kalix:deploy to automatically deploy the service to Kalix:

    mvn deploy kalix:deploy
    If you time stamp your image. For example, <dockerTag>${project.version}-${build.timestamp}</dockerTag> you must always run both targets in one pass, i.e. mvn deploy kalix:deploy. You cannot run mvn deploy first and then mvn kalix:deploy because they will have different timestamps and thus different `dockerTag`s. This makes it impossible to reference the image in the repository from the second target.
  3. You can verify the status of the deployed service using:

    kalix service list

Invoke your service

Once the service has started successfully, you can start a proxy locally to access the service:

kalix service proxy <service name>

You can use command line HTTP clients, such as curl or httpie, to invoke the service through the proxy at localhost:8080, using plaintext connections.

A customer can be created using the /customer/{customer_id}/create endpoint on CustomerEntity:

curl localhost:8080/customer/abc123/create \
  --header "Content-Type: application/json" \
  -XPOST \
  --data '{
    "email": "someone@example.com",
    "name": "Someone",
    "address": {
      "street": "123 Some Street",
      "city": "Somewhere"
    }
  }'

The /customer/abc123 endpoint can be used to retrieve this customer:

curl localhost:8080/customer/abc123

The customer can also be found through the CustomerByEmail view service.

curl localhost:8080/customer/by_email/someone@example.com

You can expose the service to the internet. A generated hostname will be returned from the expose command:

kalix service expose <service name>

Try to call the exposed service with curl:

curl https://<generated hostname>/customer/abc123

Define the CustomerByName View

Create a class named CustomersByNameView in package customer.view.

This time, we won’t use domain.Customer as the state of the view. Instead, we introduce a new class record named CustomerSummary and we will transform the incoming Customer state change to CustomerSummary

src/main/java/customer/view/CustomersByNameView.java
import customer.api.CustomerEntity;
import customer.domain.Customer;
import kalix.javasdk.annotations.Query;
import kalix.javasdk.annotations.Subscribe;
import kalix.javasdk.annotations.Table;
import kalix.javasdk.annotations.ViewId;
import kalix.javasdk.view.View;
import org.springframework.web.bind.annotation.GetMapping;
import reactor.core.publisher.Flux;

@ViewId("view_customers_by_name")
@Table("customers_by_name")
public class CustomersByNameView
  extends View<CustomersByNameView.CustomerSummary> { (1)

  public record CustomerSummary(String name, String email) { (2)
  }

  @GetMapping("/customers/by_name/{customer_name}")
  @Query("SELECT * FROM customers_by_name WHERE name = :customer_name")
  public Flux<CustomerSummary> getCustomers(String name) { (3)
    return null;
  }

  @Subscribe.ValueEntity(CustomerEntity.class) (4)
  public UpdateEffect<CustomerSummary> onUpdate(Customer customer) {
    return effects()
      .updateState(new CustomerSummary(customer.name(), customer.email()));
  }
}
1 View state type is defined as CustomerSummary.
2 For convenience, CustomerSummary is defined as an inner class record.
3 The Query method return a Flux<CustomerSummary>, meaning that the results will be streamed to the client.
4 The onUpdate method will receive state changes from the CustomerEntity and transform it to CustomerSummary. Note that the @Subscribe.ValueEntity is now added at method level performing the transformation. In the previous example, we didn’t transform the type and therefore the subscription was at the class level.

Deploy the updated service

  1. Deploy the updated service by repeating the steps in [deploy].

Invoke the CustomerByName

  1. Similar to the steps in Invoke your service.

  2. Create several customers with same name.

  3. Use the new /customers/by_name/{customer_name} endpoint instead of /customer/by_email/{email} and then you should see multiple results from CustomerSummary for customers with the same.

Next steps

  • You can read more about Views.