Kalix Workshop - Loan application - Scala

Prerequisite

Java 11 or later
SBT 1.3.6 or later
Kalix CLI
Docker 20.10.8 or higher (client and daemon)
Container registry with public access (like Docker Hub)
Access to the gcr.io/kalix-public container registry
cURL
IDE / editor

Create kickstart sbt project using Giter8

sbt new lightbend/kalix-value-entity.g8

name [My Kalix Project]: loan-application
sdk_version [1.0.1]:
sbt_version [1.6.2]:
scala_version [2.13.7]:
package [com.example]: io.kx.loanapp

Import generated project in your IDE/editor

Delete all proto files after done

Update main class

In build.sbt add:

  1. mainClass := Option("io")
  2. version := "1.0-SNAPSHOT

Loan application service

Define API data structure and endpoints (GRPC)

Create io/kx/loanapp/api folder in src/main/proto folder.
Create loan_app_api.proto in src/main/proto/io/kx/loanapp/api folder.
Create:

  • headers
  • state
  • commands
  • service

Tip: Check content in step-1 git branch

Define persistence (domain) data structure (GRPC)

Create io/kx/loanapp/doman folder in src/main/proto folder.
Create loan_app_domain.proto in src/main/proto/io/kx/loanapp/domain folder.
Create:

  • headers
  • state
  • events

Tip: Check content in step-1 git branch

Add codegen annotations in API data structure and endpoints (GRPC)

In src/main/proto/io/kx/loanapp/api/loan_app_api.proto add AkkaServerless codegen annotations to GRPC service

service LoanAppService {
option (kalix.codegen) = {
    event_sourced_entity: {
      name: "io.kx.loanapp.domain.LoanAppEntity"
      entity_type: "loanapp"
      state: "io.kx.loanapp.domain.LoanAppDomainState"
      events: [
        "io.kx.loanapp.domain.Submitted",
        "io.kx.loanapp.domain.Approved",
        "io.kx.loanapp.domain.Declined"
      ]
    }
  };
...

Note: event_sourced_entity.name has to be a unique name

Compile maven project to trigger codegen

sbt compile

Compile will generate help classes and these skeleton classes

Business logic:
src/main/scala/io/Main
src/main/scala/io/kx/loanapp/domain/LoanAppEntity

Unit tests:
src/test/scala/io/kx/loanapp/domain/LoanAppEntitySpec
Integration tests:
src/test/scala/io/kx/loanapp/api/LoanAppServiceIntegrationSpec

Implement entity skeleton class

Implement src/main/scala/io/kx/loanapp/domain/LoanAppEntity class
Tip: Check content in step-1 git branch

Implement unit test

Implement src/test/scala/io/kx/loanapp/domain/LoanAppEntitySpec class
Tip: Check content in step-1 git branch

Implement integration test

Implement src/test/scala/io/kx/loanapp/api/LoanAppServiceIntegrationSpec class
Tip: Check content in step-1 git branch

Run unit and integration tests

sbt test

Note: Integration tests uses TestContainers to span integration environment so it could require some time to download required containers. Also make sure docker is running.

Run locally

In project root folder there is docker-compose.yaml for running kalix proxy and (optionally) google pubsub emulator. Tip: If you do not require google pubsub emulator then comment it out in docker-compose.yaml

docker-compose up

Start the service:

sbt compile run

Test service locally

Submit loan application:

curl -XPOST -d '{
  "client_id": "12345",
  "client_monthly_income_cents": 60000,
  "loan_amount_cents": 20000,
  "loan_duration_months": 12
}' http://localhost:9000/loanapp/1 -H "Content-Type: application/json"

Get loan application:

curl -XGET http://localhost:9000/loanapp/1 -H "Content-Type: application/json"

Approve:

curl -XPUT http://localhost:9000/loanapp/1/approve -H "Content-Type: application/json"

Package & Publish

sbt docker:publish -Ddocker.username=<dockerId>

Note: Replace <dockerId> with required dockerId

Register for Kalix account or Login with existing account

Register

kalix CLI

Validate version:

kalix version

Login (need to be logged in the Kalix Console in web browser):

kalix auth login

Create new project:

kalix projects new loan-application --region <REGION>

Note: Replace <REGION> with desired region

List projects:

kalix projects list

Set project:

kalix config set project loan-application

Deploy service

kalix service deploy loan-application my-docker-repo/loan-application:1.0-SNAPSHOT

Note: Replace my-docker-repo with your docker repository

List services:

kalix services list
NAME               AGE    REPLICAS   STATUS   DESCRIPTION   
loan-application   102s   1          Ready  

Expose service

kalix services expose loan-application

Result: Service 'loan-application' was successfully exposed at: lingering-morning-1201.us-east1.kalix.app

Test service in production

Submit loan application:

curl -XPOST -d '{
  "client_id": "12345",
  "client_monthly_income_cents": 60000,
  "loan_amount_cents": 20000,
  "loan_duration_months": 12
}' https://lingering-morning-1201.us-east1.kalix.app/loanapp/1 -H "Content-Type: application/json"

Get loan application:

curl -XGET https://lingering-morning-1201.us-east1.kalix.app/loanapp/1 -H "Content-Type: application/json"

Approve:

curl -XPUT https://lingering-morning-1201.us-east1.kalix.app/loanapp/1/approve -H "Content-Type: application/json"

Loan application processing service

Increment version

In build.sbt set version to 1.1-SNAPSHOT

Define API data structure and endpoints (GRPC)

Create io/kx/loanproc/api folder in src/main/proto folder.
Create loan_proc_api.proto in src/main/proto/io/kx/loanproc/api folder.
Create:

  • state
  • commands
  • service

Tip: Check content in step-2 git branch

Define persistence (domain) data structure (GRPC)

Create io/kx/loanproc/domain folder in src/main/proto folder.
Create loan_proc_domain.proto in src/main/proto/io/kx/loanproc/domain folder.
Create:

  • state
  • events

Tip: Check content in step-2 git branch

Add codegen annotations in API data structure and endpoints (GRPC)

In src/main/proto/io/kx/loanproc/api/loan_proc_api.proto add AkkaServerless codegen annotations to GRPC service

service LoanProcService {
option (kalix.codegen) = {
    event_sourced_entity: {
      name: "io.kx.loanproc.domain.LoanProcEntity"
      entity_type: "loanproc"
      state: "io.kx.loanproc.domain.LoanProcDomainState"
      events: [
        "io.kx.loanproc.domain.ProcessStarted",
        "io.kx.loanproc.domain.Approved",
        "io.kx.loanproc.domain.Declined"
      ]
    }
  };
...

Note: event_sourced_entity.name has to be a unique name

Compile sbt project to trigger codegen

sbt compile

Compile will generate these skeleton classes

Business logic:
src/main/scala/io/kx/loanproc/domain/LoanProcEntity

Unit tests:
src/test/scala/io/kx/loanproc/domain/LoanProcEntityTest
Integration tests:
src/test/scala/io/kx/loanproc/api/LoanProcEntityIntegrationTest

Update Main class

In src/main/scala/io/Main you need to add new entity component (LoanProcEntity):

 KalixFactory.withComponents(
      new LoanAppEntity(_),new LoanProcEntity(_))

Implement entity skeleton class

Implement src/main/scala/io/kx/loanproc/domain/LoanProcEntity class
Tip: Check content in step-2 git branch

Implement unit test

Implement src/test/scala/io/kx/loanproc/domain/LoanProcEntitySpec class
Tip: Check content in step-2 git branch

Implement integration test

Implement src/test/scala/io/kx/loanproc/api/LoanProcServiceIntegrationSpec class
Tip: Check content in step-2 git branch

Run unit & integration test

sbt test

Note: Integration tests uses TestContainers to span integration environment so it could require some time to download required containers. Also make sure docker is running.

Package & Publish

sbt docker:publish -Ddocker.username=<dockerId> 

Note: Replace <dockerId> with required dockerId

Deploy service

kalix service deploy loan-application my-docker-repo/loan-application:1.1-SNAPSHOT

Note: Replace my-docker-repo with your docker repository

Test service in production

Start processing:

curl -XPOST -d '{
  "client_monthly_income_cents": 60000,
  "loan_amount_cents": 20000,
  "loan_duration_months": 12
}' https://lingering-morning-1201.us-east1.kalix.app/loanproc/1 -H "Content-Type: application/json"

Get loan processing:

curl -XGET https://lingering-morning-1201.us-east1.kalix.app/loanproc/1 -H "Content-Type: application/json"

Approve:

curl -XPUT https://lingering-morning-1201.us-east1.kalix.app/loanproc/1/approve -H "Content-Type: application/json"

Loan Process Views

Increment version

In build.sbt set version to 1.2-SNAPSHOT

Create a view

Create io/kx/loanproc/view folder in src/main/proto folder.
Create loan_proc_by_status_view.proto in src/main/proto/io/kx/loanproc/view folder.
Create:

  • state
  • request/response
  • service

Note: SELECT result alias AS results needs to correspond with GetLoanProcByStatusResponse parameter name repeated LoanProcViewState results
Note: Currently enums are not supported as query parameters (issue 1141) so enum number value is used for query
Tip: Check content in step-3 git branch

Compile project to trigger codegen for views

sbt compile

Compile will generate help classes (target/generated-* folders) and skeleton classes

src/main/scala/io/kx/loanproc/view/LoanProcByStatusView

In src/main/scala/io/Main you need to add view (LoanProcByStatusView) initialization:

    KalixFactory.withComponents(
      new LoanAppEntity(_),new LoanProcEntity(_), new LoanProcByStatusView(_))

Implement view LoanProcByStatusView skeleton class

Implement src/main/scala/io/kx/loanproc/view/LoanProcByStatusView class
Tip: Check content in step-3 git branch

##Unit test

Because of the nature of views only Integration tests are done.

Create integration tests for view

  1. Copy io/kx/loanproc/api/LoanProcServiceIntegrationSpec class to io/kx/loanproc/api/LoanProcServiceViewIntegrationSpec
  2. Remove all tests in
  3. Add next to clien declaration:
private val view = testKit.getGrpcClient(classOf[LoanProcByStatus])
  1. Add view test

Tip: Check content in step-3 git branch

Run integration test

sbt test

Note: Integration tests uses TestContainers to span integration environment so it could require some time to download required containers. Also make sure docker is running.

Package & Publish

sbt docker:publish -Ddocker.username=<dockerId> 

Note: Replace <dockerId> with required dockerId

Deploy service

kalix service deploy loan-application my-docker-repo/loan-application:1.2-SNAPSHOT

Note: Replace my-docker-repo with your docker repository

Test service in production

Get loan processing by status:

curl -XPOST -d {"status_id":2} https://lingering-morning-1201.us-east1.kalix.app/loanproc/views/by-status -H "Content-Type: application/json"

Event driven communication

Increment version

In build.sbt set version to 1.3-SNAPSHOT

Action for submitted event (Loan application service -> Loan application processing service)

Create io/kx/loanapp/action folder in src/main/proto folder.
Create loan_app_eventing_to_proc_action.proto in src/main/proto/io/kx/loanapp/action folder.
Create:

  • service

Tip: Check content in step-4 git branch

Action for approved & declined processing event (Loan application processing service -> Loan application service)

Create io/kx/loanproc/action folder in src/main/proto folder.
Create loan_proc_eventing_to_app_action.proto in src/main/proto/io/kx/loanproc/action folder.
Create:

  • service

Tip: Check content in step-4 git branch

Compile project to trigger codegen for actions

mvn sbt

Compile will generate skeleton classes:

src/main/scala/io/kx/loanapp/action/LoanAppEventingToProcAction
src/main/scala/io/kx/loanproc/action/LoanProcEventingToAppAction

In src/main/scala/io/Main you need to add view (LoanAppEventingToProcAction & LoanProcEventingToAppAction) initialization:

 KalixFactory.withComponents(
      new LoanAppEntity(_),new LoanProcEntity(_), new LoanAppEventingToProcAction(_), new LoanProcByStatusView(_), new LoanProcEventingToAppAction(_))

Implement view LoanAppEventingToProcAction skeleton class

Implement src/main/scala/io/kx/loanapp/action/LoanAppEventingToProcAction class
Tip: Check content in step-4 git branch

Implement view LoanProcEventingToAppAction skeleton class

Implement src/main/scala/io/kx/loanproc/action/LoanProcEventingToAppAction class
Tip: Check content in step-4 git branch

System integration tests (multiple services)

In src/test/scala/io/kx folder create new class SustemIntegrationSpec. Tip: Check content in step-4 git branch

Run integration test

sbt test

Note: Integration tests uses TestContainers to span integration environment so it could require some time to download required containers. Also make sure docker is running.

Package & Publish

sbt docker:publish -Ddocker.username=<dockerId> 

Note: Replace <dockerId> with required dockerId

Deploy service

kalix service deploy loan-application my-docker-repo/loan-application:1.3-SNAPSHOT

Note: Replace my-docker-repo with your docker repository

Test service in production

Submit loan application:

curl -XPOST -d '{
  "client_id": "123456",
  "client_monthly_income_cents": 60000,
  "loan_amount_cents": 20000,
  "loan_duration_months": 12
}' https://lingering-morning-1201.us-east1.kalix.app/loanapp/2 -H "Content-Type: application/json"

Approve loan processing:

curl -XPUT -d '{
"reviewer_id": "9999"
}' https://lingering-morning-1201.us-east1.kalix.app/loanproc/2/approve -H "Content-Type: application/json"

Get loan application :

curl -XGET https://lingering-morning-1201.us-east1.kalix.app/loanapp/2 -H "Content-Type: application/json"