Quickstart: Customer Registry with Views in Java

Create a customer registry that includes queries in Java, package it into a container, and run it on Akka Serverless.

In this Quickstart 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 Akka Serverless.

Before you begin

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

  1. Download the source code using the Akka Serverless CLI: akkasls 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 Quickstart source code using the Akka Serverless CLI:

akkasls quickstart download customer-registry-java

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

Define the CustomerByEmail View

  1. In your project, create a directory for your views protobuf files, src/main/proto/customer/view.

    Linux or macOS
    mkdir -p ./src/main/proto/customer/view
    Windows 10+
    mkdir src/main/proto/customer/view
  2. Create a customer_view.proto file and save it in the src/main/proto/customer/view directory.

    src/main/proto/customer/view/customer_view.proto
    syntax = "proto3"; (1)
    
    package customer.view; (2)
    
    option java_outer_classname = "CustomerViewModel"; (3)
    
    import "customer/domain/customer_domain.proto"; (4)
    import "akkaserverless/annotations.proto";
    1 The protobuf syntax version, proto3.
    2 The package name, customer.view.
    3 The Java outer classname, CustomerViewModel. Messages defined in this file will be generated as inner classes, for example CustomerViewModel.ByEmailRequest.
    4 Import the proto files for your domain model customer/domain/customer_domain.proto and Akka Serverless annotations akkaserverless/annotations.proto.
  3. Add the service endpoint

    src/main/proto/customer/view/customer_view.proto
    service CustomerByEmail { (1)
      option (akkaserverless.service) = { (2)
        type: SERVICE_TYPE_VIEW
      };
    
      rpc UpdateCustomer(domain.CustomerState) returns (domain.CustomerState) { (3)
        option (akkaserverless.method).eventing.in = { (4)
          value_entity: "customers"
        };
        option (akkaserverless.method).view.update = { (5)
          table: "customers_by_email"
        };
      }
    
      rpc GetCustomer(ByEmailRequest) returns (domain.CustomerState) { (6)
        option (akkaserverless.method).view.query = {
          query: "SELECT * FROM customers_by_email WHERE email = :email" (7)
        };
      }
    }
    
    message ByEmailRequest {
      string email = 1;
    }
    1 The Protobuf service for the View.
    2 The option that the Maven plugin will use to generate the CustomerByEmail View.
    3 The UpdateCustomer method defines how Akka Serverless will update the view.
    4 The source of the View is the "customers" Value Entity. This identifier is defined in the entity_type: "customers" property of the (akkaserverless.file).value_entity option in the customer_domain.proto file.
    5 The (akkaserverless.method).view.update annotation defines that this method is used for updating the View. You must define the table attribute for the table to be used in the query. Pick any name and use it in the query SELECT statement.
    6 The GetCustomers method defines the query to retrieve a customer by email.
    7 The (akkaserverless.method).view.query annotation defines that this method is used as a query of the View.
    In this sample we use the internal domain.CustomerState as the state of the view. This is convenient since it allows automatic updates of the view without any logic but has the draw back that it implicitly makes the domain.CustomerState type a part of the public service API. Transforming the state to another type than the incoming update will be illustrated in the CustomerByName example.
  4. Run mvn compile from the project root directory to generate source classes from the Protobuf definitions.

    mvn compile

    This will result in a compilation error in the Main class. That is expected because you added a new component. Fix the compilation error by adding CustomerByEmailView::new as the second parameter to AkkaServerlessFactory.withComponents in src/main/java/customer/Main.java.

Package and deploy your service

To compile, build the container image, and publish it to your container registry, follow these steps

  1. From the root project directory, compile the source code using Maven:

    mvn compile
  2. Use the deploy target to build the container image and publish it to your container registry. At the end of this command Maven will show you the container image URL you’ll need in the next part of this quickstart.

    mvn deploy
  3. Sign in to your Akka Serverless account at: https://console.akkaserverless.com/

  4. If you do not have a project, click Add Project to create one, otherwise choose the project you want to deploy your service to.

  5. On the project dashboard click the "+" next to services to start the deployment wizard

  6. Choose a name for your service and click Next

  7. Enter the container image URL from the above step and click Next

  8. Click Next (no environment variables are needed for these samples)

  9. Check both Add a route to this service and Enable CORS and click Next

  10. Click Finish to start the deployment

  11. Click Go to Service to see your newly deployed service

Invoke your service

Now that you have deployed your service, the next step is to create a Customer using gRPCurl:

  1. From the "Service Explorer" click on the Create method in CustomerService

  2. Click on "gRPCurl"

  3. In the bottom section of the dialog, fill in the values you want to send to your service

  4. In the top section of the dialog, click the "Copy to clipboard" button

  5. Open a new command line and paste the content you just copied

Then you can try to find the Customer by email using gRPCurl as described above but with the GetCustomer method in CustomerByEmail instead.

Define the CustomerByName View

  1. In the same src/main/proto/customer/view/customer_view.proto file add another View for finding customers by name.

    Add the service endpoint

    src/main/proto/customer/view/customer_view.proto
    service CustomerByName {
      option (akkaserverless.service) = {
        type: SERVICE_TYPE_VIEW
      };
    
      rpc UpdateCustomer(domain.CustomerState) returns (CustomerViewState) { (1)
        option (akkaserverless.method).eventing.in = {
          value_entity: "customers"
        };
        option (akkaserverless.method).view.update = {
          table: "customers_by_name"
          transform_updates: true (2)
        };
      }
    
      rpc GetCustomers(ByNameRequest) returns (stream CustomerViewState) { (3)
        option (akkaserverless.method).view.query = {
          query: "SELECT * FROM customers_by_name WHERE name = :customer_name"
        };
      }
    }
    
    message CustomerViewState {
      string customer_id = 1;
      string email = 2;
      string name = 3;
    }
    
    message ByNameRequest {
      string customer_name = 1;
    }
    1 The UpdateCustomer method defines how Akka Serverless will update the view. In this case use a CustomerViewState that is different from the incoming domain.CustomerState.
    2 transform_updates: true defines that you want custom code in the UpdateCustomer method.
    3 The GetCustomers method defines the query to retrieve customers by name.
  2. Run mvn compile from the project root directory to generate source classes from the Protobuf definitions.

    mvn compile

    Again, this will result in a compilation error in the Main class. That is expected because you added a new component. Fix the compilation error by adding CustomerByNameView::new as the third parameter to AkkaServerlessFactory.withComponents in src/main/java/customer/Main.java.

Implement UpdateCustomer

  1. Implement the emptyState and updateCustomer method in CustomerByEmailView:

    src/main/java/customer/view/CustomerByEmailView.java
    public class CustomerByNameView extends AbstractCustomerByNameView {
    
      public CustomerByNameView(ViewContext context) {
      }
    
      @Override
      public CustomerViewModel.CustomerViewState emptyState() { (1)
        return CustomerViewModel.CustomerViewState.getDefaultInstance();
      }
    
      @Override
      public UpdateEffect<CustomerViewModel.CustomerViewState> updateCustomer(
          CustomerViewModel.CustomerViewState state, CustomerDomain.CustomerState customerState) {
    
        CustomerViewModel.CustomerViewState newViewState = (2)
            CustomerViewModel.CustomerViewState.newBuilder()
                .setCustomerId(customerState.getCustomerId())
                .setEmail(customerState.getEmail())
                .setName(customerState.getName())
                .build();
    
        return effects().updateState(newViewState); (3)
      }
    }
    1 Empty state that will be used if no previous state has been stored for the View.
    2 From the incoming CustomerDomain.CustomerState that represents the change from the Value Entity create a CustomerViewModel.CustomerViewState.
    3 Return effects().updateState with the new state for the View.
    The state of the View is still per Entity. The CustomerDomain.CustomerState customerState parameter represents the changed state of a specific Value Entity. The state parameter is the existing state, if any, of the View for the Entity, i.e. the state that was previously returned via effects().updateState. If no previous state has been stored the emptyState() is used.

Deploy the updated service

  1. Deploy the updated service by repeating the steps in Package and deploy your service.

Invoke the CustomerByName

  1. Similar to the steps in Invoke your service

  2. Create several customers with same name

  3. Use the new CustomerByName instead of CustomerByEmail and then you should see multiple results from CustomerByName/GetCustomers for customers with the same name

Next steps