{"id":19891352,"url":"https://github.com/lightbend/kalix-city-temperature-aggregation-spring","last_synced_at":"2025-03-01T05:17:17.964Z","repository":{"id":132604444,"uuid":"604750334","full_name":"lightbend/kalix-city-temperature-aggregation-spring","owner":"lightbend","description":null,"archived":false,"fork":false,"pushed_at":"2023-05-04T19:35:28.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-01-11T19:36:03.685Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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,"publiccode":null,"codemeta":null}},"created_at":"2023-02-21T17:58:52.000Z","updated_at":"2025-01-10T12:58:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"ec57d9e5-64e3-4a29-b101-1adb847043e3","html_url":"https://github.com/lightbend/kalix-city-temperature-aggregation-spring","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-city-temperature-aggregation-spring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-city-temperature-aggregation-spring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-city-temperature-aggregation-spring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend%2Fkalix-city-temperature-aggregation-spring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lightbend","download_url":"https://codeload.github.com/lightbend/kalix-city-temperature-aggregation-spring/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241317693,"owners_count":19943203,"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.977Z","updated_at":"2025-03-01T05:17:17.953Z","avatar_url":"https://github.com/lightbend.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kalix Demo - City Temperature Aggregator - Spring\nNot supported by Lightbend in any conceivable way, not open for contributions.\u003cbr\u003e\n## Prerequisite\nJava 17\u003cbr\u003e\nApache Maven 3.6 or higher\u003cbr\u003e\n[Kalix CLI](https://docs.kalix.io/kalix/install-kalix.html) \u003cbr\u003e\nDocker 20.10.8 or higher (client and daemon)\u003cbr\u003e\nContainer registry with public access (like Docker Hub)\u003cbr\u003e\nAccess to the `gcr.io/kalix-public` container registry\u003cbr\u003e\ncURL\u003cbr\u003e\nIDE / editor\u003cbr\u003e\n\n## Create Kalix Java/Spring maven project\n\n```\nmvn \\\narchetype:generate \\\n-DarchetypeGroupId=io.kalix \\\n-DarchetypeArtifactId=kalix-spring-boot-archetype \\\n-DarchetypeVersion=LATEST\n```\nDefine value for property 'groupId': `com.example`\u003cbr\u003e\nDefine value for property 'artifactId': `city-temperature-aggregation-spring`\u003cbr\u003e\nDefine value for property 'version' 1.0-SNAPSHOT: :\u003cbr\u003e\nDefine value for property 'package' io.kx: : `com.example.city`\u003cbr\u003e\n\n## Import generated project in your IDE/editor\n\n##Define data model/structure\n1. Create `com.example.city.Model` interface\n2. Add `TemperatureRecord` Java Record\n3. Add `City` Java Record\n4. Add all helper methods to `City` Java Record (excluding event handler helper methods)\n5. Create `com.example.city.input.InputTemperatureMessage` Java Record\n6. Create `com.example.city.output.AggregatedTemperatureMessage` Java Record\n7. Add all events to `com.example.city.Model`\n8. Add event handler helper methods to `City` Java Record\n9. Add external api data model\n10. Add internal api data model\n\n##Implement entity\nEntity is modeled per one instance (in our case city).\u003cbr\u003e\n1. Create class `com.example.city.CityEntity`\n2. Class needs to extend `EventSourcedEntity\u003cModel.City\u003e` to get support for event sourcing\n3. Add class level annotation: `@RequestMapping(\"/city/{cityId}\")`\n4. Add `private final String cityId` and constructor for injecting `EventSourcedEntityContext` \n5. Add `EntityType` and `EntityKey` annotations\n6. Add `emptyState` helper method\n7. Add entity endpoints without the body: `create`, `addTemperature`, `get` and `aggregationTimeWindowTimerTrigger`\n8. Annotate each method with spring web bind annotation\n9. Add body to each method\n\n##Implement Subscriber Action\n1. Create class `com.example.city.input.InputTopicSubscriberAction`\n2. Class needs to extend `Action`\n3. Add `@Subscribe.Topic(\"input\")` annotation (exclude `@Profile(\"prod\")`)\n4. Add `onTemperature` method without body\n5. Add `private KalixClient kalixClient;` and inject via constructor \n6. Implement `onTemperature` method body\n\n##Implement Publisher Action\n1. Create class `com.example.city.output.OutputTopicPublisherAction`\n2. Class needs to extend `Action`\n3. Add `@Subscribe.EventSourcedEntity(value = CityEntity.class,ignoreUnknown = true)` annotation (exclude `@Profile(\"prod\")`)\n4. Add `onTemperatureAggregatedEvent` method with body\n5. Add method annotation `@Publish.Topic(\"output\")`\n\n##Implement Time Window Timer\n1. Create class `com.example.city.AggregationTimeWindowTimerAction`\n2. Class needs to extend `Action`\n3. Add `@Subscribe.EventSourcedEntity(value = CityEntity.class,ignoreUnknown = true)` annotation\n4. Add `onTemperature` and `onAggregationFinishedEvent` methods without body\n5. Add `private KalixClient kalixClient;` and inject via constructor\n6. Add helper method `getTimerName`\n7. Implement `onTemperature` and `onAggregationFinishedEvent` methods body\n\n##Unit tests\n1. Create `src/test` folder\n2. Create Java package `com.example.city`\n3. Create class `UnitTest`\n4. Add `aggregateByQuantity`\n5. Add `aggregateByTime`\n6. Runt the unit test with ```mvn test```\n\n##Integration test\n1. In `src/it/java/` edit class `com.example.city.IntegrationTest`\n2. Add `private Duration timeout = Duration.of(5, ChronoUnit.SECONDS);`\n3. Add `add` and `get` helper methods\n4. Add `aggregateByQuantity`\n5. Add `aggregateByTime`\n6. Add class level annotation `@Profile(\"test\")`\n7. Action to subscribe and publish to message broker are not supported, for now, by Kalix Integratiokn test so Actions need to be disabled by adding class level annotation `@Profile(\"prod\")` to `com.example.city.input.InputTopicSubscriberAction` and `com.example.city.output.OutputTopicPublisherAction`\n6. Runt the unit test with ```mvn -Pit verify```\n\n\n#Local test\n##Prune docker (optional)\n```\ndocker system prune \n```\n\n##Setup local Zookeeper and Kafka\n1. Copy `docker-compose-kafka.yaml` from the reference project\n2. Start kafka and zookeeper \u003cbr\u003e\n```\ndocker-compose -f docker-compose-kafka.yaml up\n```\n3. Create Kafka topics \u003cbr\u003e\n[Open Kafka UI](http://localhost:8081/)\n   1. Create input topic: `input` (number of partitions: 2, Min Sync replicas: 1, Replication factor: 1, Time to retain data: 10000)\n   2. Create output topic: `output`(number of partitions: 2, Min Sync replicas: 1, Replication factor: 1, Time to retain data: 60000)\n   \n##Setup Kalix Proxy\n1. Copy `docker-compose.yaml` from the reference project\n2. Start Kalix proxy\n```\ndocker-compose up\n```\n##Start user function\n```\nmvn clean compile exec:exec\n```\n##Create one city\n```\ncurl -XPOST -d '{ \n  \"name\": \"Rotterdam\",\n  \"aggregationLimit\": 2,\n  \"aggregationTimeWindowSeconds\": 60000\n}' http://localhost:9000/city/rotterdam/create -H \"Content-Type: application/json\"\n```\nNote: `aggregationLimit` is 2 and `aggregationTimeWindowSeconds` is 1min\n\n##Optional CURLs\n1. Add temperature manually (optional for test):\n```\ncurl -XPOST -d '{ \n  \"recordId\": \"11111\",\n  \"temperature\": 20\n}' http://localhost:9000/city/rotterdam/add-temperature -H \"Content-Type: application/json\"\n```\n2. Get city (optional for test):\n```\ncurl -XGET http://localhost:9000/city/rotterdam -H \"Content-Type: application/json\"\n```\n\n##Test\n[Open Kafka UI](http://localhost:8081/)\n\u003cbr\u003e\nProduce message to `input` topic\n- key does not need to be populated\n- value: \n```\n{\n\t\"cityId\": \"rotterdam\",\n\t\"recordId\": \"22226\",\n\t\"temperature\": \"10\",\n\t\"timestamp\": \"2023-02-16T20:00:00.000Z\"\n}\n```\n- header:\n```\n{\n\t\"ce-source\": \"manual\",\n\t\"ce-datacontenttype\": \"application/json\",\n\t\"ce-specversion\": \"1.0\",\n\t\"ce-type\": \"InputTemperatureMessage\",\n\t\"ce-id\": \"22226\",\n\t\"ce-time\": \"2023-02-16T20:00:00.000Z\",\n\t\"Content-Type\": \"application/json\"\n}\n```\n\n#Kalix deployment \u0026 test\n##Confluent Cloud setup (using free tier)\n1. Register: https://www.confluent.io/confluent-cloud/tryfree/\n2. Add cluster:\n   1. Cluster type: `Basic`\n   2. Region/zones: `Google Cloud`, `N.Virginia (us-east4)`, Availability: `Single zone`\n   3. Skip payment\n   4. Cluster name: `kalix`\n3. Create topics:\n   1. `input` topic:\n      1. Topic name: `input`\n      2. Partitions: `2`\n      3. Show advanced settings - Retention time: `1 hour` (just to have for testing)\n   2. `output` topic:\n      4. Topic name: `output`\n      5. Partitions: `2`\n      6. Show advanced settings - Retention time: `1 hour` (just to have for testing)\n4. Export connection configuration\n   1. Clients - `New client`\n   2. Choose language: `Java`\n   3. `Create Kafka cluster API key`\n   4. `Copy`\n   5. Create file `confluent-kafka.properties` and paste copied content in\n\n##Kalix project setup\n1. Register for free\n2. Create Kalix project:\n```\nkalix projects new city-temperature-aggregation --region gcp-us-east1\n```\n3. Set Kalix project in Kalix CLI\n```\nkalix config set project city-temperature-aggregation\n```\n4. Configure confluent kafka message broker in the Kalix project\n```\nkalix projects config set broker --broker-service kafka --broker-config-file confluent-kafka.properties\n```\n##Deploy to Kalix\n###Configure container registry\n`Note`: The most simple setup is to use public `hub.docker.com`. You just need to replace in `pom.xml`, `my-docker-repo` with your `dockerId`\u003cbr\u003e\n\u003cbr\u003e\nMore options can be found here: \u003cbr\u003e\nhttps://docs.kalix.io/projects/container-registries.html\n###Deploy\n```\nmvn deploy\n```\n`Note`: First deployment take few minutes for all required resources to be provisioned\n##Kalix connection proxy\nAll services by default are not exposed to Internet and only local/private access is allowed. For local/private access Kalix connection proxy is used.\u003cbr\u003e\nCreate Kalix connection proxy:\n```\nkalix service proxy city-temperature-aggregation-spring\n```\n\n##Create one city\n```\ncurl -XPOST -d '{ \n  \"name\": \"Rotterdam\",\n  \"aggregationLimit\": 2,\n  \"aggregationTimeWindowSeconds\": 60000\n}' http://localhost:8080/city/rotterdam/create -H \"Content-Type: application/json\"\n```\nNote: `aggregationLimit` is 2 and `aggregationTimeWindowSeconds` is 1min\n\n##Optional CURLs\n1. Add temperature manually (optional for test):\n```\ncurl -XPOST -d '{ \n  \"recordId\": \"11111\",\n  \"temperature\": 20\n}' http://localhost:8080/city/rotterdam/add-temperature -H \"Content-Type: application/json\"\n```\n2. Get city (optional for test):\n```\ncurl -XGET http://localhost:8080/city/rotterdam -H \"Content-Type: application/json\"\n```\n\n##Test\nUnfortunately Confluent Cloud Console does not have an option to produce a message to a topic with headers so that feature can not be used.\u003c\u003e\nSo we are going to use REST API to produce an input message.\n\n1. [Open Confluent Cloud](https://confluent.cloud/environments)\n2. Client - `New client`\n3. Choose your language: `REST API`\n4. `Produce records using the Confluent Cloud REST API`\n   1. Topic name: `input`\n   2. Mode: `Non-streaming mode`\n   3. `Create Kafka cluster API key`\n   4. `Copy`\n5. Produce message:\nFrom copied CURL command replace set `-d @input_message.json`\n```\n curl \\\n  -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Basic XXXXXX\" \\\n  https://\u003cURL\u003e:443/kafka/v3/clusters/lkc-nw619k/topics/input/records \\\n  -d @input_message.json \n\n```\n`Note`: Header values need to be base64 encoded but you only need to change the `value` in json structure.\u003cbr\u003e\n6. Validate\u003cbr\u003e\nCheck in Confluent cloud messages in `output` topic (You need to search by offset to be able to see all messages because consumer is configured with ). \u003cbr\u003e\nCheck the service logs:\n```\nkalix service logs city-temperature-aggregation-spring\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend%2Fkalix-city-temperature-aggregation-spring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightbend%2Fkalix-city-temperature-aggregation-spring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend%2Fkalix-city-temperature-aggregation-spring/lists"}