{"id":27974607,"url":"https://github.com/strapdata/basketapp","last_synced_at":"2025-08-18T08:11:04.026Z","repository":{"id":102897732,"uuid":"173839871","full_name":"strapdata/basketapp","owner":"strapdata","description":"Micronaut reactive demo application using Elassandra","archived":false,"fork":false,"pushed_at":"2019-10-13T19:25:33.000Z","size":401,"stargazers_count":8,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-08T00:24:32.968Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"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/strapdata.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,"zenodo":null}},"created_at":"2019-03-04T23:47:06.000Z","updated_at":"2020-08-04T19:54:47.000Z","dependencies_parsed_at":null,"dependency_job_id":"cf2055cc-4c65-47fd-aaee-af32b5041f72","html_url":"https://github.com/strapdata/basketapp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/strapdata/basketapp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapdata%2Fbasketapp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapdata%2Fbasketapp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapdata%2Fbasketapp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapdata%2Fbasketapp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strapdata","download_url":"https://codeload.github.com/strapdata/basketapp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strapdata%2Fbasketapp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270962391,"owners_count":24675965,"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","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-05-08T00:18:56.627Z","updated_at":"2025-08-18T08:11:04.017Z","avatar_url":"https://github.com/strapdata.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"This is a sample project to illustrate this [Medium article](https://medium.com/@vroyer/testing-reactive-microservices-with-micronaut-elassandra-and-junit5-970963f652bb)\n\n# How to use\n\n* To run the sample described in the article (see below) by the test, simple run `gradlew test` with a **java 8** version\n* If you want to run this sample as an application :\n    * Start an Elassandra cluster by following [this documentation](http://doc.elassandra.io/en/latest/installation.html#connect-to-cassandra-from-an-application-in-another-docker-container) \n        * \u003e If you use the docker way, \n          \u003e  * don't forget to expose the used ports (see applciation.yml) : add `-p 9042:9042 -p 9200:9200` to your **docker run** cmd\n          \u003e  * activate the ElasticQueryHandler by adding : `-e JVM_OPTS=\"-Dcassandra.custom_query_handler_class=org.elassandra.index.ElasticQueryHandler\"`\n          \u003e  ```shell\n          \u003e      docker run --name some-elassandra --rm -p 9042:9042 -p 9200:9200 -e JVM_OPTS=\"-Dcassandra.custom_query_handler_class=org.elassandra.index.ElasticQueryHandler\" -dti strapdata/elassandra:latest\n          \u003e  ```\n    * When your cluster is running \n        * insert the baskets data (under src/test/resources/basket1.json) into elassandra db : \n            * enter the cqlsh CLI by running `docker exec -it some-elassandra cqlsh`\n            * exec an INSERT INTO instruction with the JSON file content :\n        \n            ```sql\n              INSERT INTO baskets.baskets JSON '{   \"id\": \"fe2ace53-69d6-4f11-93b1-e0fde68a95cc\",   \"store_code\": \"1\",   \"basket_status\": \"Finished\",   \"processing_date\": 1551773163133,   \"items\": [     {       \"product_qty\": 1,       \"amount_paid\": 1,       \"product_code\": \"1\"     },     {       \"product_qty\": 2,       \"amount_paid\": 2,       \"product_code\": \"2\"     },     {       \"product_qty\": 3,       \"amount_paid\": 3,       \"product_code\": \"3\"     }   ] }';\n            ```\n    \n        * or use the Elastic way (in your project root) : \n                \n            ```shell\n                curl -XPUT -H Content-Type:application/json  \"http://localhost:9200/baskets/baskets/fe2ace53-69d6-4f11-93b1-e0fde68a95cc\" -d@src/test/resources/basket1.json\n            ```\n    * run the app with `./gradlew run`\n    * do a search on the API :\n        ```shell\n            curl http://localhost:8080/basket/search\n            curl http://localhost:8080/basket/search?product_code=3\n        ```\n     \n# Testing reactive microservices with Micronaut, Elassandra and JUnit5\n\n[Micronaut](https://micronaut.io/) is a new JVM-based framework designed to make creating microservices quick and easy. One of the most exciting\nfeatures of Micronaut is its support for [reactive programming](http://reactivex.io/) and we will see in this article how to store and search\ndata in [Elassandra](https://www.elassandra.io/) in a reactive way through the Cassandra driver and run unit tests with Junit5.\n\n![mirconaut-elassandra-junit5](images/micronaut-elassandra-junit5.png)\n\nTo illustrate these features, here is a basic demo application **basketapp** (available on [github](https://github.com/strapdata/basketapp)), \nstoring sales receipt in Elassandra, that is, stored in Cassandra and automatically indexed in Elasticsearch. A REST service implemented as a Micronaut \ncontroller expose endpoints to retrieve sales receipts by id or by store and product code. \n\nHere a basic JSON sales receipt with 3 products items:\n\n```json\n{\n  \"id\": \"fe2ace53-69d6-4f11-93b1-e0fde68a95cc\",\n  \"store_code\": \"1\",\n  \"basket_status\": \"Finished\",\n  \"processing_date\": 1551773163133,\n  \"items\": [\n    {\n      \"product_qty\": 1,\n      \"amount_paid\": 1,\n      \"product_code\": \"1\"\n    },\n    {\n      \"product_qty\": 2,\n      \"amount_paid\": 2,\n      \"product_code\": \"2\"\n    },\n    {\n      \"product_qty\": 3,\n      \"amount_paid\": 3,\n      \"product_code\": \"3\"\n    }\n  ]\n}\n```\n\n## Cassandra object mapping\n\nIn order to handle conversion between Cassandra types and custom Java object and generate CQL queries,\nthe java Cassandra driver provides a [Cassandra mapper](https://docs.datastax.com/en/developer/java-driver/3.5/manual/object_mapper/using/).\nBy combining [Jackson](https://github.com/FasterXML/jackson),\n[Lombok](https://projectlombok.org/) and Cassandra mapper java annotations in \nthe Basket POJO object, we get both JSON serialization and Cassandra Data Access Object.\n\n```java\n@Table(name = \"baskets\",\n    readConsistency = \"LOCAL_ONE\",\n    writeConsistency = \"LOCAL_ONE\",\n    caseSensitiveKeyspace = false,\n    caseSensitiveTable = false)\n@Data\n@Builder\n@Wither\n@ToString(includeFieldNames=true)\n@AllArgsConstructor\n@NoArgsConstructor\n@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class Basket {\n    @PartitionKey(0)\n    UUID id;\n\n    @Column(name = \"store_code\")\n    @JsonProperty(\"store_code\")\n    String storeCode;\n\n    @Column(name = \"basket_status\")\n    @JsonProperty(\"basket_status\")\n    BasketStatus basketStatus;\n\n    @Column(name = \"processing_date\")\n    @JsonProperty(\"processing_date\")\n    Date processingDate;\n\n    List\u003cBasketItem\u003e items;\n}\n```\n\nIn the same way, the **BasketItem** is mapped to a [Cassandra User Defined Type](https://docs.datastax.com/en/cql/3.3/cql/cql_using/useCreateUDT.html) as shown bellow:\n \n```java\n@UDT(name=\"basket_item\")\n@Data\n@Builder\n@Wither\n@ToString(includeFieldNames=true)\n@AllArgsConstructor\n@NoArgsConstructor\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class BasketItem {\n\n    @Field(name = \"product_qty\")\n    @JsonProperty(\"product_qty\")\n    private Integer productQuantity;\n\n    @Field(name = \"amount_paid\")\n    @JsonProperty(\"amount_paid\")\n    private Double amountPaid;\n\n    @Field(name = \"product_code\")\n    @JsonProperty(\"product_code\")\n    private String productCode;\n}\n```\n\nFinally, **BascketStatus**, a java enum, is managed through a registred codec as described in the \n[driver documentation](https://docs.datastax.com/en/developer/java-driver/3.5/manual/custom_codecs/extras/#Enums). \nUnfortunately, the java Cassandra mapper cannot generate the CQL schema, so we need to write it manually:\n\n```bash\nCREATE TYPE IF NOT EXISTS basket_item (\n    product_code  text,\n    product_qty   int,\n    amount_paid   double\n);\n\nCREATE TABLE IF NOT EXISTS baskets (\n    id              uuid PRIMARY KEY,\n    store_code      text,\n    basket_status   text,\n    processing_date timestamp,\n    total_paid      double,\n    items           list\u003cfrozen\u003cbasket_item\u003e\u003e,\n\n    es_query text,\n    es_options text\n);\n```\n\nTo manage [CQL mappers](https://docs.datastax.com/en/developer/java-driver/3.5/manual/object_mapper/)  \nand initialize the CQL schema and Elasticsearch indices, we use a Micronaut bean [ElassandraStorage](https://github.com/strapdata/basketapp/blob/master/src/main/java/com/strapdata/basketapp/ElassandraStorage.java).\nthat use the [Elasticsearch REST Client](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-create-index.html) java API.\nTo put it simply, we have run an [Elassandra discover](https://elassandra.readthedocs.io/en/latest/mapping.html#bi-directional-mapping) \nthrough the [Create Index API](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-create-index.html) \nto create the Elasticsearch index **baskets** with a mapping automatically generated from the CQL schema.\n\n\n## Elasticsearch query over CQL\n\nElassandra closely integrates the Elasticsearch code and since version 6.2.3.11+, support for Elasticsearch query \nover the Cassandra driver is opensource, meaning that you can query Elasticsearch through the various CQL driver implementations, with\nseveral advantages:\n* Reuse the same DAO's in your application when retrieving data from Cassandra or Elasticsearch.\n* No JSON overhead, query results are sent back to the application in binary.\n* The CQL paging automatically manages Elasticsearch scrolling, and the last page close the scroll context.\n* The CQL driver acts as a load balancer and know about load and availability of Elassandra nodes.\n* When authentication is enabled, the CQL driver manage authentication at a session level while per HTTP request authentication involve an overhead.\n\nTo send the Elasticsearch search requests to an Elassandra coordinator node, we need two dummy Cassandra \ncolumns **es_query** and **es_options**. The Elasticsearch results comes back as Cassandra rows:\n\n```sql\n[cqlsh 5.0.1 | Cassandra 3.11.3.5 | CQL spec 3.4.4 | Native protocol v4]\nUse HELP for help.\nadmin@cqlsh\u003e SELECT * FROM baskets.baskets WHERE es_query='{\"query\":{\"term\":{\"basket_status\":\"Finished\"}}}';\n\n id                                   | basket_status | es_options | es_query | items                                                                                                                                                           | processing_date                 | store_code | total_paid\n--------------------------------------+---------------+------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------+------------+------------\n fe2ace53-69d6-4f11-93b1-e0fde68a95cc |      Finished |       null |     null | [{product_code: '1', product_qty: 1, amount_paid: 1}, {product_code: '2', product_qty: 2, amount_paid: 2}, {product_code: '3', product_qty: 3, amount_paid: 3}] | 2019-03-05 08:06:03.133000+0000 |          1 |       null\n\n(1 rows)\nadmin@cqlsh\u003e\n```\n\nThe Cassandra [Accessors](https://docs.datastax.com/en/developer/java-driver/3.5/manual/object_mapper/using/#Accessors)\nannotation provides a nice way to map such custom queries. \nThe CQL LIMIT clause manage the number of results returned, equivalent to the Elasticsearch query size.\n\n```java\n@Accessor\npublic interface BasketAccessor {\n\n    @Query(\"SELECT * FROM baskets WHERE es_query = ? AND es_options='indices=baskets' LIMIT 500 ALLOW FILTERING\")\n    ListenableFuture\u003cResult\u003cBasket\u003e\u003e getByElasticsearchQueryAsync(String esQuery);\n\n\n    public static String storeAndProductQuery(String storeCode, String productCode) {\n        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();\n\n        if (storeCode != null)\n            queryBuilder.filter(QueryBuilders.termQuery(\"store_code\", storeCode));\n\n        if (productCode != null)\n            queryBuilder.filter(QueryBuilders.nestedQuery(\"items\", QueryBuilders.termQuery(\"items.product_code\", productCode), ScoreMode.Avg));\n\n        if (!queryBuilder.hasClauses())\n            queryBuilder.should(QueryBuilders.matchAllQuery());\n\n        return new SearchSourceBuilder().query(queryBuilder).toString(ToXContent.EMPTY_PARAMS);\n    }\n}\n```\n\nA static helper method based on the \n[Elasticsearch REST High-Level](https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html) \nAPI provides an easy way to build Elasticsearch queries, here a boolean query with two clauses, a term query and a nested query.\n\n## Micronaut Reactive Data Access\n\nMicronaut supports any framework that implements Reactive Streams, including RxJava, and Reactor.\nAs said in the documentation, if your controller method returns a non-blocking type then Micronaut will use the Event \nloop thread to subscribe to the result. \n\nIn our Micronaut basket controller, [ListenableFutures](https://google.github.io/guava/releases/21.0/api/docs/com/google/common/util/concurrent/ListenableFuture.html) \nreturned by the Cassandra driver are converted to reactive types such as Single or Observable. \n\n```java\n@Controller(\"/basket\")\n@Validated\npublic class BasketController {\n\n    private static final Logger logger = LoggerFactory.getLogger(BasketController.class);\n\n    ElassandraStorage storage;\n    BasketAccessor    basketAccessor;\n\n    public BasketController(ElassandraStorage storage) {\n        this.storage = storage;\n        this.basketAccessor = storage.getMappingManager().createAccessor(BasketAccessor.class);\n    }\n\n    @Get(\"/\")\n    public HttpStatus index() {\n        return HttpStatus.OK;\n    }\n\n    @Get(uri = \"/{id}\")\n    public Maybe\u003cBasket\u003e getById(@QueryValue(\"id\") UUID id) {\n        return Maybe.fromFuture(storage.getMapper(Basket.class).getAsync(id));\n    }\n\n    @Get(uri = \"/search\")\n    public Single\u003cList\u003cBasket\u003e\u003e getByStoreAndProduct(@Nullable @QueryValue(\"store_code\") String storeCode, @Nullable @QueryValue(\"product_code\") String productCode) {\n        String esQuery = BasketAccessor.storeAndProductQuery(storeCode, productCode);\n        return Single.fromFuture(new TransformedListenableFuture\u003cResult\u003cBasket\u003e, List\u003cBasket\u003e\u003e(this.basketAccessor.getByElasticsearchQueryAsync(esQuery), Result::all));\n    }\n}\n```\n\nThe [TransformedListenableFuture](https://github.com/strapdata/basketapp/blob/master/src/main/java/com/strapdata/basketapp/utils/TransformedListenableFuture.java)\nwraps the ListenableFuture\u003cResult\u003cX\u003e\u003e to convert the result by applying a mapper function, \nhere [Result::all](https://docs.datastax.com/en/drivers/java/3.5/index.html?com/datastax/driver/mapping/Mapper.html). \n \n## Junit5 Elassandra Tests\n\nThe [Micronaut Testing Framework extensions](https://micronaut-projects.github.io/micronaut-test/latest/guide/index.html#introduction) \nincluded support for [JUnit 5](https://junit.org/junit5/), the next generation of JUnit. To test our Cassandra\nand Elasticsearch queries, we wanted to use [Elassandra-Unit](https://github.com/strapdata/elassandra-unit) to run\nan embedded Elassandra node during unit tests.\n\n![Elassandra-Unit](images/elassandra-unit.png)\n\nNevertheless, in order to use the [Elassandra-Unit](https://github.com/strapdata/elassandra-unit) based on Junit 4, \nwe need to implements some Junit 5 extensions to trigger before and after test operations. \n\nFirst, an ElassandraCQLUnit5 extension to start an embedded Elassandra node \nwhere we set the Cassandra system property **cassandra.custom_query_handler_class** to \nto enable support for Elasticsearch query over CQL. This could be done in the **build.gradle**, but it won't be set when\nlaunching tests from IntelliJ IDEA, so this is more practical like that.\n\n```Java\npublic class ElassandraCQLUnit5 extends CassandraCQLUnit implements BeforeEachCallback, AfterEachCallback {\n\n    public ElassandraCQLUnit5() {\n        super(new SimpleCQLDataSet(\"SELECT * FROM system.peers\", true, false, ElassandraStorage.KEYSPACE));\n\n        // enable the Elasticsearch CQL query handler\n        System.setProperty(\"cassandra.custom_query_handler_class\",\"org.elassandra.index.ElasticQueryHandler\");\n    }\n\n    @Override\n    public void afterEach(ExtensionContext context) throws Exception {\n        super.after();\n    }\n\n    @Override\n    public void beforeEach(ExtensionContext context) throws Exception {\n        super.before();\n    }\n}\n```\n\nThen, our **BasketControllerTest** also implements a JUnit5 extension to open and cleanup Elassandra node before and \nafter each tests. The **testElassandraStorage** tests our elasticsearch nested query on the baskets index.\n\n```Java\n@MicronautTest\n@ExtendWith(ElassandraCQLUnit5.class)\n@ExtendWith(BasketControllerTest.class)\npublic class BasketControllerTest implements BeforeEachCallback, AfterEachCallback {\n\n    private static EmbeddedServer server;\n    private static ElassandraStorage storage;\n\n    @Inject\n    ObjectMapper mapper;\n\n    @BeforeAll\n    public static void setupServer() {\n        server = ApplicationContext.run(EmbeddedServer.class);\n        storage = server.getApplicationContext().findBean(ElassandraStorage.class).get();\n    }\n\n    @AfterAll\n    public static void stopServer() {\n        if (server != null) {\n            server.stop();\n        }\n    }\n\n    @Override\n    public void beforeEach(ExtensionContext context) throws Exception {\n        storage.open();\n    }\n\n    @Override\n    public void afterEach(ExtensionContext context) throws Exception {\n        storage.cleanup();\n    }\n\n    public static final Basket DEMO_BASKET1 = new Basket()\n        .withId(UUID.randomUUID())\n        .withBasketStatus(BasketStatus.Finished)\n        .withProcessingDate(new Date())\n        .withStoreCode(\"1\")\n        .withItems(Lists.newArrayList(\n            new BasketItem().withProductCode(\"1\").withAmountPaid(1.0).withProductQuantity(1),\n            new BasketItem().withProductCode(\"2\").withAmountPaid(2.0).withProductQuantity(2),\n            new BasketItem().withProductCode(\"3\").withAmountPaid(3.0).withProductQuantity(3)\n        ));\n\n    public static final Basket DEMO_BASKET2 = new Basket()\n        .withId(UUID.randomUUID())\n        .withBasketStatus(BasketStatus.Finished)\n        .withProcessingDate(new Date())\n        .withStoreCode(\"1\")\n        .withItems(Lists.newArrayList(\n            new BasketItem().withProductCode(\"1\").withAmountPaid(1.0).withProductQuantity(1)\n        ));\n\n    \n    @Test\n    public void testElassandraStorage() {\n        storage.getMapper(Basket.class).save(DEMO_BASKET1);\n        storage.getMapper(Basket.class).save(DEMO_BASKET2);\n\n        BasketController controller = server.getApplicationContext().getBean(BasketController.class);\n        Basket basket = controller.getById(DEMO_BASKET1.getId()).blockingGet();\n        assertEquals(DEMO_BASKET1, basket);\n\n        List\u003cBasket\u003e basketWithProduct1 = controller.getByStoreAndProduct(null,\"1\").blockingGet();\n        assertEquals(2, basketWithProduct1.size());\n        assertTrue( basketWithProduct1.contains(DEMO_BASKET1));\n        assertTrue( basketWithProduct1.contains(DEMO_BASKET2));\n\n        List\u003cBasket\u003e basketWithProduct2 = controller.getByStoreAndProduct(\"1\",\"2\").blockingGet();\n        assertEquals(1, basketWithProduct2.size());\n        assertTrue( basketWithProduct2.contains(DEMO_BASKET1));\n    }\n    \n    ...\n}\n```\n\nElassandra-Unit (like [Cassandra-Unit](https://github.com/jsevellec/cassandra-unit) developed by Jeremy Sevellec) \nuse the CQL port **9142** (not the default 9042/tcp) and cluster name **\"Test Cluster\"** (defined in a resource file of elassandra-unit), \nthe **test/resources/application.yaml** :\n\n```yaml\ncassandra:\n    default:\n        clusterName: \"Test Cluster\"\n        contactPoint: localhost\n        port: 9142\n        maxSchemaAgreementWaitSeconds: 30\n        ssl: false\n```\n\nFinally, tests are successful with our embedded Elassandra node:\n\n![Elassandra-basketapp-jacoco](images/basketapp-jacoco-report.png)\n\n## Kubernetes deployment\n\nAs a takeway, the [gradle JIB plugin](https://github.com/GoogleContainerTools/jib) quickly containerize the **basketapp**\napplication and publish the docker image to a docker registry:\n\n```bash\n./gradlew clean jib --image strapdata/basketapp:0.1\n```\n\nThen, you can deploy both Elassandra (Using the [Elassandra HELM Chart](https://github.com/strapdata/helm-charts)) and \nthe **basketapp** on Kubernetes with a service and a deployment:\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: basketapp\n  name: basketapp\n  namespace: default\nspec:\n  ports:\n  - port: 8080\n    protocol: TCP\n    targetPort: 8080\n    name: basketapp\n  selector:\n    app: basketapp\n  sessionAffinity: None\n  type: ClusterIP\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: basketapp-deployment\n  labels:\n    app: basketapp\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: basketapp\n  template:\n    metadata:\n      labels:\n        app: basketapp\n    spec:\n      imagePullSecrets:\n      - name: acr-auth\n      containers:\n      - name: basketapp\n        image: strapdata/basketapp:0.1\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 8080\n        env:\n        - name: CASSANDRA_CLUSTERNAME\n          value: elassandra\n        - name: CASSANDRA_CONTACTPOINT\n          value: ela-cassandra\n        - name: ELASTICSEARCH_HOST\n          value: ela-elasticsearch\n```\n\nYou can also deploy Kibana for reporting and Traefik to expose the basketapp service: \n\n![Kubernetes-elassandra-traefik](images/kubernetes-elassandra-traefik.png)\n\n# Conclusion\n\nEt voilà, you get a reliable, reactive and efficient REST micro-service. Data integration tests \nare very useful in terms of making sure that our code runs correctly up to the database, and\nElassandra-Unit helps you to check both Cassandra and Elasticsearch queries.\n\nNext step is to run automated integration tests, and Kubernetes can help to dynamically create a whole\nenvironment and discard it afterward. You can have a look at \nthe [Elassandra HELM charts](https://github.com/strapdata/helm-charts) for that.\n\nFinally, this architecture is easy to scale by adding nodes (app or Elassandra pods), \nalways up during node failures or rolling upgrades. No more database to Elasticsearch synchronization \nheadache, Elassandra properly index your Cassandra data into Elasticsearch !\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrapdata%2Fbasketapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrapdata%2Fbasketapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrapdata%2Fbasketapp/lists"}