<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[find in path]]></title><description><![CDATA[Programming findings]]></description><link>https://www.findinpath.com</link><generator>GatsbyJS</generator><lastBuildDate>Mon, 05 Sep 2022 07:54:13 GMT</lastBuildDate><item><title><![CDATA[Parse JSON in Trino]]></title><description><![CDATA[This post showcases a more complex scenario which makes use of the  functions
made available in Trino. Say you are tasked with retrieving…]]></description><link>https://www.findinpath.com/parse-json-in-trino/</link><guid isPermaLink="false">https://www.findinpath.com/parse-json-in-trino/</guid><pubDate>Mon, 05 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post showcases a more complex scenario which makes use of the &lt;code class=&quot;language-text&quot;&gt;JSON&lt;/code&gt; functions
made available in &lt;a href=&quot;https://trino.io/&quot;&gt;Trino&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Say you are tasked with retrieving the number of companies by creator from records
which have the following structure&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;createdBy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token property&quot;&gt;&quot;teams&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;token property&quot;&gt;&quot;companyId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token property&quot;&gt;&quot;teamId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s setup on the fly a simple scenario via Trino:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run --rm -d --name trino -p &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;:8080 trinodb/trino&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the purpose of this exercise, we can use the &lt;a href=&quot;https://trino.io/docs/current/connector/memory.html&quot;&gt;Memory connector&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;trino&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; memory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;USE&lt;/span&gt;
trino:&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; test1 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt;
trino:&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; test1
&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;createdBy&quot;:2,&quot;teams&quot;:[{&quot;companyId&quot;:10,&quot;teamId&quot;:101},{&quot;companyId&quot;:10,&quot;teamId&quot;:102}]}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;createdBy&quot;:2,&quot;teams&quot;:[{&quot;companyId&quot;:20,&quot;teamId&quot;:203},{&quot;companyId&quot;:30,&quot;teamId&quot;:204}]}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;createdBy&quot;:3,&quot;teams&quot;:[{&quot;companyId&quot;:10,&quot;teamId&quot;:103},{&quot;companyId&quot;:40,&quot;teamId&quot;:104}]}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that we have the premises to test our scenario, we can proceed to resolve the problem at hand.&lt;/p&gt;
&lt;p&gt;When dealing with &lt;code class=&quot;language-text&quot;&gt;JSON&lt;/code&gt; data, &lt;a href=&quot;https://trino.io/docs/current/functions/json.html&quot;&gt;JSON functions and operators&lt;/a&gt;
from Trino can come in handy:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
   json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.createdBy&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.teams[*].companyId&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; ARRAY WRAPPER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; companies
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt; created_by | companies
------------+-----------
 2          | [10,10]
 2          | [20,30]
 3          | [10,40]
(3 rows)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The output for the &lt;code class=&quot;language-text&quot;&gt;companies&lt;/code&gt; column is of type &lt;code class=&quot;language-text&quot;&gt;VARCHAR&lt;/code&gt;.
We need to parse this content to an &lt;code class=&quot;language-text&quot;&gt;ARRAY&lt;/code&gt; in order to expand it via &lt;a href=&quot;https://trino.io/docs/current/sql/select.html#unnest&quot;&gt;UNNEST&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       company
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json_parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;companies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; companies
    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
         &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
             json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.createdBy&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
             json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.teams[*].companyId&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; ARRAY WRAPPER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; companies
         &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test1
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;companies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;company&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;created_by | company
------------+---------
 2          |      10
 2          |      10
 2          |      20
 2          |      30
 3          |      10
 3          |      40&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now the counting problem becomes a simple grouping problem:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; input &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
                 json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.createdBy&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                             &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                 json_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;strict $.teams[*].companyId&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; ARRAY WRAPPER&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; companies
               &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; test1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     companies_by_creator &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
                                created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json_parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;companies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; companies
                              &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     creator_to_company &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
                              created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                              company
                            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; companies_by_creator
                                   &lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;companies&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;company&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     creator_to_company_count &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
                                    created_by&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                                    &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; company&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; companies
                                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; creator_to_company
                                  &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; creator_to_company_count
&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; created_by&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt; created_by | companies
------------+-----------
 2          |         3
 3          |         2&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The query has been rewritten with common table expressions for improving its readability.&lt;/p&gt;
&lt;p&gt;Now that the demo is complete, the test environment can be stopped via:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docker stop trino&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Data Mesh showcase with dbt-trino]]></title><description><![CDATA[Perform data transformations over a Data Mesh of several databases with dbt-trino adapter for dbt. Introduction One frequently asked…]]></description><link>https://www.findinpath.com/dbt-trino-data-mesh/</link><guid isPermaLink="false">https://www.findinpath.com/dbt-trino-data-mesh/</guid><pubDate>Sun, 19 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Perform data transformations over a &lt;em&gt;Data Mesh&lt;/em&gt; of several databases with &lt;a href=&quot;https://github.com/findinpath/dbt-trino&quot;&gt;dbt-trino&lt;/a&gt; adapter for &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;One frequently asked question in the context of using &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; tool is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can I connect my dbt project to two databases?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(see the answered &lt;a href=&quot;https://docs.getdbt.com/faqs/connecting-to-two-dbs-not-allowed&quot;&gt;question&lt;/a&gt; on the dbt website).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tldr;&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; stands for transformation as in &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; within &lt;code class=&quot;language-text&quot;&gt;ELT&lt;/code&gt;  pipelines, it doesn’t move data from source to a warehouse.&lt;/p&gt;
&lt;p&gt;The creators of the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; however have added support for handling such scenarios via
&lt;a href=&quot;https://github.com/dbt-labs/dbt-presto&quot;&gt;dbt-presto&lt;/a&gt; adapter.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://trino.io/&quot;&gt;Trino&lt;/a&gt; is a &lt;a href=&quot;https://trino.io/blog/2020/12/27/announcing-trino.html&quot;&gt;fork&lt;/a&gt; of the popular
&lt;em&gt;presto&lt;/em&gt; high performance, distributed SQL query engine for big data.
This SQL query engine offers a helping hand in performing SQL queries on top of a myriad of data sources.
Trino supports talking to the common relational databases (Postgres, MySQL) and also to data sources
that don’t support SQL (AWS S3, Apache Kafka, Apache Cassandra, etc.). Feel free to check
the list of supported Trino &lt;a href=&quot;https://trino.io/docs/current/connector.html&quot;&gt;connectors&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;By using Trino, there can be queried data from fully separated databases. This makes Trino
&lt;em&gt;the analytics engine for data mesh&lt;/em&gt; (quote from &lt;a href=&quot;https://www.starburst.io/&quot;&gt;Starburst Data&lt;/a&gt; website).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cd9e3a30bffeb636fa5e4957acd1fc0f/77848/dbt-trino-architecture.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;300\&apos;%3e%3cpath%20d=\&apos;M152%2033l-12%2024c-15%2030-15%2026%200%2056%2013%2026%2012%2025%2017%2025%206%200%206%200%2015-18l7-15h10l3-7%203-6%202%203c2%202%202%203-3%2011l-4%209h-7c-8%200-8%201-2%2014l4%208h17l4-9c5-9%205-12%202-18-3-5-3-5%201-5s4%200%2011%2014c8%2017%209%2019%2013%2019%202%200%203-1%201-3l-12-22c-15-30-15-28%200-58%209-17%2011-23%2010-23l-3%202c-2%202-23%2043-23%2046-1%202-1%202-5%202-3%200-4%200-6-4-2-5-2-5%200-5%207%200%206%200%2011-9l4-12c0-4-8-20-10-20l-5%209-5%209h-7c-6%200-6%200-6%202%200%205%204%2012%207%2012l2%201-5%209-4%208h-12l4-9c4-7%204-8%207-8l3-1c0-1-14-29-16-30-2-2-9-3-11-1M53%2058c-2%201-3%203-3%205%200%203%204%207%207%207s7-4%207-7-4-7-7-7l-4%202m4%2027c-3%203-3%204-1%208%202%206%2010%205%2012-1%203-6-7-12-11-7m111%2056c-2%201-1%202%204%204%205%203%205%204-1%208-6%203-5%204%202%204%205%200%2017-2%2015-3l-1-1%2012%202c21%204%2030%203%2020-2-6-4-6-5%200-8s6-4%201-5-20%202-17%204l-14-2c-13-2-18-2-21-1m109%208c0%202-4%203-38%2014l-33%2010h-22l-33-10a865%20865%200%2001-38-11c0%204%202%205%2038%2016l33%2011%2012-1h12l32-10c38-12%2038-12%2038-17v-3l-1%201m-77%2049c-2%201-3%204-4%206-1%203-2%203-5%203-5%200-13%209-10%2012%201%200%202-1%202-3%204-8%2014-8%2017%200%201%203%201%203%202%202v-4l-2-1-2-4c-2-1-2-2-2-3l5-6c3-5%203-7-1-2m169%2061c-3%201-4%209-1%2014%204%207%209%207%2014%201%203-4%204-12%202-14s-11-2-15-1m-184%205l-2%204%202%205%203%205h12l3-5%202-5-2-4-3-5h-12l-3%205m-20-2l-2%201c-2%200-2%201-2%206s0%206%202%206l2%202h8l3-2c3-1%203-12%200-12l-3-1-4-2-4%202m-149%200c-3%204%200%2013%204%2013%202%200%203%201%203%202%201%203%202%204%204%201l4-4%201-2v-4c1-4%200-6-2-6H16m277%202c-3%203-1%2012%204%2015%208%204%2018-2%2017-11%200-4-2-6-7-5h-4v7l-1%207-6-13c1-2-2-2-3%200m50%203c-1%204%201%205%208%205%2011%200%2014-5%204-6s-11-1-12%201m-268%206v5h28v-11H75v6m197-4l1%203-2-2-1-2v5c-2%201%201%204%203%203v1l2%202c6%201%209%201%209-1h1c0%203%202%202%202%200s-1-3-3-3c-1%200-2%200-1-1l-1-1c-2%202-6%200-7-3%200-2-1-3-2-3-2%200-2%200-1%202\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;dbt-trino-architecture&quot;
        title=&quot;dbt-trino-architecture&quot;
        src=&quot;/static/cd9e3a30bffeb636fa5e4957acd1fc0f/8ff1e/dbt-trino-architecture.png&quot;
        srcset=&quot;/static/cd9e3a30bffeb636fa5e4957acd1fc0f/9ec3c/dbt-trino-architecture.png 200w,
/static/cd9e3a30bffeb636fa5e4957acd1fc0f/c7805/dbt-trino-architecture.png 400w,
/static/cd9e3a30bffeb636fa5e4957acd1fc0f/8ff1e/dbt-trino-architecture.png 800w,
/static/cd9e3a30bffeb636fa5e4957acd1fc0f/6ff5e/dbt-trino-architecture.png 1200w,
/static/cd9e3a30bffeb636fa5e4957acd1fc0f/2f950/dbt-trino-architecture.png 1600w,
/static/cd9e3a30bffeb636fa5e4957acd1fc0f/77848/dbt-trino-architecture.png 3000w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;dbt Trino Architecture Image taken from &lt;a href=&quot;https://trino.io/episodes/21.html&quot;&gt;Trino Community Broadcast&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Data Mesh&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.thoughtworks.com/radar/techniques/data-mesh&quot;&gt;Data Mesh&lt;/a&gt; is a paradigm to the data engineering domain
which provides an alternative to the common recipe of using a centralized, monolithic data warehouse.&lt;/p&gt;
&lt;p&gt;The principles on which this paradigm is being founded are quoted below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;domain-oriented decentralization of data ownership and architecture&lt;/li&gt;
&lt;li&gt;domain-oriented data served as a product&lt;/li&gt;
&lt;li&gt;self-serve data infrastructure as a platform to enable autonomous, domain-oriented data teams&lt;/li&gt;
&lt;li&gt;federated governance to enable ecosystems and interoperability.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;code class=&quot;language-text&quot;&gt;jaffle_shop&lt;/code&gt; Data Mesh&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dbt-labs/jaffle_shop/&quot;&gt;jaffle_shop&lt;/a&gt; is a self-contained project showcasing the functionality
of &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt; data warehouse transformation  tool.&lt;/p&gt;
&lt;p&gt;In the context of the project &lt;a href=&quot;https://github.com/dbt-labs/jaffle_shop/&quot;&gt;jaffle_shop&lt;/a&gt; there are being used
the domains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;customers&lt;/li&gt;
&lt;li&gt;orders (customers make orders)&lt;/li&gt;
&lt;li&gt;payments (each completed order has a corresponding payment)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/846d82c8cca7fbb3abd2367104e3d2ab/6967e/jaffle_shop_erd.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 25.1453488372093%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;101\&apos;%3e%3cpath%20d=\&apos;M6%2014v9h110V5L61%204H6v10m137%200v9h110V4H143v10m141-8l-1%209v9h111V6l-55-1-55%201\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;jaffle shop entity relationship diagram&quot;
        title=&quot;jaffle shop entity relationship diagram&quot;
        src=&quot;/static/846d82c8cca7fbb3abd2367104e3d2ab/8ff1e/jaffle_shop_erd.png&quot;
        srcset=&quot;/static/846d82c8cca7fbb3abd2367104e3d2ab/9ec3c/jaffle_shop_erd.png 200w,
/static/846d82c8cca7fbb3abd2367104e3d2ab/c7805/jaffle_shop_erd.png 400w,
/static/846d82c8cca7fbb3abd2367104e3d2ab/8ff1e/jaffle_shop_erd.png 800w,
/static/846d82c8cca7fbb3abd2367104e3d2ab/6ff5e/jaffle_shop_erd.png 1200w,
/static/846d82c8cca7fbb3abd2367104e3d2ab/6967e/jaffle_shop_erd.png 1376w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Entity Relationship Diagram image taken from &lt;a href=&quot;https://github.com/dbt-labs/jaffle_shop&quot;&gt;jaffle_shop&lt;/a&gt; project&lt;/p&gt;
&lt;p&gt;In the tutorial &lt;code class=&quot;language-text&quot;&gt;jaffle_shop&lt;/code&gt; project, all the domain entities (customers, orders, payments) as well as the results of the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; transformations are obviously located in tables within the same database.&lt;/p&gt;
&lt;p&gt;This blog post provides the answer to the questions answered by the project &lt;a href=&quot;https://github.com/dbt-labs/jaffle_shop/&quot;&gt;jaffle_shop&lt;/a&gt;
in a &lt;em&gt;data mesh&lt;/em&gt; decentralized data warehouse context
where each domain (customer, order, payment) is being stored in a separate database.
The insights gained from the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; transformations will be also saved in a separate database.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/083ad040f214cfe7b92aa8463862a6c5/1b500/data_mesh.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 82.3474178403756%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;329\&apos;%3e%3cpath%20d=\&apos;M350%2019c-5%201-5%202-5%209v8h-24l-24%201v37l1-18V38h47v16l4%201c5%201%2018%201%2020-1%203-2%203-33%200-34s-15-2-19-1m-4%208c0%204%202%204%2012%204s12%200%2012-4v-2h-24v2m0%2011c0%204%202%204%2012%204s12%200%2012-4v-3l-8%201-12-1-4-1v4m0%2012c0%203%202%204%2013%204%208%200%2011-1%2011-5v-2h-24v3m-50%2053v22h-46v-2c0-2-1-3-2-3l-2-1-1-3-2-2c-1-2-1-2-31-2l-29%201-1%206c0%206-1%207-2%206h-2l-17%201h-16l19%201h18v7c0%205%200%206%202%206l2%202%202%202%202%202c0%202%201%202%2015%202h14v32l1%2031%201-32v-31h29v-7h46v18l1%2017%201-18v-18h-48v-5h15l15-1-15-1h-15v-7h48v-23l-1-23-1%2023m-112%2024l1%2012%201-11v-12h22a13829%2013829%200%20005-2h-29v13m164-11c-2%201-3%201-3%209v8h-21c-20%200-23%200-22%201l22%201h21v8c0%206%200%207%202%208%203%201%2018%202%2021%200l3-1v-33l-3-1h-20m-292%203c-1%201%200%202%201%204v7c-2%204%200%205%204%203%202-2%202-2%205%200%204%202%206%200%204-4-2-3-2-3%200-6s1-5-2-4l-3%203c0%201-1%201-3-1-4-3-5-3-6-2m132%2011c0%2015%201%2016%202%201v-11h22a942%20942%200%20013-2h-27v12m158-6c0%203%202%204%2012%204%209%200%2011-1%2012-5v-3l-6%201h-12l-6-1v4m-155%2010v12h58v-25h-58v13m155%200c0%204%202%205%2012%205s11-1%2012-5c0-3%200-3-2-3h-20c-2%200-2%200-2%203m0%2012c1%204%202%205%2012%205s11-1%2012-5c0-4%200-4-2-4h-20c-2%200-2%200-2%204m-50%2058l1%2024%2024%201h24v6c0%208%201%209%2013%208%2013%200%2013%201%2013-18l-1-17c-3-2-16-2-21-1l-4%201v19h-47v-23l-1-23-1%2023m50%2011c0%203%202%204%2012%204s12-1%2012-4v-2h-24v2m-127%2032v32c0%201-1%202-5%202-7%202-7%201-7%2019%200%2019-1%2019%2013%2019s13%200%2013-19c0-18%200-17-7-19-4%200-5-1-5-2a821%20821%200%2000-2-32m127-21c0%203%201%204%2012%204s12-1%2012-5c0-2%200-3-2-3-3%202-12%202-17%201l-5-1v4m0%2011c0%204%202%205%2011%205%2011%200%2013-1%2013-5v-4l-6%201h-12l-6-1v4m-138%2053c0%204%202%204%2012%204s12%200%2012-4v-2h-24v2m0%2011c0%203%202%204%2012%204s12-1%2012-4v-4l-6%201h-12l-6-1v4m0%2011c0%204%202%205%2012%205s12-1%2012-5v-2h-24v2\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;jaffle shop data mesh&quot;
        title=&quot;jaffle shop data mesh&quot;
        src=&quot;/static/083ad040f214cfe7b92aa8463862a6c5/8ff1e/data_mesh.png&quot;
        srcset=&quot;/static/083ad040f214cfe7b92aa8463862a6c5/9ec3c/data_mesh.png 200w,
/static/083ad040f214cfe7b92aa8463862a6c5/c7805/data_mesh.png 400w,
/static/083ad040f214cfe7b92aa8463862a6c5/8ff1e/data_mesh.png 800w,
/static/083ad040f214cfe7b92aa8463862a6c5/1b500/data_mesh.png 1065w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://github.com/findinpath/dbt_jaffle_shop_data_mesh&quot;&gt;dbt_jaffle_shop_data_mesh&lt;/a&gt; project to try out
the concepts described  in this blog post.&lt;/p&gt;
&lt;p&gt;The project is using &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; to spin up the &lt;code class=&quot;language-text&quot;&gt;jaffle_shop&lt;/code&gt; Data Mesh environment and for performing the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; transformations.
No other local installation or configuration of a tool is required on the workstation used for testing this showcase.&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;language-text&quot;&gt;dbt-trino&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; stands for transformation as in &lt;code class=&quot;language-text&quot;&gt;T&lt;/code&gt; within &lt;code class=&quot;language-text&quot;&gt;ELT&lt;/code&gt;  pipelines, it doesn’t move data from source to a warehouse.
&lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; shines at orchestrating the transformations performed inside a data warehouse.&lt;/p&gt;
&lt;p&gt;Below is a quote taken from &lt;a href=&quot;https://trino.io/trino-the-definitive-guide.html&quot;&gt;Trino: The Definitive Guide&lt;/a&gt; book,
Chapter 1, &lt;em&gt;Introducing Trino&lt;/em&gt; :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Data warehouse systems have created not only huge benefits for users but also a burden on organizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running and maintaining the data warehouse is a large, expensive project.&lt;/li&gt;
&lt;li&gt;Dedicated teams run and manage the data warehouse and the associated ETL  processes.&lt;/li&gt;
&lt;li&gt;Getting the data into the warehouse requires users to break through red tape and typically takes too much time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trino, on the other hand, can be used as a virtual data warehouse. It can be used to
define your semantic layer by using one tool and standard ANSI SQL. Once all the
databases are configured as data sources in Trino, you can query them. Trino pro‐
vides the necessary compute power to query the storage in the databases. Using SQL
and the supported functions and operators, Trino can provide you the desired data
straight from the source. There is no need to copy, move, or transform the data before
you can use it for your analysis.&lt;/p&gt;
&lt;p&gt;Thanks to the standard SQL support against all connected data sources, you can cre‐
ate the desired semantic layer for querying from tools and end users in a simpler
fashion. And that layer can encompass all underlying data sources without the need
to migrate any data. Trino can query the data at the source and storage level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/findinpath/dbt-trino&quot;&gt;dbt-trino&lt;/a&gt; is a &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; &lt;a href=&quot;https://docs.getdbt.com/docs/available-adapters&quot;&gt;community adapter&lt;/a&gt;
which offers a possible solution in dealing with transformations performed over a virtually distributed data warehouse.&lt;/p&gt;
&lt;p&gt;By using this adapter, &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; is still interacting with only one &lt;code class=&quot;language-text&quot;&gt;SQL&lt;/code&gt; query engine, but in case of Trino, this is
a fast distributed SQL query engine for big data analytics that helps you explore your distributed data universe.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;trino&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;show&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;tables&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; insights&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;Table&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-------------------&lt;/span&gt;
customer_orders
customer_payments
dim_customers
fct_orders
order_payments
stg_customers
stg_orders
stg_payments
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

Query &lt;span class=&quot;token number&quot;&gt;20210918&lt;/span&gt;_214153_00126_zb79t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; FINISHED&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; node
Splits: &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt; total&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt; done &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100.00&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;0.21&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;240&lt;/span&gt;B&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;37&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.09&lt;/span&gt;KB&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As showcased in the demo project, in the snippet above, note that the tables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;stg_customers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;stg_orders&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;stg_payments&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;are actually &lt;em&gt;views&lt;/em&gt;, virtual tables, whose contents are defined by queries that are actually performed on
external databases via Trino.&lt;/p&gt;
&lt;p&gt;In the model &lt;code class=&quot;language-text&quot;&gt;dim_customers.sql&lt;/code&gt; from the demo project:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; {{ ref&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;stg_customers&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}

&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

customer_orders &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; {{ ref&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;customer_orders&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}

&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

customer_payments &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; {{ ref&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;customer_payments&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}

&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

final &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        customers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customer_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first_order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;most_recent_order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;number_of_orders&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_payments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total_amount &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; customer_lifetime_value

    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; customers

    &lt;span class=&quot;token keyword&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; customer_orders &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; customers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customer_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; customer_orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customer_id

    &lt;span class=&quot;token keyword&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt; customer_payments &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; customers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customer_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; customer_payments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customer_id

&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; final&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;the data corresponding to the domain models of the &lt;a href=&quot;https://github.com/dbt-labs/jaffle_shop/&quot;&gt;jaffle_shop&lt;/a&gt; project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;customer&lt;/li&gt;
&lt;li&gt;order&lt;/li&gt;
&lt;li&gt;payment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;is being joined in order to provide insights across all the aforementioned domain models.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In case that this post made you interested, feel free to check out the &lt;a href=&quot;https://github.com/findinpath/dbt-trino&quot;&gt;dbt-trino&lt;/a&gt;
&lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; adapter.&lt;/p&gt;
&lt;p&gt;Try out on your workstation &lt;a href=&quot;https://github.com/findinpath/dbt_jaffle_shop_data_mesh&quot;&gt;dbt_jaffle_shop_data_mesh&lt;/a&gt; to get
you a quick look&amp;#x26;feel on how &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt; and &lt;a href=&quot;https://trino.io/&quot;&gt;Trino&lt;/a&gt; can be used
together effectively for performing transformations on top of data distributed across various silos.&lt;/p&gt;
&lt;p&gt;Feedback is very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Inline Trino integer ranges]]></title><description><![CDATA[This post shows a few ways to create inline integer ranges in Trino. While playing with a new database it is quite useful to have the…]]></description><link>https://www.findinpath.com/trino-inline-range/</link><guid isPermaLink="false">https://www.findinpath.com/trino-inline-range/</guid><pubDate>Mon, 19 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post shows a few ways to create inline integer ranges in Trino.&lt;/p&gt;
&lt;p&gt;While playing with a new database it is quite useful to have the ability to easily create
a table with a integer range content (&lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;3&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt; and so on …) on which can be executed &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; statements.&lt;/p&gt;
&lt;h2&gt;Use the memory connector and fill the table&lt;/h2&gt;
&lt;p&gt;Obviously one way to have a table to play with would be  to actually create a table and fill it
with values.&lt;/p&gt;
&lt;p&gt;A simple way to implement this scenario is by using the Trino &lt;a href=&quot;https://trino.io/docs/current/connector/memory.html&quot;&gt;memory&lt;/a&gt;
connector.&lt;/p&gt;
&lt;p&gt;To configure the Memory connector, create a catalog properties file etc/catalog/memory.properties with the following contents:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;connector.name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;memory
memory.max-data-per-node&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;128MB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create a table used for test purposes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; memory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Fill the table with values:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; memory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, the inserted values can be queried/summed/counted:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; memory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now obviously, the main inconvenient of this method is that the &lt;code class=&quot;language-text&quot;&gt;memory&lt;/code&gt; connector needs to be configured
on Trino before starting to play with the range values.&lt;/p&gt;
&lt;h2&gt;Use an inline table&lt;/h2&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Consult the documentation for &lt;a href=&quot;https://trino.io/docs/current/sql/values.html&quot;&gt;VALUES&lt;/a&gt; SQL statement for more details.&lt;/p&gt;
&lt;p&gt;The main benefit of this method is that is quite straightforward and it does everything in one split.&lt;/p&gt;
&lt;p&gt;As explained on Trino &lt;a href=&quot;https://trino.io/docs/current/overview/concepts.html?highlight=concepts#split&quot;&gt;documentation&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tasks operate on splits, which are sections of a larger data set. Stages at the lowest level of a distributed query plan retrieve data via splits from connectors, and intermediate stages at a higher level of a distributed query plan retrieve data from other stages.&lt;/p&gt;
&lt;p&gt;When Trino is scheduling a query, the coordinator queries a connector for a list of all splits that are available for a table. The coordinator keeps track of which machines are running which tasks, and what splits are being processed by which tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Unnest a generated sequence&lt;/h2&gt;
&lt;p&gt;This method requires the least typing:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SEQUENCE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Consult the documentation for &lt;a href=&quot;https://trino.io/docs/current/sql/select.html#unnest&quot;&gt;UNNEST&lt;/a&gt; and &lt;a href=&quot;https://trino.io/docs/current/functions/array.html?highlight=sequence#sequence&quot;&gt;SEQUENCE&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;UNNEST&lt;/code&gt; can be used also for an arbitrary array of integers:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ARRAY&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Inline the range via &lt;code class=&quot;language-text&quot;&gt;UNION ALL&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This method is syntactically similar to the one use to inline a table:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The main drawback in terms of efficiency of this method is that for each &lt;code class=&quot;language-text&quot;&gt;UNION ALL&lt;/code&gt; it creates a new Trino split.&lt;/p&gt;
&lt;h2&gt;Inline the range via &lt;code class=&quot;language-text&quot;&gt;WITH RECURSIVE&lt;/code&gt; clause&lt;/h2&gt;
&lt;p&gt;By means of recursiveness there can be obtained a similar result as with &lt;code class=&quot;language-text&quot;&gt;UNION ALL&lt;/code&gt; :&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; RECURSIVE t&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; t &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; n &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Consult &lt;a href=&quot;https://trino.io/docs/current/sql/select.html#with-recursive-clause&quot;&gt;WITH RECURSIVE&lt;/a&gt; documentation for more details.&lt;/p&gt;
&lt;p&gt;Also in this case, the main drawback in terms of efficiency of this method is that for each &lt;code class=&quot;language-text&quot;&gt;UNION ALL&lt;/code&gt; it creates a new Trino split.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a collection of methods that can be used for obtaining without too much effort integer
ranges in Trino.&lt;/p&gt;
&lt;p&gt;There are obviously other methods to obtain this functionality. Feel free to suggest new ways of easily obtaining
ranges.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Infer deletions in parent-child ELT staging data]]></title><description><![CDATA[This post concentrates on the enhancing ELT staging data for being able to infer entries which
are missing from parent-child  staging data…]]></description><link>https://www.findinpath.com/dbt-infer-deletions-in-parent-child-staging-data/</link><guid isPermaLink="false">https://www.findinpath.com/dbt-infer-deletions-in-parent-child-staging-data/</guid><pubDate>Wed, 17 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post concentrates on the enhancing ELT staging data for being able to infer entries which
are missing from parent-child &lt;code class=&quot;language-text&quot;&gt;JSON&lt;/code&gt; staging data.&lt;/p&gt;
&lt;p&gt;Suppose that a video platform where the users can make channel subscriptions has a data pipeline
to its Data Warehouse system for exporting in case of changes the data about its users in near
real-time.&lt;/p&gt;
&lt;p&gt;Let us suppose for the sake of simplicity that the data exported towards the data warehouse
follows this format:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;user
  - id: NUMBER
  - username: VARCHAR
  - created_at: DATETIME
  - exported_at: DATETIME

  subscriptions
    - id: NUMBER
    - name: VARCHAR
    - subscribed_at: DATETIME&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Between the user and the subscriptions there is a &lt;code class=&quot;language-text&quot;&gt;1:N&lt;/code&gt; relationship.&lt;/p&gt;
&lt;p&gt;Obviously such a design is far from ideal in case of dealing with a real-world mainstream video platform where
a user can have hundreds or even thousands of subscriptions, but due to the fact that video platform
concepts are so spread, this is a nice fit for better explaining the problem tackled in this blog post.&lt;/p&gt;
&lt;p&gt;In the time between two exports of the user subscription changes of a specific user data, there can be that new subscriptions
get added and some of the subscriptions can also be removed (when the user unsubscribes from a channel).&lt;/p&gt;
&lt;p&gt;e.g. : Let’s suppose that in the user subscriptions staging data there are found for the user &lt;em&gt;johndoe&lt;/em&gt; two entries:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;johndoe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1609455600000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;exported_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1613379600000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;subscriptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;5-Minute Crafts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1612170000000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;euronews&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1612256400000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Treehouse&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1612342800000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;johndoe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1609455600000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;exported_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1613383200000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;subscriptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;euronews&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1612256400000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Treehouse&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1612342800000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DevTips&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1613381400000&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Between &lt;code class=&quot;language-text&quot;&gt;2021-02-15 09:00:00&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;2021-02-15 10:00:00&lt;/code&gt; the user &lt;em&gt;johndoe&lt;/em&gt; made a new subscription to the channel
&lt;em&gt;DevTips&lt;/em&gt; and unsubscribed from the channel &lt;em&gt;5-Minute Crafts&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This proof of concept project offers a solution on how to infer deletions of child entities within complex objects
from the staging data.&lt;/p&gt;
&lt;p&gt;In the scenario described above, an artificially created deletion entry should be created in order to point out
approximately when the channel &lt;em&gt;5-Minute Crafts&lt;/em&gt; has been unsubscribed by the user &lt;em&gt;johndoe&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;On the basis of the insertions &amp;#x26; deletions for the user subscriptions a data engineer can accurately
find out across time the subscriptions that the users have made and how long they did last.&lt;/p&gt;
&lt;h2&gt;Historization of the user subscriptions&lt;/h2&gt;
&lt;p&gt;The basis for historization logic for the user subscriptions would be summarized by the union of the following
data sets:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt; subscriptions staging &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt;
inferred deletions &lt;span class=&quot;token keyword&quot;&gt;within&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt; subscriptions staging &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;what this project concentrates &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt;
inferred deletions &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; the active &lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt; subscriptions &lt;span class=&quot;token keyword&quot;&gt;within&lt;/span&gt; the already historized &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
that &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; appear &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;last&lt;/span&gt; exported &lt;span class=&quot;token keyword&quot;&gt;user&lt;/span&gt; subscription &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;On the example of the &lt;em&gt;johndoe&lt;/em&gt; user suggested above, the subscription availability intervals
after processing the staging data at &lt;code class=&quot;language-text&quot;&gt;2021-02-15 10:00:00&lt;/code&gt; would be:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Channel name&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;Subscribed at&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;Unsubscribed at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5-Minute Crafts&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-02-01 09:00:00&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-02-15 10:00:00&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;euronews&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-02-02 09:00:00&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treehouse&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-02-03 09:00:00&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevTips&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2021-02-15 09:30:00&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Infer deletions in the staging data&lt;/h2&gt;
&lt;p&gt;Let us assume that the raw staging data looks exactly as in the hypothetical example presented previously
for the user &lt;em&gt;johndoe&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_video_platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_user_subscription&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f5f1c82dc61119c2415f03a28bd311db/cef07/raw-user-subscriptions.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 6.182065217391305%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;25\&apos;%3e%3cpath%20d=\&apos;M0%204v4h401V0H0v4\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;raw user subscriptions&quot;
        title=&quot;raw user subscriptions&quot;
        src=&quot;/static/f5f1c82dc61119c2415f03a28bd311db/8ff1e/raw-user-subscriptions.png&quot;
        srcset=&quot;/static/f5f1c82dc61119c2415f03a28bd311db/9ec3c/raw-user-subscriptions.png 200w,
/static/f5f1c82dc61119c2415f03a28bd311db/c7805/raw-user-subscriptions.png 400w,
/static/f5f1c82dc61119c2415f03a28bd311db/8ff1e/raw-user-subscriptions.png 800w,
/static/f5f1c82dc61119c2415f03a28bd311db/6ff5e/raw-user-subscriptions.png 1200w,
/static/f5f1c82dc61119c2415f03a28bd311db/cef07/raw-user-subscriptions.png 1472w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One possible solution for the problem described in the introduction of this project is presented in the code
below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; src_user_subscriptions &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;  load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                LEAD&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;load_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; user_id &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;      &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                LEAD&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exported_at&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; user_id &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                subscriptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                ARRAY_SIZE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscriptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscriptions_size
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id                                                    &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           record:&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;::NUMBER                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           TO_TIMESTAMP&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;record:&lt;span class=&quot;token string&quot;&gt;&quot;exported_at&quot;&lt;/span&gt;::NUMBER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;            &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                           record:&lt;span class=&quot;token string&quot;&gt;&quot;subscriptions&quot;&lt;/span&gt;::VARIANT                         &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscriptions
                    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_video_platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_user_subscription
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; user_subscription &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next_load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next_exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;:&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;::NUMBER                              &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt;                                           &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;:&lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;                           &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               TO_TIMESTAMP&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;:&lt;span class=&quot;token string&quot;&gt;&quot;subscribed_at&quot;&lt;/span&gt;::NUMBER&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_subscribed_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subscriptions_size
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; src_user_subscriptions src
        &lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;FLATTEN&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subscriptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; subscription &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; subscriptions_size &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscription_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscription_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscription_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscription_subscribed_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscriptions_size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; _deleted
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; user_subscription
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; subscriptions_size&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;ALL&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- create artificially deleted entries for the subscriptions which don&apos;t exist in&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- the next staging occurrence of the user&apos;s subscriptions&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; next_load_id                             &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       next_exported_at                         &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; exported_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       subscription_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_index&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscription_subscribed_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; subscriptions_size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token boolean&quot;&gt;TRUE&lt;/span&gt;                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; _deleted
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; user_subscription   &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; current_user_subscription
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; next_load_id &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; user_subscription &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_user_subscription
        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; current_user_subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;next_load_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next_user_subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;load_id
          &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; current_user_subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subscription_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; next_user_subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subscription_id
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Summarized, the &lt;code class=&quot;language-text&quot;&gt;SQL&lt;/code&gt; query above takes the complex JSON user entities from the raw staging table and flatten them to user subscriptions.
Subsequently each of the user subscription will get attached &lt;code class=&quot;language-text&quot;&gt;load_id&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;exported_at&lt;/code&gt; information from the next user subscription staging
entry per &lt;code class=&quot;language-text&quot;&gt;user_id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Inferring the deletions is now simple because there needs only to be checked whether a user subscription
does not have a corresponding subscription in the next staging occurence for the user.&lt;/p&gt;
&lt;p&gt;The outcome of this transformation can be seen below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_video_platform&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stg_user_subscription
&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; subscription_id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/81a35eb9f768455d803116d092ac82e6/08be5/staged-user-subscriptions.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 15.678524374176547%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;63\&apos;%3e%3cpath%20d=\&apos;M0%204v3h401V0h-39a185%20185%200%2000-40%201l-64-1c-61%200-65%200-65%202h-1c0-2-2-2-21-2h-21v3l-1%204V0h-42v3l-1%204V0H0v4m66%207c-1%201%204%202%2019%202l19-1c0-2-38-2-38-1\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;staged user subscriptions with inferred deletions&quot;
        title=&quot;staged user subscriptions with inferred deletions&quot;
        src=&quot;/static/81a35eb9f768455d803116d092ac82e6/8ff1e/staged-user-subscriptions.png&quot;
        srcset=&quot;/static/81a35eb9f768455d803116d092ac82e6/9ec3c/staged-user-subscriptions.png 200w,
/static/81a35eb9f768455d803116d092ac82e6/c7805/staged-user-subscriptions.png 400w,
/static/81a35eb9f768455d803116d092ac82e6/8ff1e/staged-user-subscriptions.png 800w,
/static/81a35eb9f768455d803116d092ac82e6/6ff5e/staged-user-subscriptions.png 1200w,
/static/81a35eb9f768455d803116d092ac82e6/08be5/staged-user-subscriptions.png 1518w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the image above can be noticed that the subscription of the user &lt;em&gt;johndoe&lt;/em&gt; with the ID &lt;code class=&quot;language-text&quot;&gt;1000&lt;/code&gt;
identifying the &lt;em&gt;5-Minute Crafts&lt;/em&gt; channel  that was present for the user in the staged entry
with the ID &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; is being marked as deleted due to the fact that it does not occur anymore
in the subsequent staging entry with the ID &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;The concepts described in this blog post can be tried out in the project
&lt;a href=&quot;https://github.com/findinpath/dbt_infer_deletions_in_parent_child_staging_data&quot;&gt;dbt_infer_deletions_in_parent_child_staging_data&lt;/a&gt;
that is accompanying this blog post.&lt;/p&gt;
&lt;p&gt;The project includes a &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;data build tool&lt;/a&gt; model that can be used to infer
deletions within the parent-child JSON ELT data.
The nice thing about this SQL demo is that it contains also automatic
test specifications (based on &lt;a href=&quot;https://github.com/inside-track/dtspec&quot;&gt;dtspec&lt;/a&gt;) in order to verify
the accuracy of the implementation against &lt;a href=&quot;https://www.snowflake.com/&quot;&gt;Snowflake&lt;/a&gt; database.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a proof of concept for finding deletions within parent-child JSON staging data.
Eventual improvements to the &lt;a href=&quot;https://github.com/findinpath/dbt_infer_deletions_in_parent_child_staging_data&quot;&gt;dbt_infer_deletions_in_parent_child_staging_data&lt;/a&gt;
project code or ideas regarding alternative ways to solve this problem are very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Infer deletions in batched ELT staging data]]></title><description><![CDATA[This post concentrates on the enhancing ELT staging data for being able to infer entries which
are missing from one batch export to the next…]]></description><link>https://www.findinpath.com/dbt-infer-deletions-in-batched-staging-data/</link><guid isPermaLink="false">https://www.findinpath.com/dbt-infer-deletions-in-batched-staging-data/</guid><pubDate>Sat, 06 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post concentrates on the enhancing ELT staging data for being able to infer entries which
are missing from one batch export to the next one.&lt;/p&gt;
&lt;p&gt;Suppose that a shop is exporting daily towards the data warehouse environment the
whole list of its active products in csv files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;products-2021-01-01.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;products-2021-01-02.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;products-2021-01-03.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;products-2021-01-04.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;products-2021-01-05.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously from a day to the next there may be new products which get activated or existing
products which get deactivated.&lt;/p&gt;
&lt;p&gt;Nowadays there are source systems which simply do a snapshot of the existing products
without providing any information about the deleted products since the previous export.&lt;/p&gt;
&lt;p&gt;This missing information requires special effort on the data warehouse side in order to accurately
infer the date range(s) for which the products in the shop were active.&lt;/p&gt;
&lt;p&gt;If for a certain period of time, there is no product data ingested in the data warehouse,
it may happen that the product data corresponding to several days in a row may need to be
staged in the data warehouse.&lt;/p&gt;
&lt;p&gt;This might be also the case when a bug is found in the historization of the product data
which may involve a full reload of all the product staging data.&lt;/p&gt;
&lt;h2&gt;Historization of the product availability&lt;/h2&gt;
&lt;p&gt;In order to fully historize the product availability there needs to be made the following
data sets need to be combined together:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;product staging &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt;
inferred deletions &lt;span class=&quot;token keyword&quot;&gt;within&lt;/span&gt; the product staging &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;what this blog post concentrates &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UNION&lt;/span&gt;
inferred deletions &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; the active products &lt;span class=&quot;token keyword&quot;&gt;within&lt;/span&gt; the already historized &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
that &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; appear &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; the &lt;span class=&quot;token keyword&quot;&gt;last&lt;/span&gt; exported staging &lt;span class=&quot;token keyword&quot;&gt;day&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The formula briefly described above, provides the necessary input data for being able to
historize in detail the product availability date ranges.&lt;/p&gt;
&lt;p&gt;On the basis of the insertions &amp;#x26; deletions for the products within the data warehouse environment
the availability date ranges for each of the products from the shop can be accurately calculated.&lt;/p&gt;
&lt;h2&gt;Infer deletions in the staging data&lt;/h2&gt;
&lt;p&gt;This article doesn’t go in the details on how to historize the product availability within the
data warehouse environment, but rather concentrates on how to enhance the staging data so to identify deleted
entries within the staging data when exported data for the products corresponding to multiple days
are imported at the same time.&lt;/p&gt;
&lt;p&gt;If everything goes well and every day the products are staged successfully, the logic presented below
will never be used.&lt;/p&gt;
&lt;p&gt;On the other hand, when there are multiple days processed at the same time, it can get difficult to
find out exactly which are the availability date ranges for a product which gets activated and deactivated
several times during the processed days.&lt;/p&gt;
&lt;p&gt;Let’s suppose that some of the products are very short-lived (e.g. : they are active only for a day on the shop).&lt;/p&gt;
&lt;p&gt;e.g. : supposing that product data for each of the dates from the interval
&lt;code class=&quot;language-text&quot;&gt;2021-01-01&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;2021-01-05&lt;/code&gt; is being staged at the same time.
The product &lt;code class=&quot;language-text&quot;&gt;milk&lt;/code&gt; is available on &lt;code class=&quot;language-text&quot;&gt;2021-01-01&lt;/code&gt; , but not available
anymore on &lt;code class=&quot;language-text&quot;&gt;2021-01-02&lt;/code&gt;, then again available on &lt;code class=&quot;language-text&quot;&gt;2021-01-04&lt;/code&gt; and not available anymore
again starting from &lt;code class=&quot;language-text&quot;&gt;2021-01-05&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the scenario described above, for the product milk, there should be created artificially deletion
entries for the dates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2021-01-02&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2021-01-05&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the example of the &lt;em&gt;milk&lt;/em&gt; product suggested above, the availability intervals in the staging data
would be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2021-01-01&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;2021-01-02&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;2021-01-04&lt;/code&gt; - &lt;code class=&quot;language-text&quot;&gt;2021-01-05&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the sake of an easier reading of what this (rather exotic) problem is trying to solve,
here will be presented visually, step by step how the deletions of the products from the
staged data are being inferred.&lt;/p&gt;
&lt;p&gt;If the &lt;code class=&quot;language-text&quot;&gt;raw_products&lt;/code&gt; staging table has the following representation:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 715px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.167832167832174%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;209\&apos;%3e%3cpath%20d=\&apos;M101%207l-1%203c0%202%200%202%205%202l5-1h12V8c1-1%201-1%201%201l1%203%201-1%201-3c0-2-1-2-4-2l-4%201h-17m186%200v5l2-2c1-1%201-1%202%201h24l-1-1-2-1%202-1%202%202%201%202%201-1%201-3c0-2-1-2-5-2-5%201-6%201-6%203-1%202-1%202-1%200s-2-4-2-2h-18m51%202c0%203%200%203%202%202h7l2%201%202-1h11l2%201%202-1-1-1c-1%201-2%200-2-1l2-1h1c0-2-2-2-3-1h-2c-1-1-1-1-1%201%200%203-2%204-2%201%200-2-1-3-3-3l-3%201h-6l-5-1c-3%200-3%200-3%203\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;raw products&quot;
        title=&quot;raw products&quot;
        src=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png&quot;
        srcset=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/9ec3c/raw-products.png 200w,
/static/d6a36e65c22df8aa02b805d648dd3c36/c7805/raw-products.png 400w,
/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png 715w&quot;
        sizes=&quot;(max-width: 715px) 100vw, 715px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; that here is made the supposition that the exported products for a certain day
share the same &lt;code class=&quot;language-text&quot;&gt;export_time&lt;/code&gt; timestamp. The algorithm presented here works only when this
supposition is met.&lt;/p&gt;
&lt;p&gt;The starting point in the journey of inferring deletions within the staging data
is to build a cartesian product between the staging dates and the staged product ids:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       export_time
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; product_id
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; export_time
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; staged_date
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 588px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/261b6fd24d2b239524a589a8370c8833/4214e/cartesian-product-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.01360544217688%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;436\&apos;/%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;cartesian product&quot;
        title=&quot;cartesian product&quot;
        src=&quot;/static/261b6fd24d2b239524a589a8370c8833/4214e/cartesian-product-product-export_time.png&quot;
        srcset=&quot;/static/261b6fd24d2b239524a589a8370c8833/9ec3c/cartesian-product-product-export_time.png 200w,
/static/261b6fd24d2b239524a589a8370c8833/c7805/cartesian-product-product-export_time.png 400w,
/static/261b6fd24d2b239524a589a8370c8833/4214e/cartesian-product-product-export_time.png 588w&quot;
        sizes=&quot;(max-width: 588px) 100vw, 588px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;From the cartesian product the following &amp;#x3C;product id, export_time&gt; entries are chopped off:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the ones that happen before  the first appearance of a staged entry for a product (marked in red)&lt;/li&gt;
&lt;li&gt;the ones that are found within the staging entries (marked in yellow)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       export_time
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                 export_time
          &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; product_id
                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                  &lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; export_time
                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; staged_date
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; staged_product_date
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                                             &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                             &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                                             &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;product_id
                                         &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time
        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; export_time
                    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;product_id
               &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 588px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f072db2dd9ababb93419db182034b912/4214e/filtered-cartesian-product-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.01360544217688%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;436\&apos;%3e%3cpath%20d=\&apos;M178%208v6l2-2h2l1%202%201-1c0-1%201-1%201%201h4c1%201%201%201%203-1h2c0%202%202%202%204%200%201-1%201-1%202%201%202%201%205%201%205-1h1c1%201%202%202%203%201%203%200%203-2%200-2l-1-1%202-2c3%200%203%201%203%203l1%202%201-2%201-3c0-2-9-2-10%200h-1l-1-1-1%203-1%202-1-3-1-2h-5c-2%202-2%202-3%201s-2-2-3-1h-11m162%200l-1%204c0%203%200%203%203%203l6-1%204-1c1-2%201-2%203%200%201%202%202%202%203%201h3l1-1c0-1%201-1%202%201h1l1-5%201%203%201%202%201-2%201-3%201-1h-31m2%2021v6l2-1h3c1%201%202%201%203-1%201-1%202-2%203-1%201%203%202%203%204%202h3c1%202%203%200%203-3%200-2-3-4-4-2h-5l-2%201-2-1h-8M1%2093c0%209%200%2010%202%2010l199-1h198V82H1v11m341%2018l-1%203c0%202%203%204%204%201l2%201c2%201%202%201%203-1h2l2%201h3c2-2%202-2%202%200%201%202%204%200%204-3%200-2-3-4-4-2h-5c-2%200-3%200-2%201l1%202h-2c-1-4-2-5-4-3h-5m0%2020v6l2-1h3c1%201%202%201%203-1%201-1%202-2%203-1%201%203%202%203%204%202h3c1%202%203%200%203-2%200-3-2-5-3-3h-18m25%200v6l2-1h23c2-3%200-7-3-5h-8c-1-2-3%200-3%202%200%203-2%202-2%200-1-3-3-4-5-2h-4M2%20175v11h398v-22H2v11m340%2017l-1%203c0%202%203%204%204%202h2c2%202%203%201%204-2h2c1%203%202%204%204%202h2c1%202%204%200%204-2%200-3-2-5-4-3h-17m0%2021l-1%203c0%202%203%204%204%202%200-2%200-2%202%200h10c2-2%202-2%202%200%201%202%204%200%204-3%200-2-3-4-4-2h-5c-2%200-3%200-2%201l1%202h-2c-1-4-2-5-4-3h-5m0%2020v6l2-1h3c2%202%203%201%204-2h2c1%203%202%204%204%202h2c1%202%204%200%204-2%200-3-2-5-3-3h-18M0%20254l1%2010h199l200-1%201-10v-9H0v10m342%2020v6l2-1h3c1%201%202%201%203-1l2-2%202%202c1%202%202%202%203%201h3c1%202%203%200%203-3%200-2-3-4-4-2h-5l-2%201-2-1h-8m0%2020l-1%203c0%202%203%204%204%202h2c2%201%203%201%204-2l2-1-1%202c-1%201%200%201%202%201h5c1%202%204%200%204-2%200-3-2-5-4-3h-17m0%2021l-1%203c0%202%203%204%204%202%200-2%200-2%202%200%201%201%201%201%203-1h1l1%201h7c2%201%204-1%204-3s-3-4-4-2h-17M2%20339v12h398v-22H240l-199-1H2v11m340%2037v6l2-1h3c1%201%202%201%203-1l2-2%202%202c1%202%202%202%203%201h3c1%202%203%200%203-3%200-2-3-4-4-2h-5l-2%201-2-1h-8m25%200v6l2-1c1-2%202-2%203%200h17c0%202%203%201%204-2%200-3-1-5-4-3h-7c-1-2-4%200-4%202%200%203-2%203-2%200-1-3-3-4-5-2h-4\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;filtered cartesian product&quot;
        title=&quot;filtered cartesian product&quot;
        src=&quot;/static/f072db2dd9ababb93419db182034b912/4214e/filtered-cartesian-product-product-export_time.png&quot;
        srcset=&quot;/static/f072db2dd9ababb93419db182034b912/9ec3c/filtered-cartesian-product-product-export_time.png 200w,
/static/f072db2dd9ababb93419db182034b912/c7805/filtered-cartesian-product-product-export_time.png 400w,
/static/f072db2dd9ababb93419db182034b912/4214e/filtered-cartesian-product-product-export_time.png 588w&quot;
        sizes=&quot;(max-width: 588px) 100vw, 588px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The result of this filtering is represented by all the &amp;#x3C;product, export_time&gt; entries that are not found
within the staging entries:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 699px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fb774aec30c1e8eda4dc8f2dfbe7507d/8d69c/missing-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.902718168812587%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;128\&apos;%3e%3cpath%20d=\&apos;M90%207v5l2-2c2-1%202-1%202%201h2l11%201%2011-1h-1l-2-1c0-3%205-2%205%200%201%202%201%202%201%200l1-2%201-1h-10l-1%202c-1%202-1%202-2%200l-2-2H90m106%202c0%203%200%203%205%203l6-1h9l1%201%201-2%201-2v2l1%202%201-1%201-3c0-2-1-2-4-2l-4%201h-14c-3-2-4-1-4%202m153-2l-1%203c0%202%200%202%205%202l6-1h2l5%201c3%200%204-1%204-3s-2-4-3-2h-18\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;missing staged products&quot;
        title=&quot;missing staged products&quot;
        src=&quot;/static/fb774aec30c1e8eda4dc8f2dfbe7507d/8d69c/missing-product-export_time.png&quot;
        srcset=&quot;/static/fb774aec30c1e8eda4dc8f2dfbe7507d/9ec3c/missing-product-export_time.png 200w,
/static/fb774aec30c1e8eda4dc8f2dfbe7507d/c7805/missing-product-export_time.png 400w,
/static/fb774aec30c1e8eda4dc8f2dfbe7507d/8d69c/missing-product-export_time.png 699w&quot;
        sizes=&quot;(max-width: 699px) 100vw, 699px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now some of the resulting entries are not necessary. If a product is deleted after the first staging day and
does not occur anymore subsequently, the entry with the minimum export_time is enough information.&lt;/p&gt;
&lt;p&gt;What might happen though is that a product is deleted on one day, and appears again on a later day
and is subsequently deleted again later. Between the two deletion dates we’ll have obviously a gap
greater than one export day (because the product is in between at least one day active).&lt;/p&gt;
&lt;p&gt;In order to cope with such a situation, the &lt;a href=&quot;https://docs.snowflake.com/en/sql-reference/functions/lag.html&quot;&gt;LAG&lt;/a&gt;
window function is being used in order to identify greater gaps between the deletions :&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       export_time
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
               LAG&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; product_id &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; prev_export_time
        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                         export_time
                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; product_id
                          &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                          &lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt;
                          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; export_time
                          &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; staged_date
                  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; staged_product_date

        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                                                     &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                                     &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                                                     &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;product_id
                                                 &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time
                &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; export_time
                            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                            &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; staged_product_date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;product_id
                       &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; missing_staged_product
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;missing_staged_product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prev_export_time &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; missing_staged_product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                                                        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                                                        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                                                        &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; missing_staged_product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prev_export_time
                                                  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The filtering can be observed (marked in red) in the following image:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 699px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b1e6214dfca02db1c9f3618fce95c12b/8d69c/filtered-missing-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 31.902718168812587%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;128\&apos;%3e%3cpath%20d=\&apos;M0%2024l1%2023%201%203v3h398v-5l1-27V0l-1%2017v17H2V17L1%200%200%2024M90%207v5l2-1h4l1%201h17c1%201%204%200%204-1l-1-1h-2l2-2c2%200%203%201%202%202l1%202%201-2%201-2%201-1h-10c0-2-2%200-2%203-1%201-1%201-1-1V6h-10L90%207m106%202c0%203%201%203%203%203h4l4-1%203%201h4l1-1h1c0%202%202%201%202-1l1-2v2c0%203%202%203%202%200l1-2%201-1-4-1-5%201h-6l-6-1h-6v3m152%200l-1%202-3%201%2015-1%203%201h6l2-2%201-2v1c0%202%203%204%206%204%201-1%201-1-1-1-2-1-4-4-2-4l1-1-13-1h-14v3M198%2077v3c0%201%203%202%203%200h1c2%201%2013%201%2014-1l1-1v1l1%201h1l2%201%201-1h1c2%201%204%201%204-2%200-2-2-3-4-2h-1l-1-1-2%201h-7c-2-1-3%200-4%201%200%202-2%203-2%201%200-3-2-3-4-2h-1c0-2-3-1-3%201M1%20107v21h399V86H1v21\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;filtered missing staged products&quot;
        title=&quot;filtered missing staged products&quot;
        src=&quot;/static/b1e6214dfca02db1c9f3618fce95c12b/8d69c/filtered-missing-product-export_time.png&quot;
        srcset=&quot;/static/b1e6214dfca02db1c9f3618fce95c12b/9ec3c/filtered-missing-product-export_time.png 200w,
/static/b1e6214dfca02db1c9f3618fce95c12b/c7805/filtered-missing-product-export_time.png 400w,
/static/b1e6214dfca02db1c9f3618fce95c12b/8d69c/filtered-missing-product-export_time.png 699w&quot;
        sizes=&quot;(max-width: 699px) 100vw, 699px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The operation applied previously corresponds now to the inferred batched product deletions:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 486px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;111\&apos;/%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;inferred product deletions&quot;
        title=&quot;inferred product deletions&quot;
        src=&quot;/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png&quot;
        srcset=&quot;/static/873d4f14ad7a5164784bf237c50673e7/9ec3c/deduplicated-missing-product-export_time.png 200w,
/static/873d4f14ad7a5164784bf237c50673e7/c7805/deduplicated-missing-product-export_time.png 400w,
/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png 486w&quot;
        sizes=&quot;(max-width: 486px) 100vw, 486px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Simplified solution description&lt;/h2&gt;
&lt;p&gt;Instead of filtering out from the cartesian product of product_id and export_time
to infer the deleted products, there could be used a much simpler solution.&lt;/p&gt;
&lt;p&gt;It is assumed again that the &lt;code class=&quot;language-text&quot;&gt;raw_products&lt;/code&gt; staging table has the following representation:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 715px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.167832167832174%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;209\&apos;%3e%3cpath%20d=\&apos;M101%207l-1%203c0%202%200%202%205%202l5-1h12V8c1-1%201-1%201%201l1%203%201-1%201-3c0-2-1-2-4-2l-4%201h-17m186%200v5l2-2c1-1%201-1%202%201h24l-1-1-2-1%202-1%202%202%201%202%201-1%201-3c0-2-1-2-5-2-5%201-6%201-6%203-1%202-1%202-1%200s-2-4-2-2h-18m51%202c0%203%200%203%202%202h7l2%201%202-1h11l2%201%202-1-1-1c-1%201-2%200-2-1l2-1h1c0-2-2-2-3-1h-2c-1-1-1-1-1%201%200%203-2%204-2%201%200-2-1-3-3-3l-3%201h-6l-5-1c-3%200-3%200-3%203\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;raw products&quot;
        title=&quot;raw products&quot;
        src=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png&quot;
        srcset=&quot;/static/d6a36e65c22df8aa02b805d648dd3c36/9ec3c/raw-products.png 200w,
/static/d6a36e65c22df8aa02b805d648dd3c36/c7805/raw-products.png 400w,
/static/d6a36e65c22df8aa02b805d648dd3c36/2a207/raw-products.png 715w&quot;
        sizes=&quot;(max-width: 715px) 100vw, 715px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The result of the query&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
      export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      LEAD&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; product_id &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                   &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_product_export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
          &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                                                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      product_id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;can be used to see when the next occurrences of product exports happen in the staging data:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/903236de4fc9f4cbc1894296e089be44/88829/next-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.62928348909657%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;155\&apos;%3e%3cpath%20d=\&apos;M78%206v2h10c2%201%206%201%207-1h1c0%202%201%202%203%202s3-1%203-2h1c0%202%201%202%205%202s4%200%204-2c0-3-4-4-5-2h-1l-3-1c-2%200-3%200-3%202l-1%202c-2%200-2%200-2-2%201-2%200-2-9-2-7%200-9%201-10%202m50%200v2h37l-1-1c-2-1-2-1%200-1l2%202h13c2%201%206%201%207-1h1c0%202%201%202%208%202%208%200%208%200%208-3%200-2-3-3-5-1h-1l-3-1c-3%200-3%200-2%202%200%201%200%202-2%202l-1-2c1-2%200-2-9-2s-10%200-10%202l-2%202-1-2c1-2%201-2-3-2l-4%202h-1c0-1-1-2-8-2s-7%201-7%202l-2%202-1-2c1-2%200-2-6-2-4%200-6%201-7%202m111%200v2a76%2076%200%200025%200c2%201%206%201%207-1h1c0%203%206%203%206%200h1c0%202%201%202%205%202s4%200%204-2c0-3-4-4-5-2h-1l-3-1c-3%200-3%200-2%202%200%202%200%202-2%202l-1-2c0-2-1-2-9-2-9%200-10%200-10%202l-2%202-1-2c1-2%200-2-6-2-4%200-6%201-7%202m124%201c0%202%200%202%202%201h1l9%201c8%200%2011-1%208-2V6c2-1%202%200%202%201%200%202%201%202%204%202%205%200%207-2%205-4s-5-1-5%201l-1%202-1-2c0-3-8-3-8%200h-1c0-1-2-2-8-2-7%200-7%200-7%203\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;next product export times&quot;
        title=&quot;next product export times&quot;
        src=&quot;/static/903236de4fc9f4cbc1894296e089be44/8ff1e/next-product-export_time.png&quot;
        srcset=&quot;/static/903236de4fc9f4cbc1894296e089be44/9ec3c/next-product-export_time.png 200w,
/static/903236de4fc9f4cbc1894296e089be44/c7805/next-product-export_time.png 400w,
/static/903236de4fc9f4cbc1894296e089be44/8ff1e/next-product-export_time.png 800w,
/static/903236de4fc9f4cbc1894296e089be44/88829/next-product-export_time.png 963w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There can be now easily be seen that the &lt;code class=&quot;language-text&quot;&gt;next_product_export_time&lt;/code&gt; for some of the entries
is greater that the &lt;code class=&quot;language-text&quot;&gt;next_export_time&lt;/code&gt; or that the  &lt;code class=&quot;language-text&quot;&gt;next_product_export_time&lt;/code&gt; in some cases
is simply &lt;code class=&quot;language-text&quot;&gt;NULL&lt;/code&gt;, otherwise said, is missing.&lt;/p&gt;
&lt;p&gt;By applying the query&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
    next_export_time               &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        next_export_time &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;next_product_export_time &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; next_product_export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; next_export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                              &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; _deleted
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
            export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            LEAD&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; product_id &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                   &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_product_export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;export_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;  playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products
                &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; export_time &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;export_time
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;                                                                     &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; next_export_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            product_id
            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;  playground&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbt_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raw_products &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; src
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; _deleted&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;the valid entries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code class=&quot;language-text&quot;&gt;next_product_export_time&lt;/code&gt; is corresponding to the &lt;code class=&quot;language-text&quot;&gt;next_export_time&lt;/code&gt; (marked in red)&lt;/li&gt;
&lt;li&gt;the &lt;code class=&quot;language-text&quot;&gt;next_export_time&lt;/code&gt; is &lt;code class=&quot;language-text&quot;&gt;NULL&lt;/code&gt; (marked in yellow) meaning that
these are the product exports done on the last staging day&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;are being filtered out:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/165e8906ac8380d51cae20bfd8391857/88829/filtered-next-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 38.62928348909657%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;155\&apos;%3e%3cpath%20d=\&apos;M78%206v2h10c2%201%206%201%207-1h1c0%202%201%202%203%202s3-1%203-2h1c0%202%201%202%205%202s4%200%204-2c0-3-4-4-5-2h-1l-3-1c-2%200-3%200-3%202l-1%202c-2%200-2%200-2-2%201-2%200-2-9-2-7%200-9%201-10%202m50%200v2h37l-1-1c-2-1-2-1%200-1l2%202h13c2%201%206%201%207-1h1c0%202%201%202%208%202%208%200%208%200%208-3%200-2-3-3-5-1h-1l-3-1c-3%200-3%200-2%202%200%201%200%202-2%202l-1-2c1-2%200-2-9-2s-10%200-10%202l-2%202-1-2c1-2%201-2-3-2l-4%202h-1c0-1-1-2-8-2s-7%201-7%202l-2%202-1-2c1-2%200-2-6-2-4%200-6%201-7%202m111%200v2a76%2076%200%200025%200c2%201%206%201%207-1h1c0%203%206%203%206%200h1c0%202%201%202%205%202s4%200%204-2c0-3-4-4-5-2h-1l-3-1c-3%200-3%200-2%202%200%202%200%202-2%202l-1-2c0-2-1-2-9-2-9%200-10%200-10%202l-2%202-1-2c1-2%200-2-6-2-4%200-6%201-7%202m124%201c0%202%200%202%202%201h1l9%201c8%200%2011-1%208-2V6c2-1%202%200%202%201%200%202%201%202%204%202%205%200%207-2%205-4s-5-1-5%201l-1%202-1-2c0-3-8-3-8%200h-1c0-1-2-2-8-2-7%200-7%200-7%203M1%2044v6h399V37H1v7M0%2087v25h399V61H0v26m1%2039l-1%207v6h401v-14H202L1%20126\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;filtered next product export times&quot;
        title=&quot;filtered next product export times&quot;
        src=&quot;/static/165e8906ac8380d51cae20bfd8391857/8ff1e/filtered-next-product-export_time.png&quot;
        srcset=&quot;/static/165e8906ac8380d51cae20bfd8391857/9ec3c/filtered-next-product-export_time.png 200w,
/static/165e8906ac8380d51cae20bfd8391857/c7805/filtered-next-product-export_time.png 400w,
/static/165e8906ac8380d51cae20bfd8391857/8ff1e/filtered-next-product-export_time.png 800w,
/static/165e8906ac8380d51cae20bfd8391857/88829/filtered-next-product-export_time.png 963w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;and the result is inferred from the &lt;code class=&quot;language-text&quot;&gt;product_id&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;next_export_time&lt;/code&gt; fields:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 486px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.77777777777778%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;111\&apos;/%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;inferred product deletions&quot;
        title=&quot;inferred product deletions&quot;
        src=&quot;/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png&quot;
        srcset=&quot;/static/873d4f14ad7a5164784bf237c50673e7/9ec3c/deduplicated-missing-product-export_time.png 200w,
/static/873d4f14ad7a5164784bf237c50673e7/c7805/deduplicated-missing-product-export_time.png 400w,
/static/873d4f14ad7a5164784bf237c50673e7/11644/deduplicated-missing-product-export_time.png 486w&quot;
        sizes=&quot;(max-width: 486px) 100vw, 486px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This second solution is definitely easier to grasp and also probably more efficient.&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;The concepts described in this blog post can be tried out in the project
&lt;a href=&quot;https://github.com/findinpath/dbt_infer_deletions_in_batched_staging_data&quot;&gt;dbt_infer_deletions_in_batched_staging_data&lt;/a&gt;
that is accompanying this blog post.&lt;/p&gt;
&lt;p&gt;The project includes a &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;data build tool&lt;/a&gt; model that can be used to infer
deletions within the staging ELT data.
The nice thing about this SQL demo is that it contains also automatic
test specifications (based on &lt;a href=&quot;https://github.com/inside-track/dtspec&quot;&gt;dtspec&lt;/a&gt;) in order to verify
the accuracy of the implementation against &lt;a href=&quot;https://www.snowflake.com/&quot;&gt;Snowflake&lt;/a&gt; database.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a proof of concept for finding deletions within staging data loaded in batched fashion.
Eventual improvements to the &lt;a href=&quot;https://github.com/findinpath/dbt_infer_deletions_in_batched_staging_data&quot;&gt;dbt_infer_deletions_in_batched_staging_data&lt;/a&gt;
project code or ideas regarding alternative ways to solve this problem are very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[dbt historized macro]]></title><description><![CDATA[Historize the status changes performed on the database entities an obtain the date ranges
corresponding to the validity of each status in…]]></description><link>https://www.findinpath.com/dbt-historized-macro/</link><guid isPermaLink="false">https://www.findinpath.com/dbt-historized-macro/</guid><pubDate>Mon, 09 Nov 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Historize the status changes performed on the database entities an obtain the date ranges
corresponding to the validity of each status in which the entity has been during its lifecycle.&lt;/p&gt;
&lt;p&gt;Let’s use a fictional example of an online ecommerce store, where the &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; table
in the database backing the store is be one of the core entities.&lt;/p&gt;
&lt;p&gt;An order can find itself, throughout its lifecycle in one of the statuses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;placed&lt;/li&gt;
&lt;li&gt;shipped&lt;/li&gt;
&lt;li&gt;completed&lt;/li&gt;
&lt;li&gt;return_pending&lt;/li&gt;
&lt;li&gt;returned&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The content of the &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; table could be ingested in a data warehouse environment
in order to perform several reports concerning the performance of the ecommerce store.&lt;/p&gt;
&lt;p&gt;Nowadays a key aspect in ecommerce is the delivery time, or more concretely in the case
of our fictional ecommerce store, the period of time in which an order is marked with the
status &lt;code class=&quot;language-text&quot;&gt;shipped&lt;/code&gt; (order has been shipped to the customer and is currently in transit).&lt;/p&gt;
&lt;p&gt;The data warehouse environment could be therefore used to answer several questions that focus
on the shipment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What is the amount of orders that were marked in shipment during a certain day?
If the trend of the orders in shipment doesn’t follow closely the trend of completed
orders then the ecommerce shop has a growing number of unsatisfied customers.&lt;/li&gt;
&lt;li&gt;How many orders took more than &lt;code class=&quot;language-text&quot;&gt;3&lt;/code&gt; business days during a certain month?
Identify whether there the service level agreement for deliveries is not
being respected.&lt;/li&gt;
&lt;li&gt;What is the highest amount in days for an order in shipment during a certain month?
From an operational perspective this number should be kept as low as possible. Possible
spikes above the average should trigger automatic alerts for the person responsible
on the deliveries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the sake of having a concrete example, let’s suppose that the structure of the
staging table in the data warehouse environment that corresponds for the &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; database
table from the ecommerce store would have the following columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;load_id: monotonically increasing sequence for the order changes&lt;/li&gt;
&lt;li&gt;order_id: the order identifier&lt;/li&gt;
&lt;li&gt;updated_at: the date when the order was updated&lt;/li&gt;
&lt;li&gt;status: completion status of the order&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s try to answer the following question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is the amount of orders that were marked in shipment during a certain day?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; that a shipment can take multiple days until the package reaches the customer and the order is marked as &lt;code class=&quot;language-text&quot;&gt;completed&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A naive answer would be to count the distinct orders from the staging table where status &lt;code class=&quot;language-text&quot;&gt;shipped&lt;/code&gt; is appearing
during the specified day (e.g.: &lt;code class=&quot;language-text&quot;&gt;2018-01-01&lt;/code&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; order_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; jaffle_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stg_orders
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; updated_at &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2018-01-01&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2018-01-02&apos;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shipped&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this case the orders that started being shipped before the &lt;code class=&quot;language-text&quot;&gt;2018-01-01&lt;/code&gt; and completed during or after this day
would not be taken into account.&lt;/p&gt;
&lt;p&gt;One possible solution in order to the question previously mentioned would be to historize the status changes performed on the
orders.&lt;/p&gt;
&lt;p&gt;The image below contains historized order status changes.
Note that the [&lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;] date ranges  corresponding to an order are adjacent to each other
and only the latest entry corresponding to an order  is unbounded (&lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt; is set to &lt;code class=&quot;language-text&quot;&gt;NULL&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9d93cd79c0aea60e8a4ddbb6705ccda5/97b45/historized-order-status-changes.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.2674791533034%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;149\&apos;%3e%3cpath%20d=\&apos;M0%2053v3h401v-7H0v4m265%2068c0%201%205%202%2019%202l19-1c0-2-3-2-19-2l-19%201\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;historized-order-status-changes.png&quot;
        title=&quot;historized-order-status-changes.png&quot;
        src=&quot;/static/9d93cd79c0aea60e8a4ddbb6705ccda5/8ff1e/historized-order-status-changes.png&quot;
        srcset=&quot;/static/9d93cd79c0aea60e8a4ddbb6705ccda5/9ec3c/historized-order-status-changes.png 200w,
/static/9d93cd79c0aea60e8a4ddbb6705ccda5/c7805/historized-order-status-changes.png 400w,
/static/9d93cd79c0aea60e8a4ddbb6705ccda5/8ff1e/historized-order-status-changes.png 800w,
/static/9d93cd79c0aea60e8a4ddbb6705ccda5/6ff5e/historized-order-status-changes.png 1200w,
/static/9d93cd79c0aea60e8a4ddbb6705ccda5/97b45/historized-order-status-changes.png 1559w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This technique allows to easily find the shipment date ranges that overlap with the day on which the number or orders
in shipment needs to be calculated:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; jaffle_shop&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fct_orders
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; valid_from &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2018-01-02&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;valid_to &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2018-01-01&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; valid_to &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shipped&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This post provides a proof of concept on how to historize order status changes with &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt;
models on &lt;a href=&quot;https://www.snowflake.com/&quot;&gt;Snowflake&lt;/a&gt; data warehouse environment.&lt;/p&gt;
&lt;h2&gt;Getting started with dbt&lt;/h2&gt;
&lt;p&gt;As described in the &lt;a href=&quot;https://docs.getdbt.com/docs/introduction&quot;&gt;introduction to dbt&lt;/a&gt; :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;dbt (data build tool) enables analytics engineers to transform data in their warehouses by simply writing select statements.
dbt handles turning these select statements into tables and views.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;dbt does the T in ELT (Extract, Load, Transform) processes – it doesn’t extract or load data,
but it’s extremely good at transforming data that’s already loaded into your warehouse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/fishtown-analytics/jaffle_shop&quot;&gt;jaffle_shop&lt;/a&gt;
project is a useful minimum viable dbt project to get new &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt; users
up and running with their first dbt project. It includes &lt;a href=&quot;https://docs.getdbt.com/docs/building-a-dbt-project/seeds&quot;&gt;seed&lt;/a&gt;
files with generated data so that a user can run this project on their own warehouse.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;For more information on dbt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the &lt;a href=&quot;https://docs.getdbt.com/docs/introduction&quot;&gt;introduction to dbt&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href=&quot;https://docs.getdbt.com/docs/about/viewpoint&quot;&gt;dbt viewpoint&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;This blog post is accompanied by the &lt;a href=&quot;https://github.com/findinpath/dbt_jaffle_shop_historized&quot;&gt;dbt_jaffle_shop_historized&lt;/a&gt;
project which provides a working prototype for a minimal &lt;a href=&quot;https://www.getdbt.com/&quot;&gt;dbt&lt;/a&gt; project centered
on order status changes.&lt;/p&gt;
&lt;p&gt;The project contains the implementation for the &lt;code class=&quot;language-text&quot;&gt;historized&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; macro used for keeping track of the order status
changes in the lifecycle of orders  as well as corresponding &lt;a href=&quot;https://github.com/inside-track/dtspec&quot;&gt;dtspec&lt;/a&gt;
(data transformation specification) tests used to ensure the accuracy for the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; macro.&lt;/p&gt;
&lt;h2&gt;dbt historized macro&lt;/h2&gt;
&lt;p&gt;dbt offers the possibility of using &lt;a href=&quot;https://docs.getdbt.com/docs/building-a-dbt-project/jinja-macros/#macros&quot;&gt;macros&lt;/a&gt;.
Macros are pieces of code that can be reused multiple times - same as &lt;em&gt;functions&lt;/em&gt; in other programming languages.&lt;/p&gt;
&lt;p&gt;By means of using a specialized &lt;em&gt;macro&lt;/em&gt; for the historization functionality, the complexity of building the historization
queries is fully abstracted and the readability of the query used for historizing the status changes is greatly simplified:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;{{
    config&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        materialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;historized&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        primary_key_column_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;order_id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        valid_from_column_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;updated_at&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        load_id_column_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;load_id&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
}}

&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
        load_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        order_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        updated_at&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; {{ ref&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;stg_orders&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As can be seen in the code snippet above, same as a function, the &lt;code class=&quot;language-text&quot;&gt;historized&lt;/code&gt; macro takes a few parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;primary_key_column_name&lt;/code&gt;: the name of the column used to identify the staged entity&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;valid_from_column_name&lt;/code&gt;: the name of the column containing the timestamp when the staged entity has been last updated.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;load_id_column_name&lt;/code&gt;: the name of the column containing monotonically increasing sequence values used for distinguishing the precedence
between order status changes that happen within the same timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Behind the scenes, the logic encapsulated in the &lt;code class=&quot;language-text&quot;&gt;historized&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; macro follows the steps mentioned below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create a temporary table (suffixed by &lt;code class=&quot;language-text&quot;&gt;__tmp&lt;/code&gt;) which contains the staged entries along with &lt;code class=&quot;language-text&quot;&gt;md5_hash&lt;/code&gt; column containing hash of the concatenated columns used for
historization (e.g. : &lt;code class=&quot;language-text&quot;&gt;status&lt;/code&gt; in case of this example, but nothing speaks agains historizing more columns on an entity).
The hash column can be used to easily distinguish whether two adjacent entries (ordered by load id) are
duplicated (have the same values for the versioned columns).
For more information on hashing, read on &lt;a href=&quot;https://blog.getdbt.com/the-most-underutilized-function-in-sql/&quot;&gt;The most underutilized function in SQL&lt;/a&gt; dbt blog post.&lt;/li&gt;
&lt;li&gt;in case whether there are new entries staged (from the &lt;code class=&quot;language-text&quot;&gt;__tmp&lt;/code&gt; suffixed source table) that correspond to an
unbounded historized entity (&lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt; column is &lt;code class=&quot;language-text&quot;&gt;NULL&lt;/code&gt;) in the target table, then set the upper bound column &lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;
to the value corresponding to the minimum &lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt; value of the staged entries corresponding on the target entity&lt;/li&gt;
&lt;li&gt;deduplicate the staged entries (based on the &lt;code class=&quot;language-text&quot;&gt;md5_hash&lt;/code&gt; column)&lt;/li&gt;
&lt;li&gt;obtain the validity range for each of the staged records (&lt;code class=&quot;language-text&quot;&gt;valid_from&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;valid_to&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;join the staged records with the records from the target table and filter out eventual duplicates (based on &lt;code class=&quot;language-text&quot;&gt;md5_hash&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;insert the staged records in the target table.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;For more information on dbt macros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the &lt;a href=&quot;https://docs.getdbt.com/docs/building-a-dbt-project/jinja-macros/#macros&quot;&gt;introduction to Jinja macros&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Read about the &lt;a href=&quot;https://docs.getdbt.com/docs/building-a-dbt-project/building-models/materializations/&quot;&gt;dbt materializations&lt;/a&gt;
and their corresponding implementation on &lt;a href=&quot;https://docs.getdbt.com/docs/building-a-dbt-project/building-models/materializations/&quot;&gt;Github&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Data tranformation testing&lt;/h2&gt;
&lt;p&gt;It takes quite a while to develop a &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; macro and afterwards it needs to be tested whether it works.
It may even happen that while it is productively used, a bugfix needs to be done or that the functionality of
the macro needs to be extended.
By means of using automated tests for data transformations there could be ensured that the macro works
as expected with an extensive battery of tests on a test environment.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/inside-track/dtspec&quot;&gt;dtspec&lt;/a&gt; is an open-source framework written in Python which can be used
for specifying and testing data transformations.&lt;/p&gt;
&lt;p&gt;Within &lt;code class=&quot;language-text&quot;&gt;dtspec&lt;/code&gt; is specified in a &lt;a href=&quot;https://en.wikipedia.org/wiki/YAML&quot;&gt;yaml&lt;/a&gt; format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the source data in the table(s) to be used by the data transformation(&lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;the expected data from the target table(s).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dtspec&lt;/code&gt; framework offers means to read the yaml specification, and match the data from the actual tables, once
the data transformation has been performed (via &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt;) with the data from the specification scenario.&lt;/p&gt;
&lt;p&gt;Below is presented a snippet of the output used for running the tests on the project
&lt;a href=&quot;https://github.com/findinpath/dbt_jaffle_shop_historized&quot;&gt;dbt_jaffle_shop_historized&lt;/a&gt;
which accompanies this blog post:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;Executing test specification tests/demo-spec.yml
Truncating data from the tables [&amp;#39;raw_orders&amp;#39;, &amp;#39;fct_orders&amp;#39;]
Inserting input data into the source table raw_orders
/home/findinpath/dbt_jaffle_shop_historized/tests/..
Running with dbt=0.18.1
Found 2 models, 11 tests, 0 snapshots, 0 analyses, 148 macros, 0 operations, 1 seed file, 0 sources

21:51:29 | Concurrency: 4 threads (target=&amp;#39;dev&amp;#39;)
21:51:29 |
21:51:29 | 1 of 2 START table model jaffle_shop.stg_orders...................... [RUN]
21:51:31 | 1 of 2 OK created table model jaffle_shop.stg_orders................. [SUCCESS 1 in 2.02s]
21:51:31 | 2 of 2 START historized model jaffle_shop.fct_orders................. [RUN]
21:51:35 | 2 of 2 OK created historized model jaffle_shop.fct_orders............ [SUCCESS 1 in 4.39s]
21:51:36 |
21:51:36 | Finished running 1 table model, 1 historized model in 10.58s.

Completed successfully

Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2
Loading data from the target table fct_orders
Loading actuals for target fct_orders
Asserting Building fct_orders out of the raw_orders when the fct_orders table is empty: Basic full refresh loading of the historized fact table `fct_orders`
Tested scenarios: - order1 : the expected historized entries should follow the representation: placed &amp;gt; shipped &amp;gt; completed - order2 : the expected historized entries should follow the representation: placed - order3 : contains duplicated (with the same `updated_at` and `status` values) entries. The duplicates should be filtered out from the processing. The expected historized entries should follow the representation: placed &amp;gt; shipped &amp;gt; return_pending
 PASSED&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As can be seen from the log snippet above, there are several steps performed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the source &amp;#x26; target tables for the data transformation are truncated to make sure that the execution
of the tests is fully repeatable.&lt;/li&gt;
&lt;li&gt;input data is inserted in the source table(s)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; is executed for performing the data transformation on the: &lt;code class=&quot;language-text&quot;&gt;jaffle_shop.stg_orders&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;jaffle_shop.fct_orders&lt;/code&gt; models.&lt;/li&gt;
&lt;li&gt;the actual data from target tables of the test specification is loaded and is used in asserting
the equality with the content of the columns mentioned in the target table from the test specification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; : When running the tests for this project, there can be noticed that it takes quite some time to execute them.
This happens in part because Snowflake takes roughly a bit under a second to run any kind of query, even
if the query is performed on an empty database.&lt;/p&gt;
&lt;p&gt;Nevertheless, having automated tests for the &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt; models, even though they are slow, is much better than doing the
tests by hand or not doing them at all and leaving production as a playground for the developed models.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a proof of concept for historizing database entities with &lt;code class=&quot;language-text&quot;&gt;dbt&lt;/code&gt;.
Eventual improvements to the &lt;a href=&quot;https://github.com/findinpath/dbt_jaffle_shop_historized&quot;&gt;dbt_jaffle_shop_historized&lt;/a&gt;
project code or ideas regarding alternative ways to historize changes on the database entities are very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Feature toggles as a service]]></title><description><![CDATA[A feature toggle is a technique in software development used for allowing to
test a software feature on a live system even before it is…]]></description><link>https://www.findinpath.com/unleash-feature-toggles-service/</link><guid isPermaLink="false">https://www.findinpath.com/unleash-feature-toggles-service/</guid><pubDate>Mon, 05 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A feature toggle is a technique in software development used for allowing to
test a software feature on a live system even before it is completely implemented
and ready to be released to the general public. The feature toggle is used to hide,
enable or disable the feature during runtime.&lt;/p&gt;
&lt;p&gt;Feature toggles are an important technique used for the implementation of &lt;a href=&quot;https://en.wikipedia.org/wiki/Continuous_delivery&quot;&gt;continous
delivery&lt;/a&gt; with the intricate aim
of providing rapid feedback about the features implemented to the software engineers.&lt;/p&gt;
&lt;p&gt;Feature toggles are essentially variables that are used inside conditional statements.
Therefore, the blocks of code inside these conditional statements can be toggled
&lt;span style=&quot;color:green&quot;&gt;&lt;strong&gt;ON&lt;/strong&gt;&lt;/span&gt; or &lt;span style=&quot;color:red&quot;&gt;&lt;strong&gt;OFF&lt;/strong&gt;&lt;/span&gt; depending on the value of the feature toggles.
In a simple form a feature toggle works in the following fashion:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;modern page header is enabled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// render modern page header&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// render classic page header&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Feature toggles can be stored as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;properties in a configuration file&lt;/li&gt;
&lt;li&gt;row entries in a database table&lt;/li&gt;
&lt;li&gt;entries in external feature toggle service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a description about feature flags functionality done by Chad Dickerson from &lt;a href=&quot;https://www.etsy.com&quot;&gt;Etsy&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We practice continuous deployment and make small changes frequently to the site.
We use what we call “config flags”, …and a lot of the code for features runs “dark” for days or weeks,
and feature launches mean flipping a switch in the code.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ideally, the process of enabling or disabling a new feature in production
should happen without any code changes, new deployment or application restart.&lt;/p&gt;
&lt;h2&gt;Unleash&lt;/h2&gt;
&lt;p&gt;As pointed out in the &lt;a href=&quot;https://unleash.github.io/&quot;&gt;Unleash&lt;/a&gt; project page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Feature toggles decouple deployment of code from release of new features&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Through the usage of &lt;a href=&quot;https://unleash.github.io/&quot;&gt;Unleash&lt;/a&gt; feature toggle service,
new functionalities can be released to production with the possibility of
enabling/disabling them at any later point of time through an admin UI outside
of the running web application that makes use of the feature toggles.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;(https://unleash.github.io/)&quot;&gt;Unleash&lt;/a&gt;
feature toggle service comes with a lot of functionalities to address in depth
the needs related to feature toggles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unleash client SDK can be embedded in any kind of application written in any popular programming language&lt;/li&gt;
&lt;li&gt;provides an administration web UI to manage and track within the last minute/hour the usage of the feature toggles&lt;/li&gt;
&lt;li&gt;provides a set of strategies associated with the feature toggles in order to enable gradual rollout of the features.
In this way, a feature could be safely rolled out only for a subset (e.g. : specific emails, specific IPs,
only Desktop/Mobile) of the users of the website that makes use of feature toggles.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://unleash-hosted.com&quot;&gt;Unleash&lt;/a&gt; also comes in an enterprise edition with additional features and a hosted option (SaaS).
Check out &lt;a href=&quot;https://unleash-hosted.com&quot; target=&quot;_blank&quot;&gt;https://unleash-hosted.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Common activation strategies offered by Unleash:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Active For users with a specified userId&lt;/li&gt;
&lt;li&gt;GradualRollout to X-percent of the users&lt;/li&gt;
&lt;li&gt;Active for beta users&lt;/li&gt;
&lt;li&gt;Active only for application instances running on host x.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; that Unleash offers the possibility to easily write custom strategies (e.g. : active for users with a specified device - based
on the &lt;code class=&quot;language-text&quot;&gt;User-Agent&lt;/code&gt; web request header).&lt;/p&gt;
&lt;p&gt;Below is shown an architectural overview of the Unleash system:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 800px;&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/82c6a53e4faed8839e371bea8e72c4f4/3b243/unleash-architecture.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.37109375%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/svg+xml,%3csvg%20xmlns=\&apos;http://www.w3.org/2000/svg\&apos;%20width=\&apos;400\&apos;%20height=\&apos;221\&apos;%3e%3cpath%20d=\&apos;M18%2026l-1%205c-2%201-2%205-2%2021v20h128v-2l1-10c0-7%201-7%203-7%203%200%203-3%200-4-3%200-3%200-3-10v-9H38v-3l-1%207v10H19V34l-1-8m58%2014v12l1%2010h29c34%200%2032%201%2032-11%200-13%203-12-32-12l-30%201m46%201c-3%202-2%206%202%207l3%203%203%202h2l1-1%201-3c0-4%200-4-3-4l-3-1-4-4-2%201m140%2034v12l1%2010h44c39%200%2044%200%2044%202l-54%201h-53l-5%202-7%206c-1%203-2%203-8%203-5-1-6-1-6%201l4%202%203%201h1c0-2%203-1%203%201%201%201%200%202-1%203s0%203%201%203c1-1%203%201%206%203%207%207%2017%208%2028%206%205-2%2014-2%2047-2h41v24l7%201%207-1c1-1%206-1%206%201s2%202%209%204l8-1%203-1c4-1%205-7%206-32%200-26-1-32-6-33l-3-1c0-2-5-3-7-2h-3l-4%201c-1%200-2%201-2%203s0%202-3%202-4-1-4-2l-3-1h-4V74h-48l-48%201M18%2082l-1%203c-2%201-2%205-2%2022v20h63l65-1%201-11c0-8%201-9%203-9%203%200%203-3%200-3s-3%200-3-10v-9H91c-52%200-53%200-53-2s-1-2-10-2c-8%200-10%200-10%202m1%209v9h18V81H19v10m11-7c-1%201-2%202-5%201-3%200-4%200-4%202h1l3%201h2l-2%202c-5%203%200%2010%205%208%202-1%203-2%203-5s0-3-3-2h-4l4-1%201-1%202-1%202-2-1-1c-1%201-2%200-2-1h-2m47%2010l-1%2012%201%2010h29l31-1%201-10c0-13%202-12-32-12l-29%201m46%202c-4%201-3%206%202%208l3%202c0%204%206%203%207%200s0-6-2-5l-1-1h-1c-2%201-2%201-3-1%200-3-2-4-5-3M18%20139l-1%203c-2%201-2%204-2%2021v21h128l1-3c1-2%201-3%203-3l2%201v18h74v-30h-73l-1%202c0%201-1%202-3%202s-2%200-2-4%200-4%203-4l2-2-2-2c-2%200-3%200-3-8v-9H38v15H19v-10l-1-8m59%2012l-1%2011%201%2011h28l29-1%201-1c2%200%203-1%203-9%200-13%202-12-31-12l-30%201m45%203l-2%202c0%201%204%205%206%205l1%201%203%203h2l1-1c2%201%202-4%201-6l-3-1-3-1-4-4-2%202m249%2054c0%204%201%205%202%203h6c3%202%2012%201%2012-1%201-2%201-2%201%200%200%203%202%203%202%200%200-2-3-6-5-6v3l-1%202v-1c0-1-1-2-4-2s-4%201-4%202h-2c-1-2-1-2-3-1s-2%201-2-1l-1-2-1%204\&apos;%20fill=\&apos;%23f9ebd2\&apos;%20fill-rule=\&apos;evenodd\&apos;/%3e%3c/svg%3e&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Unleash architecture&quot;
        title=&quot;Unleash architecture&quot;
        src=&quot;/static/82c6a53e4faed8839e371bea8e72c4f4/8ff1e/unleash-architecture.png&quot;
        srcset=&quot;/static/82c6a53e4faed8839e371bea8e72c4f4/9ec3c/unleash-architecture.png 200w,
/static/82c6a53e4faed8839e371bea8e72c4f4/c7805/unleash-architecture.png 400w,
/static/82c6a53e4faed8839e371bea8e72c4f4/8ff1e/unleash-architecture.png 800w,
/static/82c6a53e4faed8839e371bea8e72c4f4/3b243/unleash-architecture.png 1024w&quot;
        sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
        loading=&quot;lazy&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As can be seen from the picture above, the client applications are supposed to embed the Unleash client SDK
and communicate with the server for reading the feature toggles. The Unleash server is written in Node.js.&lt;/p&gt;
&lt;p&gt;At the time of this writing, there exist Unleash client SDK implementations for Java, Node.js, Go, Ruby, Python and .NET Core.&lt;/p&gt;
&lt;p&gt;The client SDKs connect internally to Unleash REST API and retrieve the feature toggles with information
about their associated strategy:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;➜  ~ &lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -s http://localhost:4242/api/client/features &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;,
  &lt;span class=&quot;token string&quot;&gt;&quot;features&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;blueHeader&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Whether the header should be shown in blue&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; true,
      &lt;span class=&quot;token string&quot;&gt;&quot;strategies&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;default&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;variants&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; null,
      &lt;span class=&quot;token string&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-09-30T21:20:46.154Z&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;qotd&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Quote of the day&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; true,
      &lt;span class=&quot;token string&quot;&gt;&quot;strategies&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;userWithId&quot;&lt;/span&gt;,
          &lt;span class=&quot;token string&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;userIds&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bob,john&quot;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;variants&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; null,
      &lt;span class=&quot;token string&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-09-30T21:34:37.612Z&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;specialGreeting&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;The user receives a customized special greeting&quot;&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;enabled&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; true,
      &lt;span class=&quot;token string&quot;&gt;&quot;strategies&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;deviceClass&quot;&lt;/span&gt;,
          &lt;span class=&quot;token string&quot;&gt;&quot;parameters&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;device&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Desktop,Tablet&quot;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;token string&quot;&gt;&quot;variants&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; null,
      &lt;span class=&quot;token string&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2020-10-01T20:03:16.114Z&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As pointed out by the Unleash &lt;a href=&quot;https://unleash.github.io/docs/client_specification&quot;&gt;documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To be super fast, the client SDK caches all feature toggles and their current configuration in memory.
The activation strategies are also implemented in the SDK.&lt;/p&gt;
&lt;p&gt;This makes it really fast to check if a toggle is on or off because it is just a simple function operating
on local state, without the need to poll data from the database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; that this means that no data (regardless whether it is sensitive or not) about the users (e.g. : username, IP, name, address, etc. )
of the website will be sent towards the Unleash server in order to check whether a feature is enabled when displaying a web page.&lt;/p&gt;
&lt;p&gt;In case there exist doubts about having an ever increasing payload related to the Unleash feature toggles configuration
that needs to be continously refreshed in the client application, Pete Hodgson,
in his article &lt;a href=&quot;https://martinfowler.com/articles/feature-toggles.html&quot;&gt;Feature Toggles (aka Feature Flags)&lt;/a&gt;,
points out an interesting insight about the amount of feature toggles employed in a web application:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Savvy teams view their Feature Toggles as inventory which comes with a carrying cost, and work
to keep that inventory as low as possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Competition for Unleash&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://launchdarkly.com/&quot;&gt;LaunchDarkly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/checkr/flagr&quot;&gt;FLAGR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wix-incubator/petri&quot;&gt;Petri&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;This blog post comes with a proof of concept &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;Spring Boot&lt;/a&gt;
web application that showcases the functionality provided by &lt;a href=&quot;https://unleash.github.io/&quot;&gt;Unleash&lt;/a&gt;
feature toggle service.&lt;/p&gt;
&lt;p&gt;Check out the source code on &lt;a href=&quot;https://github.com/findinpath/unleash-demo&quot;&gt;Github&lt;/a&gt; and feel free to
try it out.&lt;/p&gt;
&lt;h2&gt;Articles related to feature toggles&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pete Hodgson - &lt;a href=&quot;https://martinfowler.com/articles/feature-toggles.html&quot;&gt;Feature Toggles (aka Feature Flags)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Martin Fowler - &lt;a href=&quot;https://martinfowler.com/bliki/FeatureToggle.html&quot;&gt;FeatureToggle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a proof of concept for using feature toggles.
Eventual improvements to the project code or ideas regarding
alternative ways to gradually roll out features are very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Price range aggregations]]></title><description><![CDATA[Recipes for building dynamically balanced price range aggregations with
Elasticsearch. A common functionality in each webshop is the ability…]]></description><link>https://www.findinpath.com/price-range-aggregations/</link><guid isPermaLink="false">https://www.findinpath.com/price-range-aggregations/</guid><pubDate>Tue, 22 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recipes for building dynamically balanced price range aggregations with
Elasticsearch.&lt;/p&gt;
&lt;p&gt;A common functionality in each webshop is the ability to show price range
aggregations when searching for products.&lt;/p&gt;
&lt;p&gt;When searching for a generic product (e.g. : backpack, shoes) the users
are nowadays accustomed to see in the search result page a panel containing
the distribution of the product prices matching their search.&lt;/p&gt;
&lt;p&gt;e.g.: when searching for &lt;code class=&quot;language-text&quot;&gt;backpack&lt;/code&gt; on a marketplace website, there could
be displayed the following price range listing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;up to 20 EUR - 10 items&lt;/li&gt;
&lt;li&gt;20 to 40 EUR - 28 items&lt;/li&gt;
&lt;li&gt;40 to 80 EUR - 52 items&lt;/li&gt;
&lt;li&gt;80 to 180 EUR - 12 items&lt;/li&gt;
&lt;li&gt;from 180 EUR - 4 items&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Elasticsearch supports performing &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-range-aggregation.html&quot;&gt;range aggregations&lt;/a&gt;
on a search request, but it requires the user to specify explicitly the ranges for the aggregation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X GET &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/products/_search?pretty&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
     &quot;match&quot;: {
     	&quot;category&quot;: &quot;Luggage&quot;
     }
  },
  &quot;aggs&quot;: {
    &quot;price_ranges&quot;: {
      &quot;range&quot;: {
        &quot;field&quot;: &quot;price&quot;,
        &quot;ranges&quot;: [
          { &quot;to&quot;: 100.0 },
          { &quot;from&quot;: 100.0, &quot;to&quot;: 200.0 },
          { &quot;from&quot;: 200.0 }
        ]
      }
    }
  }
}
&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The ranges in the price aggregation depend on the other hand pretty much on what
is being searched.
The price ranges that may be relevant to the users when searching for &lt;code class=&quot;language-text&quot;&gt;shoes&lt;/code&gt; will
very likely be different from the ones that will be shown when searching for &lt;code class=&quot;language-text&quot;&gt;laptop&lt;/code&gt;
because most of the shoes won’t cost more than 150 EUR, where most of the decent laptops
start at 300-400 EUR.&lt;/p&gt;
&lt;p&gt;There is currently quite an old open feature request to support
dynamically calculated price ranges:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/elastic/elasticsearch/issues/9572&quot;&gt;https://github.com/elastic/elasticsearch/issues/9572&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This blog post tries to provide a few possible answers on how to come
up with balanced ranges for the price aggregations on Elasticsearch searches.&lt;/p&gt;
&lt;h2&gt;Static price ranges&lt;/h2&gt;
&lt;p&gt;As described above, Elasticsearch already offers the ability of specifying
ranges for the price in a search request.
The users like to choose from a list of up to 5 price ranges in order to
avoid being overwhelmed with options.&lt;/p&gt;
&lt;p&gt;The main drawback when using this approach is that at the request time
there is no way to know how the prices of the products matching the search
will be distributed.&lt;/p&gt;
&lt;p&gt;As a consequence, the price ranges used will sometimes not contain relevant
information.&lt;/p&gt;
&lt;h2&gt;Use two queries to get balanced price range aggregations&lt;/h2&gt;
&lt;p&gt;Obtain first the distribution of prices for the search by using the
&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.9/search-aggregations-metrics-percentile-aggregation.html&quot;&gt;percentiles aggregation&lt;/a&gt;
and subsequently use this information for specifying the price ranges of the price
aggregation in the second search request.&lt;/p&gt;
&lt;p&gt;The few buckets that will be shown to the user for the price ranges will mirror
the actual price distribution for the products that are matching the search.&lt;/p&gt;
&lt;p&gt;The principal drawback in this approach is that there are two search requests needed to
be made against Elasticsearch in order to obtain this information.
Having two requests may very likely be considered a drawback in adopting this strategy
for websites with a high amount of users performing searches.&lt;/p&gt;
&lt;p&gt;For adding a bit of clarity here, a pseudocode version of the searches is exposed below, in the case
when there are up to 3 price ranges to be shown to the user:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;Search 1: Get p33, p66 percentiles aggregation for the prices matching the search
Search 2: Use [0 - p33], [p33-p66], [p66 - *] as ranges in the price aggregation&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Collapse the buckets&lt;/h2&gt;
&lt;p&gt;This method builds on top of the static price ranges previously described.
Instead of retrieving a handful of buckets for the price range aggregation,
this method relies on specifying a lot of price range buckets (e.g. : 30/40/50)
which are then collapsed/merged on the client side to a handful (e.g. : 3/4/5) of relevant price range
buckets.&lt;/p&gt;
&lt;p&gt;The main performance drawback in this approach is that there are much more price range buckets
specified to be aggregated in the search request compared to the number price range buckets that are
actually needed to be displayed to the user.&lt;/p&gt;
&lt;h2&gt;Source Code&lt;/h2&gt;
&lt;p&gt;Check out the source code of the project &lt;a href=&quot;https://github.com/findinpath/elastic-price-range-aggregation&quot;&gt;elastic-price-range-aggregation&lt;/a&gt;
accompanying this blog post. This project contains code corresponding to each of the strategies
presented in this blog post.&lt;/p&gt;
&lt;p&gt;This project contains a JUnit testcase that spawns a &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt;
&lt;a href=&quot;https://www.testcontainers.org/modules/elasticsearch/&quot;&gt;Elasticsearch container&lt;/a&gt; fills it with
test data and tries through different strategies to retrieve the price range aggregations.&lt;/p&gt;
&lt;p&gt;Run the command&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;for executing the tests from this project.&lt;/p&gt;
&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;This blog post serves as a proof of concept for performing
price aggregations. Eventual improvements to the project code or ideas regarding
alternative ways to get the price aggregations are very much welcome.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Search Alerts]]></title><description><![CDATA[Send notifications to the users whenever new content matching their interests is added
on the platform. Search alerts are designed to…]]></description><link>https://www.findinpath.com/search-alerts/</link><guid isPermaLink="false">https://www.findinpath.com/search-alerts/</guid><pubDate>Mon, 20 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Send notifications to the users whenever new content matching their interests is added
on the platform.&lt;/p&gt;
&lt;p&gt;Search alerts are designed to monitor changes to the documents being searched, whereas search engines
are designed for finding documents matching a specific search term.&lt;/p&gt;
&lt;p&gt;Check out the blog post on percolator to get an idea on how the search upside down works in Elasticsearch:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;pt&quot; dir=&quot;ltr&quot;&gt;A short primer &lt;a href=&quot;https://twitter.com/hashtag/elasticsearch?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#elasticsearch&lt;/a&gt; percolator &lt;a href=&quot;https://t.co/TLglOY15Kf&quot;&gt;https://t.co/TLglOY15Kf&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1283027120109883404?ref_src=twsrc%5Etfw&quot;&gt;July 14, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;This proof of concept handles both kind of notifications for its users:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;immediate: as soon as new content of interest is made available&lt;/li&gt;
&lt;li&gt;batched: hourly/ daily/ weekly in case there new content of interest is made available&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the sake of better understanding the purpose of this system there will be considered
that this system handles the search alert functionality for a news platform.&lt;/p&gt;
&lt;p&gt;The users of the news platform which have interest for a certain topic can register search alerts
to be notified (e.g.: by email) when new articles matching their criteria are published on the platform.
The search alerts can be configured by the users to send notifications immediately/ hourly/ daily/ weekly
about new interesting content.&lt;/p&gt;
&lt;h2&gt;Technological stack&lt;/h2&gt;
&lt;p&gt;The tech stack of the presented system makes use of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt; for streaming data between the components of the system&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/elasticsearch/&quot;&gt;Elasticsearch&lt;/a&gt; distributed, RESTful search engine providing
&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html&quot;&gt;percolator&lt;/a&gt; functionality&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cassandra.apache.org/&quot;&gt;Apache Cassandra&lt;/a&gt; for storing information about the search alert
messages already sent in order to avoid sending duplicated messages (e.g.: for a batched search alert on a specific hour/day).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;div&gt;&lt;re-img src=&quot;search-alerts-architecture.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRk4AAABXRUJQVlA4IEIAAACQAwCdASoUAAcAPtFUo0uoJKMhsAgBABoJZwAAXKdZiVkynrOAAP72Enah5+UoD8x4qLWwL9Mbxbt+rq7oDj0lAAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.9456521739130435,&amp;quot;src&amp;quot;:&amp;quot;/static/26fc83562bb117907161ccef5fbc12ac/32a6f/search-alerts-architecture.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/26fc83562bb117907161ccef5fbc12ac/dd2ee/search-alerts-architecture.webp 200w,\n/static/26fc83562bb117907161ccef5fbc12ac/f333d/search-alerts-architecture.webp 400w,\n/static/26fc83562bb117907161ccef5fbc12ac/32a6f/search-alerts-architecture.webp 800w,\n/static/26fc83562bb117907161ccef5fbc12ac/2cf13/search-alerts-architecture.webp 1200w,\n/static/26fc83562bb117907161ccef5fbc12ac/1e1a6/search-alerts-architecture.webp 1600w,\n/static/26fc83562bb117907161ccef5fbc12ac/fcd2f/search-alerts-architecture.webp 1626w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/26fc83562bb117907161ccef5fbc12ac/fcd2f/search-alerts-architecture.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;search-alerts-architecture.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:272,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;136&amp;apos;%3e%3cpath%20d=&amp;apos;M134%2024c0%203%201%204%204%203%202%200%202%200%201%201-1%202-2%202-4%201s-2-2-2-5-2-2-4%201v5c1%200%202%201%201%202%200%202%202%204%205%204%202%200%203-1%203-3s1-3%202-2v1l-1%201%201%201%202-1%201-2v-5l-2-2c-1-4-6-4-7%200m138%202c-3%202-4%204-1%203h2l3%203h-9l4%201%206%201%201%201h1l4-1c2-1%203-1%202-2l1-1c1%200%203-4%202-5h-16m-162%207c-2%201-2%201-2%2011%200%2013%200%2013%209%2013l5%201v1c1%201%201%200%201-1%200-2%200-2%208-2l7%201c0%203-1%203-9%203-6%200-6%200-3%201s4%203%204%2013c0%2011%201%2010%201-2v-9l4-2%204-1V50c0-13%200-13-8-13-4%200-5%200-5-2%200-3-10-4-16-2m139%205l-1%2011c0%2012%200%2013%208%2013%202%200%203%200%203%202%200%203%202%205%205%205s3%200%203%2010l1%206c1%201%202-10%201-15l2-1c6%200%206%200%206-12s0-12-6-12c-5%200-5-1-5-3l-2-4h-15M141%2056v6h31c23%200%2026-1%2012-1-16%200-19%200-18-2l-9-1c-12%200-13%202-1%202%208%201%208%201-3%201h-11V50h29c24%200%2028%200%2030%202%202%201%203%201%201-1-1-2-6-2-31-2h-30v7m141%200v6h63l-31-1h-31V50h29c32%200%2031%200%2032%208l1%204v-5c0-8%201-8-33-8h-30v7M131%2083l13%201h13v17h-21v8h-18l-19%201%2019%201h18v4l3%206%203%203h39v-5c0-3%200-4%202-3l2-1c1-1%200-1-1-1-3%200-3%200-3-4l-1-4-2-1c0-3-3-4-11-4h-8v-8c0-12%201-11-15-11l-13%201m151%206l1%201%201%208v8h-21v8h-8l-9%201%209%201h8v4l3%206%203%203h38v-4l1-4h13l13-1-14-1h-13v-4l-1-4-1-1c0-3-4-4-11-4h-8v-8l-1-9h-3m-140%2026v8h37v-10h-4c-3%200-3-1-3-3v-3h-30v8m199-2c-4%202-4%202-4%207%200%204%200%204%204%207l4%202%205-2c4-2%204-2%204-7%200-4%200-5-4-7s-4-2-9%200m-72%207v8h37v-16l-3%201-3%201v2l3%201c2%201%202%201-1%201s-3-1-3-3v-3h-30v8&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;There are some subtle differences in handling the immediate and the batched search alerts.&lt;/p&gt;
&lt;p&gt;The search alert immediate notifications should be sent as their name denotes as soon as a corresponding
news is being published, whereas the batched notifications are to be sent only after their corresponding
frequency time window (e.g.: hour/day/week) elapses.&lt;/p&gt;
&lt;h3&gt;Percolator&lt;/h3&gt;
&lt;p&gt;This is a rather simple component which consumes news records from the input Apache Kafka topic,
percolates them and writes each of the corresponding search alerts retrieved from Elasticsearch
to its corresponding topic to be consumed by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;em&gt;immediate-messenger&lt;/em&gt; component in case that the search alert has an immediate notification policy&lt;/li&gt;
&lt;li&gt;the &lt;em&gt;batched-messenger&lt;/em&gt; component in case that the search alert has a batched notification policy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The search alerts stored in Elasticsearch should store at a minimum meta information like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the notification policy: immediate/hourly/daily/weekly&lt;/li&gt;
&lt;li&gt;the email address of the user who registered the search alert&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Immediate search alerts&lt;/h3&gt;
&lt;p&gt;Handling immediate search alerts is relatively straightforward by following the architecture concept
diagram presented above.&lt;/p&gt;
&lt;p&gt;Each time a new document (e.g. : news) is being percolated, for each of the corresponding search alerts
there will be written to the &lt;code class=&quot;language-text&quot;&gt;immediate&lt;/code&gt; Apache Kafka topic an entry containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;news details&lt;/li&gt;
&lt;li&gt;search agent details&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using the search alert id as record key for the message sent towards the topic &lt;code class=&quot;language-text&quot;&gt;immediate&lt;/code&gt; ensures
that the notifications sent for a search alert will be handled in a serial fashion (which comes in handy
when checking for duplicates).&lt;/p&gt;
&lt;p&gt;On the &lt;em&gt;immediate-messenger&lt;/em&gt; side, the record will be simply sent to the mail (or push) notification service and
will be subsequently recorded to the database to avoid sending duplicate messages to the user in case
of errors/bugs on the &lt;em&gt;percolator&lt;/em&gt; component side.&lt;/p&gt;
&lt;p&gt;To avoid sending duplicates, in the Cassandra database backing the &lt;em&gt;immediate-messenger&lt;/em&gt; component will be
checked before sending a notification whether for the key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search alert id (partition key)&lt;/li&gt;
&lt;li&gt;news id (clustering key)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;has already been recorded.&lt;/p&gt;
&lt;p&gt;The body of the notification for the search alert is composed out of the incoming news details
in the consumer record from &lt;code class=&quot;language-text&quot;&gt;immediate&lt;/code&gt; Apache Kafka topic.&lt;/p&gt;
&lt;h3&gt;Batched search alerts&lt;/h3&gt;
&lt;p&gt;If the immediate search alerts are sent straight away after percolating a new document (e.g.: news),
the batched search alerts on the other hand need to be &lt;em&gt;“parked”&lt;/em&gt; until their corresponding
notification period (e.g.: hour/day/week) elapses.&lt;/p&gt;
&lt;p&gt;Considering that a search alert is configured to hourly notify a user about new articles matching a specific
search criteria, when an article is being published on  &lt;code class=&quot;language-text&quot;&gt;2020-06-19 09:21:05&lt;/code&gt; on the news platform,
the search alert should then notify the user at &lt;code class=&quot;language-text&quot;&gt;2020-06-19 10:00:00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The approach used by this proof of concept to solve this problem is to use corresponding topics for each
kind of frequency window(hour/day/week) offered by the search alert service.&lt;/p&gt;
&lt;p&gt;In the above mentioned case, the search alert information would be pushed to the topic
&lt;code class=&quot;language-text&quot;&gt;hourly_1592553600000&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;1592553600000&lt;/code&gt; corresponds  to &lt;code class=&quot;language-text&quot;&gt;2020-06-19 10:00:00&lt;/code&gt;).
In case that the search alert would have been configured to delivery notifications daily, then
the search alert information would be then published to the topic &lt;code class=&quot;language-text&quot;&gt;daily_1592604000000&lt;/code&gt; (&lt;code class=&quot;language-text&quot;&gt;1592604000000&lt;/code&gt;
corresponds to &lt;code class=&quot;language-text&quot;&gt;2020-06-20 00:00:00&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The consumption of the entries from the &lt;code class=&quot;language-text&quot;&gt;hourly_1592553600000&lt;/code&gt; topic will start at the earliest
at &lt;code class=&quot;language-text&quot;&gt;2020-06-19 10:00:00&lt;/code&gt;.
The consumption of the entries from the &lt;code class=&quot;language-text&quot;&gt;daily_1592604000000&lt;/code&gt; topic will start at the earliest
at &lt;code class=&quot;language-text&quot;&gt;2020-06-20 00:00:00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;batched-messenger&lt;/em&gt; component is responsible for handling the batched
search alerts from a specified topic that contain entries for the hourly/daily/weekly search alerts.
The consumption from the Apache Kafka topic corresponding to a specific hour/day/week time window
will begin only after its corresponding time window has elapsed.
Once the consumption of the Apache Kafka topic reaches the end of the topic, the
&lt;em&gt;batched-messenger&lt;/em&gt; component instance can end its runtime because there will be no new content
added to this topic in the future.&lt;/p&gt;
&lt;p&gt;Once all the partitions of the Apache Kafka topic &lt;code class=&quot;language-text&quot;&gt;hourly_1592553600000&lt;/code&gt; are fully read by the
&lt;em&gt;batched-messenger&lt;/em&gt; component (current offset is equal with the end offset on all the partitions
of the topic), the batched topic can be considered obsolete (and can be eventually be deleted)
and not being taken anymore into consideration for consumption.&lt;/p&gt;
&lt;p&gt;To avoid sending duplicates, in the Cassandra database backing the &lt;em&gt;batched-messenger&lt;/em&gt; component will be
checked before sending a notification whether for the key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;search alert id (partition key)&lt;/li&gt;
&lt;li&gt;frequency (clustering key. possible values: hour/day/week)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;there has already been recorded a timestamp corresponding for the beginning of the frequency window
that is the same (or higher) as the current frequency window being handled by the &lt;em&gt;batched-messenger&lt;/em&gt;
component.&lt;/p&gt;
&lt;p&gt;Using the search alert id as record key for the messages sent from the &lt;em&gt;percolator&lt;/em&gt; component
towards the batched topics ensures that the notifications sent for a search alert will be handled
in a serial fashion (which comes in handy when checking for duplicates).&lt;/p&gt;
&lt;p&gt;The body of the notification for the search alert is composed out of the latest news retrieved
by running the search query corresponding to the search alert (from the consumer record of the
Apache Kafka batched topic) on Elasticsearch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OPTIONAL&lt;/strong&gt; In case of finding a matching batched search alert for a percolated news article, the &lt;em&gt;percolator&lt;/em&gt; component
should be responsible to &lt;em&gt;“pause”&lt;/em&gt;  the search alert until its current batching period (hour/day/week) elapses in order to
avoid doing unnecessary matches against new incoming articles.&lt;/p&gt;
&lt;h4&gt;Orchestrating batched messengers&lt;/h4&gt;
&lt;p&gt;As mentioned previously, the batched  search alert notifications need to be &lt;em&gt;“parked”&lt;/em&gt; until their corresponding
notification period(hour/day/week) elapses.&lt;/p&gt;
&lt;p&gt;At the beginning of each batched time window (hour/day/week) should be therefor started one or multiple instances
of the &lt;em&gt;batched-messenger&lt;/em&gt; component for the batched Apache Kafka topics on which the consumer offset
of the &lt;em&gt;batched-messenger&lt;/em&gt; lags behind.&lt;/p&gt;
&lt;p&gt;Depending on the amount of the batched search alert hits from the topic that need to be processed, the
orchestrator could then choose how many instances of the &lt;em&gt;batched-messenger&lt;/em&gt; component to spawn.&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;Being a proof of concept, this blog post is accompanied by a the
&lt;a href=&quot;https://github.com/findinpath/search-alert&quot;&gt;search-alert&lt;/a&gt; Github project which contains
the following modules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;percolator&lt;/li&gt;
&lt;li&gt;immediate-messenger&lt;/li&gt;
&lt;li&gt;batched-messenger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;which fit to the description from the &lt;em&gt;Architecture&lt;/em&gt; section.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/findinpath/search-alert&quot;&gt;search-alert&lt;/a&gt; Github project makes heavy use
of the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library for spawning during the tests
trowaway containers for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Elasticsearch&lt;/li&gt;
&lt;li&gt;Apache Cassandra&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;in order to test the functionality of the code in a realstic fashion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The current implementation of the mailing service from the &lt;em&gt;messenger&lt;/em&gt; projects doesn’t actually
send email notifications, but just logs them.&lt;/p&gt;
&lt;h2&gt;Open points&lt;/h2&gt;
&lt;p&gt;A key point that hasn’t been covered
by the &lt;a href=&quot;https://github.com/findinpath/search-alert&quot;&gt;search-alert&lt;/a&gt; Github project accompanying this
blog post is the orchestration of the batched messengers.&lt;/p&gt;
&lt;p&gt;At the beginning of each batched time window (hour/day/week) there should run a program in a cron
fashion to spawn &lt;em&gt;batched-messenger&lt;/em&gt; instances for the previous time window. In case of eventual
previous failures (when batched Apache Kafka topics having consumer offset lagging behind the end offset),
this program should also spawn &lt;em&gt;batched-messenger&lt;/em&gt; instances for other previous time windows.&lt;/p&gt;
&lt;p&gt;The garbage collection for obsolete batched Apache Kafka
topics (current offset is equal with the end offset on all the partitions
of the topic) should also be taken care of by the above described program.&lt;/p&gt;
&lt;p&gt;Probably one of the next posts will tackle this problem with &lt;a href=&quot;https://kubernetes.io/&quot;&gt;kubernetes&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Elasticsearch percolator primer]]></title><description><![CDATA[Primer in understanding the reverse search concepts behind the Elasticsearch percolator. In a classical search scenario, a term query is…]]></description><link>https://www.findinpath.com/elasticsearch-percolator-primer/</link><guid isPermaLink="false">https://www.findinpath.com/elasticsearch-percolator-primer/</guid><pubDate>Mon, 13 Jul 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Primer in understanding the reverse search concepts behind the Elasticsearch percolator.&lt;/p&gt;
&lt;p&gt;In a classical search scenario, a term query is used as input and the documents
from the search index matching the term will be returned as output.
On the other hand, when percolating, a document will be used as input and the
search queries registered in the search index which match the document will
be returned as output.&lt;/p&gt;
&lt;p&gt;Technically, the basic idea behind the percolator is that (as denoted by the documentation of the percolate query):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It can be used to match queries stored in an index.
The percolate query itself contains the document that will be used as query to match with the stored queries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The percolate query is called turning search upside down in the
&lt;a href=&quot;https://livebook.manning.com/book/elasticsearch-in-action/appendix-e/&quot;&gt;Elasticsearch in action&lt;/a&gt; book
for the following reasons:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;You index queries instead of documents. This registers the query in memory, so it can be quickly run later.&lt;/li&gt;
&lt;li&gt;You send a document to Elasticsearch instead of a query. This is called percolating a document, basically indexing it into a small, in-memory index. Registered queries are run against the small index, so Elasticsearch finds out which queries match.&lt;/li&gt;
&lt;li&gt;You get back a list of queries matching the document, instead of the other way around like a regular search.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The implementation of the percolator can be found in the &lt;a href=&quot;https://github.com/elastic/elasticsearch/tree/master/modules/percolator&quot;&gt;percolator&lt;/a&gt;
module from the &lt;a href=&quot;https://github.com/elastic/elasticsearch/&quot;&gt;elasticsearch&lt;/a&gt; Github repository.&lt;/p&gt;
&lt;h2&gt;How does the percolator actually work&lt;/h2&gt;
&lt;p&gt;Initially, when the percolator  was released back in the version &lt;code class=&quot;language-text&quot;&gt;0.15.0.&lt;/code&gt; of Elasticsearch,
the execution time of the percolator was always linear to the amount of percolator queries, because
all percolator queries had to be evaluated all the time.&lt;/p&gt;
&lt;p&gt;When percolating, the document being percolated gets indexed into temporary in-memory index.
Prior to version &lt;code class=&quot;language-text&quot;&gt;5.0&lt;/code&gt; of Elsticsearch, all percolator queries needed to be executed on an
in-memory index in order to verify whether the query matches.&lt;/p&gt;
&lt;p&gt;As of version &lt;code class=&quot;language-text&quot;&gt;5.0&lt;/code&gt; of Elasticsearch there have been added dramatic improvements on how the percolator
works by being able to skip evaluating most queries against the in-memory index.&lt;/p&gt;
&lt;p&gt;The percolator analyses the queries and creates appropriate data structures in order to
be able to filter only the relevant search queries that need to be executed on the memory
index containing the percolated document:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for a  &lt;em&gt;term&lt;/em&gt; (e.g. : search for all the documents containing &lt;code class=&quot;language-text&quot;&gt;elastic&lt;/code&gt; ) query there will be
used a &lt;code class=&quot;language-text&quot;&gt;org.apache.lucene.search.TermScorer&lt;/code&gt; that will make use of the &lt;a href=&quot;https://lucene.apache.org/&quot;&gt;Lucene&lt;/a&gt;
inverted index functionality for retrieving only the documents that contain the searched term&lt;/li&gt;
&lt;li&gt;for a int field (e.g. : search all the real estate object with &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt; rooms) an intersection will be made
between the range of the percolated document (e.g. : &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt;) and the &lt;a href=&quot;https://www.youtube.com/watch?v=Z4dNLvno-EY&quot;&gt;KD tree&lt;/a&gt;
containing the ranges (e.g. : &lt;code class=&quot;language-text&quot;&gt;0&lt;/code&gt; TO &lt;code class=&quot;language-text&quot;&gt;3&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;4&lt;/code&gt; TO &lt;code class=&quot;language-text&quot;&gt;*&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;2&lt;/code&gt; TO &lt;code class=&quot;language-text&quot;&gt;5&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Some query types can’t be filtered (e.g. : wildcard queries) reason why they will be need
to be executed against the memory index to check whether they match the percolated document.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/elastic/elasticsearch/tree/master/modules/percolator&quot;&gt;percolator&lt;/a&gt; classes relevant
for understanding how the queries get analysed and converted into documents belonging to a query search index are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elastic/elasticsearch/blob/master/modules/percolator/src/main/java/org/elasticsearch/percolator/QueryAnalyzer.java&quot;&gt;QueryAnalyzer.java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/elastic/elasticsearch/blob/master/modules/percolator/src/main/java/org/elasticsearch/percolator/PercolatorFieldMapper.java&quot;&gt;PercolatorFieldMapper.java&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try out the &lt;a href=&quot;https://github.com/findinpath/elastic-percolator-primer/blob/master/src/test/java/org/elasticsearch/percolator/CandidateQueryTests.java&quot;&gt;tests&lt;/a&gt;
from the &lt;a href=&quot;https://github.com/findinpath/elastic-percolator-primer/&quot;&gt;elastic-percolator-primer&lt;/a&gt; Java project
in &lt;em&gt;Debug mode&lt;/em&gt; with breakpoints in the previously mentioned percolator classes to get a hands-on understanding
how the query search indexes used in the percolation process are built and used.&lt;/p&gt;
&lt;h2&gt;Usage demo&lt;/h2&gt;
&lt;p&gt;This demo is based on the blog post&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sharing.luminis.eu/blog/using-the-new-elasticsearch-5-percolator/&quot;&gt;https://sharing.luminis.eu/blog/using-the-new-elasticsearch-5-percolator/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;and contains just simple modifications in order to get the queries compatible with Elasticsearch &lt;code class=&quot;language-text&quot;&gt;7.x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Use either the &lt;a href=&quot;https://www.elastic.co/downloads/elasticsearch&quot;&gt;standalone&lt;/a&gt; installation or the
&lt;a href=&quot;https://hub.docker.com/_/elasticsearch&quot;&gt;docker&lt;/a&gt; image for Elasticsearch to get started.&lt;/p&gt;
&lt;h3&gt;Create &lt;code class=&quot;language-text&quot;&gt;news&lt;/code&gt; index&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Create news index and populate it with documents&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X PUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news?pretty&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;mappings&quot;: {
    &quot;properties&quot;: {
      &quot;title&quot;: {&quot;type&quot;: &quot;text&quot;},
      &quot;body&quot;: {&quot;type&quot;: &quot;text&quot;},
      &quot;category&quot;: {&quot;type&quot;: &quot;keyword&quot;}
    }
  }
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Populate &lt;code class=&quot;language-text&quot;&gt;news&lt;/code&gt; index with documents&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X PUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news/_doc/1&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;title&quot;: &quot;Early snow this year&quot;,
  &quot;body&quot;: &quot;After a year with hardly any snow, this is going to be a serious winter&quot;,
  &quot;category&quot;: &quot;weather&quot;
}&apos;&lt;/span&gt;


&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X PUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news/_doc/2&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;title&quot;: &quot;Snow on the ground, sun in the sky&quot;,
  &quot;body&quot;: &quot;I am waiting for the day where kids can skate on the water and the dog can play in the snow while we are sitting in the sun.&quot;,
  &quot;category&quot;: &quot;weather&quot;
}&apos;&lt;/span&gt;


&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X PUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news/_doc/3&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;title&quot;: &quot;Snow everywhere&quot;,
  &quot;body&quot;: &quot;Everything is covered in snow. The sun is shining.&quot;,
  &quot;category&quot;: &quot;weather&quot;
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Basic search operations on the &lt;code class=&quot;language-text&quot;&gt;news&lt;/code&gt; index&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Get one document by id&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X GET &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/news/_doc/1?pretty&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Retrieve all documents&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X GET &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/news/_search?pretty&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: { &quot;match_all&quot;: {} }
}
&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Create the &lt;code class=&quot;language-text&quot;&gt;news-notify&lt;/code&gt; index&lt;/h3&gt;
&lt;p&gt;As recommended in the documentation of
&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-search-after&quot;&gt;search_after&lt;/a&gt;
there will be used a special field &lt;code class=&quot;language-text&quot;&gt;tie_breaker_id&lt;/code&gt; for the search queries to be employed in ordering the paged
percolation results.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X PUT &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/news-notify?pretty&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
    &quot;mappings&quot;: {
        &quot;properties&quot;: {
             &quot;title&quot;: {
                 &quot;type&quot;: &quot;text&quot;
             },
             &quot;category&quot;: {
                 &quot;type&quot;: &quot;keyword&quot;
             },
             &quot;tie_breaker_id&quot;: {
                &quot;type&quot;: &quot;long&quot;
             },
             &quot;query&quot;: {
                 &quot;type&quot;: &quot;percolator&quot;
             }
        }
    }
}
&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Populate &lt;code class=&quot;language-text&quot;&gt;news-notify&lt;/code&gt; index&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XPUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_doc/1&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;must&quot;: [
        {
          &quot;match&quot;: {
            &quot;title&quot;: &quot;snow&quot;
          }
        }
      ],
      &quot;filter&quot;: [
        {
          &quot;term&quot;: {&quot;category&quot;:  &quot;weather&quot;}
        }
      ]
    }
  },
  &quot;tie_breaker_id&quot;: 1,
  &quot;meta&quot;: {
    &quot;username&quot;: &quot;sander&quot;,
    &quot;create_date&quot;: &quot;2016-10-13T14:23:00&quot;
  }
}&apos;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XPUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_doc/2&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;must&quot;: [
        {
          &quot;match&quot;: {
            &quot;title&quot;: &quot;sun&quot;
          }
        }
      ],
      &quot;filter&quot;: [
        {
          &quot;term&quot;: {
            &quot;category&quot;: {
              &quot;value&quot;: &quot;weather&quot;
            }
          }
        }
      ]
    }
  },
  &quot;tie_breaker_id&quot;: 2,
  &quot;meta&quot;: {
    &quot;username&quot;: &quot;jettro&quot;,
    &quot;create_date&quot;: &quot;2016-10-13T14:21:45&quot;
  }
}&apos;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XPUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_doc/3&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;must&quot;: [
        {
          &quot;match&quot;: {
            &quot;title&quot;: &quot;sun&quot;
          }
        }
      ],
      &quot;filter&quot;: [
        {
          &quot;term&quot;: {
            &quot;category&quot;: {
              &quot;value&quot;: &quot;weather&quot;
            }
          }
        }
      ]
    }
  },
  &quot;tie_breaker_id&quot;: 3,
  &quot;meta&quot;: {
    &quot;username&quot;: &quot;lara&quot;,
    &quot;create_date&quot;: &quot;2016-10-13T14:21:45&quot;
  }
}&apos;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XPUT &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_doc/4&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;must&quot;: [
        {
          &quot;match&quot;: {
            &quot;title&quot;: &quot;snow&quot;
          }
        }
      ],
      &quot;filter&quot;: [
        {
          &quot;term&quot;: {
            &quot;category&quot;: {
              &quot;value&quot;: &quot;weather&quot;
            }
          }
        }
      ]
    }
  },
  &quot;tie_breaker_id&quot;: 4,
  &quot;meta&quot;: {
    &quot;username&quot;: &quot;simon&quot;,
    &quot;create_date&quot;: &quot;2016-10-13T14:21:45&quot;
  }
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Percolate queries&lt;/h3&gt;
&lt;p&gt;Specify the document to be percolated within the query:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XGET &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_search&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;{
   &quot;query&quot;:{
      &quot;percolate&quot;:{
         &quot;field&quot;:&quot;query&quot;,
         &quot;document&quot;:{
           &quot;title&quot; : &quot;Early snow this year&quot;,
           &quot;body&quot; : &quot;After a year with hardly any snow, this is going to be a serious winter&quot;,
           &quot;category&quot; : &quot;weather&quot;
         }
      }
   }
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or reference it from the &lt;code class=&quot;language-text&quot;&gt;news&lt;/code&gt; index (if it has already been indexed):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XGET &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_search&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;{
   &quot;query&quot;:{
      &quot;percolate&quot;:{
         &quot;field&quot;:&quot;query&quot;,
         &quot;index&quot;:&quot;news&quot;,
         &quot;id&quot;:&quot;1&quot;
      }
   }
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Paging of the percolation results with &lt;code class=&quot;language-text&quot;&gt;search_after&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Pagination of results can be done by using the from and size,
but the cost becomes prohibitive when the deep pagination is reached.
Take a look at the &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-search-after&quot;&gt;search_after&lt;/a&gt;
documentation to see how this problem can be circumvented.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XGET &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_search&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;percolate&quot;: {
      &quot;field&quot;: &quot;query&quot;,
      &quot;index&quot;: &quot;news&quot;,
      &quot;id&quot;: &quot;2&quot;
    }
  },
  &quot;sort&quot;: [
        {&quot;tie_breaker_id&quot;: &quot;asc&quot;}
  ],
  &quot;size&quot;: 1
}&apos;&lt;/span&gt;


&lt;span class=&quot;token comment&quot;&gt;# search_after will be populated in the with the ID of the last&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# document retrieved in the last query (e.g. : `1`)&lt;/span&gt;


&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -XGET &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:9200/news-notify/_search&quot;&lt;/span&gt; -H &lt;span class=&quot;token string&quot;&gt;&apos;Content-Type: application/json&apos;&lt;/span&gt; -d&lt;span class=&quot;token string&quot;&gt;&apos;
{
  &quot;query&quot;: {
    &quot;percolate&quot;: {
      &quot;field&quot;: &quot;query&quot;,
      &quot;index&quot;: &quot;news&quot;,
      &quot;id&quot;: &quot;2&quot;
    }
  },
  &quot;sort&quot;: [
        {&quot;tie_breaker_id&quot;: &quot;asc&quot;}
  ],
  &quot;search_after&quot;:[1],
  &quot;size&quot;: 1
}&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Delete indexes&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X DELETE &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/news?pretty&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; -X DELETE &lt;span class=&quot;token string&quot;&gt;&quot;localhost:9200/news-notify?pretty&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;Announcement of the release of the Elasticsearch percolator&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/blog/percolator&quot;&gt;https://www.elastic.co/blog/percolator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When and how to percolate&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/blog/when-and-how-to-percolate-1&quot;&gt;https://www.elastic.co/blog/when-and-how-to-percolate-1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/blog/when-and-how-to-percolate-2&quot;&gt;https://www.elastic.co/blog/when-and-how-to-percolate-2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Introduction of the percolate query&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elastic.co/blog/elasticsearch-percolator-continues-to-evolve&quot;&gt;https://www.elastic.co/blog/elasticsearch-percolator-continues-to-evolve&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;Try out the tests from the Github project &lt;a href=&quot;https://github.com/findinpath/elastic-percolator-primer/&quot;&gt;elastic-percolator-primer&lt;/a&gt;
to get a feeling about how the percolator works.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[JDBC Nested Set Sink Connector]]></title><description><![CDATA[Demo showcase of JDBC Nested Set Sink Connector for Apache Kafka Connect used to eventually sync
hierachical data (e.g. : shop category tree…]]></description><link>https://www.findinpath.com/jdbc-nested-set-sink-connector/</link><guid isPermaLink="false">https://www.findinpath.com/jdbc-nested-set-sink-connector/</guid><pubDate>Sun, 24 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Demo showcase of JDBC Nested Set Sink Connector for Apache Kafka Connect used to &lt;em&gt;eventually&lt;/em&gt; sync
hierachical data (e.g. : shop category tree) from Apache Kafka towards a sink database table
via Apache Kafka Connect in an untainted fashion without intermittently having corrupt content on the sink
database destination nested set model table.&lt;/p&gt;
&lt;h2&gt;Nested Set Model&lt;/h2&gt;
&lt;p&gt;There are multiple ways of storing and reading hierarchies in a relational database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;adjacency list model: each tuple has a parent id pointing to its parent&lt;/li&gt;
&lt;li&gt;nested set model: each tuple has &lt;code class=&quot;language-text&quot;&gt;left&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;right&lt;/code&gt; coordinates corresponding to the preordered representation of the tree&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Details about the advantages of the nested set model are already very well described in
the following article:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sitepoint.com/hierarchical-data-database/&quot;&gt;https://www.sitepoint.com/hierarchical-data-database/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; As mentioned on &lt;a href=&quot;https://en.wikipedia.org/wiki/Nested_set_model&quot;&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The nested set model is a technique for representing nested sets
(also known as trees or hierarchies) in relational databases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;re-img src=&quot;nested-set-tree.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRmIAAABXRUJQVlA4IFYAAACQAwCdASoUAAgAPtFUo0uoJKMhsAgBABoJZwAAW+/qqB643FpAAP71HEeMkjFZQBbe6oHazQ8QUimUvPc0G9mHXHy2+U+K7d0txSdeNYAVPiLWAFkAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.4759036144578315,&amp;quot;src&amp;quot;:&amp;quot;/static/a8eb9b000f62bb30a0ade103758c6303/15ce1/nested-set-tree.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/a8eb9b000f62bb30a0ade103758c6303/dd2ee/nested-set-tree.webp 200w,\n/static/a8eb9b000f62bb30a0ade103758c6303/f333d/nested-set-tree.webp 400w,\n/static/a8eb9b000f62bb30a0ade103758c6303/15ce1/nested-set-tree.webp 411w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 411px) 100vw, 411px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/a8eb9b000f62bb30a0ade103758c6303/15ce1/nested-set-tree.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;nested-set-tree.png&amp;quot;,&amp;quot;density&amp;quot;:300,&amp;quot;presentationWidth&amp;quot;:411,&amp;quot;presentationHeight&amp;quot;:166,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;162&amp;apos;%3e%3cpath%20d=&amp;apos;M143%201l-1%2012v12h38v9H97v11H59v25h38v9H73l-24%201-1%205v6H10v24h38v21H10v24h78v-24H50v-21h38V91H50V81h96v10h-39v24h19l20%201v20h-19l-19%201-1%2011v12h79v-24h-38v-20l19-1h19V91h-38l-1-6v-6H99v-9h38V46l-19-1H99v-9h192v4l1%205h-19l-19%201-1%2012v12h38v5l1%204h-49v12h-38v24h78V91h-38V81h95v10h-38v24h78V91h-38V79h-49v-9h39V60c0-16%202-15-20-15h-19V34H182v-9h38V0h-38l-39%201m1%2012v10h74V2h-74v11m50-6c0%201-1%203-3%203-2%202-2%202-3%200-2-2-7%200-7%202l-2-1c-2-3-7-2-7%202%200%205%204%206%207%203l2-1c0%203%204%204%206%201%201-2%202-2%203%200%201%201%202%202%204%201%203%200%203-1%203-6l-1-5-2%201m-89%207c-24%204-38%2010-48%2019-5%205-5%205-5%203s0-2-1-1c-2%201%200%207%203%206%203%200%204-1%202-2-3-2%205-9%2016-14a168%20168%200%200151-13l-18%202m70%2029c-5%201-12%205-14%207-2%203-1%202%205-1l10-5c12-3%2049%202%2044%206-1%201-1%201%201%201%204%200%207-2%205-5%200-2-1-3-2-3v2c2%203%201%203-3%201-14-4-35-6-46-3M60%2058v10h75V47H60v11m195%200v10h75V47h-75v11m-88-1c-3%201-3%201-3%203l1%203%201%201v-2c0-2%200-2%202-1%2013%204%2020%2013%2022%2032%202%2010%202%2010%202%201%200-16-7-28-18-33-5-2-5-2-3-3s0-2-4-1M33%2058C20%2060%208%2069%204%2080l-3%205v5c0%202%203%202%205%200%201-1%201-1-1-1-2%201-2%201-2-3%203-15%2019-27%2038-27l4-1H33m207%209c-3%204-8%206-17%206-10%201-18%204-22%2010-2%203-2%203-3%201s-1-2-1%201c1%204%205%205%207%202%201-1%201-1-1-1l-3%201%202-3c5-6%209-8%2022-9l12-2c3-2%207-7%206-8l-2%202M12%20103v10h74V92H12v11m97-1v11h75V93l-38-1h-37v10m98%201v10h74V93h-74v10m97%200v10h75V93h-75v10m-154-2c-2%203-1%206%202%207%203%200%203%200%204-3l2-3v3c1%203%204%204%205%201v-2l1%202c1%203%203%202%205-2%201-4%200-5-3-2h-1c0-2-3-3-3%200l-1%201-1-1c0-2-3-3-3-1h-7m48%203c0%203%205%2019%208%2025%205%2011%2011%2013%2038%2013%2021%200%2022%200%2030-2%205-2%2012-8%2013-13%201-3%203-5%203-3l2%203c2%202%201%200-1-4s-2-3-6-1c-5%201-5%202-1%201%203%200%203%200%202%202-3%2013-15%2017-51%2016-22-1-27-5-33-28-3-10-4-13-4-9M12%20149v10h74v-21H12v11m97%200v10h75v-21h-75v11&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;div&gt;&lt;re-img src=&quot;nested-set-model.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRkwAAABXRUJQVlA4IEAAAADwAgCdASoUABIAPtFepU6oJSMiKAqpABoJaQAAS4mUgAD+8T6+CmkeXNGQzBXhvMRKZk8d1J6lG2BOfMdzAAAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.1225165562913908,&amp;quot;src&amp;quot;:&amp;quot;/static/5eb9bebf4a1ede9fc32c5eb41eed5892/11c57/nested-set-model.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/5eb9bebf4a1ede9fc32c5eb41eed5892/dd2ee/nested-set-model.webp 200w,\n/static/5eb9bebf4a1ede9fc32c5eb41eed5892/11c57/nested-set-model.webp 339w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 339px) 100vw, 339px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/5eb9bebf4a1ede9fc32c5eb41eed5892/11c57/nested-set-model.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;nested-set-model.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:339,&amp;quot;presentationHeight&amp;quot;:302,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;356&amp;apos;/%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;h2&gt;Syncing nested set models over Apache Kafka&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.confluent.io/current/connect/index.html&quot;&gt;Kafka Connect&lt;/a&gt;
is an open source component of &lt;a href=&quot;http://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt; which
in a nutshell, as described on &lt;a href=&quot;https://www.confluent.io/blog/kafka-connect-deep-dive-jdbc-source-connector/&quot;&gt;Confluent blog&lt;/a&gt;
provides the following functionality for databases:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It enables you to pull data (source) from a database into Kafka, and to push data (sink) from a Kafka topic to a database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;re-img src=&quot;kafka-connect.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRloBAABXRUJQVlA4WAoAAAAQAAAAEwAADgAAQUxQSLkAAAABgBjJtmlrDp9t69u2bdtG/iHcxwwiYgIgkVYhAPckTUYGI1srFxvz3fmVjd2D0SICwv3h8f3j5YvuruOd0+3BkkMKVc1nUplyOR2PxrMu2i1EHeEEIQT1QxPUjdZwuvPVJLl3N9paJj5MIB1m6m1x1XSfuscnyv1m8Hj4f3aO0vPemBq68feNjVbt8k3X5ec27r6hFD0/OlTsnZeTD57UYg91h38EUhtWHqkrhCBcpm6qAgg6KRqIOgBWUDggegAAAPADAJ0BKhQADwA+0VSjS6gkoyGwCAEAGglsAJ0ygAL17bPzLVBAWeAA/uoFfOk/7jwtUSw4U+uk/t6E05BsEibOl1pCZnBjtGXvwbVatKfAsXjtbpQg+ShSrAq3cCAWGzF770ghThqDIcXDw2JkVNnEDVPF7+KMwvAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.3333333333333333,&amp;quot;src&amp;quot;:&amp;quot;/static/8609c45f3e67bc458748a39f43a80794/a8429/kafka-connect.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/8609c45f3e67bc458748a39f43a80794/dd2ee/kafka-connect.webp 200w,\n/static/8609c45f3e67bc458748a39f43a80794/f333d/kafka-connect.webp 400w,\n/static/8609c45f3e67bc458748a39f43a80794/a8429/kafka-connect.webp 600w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 600px) 100vw, 600px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/8609c45f3e67bc458748a39f43a80794/a8429/kafka-connect.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;kafka-connect.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:600,&amp;quot;presentationHeight&amp;quot;:450,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;300&amp;apos;%3e%3cpath%20d=&amp;apos;M55%207c-3%204-2%2010%201%2012%204%202%204%209%200%2011-3%202-3%202-3%205v1h-1c0%202%203%206%206%207l3%203c0%202%200%202%201%201l3-6c3-2%204-3%202-7v-2l-4-2c-4-2-5-8%200-12%204-3%204-6%201-11-2-4-6-4-9%200m168%203c-6%207-12%2024-11%2032%202%209%2013%2012%2021%207%202-2%201-4-3-2-5%202-10%201-12-1-5-4-1-22%206-32%205-7%2010-5%209%204-1%205-1%206%201%206%204%200%204-10%200-15-3-4-6-4-11%201m-118%202c-1%203%201%2045%203%2046l1-10V38l4%202c4%202%209%207%2012%2012%202%203%206%201%208-4%201-3%201-4%204-4s4%201%204%208c1%204%202%202%203-3l2-6c1-1%200-4-2-4-1%201-2-3-2-8l-1-5c-2-3-3-1-10%2018-3%207-3%207-6%202l-8-7-5-4%203-2%2014-12c-1-3-3-2-8%203l-9%207-3%202V23l-1-11h-3m-30%208c-3%202-4%208-2%2010%205%204%2011%200%209-7l-2-2h-2c-2%200-2%200%200-1%202%200%202-1%201-1-1-1-3%200-4%201m114%206v2c1%202-2%2011-4%2012-1%201-2%203-1%204%200%203%200%202-7-1l-5-3%207-4c6-5%207-7%205-8l-6%203c-6%206-6%206-8%201-1-6-2-3-2%209l1%2011%201-4%201-4c1%200%2011%205%2012%207s3%201%204-4c1-2%202-3%204-3s2%200%202%206l1%205c2%200%203-4%203-7v-8l-4-8c-1-7-3-9-4-6m-40%201c-2%201-1%2024%201%2024l1-5v-5h6l7-1c2-1%200-3-4-3-8%201-9%200-9-3v-4h7l6-1-2-2h-13m109%202c-1%202%200%2022%202%2023l1-6%201-7c1%200%209%209%209%2011l2%201%201-13c0-10%200-11-2-11-1%200-2%202-2%208v7l-4-4-3-7c0-4-3-5-5-2m34-1v18l-4-4c-4-4-4-5-4-9%201-4-2-6-3-2%200%201-1%202-2%201-2-1-3%201-1%204%202%202%202%203%201%209l1%206c2%200%203-2%203-6v-3l5%204c6%206%207%206%208%202%201-6-2-23-4-20m10%200l-1%204-2%205v2l1%205c0%206%202%207%208%207%205-1%205-2%200-3-4-1-4-2-4-5%200-2%200-3%204-3%207-2%208-5%200-4l-4-1%201-3h3c4%200%207-1%207-3l-5-1-6-1-2%201m22%201c-5%203-9%2013-7%2018%201%204%205%206%209%204%203-1%202-3-2-3-3%200-4-1-4-3%200-5%204-13%207-12h13v9c0%208%201%2012%203%2011V33h3c5%200%204-3-1-3l-17-1h-4m-82%201l-2%2010c0%207%200%208%202%2010%203%204%206%204%209%201%206-6%206-18%200-21h-9m3%204c-2%204-2%2013%200%2015%202%201%202%201%205-1%203-4%203-11%200-14s-4-2-5%200M73%2041c-3%203-2%208%201%2010%204%202%209%200%2010-4%201-5-8-10-11-6m233%2019a10685%2010685%200%2001-208%204l1%201a7852%207852%200%2000249-4c-1-2-6-2-42-1M116%20164l-2%2030-2-1h-1l-11%201-12%201h24l1%201%201%2024%201%2024a1699%201699%200%20001-80m156-13c0%209%200%2016-1%2015l-14%201-13%201h28v8c0%208%200%209-3%207h-1l-11%201-11%201h20c4-1%205%201%205%208s0%207-2%206h-3c1%201-3%201-9%201-9%201-9%201%202%201l12%201v22a2521%202521%200%20001-73m-239%204c-9%201-17%204-19%207a259%20259%200%20000%2025c0-15%200-18%201-17%205%203%2037%204%2048%201%2012-4%2012-12%201-16-6-2-21-2-31%200m311%200l-8%201c-11%201-21%206-21%2010l-2%202-3%201%202%201c3-2%204%200%204%2014%200%2013%200%2014-2%2014l-2-1h-1l-3%201c-4%200-2%201%203%201%204%200%205%201%205%204l1-14%201-18%202%201c6%203%2036%203%2046%200%2015-5%2011-14-7-17h-15m-311%201c-11%202-19%206-19%209%201%207%2038%208%2052%201%2013-6-11-14-33-10m297%202c-9%203-14%206-13%209%203%207%2045%206%2054-1%203-2%202-4-3-7-5-2-30-3-38-1m-151%206a16960%2016960%200%200056%203l1%2024v23l-31-1a518%20518%200%200032%202c2-2%201-47-1-50-2-1-5-2-33-2-17%200-28%200-24%201M72%20182v16l-4%203c-5%206-12%207-28%207-10%200-13%201-9%201%208%202%2027%201%2033-2l6-5%202-1c1%201%201-5%201-17v-18l-1%2016m303%205v13l-4%203c-6%205-15%207-31%207h-8l15%201c12%200%2019-1%2025-6l4-2v-28l-1%2012&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The topic of synchronizing nested set model data between databases has been already discussed in the blog post :&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Ever wondered how to sync trees or hierarchies over &lt;a href=&quot;https://twitter.com/apachekafka?ref_src=twsrc%5Etfw&quot;&gt;@apachekafka&lt;/a&gt; by making use of &lt;a href=&quot;https://twitter.com/confluentinc?ref_src=twsrc%5Etfw&quot;&gt;@confluentinc&lt;/a&gt; kafka-connect-jdbc ? &lt;a href=&quot;https://t.co/p85iH3a6Yu&quot;&gt;https://t.co/p85iH3a6Yu&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1251752936692203521?ref_src=twsrc%5Etfw&quot;&gt;April 19, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;In the previous blog post the nested set model data published over
&lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/source-connector/index.html&quot;&gt;kafka-connect-jdbc source connector&lt;/a&gt;
from the source database to Apache Kafka was being handled explicitly in the business logic of the sink microservice application.&lt;/p&gt;
&lt;p&gt;It is definitely possible to integrate the synchronization logic for the nested set data explicitly in a
microservice that depends on the nested set model data (e.g. : shop category tree), but this adds, possibly unwanted,
further complexity to the microservice.&lt;/p&gt;
&lt;p&gt;The synchronization functionality for the nested set model data which is a non-core functionality of
the microservice needs to be maintained and monitored.&lt;/p&gt;
&lt;p&gt;Another possibility would be to delegate the responsibility of syncing the nested set model data to a JDBC Sink Connector
from the &lt;a href=&quot;https://www.confluent.io/hub/&quot;&gt;Confluent Hub&lt;/a&gt;. This approach would have the advantage that the microservice consuming
the nested set model data would solely concentrate on its core functionality.&lt;/p&gt;
&lt;p&gt;This post describes how to use the &lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink&quot;&gt;JDBC Nested Set Sink Connector&lt;/a&gt;
created to generically sink nested set model data via Apache Kafka Connect in a destination nested set model table.&lt;/p&gt;
&lt;h2&gt;End to end synchronization over Apache Kafka Connect&lt;/h2&gt;
&lt;p&gt;Syncing of nested set model from the &lt;strong&gt;source database&lt;/strong&gt; to Apache Kafka
can be easily taken care of by a kafka-connect-jdbc source connector
which can be initialized by posting the following configuration
to the &lt;code class=&quot;language-text&quot;&gt;/connectors&lt;/code&gt; endpoint of
Kafka Connect (see &lt;a href=&quot;https://docs.confluent.io/current/connect/references/restapi.html#post--connectors&quot;&gt;Kafka Connect REST interface&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connector.class&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;io.confluent.connect.jdbc.JdbcSourceConnector&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;timestamp+incrementing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;timestamp.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;updated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;incrementing.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;topic.prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.password&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p@ssw0rd!source&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;validate.non.null&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;tasks.max&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc:postgresql://source:5432/source?loggerLevel=OFF&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;table.whitelist&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nested_set_node&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; in the configuration above, the &lt;code class=&quot;language-text&quot;&gt;tasks.max&lt;/code&gt; is set to &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; because JDBC source connectors can deal
only with one &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; statement at a time for retrieving the updates performed on a table.
It is advisable to use also for Apache Kafka a topic with only &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; partition for syncing the nested set content
towards downstream services.&lt;/p&gt;
&lt;p&gt;Syncing of the nested set model data from Apache Kafka towards the &lt;strong&gt;sink database&lt;/strong&gt;
can be now easily taken care of by the &lt;strong&gt;JDBC Nested Set Sink Connector&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc-nested-set-node-sink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc-nested-set-node-sink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connector.class&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;com.findinpath.connect.nestedset.jdbc.NestedSetJdbcSinkConnector&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;tasks.max&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;topics&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath.nested_set_node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc:postgresql://sink:5432/postgres&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.password&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p@ssw0rd!sink&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;pk.fields&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;table.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nested_set_node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;table.left.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lft&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;table.rgt.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rgt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.table.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nested_set_node_log&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.table.primary.key.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.table.operation.type.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;operation_type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.offset.table.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nested_set_node_log_offset&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.offset.table.log.table.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log_table_name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;log.offset.table.offset.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log_table_offset&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Functionality overview&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;JDBC Nested Set Sink Connector&lt;/em&gt; is relatively similar in functionality to the &lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/sink-connector/index.html&quot;&gt;JDBC Sink Connector for Confluent Platform&lt;/a&gt;
because it ingests &lt;a href=&quot;https://github.com/apache/kafka/blob/trunk/connect/api/src/main/java/org/apache/kafka/connect/sink/SinkRecord.java&quot;&gt;SinkRecord&lt;/a&gt; entries and writes
them in a database table. This is also the reason why this connector made use of a great part of the sink logic of the &lt;a href=&quot;https://github.com/confluentinc/kafka-connect-jdbc&quot;&gt;kafka-connect-jdbc&lt;/a&gt; project
code.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;JDBC Nested Set Sink Connector&lt;/em&gt; writes the sink records in a database table structure similar to the one shown below:&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;sink-database-table-diagram.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRnQAAABXRUJQVlA4IGgAAAAwBACdASoUABIAPtFYpUyoJSOiKA1RABoJZQAALtHfvgFOoEgVGMNqQoAA/u41T6MO/vStKlUwtda6RTtYI6v8PvSUOA0ThhMn/uT9p5A5Mf86nsSCTQ4avH7sRAKFbMaKlD6M8wQIAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.0878859857482186,&amp;quot;src&amp;quot;:&amp;quot;/static/825b73cbbe57f5d496444441642df10f/83903/sink-database-table-diagram.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/825b73cbbe57f5d496444441642df10f/dd2ee/sink-database-table-diagram.webp 200w,\n/static/825b73cbbe57f5d496444441642df10f/f333d/sink-database-table-diagram.webp 400w,\n/static/825b73cbbe57f5d496444441642df10f/83903/sink-database-table-diagram.webp 458w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 458px) 100vw, 458px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/825b73cbbe57f5d496444441642df10f/83903/sink-database-table-diagram.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;sink-database-table-diagram.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:458,&amp;quot;presentationHeight&amp;quot;:421,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;368&amp;apos;%3e%3cpath%20d=&amp;apos;M21%2027c0%201%201%202%202%201l1%201-2%201v1l2%201-1%201h-2l2%201%201%201c-1%201%201%201%204%201h5V26h-6l-6%201m216%204v5h5l5-1v-1l2-1h-1l-2-1%202-1v-1l-2-1%202-1%201-1-6-1h-6v5M21%20287l2%201c2%200%201%204-1%204v1l2%201-1%201h-2l6%201c3%200%205%200%204-1l-2-1h3c1%201%201-1%201-4v-5h-6c-5%200-6%200-6%202m-3%2017l116%201a2538%202538%200%20000-2l-116%201m97%2030v9l1-2c0-2%201-3%202-3%201-1%201%200%201%202l1%203%201-4c0-2%203-2%204%200l1%202-2%201h12l-2-1c-1-1-1-1%201-1l2-2c-1-2-5-3-6%200-1%201-2%202-3%201v-1l1-1-2-1c-5%200-6%200-4-1l1-2-3%201h-6&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;nested_set_node_log&lt;/strong&gt; table is an &lt;strong&gt;INSERT only&lt;/strong&gt; table which simulates a certain extent the transaction logs
on the nested set model data.&lt;/p&gt;
&lt;p&gt;After writing new entries on the &lt;strong&gt;nested_set_node_log&lt;/strong&gt;  table, there will be an attempt to synchronize them towards
the destination &lt;strong&gt;nested_set_node&lt;/strong&gt; table. This operation will succeed only when the nested set model resulting from the
merge of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;existing content from the &lt;strong&gt;nested_set_node&lt;/strong&gt; table&lt;/li&gt;
&lt;li&gt;new &lt;strong&gt;nested_set_node_log&lt;/strong&gt; table entries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;is valid.&lt;/p&gt;
&lt;p&gt;The table &lt;strong&gt;nested_set_node_log_offset&lt;/strong&gt; will contain only a pointer towards the ID of the latest &lt;strong&gt;nested_set_node_log&lt;/strong&gt; entry
synchronized successfully into the &lt;strong&gt;nested_set_node&lt;/strong&gt; table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The microservice that makes use of the nested set model tree content should continuously poll the table &lt;strong&gt;nested_set_node_log_offset&lt;/strong&gt;
in order to know when the nested set model has been updated.&lt;/p&gt;
&lt;p&gt;At the time of this writing the connector supports and has been tested with the following databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Postgres 12&lt;/li&gt;
&lt;li&gt;MySQL 8&lt;/li&gt;
&lt;li&gt;MS SQL Server 2017&lt;/li&gt;
&lt;li&gt;sqlite 3&lt;/li&gt;
&lt;li&gt;Oracle 18.4.0 XE&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;em&gt;JDBC Nested Set Sink Connector&lt;/em&gt; supports both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink/blob/master/TESTING_UPSERT.md&quot;&gt;upsert&lt;/a&gt;: the position of the node in the tree or its data can be upserted (updated/inserted)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink/blob/master/TESTING_DELETE.md&quot;&gt;deletion&lt;/a&gt;: the node can be removed from the tree (works with &lt;a href=&quot;https://debezium.io/&quot;&gt;Debezium&lt;/a&gt; change data capture Kafka Connect source connector)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;operations on the nested set model entries.&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;It is relatively easy to think about a solution for the previously exposed problem, but before putting it to a production
environment the solution needs proper testing in conditions similar to the environment in which it will run.&lt;/p&gt;
&lt;p&gt;This is where the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library helps a great deal by providing lightweight,
throwaway instances of common databases that can run in a Docker container.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;end-to-end-test-architecture-diagram.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRl4AAABXRUJQVlA4IFIAAAAQBACdASoUAAwAPtFUo0uoJKMhsAgBABoJZwAAXBumMnfQv83FMn4gAAD+9NM1xX3IYTfbhKUVpgfJRYPm54eGXTqt2EvP1qmU1jJw5OvH8AAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.6617977528089887,&amp;quot;src&amp;quot;:&amp;quot;/static/92ab9dd52886150a2f58e5fcc691ca12/32a6f/end-to-end-test-architecture-diagram.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/92ab9dd52886150a2f58e5fcc691ca12/dd2ee/end-to-end-test-architecture-diagram.webp 200w,\n/static/92ab9dd52886150a2f58e5fcc691ca12/f333d/end-to-end-test-architecture-diagram.webp 400w,\n/static/92ab9dd52886150a2f58e5fcc691ca12/32a6f/end-to-end-test-architecture-diagram.webp 800w,\n/static/92ab9dd52886150a2f58e5fcc691ca12/2cf13/end-to-end-test-architecture-diagram.webp 1200w,\n/static/92ab9dd52886150a2f58e5fcc691ca12/cb94c/end-to-end-test-architecture-diagram.webp 1479w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/92ab9dd52886150a2f58e5fcc691ca12/cb94c/end-to-end-test-architecture-diagram.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;end-to-end-test-architecture-diagram.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:481,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;241&amp;apos;%3e%3cpath%20d=&amp;apos;M36%20120v116h96v-38h11v38h253V6H144l-1%2022v22h-11V5L84%204H36v116m1%201v114h93v-37H88v-12l-1-12-2-1%205-1%205%201-2%201c-4%200-4%200-4%2012v11h41V52H89v8l-1%209v-9l1-9%2020-1h21V6H37v115m107-92v21h46l45-1v-5c0-5%202-6%2010-6%2010%200%2010%200%2010%2014v11l-4%201c-5%201-5%202-5%205%200%207-2%207-2%200%200-4-1-4-4-4-4-1-5-3-5-7%201-6%204-6-46-6h-45v145h43l46-1c3-1%204-9%201-9a504%20504%200%2001-37-2l15%201-1-3c0-3%200-3%201-1%202%203%203%203%205%202%202-2%201-5-1-5-2%201-4-1-2-3s6%203%206%207v3l8-1c6%200%207%200%207-2%201-4%205-5%2012-5%208%201%208%201%208%2013%200%2014%200%2014-10%2014-7%200-10-1-10-5v-2h-91v37h250V7H144v22m103-14v2h16l1-1h1c1-1%201-1%200%200%200%202%207%203%207%201h1c0%202%202%201%202-1%200-3-3-4-4-2l-2%201v-1l-10-1c-8%200-11%201-12%202m21%206l-5%204-3%202%203%203c3%203%207%203%2012-1l4-4-2-1c-1-2-2-2-2%200-1%202-1%202-2%201l-2-3c0-2-1-2-3-1m42%2017c1%202-2%205-4%203-1-1-1%200-1%203v5c-1%202%206%202%206%200h1c0%202%204%203%204%201l-1-1-1-4v-7c0-2%200-2-2-2s-2%200-2%202m20%200l-2%201-3%203-3%203%202%202c3%203%208%203%2012-1l4-3c2%200%201-1-1-3-2-1-2-1-3%201%200%202-2%202-3-2-1-2-3-3-3-1m-198%2087v72h11V52h-11v73m24-60v11h69V66l-1-12-35-1h-33v12m1%200v10h67v-9l-1-9-1-1c0-2-4-2-32-2h-33v11m10-7l-2%201%2020%201%2020%201-3%201c-2-1-3%200-3%201l4%201c3%200%203-1%203-2%200-2%201-2%203-2l3-1-1-1h-44m131%203c0%204%200%205-2%205h-18v51l1-25V67h8l7%201%202%201c2-1%202-1%202%203l1%205h43V57h-44v4m2%206v9h41v-6l-1-6-1-2c0-2%200-3-2-3-3%200-3%201-3%204l-1%201-1-3%201-3h-33v9m44%201v9h47v-8c0-10%201-10-25-10h-22v9m1%200v8h45v-7l-1-7-1-1-21-1h-22v8m-14%2018l-5%204-3%202%203%203c3%203%207%203%2012-1s5-4%202-6c-2-1-2-1-2%201l-1%202-3-4c0-2-1-2-3-1m-154%2017v17h-21v10h-11v57h6l6-1-6-1h-5v-54h10v5c0%203%200%204%202%204l1%201-1%201-4%202-3%202%202%203c4%203%209%203%2014-1%204-4%204-4%202-5-3-1-3-1-3%201h-3c-2-3%200-4%209-4h9v10l1%209%201-9v-9l3-1h3v11h36l-18-1h-17v-5l1-4%204-1h4v-20h-11v-15l-1%208v7h-9V87h34l34-1h-69v17m145-3v2c1%201-3%201-10%201h-12v20h21v9c0%2011%201%2013%201%203l1-7v-3c0-2%200-2%2010-2l11-1%201-1%201-8-1-10c-5%200-6%200-6-2h1c1%201%202%200%202-2l-20%201m-5%205h-16v17h41v-11h-4c-4%200-4-1-4-4v-3l-17%201m-239%206c-1%201%201%201%204%201h6v53l3%201%205-1h2c2%201%2014%201%2014-1%201-1%200-1-5-1H89v-52l-6-1-5%201m79%2020v8h41v-11h-4c-4%200-4-1-4-3v-3h-33v9m129%200l-5%204-3%202%203%203c3%203%207%203%2012-1l4-4-2-1c-1-2-2-2-2%200-1%202-1%202-2%201l-2-3c0-2-1-2-3-1m-139%2041v9h62v-7c0-7-1-10-2-8v1l1%207v6h-10l-6-1c6%200%205-2-1-2s-8%201-5%202c1%201-7%201-18%201h-20v-8l-1-8v8m212%2014l-6%203v12l7%204%206%204%207-4%206-4v-12l-6-3c-8-5-6-5-14%200m-105%2020l-2%201c-2%200-6%204-6%205%200%204%207%206%2011%204s8-7%207-7l-2-1-1-1-1%202c0%202-2%201-3-2-2-2-3-3-3-1&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;Docker containers are used for interacting with the Apache Kafka ecosystem as well as the source and sink databases.&lt;/p&gt;
&lt;p&gt;This leads to tests that are easy to read and allow the testing of the sync operation for various nested set models&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;/**
     *   Ensure that the sync the content of a more complex nested set model
     *   is performed successively each time after performing updates on the
     *   nested set model on the source database.
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;syncingSuccessiveChangesToTheTreeDemo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; clothingNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertRootNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Clothing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// |1| Clothing |2|&lt;/span&gt;


        &lt;span class=&quot;token comment&quot;&gt;// Add now Men&apos;s and Women&apos;s children and wait for the syncing&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; mensNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Men&apos;s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;long&lt;/span&gt; womensNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Women&apos;s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// |1| Clothing |6|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//     ├── |2| Men&apos;s |3|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//     └── |4| Women&apos;s |5|&lt;/span&gt;



        &lt;span class=&quot;token comment&quot;&gt;// Add new children categories for both Men&apos;s and Women&apos;s nodes&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Suits&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dresses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Skirts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Blouses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//   |1| Clothing |14|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       ├── |2| Men&apos;s |5|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       │       └── |3| Suits |4|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       └── |6| Women&apos;s |13|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               ├── |7| Dresses |8|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               ├── |9| Skirts |10|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               └── |11| Blouses |12|&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink/blob/master/src/test/java/com/findinpath/connect/nestedset/jdbc/DemoNestedSetSyncTest.java&quot;&gt;DemoNestedSetSyncTest&lt;/a&gt;
for several syncing test cases.&lt;/p&gt;
&lt;p&gt;This project provides a functional prototype on how to setup the whole
Confluent environment (including &lt;strong&gt;Confluent Schema Registry&lt;/strong&gt; and &lt;strong&gt;Apache Kafka Connect&lt;/strong&gt;)
via testcontainers.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink/blob/master/src/test/java/com/findinpath/connect/nestedset/jdbc/AbstractNestedSetSyncTest.java&quot;&gt;AbstractNestedSetSyncTest&lt;/a&gt;
for details.&lt;/p&gt;
&lt;h2&gt;Source code&lt;/h2&gt;
&lt;p&gt;Checkout the github project &lt;a href=&quot;https://github.com/findinpath/kafka-connect-nested-set-jdbc-sink/&quot;&gt;kafka-connect-nested-set-jdbc-sink&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Try out the tests via&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Installation notes&lt;/h2&gt;
&lt;p&gt;The connector is available on &lt;a href=&quot;https://www.confluent.io/hub/findinpath/kafka-connect-nested-set-jdbc-sink&quot;&gt;Confluent Hub&lt;/a&gt;
and can be installed via the following command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;confluent-hub &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; findinpath/kafka-connect-nested-set-jdbc-sink:1.0.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Sync tree database table over Apache Kafka]]></title><description><![CDATA[Showcase on how to eventually sync hierachical data
from a source database table towards a sink database table via Apache Kafka in a…]]></description><link>https://www.findinpath.com/tree-db-table-kafka-sync/</link><guid isPermaLink="false">https://www.findinpath.com/tree-db-table-kafka-sync/</guid><pubDate>Sun, 19 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Showcase on how to &lt;em&gt;eventually&lt;/em&gt; sync hierachical data
from a source database table towards a sink database table via Apache Kafka in a
untainted fashion without intermittently having corrupt content on the sink
database table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2020-05-28&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The problem showcased here can be solved also by using the new &lt;a href=&quot;https://www.confluent.io/hub/findinpath/kafka-connect-nested-set-jdbc-sink&quot;&gt;JDBC Nested Set Sink Connector&lt;/a&gt;
for Kafka Connect.
Check out the following blog post for more details on the &lt;em&gt;JDBC Nested Set Sink Connector&lt;/em&gt; :&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Showcase of JDBC Nested Set Sink Connector for &lt;a href=&quot;https://twitter.com/confluentinc?ref_src=twsrc%5Etfw&quot;&gt;@confluentinc&lt;/a&gt; platform. &lt;a href=&quot;https://t.co/rGxIk3HPRy&quot;&gt;https://t.co/rGxIk3HPRy&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1265149760614338567?ref_src=twsrc%5Etfw&quot;&gt;May 26, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2&gt;Nested Set Model&lt;/h2&gt;
&lt;p&gt;There are multiple ways of storing and reading hierarchies in a relational database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;adjacency list model: each tuple has a parent id pointing to its parent&lt;/li&gt;
&lt;li&gt;nested set model: each tuple has &lt;code class=&quot;language-text&quot;&gt;left&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;right&lt;/code&gt; coordinates corresponding to the preordered representation of the tree&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Details about the advantages of the nested set model are already very well described in
the following article:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sitepoint.com/hierarchical-data-database/&quot;&gt;https://www.sitepoint.com/hierarchical-data-database/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt; As mentioned on &lt;a href=&quot;https://en.wikipedia.org/wiki/Nested_set_model&quot;&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The nested set model is a technique for representing nested sets
(also known as trees or hierarchies) in relational databases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;re-img src=&quot;nested-set-tree.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRl4AAABXRUJQVlA4IFIAAADQAwCdASoUAAkAPtFUo0uoJKMhsAgBABoJaQAAW+yYEf99vDqvKsAA/vUcR69FU8JHV8IWEmKv6UR8Vy356MybU/engQkthW+HN9ICMAFkAAAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.326923076923077,&amp;quot;src&amp;quot;:&amp;quot;/static/a03f06ad5930d1425cc0ce4d391fe8dd/f8a92/nested-set-tree.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/a03f06ad5930d1425cc0ce4d391fe8dd/dd2ee/nested-set-tree.webp 200w,\n/static/a03f06ad5930d1425cc0ce4d391fe8dd/f8a92/nested-set-tree.webp 363w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 363px) 100vw, 363px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/a03f06ad5930d1425cc0ce4d391fe8dd/f8a92/nested-set-tree.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;nested-set-tree.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:363,&amp;quot;presentationHeight&amp;quot;:156,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;172&amp;apos;%3e%3cpath%20d=&amp;apos;M151%2012v21c0%202%203%202%2018%202h17v8h-39l-38%201-1%204v5H91l-17%201v23h34v8H64v11H28v12l1%2012h35v18H46l-17%201v23h72v-23l-18-1H66v-18h35V96H66v-9h88v9h-36v5c0%203%200%204-2%204-1%200-2%201-2%203%200%204%202%204%203%201%200-2%201-1%201%204l1%207h35v18h-18l-17%201v23h72v-24h-35v-18h35v-4l1-4%201%202%201%201c1%200%202-1%201-3%200-1-1-2-2-1-2%200-2%200-2-7v-8h-35V85h-45v-8h35V54l-18-1h-17v-8h178v8h-17l-18%201v23h35v8h-22l-22%201-1%205v5h-35v24h35l37-1%203-2c2%200%202%200%202-2l-1-2-1-1c-2%201-2%200-2-6%200-11%201-10-19-10h-17v-4l1-4%2044-1h44v9h-36v12l1%2012h72V96h-35V86l-23-1h-23v-8h36V54l-18-1h-18V43H188v-8h35V12l-1-2h-35c-34%200-35%200-36%202m2%202l-1%209v9h69V13h-34l-34%201m19%209l1%205%201-2c0-3%204-3%204-1%201%203%205%204%207%202l2-1c1%202%205%202%206%200h1l4%202c3%200%203%200%203-5l-1-6-1%201-2%203c-2%202-3%202-4%200-2-1-6-1-6%201h-2c-2-2-2-2-5-1l-5%201c-1-1-1-1%201-2%203-1%202-3-2-3-2%200-2%200-2%206m-59%202c-24%206-31%208-40%2017-4%204-5%204-6%203%200-3-1-1-1%203%201%203%206%203%205-1%200-4%2014-13%2027-17l21-4%2014-3c0-2-5-1-20%202m66%2027l-9%205c-5%204-3%204%203%200%208-5%2017-7%2030-5%2011%201%2024%205%2020%206-1%201%200%201%202%201%203%200%204-1%204-3l-2-4v2c0%202%200%202-4%201-11-5-34-6-44-3M75%2066v9h69V56H75v10m180-1l1%2010h69V56h-70v9m20-3l-1%205%201%203%201-3v-2l1%202c1%204%203%204%205%201%200-2%200-2%201%200%200%201%201%202%205%202%205%200%206-1%203-2l-2-1h2c3%200%203-4%200-5-2%200-3%200-4%202l-2%202v-3c0-4-3-4-5%200l-1%203v-3c-1-3-4-4-4-1M53%2065c-18%204-26%209-31%2022-1%203-2%204-3%203-2-1-1%205%201%207h4v-2c-2%200-2%200-2-2l2-6c3-11%2017-20%2033-20l4-1c0-2-1-2-8-1m120%200c-3%200-2%206%200%206l1-2c-1-1-1-1%202-1l11%207%202%203c2%202%206%2013%207%2021%200%205%202%209%202%204%200-16-9-33-18-35-2%200-3-1-2-2%200-2-1-2-5-1m67%2010c-2%203-8%205-15%205-8%200-15%203-19%208-3%204-3%204-4%202-1-1-1-1-1%201%200%204%202%205%206%203%202-1%202-1%201-2-3%200-2-1%201-5%204-4%206-4%2016-5%209-1%2015-3%2017-7%201-2%200-3-2%200M30%20108v9h69V98H30v10m90%200v9h69V98h-69v10m91-1v10h69V99l-35-1h-34v9m90%200v10h70V99l-35-1h-35v9m-228-4c0%202-4%204-6%202s-7%202-5%205c1%203%204%203%206%201h1c0%201%202%202%205%201%202%200%202-1%202-6s-2-7-3-3m209%2022c-4%201-4%202%200%202%204-1%204%200%200%205-4%207-8%209-20%2010-24%202-40%201-46-5l-4-1c-1%201%206%206%2011%207%207%202%2041%202%2048%200s13-7%2014-13c2-4%202-4%202-2%201%202%203%203%203%201s-3-5-4-5l-4%201M64%20141H30v19h69v-9l-1-10H64m56%201v9l1%209h69v-19h-35l-35%201m-59%207c-2%203%200%206%204%206l2-1-2-1-1-1c0-2%204-1%204%201%201%203%203%202%203-1s2-3%202%200c1%203%203%204%203%201%201-4%202-4%203-1v4c-2%201-1%202%201%202%203-1%206-11%204-11l-1%202-1%201-1-1c0-2-1-2-6-2l-7%201h-1c-2-2-5-1-6%201&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;div&gt;&lt;re-img src=&quot;nested-set-model.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRmgAAABXRUJQVlA4IFwAAACQAwCdASoUABAAPtFUo0uoJKMhsAgBABoJaQAAYf/ipFVbWfIAAP7x6ZxZGUfrvSmkqCUyX/n8g6KUKpNxigiCOYU0MO6dNbsPHcy98PddC78Qxjwd6YF6IAAAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.255952380952381,&amp;quot;src&amp;quot;:&amp;quot;/static/cb63cc5c3f6da2858f198a2d373cdef4/c0627/nested-set-model.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/cb63cc5c3f6da2858f198a2d373cdef4/dd2ee/nested-set-model.webp 200w,\n/static/cb63cc5c3f6da2858f198a2d373cdef4/c0627/nested-set-model.webp 211w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 211px) 100vw, 211px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/cb63cc5c3f6da2858f198a2d373cdef4/c0627/nested-set-model.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;nested-set-model.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:211,&amp;quot;presentationHeight&amp;quot;:168,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;318&amp;apos;%3e%3cpath%20d=&amp;apos;M37%2045c0%207%200%208%203%207%201-1%201-1-1-2-1-2-1-2%201-2%203%200%208-2%208-3l3-1%202%201-2%201-3%201%204%201%206-1-1-1-2-4c0-3-1-4-4-4l-3%201-2%201-2-1-2-1-2%201h-1c-2-2-3-1-2%206m302-6c-3%203-1%209%204%209l3%201-4%201-2%201c0%202%206%201%207%200v-5l2-5v-2h-10m13%2042v12c4%203%209%200%209-6s-5-9-9-6M38%20149c-1%203-2%2012-1%2013h3v-3c-2-1-2-1%200-1l5%202%207%202c5%200%205-2-1-3l-3-1h3c4%200%204%200%204-3s0-3-3-3l-4%201%202%201%204%201h-7l-1-3c0-3-2-2-2%201%200%202-1%203-3%203-3%200-3%200-3-3%200-2%201-3%203-3l2-1h-5m314%2025l-1%206v4h5c4%200%205-1%205-4%200-6-5-9-9-6m-207%2022v11l-1%201h9v-12c-2-1-8-2-8%200m24%204l1%202%201%203-1%202-1%201%206%201%206-1h17l-1-1c-2%200-1-4%201-5%202%200%202%200%202%203-1%204-1%204%202%204%202%200%202%200%201-2l-2-4c0-3-2-5-3-3h-2c-1-2-4-1-4%201l1%201c2%200%201%204-1%205-2%200-2-1-2-2%200-8-8-7-10%201-1%201-1%201-1-1l-1-4c-2-2-9-2-9-1m-23%2025l-1%205-1%201c0%202%2011%201%2013-1l6-2c4%200%206-2%203-5h-8l4%201%203%201-3%201-4-1h-1l-1%203-1%202v-10c1-1%200-1-4-1h-5v6M37%20248l1%207%202-1-1-2-1-2c0-3%202-4%202-2h2c2-1%202-1%202%201l-1%204c-3%201%200%202%206%202%206-1%208-2%203-3-6-1-5-2%200-3%203%200%204%200%204-2l-5-1c-5%200-5%200-5-3%201-2%201-2-1-2s-3%201-3%202v3l-1-3-3-2-1%207m107-6l1%201v10c-2%201%200%202%203%202l5-1v-12h-9M37%20271l1%207%202-1-1-2-1-2%201-3%201%202%201%201%201-1%201-2v5c-3%201%200%203%206%203%206-1%209-2%203-3-5%200-5-2%200-2%203%200%203%200%203-2%200-4-5-5-7-3-1%203-2%202-2-1%201-3%201-3-2-3-2%200-2%201-2%203v3l-1-3c0-1-1-3-3-3l-1%207&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;h2&gt;Syncing nested set models over Apache Kafka&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.confluent.io/current/connect/index.html&quot;&gt;Kafka Connect&lt;/a&gt;
is an open source component of &lt;a href=&quot;http://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt; which
in a nutshell, as described on &lt;a href=&quot;https://www.confluent.io/blog/kafka-connect-deep-dive-jdbc-source-connector/&quot;&gt;Confluent blog&lt;/a&gt;
provides the following functionality for databases:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It enables you to pull data (source) from a database into Kafka, and to push data (sink) from a Kafka topic to a database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;More details about kafka-connect-jdbc connector can be found on the
&lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/index.html&quot;&gt;Conflent documentation&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;JDBC-connector.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRlgAAABXRUJQVlA4IEwAAACQAwCdASoUAAcAPtFUpEuoJKOhsAgBABoJZwAAW+k5uFYsyYAAAP7z7rh7FUjK3ogTz6sjoTkxLXukLTwi0w1/bULcA2mhXzrpQAAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.751412429378531,&amp;quot;src&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/32a6f/JDBC-connector.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/dd2ee/JDBC-connector.webp 200w,\n/static/4f11f3fa83975efdfbea71362207418f/f333d/JDBC-connector.webp 400w,\n/static/4f11f3fa83975efdfbea71362207418f/32a6f/JDBC-connector.webp 800w,\n/static/4f11f3fa83975efdfbea71362207418f/5d180/JDBC-connector.webp 974w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/5d180/JDBC-connector.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;JDBC-connector.png&amp;quot;,&amp;quot;density&amp;quot;:150,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:291,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;145&amp;apos;%3e%3cpath%20d=&amp;apos;M319%2019l1%205%201%203%201-2c-1-3%201-3%202%200l2%202%202%201%202-1%203-1c1%201%201%200%201-1%200-3%202-3%203%200%201%202%201%202%204%202l1%202c0%204-1%204-4%202h-2c2%205%207%204%207-2%200-3%200-3%202-2h3c1-1%201-1-1-1s-2%200-1-1c2-1%203-4%201-5l-3%201c-1%201-2%202-3%201v3h-1l-1-2-2-1h-4c-2%202-2%202-2%200l-3-1v2l-2%201-4-3-3-2M194%2067l5%203%206%202h-17l-16%201%2016%201h17l-7%203c-4%202-6%203-5%204l18-8-16-7-1%201M8%2083v9h47c2-3%201-16-1-16l-24-1H8v8m1%201v7h45V77H34l8%201c10%201%2011%201%2011%206s-5%206-18%204H22c-12%200-16-7-5-9l7-1%206-1-10-1H9v8m302-1l-1%2010c0%208%200%208%202%209%207%201%207%200%208-10%200-10%200-10-2-10l-5-1c-1%200-2%200-2%202m12-1v11l1%209h9V92c0-10%200-10-2-10l-5-1-3%201m13%201v17l5%201%204%201V82l-5-1c-3%200-4%200-4%202m12%208v10h4c6%201%206%200%206-10s0-10-7-10h-3v10m13-8v17l5%201%204%201V82l-5-1c-3%200-4%200-4%202m12%208v10h10V82l-5-1h-5v10m13%200v9l5%201%204%201V82l-5-1h-4v10&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;Syncing of nested set model from the source database to Apache Kafka
can be easily taken care of by a kafka-connect-jdbc source connector
which can be initialized by posting the following configuration
to the &lt;code class=&quot;language-text&quot;&gt;/connectors&lt;/code&gt; endpoint of
Kafka Connect (see &lt;a href=&quot;https://docs.confluent.io/current/connect/references/restapi.html#post--connectors&quot;&gt;Kafka Connect REST interface&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connector.class&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;io.confluent.connect.jdbc.JdbcSourceConnector&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;mode&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;timestamp+incrementing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;timestamp.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;updated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;incrementing.column.name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;topic.prefix&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.user&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.password&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p@ssw0rd!source&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;validate.non.null&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;tasks.max&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;findinpath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;connection.url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;jdbc:postgresql://source:5432/source?loggerLevel=OFF&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;table.whitelist&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nested_set_node&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; in the configuration above, the &lt;code class=&quot;language-text&quot;&gt;tasks.max&lt;/code&gt; is set to &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; because JDBC source connectors can deal
only with one &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; statement at a time for retrieving the updates performed on a table.
It is advisable to use also for Apache Kafka a topic with only &lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; partition for syncing the nested set content
towards downstream services.&lt;/p&gt;
&lt;h3&gt;Refresh the nested set model on the sink database&lt;/h3&gt;
&lt;p&gt;On the sink database side there needs to be implemented a mechanism that contains
a safeguard for not adding invalid updates to the nested set model.
A concrete example in this direction is when going from the nested set model:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;|1| A |2|&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;to the nested set model (after adding two children)&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;|1| A |6|
    ├── |2| B |3|
    └── |4| C |5|&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the snippets above the tree node labels are prefixed by their &lt;code class=&quot;language-text&quot;&gt;left&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;right&lt;/code&gt;
nested set model coordinates.&lt;/p&gt;
&lt;p&gt;Via &lt;code class=&quot;language-text&quot;&gt;kafka-connect-jdbc&lt;/code&gt; the records corresponding to the tuple updates may come in various
ordering:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;| label | left | right |
|-------|------|-------|
| A     | 1    | 6     |
| B     | 2    | 3     |
| C     | 4    | 5     |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;| label | left | right |
|-------|------|-------|
| B     | 2    | 3     |
| C     | 4    | 5     |
| A     | 1    | 6     |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or any other combinations of the three tuples listed above because the records
are polled in batches of different sizes from Apache Kafka.&lt;/p&gt;
&lt;p&gt;Going from the nested set model table content&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;| label | left | right |
|-------|------|-------|
| A     | 1    | 2     |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;towards&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;textmate&quot;&gt;&lt;pre class=&quot;language-textmate&quot;&gt;&lt;code class=&quot;language-textmate&quot;&gt;| label | left | right |
|-------|------|-------|
| A     | 1    | 6     |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;generic&quot;&gt;&lt;pre class=&quot;language-generic&quot;&gt;&lt;code class=&quot;language-generic&quot;&gt;| label | left | right |
|-------|------|-------|
| A     | 1    | 6     |
| B     | 2    | 3     |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;would intermittently render the nested set model corrupt until
all the records from the source nested set model are synced over Apache Kafka.&lt;/p&gt;
&lt;p&gt;Using a kafka-connect-jdbc sink connector is therefor out of the question for
syncing the contents of trees from a source service towards downstream online services.&lt;/p&gt;
&lt;p&gt;One solution to cope with such a problem would be to separate the nested set model from
what is being synced over Apache Kafka.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;sink-database-table-diagram.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRnQAAABXRUJQVlA4IGgAAACwAwCdASoUABAAPtFUo0uoJKMhsAgBABoJQAALieDBehMpkd3pAAD+8EIAtrVNYBhznV0DIgzNBmtMoakamZk57adn2lD/vr+hJQpfFTNxdgY7uQEd+MhUjBrs4YPwvIZXVqrhmgKAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.2256410256410257,&amp;quot;src&amp;quot;:&amp;quot;/static/9449ad9e1b053a619a238d35a00050c8/94250/sink-database-table-diagram.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/9449ad9e1b053a619a238d35a00050c8/dd2ee/sink-database-table-diagram.webp 200w,\n/static/9449ad9e1b053a619a238d35a00050c8/f333d/sink-database-table-diagram.webp 400w,\n/static/9449ad9e1b053a619a238d35a00050c8/94250/sink-database-table-diagram.webp 478w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 478px) 100vw, 478px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/9449ad9e1b053a619a238d35a00050c8/94250/sink-database-table-diagram.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;sink-database-table-diagram.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:478,&amp;quot;presentationHeight&amp;quot;:390,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;326&amp;apos;%3e%3cpath%20d=&amp;apos;M216%2030c0%203%200%204%201%203h2l-1%201c-1%201%201%201%204%201h6V25h-12v5M17%2047l71%201a955%20955%200%20000-2l-71%201m201%20205l1%205%201-1v-1l1%201%201%201%201-1c-1-1%200-1%203-1l3%201v1l1-5v-4l-6-1h-6v5m30%2036&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;In the table diagram above, the &lt;code class=&quot;language-text&quot;&gt;nested_set_node_log&lt;/code&gt; table is an &lt;code class=&quot;language-text&quot;&gt;INSERT only&lt;/code&gt; table
in which is written whenever a new record(s) is read from Apache Kafka.
The &lt;code class=&quot;language-text&quot;&gt;log_offset&lt;/code&gt; table has only one tuple pointing to the last &lt;code class=&quot;language-text&quot;&gt;nested_set_node_log&lt;/code&gt; tuple id
processed when updating the &lt;code class=&quot;language-text&quot;&gt;nested_set_node&lt;/code&gt; table.&lt;/p&gt;
&lt;p&gt;Whenever new records are read from Apache Kafka, there will be a transactional attempt
to apply all the updates from &lt;code class=&quot;language-text&quot;&gt;nested_set_node_log&lt;/code&gt; made after the saved entry in the &lt;code class=&quot;language-text&quot;&gt;log_offset&lt;/code&gt;
table to the existing configuration of the &lt;code class=&quot;language-text&quot;&gt;nested_set_node&lt;/code&gt; nested set model.&lt;/p&gt;
&lt;p&gt;If the applied updates lead to a valid nested set model configuration, then the &lt;code class=&quot;language-text&quot;&gt;nested_set_node&lt;/code&gt;
table will be updated and the log offset will be set to the latest processed &lt;code class=&quot;language-text&quot;&gt;nested_set_node_log&lt;/code&gt; entry,
otherwise the &lt;code class=&quot;language-text&quot;&gt;nested_set_node&lt;/code&gt; table stays in its previous state.&lt;/p&gt;
&lt;h2&gt;Caching&lt;/h2&gt;
&lt;p&gt;On the sink side is implemented the &lt;a href=&quot;https://github.com/google/guava/wiki/CachesExplained&quot;&gt;Guava’s Cache&lt;/a&gt;
for avoiding to read each time from the persistence the contents of the nested set model.
This approach nears the usage on a productive system, where the contents of the nested set model
are cached and not read from the database for each usage.&lt;/p&gt;
&lt;p&gt;When there are new contents added to the nested set model, the cache is notified for
invalidating its contents.&lt;/p&gt;
&lt;h2&gt;JDBC Transactions&lt;/h2&gt;
&lt;p&gt;One of the challenges faced before implementing this proof of concept
was whether to use &lt;a href=&quot;https://spring.io/&quot;&gt;spring framework&lt;/a&gt; to wrap the
complexities of dealing with JDBC. It is extremely appealing to use
production ready frameworks and not care about their implementation complexity.&lt;/p&gt;
&lt;p&gt;The decision made in the implementation was to avoid using &lt;code class=&quot;language-text&quot;&gt;spring&lt;/code&gt; and
&lt;code class=&quot;language-text&quot;&gt;JPA&lt;/code&gt; and go with plain old &lt;code class=&quot;language-text&quot;&gt;JDBC&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Along the way in the implementation, one open question was whether to group
all the JDBC complexity in one repository or in multiple repositories.
Due to the fact that multiple repositories bring a better overview in the
maintenance, the decision was made to go with multiple repositories.&lt;/p&gt;
&lt;p&gt;There were some scenarios which involved transaction handling over multiple
DAO objects. The possible ways of handling transactions over multiple repositories is very
well described in the stackexchange post:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://softwareengineering.stackexchange.com/a/339458/363485&quot;&gt;https://softwareengineering.stackexchange.com/a/339458/363485&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The solution used to cope with this situation within this proof of concept was to create
repositories for each service operation and inject  the connection in the repositories.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dependency injection of connection: Your DAOs are not singletons but throw-away objects, receiving the connection on creation time. The calling code will control the connection creation for you.&lt;/p&gt;
&lt;p&gt;PRO: easy to implement&lt;/p&gt;
&lt;p&gt;CON: DB connection preparation / error handling in the business layer&lt;/p&gt;
&lt;p&gt;CON: DAOs are not singletons and you produce a lot of trash on the heap (your implementation language may vary here)&lt;/p&gt;
&lt;p&gt;CON: Will not allow stacking of service methods&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;It is relatively easy to think about a solution for the previously exposed problem, but before putting it to a production
environment the solution needs propper testing in conditions similar to the environment in which it will run.&lt;/p&gt;
&lt;p&gt;This is where the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library helps a great deal by providing lightweight,
throwaway instances of common databases that can run in a Docker container.&lt;/p&gt;
&lt;p&gt;More details on how to perform integration tests involving kafka-connect-jdbc  can be found on the blog post:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Showcase of testing syncing of &lt;a href=&quot;https://twitter.com/hashtag/postgres?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#postgres&lt;/a&gt; &lt;br&gt;data via &lt;a href=&quot;https://twitter.com/confluentinc?ref_src=twsrc%5Etfw&quot;&gt;@confluentinc&lt;/a&gt; kafka-connect-jdbc towards &lt;a href=&quot;https://twitter.com/apachekafka?ref_src=twsrc%5Etfw&quot;&gt;@apachekafka&lt;/a&gt; with &lt;a href=&quot;https://twitter.com/testcontainers?ref_src=twsrc%5Etfw&quot;&gt;@testcontainers&lt;/a&gt;&lt;a href=&quot;https://t.co/HdwH12neDW&quot;&gt;https://t.co/HdwH12neDW&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1248517825297362945?ref_src=twsrc%5Etfw&quot;&gt;April 10, 2020&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;div&gt;&lt;re-img src=&quot;nested-set-kafka-sync-system-tests.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRmQAAABXRUJQVlA4IFgAAABwAwCdASoUAA0APtFUo0uoJKMhsAgBABoJaQAAWp7wEhjs7x4A/vOJ5gGFeYarEejDXfgSVaAmm2pVzv1enA3Jqr/dfUSPs/nxkdwOiYk+hYUYwTcBRAAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.551454138702461,&amp;quot;src&amp;quot;:&amp;quot;/static/d8f45207338261fe9e852da1a2ba6bd4/32a6f/nested-set-kafka-sync-system-tests.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/d8f45207338261fe9e852da1a2ba6bd4/dd2ee/nested-set-kafka-sync-system-tests.webp 200w,\n/static/d8f45207338261fe9e852da1a2ba6bd4/f333d/nested-set-kafka-sync-system-tests.webp 400w,\n/static/d8f45207338261fe9e852da1a2ba6bd4/32a6f/nested-set-kafka-sync-system-tests.webp 800w,\n/static/d8f45207338261fe9e852da1a2ba6bd4/2cf13/nested-set-kafka-sync-system-tests.webp 1200w,\n/static/d8f45207338261fe9e852da1a2ba6bd4/2c342/nested-set-kafka-sync-system-tests.webp 1387w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/d8f45207338261fe9e852da1a2ba6bd4/2c342/nested-set-kafka-sync-system-tests.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;nested-set-kafka-sync-system-tests.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:516,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;258&amp;apos;%3e%3cpath%20d=&amp;apos;M6%20131v123h102v-47h9v47h270V9H118l-1%2029v29h-9V7H6v124m1%200v122h100v-46H62v-40l1%2019v20l22-1h22v-66h-6l-7-1%207-1h6v-34a147%20147%200%2000-1-36l1-29V8H7v123M119%2010v57h28v-6c0-7%201-8%2011-8s10%200%2010%2014%200%2015-10%2015c-11%200-11-1-11-8v-6h-28v69h19c1%202-1%202-10%202h-9v66h11a286%20286%200%200015%200c2%200%202-1%202-5%200-7%201-8%2011-8%208%200%2010%201%2010%207v5l12%201c16%200%2019%202%203%202h-13c-2%200-3%202-2%206%200%205-1%207-5%208-3%200-4%202-2%205%202%202%203%201%203-1s0-2%202-1l3%202-1%202-1%201-3%201c-4%204-9%205-13%201l-3-2%203-3%205-3c3%200%201-2-4-3h-4l-1-7v-6h-28v46h266v-68a1413%201413%200%2000-1-69%20160%20160%200%2000-2-47l-1-1c0-2-3-2-22-2h-23v17h24l25%201h-50V65h23c20%200%2024%200%2025%202%202%201%202%200%202-28V10H119m176%2011v9h88v-8c0-12%204-11-46-11h-42v10m1%200v8h86v-7l-1-7-2-1c0-2-5-2-41-2h-42v9m3%2021v2l-1%202h-4c-1-1-1-1-1%202l-1%206c0%201%200%202%202%202%202%201%203%200%201-2-2-3-1-6%201-6s2%200%201%205l1%204%201-1%201-2v1l4%202v-3l-1-4v-5l-1-2c1-2-3-3-3-1m21%201l-2%201-4%203-3%203%203%202%204%203c3%200%208-2%209-4l2-2c2%202%202-2%200-4-2-1-3-1-3%202l-1%201-3-4-1-2-1%201m-34%2025c0%204%200%205-2%205h-62v56h-23l-22%201h46V74h30a227%20227%200%200131%201c2%200%202%200%202%205l1%205h46V64l-24-1h-23v5m1%206v9h44V71h-5c-5-1-6-2-4-6%201-1-3-1-17-1h-18v10m-179%2028v35h5l4%201V68h-9v34m49-15l-2%201-4%203-3%202%203%203c4%203%209%203%2014-2%204-3%204-4%202-5-3-1-3-1-3%201l-1%201-3-4-1-1-2%201m-27%205c-1%202%201%207%202%207l3%202c1%201%201%201%203-1%203-2%203-8%202-9-3-2-10-1-10%201m189%203c-6%204-7%205-5%208%201%201%203%202%204%201v1c-1%201-1%201%202%201%205%200%2014-7%2011-9-2-1-3-1-3%201l-1%201-3-4c0-2-1-2-5%200m-30%2018c-2%200-2%201-2%205s0%204-2%204c-1-1-2%200-2%201%200%202%201%202%202%202%202-1%202%200%202%204v5h47v-21h-20a353%20353%200%2001-25%200m0%2011v9h44v-12h-5c-4%200-4-1-4-4v-3h-35v10m-48%209l6%201h5v31h-13v-10l-20-1h-20l19%201%2019%201v9h-22v21h11l11%201v18h-8l-9%201c-1%201%205%201%2018%201l19-1h2c0-2-2-3-5-3l-3%201c0%202-2%203-2%201v-1l1-1h-6l-1-1h-2c-2%201-2%201-2-7v-9h22v-21h-8v-15c0-14%200-15%202-16%204-1%202-2-6-2l-8%201m-133%2039v33h5l4%201v-67h-9v33m34-25l-1%201-5%203-3%203%203%202c4%204%209%203%2013-1l3-1%201-1%201-2-3-2c-2-1-2-1-2%201l-1%202-3-4-1-2-2%201m144%2028v9h66v-8c0-7-1-10-2-8v1l1%207v7h-64v-17h31a160%20160%200%200131%201c3-2-3-2-32-2h-31v10m-70%201v9h44v-12h-5c-4%200-5-1-5-3l2-3c1-1-5-1-17-1h-19v10m79-6l12%201%2010%201-8%201-7%201%205%202c2%200%202%200%201%201h-4l-2%201c0%201%201%202%203%202a149%20149%200%20015-2c-2-1%201-3%205-2%204%200%205-1%205-2l-1-1%201-1c6-2%202-3-11-3l-14%201m-46%2019l-2%201-4%203c-3%203-3%203-1%205l3%201%201%201c-2%203%207%200%2010-2l3-4c2-1%202-1%200-2-3-2-3-2-3%200%200%203-3%202-4-2-1-2-3-3-3-1m102%2011l-7%204v12l7%205%207%204%207-4%207-4v-13l-7-4-7-4-7%204m-221%2029c-2%202%200%207%202%208%202%200%203%201%203%202%201%203%206-3%206-7%200-3-1-3-6-4l-5%201m203%209v9h50v-8c0-12%202-11-26-11h-24v10m1%200v8h48v-7c0-6-1-9-2-6-1%201-3%202-4%201%200-1%200-2%202-2%201%200%202-1%201-2l-23-1h-22v9&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;Docker containers are used for interacting with the Apache Kafka ecosystem as well as the source and sink databases.&lt;/p&gt;
&lt;p&gt;This leads to tests that are easy to read and allow the testing of the sync operation for various nested set models&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;/**
     *   Ensure that the sync the content of a more complex nested set model
     *   is performed successively each time after performing updates on the
     *   nested set model on the source database.
     */&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;syncingSuccessiveChangesToTheTreeDemo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; clothingNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertRootNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Clothing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// |1| Clothing |2|&lt;/span&gt;


        &lt;span class=&quot;token comment&quot;&gt;// Add now Men&apos;s and Women&apos;s children and wait for the syncing&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mensNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Men&apos;s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; womensNodeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Women&apos;s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clothingNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// |1| Clothing |6|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//     ├── |2| Men&apos;s |3|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//     └── |4| Women&apos;s |5|&lt;/span&gt;



        &lt;span class=&quot;token comment&quot;&gt;// Add new children categories for both Men&apos;s and Women&apos;s nodes&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Suits&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dresses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Skirts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        sourceNestedSetService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Blouses&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;awaitForTheSyncOfTheNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;womensNodeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;logSinkTreeContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// The current structure of the tree should be:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//   |1| Clothing |14|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       ├── |2| Men&apos;s |5|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       │       └── |3| Suits |4|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//       └── |6| Women&apos;s |13|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               ├── |7| Dresses |8|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               ├── |9| Skirts |10|&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;//               └── |11| Blouses |12|&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/findinpath/nested-set-kafka-sync/blob/master/end-to-end-tests/src/test/java/com/findinpath/DemoNestedSetSyncTest.java&quot;&gt;DemoNestedSetSyncTest&lt;/a&gt;
for several syncing test cases.&lt;/p&gt;
&lt;p&gt;This project provides a functional prototype on how to setup the whole
Confluent environment (including &lt;strong&gt;Confluent Schema Registry&lt;/strong&gt; and &lt;strong&gt;Apache Kafka Connect&lt;/strong&gt;)
via testcontainers.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/findinpath/nested-set-kafka-sync/blob/master/end-to-end-tests/src/test/java/com/findinpath/AbstractNestedSetSyncTest.java&quot;&gt;AbstractNestedSetSyncTest&lt;/a&gt;
and the &lt;a href=&quot;https://github.com/findinpath/nested-set-kafka-sync/tree/master/end-to-end-tests/src/test/java/com/findinpath/testcontainers&quot;&gt;testcontainers package&lt;/a&gt; for details.&lt;/p&gt;
&lt;h3&gt;Kafka Connect&lt;/h3&gt;
&lt;p&gt;In order to use the Confluent’s Kafka Connect container, this project made use of the already existing code
for &lt;a href=&quot;https://github.com/ydespreaux/testcontainers/blob/master/testcontainers-kafka/src/main/java/com/github/ydespreaux/testcontainers/kafka/containers/KafkaConnectContainer.java&quot;&gt;KafkaConnectContainer&lt;/a&gt;
on &lt;a href=&quot;https://github.com/ydespreaux&quot;&gt;ydespreaux&lt;/a&gt; Github account.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; that the &lt;code class=&quot;language-text&quot;&gt;KafkaConnectContainer&lt;/code&gt; class previously mentioned has also corresponding test cases
within the project &lt;a href=&quot;https://github.com/ydespreaux/shared/tree/master/lib-kafka-connect&quot;&gt;lib-kafka-connect&lt;/a&gt; in order to have a clue
on how to interact in an integration test with the container.&lt;/p&gt;
&lt;h2&gt;Sample code&lt;/h2&gt;
&lt;p&gt;Checkout the github project sample project &lt;a href=&quot;https://github.com/findinpath/nested-set-kafka-sync&quot;&gt;nested-set-kafka-sync&lt;/a&gt; and try out the tests via&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[kafka-connect-jdbc testcontainers tests]]></title><description><![CDATA[Test a system that depends on kafka-connect-jdbc by bootstrapping
the Apache Kafka ecosystem artifacts and PostgreSQL through testcontainers…]]></description><link>https://www.findinpath.com/kafka-connect-jdbc-system-tests/</link><guid isPermaLink="false">https://www.findinpath.com/kafka-connect-jdbc-system-tests/</guid><pubDate>Fri, 10 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Test a system that depends on kafka-connect-jdbc by bootstrapping
the Apache Kafka ecosystem artifacts and PostgreSQL through &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a nutshell, as described on
&lt;a href=&quot;https://www.confluent.io/blog/kafka-connect-deep-dive-jdbc-source-connector/&quot;&gt;Confluent blog&lt;/a&gt;
the &lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/index.html&quot;&gt;kafka-connect-jdbc&lt;/a&gt;
provides the following functionality:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; It enables you to pull data (source) from a database into Kafka, and to push data (sink) from a Kafka topic to a database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;re-img src=&quot;JDBC-connector.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRlgAAABXRUJQVlA4IEwAAACQAwCdASoUAAcAPtFUpEuoJKOhsAgBABoJZwAAW+k5uFYsyYAAAP7z7rh7FUjK3ogTz6sjoTkxLXukLTwi0w1/bULcA2mhXzrpQAAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.751412429378531,&amp;quot;src&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/32a6f/JDBC-connector.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/dd2ee/JDBC-connector.webp 200w,\n/static/4f11f3fa83975efdfbea71362207418f/f333d/JDBC-connector.webp 400w,\n/static/4f11f3fa83975efdfbea71362207418f/32a6f/JDBC-connector.webp 800w,\n/static/4f11f3fa83975efdfbea71362207418f/5d180/JDBC-connector.webp 974w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/4f11f3fa83975efdfbea71362207418f/5d180/JDBC-connector.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;JDBC-connector.png&amp;quot;,&amp;quot;density&amp;quot;:150,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:291,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;145&amp;apos;%3e%3cpath%20d=&amp;apos;M319%2019l1%205%201%203%201-2c-1-3%201-3%202%200l2%202%202%201%202-1%203-1c1%201%201%200%201-1%200-3%202-3%203%200%201%202%201%202%204%202l1%202c0%204-1%204-4%202h-2c2%205%207%204%207-2%200-3%200-3%202-2h3c1-1%201-1-1-1s-2%200-1-1c2-1%203-4%201-5l-3%201c-1%201-2%202-3%201v3h-1l-1-2-2-1h-4c-2%202-2%202-2%200l-3-1v2l-2%201-4-3-3-2M194%2067l5%203%206%202h-17l-16%201%2016%201h17l-7%203c-4%202-6%203-5%204l18-8-16-7-1%201M8%2083v9h47c2-3%201-16-1-16l-24-1H8v8m1%201v7h45V77H34l8%201c10%201%2011%201%2011%206s-5%206-18%204H22c-12%200-16-7-5-9l7-1%206-1-10-1H9v8m302-1l-1%2010c0%208%200%208%202%209%207%201%207%200%208-10%200-10%200-10-2-10l-5-1c-1%200-2%200-2%202m12-1v11l1%209h9V92c0-10%200-10-2-10l-5-1-3%201m13%201v17l5%201%204%201V82l-5-1c-3%200-4%200-4%202m12%208v10h4c6%201%206%200%206-10s0-10-7-10h-3v10m13-8v17l5%201%204%201V82l-5-1c-3%200-4%200-4%202m12%208v10h10V82l-5-1h-5v10m13%200v9l5%201%204%201V82l-5-1h-4v10&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;This post is a showcase on how to test the synchronization of the contents
of a &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; table
via &lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/index.html&quot;&gt;kafka-connect-jdbc&lt;/a&gt;
towards &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The contents of the input PostgreSQL table are synced as &lt;a href=&quot;https://avro.apache.org/&quot;&gt;AVRO&lt;/a&gt; messages
towards Apache Kafka.&lt;/p&gt;
&lt;p&gt;The showcased project &lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-connect/&quot;&gt;testcontainers-kafka-connect&lt;/a&gt;
makes use of &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; containers
(via &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library) for showcasing
the Confluent Kakfa Connect functionality in an automated test case.&lt;/p&gt;
&lt;p&gt;The interactions from this proof of concept are described visually in the image below:&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;kafka-connect-jdbc_system-test_architecture.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRlYAAABXRUJQVlA4IEoAAAAQAwCdASoUAA4APtFUo0uoJKMhsAgBABoJaQAAetENeAAA/vHQNvkCn4GxazPjpfWH2oyMQGiAGhSAZ0GJimwkeGwdfyfRgEXAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.4554455445544554,&amp;quot;src&amp;quot;:&amp;quot;/static/5bc31026e0e9d5cf3c5bac7e9fb10835/32a6f/kafka-connect-jdbc_system-test_architecture.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/5bc31026e0e9d5cf3c5bac7e9fb10835/dd2ee/kafka-connect-jdbc_system-test_architecture.webp 200w,\n/static/5bc31026e0e9d5cf3c5bac7e9fb10835/f333d/kafka-connect-jdbc_system-test_architecture.webp 400w,\n/static/5bc31026e0e9d5cf3c5bac7e9fb10835/32a6f/kafka-connect-jdbc_system-test_architecture.webp 800w,\n/static/5bc31026e0e9d5cf3c5bac7e9fb10835/2cf13/kafka-connect-jdbc_system-test_architecture.webp 1200w,\n/static/5bc31026e0e9d5cf3c5bac7e9fb10835/1a04b/kafka-connect-jdbc_system-test_architecture.webp 1470w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/5bc31026e0e9d5cf3c5bac7e9fb10835/1a04b/kafka-connect-jdbc_system-test_architecture.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;kafka-connect-jdbc_system-test_architecture.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:550,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;275&amp;apos;%3e%3cpath%20d=&amp;apos;M36%20105v100h94v-34h11v34h254V5H268L142%206l-1%2049v49h-11V5L83%204H36v101m1%200v99h91v-33H89v-11l-1-11-1-1c0-1%202-2%206-1l9%201-6%201-6-1v22h38v-65h-17l9-1h8V6H37v99m105-49v48h8l11%201-10%201h-8l-1%2032v32h13c15%200%2014%200%2014-7%200-5%202-6%2010-6s10%201%2010%206c0%207-1%207%2012%207l12%201h-22c-2%200-2%201-2%207%200%204-2%206-6%206-2%200-3%202-1%205h3c0-2%201-2%204-1v3l-1%201-4%202c-3%203-9%203-11%200l-3-2%203-3%205-3%201-1-4-1c-4%200-6-2-6-6%200-7%201-7-13-7h-14v33h202v-18h22c26%200%2025%200%2025%2010%200%206%200%208%202%208l1-98V7h-6c-4%200-5%200-4%201l1%209v8h-83V7H142v49m161-40v8h81v-6c0-12%203-11-42-11h-39v9m-58-2c0%202%200%202%205%202l4-1h1c1%202%2017%201%2017-1%201-3%200-3-4-3-3%200-4%200-4%202v3l-2-3c-2-3-17-3-17%201m-46%2014l-1%201-5%203-3%202%203%203c3%203%207%203%2014-2l1-1%201-1v-2l-2-1-1-1-1%202c0%202%200%202-2%201l-2-3-1-2-1%201m-9%2013l-1%202-11%201-10%201v20h9c8%200%209%200%208%202%200%201%200%202%202%202%201%200%202-1%201-2%200-2%201-2%2012-2h12V55c0-7%200-9-2-10v-3l-1-1h-19m25%2012v9h47v-7c0-7-1-10-2-8v1l1%207v6h-45V45h21l21%201h1c3-1-2-2-22-2h-22v9m-46%202v9h41v-6l-1-6v-2c0-2%200-3-3-3-4-1-5%204-1%204%202%201%202%201-1%201s-3%200-3-3l1-3h-33v9m164%2010l-1%201-4%203-3%202%203%203c3%203%207%203%2011%200%201-2%203-3%204-2v-4c-2-2-3-2-3%200l-2%201-2-1v-2l-1-2-2%201M172%2077l-5%203-3%203%203%202c3%204%208%203%2013-1%204-4%204-4%202-5-3-1-3-1-3%201l-1%201-3-4h-3m154%200c-2%201-2%202-2%203%201%202%200%202-11%202h-11v5c0%204%200%204-2%204h-3l-3%201c-2%200-2%200%201%201h5c2%200%202%201%202%204v5h22l23-1v-2l-1-15%2023-1%2022-1h-32a499%20499%200%2001-18-2h1c1%201%202%200%202-2l-8-1h-10m-23%2015v9h41V90h-4c-4%200-4-1-4-4v-3h-33v9m-173%2046v32h11v-65h-11v33m88-16h36v5l1%204h-22v10l1%2011h21l-1%209v9h-9l-8%201h19v-19h11l10-1%201-2%203-2h2l-1%201v1l3-1c1-2%203-2%203-1l-2%201c-1%201%200%201%203%201l4-1h1c0%202%202%201%202-1%201-2%200-2-3-2h-14c-2%200-2-1-2-3v-3l3%202c4%204%209%204%2013-1l2-1%201-1c1-1%200-2-1-3-2-1-3-1-3%201l-1%201-3-4c0-2-4%200-7%203s-4%203-4-1v-4h-21v-9l-19-1-19%201m16%2020v8h42v-11h-5c-4%200-4-1-4-3v-3h-33v9m127%2018l-6%203v13l6%203%207%204%207-4%206-3v-12l-6-3c-7-5-7-5-14-1m-16%2035v8h45v-6l-1-7-1%201-2%201c-1%201-2%200-2-1l2-1%202-1c0-2-3-2-21-2h-22v8m-165%2045l-1%2015v15h50v-14c0-12%200-14-2-16-2-1-46-2-47%200m1%201l-1%2014v14h48v-12c0-18%202-17-25-17l-22%201&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The proof of concept &lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-connect/&quot;&gt;testcontainers-kafka-connect&lt;/a&gt;
project can be used for making end to end system test cases with
&lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; for architectures that rely on
&lt;a href=&quot;https://docs.confluent.io/current/connect/kafka-connect-jdbc/index.html&quot;&gt;kafka-connect-jdbc&lt;/a&gt;
for syncing content from a relational database (PostgreSQL is the database used in the
aforementioned project).&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library already
offers a &lt;a href=&quot;https://www.testcontainers.org/modules/kafka/&quot;&gt;Kafka&lt;/a&gt; module
for interacting with &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt;, but
there is not, at the moment, a testcontainers module for the whole
Confluent environment (Confluent Schema Registry / Apache Kafka Connect
container support is missing from the module previously mentioned).&lt;/p&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-connect/&quot;&gt;testcontainers-kafka-connect&lt;/a&gt;
contains custom implementations for &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; extensions
corresponding for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Zookeeper&lt;/li&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Confluent Schema Registry&lt;/li&gt;
&lt;li&gt;Apache Kafka Connect&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;that can be used to reproduce a multi-container scenario that involves working with the
aforementioned components of the Apache Kafka ecosystem.&lt;/p&gt;
&lt;p&gt;As a side note, the containers used do not use the default ports exposed
by default in the artifacts (e.g. : Apache Zookeeper &lt;em&gt;2181&lt;/em&gt;, Apache Kafka &lt;em&gt;9092&lt;/em&gt;,
Confluent Schema Registry &lt;em&gt;8081&lt;/em&gt;, Apache Kafka Connect &lt;em&gt;8083&lt;/em&gt;), but rather free
ports available on the test machine avoiding therefor possible conflicts
with already running services on the test machine.&lt;/p&gt;
&lt;p&gt;For the test environment the following containers will be started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Zookeeper&lt;/li&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Confluent Schema Registry&lt;/li&gt;
&lt;li&gt;Confluent Kafka Connect&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is quite impressive to see how close a productive environment can be simulated in the test cases
with the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library.&lt;/p&gt;
&lt;h2&gt;Demo test&lt;/h2&gt;
&lt;p&gt;Once the test environment is started, via a HTTP call performed with &lt;a href=&quot;http://rest-assured.io/&quot;&gt;rest-assured&lt;/a&gt;
a kafka-connect-jdbc connector will be registered for the &lt;code class=&quot;language-text&quot;&gt;bookmarks&lt;/code&gt; PostgreSQL table.&lt;/p&gt;
&lt;p&gt;The kafka-connect-jdbc connector will afterwards then continously poll the &lt;code class=&quot;language-text&quot;&gt;bookmarks&lt;/code&gt; table
and will sync its contents towards the &lt;code class=&quot;language-text&quot;&gt;findinpath.bookmarks&lt;/code&gt; Apache Kafka topic.&lt;/p&gt;
&lt;p&gt;The demo verifies whether the dynamically inserted contents
into the &lt;code class=&quot;language-text&quot;&gt;bookmarks&lt;/code&gt; Postgres table get successfully synced in
&lt;a href=&quot;http://avro.apache.org/&quot;&gt;AVRO&lt;/a&gt; format on the Apache Kafka
topic &lt;code class=&quot;language-text&quot;&gt;findinpath.bookmarks&lt;/code&gt; in the same order as they were inserted.&lt;/p&gt;
&lt;h2&gt;Sample code&lt;/h2&gt;
&lt;p&gt;Checkout the github project sample project &lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-connect&quot;&gt;testcontainers-kafka-connect&lt;/a&gt; and try out the tests via&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Dynamic property sources for PostgreSQL spring boot tests]]></title><description><![CDATA[Make use of the newly introduced
DynamicPropertySource spring annotation
in configuring DataSource spring beans required in the tests
that…]]></description><link>https://www.findinpath.com/spring-boot-dynamicpropertysource-postgres/</link><guid isPermaLink="false">https://www.findinpath.com/spring-boot-dynamicpropertysource-postgres/</guid><pubDate>Sun, 29 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Make use of the newly introduced
DynamicPropertySource spring annotation
in configuring DataSource spring beans required in the tests
that make use of &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Spring Boot interacts with databases via with data sources
mapped via the &lt;code class=&quot;language-text&quot;&gt;spring.datasource&lt;/code&gt; in the &lt;code class=&quot;language-text&quot;&gt;application.yml&lt;/code&gt; file.
In the Spring Boot internals, via
&lt;code class=&quot;language-text&quot;&gt;org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration&lt;/code&gt;, is triggered
the creation of the much needed spring bean instance of type &lt;code class=&quot;language-text&quot;&gt;javax.sql.DataSource&lt;/code&gt; for JDBC/JPA interactions.&lt;/p&gt;
&lt;p&gt;In the context of testing DAO (Data Access Object) classes, it is very common to make use of
the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before introducing the annotation &lt;code class=&quot;language-text&quot;&gt;@DynamicPropertySource&lt;/code&gt; there was needed an
implementation of the &lt;code class=&quot;language-text&quot;&gt;ApplicationContextInitializer&lt;/code&gt; to introduce dynamicaly the required properties in
the configurable application context for bootstrapping the creation of the data source backed (via
&lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt;  library) by a database living in a &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; container :&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringJUnitConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; initializers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;PostgresIntegrationTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Initializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Testcontainers&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgresIntegrationTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Container&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgreSQLContainer&lt;/span&gt; postgreSQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;postgres:12&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withDatabaseName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;integration-tests-db&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;


  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JdbcTemplate&lt;/span&gt; jdbcTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Initializer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ApplicationContextInitializer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigurableApplicationContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ConfigurableApplicationContext&lt;/span&gt; configurableApplicationContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;TestPropertyValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.url=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJdbcUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.username=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.password=&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;applyTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;configurableApplicationContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEnvironment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     jdbcTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SELECT 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the introduction of the &lt;code class=&quot;language-text&quot;&gt;@DynamicPropertySource&lt;/code&gt; there is no need for an extra &lt;code class=&quot;language-text&quot;&gt;ApplicationContextInitializer&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringJUnitConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Testcontainers&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgresIntegrationTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Container&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgreSQLContainer&lt;/span&gt; postgreSQLContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;postgres:12&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withDatabaseName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;integration-tests-db&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;


  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JdbcTemplate&lt;/span&gt; jdbcTemplate&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@DynamicPropertySource&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgresProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;DynamicPropertyRegistry&lt;/span&gt; registry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getJdbcUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.username&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    registry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spring.datasource.password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; postgreSQLContainer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    jdbcTemplate&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SELECT 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As can be seen from above, the newly introduced &lt;code class=&quot;language-text&quot;&gt;@DynamicPropertySource&lt;/code&gt; is somehow similar to the
commonly used &lt;code class=&quot;language-text&quot;&gt;@TestPropertySource&lt;/code&gt; annotation with the mention that it allows the usage of dynamic resources
such as the IP and port assigned to the container (needed in the &lt;code class=&quot;language-text&quot;&gt;jdbcUrl&lt;/code&gt; in the example above).&lt;/p&gt;
&lt;p&gt;Check out the full source code (and corresponding documentation) of the
&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/findinpath/postgres-spring-boot-dynamicpropertysource/blob/master/src/test/java/com/findinpath/springboot/testcontainers/PostgresIntegrationTest.java&quot;&gt;PostgresIntegrationTest.java&lt;/a&gt;
class.&lt;/p&gt;
&lt;p&gt;See more details about the usage of the &lt;code class=&quot;language-text&quot;&gt;@DynamicPropertyResource&lt;/code&gt; in the Spring framework
&lt;a target=&quot;_blank&quot; href=&quot;https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-dynamic-property-sources&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Sample code&lt;/h2&gt;
&lt;p&gt;Checkout the github project sample project &lt;a href=&quot;https://github.com/findinpath/postgres-spring-boot-dynamicpropertysource/&quot;&gt;postgres-spring-boot-dynamicpropertysource&lt;/a&gt; and try out the tests via&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Process generic avro records in Kafka Streams]]></title><description><![CDATA[Via Kafka Streams can be processed 
avro records of different types in order to benefit of the 
ordering of the events that relate to the…]]></description><link>https://www.findinpath.com/kafka-streams-generic-avro/</link><guid isPermaLink="false">https://www.findinpath.com/kafka-streams-generic-avro/</guid><pubDate>Sat, 28 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Via &lt;a href=&quot;https://kafka.apache.org/documentation/streams/&quot;&gt;Kafka Streams&lt;/a&gt; can be processed
&lt;a href=&quot;https://avro.apache.org/&quot;&gt;avro&lt;/a&gt; records of different types in order to benefit of the
ordering of the events that relate to the same domain entity.&lt;/p&gt;
&lt;p&gt;As described in Martin Kleppmann’s article &lt;a href=&quot;https://www.confluent.io/blog/put-several-event-types-kafka-topic/&quot;&gt;Should you put several event types in the same Kafka topic&lt;/a&gt; there are good reasons why it would
make sense to stuff multiple event types in the same Kafka topic:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The most important rule is that any events that need to stay in a fixed order must go in the same topic (and they must also use the same partitioning key). Most commonly, the order of events matters if they are about the same entity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the data encoding for the records passed through Apache Kafka topic is JSON it is then relatively
easy to setup an application consuming the records. The problems occur when there are modifications brought to the schema of the records in order to match the new business requirements of the system
because these changes make it difficult to know what version of the JSON objects are we dealing with
Different clients can very likely use different versions for the schema of the JSON object that they
send for processing, but the Kafka application on the receiving end needs to be able to accurately
process &lt;em&gt;ALL&lt;/em&gt; the JSON objects.
This is why the Apache Kafka &lt;a href=&quot;https://www.confluent.io/confluent-schema-registry/&quot;&gt;documentation&lt;/a&gt;  highly encourages the usage of the &lt;a href=&quot;https://avro.apache.org/&quot;&gt;avro&lt;/a&gt; library for data encoding of the records passed through Apache Kafka topics.&lt;/p&gt;
&lt;p&gt;Processing generic records comes at the cost of having a higher complexity on the
Apache Kafka consumer side, but having the ability to keep in order the events happening on a domain entity is worth the trouble.
A concrete example to showcase the need to keep in order the events for an entity would be the
registration of a new user of a site and subsequently the change of the address of the user via his profile page.  In this case, the Apache Kafka application consuming the events related to users may
process an address change for a user that does not exist if the event corresponding for the creation
of the new user has been delayed.&lt;/p&gt;
&lt;p&gt;This article is accompanied by the &lt;a href=&quot;https://github.com/findinpath/kafka-streams-generic-avro&quot;&gt;kafka-streams-generic-avro&lt;/a&gt; sample project which showcases the strategies available for Kafka Streams
to cope with generic avro records.&lt;/p&gt;
&lt;p&gt;The topologies showcased in the project are overly simplistic with the sole purpose of echoing the
information that they receive for processing to the destination topics. &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    records
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; LOGGER
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Processing entry with the key &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; and value &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outputTopic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The test code based on &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; corresponding
to these topologies is used to showcase the differences between the two strategies available for
processing generic avro records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;GenericRecord&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;SpecificRecord&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Generic Record Topologies&lt;/h2&gt;
&lt;p&gt;The main advantages of building topologies based on the type &lt;code class=&quot;language-text&quot;&gt;org.apache.avro.generic.GenericRecord&lt;/code&gt; is
that there can be processed virtually any kind of messages by the client. This is pretty much similar to
processing JSON objects without prior knowledge of their types.&lt;/p&gt;
&lt;h2&gt;Specific Record Topologies&lt;/h2&gt;
&lt;p&gt;When consuming specific records, based on the type &lt;code class=&quot;language-text&quot;&gt;org.apache.avro.generic.SpecificRecord&lt;/code&gt;, the code of
the topology has the benefit of working with typed records, which can ease up the handling of these records.
The one possible inconvenient in this case is that the topology must have in its classpath the types of the
records that it intends to process.&lt;/p&gt;
&lt;h2&gt;Pattern for handling the records&lt;/h2&gt;
&lt;p&gt;A possibility to handle a finite amount of record types is to have a series of &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt; statements
chained together:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;   &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BookmarkEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;record&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserCreatedEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;record&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

   &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The main problem with this approach is that the more record types the topology tends to handle,
the longer (and error prone) this handling code based on the type gets. &lt;/p&gt;
&lt;p&gt;An alternative to chained &lt;code class=&quot;language-text&quot;&gt;if&lt;/code&gt; statements is the usage of a handler map:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpecificRecord&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BiConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpecificRecord&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; handlers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    handlers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;BookmarkEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; LOGGER
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Processing bookmark entry with the key &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; and value &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    handlers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserCreatedEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; LOGGER
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Processing user created entry with the key &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; and value &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    records
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; handlers
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;record&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; LOGGER&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Handler not configured for the record &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; record &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; with key &quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; of type &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; record&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;outputTopic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Schema Registry settings&lt;/h2&gt;
&lt;p&gt;As already mentioned in the article &lt;a href=&quot;https://www.confluent.io/blog/put-several-event-types-kafka-topic/&quot;&gt;Should you put several event types in the same Kafka topic&lt;/a&gt; in order to deal with several event types
in the same topic there are two options for naming the avro subjects in the &lt;a href=&quot;https://www.confluent.io/confluent-schema-registry&quot;&gt;Confluent Schema Registry&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;io.confluent.kafka.serializers.subject.RecordNameStrategy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;io.confluent.kafka.serializers.subject.TopicRecordNameStrategy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the sample project &lt;a href=&quot;https://github.com/findinpath/kafka-streams-generic-avro&quot;&gt;kafka-streams-generic-avro&lt;/a&gt; the &lt;code class=&quot;language-text&quot;&gt;RecordNameStrategy&lt;/code&gt; was used for naming the subjects
corresponding to the record values that are written to the input Apache Kafka topic.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    streamsConfiguration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;AbstractKafkaAvroSerDeConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;VALUE_SUBJECT_NAME_STRATEGY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RecordNameStrategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Sample code&lt;/h2&gt;
&lt;p&gt;Checkout the github project sample project &lt;a href=&quot;https://github.com/findinpath/kafka-streams-generic-avro&quot;&gt;kafka-streams-generic-avro&lt;/a&gt; and try out the &lt;a href=&quot;https://github.com/findinpath/kafka-streams-generic-avro/blob/master/src/test/java/com/findinpath/GenericKafkaStreamsAvroDemoTest.java&quot;&gt;GenericKafkaStreamsAvroDemoTest&lt;/a&gt; test case to get see the concepts exposed above in action.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Bind spring-boot configuration properties in Kotlin]]></title><description><![CDATA[This post showcases how to bind  in spring-boot
projects written in kotlin. Spring Boot offers the @ConfigurationProperties annotation in…]]></description><link>https://www.findinpath.com/spring-boot-configurationproperties/</link><guid isPermaLink="false">https://www.findinpath.com/spring-boot-configurationproperties/</guid><pubDate>Thu, 28 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post showcases how to bind &lt;code class=&quot;language-text&quot;&gt;@ConfigurationProperties&lt;/code&gt; in &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;spring-boot&lt;/a&gt;
projects written in &lt;a href=&quot;https://kotlinlang.org/&quot;&gt;kotlin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Spring Boot offers the &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationProperties.html&quot;&gt;@ConfigurationProperties&lt;/a&gt; annotation in order to allow binding and validating properties set externally (e.g. via &lt;code class=&quot;language-text&quot;&gt;application.yml&lt;/code&gt; file).&lt;/p&gt;
&lt;p&gt;When writing a &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;Spring Boot&lt;/a&gt; application in Kotlin programming language, the usage of
&lt;a href=&quot;https://kotlinlang.org/docs/reference/data-classes.html&quot;&gt;data classes&lt;/a&gt;
comes like a good candidate to be used for binding external properties that are to be used subseqently in the business logic code of the
spring beans.&lt;/p&gt;
&lt;p&gt;Nevertheless, using an approach like the following&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;will result at the start of the spring boot application in the following binding error:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;The elements &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;project.code,project.name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; were left unbound.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This happens because the binding is done by property setters (which can’t succeed when using &lt;code class=&quot;language-text&quot;&gt;val&lt;/code&gt; fields in kotlin).&lt;/p&gt;
&lt;p&gt;There are several solutions to solve the binding of the external properties in kotlin classes.&lt;/p&gt;
&lt;p&gt;The outcome of solutions is that the fields of the &lt;code class=&quot;language-text&quot;&gt;Project&lt;/code&gt; class will be kotlin friendly non-null fields
and that the bootstrap of the spring application will fail in case that the required properties are not specified. &lt;/p&gt;
&lt;h2&gt;ConstructorBinding&lt;/h2&gt;
&lt;p&gt;By using the &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConstructorBinding.html&quot;&gt;@ConstructorBinding&lt;/a&gt;
annotation in the data class we can indicate that configuration properties should be bound using constructor arguments rather than by calling setters. &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; org&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;springframework&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;boot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ConstructorBinding

&lt;span class=&quot;token annotation builtin&quot;&gt;@ConstructorBinding&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;val&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Lateinit properties&lt;/h2&gt;
&lt;p&gt;Use &lt;code class=&quot;language-text&quot;&gt;lateinit&lt;/code&gt; modifier for allowing the properties of the class to be set at a later time.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;kotlin&quot;&gt;&lt;pre class=&quot;language-kotlin&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; Project&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String
    &lt;span class=&quot;token keyword&quot;&gt;lateinit&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String


    &lt;span class=&quot;token keyword&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Project(name=&apos;&lt;span class=&quot;token interpolation variable&quot;&gt;$name&lt;/span&gt;&apos;, code=&apos;&lt;span class=&quot;token interpolation variable&quot;&gt;$code&lt;/span&gt;&apos;)&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Testing spring-retry functionality with Cucumber]]></title><description><![CDATA[This post is intended to be heads up on the benefits in the readability
of the test scenarios that come when using
Cucumber library. The…]]></description><link>https://www.findinpath.com/spring-retry-cucumber/</link><guid isPermaLink="false">https://www.findinpath.com/spring-retry-cucumber/</guid><pubDate>Sat, 23 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post is intended to be heads up on the benefits in the readability
of the test scenarios that come when using
&lt;a href=&quot;https://github.com/cucumber/cucumber-jvm&quot;&gt;Cucumber&lt;/a&gt; library.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;spring-retry-cucumber.jpg&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRpgAAABXRUJQVlA4IIwAAABwBACdASoUAAkAPtFYpEwoJSOiMAgBABoJaACdDBUP/QRRgvx+u1mWPH/MQAD+8m/7c5ztcm3CBuTudJlXXbyOiRD16fz9upum4BHC935ctQUC8IsVSq2aqaf0PoQJ+in3ZpnHueANAm5n90d0TQ1XfjdtJiU6qj8h2ZQRJeNZiew2iu9+t8LheTUAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.125,&amp;quot;src&amp;quot;:&amp;quot;/static/57b7bd7a7c5404ef1e13a3497794ba50/32a6f/spring-retry-cucumber.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/57b7bd7a7c5404ef1e13a3497794ba50/dd2ee/spring-retry-cucumber.webp 200w,\n/static/57b7bd7a7c5404ef1e13a3497794ba50/f333d/spring-retry-cucumber.webp 400w,\n/static/57b7bd7a7c5404ef1e13a3497794ba50/32a6f/spring-retry-cucumber.webp 800w,\n/static/57b7bd7a7c5404ef1e13a3497794ba50/2cf13/spring-retry-cucumber.webp 1200w,\n/static/57b7bd7a7c5404ef1e13a3497794ba50/1e1a6/spring-retry-cucumber.webp 1600w,\n/static/57b7bd7a7c5404ef1e13a3497794ba50/27ea7/spring-retry-cucumber.webp 1700w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/57b7bd7a7c5404ef1e13a3497794ba50/27ea7/spring-retry-cucumber.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;spring-retry-cucumber.jpg&amp;quot;,&amp;quot;density&amp;quot;:300,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:376,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;188&amp;apos;%3e%3cpath%20d=&amp;apos;M63%2039c-5%202-7%205-20%2030-16%2031-16%2026%201%2058%209%2016%2014%2025%2016%2026%202%202%204%203%2029%203%2026%200%2027%200%2030-2s6-8%2016-26c17-33%2017-28%201-59-13-25-15-27-20-30H63m238%200c-25%204-45%2022-49%2044-4%2026%2015%2050%2044%2057l4%201v15l5-1c50-9%2079-56%2056-91a62%2062%200%2000-60-25m19%2019c-5%202-8%2013-4%2017s16-8%2013-13c-1-3-6-5-9-4M86%2059c-2%201-3%2035-1%2039%202%202%206%203%208%201V59h-7m207%200c-5%203-1%2013%207%2016%203%201%205%200%206-5%201-8-6-14-13-11M67%2068a39%2039%200%2000-6%2043c10%2022%2036%2026%2051%209%2013-14%2012-39-1-52-7-7-12%200-6%207s7%2011%207%2020c0%2016-9%2027-24%2026-22-1-30-29-14-46%203-4%203-4%202-7-1-4-5-4-9%200m120%200c-6%202-11%205-14%209l-3%203v-3c0-2%200-2-1-1v2l-1%201c-3-2-7%2015-6%2024%201%2010%208%2024%208%2016l3%203c10%2010%2026%2013%2039%206%2010-5%2017-13%2015-17-1-1-2%200-5%203a30%2030%200%2001-45%200l-5-3h-1l6-12%2010%209-2%201c-2%200%200%204%205%207s10%204%2015%204%2014-4%2012-5v-1h-2v-2l-1-1-3-5-2-3v-5c-2-2%203-1%208%201%208%204%209%203%2011-6l1-6-4-6c-5-7-7-8-13-12-7-3-18-4-25-1m4%204c-6%202-10%204-14%208s-4%205%201%201c10-7%2022-7%2031%202%204%204%205%206%202%206-4%200-2%201%205%205l6%203%201-3a1353%201353%200%20001-11c-1%201-4-1-6-4-8-6-17-9-27-7m35%203c15%2020%209%2047-12%2057-14%207-33%203-42-7l-3-3c-1%201%205%207%2010%2011%207%205%2014%206%2024%206%2014-1%2024-8%2031-20%203-6%204-7%204-17%200-13-2-18-9-26-4-5-6-6-3-1m49%200c-8%205%206%2014%2017%2010%204-1%204-2%201-5-4-6-13-8-18-5m54%2018c-5%202-3%205%204%2010%206%204%2015%202%2015-3s-11-9-19-7m-50%201c-3%201-6%204-6%206%200%203%204%205%208%205%207%200%2017-9%2011-11h-13m20%2010c-7%204-10%2011-6%2014%208%206%2016-3%2012-13-1-3-2-4-6-1m17%201c-2%204%200%2012%203%2014%204%203%2011-1%2011-5%200-7-11-14-14-9&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The Github project &lt;a href=&quot;https://github.com/findinpath/spring-retry-cucumber&quot;&gt;spring-retry-cucumber&lt;/a&gt;
which accompanies this blog post represents a simplisting Github API client that exposes operations
for retrieving Github user details.&lt;/p&gt;
&lt;p&gt;Feel free to checkout the source code of the project and run the tests by using the command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Below is presented a simple &lt;a href=&quot;https://github.com/findinpath/spring-retry-cucumber/blob/master/src/main/java/com/findinpath/DemoApplication.java&quot;&gt;demo&lt;/a&gt; of the functionality of this Github API client.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://api.github.com/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; github &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Github&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; usersEndpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; github&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;usersEndpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;findinpath&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;out&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;usersEndpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In case that sporadic exceptions may occur on the Github API,
through the usage of the &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt; mechanism
built on top of the previously demoed Github API client,these failures should go unnoticed
in the client program flow.&lt;/p&gt;
&lt;p&gt;The program flow will retrieve successfully the user details even though sometimes one sporadic
API call will fail, because the API call will be retried and therefor in the client context,
the API call will appear as successful (even though it was actually performed twice).&lt;/p&gt;
&lt;h2&gt;Cucumber&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://cucumber.io/docs/guides/&quot;&gt;Cucumber&lt;/a&gt; is a software library that enables the usage of &lt;a href=&quot;https://cucumber.io/docs/bdd/&quot;&gt;Behaviour Driven Development&lt;/a&gt; in the projects where it is integrated.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cucumber.io/docs/gherkin/&quot;&gt;Gherkin&lt;/a&gt; is a core component for Cucumber responsible
for parsing the executable test specifications is .
Gherkin is a set of grammar rules that makes plain text structured enough for Cucumber to understand.&lt;/p&gt;
&lt;p&gt;Below can be seen the accuracy &amp;#x26; failure test scenarios written for the &lt;a href=&quot;https://github.com/findinpath/spring-retry-cucumber&quot;&gt;spring-retry-cucumber&lt;/a&gt; demo project.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;accuracy-test-scenarios.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRkYAAABXRUJQVlA4IDoAAAAwAwCdASoUAAcAPtFUo0uoJKMhsAgBABoJaQAAXKZnE04AAP7v5LZQynulZU3TCZl/jRqzYHA1VYAA&amp;quot;,&amp;quot;aspectRatio&amp;quot;:3.066350710900474,&amp;quot;src&amp;quot;:&amp;quot;/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/32a6f/accuracy-test-scenarios.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/dd2ee/accuracy-test-scenarios.webp 200w,\n/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/f333d/accuracy-test-scenarios.webp 400w,\n/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/32a6f/accuracy-test-scenarios.webp 800w,\n/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/2cf13/accuracy-test-scenarios.webp 1200w,\n/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/3e090/accuracy-test-scenarios.webp 1294w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/45a7ab5d9a5597dc61fee7a0ee7adcdd/3e090/accuracy-test-scenarios.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;accuracy-test-scenarios.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:261,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;130&amp;apos;/%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;div&gt;&lt;re-img src=&quot;failure-test-scenarios.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRk4AAABXRUJQVlA4IEIAAADwAgCdASoUAAwAPtFUpEuoJKOhsAgBABoJaQDLLDKUAAD+8NRsuyB7Z2Mr3XdnGoFcpg4S016cqffh7h/9Eg+4AAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.6786469344608879,&amp;quot;src&amp;quot;:&amp;quot;/static/ae08f10aff5a63a59315876fe5b0d14c/32a6f/failure-test-scenarios.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/ae08f10aff5a63a59315876fe5b0d14c/dd2ee/failure-test-scenarios.webp 200w,\n/static/ae08f10aff5a63a59315876fe5b0d14c/f333d/failure-test-scenarios.webp 400w,\n/static/ae08f10aff5a63a59315876fe5b0d14c/32a6f/failure-test-scenarios.webp 800w,\n/static/ae08f10aff5a63a59315876fe5b0d14c/2cf13/failure-test-scenarios.webp 1200w,\n/static/ae08f10aff5a63a59315876fe5b0d14c/fad6b/failure-test-scenarios.webp 1588w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/ae08f10aff5a63a59315876fe5b0d14c/fad6b/failure-test-scenarios.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;failure-test-scenarios.png&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:477,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;238&amp;apos;/%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;When having a quick look over the scenarios above, it gets strikingly clear how the Github API
client is expected to work.&lt;/p&gt;
&lt;p&gt;The demo project &lt;a href=&quot;https://github.com/findinpath/spring-retry-cucumber&quot;&gt;spring-retry-cucumber&lt;/a&gt;
has a few particularities in terms of usage for the &lt;a href=&quot;https://github.com/cucumber/cucumber-jvm&quot;&gt;Cucumber&lt;/a&gt;
framework which will be detailed in the lines below.&lt;/p&gt;
&lt;h4&gt;Support for custom parameter types&lt;/h4&gt;
&lt;p&gt;In the images above that showcase the accuracy &amp;#x26; failures test scenarios for the
Github API client, in the scenario steps, there are used the highlighted tokens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;GET&lt;/code&gt; (corresponds to &lt;code class=&quot;language-text&quot;&gt;RequestMethod.GET&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;INTERNAL_SERVER_ERROR&lt;/code&gt; (corresponds to &lt;code class=&quot;language-text&quot;&gt;HttpStatus.INTERNAL_SERVER_ERROR&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the scenario steps they are referenced in the following manner:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WireMockApiSteps.java&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;I have made {int} {requestMethod} calls made towards Github API {string} resource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkNumberOfApiCalls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; count&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt; requestMethod&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;UserSteps.java&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;I will receive an {httpStatus} response status instead of the user details&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkErroneousCall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt; httpStatus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://cucumber.io/docs/gherkin/&quot;&gt;Gherkin&lt;/a&gt; allows the registration of custom parameter
types. See below the corresponding code for registering the custom parameter types:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ParameterTypes.java&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    typeRegistry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defineParameterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParameterType&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;requestMethod&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// name&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;GET|POST|PUT|DELETE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// regexp&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// type&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cucumber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cucumberexpressions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transformer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; s &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt; requestMethod&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            requestMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GET&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            requestMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;PUT&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            requestMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PUT&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            requestMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DELETE&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Unknown value &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; s &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; for RequestMethod&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; requestMethod&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; regexpHttpStatus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Enum&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joining&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;|&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    typeRegistry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defineParameterType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParameterType&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;httpStatus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// name&lt;/span&gt;
        regexpHttpStatus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// regex&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// type&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cucumber&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cucumberexpressions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Transformer&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;valueOf&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Data tables&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://cucumber.io/docs/gherkin/reference/#data-tables&quot;&gt;Data tables&lt;/a&gt; is a feature
of Gherkin through which can be elegantly wrapped related values as objects
in order to be passed the step definitions: &lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;AccuracyCases.feature&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cucumber&quot;&gt;&lt;pre class=&quot;language-cucumber&quot;&gt;&lt;code class=&quot;language-cucumber&quot;&gt;    Given I have configured the responses for the Github API
      | uri               | httpStatus | payloadFile                      |
      | /users/findinpath | 200        | api/github/users/findinpath.json |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;WireMockApiSteps.java&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;I have configured the responses for the Github API&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configureApiResponses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GithubApiResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; responseList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubApiResponse&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; httpStatus&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; payloadFile&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Cucumber Spring Integration&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cucumber/cucumber-jvm&quot;&gt;Cucumber&lt;/a&gt; integration with &lt;a href=&quot;https://spring.io/&quot;&gt;spring&lt;/a&gt; framework has a few tweaks are worth mentioning in the lines below.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;CommonSteps.java&lt;/code&gt; class is the only one annotated with &lt;code class=&quot;language-text&quot;&gt;@SpringBootTest&lt;/code&gt; annotation.
This is one of the limitations imposed by the Cucumber framework in the ingration
with the &lt;a href=&quot;https://spring.io/&quot;&gt;spring&lt;/a&gt; framework.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SpringDemoTestApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ActiveProfiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CommonSteps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The step definition classes, the so-called “glue” to the test code get their dependendant spring beans
injected by using the &lt;code class=&quot;language-text&quot;&gt;@Autowired&lt;/code&gt; annotation on the constructor of the step class.
Below is presented the code of &lt;code class=&quot;language-text&quot;&gt;UserSteps&lt;/code&gt; class constructor for showcasing this particularity:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserSteps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UsersEndpoint&lt;/span&gt; usersEndpoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;UserSharedScenarioData&lt;/span&gt; userSharedScenarioData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;usersEndpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; usersEndpoint&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userSharedScenarioData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userSharedScenarioData&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;WireMock&lt;/h2&gt;
&lt;p&gt;The library &lt;a href=&quot;http://wiremock.org/&quot;&gt;WireMock&lt;/a&gt; is being used in the tests in order to be able to mock the Github API. &lt;/p&gt;
&lt;p&gt;Particularly useful, in case of failure tests, was the &lt;a href=&quot;http://wiremock.org/docs/stateful-behaviour/&quot;&gt;Stateful Behavior&lt;/a&gt; for being able to simulate failures and recoveries when calling the mock API
for a specific endpoint. The relevant code from &lt;code class=&quot;language-text&quot;&gt;WireMockApiSteps.java&lt;/code&gt; for configuring the mocked
Github API responses is presented below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cucumber&quot;&gt;&lt;pre class=&quot;language-cucumber&quot;&gt;&lt;code class=&quot;language-cucumber&quot;&gt;    Given I have configured the responses for the Github API
      | uri               | httpStatus | payloadFile                      |
      | /users/findinpath | 500        |                                  |
      | /users/findinpath | 200        | api/github/users/findinpath.json |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;I have configured the responses for the Github API&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;configureApiResponses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GithubApiResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; responseList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wireMockGithubApi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWireMockServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; responsesByUriMap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; responseList
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;groupingBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    responsesByUriMap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uriResponseList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; index &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; uriResponseList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; index&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; scenarioState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Scenario&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;STARTED &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Attempt &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; for &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; scenarioName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; githubApiResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; uriResponseList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; scenarioMappingBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WireMock&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;WireMock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;urlEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inScenario&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scenarioName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenScenarioStateIs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;scenarioState&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; uriResponseList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          scenarioMappingBuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scenarioMappingBuilder
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willSetStateTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Attempt &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; for &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; uri&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OK&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WireMock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPayloadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withBodyFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPayloadFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stubFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              scenarioMappingBuilder
                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stubFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              scenarioMappingBuilder
                  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;willReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                      &lt;span class=&quot;token class-name&quot;&gt;WireMock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHttpStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also very useful has proved to be the ability to browse through the requests made
towards the WireMock server.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;cucumber&quot;&gt;&lt;pre class=&quot;language-cucumber&quot;&gt;&lt;code class=&quot;language-cucumber&quot;&gt;    And I have made 2 GET calls made towards Github API &amp;quot;/users/findinpath&amp;quot; resource
    But I have a backoff delay between GET requests 1 and 2 made towards Github API &amp;quot;/users/findinpath&amp;quot; resource&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check more details on how to browse through the requests reaching WireMock Server on the blog post&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Browse the wiremock requests &lt;a href=&quot;https://t.co/ze1neOa1CK&quot;&gt;https://t.co/ze1neOa1CK&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1194939444501069824?ref_src=twsrc%5Etfw&quot;&gt;November 14, 2019&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;h2&gt;Spring-retry&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt; topic has already been
covered in a previous post. Check it out for seeing how to layer several &lt;a href=&quot;https://en.wikipedia.org/wiki/Aspect-oriented_programming&quot;&gt;AOP&lt;/a&gt; aspects on top of each other in order to get in-depth metrics
(including retries) on how much time external API call takes. &lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Add metrics to spring-retry functionality &lt;a href=&quot;https://t.co/LYCuunrztT&quot;&gt;https://t.co/LYCuunrztT&lt;/a&gt;&lt;/p&gt;&amp;mdash; findinpath (@findinpath) &lt;a href=&quot;https://twitter.com/findinpath/status/1197271730424893443?ref_src=twsrc%5Etfw&quot;&gt;November 20, 2019&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p&gt;The spring-retry functionality is configured in the &lt;code class=&quot;language-text&quot;&gt;github-api-aop-config.xml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;pointcut&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;execution(* com.findinpath.api.github.UsersEndpoint.*(..))  &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;advisor&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;pointcut-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;advice-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;githubApiRetryAdvice&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;As already mentioned in the Cucumber &lt;a href=&quot;https://cucumber.io/docs/bdd/&quot;&gt;documentation&lt;/a&gt; the main
benefits of using this libray for testing a software project are:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Encouraging collaboration across roles to build shared understanding of the the problem to be solved&lt;/li&gt;
&lt;li&gt;Working in rapid, small iterations to increase feedback and the flow of value&lt;/li&gt;
&lt;li&gt;Producing system documentation that is automatically checked against the system’s behaviour&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nevertheless, there are some drawbacks that have to be taken into account before integrating
Cucumber in your project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Poorly written tests can easily increase test-maintenance cost&lt;/li&gt;
&lt;li&gt;Cucumber is based (at the moment of this writing) on JUnit 4. Junit 5 will be supported with the upcoming release of Cucumber 5 (see on &lt;a href=&quot;https://github.com/cucumber/cucumber-jvm/issues/1149&quot;&gt;Github&lt;/a&gt; the &lt;em&gt;Add JUnit 5 Support&lt;/em&gt; issue)&lt;/li&gt;
&lt;li&gt;Cucumber tests are executed in a single-threaded fashion. This may be an incovenience for projects
having a lot of tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all these things said, Cucumber is a very viable alternative for end to end tests because
it enables collaboration across all the roles in the project (software engineer, quality assurance, project management, requirements analyst) to build shared understanding of the the problem to be solve.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Spring retry metrics]]></title><description><![CDATA[This demo showcases how to add metrics on the 
spring-retry functionality
with the micrometer library. The project spring-retry offers both…]]></description><link>https://www.findinpath.com/spring-retry-metrics/</link><guid isPermaLink="false">https://www.findinpath.com/spring-retry-metrics/</guid><pubDate>Wed, 20 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This demo showcases how to add metrics on the
&lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt; functionality
with the &lt;a href=&quot;http://micrometer.io/&quot;&gt;micrometer&lt;/a&gt; library.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;spring-retry-metrics.jpg&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRpIAAABXRUJQVlA4IIYAAABQBACdASoUAAkAPtFWpEwoJKOiMAgBABoJagCdDBNyIVF+QttZOgUd70kwAP7yb/tz0SoPCudxsUGSUeloKOwR5CtEw6I98j2FMfcxzsXG4G/GCUc21nRAcc0IKj35dtxWnDEjlgdCw0t6WmZsjWeh5+9TdO/h1E/wVF17ak+lBuIiODUAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.125,&amp;quot;src&amp;quot;:&amp;quot;/static/ffcf6fffa0a8b2ac30216adfde042089/32a6f/spring-retry-metrics.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/ffcf6fffa0a8b2ac30216adfde042089/dd2ee/spring-retry-metrics.webp 200w,\n/static/ffcf6fffa0a8b2ac30216adfde042089/f333d/spring-retry-metrics.webp 400w,\n/static/ffcf6fffa0a8b2ac30216adfde042089/32a6f/spring-retry-metrics.webp 800w,\n/static/ffcf6fffa0a8b2ac30216adfde042089/2cf13/spring-retry-metrics.webp 1200w,\n/static/ffcf6fffa0a8b2ac30216adfde042089/1e1a6/spring-retry-metrics.webp 1600w,\n/static/ffcf6fffa0a8b2ac30216adfde042089/27ea7/spring-retry-metrics.webp 1700w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/ffcf6fffa0a8b2ac30216adfde042089/27ea7/spring-retry-metrics.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;spring-retry-metrics.jpg&amp;quot;,&amp;quot;density&amp;quot;:300,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:376,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;188&amp;apos;%3e%3cpath%20d=&amp;apos;M61%2041c-5%203-3%200-23%2037-10%2020-11%2018%205%2049%2011%2020%2014%2025%2017%2027%205%203%2054%203%2059%200%203-2%206-8%2016-28%2012-24%2013-26%2013-30-1-5-24-50-28-54l-3-2-27-1c-24%200-27%200-29%202m235%200c-35%209-53%2044-41%2078%202%205%203%206%2010%204%202%200%203-1%205-5%202-5%203-6%208-7%203-1%205-3%205-4a597%20597%200%20008-33c0-1%205%203%2010%209l9%2010%209-10%209-10%205%2015c4%2015%204%2015%207%2015%202%200%203-2%209-12l7-13%204-4%203-2-5-7a59%2059%200%2000-62-24M86%2060c-3%202-3%2037%200%2039%202%203%205%202%206%200%202-2%202-6%202-19%200-14%200-18-2-20-1-2-4-3-6%200m-18%208c-8%207-13%2023-11%2033%203%2018%2014%2029%2030%2030%2028%202%2045-34%2027-59-5-6-8-8-11-6-3%203-2%205%202%2010%2020%2021-3%2057-28%2042-14-8-16-31-4-43%204-3%204-7%201-9-3-1-2-1-6%202m119-2c-5%202-10%205-14%209-1%202-3%204-4%203v1l-1%202h-2c-1-4-4%209-4%2018s0%2010%203%2016l3%203v-3l1%204h1c0-1%202%200%204%203%2012%2012%2033%2012%2046%200%204-4%207-9%207-12l-5%203c-12%2014-34%2014-45%201l-4-4h-2l5-12%206%205c4%203%204%204%203%205-2%201%203%206%208%209%206%203%2019%202%2022-1l2-1-1-3-4-3c-2%201-3-3-4-9%200-6%200-7%203-5%201%202%2012%205%2013%205%204-2%205-15%203-17-2-1-3%201-4%208l-1%205-5-3-6-2-2-1%202-2c3%200%202-1-2-5-8-9-21-10-30-4-4%203-5%202-2-1%2010-10%2029-9%2040%201%204%204%208%205%208%202%200-2-10-11-15-13-5-3-16-4-23-2m176%2015l-9%2015-6%2012-8%202c-7%202-7%202-8%200l-4-12-3-11-7%208-8%209-7-8c-4-5-8-8-8-7l-3%2011c-3%2014-3%2015-9%2016-5%202-6%202-8%208-3%206-3%206-7%206-7%200-7%201%200%208%2014%2015%2036%2022%2056%2016%2030-7%2049-37%2044-67-2-8-2-8-5-6m-132%200l2%206c2%208%201%2019-3%2027-8%2017-26%2024-43%2019-8-2-9-2-2%201%2010%205%2025%204%2035-3%2012-7%2019-24%2016-37l-5-13&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt; offers both declarative and
imperative retry support for &lt;a href=&quot;https://spring.io/&quot;&gt;spring&lt;/a&gt; applications.&lt;/p&gt;
&lt;p&gt;Below is presented a simple code snippet to get an idea about how the imperative retry support can be
configured for a retriable functionality:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;RetryTemplate&lt;/span&gt; template &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RetryTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;maxAttempts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fixedBackoff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;retryOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;RemoteAccessException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

template&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... do something&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Coupling together spring AOP and spring-retry gives
the ability to perform retries on the public method calls that correspond to a pointcut.
This could come pretty handy when dealing with a class or a set of classes
which expose the API of an external service. &lt;/p&gt;
&lt;p&gt;Due to various reasons, every now and then it happens that an API call doesn’t succeed,
but when trying again everything works just fine.  &lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;Depositphotos_52907239_m-2015.jpg&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRmAAAABXRUJQVlA4IFQAAADwAwCdASoUAA0APtFUo0uoJKMhsAgBABoJZwDCgCIj928jEih0CrAwAP7zfmo66SpPVXY8VwFzy8aru/1Z2UaQz8RDiP7Cz5wt31uIT1jFJryhAAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.4992503748125936,&amp;quot;src&amp;quot;:&amp;quot;/static/f3786520f6a9f12cf12aa8febc385f1a/32a6f/Depositphotos_52907239_m-2015.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/f3786520f6a9f12cf12aa8febc385f1a/dd2ee/Depositphotos_52907239_m-2015.webp 200w,\n/static/f3786520f6a9f12cf12aa8febc385f1a/f333d/Depositphotos_52907239_m-2015.webp 400w,\n/static/f3786520f6a9f12cf12aa8febc385f1a/32a6f/Depositphotos_52907239_m-2015.webp 800w,\n/static/f3786520f6a9f12cf12aa8febc385f1a/fdcb7/Depositphotos_52907239_m-2015.webp 1000w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/f3786520f6a9f12cf12aa8febc385f1a/fdcb7/Depositphotos_52907239_m-2015.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;Depositphotos_52907239_m-2015.jpg&amp;quot;,&amp;quot;density&amp;quot;:300,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:534,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;267&amp;apos;%3e%3cpath%20d=&amp;apos;M162%2049l-5%201c-3%200-4%201-4%202h-1c-2-2-19%207-24%2013-8%2010-6%2011%203%202%208-7%2010-9%2021-12%209-2%2020-3%2027%200h4l1%201%204%202%203%201c1%202%208%206%208%205l1%201%203%204c5%205%208%2012%209%2019%200%205%202%208%202%204%200-9-6-21-13-29-3-2-5-5-4-5l-7-4h-2l-2-1-6-2c-3-1-17-3-18-2m-44%2014l-3%2017v8l3-1c5%200%2012-3%2012-4l4-2%205-1c1-2-4-1-9%201-3%201-6%202-8%201h-3l1-7%201-11V54l-3%209m130%2027c-6%201-13%204-12%205l-1%201c-2-2-9%2010-9%2016v4l1-4c2-9%207-15%2011-14%202%200%206-2%205-3l2-1h1l20%201%209%206c4%203%206%203%203-1-5-7-18-12-30-10m-90%201c-10%202-21%2010-18%2013%200%201%201%201%202-1%206-7%2025-11%2033-7h2l1%201c0%201%204%203%205%202l1%201v1c2%200%205%204%207%209%202%204%202%205%202%203%200-4-3-10-7-14l-4-5c0-1-3-2-6-1l-3-1c-2-1-10-2-15-1M47%20124c-4%202-6%207-5%2011%203%2011%203%2011%203%205l1-5%201%201%208-1c1-1%201%200%201%202%200%204%201%206%203%205%202-3-1-19-4-19h-1c-2-1-4-1-7%201m130%200c-4%201-5%202-1%202l4%201v1l2-1c1-1%201%200%201%202l-1%203v7c0%205%200%205%202%205l1-9v-8h4l5-1v-2h-17m25%200c-4%201-5%201-5%204l-1%203c-2%200%200%2012%201%2013%202%201%202%201%202-2l1-3%201%201c0%202%208%206%209%205s0-2-3-4l-2-4c6-1%208-3%208-7%200-6-4-8-11-6m67%201l-2%201v1l-1%201-1%202c0%202%201%202%203%202l1%201%201%201%204%201c3%203%202%206-3%207-3%200-5%202-3%203%203%202%206%201%209-2%202-2%203-3%204-1h2l1%201c-1%201-1%201%200%200l2%201c2%205%2011-2%2011-9v-3l1%204%204%207c4%203%208%204%208%202l-2-2c-2%200-6-5-6-8%200-5%205-11%209-11%202-1%202-1-1-1l-6%202-4%201-1%202c-1%201-1%201-2-1s-2-1-3%204c-1%2011-8%2014-10%205l-1-4-1-5v-3l-1%208c1%207%200%209-1%204l-2-1-1-1c1-1%200-2-3-3s-4-2-3-4c0-3%202-4%205-3l2-1c-2-2-7-1-9%202m-154-1c-3%201-3%201-1%201%204%200%206%201%205%202l1%201c2%200%201%205%200%206-4%202-6%201-6-4-1-5-3-6-3-1l-1%203v1l1%206c0%205%202%207%203%201l1-2c1%200%202%201%201%202l1%201%203%202c3%203%205%201%203-2s-2-3-1-5c3-1%203-9%201-11-1-1-5-2-8-1m18%200c-4%202-4%202-4%204l-1%203c-2%200-1%205%201%208%202%204%207%206%2011%206%203-1%203-2-1-3-3%200-8-5-7-6h1c1%201%209%200%209-1%200-2-4-3-9-3l-2-1c0-2%206-6%2010-6l3-1h-11m84%201l1%203v1h1l3%202%202%202-2%202-2%203-2%206%202-2%204-3%203-3%206-7c2-2%202-3%201-4s-2-1-4%202l-3%203-4-3-6-4h-2l2%202m103%200l-4%201-1%203c0%203%200%203-1%202-1-2-1-1-1%203%201%207%205%2012%2010%2012%203-1%203-2-1-3-5-2-7-8-3-13%203-4%204-5%206-5l3-1c0-1-4-1-8%201m14%200c-4%202-7%204-4%204v2c-3%206%204%2016%209%2015%204-1%204-4%200-4-2%200-5-2-4-4%200-1%201-2%203-2%205%201%205-2-1-3-5%200-5-1-2-5%202-2%203-3%206-3s3%200%202-1c-3-1-4-1-9%201m15-1l-2%206c0%203%200%203%202%203s3%200%202%201v1c1-1%205%203%205%205s-3%203-6%202c-3-2-5%200-2%203%202%202%209%201%2011-2%203-3%201-8-4-11-7-5-8-8-2-7%203%201%204%200%201-1h-5m15%201c-2%201-2%202-2%204v4h3l2%201v1l3%202c3%204%201%207-4%205-5-1-6%200-3%202%205%204%2014%201%2012-4l-1-3c0-2-4-6-6-7-4-1-3-5%201-5h3l-1-1-3-1-4%202m-273%202c-1%2010%200%2015%201%2015s2%200%201%201l1%201h1c0%203%207%201%2010-3%202-2%202-13%201-13-2-1-2-1-2%204-1%205-3%209-6%209s-6-8-6-13v-3l-1%202m42%2029c0%205%203%209%206%2012l1%203v1h3l1%201v2l5%201h2l-1%201h4c6%203%2026-2%2031-9%203-5%201-5-4-1-7%206-20%209-28%206l-4-1c-5-1-15-12-15-17l-1-2v3m54%201l-5%203c-2%201-2%201%201%201h4l2-2v1h1l1%207v7c2%201%204-7%204-14%200-4%200-4-3-5l-5%202m-73%2020c1%207%205%2015%209%2021l2%205v2h2c0-1%201-1%202%201l1%202%208%205c6%200%206%200%205%202l1%201%201-1c-1-2%201-1%203%200%202%203%2013%203%2022%202%2013-3%2024-9%2031-18%203-6%204-7%202-7l-2%201c0%202-13%2013-17%2015-14%207-27%207-40%203-9-4-21-13-21-17l-1-1c-1%202-5-9-6-15l-1-7-1%206m86%202l-9%203c-2%200-2%200-1%201s3%201%208-1c12-4%2012-4%209%2022l1%206c2%200%205-17%205-29%200-5%200-6-3-6l-10%204&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;This post showcases the ability to collect metrics via &lt;a href=&quot;https://micrometer.io/&quot;&gt;micrometer&lt;/a&gt;
library on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how many calls to the external API were successful&lt;/li&gt;
&lt;li&gt;how many calls to the external API were retried once, twice, three times&lt;/li&gt;
&lt;li&gt;how many calls to the external API have failed and with which exception type &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/findinpath/spring-retry-metrics&quot;&gt;spring-retry-metrics&lt;/a&gt;
is referenced throughout this blog post in order to have a concrete implementation
of the concepts presented.&lt;/p&gt;
&lt;p&gt;For the sake of a concrete example of an external API,
the project &lt;code class=&quot;language-text&quot;&gt;spring-retry-metrics&lt;/code&gt; makes use of a  slimmed mocked version
of the Github API.
No actual calls towards &lt;a href=&quot;https://api.github.com/&quot;&gt;Github API&lt;/a&gt; are made during the
tests of this project.   &lt;/p&gt;
&lt;p&gt;Below is presented a test scenario from the project for showing at work
both the spring-retry functionality (the Github repository details are
retrieved successfully, even though the first call to the Github API fails)
and the metrics of the spring-retry functionality on the methods advised
via spring AOP for the class &lt;code class=&quot;language-text&quot;&gt;com.findinpath.github.api.GithubApi&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;GithubApiRetryTest.java&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;firstApiOperationCallFails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; blogRepository &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BLOG_REPOSITORY_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/findinpath/blog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;restClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getForEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;API_URL &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;orgs/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ORGANISATION_NAME &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/repos/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; BLOG_REPOSITORY_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;GithubRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Internal server error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blogRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; repository &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; githubApi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getOrganisationRepository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ORGANISATION_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; BLOG_REPOSITORY_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;repository&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blogRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// check that the metrics are collected as expected&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; meters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; meterRegistry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMeters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; githubApiTimer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExactlyOneMeter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; API_METRIC_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;exception&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;none&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GithubApi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getOrganisationRepository&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; githubApiExceptionTimer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExactlyOneMeter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; API_METRIC_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;exception&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;IllegalStateException&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GithubApi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getOrganisationRepository&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; githubApiRetryTimer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExactlyOneMeter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; API_RETRY_METRIC_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GithubApi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getOrganisationRepository&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiExceptionTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiRetryTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1L&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiRetryTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;greaterThan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiRetryTimer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimeUnit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MILLISECONDS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;greaterThan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GithubApiRetryTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TestConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;INITIAL_BACKOFF_TIME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; githubApiRetriesCounter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getExactlyOneMeter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; API_METRIC_NAME &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;_retries&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MicrometerRetryListenerSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CLASS_TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GithubApi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MicrometerRetryListenerSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;METHOD_TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getOrganisationRepository&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MicrometerRetryListenerSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RETRY_TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MicrometerRetryListenerSupport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EXCEPTION_TAG_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;IllegalStateException&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;githubApiRetriesCounter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Spring AOP configuration&lt;/h2&gt;
&lt;p&gt;The following Spring AOP configuration serves for the following purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;times how long each Github API call takes to complete including internal
retries (in case that the first,second, … call to the API doesn’t succeed)   &lt;/li&gt;
&lt;li&gt;adds spring-retry functionality on all exposed Github API calls&lt;/li&gt;
&lt;li&gt;times how long each Github API call takes to complete&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;github-api-aop-config.xml&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;pointcut&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;execution(* com.findinpath.github.api.GithubApi.*(..))  &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--
    the githubApiRetriesIncludedTimedAdvice advice wraps
    the githubApiRetryAdvice advice and this it can provide
    timing information for the duration of the API call
    including retries
     --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;advisor&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;pointcut-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;advice-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;githubApiRetriesIncludedTimedAdvice&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;advisor&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;pointcut-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;advice-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;githubApiRetryAdvice&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--
    timing advice for each (retries are not taken into account)
    of the Github API calls.
    --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;advisor&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;pointcut-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;github-api-calls&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;advice-ref&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;githubApiTimedAdvice&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;3&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;aop:&lt;/span&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice on the configuration above that several Spring AOP advisors are stacked like ognion layers
on top of each other.
The precedence of the advisor is determined by the &lt;code class=&quot;language-text&quot;&gt;order&lt;/code&gt; parameter of the advisor. &lt;/p&gt;
&lt;h2&gt;Spring-retry enhancements&lt;/h2&gt;
&lt;p&gt;At the time of this writing, &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt;
library in the version &lt;code class=&quot;language-text&quot;&gt;1.2.4.RELEASE&lt;/code&gt; doesn’t provide the ability to retrieve information
about the invoked method (see &lt;code class=&quot;language-text&quot;&gt;org.aopalliance.intercept.MethodInvocation&lt;/code&gt;) in the spring AOP context.
See the corresponding Github &lt;a href=&quot;https://github.com/spring-projects/spring-retry/issues/119&quot;&gt;issue&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This limitation doesn’t allow to access the invoked class and method name in
the concrete implementations of the &lt;code class=&quot;language-text&quot;&gt;org.springframework.retry.listener.RetryListenerSupport&lt;/code&gt;
defined for the spring-retry’s &lt;code class=&quot;language-text&quot;&gt;RetryTemplate&lt;/code&gt;.
What this means is that when a spring-retry failure occurs on a specific API call, we only would
know in the monitoring that one of the API calls failed, but not exactly which.
For our concrete example, if Github API has several exposed endpoints, it would be surely important
to know via monitoring dashboards which one of these endpoints is causing failures in our application.&lt;/p&gt;
&lt;p&gt;In order to enhance the metrics collected for the methods advised with spring-retry functionality, the
project &lt;code class=&quot;language-text&quot;&gt;spring-retry-metrics&lt;/code&gt; has performed a little enhancement on the
default &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;spring-retry&lt;/a&gt; functionality
by adding the &lt;code class=&quot;language-text&quot;&gt;MethodInvocation&lt;/code&gt; of the advised method to the retry context.&lt;/p&gt;
&lt;p&gt;See the code source for the class
&lt;code class=&quot;language-text&quot;&gt;org.springframework.retry.interceptor.MethodInvocationRetryOperationsInterceptor&lt;/code&gt;
for more details.&lt;/p&gt;
&lt;h2&gt;Run the project&lt;/h2&gt;
&lt;p&gt;Run the command&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;for executing the tests from this project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[End to end tests for KafkaListener]]></title><description><![CDATA[This post shows how the 
KafkaListener
belonging to the spring-kafka library can
be tested in an end-to-end fashion for both json and avro…]]></description><link>https://www.findinpath.com/kafkalistener-end-to-end-tests/</link><guid isPermaLink="false">https://www.findinpath.com/kafkalistener-end-to-end-tests/</guid><pubDate>Mon, 18 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post shows how the
&lt;a href=&quot;https://docs.spring.io/spring-kafka/api/org/springframework/kafka/annotation/KafkaListener.html&quot;&gt;KafkaListener&lt;/a&gt;
belonging to the &lt;a href=&quot;https://spring.io/projects/spring-kafka&quot;&gt;spring-kafka&lt;/a&gt; library can
be tested in an end-to-end fashion for both json and avro mesages.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;apache-kafka.png&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRn4AAABXRUJQVlA4IHIAAADwAwCdASoUAAoAPtFUo0uoJKMhsAgBABoJaQAD4wIHn1RIL7fd/qYAAP5bbnhB2flzY9V51cHsko+GmXqZuRRRRgJ7hs/C/jCAEQr5MNjJ58QTb2sovl1Vyh8X7s9Dm4kTIFihJIzsRzCapnkTx+kQAAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.0255009107468123,&amp;quot;src&amp;quot;:&amp;quot;/static/5519eab4d855481e702947ae48bea85a/32a6f/apache-kafka.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/5519eab4d855481e702947ae48bea85a/dd2ee/apache-kafka.webp 200w,\n/static/5519eab4d855481e702947ae48bea85a/f333d/apache-kafka.webp 400w,\n/static/5519eab4d855481e702947ae48bea85a/32a6f/apache-kafka.webp 800w,\n/static/5519eab4d855481e702947ae48bea85a/59f06/apache-kafka.webp 1112w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/5519eab4d855481e702947ae48bea85a/59f06/apache-kafka.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;apache-kafka.png&amp;quot;,&amp;quot;density&amp;quot;:300,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:395,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;197&amp;apos;%3e%3cpath%20d=&amp;apos;M29%2012C8%2020%208%2049%2028%2057c4%202%204%202%204%207v6l-6%202c-26%209-25%2047%201%2057l5%201v6c0%205%200%205-4%207-20%208-20%2037%200%2045%2021%207%2040-14%2031-34-3-5-9-10-14-12-2%200-2-1-2-6v-6l4-1%209-5%205-4%204%203%204%203v7c1%2031%2048%2032%2048%200%200-20-22-32-38-20l-3%203-5-3-4-3V90l4-3%205-2%203%202c17%2012%2038%200%2038-19%200-32-47-32-48-1v8l-4%202-5%203-4-3c-2-2-6-5-8-5-4-2-4-2-5-7%200-6%200-6%203-7%205-3%2010-7%2013-12%209-20-10-41-30-34m1%2014c-6%204-6%2014%201%2019s18-2%2017-11-10-13-18-8m224%2031c-6%204-7%207-8%2023%200%2012%200%2013-2%2013-3%200-3%2010%200%2011%202%200%202%201%202%2019l1%2020h11v-39h13V93h-12l-1-11c0-15%202-18%2011-15%203%202%203-10-1-11-4-2-10-1-14%201M88%2057c-6%203-8%2013-3%2018%208%209%2023%200%2019-12-3-6-9-8-16-6m51%2043v43h12v-10l1-10%209%209%208%2011h8c6%200%207%200%206-1l-11-15-11-13%2011-11%2011-10h-16l-8%208-8%207V56h-12v44m140-1l1%2044h11v-10l1-10%209%209%208%2011h15l-11-15-12-14%2011-11%2011-10h-16l-8%208-8%207V56h-12v43M32%2083c-11%203-16%2014-11%2025%208%2017%2034%2010%2033-9-1-11-11-19-22-16m170%209c-28%207-23%2052%204%2052%205%200%2011-2%2014-5%202-1%202-1%202%201%200%203%200%203%206%203h6V93h-6c-6%200-6%200-6%203v2l-3-2c-5-4-11-6-17-4m139%200c-17%204-24%2030-11%2044%208%209%2022%2011%2030%203%202-1%202-1%202%201%200%203%200%203%206%203h6V93h-6c-6%200-6%200-6%203v2l-3-2c-6-4-11-6-18-4m-139%2012c-13%209-6%2032%208%2030%206-1%209-4%2011-9%206-14-7-29-19-21m140%200c-6%204-9%2013-5%2021%205%2011%2019%2012%2024%201%207-15-6-30-19-22M88%20122c-5%203-8%2012-4%2017%207%2011%2025%202%2020-11-3-6-9-8-16-6m298%204c-9%203-7%2017%203%2017%203%200%208-5%208-9%200-5-6-10-11-8m-2%203c-3%202-3%206-1%209%204%206%2012%203%2012-4%200-6-6-9-11-5M30%20156c-4%203-6%209-4%2013%204%208%2013%2010%2019%203%209-9-5-24-15-16&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://spring.io/projects/spring-kafka&quot;&gt;spring-kafka&lt;/a&gt; comes with a few testing
utilities, but it doesn’t provide any utilities for testing the methods
annotated with the &lt;a href=&quot;https://docs.spring.io/spring-kafka/api/org/springframework/kafka/annotation/KafkaListener.html&quot;&gt;KafkaListener&lt;/a&gt;
annotation. Moreover, it makes use of an embedded Apache Kafka broker, instead of
dockerized Apache Kafka container image artifacts. &lt;/p&gt;
&lt;p&gt;This post concentrates on the concepts implemented in the project &lt;a href=&quot;https://github.com/findinpath/kafkalistener-e2e-test&quot;&gt;kafkalistener-e2e-test&lt;/a&gt; for dealing with end-to-end-testing for the methods annotated with the
&lt;a href=&quot;https://docs.spring.io/spring-kafka/api/org/springframework/kafka/annotation/KafkaListener.html&quot;&gt;KafkaListener&lt;/a&gt; annotation.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library
is employed for spawning before the tests a complete &lt;a href=&quot;https://www.confluent.io/&quot;&gt;Confluent&lt;/a&gt; ecosystem
of &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; container images for artifacts related to Apache Kafka:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Apache Zookeeper&lt;/li&gt;
&lt;li&gt;Confluent Schema Registry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using versions for the container images that correspond to the Apache Kafka
ecosystem from the production environment, there is simulated an environment
which is very close to the one running in the  production.
This particularity gives a high relevance to the integration/ end-to-end tests
for the kafka listener functionality.  &lt;/p&gt;
&lt;p&gt;It is very important to have the ability to perform end-to-end tests in a throwaway
dockerized environment because there can be executed with a high certainty common
scenarios that the kafka listener service is supposed to handle as part of its contract.&lt;/p&gt;
&lt;h2&gt;End to End Test setup&lt;/h2&gt;
&lt;p&gt;As mentioned previously, by employing the &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library
an entire Apache Kafka ecosystem will be spawned at the beginning of the tests.
Check out the implementation related to testcontainers in the project &lt;a href=&quot;https://github.com/findinpath/kafkalistener-e2e-test/tree/master/src/test/java/com/findinpath/kafka/testcontainers&quot;&gt;kafkalistener-e2e-test&lt;/a&gt;
for seeing how the Apache Kafka ecosystem artifacts are configured to work together
for setting up the testing enviroment for the end to end tests. &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//KafkaTestContainers.java&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaTestContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;network &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Network&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;zookeeperContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kafkaContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getZookeeperConnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;schemaRegistryContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SchemaRegistryContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getZookeeperConnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;Startables&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deepStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; kafkaContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; schemaRegistryContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once the Apache Kafka ecosystem is up and running, the topics necessary for the end-to-end
tests are created and the &lt;a href=&quot;https://avro.apache.org/&quot;&gt;AVRO&lt;/a&gt; types are registered
to Confluent Schema Registry docker container. &lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// KafkaDockerConfiguration.java&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaTestContainers&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;kafkaTestContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${kafka.userBookmarkEventsJson.topic}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userBookmarkEventJsonTopic&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${kafka.userBookmarkEventsAvro.topic}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; userBookmarkEventAvroTopic
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; kafkaTestContainers &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaTestContainers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;createTopics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kafkaTestContainers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userBookmarkEventJsonTopic&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userBookmarkEventAvroTopic&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;registerSchemaRegistryTypes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kafkaTestContainers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSchemaRegistryContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; kafkaTestContainers&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After this setup, the rest of the spring beans from Spring’s dependency
injection container (including the kafka listeners) are initialized and at this time
there can be executed end-to-end tests.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// UserBookmarkEventJsonListenerTest.java&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@MockBean&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserBookmarkEventService&lt;/span&gt; userBookmarkEventService&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// GIVEN&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; userId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UUID&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://findinpath.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;UserBookmarkEvent&lt;/span&gt; userBookmarkEvent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserBookmarkEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toEpochMilli&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// WHEN&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;writeToTopic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userBookmarkEventJsonTopic&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userBookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// THEN&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; argumentCaptor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ArgumentCaptor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserBookmarkEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userBookmarkEventService&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ingest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;argumentCaptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;capture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;UserBookmarkEvent&lt;/span&gt; capturedUserBookmarkEvent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; argumentCaptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userBookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;capturedUserBookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The demo test is quite straightforward, because it only concentrates to make sure that the service
responsible of the business logic of handling the message is being called.
Nevertheless, such a test ensures that the correct service is being called in the kafka listener
and also that the message sent to the kafka topic is correctly deserialized. &lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;Compared to the tests in which the tests in which the
&lt;a href=&quot;https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html&quot;&gt;KafkaConsumer&lt;/a&gt;
can be manipulated directly in order to be able to reset the consumer offset
after each test, &lt;a href=&quot;https://spring.io/projects/spring-kafka&quot;&gt;spring-kafka&lt;/a&gt; hides the
consumer instance inside the class &lt;code class=&quot;language-text&quot;&gt;org.springframework.kafka.listener.KafkaMessageListenerContainer.listenerConsumer&lt;/code&gt;
with a &lt;code class=&quot;language-text&quot;&gt;private&lt;/code&gt; access.
Even with extra motivation, when accessing the private field via Java Reflection, for resetting
its offset, the operations on it will fail because multi-threaded
access on the consumer is not supported (see &lt;code class=&quot;language-text&quot;&gt;org.apache.kafka.clients.consumer.KafkaConsumer.acquire&lt;/code&gt; method).&lt;/p&gt;
&lt;p&gt;But even with the limitation of not being able to reset the consumer offset,
it is still quite useful to ensure the fact that the right service is being called to handle
Kafka message sent over the topic (otherwise said, regression test).&lt;/p&gt;
&lt;h2&gt;Source code&lt;/h2&gt;
&lt;p&gt;The proof of concept project &lt;a href=&quot;https://github.com/findinpath/kafkalistener-e2e-test&quot;&gt;kafkalistener-e2e-test&lt;/a&gt; offers
two end-to-end sample tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;com.findinpath.kafka.listener.UserBookmarkEventAvroListenerTest&lt;/code&gt; : for
testing the consumption of messages serialized in &lt;a href=&quot;https://avro.apache.org/&quot;&gt;AVRO&lt;/a&gt; format&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;com.findinpath.kafka.listener.UserBookmarkEventJsonListenerTest&lt;/code&gt; : for
testing the consumption of messages serialized in &lt;code class=&quot;language-text&quot;&gt;JSON&lt;/code&gt; format &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run the command&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;for executing the tests from this project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Timing spring-data-cassandra repository methods]]></title><description><![CDATA[This post showcases how to make use of Spring AOP / AspectJ for timing the
duration of both synchronous and asynchrnous methods belonging to…]]></description><link>https://www.findinpath.com/spring-data-cassandra-repository-methods-timing/</link><guid isPermaLink="false">https://www.findinpath.com/spring-data-cassandra-repository-methods-timing/</guid><pubDate>Fri, 15 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post showcases how to make use of Spring AOP / AspectJ for timing the
duration of both synchronous and asynchrnous methods belonging to
&lt;a href=&quot;https://spring.io/projects/spring-data-cassandra&quot;&gt;spring data cassandra&lt;/a&gt; repositories.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;harry-sandhu-FpYoDqGGI4A-unsplash.jpg&quot; title=&quot;Photo by Harry Sandhu on Unsplash&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRnIAAABXRUJQVlA4IGYAAADQAwCdASoUAA0APtFUo0uoJKMhsAgBABoJZwDE2BxMf6GmftL0wAAA/vDfFf8xXduGNo61BoZ8TVaduQfoQSOzVw8gGGaZzlDfJrwysx0g1E/4YicczT4swUn9Ar6XffjfWUKNEAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.5,&amp;quot;src&amp;quot;:&amp;quot;/static/2e5f4bcf2028f83b6405d79db94911d5/32a6f/harry-sandhu-FpYoDqGGI4A-unsplash.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/2e5f4bcf2028f83b6405d79db94911d5/dd2ee/harry-sandhu-FpYoDqGGI4A-unsplash.webp 200w,\n/static/2e5f4bcf2028f83b6405d79db94911d5/f333d/harry-sandhu-FpYoDqGGI4A-unsplash.webp 400w,\n/static/2e5f4bcf2028f83b6405d79db94911d5/32a6f/harry-sandhu-FpYoDqGGI4A-unsplash.webp 800w,\n/static/2e5f4bcf2028f83b6405d79db94911d5/2cf13/harry-sandhu-FpYoDqGGI4A-unsplash.webp 1200w,\n/static/2e5f4bcf2028f83b6405d79db94911d5/ffdbf/harry-sandhu-FpYoDqGGI4A-unsplash.webp 1350w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/2e5f4bcf2028f83b6405d79db94911d5/ffdbf/harry-sandhu-FpYoDqGGI4A-unsplash.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;harry-sandhu-FpYoDqGGI4A-unsplash.jpg&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:533,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;267&amp;apos;%3e%3cpath%20d=&amp;apos;M0%20134v133h148v-9a31188%2031188%200%2001-4-249V0H0v134M168%2012l1%2034v21l7%201a7574%207574%200%200099%2020l-1-3c-1-2-3-31-3-57V14l-10-3-27-6-17-4-25-1h-24v12m193-5c29%208%2029%208%2029%206l-1-2c-2%200-2-1-2-4%200-7%202-7-27-7h-26l27%207M96%2018c-4%203-5%206-1%209v2c-2%202-1%203%202%203l3-1-1-1-1-1%202-1%201-6c0-8%200-8-5-4m203%204l4%2059%201%2012%2010%203%2082%2016-1-4c-3-7-4-53-1-55%201-1%201-2-1-3-3-1-3-2-3-5v-3l-25-6a307040%20307040%200%2000-66-14m42%20183l-59%2019-28-3a1167%201167%200%2000-71-8v44l-15%205-15%205h247v-82l-59%2020m-157%2021l1%207%2028%204c2%200%202-1%202-5l-2-7c-1-1-10-3-22-4h-7v5m8%2021l-2%207%2022-7c2-1%201-4-2-4h-9l-7-1-2%205&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;h2&gt;Metrics in the data access layer of the application&lt;/h2&gt;
&lt;p&gt;Having an overview on the &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt; for how each of the operations
exposed by the &lt;em&gt;Data Access Layer&lt;/em&gt; performs is a tremendous improvement
for monitoring and tackling production issues on a live application.&lt;/p&gt;
&lt;p&gt;To give a pretty common example, say that with a new release of the application
one &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; statement from a repository class gets changed that causes the
application to run normal on the testing environment, but on the productive environment
where there’s much more data to perform much worse than it did before.
In that case after the release, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Quality_of_service&quot;&gt;Quality of Service&lt;/a&gt;
will drop to a significant amount, and the engineer responsible for the release
will need to investigate what has changed for the worse since the last release.&lt;/p&gt;
&lt;p&gt;This is where nowadays metric collector services (like &lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt;)
shine and provide a lot of meaningful answers in such situations.
It would obviously be very helpful to see in &lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt;
or even receive a &lt;a href=&quot;https://prometheus.io/docs/practices/alerting/&quot;&gt;Prometheus alert&lt;/a&gt;
whether an operation or a set of operations performed by the application performs
much more slowly than before.&lt;/p&gt;
&lt;p&gt;Seeing a continuous increase of average duration or
repeated failures on the data access layer for a
certain operation from a repository in the data access layer of the application
since the last release would help to identify very fast
the problem and subsequently revert the change or fix the problem.&lt;/p&gt;
&lt;h2&gt;Timing the methods of spring data repositories&lt;/h2&gt;
&lt;p&gt;When using the &lt;code class=&quot;language-text&quot;&gt;io.micrometer.core.annotation.Timed&lt;/code&gt; annotation that comes
with &lt;a href=&quot;https://micrometer.io/&quot;&gt;micrometer&lt;/a&gt; library in a
&lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;spring-boot&lt;/a&gt; application
there is obtained the emission of timing metrics for the methods annotated
with this annotation.&lt;/p&gt;
&lt;p&gt;When annotating the class with the &lt;code class=&quot;language-text&quot;&gt;@Timed&lt;/code&gt; annotation, all the public methods
of the spring bean class will be timed. &lt;/p&gt;
&lt;p&gt;This seems like a good solution, but very often it happens that through
omission/refactoring in the code, the annotation is being removed by mistake
from the class and therefore the valuable metrics and not emitted anymore
to the micrometer’s &lt;code class=&quot;language-text&quot;&gt;MeterRegistry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another drawback of the &lt;code class=&quot;language-text&quot;&gt;io.micrometer.core.annotation.Timed&lt;/code&gt; annotation
(and the &lt;code class=&quot;language-text&quot;&gt;io.micrometer.core.aop.TimedAspect&lt;/code&gt; which takes care of intercepting
the methods annotated with &lt;code class=&quot;language-text&quot;&gt;Timed&lt;/code&gt; annotation) is that it doesn’t have support
for dealing with asynchronous methods.&lt;/p&gt;
&lt;p&gt;Through &lt;a href=&quot;https://spring.io/projects/spring-data-cassandra&quot;&gt;spring data cassandra&lt;/a&gt; utility classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;org.springframework.data.cassandra.core.CassandraTemplate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;org.springframework.data.cassandra.core.AsyncCassandraTemplate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;there can be executed statements on the Cassandra database in both fashions,
synchronous and asynchronous.&lt;/p&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/findinpath/spring-data-cassandra-repository-methods-timing&quot;&gt;spring-data-cassandra-repository-methods-timing&lt;/a&gt;
developed a custom &lt;a href=&quot;https://en.wikipedia.org/wiki/AspectJ&quot;&gt;AspectJ&lt;/a&gt; class
for taking care of timing all the public methods exposed by the spring data repository bean
classes from the project.&lt;/p&gt;
&lt;p&gt;Spring data repository classes are considered any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the class is annotated with the &lt;code class=&quot;language-text&quot;&gt;@org.springframework.stereotype.Repository&lt;/code&gt; annotation&lt;/li&gt;
&lt;li&gt;the class implements &lt;code class=&quot;language-text&quot;&gt;org.springframework.data.repository.Repository&lt;/code&gt; class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a similar fashion to the  micrometer’s &lt;code class=&quot;language-text&quot;&gt;io.micrometer.core.aop.TimedAspect&lt;/code&gt;, the
AspectJ &lt;code class=&quot;language-text&quot;&gt;com.findinpath.aop.RepositoryTimerAspect&lt;/code&gt; class from the proof of concept project intercepts
the methods of spring data repository beans from the project and emits timer information
to micrometer’s &lt;code class=&quot;language-text&quot;&gt;io.micrometer.core.instrument.MeterRegistry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The information get emitted to the micrometer metric named &lt;code class=&quot;language-text&quot;&gt;repository&lt;/code&gt; with the following tags:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; className&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;successful&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; successful&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;className&lt;/code&gt; can be on of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the interface corresponding to the repository -
see &lt;code class=&quot;language-text&quot;&gt;com.findinpath.repository.ConfigRepository&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the concrete class annotated with &lt;code class=&quot;language-text&quot;&gt;@org.springframework.stereotype.Repository&lt;/code&gt;
annotation - see &lt;code class=&quot;language-text&quot;&gt;com.findinpath.repository.UserBookmarkRepository&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;successful&lt;/code&gt; flag states whether the call was or not successfully executed.&lt;/p&gt;
&lt;h3&gt;Dealing with asynchronous methods&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, there can be executed asynchronous methods on Cassandra.
The implementation of the asynchronous methods with spring data cassandra
is straightforward (see &lt;code class=&quot;language-text&quot;&gt;com.findinpath.repository.UserBookmarkRepository&lt;/code&gt; for more details):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ListenableFuture&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserBookmark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;UserBookmark&lt;/span&gt; userBookmark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; asyncCassandraOperations&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userBookmark&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A little trick needs therefor to be employed in the &lt;code class=&quot;language-text&quot;&gt;AspectJ&lt;/code&gt; &lt;code class=&quot;language-text&quot;&gt;RepositoryTimerAspect&lt;/code&gt;class
for timing such non-blocking methods. After retrieving the result from the target method
invocation, a callback is being registered for emiting the metrics in both success and
failure cases after the completion of the method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; asyncResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ListenableFuture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; proceedingJoinPoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;proceed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    asyncResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;emitMetrics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meterRegistry&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sample&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; className&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        ex &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;emitMetrics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meterRegistry&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sample&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; className&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; methodName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; asyncResult&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Spring AOP&lt;/h2&gt;
&lt;p&gt;In case that it is needed for reference a Spring AOP implementation
for timing the spring data repositories,
the project source code provides also such an implementation, although commented,
in order to avoid causing issues with the &lt;code class=&quot;language-text&quot;&gt;AspectJ&lt;/code&gt; `RepositoryTimerAspect.&lt;/p&gt;
&lt;p&gt;See &lt;code class=&quot;language-text&quot;&gt;com.findinpath.config.RepositoryTimerConfiguration&lt;/code&gt; for details.&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/findinpath/spring-data-cassandra-repository-methods-timing&quot;&gt;spring-data-cassandra-repository-methods-timing&lt;/a&gt;
comes with a &lt;code class=&quot;language-text&quot;&gt;com.findinpath.DemoTest&lt;/code&gt; class which contains test scenarios
for timing both kinds of spring data repositories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the class is annotated with the &lt;code class=&quot;language-text&quot;&gt;@org.springframework.stereotype.Repository&lt;/code&gt; annotation&lt;/li&gt;
&lt;li&gt;the class implements &lt;code class=&quot;language-text&quot;&gt;org.springframework.data.repository.Repository&lt;/code&gt; class&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and also both synchronous and asynchronous methods.&lt;/p&gt;
&lt;p&gt;Below are the measurements logged from one of the runs of the &lt;code class=&quot;language-text&quot;&gt;DemoTest&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ConfigRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;176&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; ms
The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ConfigRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;findById&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;22&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; ms&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserBookmarkRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;save&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt; ms
The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserBookmarkRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;saveAsync&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;33&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; ms
The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserBookmarkRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;findLatestBookmarks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; ms
The timer MeterId&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;repository&apos;&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;class&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserBookmarkRepository&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;findLatestBookmarksAsync&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;,tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;successful&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; has max value: &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; ms and mean value: &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt; ms&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The test is being executed against a throwaway Cassandra database container (through the
help of the genius team formed from &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; and the
&lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library).&lt;/p&gt;
&lt;p&gt;In order to keep the demo project simple to go through, no setup has been
made for a web application that would expose operations to interact with Cassandra
database. This is why in order to test the concepts showcased by the project,
only the  &lt;code class=&quot;language-text&quot;&gt;com.findinpath.DemoTest&lt;/code&gt; class is provided.&lt;/p&gt;
&lt;p&gt;Simply use &lt;code class=&quot;language-text&quot;&gt;mvn clean install&lt;/code&gt; for trying out the project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Cassandra schema migrations on application startup]]></title><description><![CDATA[This post shows how to setup a spring boot
project in order to have the Cassandra CQL schema migrations securely performed on application…]]></description><link>https://www.findinpath.com/cassandra-migration-spring-boot/</link><guid isPermaLink="false">https://www.findinpath.com/cassandra-migration-spring-boot/</guid><pubDate>Thu, 14 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post shows how to setup a &lt;a href=&quot;https://spring.io/projects/spring-boot&quot;&gt;spring boot&lt;/a&gt;
project in order to have the Cassandra CQL schema migrations securely performed on application startup
through the help of the &lt;a href=&quot;https://github.com/patka/cassandra-migration&quot;&gt;cassandra-migration&lt;/a&gt;
library.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;julia-craice-faCwTallTC0-unsplash.jpg&quot; title=&quot;Flying colhereiros - Julia Craice on Unsplash&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRlwAAABXRUJQVlA4IFAAAADwAgCdASoUAA0APtFWpEuoJKOhsAgBABoJaQDG9DbQAAD+8UC/DJLEcviXaDBrwHxe+5h9ePBxbkVHaDNoNlezgZvuNTsbbTqUtJBFz0AAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.5001791472590469,&amp;quot;src&amp;quot;:&amp;quot;/static/323f105014bafc11d83d31aa9e166670/32a6f/julia-craice-faCwTallTC0-unsplash.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/323f105014bafc11d83d31aa9e166670/dd2ee/julia-craice-faCwTallTC0-unsplash.webp 200w,\n/static/323f105014bafc11d83d31aa9e166670/f333d/julia-craice-faCwTallTC0-unsplash.webp 400w,\n/static/323f105014bafc11d83d31aa9e166670/32a6f/julia-craice-faCwTallTC0-unsplash.webp 800w,\n/static/323f105014bafc11d83d31aa9e166670/2cf13/julia-craice-faCwTallTC0-unsplash.webp 1200w,\n/static/323f105014bafc11d83d31aa9e166670/1e1a6/julia-craice-faCwTallTC0-unsplash.webp 1600w,\n/static/323f105014bafc11d83d31aa9e166670/b08cb/julia-craice-faCwTallTC0-unsplash.webp 4187w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/323f105014bafc11d83d31aa9e166670/b08cb/julia-craice-faCwTallTC0-unsplash.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;julia-craice-faCwTallTC0-unsplash.jpg&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:533,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;267&amp;apos;%3e%3cpath%20d=&amp;apos;M347%207c-2%202-2%209-1%2011%202%205%201%206-7%206-6%200-6%200-2%201h5c2-1%205%201%204%203-1%203%200%206%203%206%202%200%205-4%206-7l4-1c4%200%204-1-1-3-3%200-4-1-4-3l-1-4-1-5c-1-3-4-6-5-4m-36%2021l1%206c1%204%200%206-5%205l-5%201%205%201h6l-1%205c0%205%201%209%203%208%202%200%206-9%206-11s1-2%204-2h5l-5-2c-4-1-5-2-5-4%200-3-4-10-7-10-2-1-2%200-2%203m-35%2024c0%203-2%204-6%205h-5l5%201c5%200%205%200%203%207%200%204%201%2010%202%209v-1l1-1%201-2%201-2%201-1c-1%200%200-2%202-3l2-4%204-1h5l-4-1-4-3c0-2-2-4-6-4-2%200-3%200-2%201m-60%2018c-2%200-3%204-2%206%202%203%200%205-5%205l-4%201h6c5%200%205%200%205%202-3%208-1%2017%201%2015%201-1%202-3%201-4l2-4c1-1%202-2%201-3l1-1%201-2c0-2%201-3%205-3h4l-4-1c-4-1-5-2-5-5-1-3-5-7-7-6m-60%2023c-2%202-3%203-2%204v11l1%201%203%204c4%205%207%205%205%200-1-3-1-5%203-5%203%200%205-2%202-2-6%200-6-1-6-9%200-7-2-8-6-4M44%20128c2%203%201%206-2%208-2%201-2%201%200%201l2%201%204%207%205%205v-5c0-4%200-6%202-7l2-2h-2l-2-3c0-3-4-6-8-6-2%200-2%200-1%201m48%2021l1%203c2%202-1%204-5%204h-4l5%201h5l-1%206c-1%2012%202%2013%208%201%203-7%203-7%206-6%203%200%203%200%200-2l-5-3c0-5-8-8-10-4m59%209c-1%203-2%206-1%209l-1%206c-2%202%200%203%209%203h7l-4-2c-4-2-4-1-5-6l-1-6c-1-2-1-3%201-4v-3c-2-2-3-1-5%203m32%2017v8c3%208%202%209-4%209h-4l5%201h5v6c0%2012%208%208%208-4l4-2%204-1h-3c-5-2-6-3-6-5%200-3-5-12-6-13l-3%201m37%2032l-2%204%201%202v2c-1%202%201%209%203%2010l3-6c3-5%204-6%206-6h4l-4-1-4-4c-1-3-1-3-3-2h-3c-1-1-1-1-1%201m52%207v8c1%203%201%203-1%205s-2%202%200%202c2%201%202%201%201%204l-1%209c0%205%200%205%202%204l3-3%202-4%201-3v-1l1-3c0-1%201-2%203-2%203-1%203-2%200-3-1%200-2-1-2-4-2-7-7-12-9-9m41%2022a621%20621%200%2001-2%2012c0%204-1%208-3%209s-1%201%204%201c10%201%2020%200%2011-1l-4-2v-7l-2-7v-2c0-2-4-5-4-3&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;Database migrations are a topic that the software engineers have long struggled with.
After doing a &lt;code class=&quot;language-text&quot;&gt;git pull&lt;/code&gt; for the latest changes on the application code, it happened quite often
that the application didn’t work anymore on the local machine simply because a database
table had to be created or altered manually on the local database.&lt;/p&gt;
&lt;p&gt;Not that seldom production application crashes occurred during deployments due to the fact that the
database engineer didn’t run all the database scripts corresponding to the new application release. &lt;/p&gt;
&lt;p&gt;In order to address the before mentioned problems, in the relational database world,
the &lt;code class=&quot;language-text&quot;&gt;SQL&lt;/code&gt; schema migrations have been integrated in the application code and the new
schema migrations are executed on the database while the application is being started. &lt;/p&gt;
&lt;p&gt;The libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://flywaydb.org/&quot;&gt;flyway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.liquibase.org/&quot;&gt;liquibase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;are used extensively in the Java world for performing database schema migrations
on the application startup.
As a side note, these libraries offer the possibility to dryrun and generate all the &lt;code class=&quot;language-text&quot;&gt;SQL&lt;/code&gt; scripts
relevant for the new productive application release into a .sql file which can be then reviewed and
executed manually by a database system administrator.&lt;/p&gt;
&lt;p&gt;Due to the fact that Cassandra database is not a relational database, flyway doesn’t offer support
for performing schema migrations on top of Cassandra.can’t be
There is actually an &lt;a href=&quot;https://github.com/flyway/flyway/issues/823&quot;&gt;Github issue&lt;/a&gt; that dates since
2014 on flyway, which eventually sparked the creation of the
&lt;a href=&quot;https://github.com/patka/cassandra-migration&quot;&gt;cassandra-migration&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;This post shows how to integrate the cassandra-migration library in a spring boot application
in order not only to perform the schema migrations on the application startup,
but also when the test environment is being setup.&lt;/p&gt;
&lt;p&gt;In this way, both the productive and the test code use the same database schema.   &lt;/p&gt;
&lt;h2&gt;Separation of concerns&lt;/h2&gt;
&lt;p&gt;As already mentioned, the schema migrations are performed only during application startup phase.
This kind of database statements (&lt;code class=&quot;language-text&quot;&gt;CREATE&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ALTER&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;DROP&lt;/code&gt;) are to be performed by a database
user which obviously is thought only for schema migrations purposes.&lt;/p&gt;
&lt;p&gt;On the other hand, when thinking of security concerns for the application, the
database user required for executing database statements required for
covering the application functionality (&lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt;) should have
more restrictive permissions. In this manner, there can be avoided catastrophic
changes on the database (&lt;code class=&quot;language-text&quot;&gt;DROP&lt;/code&gt; , &lt;code class=&quot;language-text&quot;&gt;TRUNCATE&lt;/code&gt;) that can occur through software bugs
or application attacks.&lt;/p&gt;
&lt;p&gt;Therefor it makes very much sense to have two different Cassandra database users
in the application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a migration user dealing solely with &lt;code class=&quot;language-text&quot;&gt;CQL&lt;/code&gt; migrations on the application startup&lt;/li&gt;
&lt;li&gt;an application user dealing with the &lt;code class=&quot;language-text&quot;&gt;CQL&lt;/code&gt; queries required for offering the
functionality exposed by the application&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Spring-boot integration&lt;/h2&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration&lt;/code&gt;
and &lt;code class=&quot;language-text&quot;&gt;org.cognitor.cassandra.migration.spring.CassandraMigrationAutoConfiguration&lt;/code&gt; spring boot
auto-configuration classes are based on the presumption that only one database user
is used for both application and migration database queries.
For this reason, they need to be excluded from being taken into consideration when initializing
the spring dependency injection bean container.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootApplication&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exclude &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CassandraDataAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CassandraMigrationAutoConfiguration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DemoApplication&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The project sample code provided with this post creates explicitly two different clusters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;applicationCluster&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;migrationCluster&lt;/code&gt;    &lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@Profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@CassandraMigrationCluster&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;migrationCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.contact.points}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; contactPoints&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.migration.username}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.migration.password}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;PoolingOptions&lt;/span&gt; poolingOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PoolingOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConnectionsPerHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HostDistance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCAL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConnectionsPerHost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HostDistance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REMOTE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxRequestsPerConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HostDistance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LOCAL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setMaxRequestsPerConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HostDistance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;REMOTE&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createCassandraClusterFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolingOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contactPoints&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;applicationCluster&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applicationCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.contact.points}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; contactPoints&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.application.username}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.application.password}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;PoolingOptions&lt;/span&gt; poolingOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PoolingOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createCassandraClusterFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolingOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contactPoints&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;applicationCluster&lt;/code&gt; is being used for building the &lt;code class=&quot;language-text&quot;&gt;CassandraTemplate&lt;/code&gt;
bean which will subsequently power all the spring data cassandra
repository classes.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraSessionFactoryBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cassandraSessionFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Qualifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;applicationCluster&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; applicationCluster&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@Value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;${cassandra.application.keyspaceName}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt; keyspaceName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; cluster &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationCluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;CassandraSessionFactoryBean&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraSessionFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cluster&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setKeyspaceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keyspaceName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cassandraConverter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSchemaAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;SchemaAction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NONE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraOperations&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cassandraTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;CassandraSessionFactoryBean&lt;/span&gt; cassandraSessionFactoryBean&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraTemplate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraSessionFactoryBean&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;migrationCluster&lt;/code&gt; cluster on the other hand is being used only during
the application startup for performing the Cassandra schema migrations
with the help of &lt;a href=&quot;https://github.com/patka/cassandra-migration&quot;&gt;cassandra-migration&lt;/a&gt;
library.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@EnableConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CassandraMigrationConfigurationProperties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraMigrationConfiguration&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraMigrationAutoConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;initMethod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;migrate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@ConditionalOnMissingBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MigrationTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MigrationTask&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;migrationTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token annotation punctuation&quot;&gt;@CassandraMigrationCluster&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ObjectProvider&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; migrationClusterProvider&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;Cluster&lt;/span&gt; migrationCluster &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; migrationClusterProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getIfAvailable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;migrationTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;migrationCluster&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the configurations listed above the secure integration of the
&lt;a href=&quot;https://github.com/patka/cassandra-migration&quot;&gt;cassandra-migration&lt;/a&gt; library in
the spring boot application should be almost complete.&lt;/p&gt;
&lt;p&gt;All what is left, are the actual &lt;code class=&quot;language-text&quot;&gt;CQL&lt;/code&gt; migration files that come into the directory denoted by
&lt;code class=&quot;language-text&quot;&gt;${cassandra.migration.scriptLocation}&lt;/code&gt; environment property.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt; that each migration file should contain exactly one &lt;code class=&quot;language-text&quot;&gt;CQL&lt;/code&gt; statement. Therefor, in case that
an application feature needs more database changes, they must be split to different files.
Nevertheless, the naming of the migration files is pretty liberal, so the files can look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;0080_awesome_feature_create_bookmarks_table.cql&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;0081_awesome_feature_alter_users_table.cql&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A word about testing&lt;/h2&gt;
&lt;p&gt;It would be ideal to have a throw-away database instance which has the same schema
as the productive database that is being made available at the
beginning of the tests .
When using &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library  together with
&lt;a href=&quot;https://github.com/patka/cassandra-migration&quot;&gt;cassandra-migration&lt;/a&gt;
this feat of quality assurance  engineering is easily achieved.&lt;/p&gt;
&lt;p&gt;Below is presented a relevant snippet from the Cassandra configuration class used for test purposes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraDockerConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraContainer&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cassandraContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; cassandraContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CassandraContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IMAGE
        &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; CASSANDRA_DOCKER_IMAGE_VERSION&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;loadTestData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;applicationCluster&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;applicationCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CassandraContainer&lt;/span&gt; cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; cluster &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContactPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContainerIpAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFirstMappedPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Bean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;migrationCluster&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token annotation punctuation&quot;&gt;@CassandraMigrationCluster&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;migrationCluster&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;CassandraContainer&lt;/span&gt; cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt; cluster &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CassandraClusterFactoryBean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setContactPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContainerIpAddress&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cluster&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cassandraContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFirstMappedPort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cluster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The setup of the tests is done afterwards pretty straightforward:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@SpringBootTest&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@ActiveProfiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token annotation punctuation&quot;&gt;@TestPropertySource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cassandra.migration.keyspaceName = demo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;cassandra.migration.scriptLocation: cassandra/migration&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DemoTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Runnable sample code&lt;/h2&gt;
&lt;p&gt;The java project &lt;a href=&quot;https://github.com/findinpath/cassandra-migration-spring-boot-demo&quot;&gt;cassandra-migration-spring-boot-demo&lt;/a&gt;
provides a runnable example on how to perform the separation of concerns between the Cassandra
application and migration users. The project also makes use of &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt;
library allowing therefor the repository tests to be executed against a real (and not embedded) Cassandra
database.&lt;/p&gt;
&lt;p&gt;Simply use &lt;code class=&quot;language-text&quot;&gt;mvn clean install&lt;/code&gt; for trying out the project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Browse the requests made towards WireMock]]></title><description><![CDATA[This post showcases how to browse/filter the requests made towards Wiremock. When doing integration tests that involve one or more external…]]></description><link>https://www.findinpath.com/wiremock-logged-requests/</link><guid isPermaLink="false">https://www.findinpath.com/wiremock-logged-requests/</guid><pubDate>Wed, 13 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post showcases how to browse/filter the requests made towards &lt;a href=&quot;http://wiremock.org/&quot;&gt;Wiremock&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;wiremock.jpg&quot; title=&quot;WireMock&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRrQAAABXRUJQVlA4IKgAAACwBACdASoUAAsAPtFUo0uoJKMhsAgBABoJbACdMoMf4IxNLAGMiR3FkbbpLy3wAP7rvDQHGHpppP/q2xSTjSP/fhnLMbAfC+Ou5nQYE6vuMh0hC6vrnZwa6nk14nfQQE3W9nmjJLn0Fz0JMEY20N7lSV6l04WUD6CIKAbWJ+f4sE4PTM1Hc2XgrMOdmVlwEXehRvYJ5pP3Ovf7dCy/DYjP4UtNu9wAAAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:1.7772511848341233,&amp;quot;src&amp;quot;:&amp;quot;/static/0033d06ce62327eeaabd68e70d2d076a/13f2e/wiremock.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/0033d06ce62327eeaabd68e70d2d076a/dd2ee/wiremock.webp 200w,\n/static/0033d06ce62327eeaabd68e70d2d076a/f333d/wiremock.webp 400w,\n/static/0033d06ce62327eeaabd68e70d2d076a/13f2e/wiremock.webp 750w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 750px) 100vw, 750px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/0033d06ce62327eeaabd68e70d2d076a/13f2e/wiremock.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;wiremock.jpg&amp;quot;,&amp;quot;presentationWidth&amp;quot;:750,&amp;quot;presentationHeight&amp;quot;:422,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;225&amp;apos;%3e%3cpath%20d=&amp;apos;M33%2027l7%2023%2020%2062%2013%2040h40l9-28%2010-29%2010%2029%208%2028h64l2-6a3227%203227%200%200112-41c3-13%203-12%206-4l8%2029%206%2022h22c21%200%2021%200%2022-2a1249%201249%200%200016-53l6%2018%208%2028%203%209h42a29708%2029708%200%2001-26-86l-13-40h-20l-19%201a51210%2051210%200%2000-19%2056l-10-28-9-28-32-1c-31%200-33%200-33%202l-16%2052-7-21-8-27-1-5-22-1h-22l-2%207a3963%203963%200%2000-15%2050l-2-6-9-29-6-22H55l-22%201m225%20130c-7%202-14%2010-14%2017%200%205-1%203-4-8l-3-9h-12l-2%205-3%209-2%204-3-9-3-9h-12l-3%2012-7%2019-2%207h14l2-9c3-9%203-9%206%201%203%208%203%208%209%209%205%200%205%200%209-11%202-8%202-8%205%201l2%209h14l-1-3c-2-4-1-4%202-1%2017%2014%2042-2%2034-23-4-9-16-14-26-11m43%200c-7%202-13%2011-13%2019%200%2015%2016%2025%2030%2018l8-8-5-3-5-3-2%202c-8%207-17-5-10-12%202-3%207-3%2010%200l2%202%205-3c4-3%205-3%204-5-4-6-15-10-24-7m-268%201l7%2020%205%2018h6c6%200%206-1%209-11%203-8%203-8%207%202l3%209h11l7-20%206-19H80l-2%208-3%208-3-8-3-7-6-1h-5l-3%208c-3%2010-3%2010-6%201l-2-9h-7l-7%201m63%2018v19h13v-38H96v19m17%200v19h13v-5c1-7%202-7%206-1l4%206h7l7-1-3-5c-5-7-5-6-1-10s4-12%201-16c-4-5-8-6-21-6h-13v19m39%200v19h34v-10h-10c-9%200-11-1-11-2%200-2%201-2%209-2h10v-10h-10c-8%200-9%200-9-2%200-1%202-2%2010-2h10v-10h-33v19m177%200v19h12v-3c0-3%203-9%204-8l4%206%203%205h8l7-1-7-10-6-10%203-4%207-9%203-4h-15l-5%206-5%207-1-6v-7h-12v19m-68-8c-4%202-6%2010-2%2014%202%203%208%203%2011%201%207-6%200-18-9-15&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;When doing integration tests that involve one or more external APIs, it is worth considering
a mock server for the job. &lt;/p&gt;
&lt;p&gt;Some of the advantages of using such a server is that it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;always performs as configured (even the delay for a API response can be in detail configured)&lt;/li&gt;
&lt;li&gt;doesn’t have downtimes while the tests are being executed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously, there are downsides when using a mock server for any testing scenario. It is
advisable that for end to end tests to use the actual external APIs instead of the mocked versions
of them in order to avoid discovering regression issues only in production.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://wiremock.org/&quot;&gt;WireMock&lt;/a&gt; is one of the best libraries that act as a simulator for
HTTP-based APIs. It can be executed from a &lt;a href=&quot;https://junit.org/&quot;&gt;Junit&lt;/a&gt; test, as well as
a standalone application.&lt;/p&gt;
&lt;p&gt;WireMock offers the ability to browse through the requests made towards it and on top of
this functionality there can be made assertions. This functionality can
come in handy in several test scenarios.&lt;/p&gt;
&lt;p&gt;Verify that a specific API endpoint has been called only once&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;wiremockServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postRequestedFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;urlEqualTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;api/order&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Verify the order in which the API calls are made&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; paymentRequests &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wiremockServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postRequestedFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;urlMatching&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/payment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; shipmentRequests &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; wiremockServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postRequestedFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;urlMatching&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/api/shipment&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// shipment shouldn&apos;t happen before payment&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shipmentRequests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLoggedDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;greaterThan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;paymentRequests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLoggedDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In case that a certain test needs a more complex handling (e.g. : callbacks in the test code) for
specific requests, WireMock also provides a series of instruments that can be used to
extend its basic functionality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;com.github.tomakehurst.wiremock.extension.PostServeAction&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Complex needs in the tests can be covered when registering a concrete implementation of
the classes above as an extension for the WireMock server.&lt;/p&gt;
&lt;p&gt;The java project &lt;a href=&quot;https://github.com/findinpath/wiremock-recorded-request-timestamp&quot;&gt;wiremock-recorded-request-timestamp&lt;/a&gt;
provides a runnable example on how to retrieve the logged date of the requests made towards
the WireMock server. &lt;/p&gt;
&lt;p&gt;Simply use &lt;code class=&quot;language-text&quot;&gt;mvn clean install&lt;/code&gt; for trying out the project.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Exchange Kafka avro messages in testcontainers ecosystem]]></title><description><![CDATA[This post showcases a manner on which to ensure the quality of Apache Kafka
producers & consumers that exchange avro messages in a…]]></description><link>https://www.findinpath.com/testcontainers-kafka-avro/</link><guid isPermaLink="false">https://www.findinpath.com/testcontainers-kafka-avro/</guid><pubDate>Thu, 07 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post showcases a manner on which to ensure the quality of &lt;a href=&quot;https://kafka.apache.org/&quot;&gt;Apache Kafka&lt;/a&gt;
producers &amp;#x26; consumers that exchange &lt;a href=&quot;https://avro.apache.org/&quot;&gt;avro&lt;/a&gt; messages in a dockerized environment.&lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;richard-r-schunemann-EIeQUi77QGg-unsplash.jpg&quot; title=&quot;Avro Aircrafts - Photo by Richard R Sch&amp;#xFC;nemann on Unsplash&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRnQAAABXRUJQVlA4IGgAAACQBACdASoUAAkAPtFUpEuoJKOhsAgBABoJYgCdMoMYK8AAeLduy250gu0g5QAA/pGfj7HXwWifKckPL/aGFYlKxJdiRLl5vkRGL/HO4VBEgfSB1f0yBVFSO+PO0BVpbVrmOL3uanNAAA==&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.2163355408388523,&amp;quot;src&amp;quot;:&amp;quot;/static/50c27db2024f63a028e57ff8b031c7c7/32a6f/richard-r-schunemann-EIeQUi77QGg-unsplash.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/50c27db2024f63a028e57ff8b031c7c7/dd2ee/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 200w,\n/static/50c27db2024f63a028e57ff8b031c7c7/f333d/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 400w,\n/static/50c27db2024f63a028e57ff8b031c7c7/32a6f/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 800w,\n/static/50c27db2024f63a028e57ff8b031c7c7/2cf13/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 1200w,\n/static/50c27db2024f63a028e57ff8b031c7c7/1e1a6/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 1600w,\n/static/50c27db2024f63a028e57ff8b031c7c7/69a1f/richard-r-schunemann-EIeQUi77QGg-unsplash.webp 4016w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/50c27db2024f63a028e57ff8b031c7c7/69a1f/richard-r-schunemann-EIeQUi77QGg-unsplash.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;richard-r-schunemann-EIeQUi77QGg-unsplash.jpg&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:361,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;180&amp;apos;%3e%3cpath%20d=&amp;apos;M265%201l-4%201-3%202c-1%202-6%203-6%201l-2-1-2%201-3%202-3%204-1%203v6c1%201-3%207-5%209s-2%203-1%207c1%202-1%205-4%205l-4%202c-1%201-4%201-4-1l-3-2-3-2-2-3v-1l-1-1c-1%201-8-4-7-6h-1c-1%201-1%201%200%200l-1-1c0%201-5-2-9-6-10-8-13-9-11-4l3%204%201%202%201%201c1-1%206%205%205%206h1c1-1%201-1%200%200l1%201%207%206-1%201h-1l-3%201-4%201-13%202c-6%200-10%200-9-1%202-1%202-1%201-2-2-1-2-1-4%201h-2l2-3v-2l-2-3c0-2-3-2-7-1l-2%202c1%201%201%201-1%201l-6%202c-3%202-5%202-7%202-2-1-3-1-5%201l-3%202-1%201c-1%202-7%204-8%203s-1-1-3%201c-1%201-2%202-5%202s-4%201-6%203l-6%202-12%201%201%201%201%201-3%201c-2-1-5%201-10%203-2%202-7%202-18%202l-11%201h-2l-9-1a9012%209012%200%2001-19%203l-7%202c-2%201-6%202-7%201l-2%201c0%202%200%202-2%201-8-3-8-4-8%2035v34l2-2%204-5%205-4%206-3c3-1%204-2%207-1%206%201%2012%205%2012%208l4%203c1%201%202%201%202%203-1%203%203%207%207%209%203%201%205%202%209%2011l3%203%201%201v1l1%202%201%201c2%200%206%209%206%2012v3h88a1464%201464%200%200086-2l-2-2c0-2-1-4-3-5l-3-3-2-4-2-4c0-1-7-4-9-3-3%202-7%201-10-1l-4-2-1-3c-2-5-5-7-8-7-3%201-5%200-8-2l-6-3-1-1-1-3%201-3c1-1%201-1-1-1s0-1%207-6c10-6%2010-6%209-7l-4%201c-5%202-5%202-5%200l2-3%201-2v-2c1-1%202-3%201-5l1-3c2%200%203-3%201-3l-1-3c0-2%201-2%202-2l3-3c0-2%201-3%202-3l3-1%203-2%203-3c0-2%204-4%2011-6l2-2c0-2%205-3%208-1l5%202%206-1%205-1%2031%2016c11%206%2016%208%2016%206a274%20274%200%2000-34-25l-1-1-2-1c0-2%202-2%204-1h2c0-1%200-2-2-3-2-2-3-4%200-5%205-2%2028-4%2029-2l8%201c6%200%207%200%207-2l4-4c5-2%205-2%208-1%203%202%204%202%206%201%203-2%206-1%206%201%200%203%201%205%204%205l4%201c1%201%202%200%204-1l4-1c4%201%2016-1%2020-4%204-2%205-2%2014-1s9%201%2012-1l5-3c2-1%202-1%202-26V0h-67l-68%201&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;The library &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; can be used for providing lightweight,
throw-away instances of &lt;a href=&quot;https://www.docker.com/&quot;&gt;docker&lt;/a&gt; containers corresponding to the
artifacts of the Apache Kafka ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Zookeeper&lt;/li&gt;
&lt;li&gt;Apache Kafka&lt;/li&gt;
&lt;li&gt;Confluent Schema Registry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this fashion, there can be tested complex integration scenarios which would
otherwise involve the presence of an actual Apache Kafka testing environment.
Simulating, for example, a schema version update for an avro event transported
over Apache Kafka, can be tested therefor safely in an integration test
without needing to occasionally manually test this operation. &lt;/p&gt;
&lt;p&gt;The testcontainers library already offers a &lt;a href=&quot;https://www.testcontainers.org/modules/kafka/&quot;&gt;Kafka&lt;/a&gt; module
for interacting with Apache Kafka, but there is not, at the moment, support on it for a
&lt;a href=&quot;https://www.confluent.io/confluent-schema-registry/&quot;&gt;Confluent Schema Registry&lt;/a&gt; container. &lt;/p&gt;
&lt;p&gt;The project &lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-avro&quot;&gt;testcontainers-kafka-avro&lt;/a&gt;
is a working prototype on how to setup and use in unit tests all the containers
corresponding to the artifacts of the Apache Kafka ecosystem.&lt;/p&gt;
&lt;p&gt;Setting up of the containers is done in the following manner:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@BeforeAll&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;confluentSetup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    network &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Network&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    zookeeperContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ZookeeperContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    kafkaContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getZookeeperConnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    schemaRegistryContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SchemaRegistryContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getZookeeperConnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withNetwork&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;network&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Startables&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deepStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;zookeeperContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; kafkaContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; schemaRegistryContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The beauty of working with the testcontainers library is that there (considering that docker
installed on the test machine) is nothing related to Apache Kafka to be installed on the test
machine. Simply by running &lt;code class=&quot;language-text&quot;&gt;mvn clean install&lt;/code&gt; everything is being automatically setup on
docker and at the end of the test, the containers needed by it will be disposed.&lt;/p&gt;
&lt;p&gt;As a side note, the containers used do not use the default ports exposed by default in
their corresponding artifacts (e.g. : Apache Zookeeper &lt;code class=&quot;language-text&quot;&gt;2181&lt;/code&gt;, Apache Kafka &lt;code class=&quot;language-text&quot;&gt;9092&lt;/code&gt;,
Confluent Schema Registry &lt;code class=&quot;language-text&quot;&gt;8081&lt;/code&gt;), but rather free ports available on the test/build
machine. In this fashion there are avoided possible conflicts with already running
services on the test/build machine.&lt;/p&gt;
&lt;p&gt;The creation of the Apache Kafka topic needed in the test, as well as the registration of
the avro types in the Confluent Schema Registry is done once
while setting up the Confluent environment.&lt;/p&gt;
&lt;p&gt;The
&lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-avro/blob/master/src/test/java/com/findinpath/AvroDemoTest.java&quot;&gt;test&lt;/a&gt;
showcased in the project shows how a Kafka Producer writes a &lt;code class=&quot;language-text&quot;&gt;BookmarkEvent&lt;/code&gt;
avro event to the &lt;code class=&quot;language-text&quot;&gt;BookmarkEvents&lt;/code&gt; topic from which a Kafka Consumer reads it
and verifies that the message received is the same as the message sent.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;  &lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;demo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UUID&lt;/span&gt; userUuid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UUID&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BookmarkEvent&lt;/span&gt; bookmarkEvent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BookmarkEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userUuid&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; URL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toEpochMilli&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;produce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TOPIC&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    LOGGER&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Successfully sent 1 BookmarkEvent message to the topic called %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TOPIC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; consumerRecords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dumpTopic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TOPIC&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; POLL_TIMEOUT_MS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    LOGGER&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Retrieved %d consumer records from the topic %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        consumerRecords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TOPIC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerRecords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerRecords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserUuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerRecords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;equalTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bookmarkEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Even though this demo showcases a rather naive usecase, the setup shown above for
Apache Kafka ecosystem can be used for complex quality assurance scenarios like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;simulating how are handled by the producer/consumer the schema version updates of the
avro messages&lt;/li&gt;
&lt;li&gt;investigate how the consumer/producer functions when the schema registry is temporarily
not available (when pausing the schema registry docker container)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to integrate the Apache Kafka ecosystem
&lt;a href=&quot;https://github.com/findinpath/testcontainers-kafka-avro/tree/master/src/test/java/com/findinpath/testcontainers&quot;&gt;testcontainers classes&lt;/a&gt;
in your project to ensure the accuracy of your Apache Kafka avro producers &amp;#x26; consumers.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Retrieve the distinct partition keys of a Cassandra table]]></title><description><![CDATA[Selecting the distinct partition keys of a Cassandra table is very straightforward when performing a CQL query to retrieve the first page
of…]]></description><link>https://www.findinpath.com/distinct-cassandra-partition-keys/</link><guid isPermaLink="false">https://www.findinpath.com/distinct-cassandra-partition-keys/</guid><pubDate>Tue, 05 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Selecting the distinct partition keys of a Cassandra table is very straightforward when performing a CQL query to retrieve the first page
of &lt;code class=&quot;language-text&quot;&gt;DISTINCT&lt;/code&gt; partition keys of the Cassandra table, but in order to select &lt;em&gt;all&lt;/em&gt; the distinct partition keys of the table without needing
to retrieve all the rows of the table the &lt;code class=&quot;language-text&quot;&gt;TOKEN&lt;/code&gt; Cassandra function  needs to be employed. &lt;/p&gt;
&lt;div&gt;&lt;re-img src=&quot;photo-1513542789411-b6a5d4f31634.jpg&quot; rehyped=&quot;{&amp;quot;base64&amp;quot;:&amp;quot;data:image/webp;base64,UklGRloAAABXRUJQVlA4IE4AAAAwAwCdASoUAAkAPtFUo0uoJKMhsAgBABoJYgCdADBhgn/wAP7vQ/3Dzh1yxEpnb136IMeWwW+trtD8pUR5jt/kmItin9UEUfnXh+EgAAA=&amp;quot;,&amp;quot;aspectRatio&amp;quot;:2.2094240837696337,&amp;quot;src&amp;quot;:&amp;quot;/static/18c822bc8cc3445ee2c484333e3ca3ca/32a6f/photo-1513542789411-b6a5d4f31634.webp&amp;quot;,&amp;quot;srcSet&amp;quot;:&amp;quot;/static/18c822bc8cc3445ee2c484333e3ca3ca/dd2ee/photo-1513542789411-b6a5d4f31634.webp 200w,\n/static/18c822bc8cc3445ee2c484333e3ca3ca/f333d/photo-1513542789411-b6a5d4f31634.webp 400w,\n/static/18c822bc8cc3445ee2c484333e3ca3ca/32a6f/photo-1513542789411-b6a5d4f31634.webp 800w,\n/static/18c822bc8cc3445ee2c484333e3ca3ca/2cf13/photo-1513542789411-b6a5d4f31634.webp 1200w,\n/static/18c822bc8cc3445ee2c484333e3ca3ca/2128b/photo-1513542789411-b6a5d4f31634.webp 1266w&amp;quot;,&amp;quot;srcSetType&amp;quot;:&amp;quot;image/webp&amp;quot;,&amp;quot;sizes&amp;quot;:&amp;quot;(max-width: 800px) 100vw, 800px&amp;quot;,&amp;quot;originalImg&amp;quot;:&amp;quot;/static/18c822bc8cc3445ee2c484333e3ca3ca/2128b/photo-1513542789411-b6a5d4f31634.webp&amp;quot;,&amp;quot;originalName&amp;quot;:&amp;quot;photo-1513542789411-b6a5d4f31634.jpg&amp;quot;,&amp;quot;density&amp;quot;:72,&amp;quot;presentationWidth&amp;quot;:800,&amp;quot;presentationHeight&amp;quot;:362,&amp;quot;tracedSVG&amp;quot;:&amp;quot;data:image/svg+xml,%3csvg%20xmlns=&amp;apos;http://www.w3.org/2000/svg&amp;apos;%20width=&amp;apos;400&amp;apos;%20height=&amp;apos;181&amp;apos;%3e%3cpath%20d=&amp;apos;M379%2026l-2%2011c1%201%206-3%207-6%202-5%200-16-2-16-1%200-2%202-3%2011M82%2066a473%20473%200%2001-1%2013l2%201%202%201h2c1%201%201%201%201-3l-2-10c0-8-3-9-4-2m56%2014c-1%2010-1%2011%202%209l4-1%201-2-2-9c0-6-1-7-2-7s-2%202-3%2010m-96-3l-2%2010v5h3c5%200%205-1%204-11-1-7-2-9-3-9s-2%201-2%205m18%2017c-2%2013-2%2017%200%2010l2-3%201-2%203-3%201-4c0-7-1-10-3-11-1%200-2%202-4%2013m267-12l-1%208c0%206%200%207%202%209l2%203%202%201%201-2-3-20-3%201M3%2095l-2%2010%202%202c2%202%203%202%205%200l2-2-1-10c-2-13-4-14-6%200m191-2c-1%209-1%2012%201%2012l1%201%201%201c2%200%203-3%203-5l-1-8c-1-9-3-10-5-1m-75%2023c-2%2012-3%2015-5%2014l-1%202-1%2010c-2%2012-2%2039-1%2039l1-14c0-18%203-35%206-36%206-2%207-5%206-14%200-7-1-8-2-8s-2%201-3%207m94-6a1031%201031%200%2001-2%2024l3%204c2%202%202%202%204%201%202-2-1-29-4-29h-1m151%206c-2%204-3%2017-2%2018%202%205%209%205%207%201l-1-11c-1-8-3-11-4-8m-234%2014l-1%202-1%202v10c1%207%202%2013%201%2023%200%2017%201%2020%202%204%201-12%200-43-1-41m75%205a296%20296%200%20000%2030l2-19%201-9c-1%202-1%201-2-3-1-1-1-1-1%201&amp;apos;%20fill=&amp;apos;%23f9ebd2&amp;apos;%20fill-rule=&amp;apos;evenodd&amp;apos;/%3e%3c/svg%3e&amp;quot;}&quot;&gt;&lt;/re-img&gt;&lt;/re-img&gt;&lt;/div&gt;
&lt;p&gt;Let’s consider for the sake of having a concrete example to work on that we store the bookmarks made by the users
in a Cassandra table.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; DEMO&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;USER_BOOKMARKS &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
	user_id UUID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt; TIMEUUID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	url &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; CLUSTERING &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When the following query:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; user_id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; demo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user_bookmarks
&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;is executed, it will provide the first page containing 1000 distinct partition keys of the table &lt;code class=&quot;language-text&quot;&gt;demo.user_bookmarks&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For selecting the next page, an extra filter based the &lt;code class=&quot;language-text&quot;&gt;TOKEN&lt;/code&gt; function has to be employed in order to retrieve
the next page of distinct partition keys which all have the token greater as the token of the last &lt;code class=&quot;language-text&quot;&gt;user_id&lt;/code&gt; selected on the previous page.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DISTINCT&lt;/span&gt; user_id
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; demo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user_bookmarks
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; TOKEN&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; TOKEN&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userIds&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; 
&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The corresponding java implementation looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResultSet&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDistinctUserIdsBatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Session&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;token generics&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;UUID&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; lastUserIdProcessed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; batchSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; batchedDistinctUserSelect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueryBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;USER_ID_COLUMN_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;distinct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DEMO_KEYSPACE_NAME&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; USER_BOOKMARKS_TABLE_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	lastUserIdProcessed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ifPresent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
			uuid &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; batchedDistinctUserSelect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;gt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;USER_ID_COLUMN_NAME&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uuid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	batchedDistinctUserSelect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;batchSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;batchedDistinctUserSelect&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.datastax.com/en/archived/cql/3.3/cql/cql_using/useToken.html&quot;&gt;Cassandra TOKEN documentation&lt;/a&gt; is a good starter to
get a feeling of what the &lt;code class=&quot;language-text&quot;&gt;TOKEN&lt;/code&gt; function does.&lt;/p&gt;
&lt;p&gt;The java project &lt;a href=&quot;https://github.com/findinpath/cassandra-select-distinct-partition-keys/&quot;&gt;cassandra-select-distinct-partition-keys&lt;/a&gt;
provides a runnable test which starts a Cassandra docker container (via &lt;a href=&quot;https://www.testcontainers.org/&quot;&gt;testcontainers&lt;/a&gt; library)
fills it with data (the bookmarks made by a list of random users) and selects the distinct user identifiers.&lt;/p&gt;
&lt;p&gt;Simply use &lt;code class=&quot;language-text&quot;&gt;mvn clean install&lt;/code&gt; for trying out the project.&lt;/p&gt;</content:encoded></item></channel></rss>