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’re new to Kalix, create an account, so you can try out Kalix for free.
-
You’ll need to install the Kalix CLI to deploy from a terminal window.
-
You’ll also need
-
Java 17 or higher
If you want to bypass writing code and jump straight to the deployment:
|
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
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.
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:
-
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
-
Use the
deploy
target to build the container image, publish it to the container registry as configured in thepom.xml
file, and the targetkalix: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 runmvn deploy
first and thenmvn 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. -
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
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
-
Deploy the updated service by repeating the steps in [deploy].
Invoke the CustomerByName
-
Similar to the steps in Invoke your service.
-
Create several customers with same name.
-
Use the new
/customers/by_name/{customer_name}
endpoint instead of/customer/by_email/{email}
and then you should see multiple results fromCustomerSummary
for customers with the same.
Next steps
-
You can read more about Views.