Customer Registry in Java

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

In this sample you will learn:

  • How to add additional functionality, allowing customers to be queried by name and email.

  • How to package the customer registry into a container.

  • How to deploy and run the customer registry 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-java

  2. Skip to Package and deploy your service.

Generate and build the Kalix project

The Maven archetype template prompts you to specify the project’s group ID, name and version interactively. Run it using the commands shown for your OS.

In IntelliJ, you can skip the command line. Open the IDE, select File > New > Project, and click to activate Create from archetype. Use the UI to locate the archetype and fill in the blanks.

Follow these steps to generate and build your project:

  1. From a command window, run the template in a convenient location:

    Linux or macOS
    mvn archetype:generate \
      -DarchetypeGroupId=io.kalix \
      -DarchetypeArtifactId=kalix-spring-boot-archetype \
      -DarchetypeVersion=1.5.5
    Windows 10+
    mvn archetype:generate ^
      -DarchetypeGroupId=io.kalix ^
      -DarchetypeArtifactId=kalix-spring-boot-archetype ^
      -DarchetypeVersion=1.5.5
  2. Navigate to the new project directory.

  3. Open it on your preferred IDE / Editor.

Customer Registry Service

The service contains only one Value Entity that exposes the operation to mutate a Customer model. The entity itself exposes service endpoints and eventually encapsulates some basic validation. The incoming commands/request are then applied to the model and the entity instructs Kalix, through the Effect API what needs to be done next.

Define the domain model

First, define the domain classes in package customer.domain.

src/main/java/customer/domain/Customer.java
package customer.domain;

public record Customer(String email, String name, Address address) { (1)

  public Customer withName(String newName) { (2)
    return new Customer(email, newName, address);
  }

  public Customer withAddress(Address newAddress) { (2)
    return new Customer(email, name, newAddress);
  }
}
1 Define a Java record email, name and Address.
2 Defined methods implementing the mutations. Note that both methods return a new version of the Customer record and only modify one field.

Finally, the Address record.

src/main/java/customer/domain/Address.java
package customer.domain;

public record Address(String street, String city) {}

Define the external API

The Customer API is defined by the CustomerEntity.

Create a class named CustomerEntity in package customer.api.

src/main/java/customer/api/CustomerEntity.java
import kalix.javasdk.valueentity.ValueEntity;
import kalix.javasdk.annotations.Id;
import kalix.javasdk.annotations.TypeId;
import org.springframework.web.bind.annotation.*;
import io.grpc.Status;
import customer.domain.Address;
import customer.domain.Customer;

@TypeId("customer") (1)
@Id("customer_id") (2)
@RequestMapping("/customer/{customer_id}") (3)
public class CustomerEntity extends ValueEntity<Customer> { (4)

  @PostMapping("/create") (5)
  public ValueEntity.Effect<String> create(@RequestBody Customer customer) { 
    if (currentState() == null)
      return effects()
        .updateState(customer) (6)
        .thenReply("OK");  (7)
    else
      return effects().error("Customer exists already");
  }

  @GetMapping()
  public ValueEntity.Effect<Customer> getCustomer() {
    if (currentState() == null)
      return effects().error(
          "No customer found for id '" + commandContext().entityId() + "'",
          Status.Code.NOT_FOUND
        );
    else   
      return effects().reply(currentState());
  }

  @PostMapping("/changeName/{newName}")
  public Effect<String> changeName(@PathVariable String newName) {
    Customer updatedCustomer = currentState().withName(newName);
    return effects()
            .updateState(updatedCustomer)
            .thenReply("OK");
  }

  @PostMapping("/changeAddress")
  public Effect<String> changeAddress(@RequestBody Address newAddress) {
    Customer updatedCustomer = currentState().withAddress(newAddress);
    return effects().updateState(updatedCustomer).thenReply("OK");
  }

}
1 Each Entity needs a unique logical type name. This must be unique per Kalix service.
2 The entity needs to be address by a unique identifier. The @Id declares the name of the path variable that Kalix should use as unique identifier.
3 The @RequestMapping defines the base path to access the entity. Note that the {customer_id} matches the value of @Id.
4 CustomerEntity must inherit from kalix.javasdk.valueentity.ValueEntity.
5 Each API method must be exposed as a REST endpoint using Spring’s REST annotations.
6 The implementation instructs Kalix to persist the state customer.
7 After persisting, Kalix is instructed to return the String 'Ok'.

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

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

Next steps

  • You can learn more about Value Entities.

  • Continue this example by adding Views, which makes it possible to query the customer registry.