{"id":19891346,"url":"https://github.com/lightbend/kalix-trial-java-shoppingcart","last_synced_at":"2025-05-02T18:31:18.799Z","repository":{"id":165532114,"uuid":"639799329","full_name":"lightbend/kalix-trial-java-shoppingcart","owner":"lightbend","description":"An e-commerce example for Kalix","archived":true,"fork":false,"pushed_at":"2024-01-30T19:41:49.000Z","size":240,"stargazers_count":1,"open_issues_count":0,"forks_count":7,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-04-13T14:18:43.336Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.kalix.io/developer/experience/java/e-commerce","language":"Java","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lightbend.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-05-12T08:53:43.000Z","updated_at":"2025-01-24T21:43:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"0c4798db-760d-46ac-99ab-34d8869f5510","html_url":"https://github.com/lightbend/kalix-trial-java-shoppingcart","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-trial-java-shoppingcart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-trial-java-shoppingcart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-trial-java-shoppingcart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-trial-java-shoppingcart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lightbend","download_url":"https://codeload.github.com/lightbend/kalix-trial-java-shoppingcart/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252088411,"owners_count":21692790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T18:17:57.030Z","updated_at":"2025-05-02T18:31:18.078Z","avatar_url":"https://github.com/lightbend.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prerequisite\nJava 17 or later\u003cbr\u003e\nMaven 3.6 or later\u003cbr\u003e\nDocker 20.10.14 or higher (to run locally)\u003cbr\u003e\n# Introduction\n\nIf you have gone through the `Explore Kalix Virtaully` journey, you are all set to :\n- [Run locally in prod-like environment](#run-locally-in-prod-like-environment) \u003cbr\u003e\n- [Deploy and run on Kalix Platform on Cloud Provider of your choice ](deploy-and-run-on-kalix-platform-on-cloud-provider-of-your-choice)\n\nHowever, if you would like to better understand the `Kalix Developer Experience` and how to build applications on Kalix, here is more details on the same-use.    \n# Kalix Trial - eCommerce - Java\n## Designing Kalix Services\n### Use case \n![Use case](images/ecommerce-design_kalix_services.png)\u003cbr\u003e\neCommerce use case is a simple shopping cart example consisting of product stock and shopping cart.\nProduct stock models stock (simple quantity) of products that are being sold and Shopping Cart models list of products customer intends to buy.\u003cbr\u003e\nIn this exercise focus is on implementing Product Stock functionalities\n#### Product Stock\nData model:\n- quantity\n\n**Note**: Product stock data model does not hold the productId because the HTTP/REST API is designed around productId itself. \nOperations:\n- CREATE/READ/UPDATE/DELETE product stock\n\n### Kalix components\nKalix components serve to abstract functionalities.\u003cbr\u003e \nIn this particular use case, we will utilize a Kalix component known as `Entity`.\u003cbr\u003e\n![Entity](images/e-commerce-kalix-component-entity.png)\u003cbr\u003e\nEntity:\n- component for modeling of data and data business logic operations\n- removes complexities around data:\n  - caching\n  - concurrency\n  - distributed locking\n- simplifies data modeling, business logic implementation with out-of-the box scalability, resilience\nKalix provides other components that are not used in this use case and more details can be found in [Kalix documentation](https://docs.kalix.io/):\u003cbr\u003e\n![Other Kalix components](images/ecommerce-other_components.png)\u003cbr\u003e\n\n### Design Product Stock Kalix service\n**Product Stock Service**\n- implements Product Stock functionalities\n- Kalix component used: Entity\n\n![Product Stock Kalix Service](images/ecommerce-designing-product-service.png)\n\n**ProductStock Entity**\n- models one product stock instance and business logic operations over that one instance\n\n**Data model**\n- quantity\n\n**API**\n- HTTP/RES\n- Endpoints:\n  - **Create**\n    `POST /product-stock/{productId}/create`\n    \n    Request (JSON):\n    - quantity (int)\n    \n    Response (String): \"OK\"\n  - **Read**\n    `GET /product-stock/{productId}/get`\n    Request (JSON): No body\n    \n    Response (JSON):\n    - quantity (int)\n  - **Update**\n    `PUT /product-stock/{productId}/update`\n  \n    Request (JSON):\n    - quantity (int)\n  \n    Response (String): \"OK\"\n  - **Delete**\n    `DELETE /product-stock/{productId}/delete`\n  \n    Request (JSON): No body\n  \n    Response (String): \"OK\"\n\n## Kickstart Kalix development project\n### Kalix Maven ArchType\nKalix [Maven ArchType](https://maven.apache.org/archetype/index.html) generates a new Maven development project from Kalix template\n### Create shopping cart Maven project from Kalix template \nExecute in command line:\n```\nmvn archetype:generate \\\n  -DarchetypeGroupId=io.kalix \\\n  -DarchetypeArtifactId=kalix-spring-boot-archetype \\\n  -DarchetypeVersion=1.3.7\n```\nUse this setup:\n```\nDefine value for property 'groupId': com.example\nDefine value for property 'artifactId': kalix-trial-shoppingcart\nDefine value for property 'version' 1.0-SNAPSHOT: :\nDefine value for property 'package' com.example: : com.example.shoppingcart\n```\nMaven ArchType generates Maven project:\n\n![Maven project structure](images/ecommerce-maven-project-strcuture.png)\n\n- `pom.xml` with all pre-configured Maven plugins and dependencies required development, testing and packaging of Kalix service code\n- `Main`, Java Class for bootstrapping Kalix service\n- `resources` directory with minimal required configuration\n- `it` directory with integration test example\n\n## Define data structure\nCreate ProductStock `Java record` in `com.example.shoppingcart package`.\u003cbr\u003e \nAdd helper methods for creating `empty` product stock structure and to validate if `isEmpty`.\n```\npublic record ProductStock(Integer quantity){\n    public static ProductStock empty(){\n        return new ProductStock(null);\n    }\n    @JsonIgnore\n    public boolean isEmpty(){\n        return quantity == null;\n    }\n}\n\n```\n## Define API - Product Stock Entity API\n1. Create `ProductStockEntity` Java class in `com.example.shoppingcart` package that `extends` `kalix.javasdk.valueentity.ValueEntity` with inner type `ProductStock` type\n2. Add `productId` class parameter\n3. Add constructor for Kalix to inject `ValueEntityContext` from which `entityId` is used set `productId`\n4. Annotate `ProductStockEntity` class with spring web bind annotation `@RequestMapping` and configure path with `productId` as in-path parameter\n5. Annotate class with `@Id(\"productId\")` to configure parameter `productId` as the entity key\n6. Annotate class with `@TypeId(\"product-stock\")` to assign reference name to the entity\n7. Override `emptyState` method and return `empty` `ProductStock` value\n\n```\n@Id(\"productId\")\n@TypeId(\"product-stock\")\n@RequestMapping(\"/product-stock/{productId}\")\npublic class ProductStockEntity extends ValueEntity\u003cProductStock\u003e{\n    private final String productId;\n    public ProductStockEntity(ValueEntityContext context) {\n            this.productId = context.entityId();\n    }\n    @Override\n    public ProductStock emptyState() {\n       return ProductStock.empty();\n    }\n}\n\n```\n8. Annotate `ProductStockEntity` class with spring web bind annotation `@RequestMapping` and configure path with `productId` as in-path parameter\n9. For each endpoints:\n   - To `ProductStockEntity` class add method per endpoint (`create`, `get`, `update`, `delete`)\n   - Each method:\n     - input: HTTP request data structure (using spring web annotations)\n     - return: `ValueEntity.Effect`  with HTTP response data structure as an inner type\n     - using spring web annotation mappings for REST method and path mapping (`@PostMapping`,...)\n```\n@Id(\"productId\")\n@TypeId(\"product-stock\")\n@RequestMapping(\"/product-stock/{productId}\")\npublic class ProductStockEntity extends ValueEntity\u003cProductStock\u003e{\n   private final String productId;\n   public ProductStockEntity(ValueEntityContext context) {\n   \t\tthis.productId = context.entityId();\n   }\n   @Override\n   public ProductStock emptyState() {\n     return ProductStock.empty();\n   }\n   @PostMapping(\"/create\")\n   public Effect\u003cString\u003e create(@RequestBody ProductStock productStock){}\n   @GetMapping(\"/get\")\n   public Effect\u003cProductStock\u003e get(){}\n   @PutMapping(\"/update\")\n   public Effect\u003cString\u003e update(@RequestBody ProductStock productStock){}\n   @DeleteMapping(\"/delete\")\n   public Effect\u003cString\u003e delete(){}\n}\n\n```\n## Implementing business logic\nHelper methods from `ValueEntity` class:\n- `currentState()` facilitates access to current value of the data for that product stock instance (e.g. productId: 111)\n- `effects()` facilitates actions that Kalix needs to perform\n- `updateState()` - to persist data\n- `thenReply()` - to send response after persistence is successful\n- `error()` - to send error response back\n- Kalix ensures that each method (create, get, update, delete) is executed in sequence for one product stock instance (e.g. productId: 111) ensuring consistency and resolving concurrent access.\n\n### `create` endpoint\nBusiness logic for create is to persist product stock data if not yet exists. In other cases returns an ERROR. \n```\n@PostMapping(\"/create\")\npublic Effect\u003cString\u003e create(@RequestBody ProductStock productStock){\n   if(currentState().isEmpty())\n       return effects().updateState(productStock).thenReply(\"OK\");\n   else\n       return effects().error(\"Already created\");\n}\n```\n### `get` endpoint\nBusiness logic for get is to product stock data if exists and if not return not found error.\n```\n@GetMapping(\"/get\")\npublic Effect\u003cProductStock\u003e get(){\n   if(currentState().isEmpty())\n       return effects().error(\"Not found\", Status.Code.NOT_FOUND);\n   else\n       return effects().reply(currentState());\n}\n\n```\n### `update` endpoint\nBusiness logic for update is to update product stock data if product was already created. If product is not found, return NOT FOUND error.\n```\n@PutMapping(\"/update\")\npublic Effect\u003cString\u003e update(@RequestBody ProductStock productStock){\n   if(currentState().isEmpty())\n       return effects().error(\"Not found\", Status.Code.NOT_FOUND);\n   else\n       return effects().updateState(productStock).thenReply(\"OK\");\n}\n\n```\n### `delete` endpoint\nBusiness logic for delete is delete data if product stock exists and return NOT FOUND error if not. \nHere the soft delete is done by `emptying` the Product Stock.\n```\n@DeleteMapping(\"/delete\")\npublic Effect\u003cString\u003e delete(){\n   if(currentState().isEmpty())\n       return effects().error(\"Not found\", Status.Code.NOT_FOUND);\n   else\n       return effects().updateState(ProductStock.empty()).thenReply(\"OK\");\n}\n\n```\n## Test\nKalix comes with very rich test kit for unit and integration testing of Kalix code\n\n`Test kit` provides help (custom assertions, mocks,...) with:\n- unit testing of individual Kalix components (e.g `Entity`) in isolation\n- integration testing in Kalix Platform simulated environment in isolation\n\n- Allows easy test automation with very high test coverage\n\n### Unit test\n1. Create a new `test/java` directories in `src` directory\n2. Create `com.example.shoppingcart` package in `test/java`\n3. Create `ProductStockEntityTest` class created package\n4. Create `testCreate` method with `JUnit Jupiter Test` annotation\n5. `ValueEntityTestKit` class is used for unit testing `Value Entity` component. Inner types are `ProductStock` and `ProductStockEntity`. \n    It is for unit testing one product instance so `productId` needs to be provided\n6. `Testkit` call method is used for triggering each entity endpoint and result is `ValueEntityResult` with inner type as a HTTP result  \n7. result can be used for test assertion\n   - `isError` - assert error\n   - `getReply` - assert reply\n   - `getUpdatedState` - assert persistent data\n```\npublic class ProductStockEntityTest {\n   @Test\n   public void testCreate()throws Exception{\n       var productId = UUID.randomUUID().toString();\n       ProductStock productStock = new ProductStock(10);\n\n       ValueEntityTestKit\u003cProductStock, ProductStockEntity\u003e testKit = ValueEntityTestKit.of(productId, ProductStockEntity::new);\n\n       ValueEntityResult\u003cString\u003e res = testKit.call(entity -\u003e entity.create(productStock));\n       assertFalse(res.isError());\n       assertEquals(\"OK\",res.getReply());\n       ProductStock persistedProductStock = (ProductStock)res.getUpdatedState();\n       assertEquals(productStock.quantity(),persistedProductStock.quantity());\n   }\n}\n\n```\nRun the unit test:\n```\nmvn test\n```\n### Integration test\nKalix test kit for integration testing runs code using test containers to simulate Kalix Platform runtime environment.\nIntegration test uses spring reactive WebClient to interact with running code.\nIntegrationTest class is created during development project kick start and is pre-configured with WebClient\nEach endpoint is tested from the client perspective\n```\npublic class IntegrationTest extends KalixIntegrationTestKitSupport {\n @Autowired\n private WebClient webClient;\n private Duration timeout = Duration.of(5, ChronoUnit.SECONDS);\n @Test\n public void test() throws Exception {\n   var productId = UUID.randomUUID().toString();\n   ProductStock productStock = new ProductStock(10);\n   var res = webClient.post()\n               .uri(\"/product-stock/%s/create\".formatted(productId))\n               .bodyValue(productStock)\n               .retrieve()\n               .toEntity(String.class)\n               .block(timeout);\n   var getProductStock = webClient.get()\n           .uri(\"/product-stock/%s/get\".formatted(productId))\n           .retrieve()\n           .toEntity(ProductStock.class)\n           .block(timeout)\n           .getBody();\n   assertEquals(productStock.quantity(),getProductStock.quantity());\n }\n}\n```\nRun the integration test:\n```\nmvn -Pit verify\n```\n### Run locally in prod-like environment\nRun Kalix service locally:\n```\nmvn kalix:runAll\n```\nThis command runs the Kalix service locally and exposes it on `localhost:9000`.\n#### Test\nTesting using `CURL`:\n1. Create product:\n```\ncurl -XPOST -d '{ \n  \"quantity\": 10\n}' http://localhost:9000/product-stock/apple/create -H \"Content-Type: application/json\"\n```\nResult:\n```\n\"OK\"\n```\n2. Get product:\n```\ncurl -XGET http://localhost:9000/product-stock/apple/get\n```\nResult:\n```\n{\"quantity\":10}\n```\n3. Update product:\n```\ncurl -XPUT -d '{\n\"quantity\": 20\n}' http://localhost:9000/product-stock/apple/update -H \"Content-Type: application/json\"\n```\nResult:\n```\n\"OK\"\n```\n4. Delete product:\n```\ncurl -XDELETE http://localhost:9000/product-stock/apple/delete\n```\nResult:\n```\n\"OK\"\n```\n### Deploy and run on Kalix Platform on Cloud Provider of your choice \n1. Install Kalix CLI\nhttps://docs.kalix.io/kalix/install-kalix.html\n2. Kalix CLI \n   1. Register (FREE)\n    ```\n    kalix auth signup\n    ```\n    **Note**: The above command will open a browser where registration information can be filled in\u003cbr\u003e\n   2. Login\n    ```\n    kalix auth login\n    ```\n    **Note**: The above command will open a browser where authentication approval needs to be provided\u003cbr\u003e\n\n   3. Check your organization name\n   ```\n   kalix organization list\n   ```\n   Execute the above command to view a list of organizations associated with your account. Find your organization name, likely the same as your username.\n\n   4. Create a project\n    ```\n    kalix projects new kalix-trial-java-ecommerce --region=gcp-us-east1 --organization=your-organization-name\n    ```\n    For trial projects, provide `gcp-us-east1` as the region. Replace `your-organization-name` with your actual organization name obtained from the previous step.\u003cbr\u003e\n\n   5. Authenticate local docker for pushing docker image to `Kalix Container Registry (KCR)`\n    ```\n    kalix auth container-registry configure\n    ```\n    **Note**: The command will output `Kalix Container Registry (KCR)` path that will be used to configure `dockerImage` in `pom.xml`\u003cbr\u003e\n\n   6. Extract Kalix user `username`\n   ```\n   kalix auth current-login\n   ```\n   **Note**: The command will output Kalix user details and column `USERNAME` will be used to configure `dockerImage` in `pom.xml`\u003cbr\u003e\n\n3. Configure `dockerImage` path in `pom.xml`\nReplace `my-docker-repo` in `dockerImage` in `pom.xml` with: \u003cbr\u003e\n`Kalix Container Registry (KCR)` path + `/` + `USERNAME` + `/kalix-trial-java-ecommerce`\u003cbr\u003e\n**Example** where `Kalix Container Registry (KCR)` path is `kcr.us-east-1.kalix.io` and `USERNAME` is `myuser`:\u003cbr\u003e\n```\n\u003cdockerImage\u003ekcr.us-east-1.kalix.io/myuser/kalix-trial-java-ecommerce/${project.artifactId}\u003c/dockerImage\u003e\n```\n4. Deploy service in Kalix project:\n ```\nmvn deploy kalix:deploy\n ```\nThis command will: \n- compile the code\n- execute tests\n- package into a docker image\n- push the docker image to Kalix docker registry\n- trigger service deployment by invoking Kalix CLI\n5. Check deployment:\n```\nkalix service list\n```\nResult:\n```\nkalix service list                                                                         \nNAME                                         AGE    REPLICAS   STATUS        IMAGE TAG                     \nkalix-trial-shoppingcart                     50s    0          Ready         1.0-SNAPSHOT                  \n```\n**Note**: When deploying service for the first time it can take up to 1 minute for internal provisioning\n#### Proxy connection to Kalix service via Kalix CLI\n1. Proxy connection to Kalix service via Kalix CLI\n```\nkalix service proxy kalix-trial-shoppingcart\n```\nProxy Kalix CLI command will expose service proxy connection on `localhost:8080`.\n#### Test\nTesting using `CURL`:\n1. Create product:\n```\ncurl -XPOST -d '{ \n  \"quantity\": 10\n}' http://localhost:8080/product-stock/apple/create -H \"Content-Type: application/json\"\n```\nResult:\n```\n\"OK\"\n```\n2. Get product:\n```\ncurl -XGET http://localhost:8080/product-stock/apple/get\n```\nResult:\n```\n{\"quantity\":10}\n```\n3. Update product:\n```\ncurl -XPUT -d '{\n\"quantity\": 20\n}' http://localhost:8080/product-stock/apple/update -H \"Content-Type: application/json\"\n```\nResult:\n```\n\"OK\"\n```\n4. Delete product:\n```\ncurl -XDELETE http://localhost:8080/product-stock/apple/delete\n```\nResult:\n```\n\"OK\"\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend%2Fkalix-trial-java-shoppingcart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightbend%2Fkalix-trial-java-shoppingcart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend%2Fkalix-trial-java-shoppingcart/lists"}