Kalix Workshop - Loan application - Spring
Not supported by Lightbend in any conceivable way, not open for contributions.
Prerequisite
Java 17
Apache Maven 3.6 or higher
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 maven project
mvn \
archetype:generate \
-DarchetypeGroupId=io.kalix \
-DarchetypeArtifactId=kalix-spring-boot-archetype \
-DarchetypeVersion=LATEST
Define value for property 'groupId': io.kx
Define value for property 'artifactId': loan-application-spring
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' io.kx: : io.kx.loanapp
Import generated project in your IDE/editor
Update main class
- Move
io.kx.Main
toio.kx
package - Change default annotation for
ACL
to:@Acl(allow = @Acl.Matcher(principal = Acl.Principal.ALL))
##Update pom.xml
In pom.xml
:
- In
<mainClass>io.kx.Main</mainClass>
replaceio.kx.Main
withio.kx.Main
- In
<dockerImage>my-docker-repo/${project.artifactId}</dockerImage>
replacemy-docker-repo
with the yourdockerId
Loan application service
Define persistence (domain)
- Create package
io.kx.loanapp.doman
- Create enum
LoanAppDomainStatus
- Create Java Record
LoanAppDomainState
and add parameters - Create Java Interface
LoanAppDomainEvent
and add Java records for eventsSubmitted
,Approved
,Declined
and Jackson annotations for polymorph serialization - In
LoanAppDomainState
Java Record implementempty
,onSubmitted
,onApproved
andonDeclined
methods
Tip: Check content in loan-app-step-1
git branch
Define API data structure and endpoints
- Create package
io.kx.loanapp.api
- Create Java Interface
LoanAppApi
and add Java Records for requests and responses - Create class
LoanAppService
extendingEventSourcedEntity<LoanAppDomainState>
- add class level annotations (event sourcing entity configuration):
@EntityKey("loanAppId") @EntityType("loanapp") @RequestMapping("/loanapp/{loanAppId}")
- add class level annotations (path prefix):
@RequestMapping("/loanapp/{loanAppId}")
- Override
emptyState
and returnLoanAppDomainState.empty()
, set loanAppId viaEventSourcedEntityContext
injected through the constructor - Implement each request method and event handlers
Tip: Check content in loan-app-step-1
git branch
Implement unit test
- Create
src/test/java
- Create
io.kx.loanapp.LoanAppServiceTest
class - Implement
happyPath
Tip: Check content inloan-app-step-1
git branch
Run unit test
mvn test
Implement integration test
- Edit
io.kx.loanapp.IntegrationTest
class - Implement
happyPath
Tip: Check content inloan-app-step-1
git branch
Run integration test
mvn -Pit verify
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: You can comment out google pubsub emulator from docker-compose.yaml
because it is not used here
docker-compose up
Start the service:
mvn exec:exec
Test service locally
Submit loan application:
curl -XPOST -d '{
"clientId": "12345",
"clientMonthlyIncomeCents": 60000,
"loanAmountCents": 20000,
"loanDurationMonths": 12
}' http://localhost:9000/loanapp/1/submit -H "Content-Type: application/json"
Get loan application:
curl -XGET http://localhost:9000/loanapp/1 -H "Content-Type: application/json"
Approve:
curl -XPOST http://localhost:9000/loanapp/1/approve -H "Content-Type: application/json"
Register for Kalix account or Login with existing account
kalix CLI
Login (need to be logged in the Kalix Console in web browser):
kalix auth login
Create new project:
kalix projects new loan-application --region gcp-us-east1
Note: Replace <REGION>
with desired region
List projects:
kalix projects list
Set project:
kalix config set project loan-application
Package & Deploy
Note: Make sure you have replaced my-docker-repo
with the your dockerId
in <dockerImage>my-docker-repo/${project.artifactId}</dockerImage>
mvn deploy
Expose service
kalix services expose loan-application-spring
Result:
Service 'loan-application' was successfully exposed at: <some_host>.us-east1.kalix.app
Test service in production
Submit loan application:
curl -XPOST -d '{
"clientId": "12345",
"clientMonthlyIncomeCents": 60000,
"loanAmountCents": 20000,
"loanDurationMonths": 12
}' https://<somehost>.kalix.app/loanapp/1/submit -H "Content-Type: application/json"
Get loan application:
curl -XGET https://<somehost>.kalix.app/loanapp/1 -H "Content-Type: application/json"
Approve:
curl -XPOST https://<somehost>.kalix.app/loanapp/1/approve -H "Content-Type: application/json"
Loan application processing service
Create loan application processing packages
Create package io.kx.loanproc
in main
and test
Define persistence (domain) data structure (GRPC)
- Create package
io.kx.loanproc.doman
- Create enum
LoanProcDomainStatus
- Create Java Record
LoanProcDomainState
- Create Java Interface
LoanProcDomainEvent
and add Java records for eventsReadyForReview
,Approved
,Declined
and Jackson annotations for polymorph serialization - In
LoanProcDomainState
Java Record implementempty
,onReadyForReview
,onApproved
andonDeclined
methods
Tip: Check content in loan-proc-step-2
git branch
Define API data structure and endpoints (GRPC)
- Create package
io.kx.loanproc.api
- Create Java Interface
LoanProcApi
and add Java records for requests and responses - Create class
LoanProcService
extendingEventSourcedEntity<LoanProcDomainState>
- add class level annotations (event sourcing entity configuration):
@EntityKey("loanAppId") @EntityType("loanproc") @RequestMapping("/loanproc/{loanAppId}")
- add class level annotations (path prefix):
@RequestMapping("/loanproc/{loanAppId}")
- Override
emptyState
and returnLoanProcDomainState.empty()
, set loanAppId viaEventSourcedEntityContext
injected through the constructor - Implement each request method and event handlers
Tip: Check content in loan-proc-step-2
git branch
Implement unit test
- Create
src/test/java
- Create
io.kx.loanproc.LoanProcServiceTest
class - Create
happyPath
Tip: Check content inloan-proc-step-2
git branch
Run unit test
mvn test
Implement integration test
- Edit
io.kx.loanproc.IntegrationTest
class
Tip: Check content in loan-proc-step-2
git branch
Run integration test
mvn -Pit verify
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: You can comment out google pubsub emulator from docker-compose.yaml
because it is not used here
docker-compose up
Start the service:
mvn exec:exec
Test service locally
Start processing:
curl -XPOST http://localhost:9000/loanproc/1/process -H "Content-Type: application/json"
Get loan processing:
curl -XGET http://localhost:9000/loanproc/1 -H "Content-Type: application/json"
Approve:
curl -XPOST -d '{"reviewerId":"9999"}' http://localhost:9000/loanproc/1/approve -H "Content-Type: application/json"
Package & Deploy
mvn deploy
Test service in production
Start processing:
curl -XPOST https://<somehost>.kalix.app/loanproc/1/process -H "Content-Type: application/json"
Get loan processing:
curl -XGET https://<somehost>.kalix.app/loanproc/1 -H "Content-Type: application/json"
Approve:
curl -XPOST -d '{"reviewerId":"9999"}' https://<somehost>.kalix.app/loanproc/1/approve -H "Content-Type: application/json"
Views
Create a view
- Create package
io.kx.loanproc.view
- Create Java Interface
io.kx.loanproc.view.LoanProcViewModel
with Java records forViewRecord
andViewRequest
- Create
io.kx.loanproc.viewLoanProcByStatusView
class extendingView
- Add class level annotation for table name:
@Table("loanproc_by_status")
- Implement getLoanProcByStatus with
@Query
and@PostMapping
annotations - Implement event handler methods for each domain event
- Add class level annotation for table name:
Tip: Check content in views-step-3
git branch
##Unit test Because of the nature of views only Integration tests are done.
Create integration tests for view
In io.kx.loanproc.IntegrationTest
copy loanProcHappyPathWith
test to loanProcHappyPathWithView
and add view query via webClient
Tip: Check content in views-step-3
git branch
Run integration test
mvn -Pit verify
Package & Deploy
mvn deploy
Test service in production
curl -XPOST -d {"statusId":"STATUS_APPROVED"} https://<somehost>.kalix.app/loanproc/views/by-status -H "Content-Type: application/json"
Eventing - Event driven communication
Action for submitted event (Loan application service -> Loan application processing service)
- Create package
io.kx.loanapp.action
- Create
io.kx.loanapp.action.LoanAppToLoanProcEventingAction
class extendingAction
- Add class level annotation:
@Subscribe.EventSourcedEntity(value = LoanAppService.class, ignoreUnknown = true)
- Inject
KalixClient
via constructor - Implement
onSubmitted
event handler method
Tip: Check content in eventing-step-4
git branch
Action for approved & declined processing event (Loan application processing service -> Loan application service)
- Create package
io.kx.loanproc.action
- Create
io.kx.loanproc.action.LoanProcToLoanAppEventingAction
class extendingAction
- Add class level annotation:
@Subscribe.EventSourcedEntity(value = LoanProcService.class, ignoreUnknown = true)
- Inject
KalixClient
via constructor - Implement
onApproved
andonDeclined
event handler methods
Tip: Check content in eventing-step-4
git branch
Create integration tests for eventing (end-to-end test)
Update io.kx.IntegrationTest
and add endToEndHappyPath
and endToEndHappyPathWithDecline
test
Tip: Check content in eventing-step-4
git branch
Run integration test
mvn -Pit verify
Package & Deploy
mvn deploy
Test service in production
Submit loan application:
curl -XPOST -d '{
"clientId": "12345",
"clientMonthlyIncomeCents": 60000,
"loanAmountCents": 20000,
"loanDurationMonths": 12
}' https://<somehost>.kalix.app/loanapp/3/submit -H "Content-Type: application/json"
Check loan processing status:
curl -XPOST -d {"statusId":"STATUS_READY_FOR_REVIEW"} https://<somehost>.kalix.app/loanproc/views/by-status -H "Content-Type: application/json"
Approve loan processing:
curl -XPOST -d '{"reviewerId":"9999"}' https://<somehost>.kalix.app/loanproc/3/approve -H "Content-Type: application/json"
Get loan application:
curl -XGET https://<somehost>.kalix.app/loanapp/3 -H "Content-Type: application/json"
Timers
##Creating config
- Create class
io.kx.loanproc.LoanProcConfig
- Add class level annotation:
@Configuration
@ConfigurationProperties(prefix = "loanproc")
- Add parameter
Integer timeoutMillis
with getter and setter - In
src/main/resources/application.properties
addloanproc.timeoutMillis = 600000
- Create folder
src/it/resources
- Create file
src/it/resources/test.properties
and addloanproc.timeoutMillis = 5000
##Action for managing timer for timeout - Create
io.kx.loanproc.action.LoanProcTimeoutAction
class extendingAction
- Add class level annotation
@Subscribe.EventSourcedEntity(value = LoanProcService.class, ignoreUnknown = true)
- Inject
KalixClient kalixClient
andLoanProcConfig config
via constructor - Implement
getTimerName
method - Implement event handler methods for
onReadyForReview
,onApproved
andonDeclined
Tip: Check content in timers-step-5
git branch
Create integration tests for eventing (end-to-end test)
- Update
io.kx.IntegrationTest
and addendToEndProcessingDeclinedByTimeout
test - Add class level annotation:
@TestPropertySource(locations="classpath:test.properties")
Tip: Check content intimers-step-5
git branch
Run integration test
mvn -Pit verify
Package & Deploy
mvn deploy
Action as a controller (API gateway)
Action
- Create
io.kx.loanapp.action.LoanAppServiceGatewayAction
class extendingAction
- add class level annotations (path prefix):
@RequestMapping("/loanapp-gw")
- add
submit
method Tip: Check content incontroller-action-step-6
git branch
Integration test
Add new test case endToEndHappyPathWithGw
and use loanapp-gw
instead of loanapp
for submitting. Set loanAppId
from the response.
Tip: Check content in controller-action-step-6
git branch
Run integration test
mvn -Pit verify
Package & Deploy
mvn deploy