<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniil Roman</title>
    <description>The latest articles on DEV Community by Daniil Roman (@daniilroman).</description>
    <link>https://dev.to/daniilroman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F854738%2Fc41bf28b-90ae-4936-8dbe-088341ec80df.jpeg</url>
      <title>DEV Community: Daniil Roman</title>
      <link>https://dev.to/daniilroman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daniilroman"/>
    <language>en</language>
    <item>
      <title>Automate Your Java Upgrades: A Practical Case Study with OpenRewrite and GitHub Actions</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Fri, 10 Oct 2025 13:12:02 +0000</pubDate>
      <link>https://dev.to/berlin-tech-blog/automate-your-java-upgrades-a-practical-case-study-with-openrewrite-and-github-actions-3od7</link>
      <guid>https://dev.to/berlin-tech-blog/automate-your-java-upgrades-a-practical-case-study-with-openrewrite-and-github-actions-3od7</guid>
      <description>&lt;p&gt;Tech debt grows relentlessly. Even on services you don't touch, dependencies become outdated, creating a constant maintenance burden. Manually upgrading dozens of services is slow and error-prone. What if you could automate a significant part of that process?&lt;/p&gt;

&lt;p&gt;Remember your last big Spring Boot or Java version upgrade? How did it go? Did you spend hours renaming &lt;code&gt;javax&lt;/code&gt; to &lt;code&gt;jakarta&lt;/code&gt; packages across 30 or maybe 50 different services? Or perhaps you lost a whole day figuring out why a simple dependency bump broke the build? If any of this sounds familiar, you're in the right place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenRewrite?
&lt;/h2&gt;

&lt;p&gt;OpenRewrite is an open-source refactoring engine that automates code modifications at scale, enabling consistent and reliable refactoring across large codebases and significantly reducing manual effort.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OpenRewrite works by making changes to &lt;a href="https://docs.openrewrite.org/concepts-and-explanations/lossless-semantic-trees" rel="noopener noreferrer"&gt;Lossless Semantic Trees&lt;/a&gt; (LSTs) that represent your source code and printing the modified trees back into source code. You can then review the changes in your code and commit the results. Modifications to the LST are performed in &lt;a href="https://docs.openrewrite.org/concepts-and-explanations/visitors" rel="noopener noreferrer"&gt;Visitors&lt;/a&gt; and visitors are aggregated into &lt;a href="https://docs.openrewrite.org/concepts-and-explanations/recipes" rel="noopener noreferrer"&gt;Recipes&lt;/a&gt;. OpenRewrite recipes make minimally invasive changes to your source code that honor the original formatting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can check this project out on &lt;a href="https://github.com/openrewrite/rewrite" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yzc37ojz6j023vw1rqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yzc37ojz6j023vw1rqx.png" alt="OpenRewrite statistics on GitHub" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When would you use OpenRewrite?
&lt;/h2&gt;

&lt;p&gt;You might be thinking, "This sounds great in theory, but what are the real-world use cases?" Let's recall some notable migrations many of us have faced recently:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spring Boot 2.x to 3.x migration&lt;/strong&gt;&lt;br&gt;
This migration was especially remarkably by the seemingly simple task of renaming &lt;code&gt;javax.*&lt;/code&gt; to &lt;code&gt;jakarta.*&lt;/code&gt; namespaces. However, this change had to be applied to every single service using Spring Boot. In a typical Java and Spring ecosystem, that means changing it everywhere. &lt;/p&gt;

&lt;p&gt;OpenRewrite offers a recipe that automates this entire process.&lt;br&gt;
&lt;a href="https://docs.openrewrite.org/recipes/java/spring/boot3/upgradespringboot_3_0" rel="noopener noreferrer"&gt;https://docs.openrewrite.org/recipes/java/spring/boot3/upgradespringboot_3_0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JUnit 4 to JUnit 5 Migration&lt;/strong&gt; &lt;br&gt;
Imagine you need to migrate from JUnit 4 to JUnit 5, but your codebase still has a few outdated annotations scattered around. As a part of this migration you'd need to rename &lt;code&gt;@BeforeClass&lt;/code&gt; to &lt;code&gt;@BeforeAll&lt;/code&gt; or &lt;code&gt;@AfterClass&lt;/code&gt; to &lt;code&gt;@AfterAll&lt;/code&gt;. &lt;br&gt;
It doesn't sound too complicated, but it's tedious work that can be fully automated with an OpenRewrite recipe. &lt;br&gt;
&lt;a href="https://docs.openrewrite.org/running-recipes/popular-recipe-guides/migrate-from-junit-4-to-junit-5" rel="noopener noreferrer"&gt;https://docs.openrewrite.org/running-recipes/popular-recipe-guides/migrate-from-junit-4-to-junit-5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java Version Upgrades:&lt;/strong&gt; &lt;br&gt;
Or maybe you're upgrading to Java 21 and want to replace the deprecated &lt;code&gt;new URL(String)&lt;/code&gt; constructor with the &lt;code&gt;URI.create(String).toURL()&lt;/code&gt; across your entire codebase. &lt;br&gt;
There's a recipe for that too: &lt;a href="https://docs.openrewrite.org/recipes/java/migrate/upgradetojava21" rel="noopener noreferrer"&gt;https://docs.openrewrite.org/recipes/java/migrate/upgradetojava21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other words, we developers are constantly challenged with keeping our dependencies up to date. OpenRewrite is here to rescue us—or at least, to significantly reduce the pain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Our experience of using OpenRewrite
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What worked for us
&lt;/h3&gt;

&lt;p&gt;Let's start with what went well, as we found the tool both useful and promising.&lt;/p&gt;
&lt;h4&gt;
  
  
  Keeping &lt;code&gt;pom.xml&lt;/code&gt; in shape
&lt;/h4&gt;

&lt;p&gt;In the OpenRewrite ecosystem, the magic comes from its recipes. For us, the first no-brainer was the &lt;a href="http://docs.openrewrite.org/recipes/maven/bestpractices" rel="noopener noreferrer"&gt;Apache Maven best practices&lt;/a&gt; recipe.&lt;br&gt;
It was immediately clear that we had no other tool in our stack that could consistently keep our &lt;code&gt;pom.xml&lt;/code&gt; files in good shape.&lt;/p&gt;

&lt;p&gt;As a simple but welcome feature, this recipe reorders the sections of a &lt;code&gt;pom.xml&lt;/code&gt; to follow a standard pattern. This helps with readability, especially in large files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kmgn45dsdpr5ah0m5r6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kmgn45dsdpr5ah0m5r6.png" alt="pom.xml reordering result" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But its real power lies elsewhere: the recipe can find and remove duplicate or unused dependencies, improving the long-term stability of a service. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfm9ns4i3f83mox52ec4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfm9ns4i3f83mox52ec4.png" alt="Unused or duplicated dependencies were removed" width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a note of caution, I admit it can be scary at first to accept an automated PR that removes dependencies. &lt;strong&gt;Make sure you have good test coverage&lt;/strong&gt; before trusting any automated tool to this extent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;
  
  
  Refactoring test libraries
&lt;/h5&gt;

&lt;p&gt;Of course, we wanted to see it work on actual Java code. As always, the safest place to try a new tool is on your tests, so that's exactly what we did. We were already in the process of standardizing on AssertJ, so we introduced three relevant recipes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;org.openrewrite.java.testing.assertj.Assertj&lt;/li&gt;
&lt;li&gt;org.openrewrite.java.testing.mockito.MockitoBestPractices&lt;/li&gt;
&lt;li&gt;org.openrewrite.java.testing.testcontainers.TestContainersBestPractices
We ran these without specific expectations and were pleasantly surprised when they spotted and fixed several sore spots in our test code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqi9ods6f1pyb8y1pqzi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqi9ods6f1pyb8y1pqzi.png" alt="Static import was fixed" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrwygleopwk62rdbhz0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrwygleopwk62rdbhz0s.png" alt="The result of a recipe for testcontainers to use a specific Docker image" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  What didn't work
&lt;/h4&gt;

&lt;p&gt;But of course, the juicy part is always where things go wrong.&lt;br&gt;
In the following paragraphs, we will look at a few examples that we were not entirely satisfied with. &lt;/p&gt;
&lt;h5&gt;
  
  
  Complex, custom refactoring
&lt;/h5&gt;

&lt;p&gt;We tried to use a recipe to fully migrate our tests from Hamcrest to AssertJ, but it simply ignored our custom matchers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3spex3ti9u7kcngo5gpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3spex3ti9u7kcngo5gpz.png" alt="Complex Hamcrest matcher that OpenRewrite recipe wasn't able to migrate to AssertJ" width="800" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While some recipes are more powerful than others, our general feeling is that OpenRewrite struggles with highly complex or bespoke refactorings on its own.&lt;/p&gt;
&lt;h5&gt;
  
  
  Running Java recipes on Kotlin projects
&lt;/h5&gt;

&lt;p&gt;It may seem obvious that Java recipes should only be run on Java projects. However, like many companies, we have a mix of Java and Kotlin projects, so we simply ran the recipes against all of our team's services to see what would happen. It turns out that it &lt;em&gt;partially&lt;/em&gt; works, but it fails in enough cases to produce strange changes and broken PRs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuux72pcmc9rhdd1raixo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuux72pcmc9rhdd1raixo.png" alt="A result of running a Java recipe against Kotlin project" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes things tricky if you want to run a uniform set of recipes across all repositories using a tool like GitHub Actions, which we'll cover next.&lt;/p&gt;
&lt;h5&gt;
  
  
  Newest recipes are often commercial
&lt;/h5&gt;

&lt;p&gt;This might be obvious if you're already familiar with OpenRewrite, but it's worth mentioning. If you want to use OpenRewrite for Spring Boot upgrades, you'll find that the recipe for the latest version might be under a commercial license. For example, if Spring Boot 3.5 is the latest release, the open-source recipe might only support up to version 3.4. This makes perfect sense from a business perspective, but it's something to keep in mind. In short: the OpenRewrite &lt;em&gt;engine&lt;/em&gt; is open-source, but the most cutting-edge &lt;em&gt;recipes&lt;/em&gt; are often licensed separately.&lt;/p&gt;
&lt;h2&gt;
  
  
  Our automation setup with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;There are a few ways to run OpenRewrite recipes. If you're using Maven, you can add the &lt;code&gt;rewrite-maven-plugin&lt;/code&gt; directly to your &lt;code&gt;pom.xml&lt;/code&gt;. This can be configured to run during your local build or only on CI.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.openrewrite.maven&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;rewrite-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;6.18.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;exportDatatables&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/exportDatatables&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;activeRecipes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;recipe&amp;gt;&lt;/span&gt;org.openrewrite.staticanalysis.JavaApiBestPractices&lt;span class="nt"&gt;&amp;lt;/recipe&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/activeRecipes&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.openrewrite.recipe&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;rewrite-static-analysis&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.17.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can run the plugin directly from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nt"&gt;-U&lt;/span&gt; org.openrewrite.maven:rewrite-maven-plugin:run &lt;span class="nt"&gt;-Drewrite&lt;/span&gt;.recipeArtifactCoordinates&lt;span class="o"&gt;=&lt;/span&gt;org.openrewrite.recipe:rewrite-static-analysis:RELEASE &lt;span class="nt"&gt;-Drewrite&lt;/span&gt;.activeRecipes&lt;span class="o"&gt;=&lt;/span&gt;org.openrewrite.staticanalysis.JavaApiBestPractices &lt;span class="nt"&gt;-Drewrite&lt;/span&gt;.exportDatatables&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the flexibility to run it manually, as part of a CI pipeline, or on a nightly schedule. &lt;br&gt;
We chose the third option: &lt;strong&gt;run it via a scheduled GitHub Action on a daily basis and automatically create a PR if any changes are detected.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not just use the Maven plugin?
&lt;/h3&gt;

&lt;p&gt;The main drawback of adding the plugin to your &lt;code&gt;pom.xml&lt;/code&gt; is that it significantly slows down every build, even when there are no changes to be made. You could run it as a CI-only check, but that creates a frustrating workflow: the CI build would fail, and a developer would have to run the command locally to generate the changes and push another commit. This kind of friction hurts tool adoption.&lt;/p&gt;

&lt;p&gt;Running OpenRewrite on a schedule mimics the behavior of Dependabot or Renovate. Developers don't have to actively run anything; they simply review and merge the auto-generated PRs. Ideally, only a small portion of these PRs will have failures.&lt;/p&gt;

&lt;p&gt;Another benefit of the GitHub Action approach is centralization. We can update the recipes in a single, shared workflow file and have that change apply to all our repositories without touching a single &lt;code&gt;pom.xml&lt;/code&gt;. And since the result is always a PR and not a direct commit, it's a completely safe operation.&lt;/p&gt;
&lt;h3&gt;
  
  
  The GitHub workflow in detail
&lt;/h3&gt;

&lt;p&gt;Imagine you have 20 repositories. Modifying the &lt;code&gt;pom.xml&lt;/code&gt; in every one of them just to add or change a recipe would be painful and would quickly lead to abandoning the tool. With a centralized GitHub Action, however, each repository only needs a small trigger file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenRewrite Scheduled PR&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MON-FRI'&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Allows manual triggering&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;call-openrewrite-workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-organisation/your-repository/.github/workflows/reusable-openrewrite-auto-pr.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inherit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file references a &lt;strong&gt;reusable workflow&lt;/strong&gt;, which contains the actual logic and OpenRewrite configuration. Now, whenever we add or remove a recipe, we only modify the central workflow, and all repositories pick up the change on their next scheduled run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reusable OpenRewrite Auto PR Workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ADDITIONAL_MVN_COMMAND_TO_APPLY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Additional&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OpenRewrite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apply'&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PR_BRANCH_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openrewrite/auto-improvements&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check if branch exists&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;should_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.check_branch.outputs.should_run }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check if branch exists&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check_branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;if git ls-remote --heads origin ${{ env.PR_BRANCH_NAME }} | grep -q ${{ env.PR_BRANCH_NAME }}; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Branch ${{ env.PR_BRANCH_NAME }} already exists. Skipping workflow."&lt;/span&gt;
            &lt;span class="s"&gt;echo "should_run=false" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;echo "Branch does not exist. Proceeding with workflow."&lt;/span&gt;
            &lt;span class="s"&gt;echo "should_run=true" &amp;gt;&amp;gt; $GITHUB_OUTPUT&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

  &lt;span class="na"&gt;openrewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Apply OpenRewrite recommendations&lt;/span&gt;

    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-branch&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;needs.check-branch.outputs.should_run == 'true'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Java&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;21'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;
          &lt;span class="na"&gt;settings-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run OpenRewrite via Maven&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;./mvnw --batch-mode -U org.openrewrite.maven:rewrite-maven-plugin:run \&lt;/span&gt;
            &lt;span class="s"&gt;-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE \&lt;/span&gt;
            &lt;span class="s"&gt;-Drewrite.activeRecipes=org.openrewrite.staticanalysis.JavaApiBestPractices,org.openrewrite.maven.BestPractices&lt;/span&gt;

          &lt;span class="s"&gt;${{ inputs.ADDITIONAL_MVN_COMMAND_TO_APPLY }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Pull Request&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create_pr&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peter-evans/create-pull-request@v7&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;commit-message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refactor:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OpenRewrite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recommendations"&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refactor:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OpenRewrite&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recommendations"&lt;/span&gt;
          &lt;span class="na"&gt;add-paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;.&lt;/span&gt;
            &lt;span class="s"&gt;:!settings.xml&lt;/span&gt;
            &lt;span class="s"&gt;:!toolchains.xml&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;This Pull Request was automatically generated by OpenRewrite.&lt;/span&gt;

            &lt;span class="s"&gt;## Changes Applied&lt;/span&gt;
            &lt;span class="s"&gt;The following recipes were applied:&lt;/span&gt;
            &lt;span class="s"&gt;- `org.openrewrite.maven.BestPractices` (Maven best practices)&lt;/span&gt;
            &lt;span class="s"&gt;- `org.openrewrite.staticanalysis.JavaApiBestPractices` (Java API best practices)&lt;/span&gt;
            &lt;span class="s"&gt;${{ inputs.OPENREWRITE_RECIPES_TO_APPLY }}&lt;/span&gt;

            &lt;span class="s"&gt;Please review the changes and merge if acceptable.&lt;/span&gt;
          &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.PR_BRANCH_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;delete-branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;openrewrite&lt;/span&gt;
            &lt;span class="s"&gt;automated-pr&lt;/span&gt;
          &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR Status&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Pull request creation status: ${{ steps.create_pr.outputs.pull-request-operation }}"&lt;/span&gt;
          &lt;span class="s"&gt;echo "Pull request number: ${{ steps.create_pr.outputs.pull-request-number }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: &lt;br&gt;
Several unrelated and infrastructure-specific steps have been removed from the GitHub workflow described above.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below you can see the steps of the GitHub workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbhnr0zx5sa19b9uey0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbhnr0zx5sa19b9uey0z.png" alt="OpenRewrite GitHub workflow steps" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run OpenRewrite via Maven&lt;/li&gt;
&lt;li&gt;Create Pull Request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach has proven to be robust; we've already added new recipes and removed old ones, confirming it works well in different scenarios. You can scope a shared GitHub Action to a team or an entire organization and still provide repository-specific overrides using environment variables. This allows the shared workflow to be as complex as necessary, as long as it remains maintainable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Our results
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;We ran &lt;strong&gt;5 distinct recipes&lt;/strong&gt; on a schedule across our team's repositories.&lt;/li&gt;
&lt;li&gt;The workflow was rolled out to &lt;strong&gt;14 services&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In a short time, we have already merged over &lt;strong&gt;40 automated PRs&lt;/strong&gt; generated by this system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw8iw4rzl3zoldt0fsbt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw8iw4rzl3zoldt0fsbt.png" alt="PR description of a successful run of an OpenRewrite GitHub workflow" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;In this article, we covered the pain points OpenRewrite solves, what worked (and didn't work) for us, and how we use the open-source version with GitHub Actions to run recipes automatically across all our repositories.&lt;/p&gt;

&lt;p&gt;So far, our experience with OpenRewrite has been very positive. It has filled a crucial gap in our toolkit, especially for keeping our Maven &lt;code&gt;pom.xml&lt;/code&gt; files clean and consistent.&lt;/p&gt;

&lt;p&gt;Check out the OpenRewrite documentation to find a recipe that fits your needs, and feel free to use our GitHub workflow as inspiration for your own automation.&lt;/p&gt;

</description>
      <category>java</category>
      <category>openrewrite</category>
      <category>githubactions</category>
      <category>techdebt</category>
    </item>
    <item>
      <title>GitHub Innovation Graph analysis</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Sun, 15 Oct 2023 13:10:53 +0000</pubDate>
      <link>https://dev.to/daniilroman/github-innovation-graph-analysis-27m3</link>
      <guid>https://dev.to/daniilroman/github-innovation-graph-analysis-27m3</guid>
      <description>&lt;p&gt;GitHub has recently unveiled its &lt;a href="https://innovationgraph.github.com" rel="noopener noreferrer"&gt;Innovation Graph&lt;/a&gt;, offering us a high level view of the global tech landscape. With this tool, we can assess how various countries contribute to the global tech community on GitHub. We can also delve into the shifts occurring in different regions, identifying those that are net contributors versus net consumers. Additionally, we can pinpoint the countries with the highest numbers of newly established organizations and new developers signing up for GitHub accounts.&lt;/p&gt;

&lt;p&gt;There's a ton of data, so we'll have plenty of charts and a brief overview without digging too deep into each one.&lt;/p&gt;

&lt;p&gt;If you want access to a Jupyter Notebook with a more detailed analysis, you can take a look at &lt;a href="https://github.com/DaniilRoman/github_innovation_graph_analysis" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notes about the data&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certainly, it's important to remember that this data isn't the absolute truth about the current state of the global tech scene. However, we all know that GitHub is a big deal in our tech world. In this article, I'm working on the assumption that this data gives us a good idea of what's happening in the tech world across the globe. Plus, I'll be sharing a few conclusions that strongly support the idea that this data is quite reliable, and we can trust it. 
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Common numbers
&lt;/h1&gt;

&lt;p&gt;First and foremost, let's begin by examining some key figures. Specifically, we'll focus on the growth of organizations and developers on GitHub, as well as the overall contributions to third countries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79dzlbpanq3ho6apprvi.png" alt=" " width="554" height="455"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39j3ybmtohd24hw8yiu8.png" alt=" " width="567" height="455"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvom24whcb7n3gkpx1cbs.png" alt=" " width="554" height="455"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In essence, the count of developers worldwide has seen a significant increase, and GitHub contributions have doubled over the past three years. Additionally, the number of organizations has grown by more than one and a half.&lt;/p&gt;

&lt;p&gt;This information provides valuable insights into the overall growth of the global tech sector. It also allows us to identify outliers among countries that have experienced notably lower or higher growth rates compared to these data trends.&lt;/p&gt;

&lt;h1&gt;
  
  
  Numbers of developers and organisations
&lt;/h1&gt;

&lt;p&gt;Sure, let's dig deeper into the numbers of developers and organizations.&lt;/p&gt;

&lt;p&gt;Check out the breakdown by country for the distribution of developers and organizations worldwide.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9b52ufjaybt7eperh90.png" alt=" " width="800" height="536"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8jnvw37tkiqnsonihj7.png" alt=" " width="800" height="532"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Charts explanations&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You're likely to encounter many similar charts. In these charts, the "absolute" numbers show the percentage of the total (100%) on the Y-axis, with the change in this percentage from 2020 represented on the X-axis.

For example, in the chart below, we can focus on India (IN). This means that currently, approximately 7.5% of all organizations created on GitHub are from India. This number has increased by around 0.5% since 2020.

If the values were relative instead, it would signify, for instance, that 100,000 new organizations were established in India since 2020 (on the Y-axis), with a 5% increase since that year (on the X-axis).

The colors in these charts are used for grouping purposes, although the boundaries between groups can vary widely. They are primarily for visualization and don't carry specific conclusions.
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The trends for organisation growth per country:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lyskyn59g8noltnhzqf.png" alt=" " width="700" height="568"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmucqrmcqse0p35d1un30.png" alt=" " width="691" height="568"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The trends for developers growth per country:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8nrn1xj5dgv1cd6jd321.png" alt=" " width="700" height="568"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiedvdy5wu8idp32gdsf5.png" alt=" " width="691" height="568"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The main takeaway here is quite straightforward: the number of organizations is increasing at a quicker pace in developed countries, while on the flip side, the number of developers is growing faster in developing countries.&lt;/p&gt;

&lt;p&gt;Moreover, the United States, India, and China are undeniably the top leaders in both of these measures. Even though there's a noticeable declining trend in China, it's worth considering the possibility that developers and organizations might be shifting to different Git providers or relocating to other countries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contribution and consumption on GitHub per region
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd0pt1gfcti3vs2i1nhwa.png" alt=" " width="736" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmqffeib75q1ivmp2y16.png" alt=" " width="736" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In Developed Europe, which includes the EU, the UK, and some other countries, they account for half of the total contributing to other countries. This implies that if a company is considering hiring developers from abroad, it's more likely they'll hire from this region. However, please note that this is a general global observation.&lt;/p&gt;

&lt;p&gt;On the flip side, if you're seeking employment in a company from a foreign country, chances are it will be a company from the US or Canada.&lt;/p&gt;

&lt;p&gt;In East Asia (encompassing China, India, Hong Kong, Japan, and Singapore), the tech sector is growing rapidly and already has a significant global presence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Insights per region
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7nwts6kc2zy1fal9msi.png" alt=" " width="689" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpj11cwax0gsz1zd597r.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16879lrw69q01ad2p84t.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0rib8l4ez69qjzwp2kv.png" alt=" " width="687" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fow37j6aqz1dxsw2kjn6q.png" alt=" " width="715" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp789xxdh771psz8wavz9.png" alt=" " width="718" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0w44lpu2i0g3m6sjcgl.png" alt=" " width="699" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsgj9bo1dtymwh766kbqs.png" alt=" " width="700" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In nearly every region, we see just two or three prominent leaders.&lt;/p&gt;

&lt;p&gt;Furthermore, these leading figures are gradually losing their dominance in their respective regions, except for &lt;code&gt;India&lt;/code&gt; in East Asia and &lt;code&gt;Nigeria&lt;/code&gt; in Africa.&lt;/p&gt;

&lt;p&gt;On average, the fastest-growing leaders in each region represent no more than &lt;code&gt;10% contribution&lt;/code&gt; of that region.&lt;/p&gt;

&lt;p&gt;In contrast, Developed Europe (comprising the EU, UK, and some other countries) stands out as the most diversified region. This suggests that it might be beneficial to further dissect this region into smaller sub-regions for a more detailed analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contribution on GitHub per country
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The two charts below illustrate the rate of growth for each country. They show the relative change in contribution between 2020 and 2023.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxdlr4h3y0kuif6ymtd3.png" alt=" " width="753" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszh150mm63besukivrgt.png" alt=" " width="800" height="508"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The two charts below present the current position in global contribution. They indicate the percentage of the total contribution, representing 100%, and the percentage of change between 2020 and 2023.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1e0rhezmodzohxranbi.png" alt=" " width="689" height="568"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5lp5avfkpp0afcamgn4.png" alt=" " width="711" height="568"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For instance, if we examine Canada and India, we notice that India has actually contributed a larger amount in relative terms over the past three years. However, in absolute figures, there is still a &lt;code&gt;gap of 2%&lt;/code&gt; when compared to the global total.&lt;/p&gt;

&lt;p&gt;Nevertheless, it's important to highlight that 2% of the global contribution is quite significant. This gap could be closed if, for example, the entire tech sector of Italy or Brazil were to relocate to India.&lt;/p&gt;

&lt;h1&gt;
  
  
  Consumption from third countries
&lt;/h1&gt;

&lt;p&gt;From left to right, each image offers a distinct level of zoom to display the distribution from various perspectives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Absolute values
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8c6fmyscxy2miku84bf.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7kzhj2ff4zr988axxlgv.png" alt=" " width="678" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oydwmh4r44zmendpi5j.png" alt=" " width="691" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Relative growth
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyo1bm54a3l5t0m49us4.png" alt=" " width="700" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2m661wa43mxyu6ecmn4p.png" alt=" " width="721" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkew9yuv2k3hondz9ta3i.png" alt=" " width="723" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The primary insight here is undoubtedly that the US is the predominant employer in this context. The US consumes nearly half of the global contributions on GitHub.&lt;/p&gt;

&lt;p&gt;Additionally, Georgia has shown remarkable changes in contributions since 2020, which we'll investigate further in detail shortly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rise of Poland, Portugal and Spain in Europe
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42if9jnsbu7j6yrymz9q.png" alt=" " width="687" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdaob2lji9v2t518zphk2.png" alt=" " width="721" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomriejynun3ej3o25nd2.png" alt=" " width="693" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtwahayd9w33oxa00jz1.png" alt=" " width="721" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Indeed, in many charts, these three countries stand out as fast-growing contributors from a contribution point of view. Each of them should be thoroughly researched individually, as there could be entirely different reasons for their remarkable growth.&lt;/p&gt;

&lt;p&gt;One possible assumption is that they have benefited from the fact that Europe remains a significant player in the tech sector. And these countries may offer a lower cost of living compared to other developed European nations.&lt;/p&gt;

&lt;p&gt;Furthermore, when we focus on Portugal, for example, we can find news about special tax incentives and visas for the tech sector, which have had a substantial impact on its growth. Various articles, such as &lt;a href="https://techcrunch.com/2021/01/07/lisbons-startup-scene-rises-as-portugal-gears-up-to-be-a-european-tech-tiger/?guccounter=1&amp;amp;guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&amp;amp;guce_referrer_sig=AQAAAIJr5GSWiQ3ODLzmuSimPpZQLtTGGhcgC-3MqQHXnUJaG9PXQB6fnCffYWE6ulFcCVNhPObUmv3FKVsPV4lXnxXa8h4Ki_u-Y1AUcuZDkrfyABzED60vFtvoxdV4zCkxUlweBUJ9EWxBYmH7397sXUY-vHfL6YiBjRLdyzvg09QD" rel="noopener noreferrer"&gt;this one&lt;/a&gt; from TechCrunch, support this observation and demonstrate the positive impact of such policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  China and Honk Kong
&lt;/h2&gt;

&lt;p&gt;We've seen numerous graphs that clearly depict a significant and noticeable decline in China's contribution, while concurrently, Hong Kong has experienced substantial growth.&lt;/p&gt;

&lt;p&gt;It's worth noting that reports indicate that companies are shifting from mainland China to Hong Kong, as evidenced in articles like &lt;a href="https://hongkongfp.com/2023/10/05/firms-already-in-hong-kong-among-those-drawn-by-govt-push-to-boost-tech-sector-up-to-80-from-mainland-china/" rel="noopener noreferrer"&gt;this one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The charts below display both contribution and consumption figures for several countries in the region, including China and Hong Kong.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1n0mzxz0dv42r75wwn6y.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm61t2k0lcdw0q3qg1aaz.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljjyx0f95glhca2nlq2z.png" alt=" " width="694" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5nw2p07ox50i0s6yt72.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flprfe82l84t31e11a0cs.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxp143lznfj39evkpdzf.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcpr0gl14eqog07jzkgin.png" alt=" " width="700" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j3rp75h7g7qtpfj8dpz.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hong Kong has a remarkable level of penetration to closest countries in the region. Additionally, Hong Kong has increased its presence by nearly 100% in Singapore and Taiwan.&lt;/p&gt;

&lt;h1&gt;
  
  
  Russia
&lt;/h1&gt;

&lt;p&gt;In early 2022, Russia invaded Ukraine, leading to a significant exodus of tech companies and professionals.&lt;br&gt;
The charts presented below could provide insights into where these companies and developers may have resettled. They may also reveal common trends and connections within the Russian tech sector.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtxyatlsxg9mzgob2qyw.png" alt=" " width="678" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa500ntws2mly1quw0ony.png" alt=" " width="719" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Following charts less interesting and generally just confirm assumption above. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbi4of2k0k5mkyw9fy0r.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwdqc5450k5j46fatjrfj.png" alt=" " width="678" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3zk0yfjzs6sjft565by.png" alt=" " width="724" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn7lmp4p8iyc6lvviaw8.png" alt=" " width="704" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This serves as yet another illustration and confirmation of the high mobility within the tech industry, which often surpasses that of other industries.&lt;/p&gt;

&lt;h1&gt;
  
  
  Georgia
&lt;/h1&gt;

&lt;p&gt;Georgia has become as the primary destination for Russian tech companies and professionals.&lt;br&gt;
Georgia's remarkable growth has made it one of the standout performers on global charts, particularly in terms of fast growth.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9rwxrd31oll2u121x56.png" alt=" " width="753" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmd5pv3cs55wzwavelv8f.png" alt=" " width="687" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the charts that follow, we can observe the primary connections. What's particularly interesting is that the number of connected countries has expanded significantly, growing from &lt;strong&gt;4&lt;/strong&gt; to &lt;strong&gt;36&lt;/strong&gt; since 2020.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvfiy8senob3krcydkbr.png" alt=" " width="713" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9bo3tvkteoz3ly8mr3g.png" alt=" " width="704" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Contribution to Georgia: total values changed from 3625 in 2020 to 52083 in 2023 = 1336%&lt;/p&gt;

&lt;p&gt;Contribution from Georgia: total values changed from 105 in 2020 to 14984 in 2023 = 14170%&lt;/p&gt;

&lt;h1&gt;
  
  
  US
&lt;/h1&gt;

&lt;p&gt;The subsequent charts reveal the resemblance between U.S. consumption and worldwide contribution to third countries, and they appear to be quite similar. This provides further confirmation of the fact that the U.S. is a major employer in the tech industry.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w30y1wjhttuzh3tbmkk.png" alt=" " width="724" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1qpnk6ovkpq981vj9zg.png" alt=" " width="724" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcpvni9l2ma0om9cvn8t.png" alt=" " width="753" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lhpbpqm23py6qc2iajc.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvq8vbeclyfl6i7u4j1tw.png" alt=" " width="686" height="547"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1y1qa7jrmadhohc82tny.png" alt=" " width="689" height="568"&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;The information we have mostly deals with countries that are heavily into using GitHub. However, in Russia for example, organizations have been favoring on-premise GitLab for quite a while. So, it's essential to realize that this data doesn't provide a complete picture of the entire tech landscape. But correlation between GitHub data and real world events prove this data to be a reliable source of truth.&lt;/li&gt;
&lt;li&gt;It's highly unlikely that the contribution of any country could increase by hundreds of percentage points without the influence of external factors.&lt;/li&gt;
&lt;li&gt;The United States is the primary employer in the tech sector, consuming nearly half of all GitHub contributions from third countries.&lt;/li&gt;
&lt;li&gt;Simultaneously, Europe stands as the primary activity contributor on GitHub to third countries.&lt;/li&gt;
&lt;li&gt;For the most part, this data prove many fairly obvious assumptions, such as the faster growth of organization numbers in developed countries and the quicker increase in developer numbers in developing nations.&lt;/li&gt;
&lt;li&gt;The tech economy is highly dynamic, and individuals and companies are quick to relocate when the need arises.&lt;/li&gt;
&lt;li&gt;Countries with a lower cost of living have proven to be more attractive to companies, resulting in a greater increase in both contribution and consumption when compared to countries with higher living costs.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>innovationgraph</category>
      <category>analytics</category>
    </item>
    <item>
      <title>How AI helped to chat with my grandma on Telegram</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Sat, 16 Sep 2023 21:35:26 +0000</pubDate>
      <link>https://dev.to/daniilroman/would-your-grandma-use-a-voice-chat-bot-hca</link>
      <guid>https://dev.to/daniilroman/would-your-grandma-use-a-voice-chat-bot-hca</guid>
      <description>&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;Have you ever wondered how to keep in contact with your elderly grandma who lives in another country? What if she's never used any messaging apps and doesn't even have a smartphone?&lt;/p&gt;

&lt;p&gt;Well, in this article, I'm going to share my story about overcoming this challenge 😁 and why I even ended up writing some code for it.&lt;/p&gt;

&lt;p&gt;By the end of the article, you'll understand why smart assistants, messengers, and chatbots come into play here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;I didn't begin with creating anything too complex, of course. Firstly, I simply handed her my old smartphone, where I removed all the icons except for Telegram, which is my primary messaging app. I increased the Telegram icon as much as possible and centered it on the screen. I even opened the dialog with myself there so that she could easily call me with just two clicks.&lt;/p&gt;

&lt;p&gt;And... that's it. I'm glad you took the time to read this. I put a lot of effort into this article, so thank you all, and see you next time!👋&lt;/p&gt;

&lt;p&gt;But, of course, that wasn't everything I did. In fact, it was just 20% of the work that yielded 80% of the results. But let's delve into the other 80%.&lt;/p&gt;

&lt;p&gt;Around that time, I introduced her to a home assistant (&lt;a href="https://yandex.ru/alice" rel="noopener noreferrer"&gt;Yandex's smart assistant&lt;/a&gt; - Alisa), and all of a sudden, she started using it quite extensively. This got me thinking, "What if I could chat with my grandma through this device in some way?"&lt;/p&gt;

&lt;p&gt;I personally prefer texting; sending a simple message is 10 times easier and faster than making a call. Plus, I might not always be available when my grandma wants to call, but I can respond to her messages within minutes whenever I have the time.&lt;/p&gt;

&lt;p&gt;In conclusion, this idea led me to connect the home assistant (Alisa) with Telegram&lt;/p&gt;

&lt;h2&gt;
  
  
  System design
&lt;/h2&gt;

&lt;p&gt;Why did I even think it was feasible to implement this in a timely manner? Well, I had no intention of starting from scratch using any programming language and APIs.&lt;/p&gt;

&lt;p&gt;No, no, no... "no-code" is my go-to solution in this case.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35ie24l9eo7ei90wd6nn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35ie24l9eo7ei90wd6nn.gif" alt="gif 1" width="480" height="480"&gt;&lt;/a&gt;&lt;br&gt;
(low code actually, but who cares) &lt;/p&gt;

&lt;p&gt;At that time, I worked at a company where we were developing a low-code platform for chat-bots, and this problem presented itself as the perfect use case to put my skills to the test.&lt;/p&gt;

&lt;p&gt;So, after a few iterations, I came up with this “design”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxcpar22b8tlpfg6z9d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffxcpar22b8tlpfg6z9d6.png" alt="high level system design" width="800" height="472"&gt;&lt;/a&gt;&lt;br&gt;
Each Alisa icon and Telegram icon represents a separate chat-bot.&lt;/p&gt;

&lt;p&gt;I opted to use two different chat-bots for Alisa to ensure that I don't have any NLP logic embedded within my chat-bot scenario. Instead, I've dedicated the NLP logic to the higher level within the Alisa framework, rather than incorporating it directly into the chat-bot.&lt;/p&gt;

&lt;p&gt;But what does this mean? If I had included NLP logic within my chat-bot, let's call it an app, I would receive plain text messages from my grandma and then route her questions or requests to the appropriate state, handler, or section of my chat-bot's code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb74hmrb0yblw1q0adnbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb74hmrb0yblw1q0adnbk.png" alt="monolith chat-bot" width="776" height="446"&gt;&lt;/a&gt;&lt;br&gt;
Instead, I rely on Alisa's built-in NLP capability, and when my chat-bot is activated, it signifies that the correct request from my grandma has been successfully recognized. In other words, the right chat-bot wouldn't even be triggered otherwise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3x46wp51u7tw0lsc6gke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3x46wp51u7tw0lsc6gke.png" alt="separated chat-bots" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
I use Google Sheets as a simple message queue (chosen mainly for its technical convenience). The Telegram bot writes to Google Sheets, and the &lt;code&gt;Alisa reading bot&lt;/code&gt; retrieves messages from there. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Alisa writing bot&lt;/code&gt; can communicate directly with the Telegram bot through the platform.&lt;/p&gt;

&lt;p&gt;The happy path is quite straightforward, actually. &lt;/p&gt;

&lt;p&gt;Here's how it goes: My grandma calls the Alisa chat-bot to send me a message. I received the message in Telegram and responded to her. My response is sent to Google Sheets. Then, my grandma can occasionally call the other Alisa chat-bot to read from Google Sheets. If there are any messages for her, they'll be voiced and then removed from Google Sheets.&lt;/p&gt;

&lt;p&gt;Pretty clear, isn't it?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq37g9i93qdk1h90z7lzh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq37g9i93qdk1h90z7lzh.gif" alt="gif 2" width="480" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The most exciting thing is that I managed to implement this idea in just one weekend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only minor disappointment was that I couldn't add a notification feature in Alisa (home assistant) to alert my grandma when she received a response from me. It seemed there was no way to send such notifications to the home assistant.&lt;/p&gt;
&lt;h2&gt;
  
  
  JAICP intro
&lt;/h2&gt;

&lt;p&gt;Let's delve a bit deeper, and here's a brief introduction to the platform I built upon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.jaicp.com/" rel="noopener noreferrer"&gt;JAICP&lt;/a&gt; is a cloud-based, low-code platform where you can create your chat-bot using a custom DSL mixed with JavaScript code. The DSL defines the structure of the chat-bots, while JavaScript empowers your bots with intelligence.&lt;/p&gt;

&lt;p&gt;If you're interested, you can find more information in the &lt;a href="https://help.just-ai.com/docs/en/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. There's tons of useful information covering all the functionalities the platform offers. &lt;/p&gt;

&lt;p&gt;What's particularly great in our case is that hosting costs us practically nothing. We'll have exactly one user for each chat-bot, and with such minimal traffic, it won't cost any expenses.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;We're going to dive deep into the codebase with detailed line-by-line explanations. If this isn't your cup of tea, feel free to skip this part.&lt;/p&gt;

&lt;p&gt;Of course, you can find all the code on &lt;a href="https://github.com/DaniilRoman/babushka-chat-bot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Naturally, we have to create our bots on the Telegram and Alisa side first in order to get tokens. I won't cover how to configure everything, instead, I will focus only on chat-bots coding part&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s have a closer look at Alisa’s bots as they’re more finely-grained compared to the Telegram bot.&lt;/p&gt;

&lt;p&gt;Below you’ll find all the code for the &lt;code&gt;writing Alisa bot&lt;/code&gt; and yes, it's just 14 lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;

    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BabushkaSay&lt;/span&gt;
        &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$regex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/start&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;What&lt;/span&gt; &lt;span class="nx"&gt;would&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;like&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Daniil&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NoMatch&lt;/span&gt;
        &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;noMatch&lt;/span&gt;
        &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;toSendRightNowTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2022-07-02T10:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;$pushgate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toSendRightNowTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SetBabushkaData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sayData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; 
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telegram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TLGRM_BOT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TLGRM_USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="nx"&gt;was&lt;/span&gt; &lt;span class="nx"&gt;sent&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned earlier, I don't include any NLP logic within my chat-bots. This means that when my grandma says, "Alisa, text a message to Daniil," Alisa is responsible for recognition. I only receive the &lt;code&gt;start command&lt;/code&gt; on the 4th line and respond with &lt;code&gt;What would you like to text to Daniil?&lt;/code&gt; on the next line.&lt;/p&gt;

&lt;p&gt;To retrieve all the text my grandma said, I make use of the system variable &lt;strong&gt;&lt;code&gt;$request.query&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;strong&gt;&lt;code&gt;$pushgate.createEvent&lt;/code&gt;&lt;/strong&gt; comes in handy here, allowing us to send the event directly to the Telegram chat-bot. You can find more information about this method in the &lt;strong&gt;&lt;a href="https://help.just-ai.com/docs/en/JS_API/built_in_services/pushgate/createEvent" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is some configuration happening behind the scenes, but in general, there are only 14 lines of code that handle the task quite effectively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's almost the same for the &lt;code&gt;reading Alisa's bot&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;

    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BabushkaSay&lt;/span&gt;
        &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$regex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/start&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;danilAnswers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleSheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readDataFromCells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INTEGRATION_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;danilAnswers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;$reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Danil haven't had time to respond yet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;$reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Danil is talking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;danilAnswers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
                &lt;span class="nx"&gt;$reactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                                &lt;span class="nx"&gt;$integration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;googleSheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeDataToCells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INTEGRATION_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nx"&gt;$secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
                    &lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GoogleSheetError&lt;/span&gt;
        &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Couldn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;t get the data, call to Danil
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;We read from Google Sheets&lt;/li&gt;
&lt;li&gt;If there's anything in Google Sheets, we send a response to Alisa to speak. &lt;/li&gt;
&lt;li&gt;Clear the data in Google Sheets&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;$integration.googleSheets&lt;/code&gt; provides functionality to read and write to Google Sheets (probably it’s quite obvious)&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;$reactions.answer&lt;/code&gt; is used to send a message to the connected channel, which in our case is Alisa. &lt;/p&gt;

&lt;p&gt;Here, we do have a few more lines of code, but everything remains at a high level and is quite readable, which is great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;So, what happened next you might ask? &lt;br&gt;
Let's recall that my granny was already a “strong” Telegram user at that point. I was excited to introduce her to my project, but before anything else, I had to sell her on the idea, to convince her to give it a try.&lt;br&gt;
I spent just one weekend to implement that and all my emotions were absolutely fresh. &lt;/p&gt;

&lt;p&gt;On the day "X", I traveled from another city to visit her. I talked to my grandma about Telegram and the Alisa home assistant to know her thoughts about new stuff. And then, I presented her with this chat-bot and the new way to chat with me anytime. &lt;/p&gt;

&lt;p&gt;When I showed my grandma what I had created, I was bursting with pride and eagerly anticipated how she would use it. However, reality hit hard – she refused to use it because she found it too difficult.&lt;/p&gt;

&lt;p&gt;The end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F919bv91sqf6pdy503uq9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F919bv91sqf6pdy503uq9.jpg" alt="the end picture" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
There were two main reasons actually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The chat-bot cold start &lt;br&gt;
The platform functions much like any other lambda or serverless function, and it experiences a cold start like any other.&lt;br&gt;
While this might not be a significant issue with a high volume of users who use the chat-bot frequently (as it would always be cached), or if it were a text-based chat-bot like the ones on Telegram. However, it presented a challenge in our case.&lt;br&gt;
With only one user and a voice-based chat-bot, there were occasions when the chat-bot took too long to start for my grandma. She became confused when she didn't receive any responses from Alisa and eventually gave up. There were even times when the bot responded with a timeout error, which was a frustrating experience for her. She doesn’t have the mindset that if something didn't work initially, it could be retried or restarted. From her perspective, if something didn't work, it was broken beyond repair.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Alisa’s bots voice UX&lt;br&gt;
To activate an Alisa bot, you have to say something like "Alisa, enable bot XXX." Then, you have to wait for a moment to receive a response from the chat-bot itself (this is where the chat-bot's cold start occurs). In my case, the chat-bot responds with "What do you want to send to Daniil?" as a welcome phrase. Only after this, my grandma can send the message to me.&lt;br&gt;
This process involved several steps for her, and it was a longer and more complicated journey compared to simply making a phone call.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What could be next steps for this chat-bot system?
&lt;/h2&gt;

&lt;p&gt;So, in the end, we received feedback from customers, and it appears there's no real demand for this product 😅&lt;br&gt;
Or, to put it another way, my grandma simply said she's not going to use this darn thing 🙃&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what could we potentially do to improve the situation if our MVP was to be successful?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Resolving the cold start issue is a fairly manageable task. It would, of course, require writing more code to integrate everything and creating and deploying my own service, which would need to be available 24/7 (perhaps even a very lightweight lambda function in the cloud could suffice). However, the outcome would be a straightforward service where I could store everything, possibly even locally, and seamlessly route messages between Alisa and Telegram, ensuring a smoother user experience.&lt;/p&gt;

&lt;p&gt;The second issue is a bit trickier. We have to react to the &lt;code&gt;/start&lt;/code&gt; event in any case, even if we don't respond with anything, and only then we can receive the text to send to Telegram.&lt;/p&gt;

&lt;p&gt;This means we cannot eliminate the first step entirely and be ready to receive the text as soon as my grandma begins speaking.&lt;/p&gt;

&lt;p&gt;We could make an assumption that as soon as the skill is enabled, we are ready to accept text to send because there's almost no cold start. This would eliminate the need to send any response for the &lt;strong&gt;&lt;code&gt;/start&lt;/code&gt;&lt;/strong&gt; event to notify my grandma. However, it's important to note that this can only be an assumption and not a strict rule. By making this assumption, there's a potential risk of losing some data.&lt;/p&gt;

&lt;p&gt;Nevertheless, there are ways to address this problem and make this idea accessible even for my elderly grandmother.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened next, you may wonder?
&lt;/h2&gt;

&lt;p&gt;The idea of chatting with grandma through chat-bots faded away, and we went back to good old voice calls via Telegram. However, that wasn't the end of the story 🙃 &lt;/p&gt;

&lt;p&gt;All of a sudden, she was able to call me, but for some reason, she couldn't answer my calls.&lt;br&gt;
So, here's the latest chapter about how I communicate with grandma over the Internet.&lt;/p&gt;

&lt;p&gt;As you might recall, I only have two options: 1 - Telegram and 2 - Alisa home assistant. If Telegram didn't work out, it was time to explore all available options to work with the home assistant, excluding a custom chat-bot. &lt;/p&gt;

&lt;p&gt;And there was one option that came to light. I discovered that I could call the home assistant directly through a special messenger designed specifically for this home assistant. You can't call anyone through the home assistant, but you can answer a call and talk through the home assistant, which was quite remarkable. It was something I couldn't even have imagined before.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick reminder: The options available to you are, of course, strictly dependent on the ecosystem you are using.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, in the end, we didn't end up using those chat-bots; we simply call each other. She calls through Telegram, and I call her back through the special messenger, and she answers the call by using the home assistant. This setup still works perfectly for us 😄 &lt;/p&gt;

&lt;p&gt;Even though the chat-bot idea didn't work out as expected, it was an incredibly interesting and educational journey. I gained hands-on experience in creating and validating a quick MVP. I highly recommend this to everyone, especially software developers. Finding your own ideas, creating, and validating end-to-end MVPs can give you tons of insights about product development in general. &lt;/p&gt;

&lt;p&gt;In my case, I understood how crucial UX (User Experience) is and how a cold start can vanish all your efforts.&lt;/p&gt;

&lt;p&gt;So, as a final word, I’d like to remind you of a simple and hopefully well-known thought: All aspects of software development are equally important!&lt;/p&gt;

&lt;p&gt;And don’t forget to call your grandma 😄&lt;/p&gt;

</description>
      <category>chatbot</category>
      <category>cloud</category>
      <category>serverless</category>
      <category>nocode</category>
    </item>
    <item>
      <title>Re2j instead of default regEx in Java: when and how to use it</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Thu, 30 Jun 2022 12:21:43 +0000</pubDate>
      <link>https://dev.to/daniilroman/re2j-instead-of-default-regex-in-java-when-and-how-to-use-it-5bgn</link>
      <guid>https://dev.to/daniilroman/re2j-instead-of-default-regex-in-java-when-and-how-to-use-it-5bgn</guid>
      <description>&lt;p&gt;Hi everyone! I’m Daniil and I’m a Java developer at &lt;a href="https://just-ai.com/en/" rel="noopener noreferrer"&gt;Just AI&lt;/a&gt;. In this article, I’m going to tell you how we faced a backtracking issue for regex and how we solved this problem using re2j. &lt;br&gt;
But most importantly, I’ll cover both advantages and disadvantages of this library. &lt;/p&gt;

&lt;p&gt;Here’s what you’ll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How we got a problem with backtracking in a regular expression.&lt;/li&gt;
&lt;li&gt;How we dealt with it using re2j.&lt;/li&gt;
&lt;li&gt;What not obvious issues can you face while using this library?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What is backtracking?
&lt;/h2&gt;

&lt;p&gt;There’s been a lot of discussions around this problem. And for sure, I’m not the first or the last person to raise this question.&lt;/p&gt;

&lt;p&gt;In a few words, this problem appears under specific circumstances and causes regex matching to run for a really long time.&lt;/p&gt;

&lt;p&gt;Also, the real problem starts when you have no control over the regex your customers create and handling customer data is a very sensitive task .  &lt;/p&gt;

&lt;p&gt;You can checkout &lt;a href="https://www.regular-expressions.info/catastrophic.html" rel="noopener noreferrer"&gt;this article&lt;/a&gt; for more details and dive deeper into the subject. &lt;/p&gt;
&lt;h2&gt;
  
  
  How we addressed this issue
&lt;/h2&gt;

&lt;p&gt;We’re developing JAICP - is a conversational AI platform that helps users create chatbots and often have to deal with customer information in conversations with bots.&lt;/p&gt;

&lt;p&gt;Of course, customer data can be quite sensitive. But we really care about privacy and obfuscate confidential information.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrhyq8axkj4usgl58vgt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrhyq8axkj4usgl58vgt.png" alt="Sensitive data" width="465" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv1xf4u7sg1001taimed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv1xf4u7sg1001taimed.png" alt="Masking result" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key moment here is that customers can set their own regex to obfuscate data and create a string as a substitution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiy81cqp8k5r4opfldpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiy81cqp8k5r4opfldpf.png" alt="log obfuscation ui" width="800" height="484"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscav01xgnbwkl2yw5nid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscav01xgnbwkl2yw5nid.png" alt="obfuscation example" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Let’s catch problem first
&lt;/h2&gt;

&lt;p&gt;Now that we know what our task is, let’s first take a look at a rather obvious solution with the standard Java regex.&lt;/p&gt;

&lt;p&gt;We have quite a common example of obfuscation and it’s also a basic pattern for our case. It is an email obfuscation. Let's substitute them with &lt;a href="mailto:xxxx@xxxx.xx"&gt;xxxx@xxxx.xx&lt;/a&gt; for example. Here is an email regex pattern for substitution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[a-z0-9!#$%&amp;amp;'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&amp;amp;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Instead of this regex, customers can write whatever they want.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see how long the following code will run with the standard java engine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;inputText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRandomLongWord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test@test.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Matcher&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;emailRegex&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"XXX@XXX.XX"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In this and the following examples, we’ll generate random text with target injections. In the example above, it’s an email address. It should look something like the following: &lt;code&gt;...UUID.randomUUID()+"test@test.com"+UUID.randomUUID()&lt;/code&gt;&lt;br&gt;
We call randomUUID 500 times and inject target text every 10 calls.&lt;br&gt;
I used an  i7-10510U CPU 1.80GHz × 8 machine and jdk 1.8.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Execution time - 36 ms&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;It’s quite fast. But, let’s see what happens when the target text hasn’t been injected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The text is the same, but now it looks like this: &lt;code&gt;UUID.random()+UUID.random()&lt;/code&gt;&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;inputText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRandomLongWord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Matcher&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;emailRegex&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"XXX"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Execution time - 8879 ms&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;🤔 We seem to have a backtracking problem now. &lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To fix it, we’ll use the &lt;a href="https://github.com/google/re2j" rel="noopener noreferrer"&gt;Re2j&lt;/a&gt; library. &lt;/p&gt;

&lt;p&gt;This library allows for running regexes in linear time. And it doesn’t use a standard &lt;a href="https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton" rel="noopener noreferrer"&gt;NFA&lt;/a&gt; approach for a regex engine, but &lt;a href="https://en.wikipedia.org/wiki/Deterministic_finite_automaton" rel="noopener noreferrer"&gt;DFA&lt;/a&gt; instead. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are articles like &lt;a href="https://www.geeksforgeeks.org/regular-expression-to-dfa/" rel="noopener noreferrer"&gt;this&lt;/a&gt; and &lt;a href="https://stackoverflow.com/questions/3978438/dfa-vs-nfa-engines-what-is-the-difference-in-their-capabilities-and-limitations" rel="noopener noreferrer"&gt;this&lt;/a&gt; one to help you find your way around it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s important to note that this library was invented exactly for the situations when we don’t know the regex to execute. If you’re facing the same issue, you definitely should give it a go.&lt;/p&gt;

&lt;p&gt;But is Re2j so good that it can fix the 9 seconds issue? Let’s see!&lt;/p&gt;

&lt;p&gt;To check it, we should just substitute our &lt;code&gt;Pattern&lt;/code&gt; with  &lt;code&gt;com.google.re2j.Pattern&lt;/code&gt; and that’s it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;re2j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emailRegex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRandomLongWord&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"XXX"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new execution time is &lt;code&gt;88 ms&lt;/code&gt; 🔥🔥🔥&lt;/p&gt;

&lt;p&gt;Amazing! I’m sure you are with me in wanting to call it a silver bullet and start to use this library everywhere.&lt;/p&gt;

&lt;p&gt;But first, let’s take a closer look at a few more things. &lt;/p&gt;

&lt;h2&gt;
  
  
  What about the performance?
&lt;/h2&gt;

&lt;p&gt;To measure performance, we will use the &lt;a href="https://openjdk.java.net/projects/code-tools/jmh/" rel="noopener noreferrer"&gt;JMH benchmark&lt;/a&gt;. It’s a popular open-source tool for such purposes. &lt;/p&gt;

&lt;p&gt;The benchmark body looks the same as in the example above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Benchmark&lt;/span&gt;
&lt;span class="nd"&gt;@BenchmarkMode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AverageTime&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Fork&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Warmup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Measurement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;standartRegexWithEmailDataBenchmark&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyState&lt;/span&gt; &lt;span class="n"&gt;myState&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Blackhole&lt;/span&gt; &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;myState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;standartEmailRegex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;emailInputTextWords&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;replaceAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"XXX"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;standartEmailRegex&lt;/code&gt; is &lt;code&gt;java.util.regex.Pattern.compile(TestUtils.emailRegex)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The average &lt;code&gt;replaceAll&lt;/code&gt; execution time for the pattern with email injection is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default java pattern - &lt;code&gt;0.004 second&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re2j pattern - &lt;code&gt;0.002 second&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can see little performance advantages of the re2j over the default pattern. But the main thing is the re2j result wasn’t worse then the default one. And that means we can use it even for call-intensive parts of our program. &lt;/p&gt;

&lt;h2&gt;
  
  
  What about the big dictionary?
&lt;/h2&gt;

&lt;p&gt;The re2j performs well in practice. But it’s worth checking a case where we have a big dictionary of words to match with the target text.&lt;/p&gt;

&lt;p&gt;Let’s take a look at an example with the US cities: Boston, Chicago, Washington...&lt;br&gt;&lt;br&gt;
It transfers to regex like Boston|Chicago|Washington...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Of course, we use different data at work. But as an example, I took a list of the US cities from this &lt;a href="https://www.britannica.com/topic/list-of-cities-and-towns-in-the-United-States-2023068" rel="noopener noreferrer"&gt;website&lt;/a&gt;. I got it using the following js code: &lt;br&gt;
&lt;code&gt;Array.from(document.getElementsByClassName("topic-content pt-sm-15")[0].getElementsByTagName("section")).slice(1).flatMap(x =&amp;gt; Array.from(x.children[1].children)).map(x =&amp;gt; x.outerText)&lt;/code&gt;&lt;br&gt;
 This resulted in a dictionary with  &lt;code&gt;1,950&lt;/code&gt; items. It’s smaller than the one we have in production but it’s still quite big.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, we have a default &lt;code&gt;java.util.regex&lt;/code&gt; Pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"|"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usCitiesAndTowns&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;com.google.re2j&lt;/code&gt; Pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;re2j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Pattern&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"|"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usCitiesAndTowns&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use the JMH again.&lt;/p&gt;

&lt;p&gt;An average &lt;code&gt;replaceAll&lt;/code&gt; execution time for the pattern with cities injection is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default java pattern - &lt;code&gt;0.147 second&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re2j pattern - &lt;code&gt;0.625 second&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It means  &lt;code&gt;Re2j&lt;/code&gt;  is  &lt;code&gt;4 times slower&lt;/code&gt; then the default java regex😲&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about the pattern size?
&lt;/h2&gt;

&lt;p&gt;It’s not an obvious one but let’s check compiled size of our pattern.  &lt;/p&gt;

&lt;p&gt;Could something like &lt;code&gt;Pattern.compile("\\d{2}")&lt;/code&gt; be a problem?&lt;/p&gt;

&lt;p&gt;To see,  we’ll use  &lt;code&gt;ObjectSizeCalculator.getObjectSize&lt;/code&gt; from &lt;code&gt;nashorn&lt;/code&gt; package. It’s not available in versions newer than JDK 8, but in version 8 it’s quite a good way to measure an object size in runtime.  &lt;/p&gt;

&lt;p&gt;So, we’ll measure the following code:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Pattern.compile(String.join("|", TestUtils.usCitiesAndTowns)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;TestUtils.usCitiesAndTowns&lt;/code&gt; is that big dictionary from the example above.&lt;/p&gt;

&lt;p&gt;The final result should be something like: &lt;code&gt;ObjectSizeCalculator.getObjectSize(Pattern.compile(String.join("|", TestUtils.usCitiesAndTowns)))&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;com.google.re2j.Pattern&lt;/code&gt; - 1026872 bytes (~1mb)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;java.util.regex.Pattern&lt;/code&gt; - 197608 bytes (~0.2mb)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We got a five-fold difference!!! 🦾&lt;/p&gt;

&lt;p&gt;The re2j pattern spends 5x more memory then the default one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even our big but not gigantic dictionary uses up to 1 mb of memory. But the real-life dictionary for such cases could be much bigger. And it could be duplicated to several services and instances. So, our pattern could take a lot of memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We’ve discussed the re2j from different sides and mentioned some obscure but important disadvantages. &lt;/p&gt;

&lt;p&gt;To sum it all up:&lt;/p&gt;

&lt;p&gt;The main goal of this library is to solve long execution issues for cases with regex coming from the outside.  &lt;/p&gt;

&lt;p&gt;When using this library make sure to test it on your data and for your cases because: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There could be opposite performance results.&lt;/li&gt;
&lt;li&gt;The Compiled Pattern could use more memory which may cause problems.&lt;/li&gt;
&lt;li&gt;Re2j doesn’t support all sets of regex abilities from the default java implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P.S.&lt;br&gt;
The entire piece of code is available on &lt;a href="https://github.com/DaniilRoman/re2j_test" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>kotlin</category>
      <category>regex</category>
      <category>re2j</category>
    </item>
    <item>
      <title>Maven plugin to check dependency versions</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Wed, 22 Jun 2022 06:22:49 +0000</pubDate>
      <link>https://dev.to/daniilroman/maven-plugin-to-check-dependencies-versions-318m</link>
      <guid>https://dev.to/daniilroman/maven-plugin-to-check-dependencies-versions-318m</guid>
      <description>&lt;p&gt;Hi everyone! In this post I’m gonna show you why you have to check your dependency versions in your maven project and how you can do it.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Some time ago I got the bug at work that forced me to spend some hours to understand what's going on. And when I finally realised the cause I was surprised, the bug was because we had dynamic changing version for some dependency in our maven project. This dependency had the &lt;code&gt;LATEST&lt;/code&gt; version. &lt;/p&gt;

&lt;p&gt;And I had a thought: Why we don’t have step in our ci pipeline to check such issues? And Why I haven't even heard about such tool?&lt;/p&gt;

&lt;p&gt;So In that moment I decided to find or create maven plugin to fail our build if we have dynamic changing versions of dependencies. Such as &lt;code&gt;LATEST&lt;/code&gt;, &lt;code&gt;RELEASE&lt;/code&gt; or something like &lt;code&gt;[1.5,)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When could such a case happen? Just accidentially. Or by copy pasting the code. Of course it should be detected in code review. But it could easilly miss in the fast development step. &lt;/p&gt;

&lt;p&gt;I can't imagine the situation when such dynamic changing versions could be helpful. If you have one, share this in the comments. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why maven?
&lt;/h2&gt;

&lt;p&gt;There are not only maven to build java projects. Gradle is on the way to mass adoption. And some things we can make really easily with Gradle comparing to maven. But maven is still the most popular one. Specially for big and old enterprise companies. And when I got this problem we used maven for our projects. So it is why I'm showing how to solve it using maven.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research
&lt;/h2&gt;

&lt;p&gt;I started to search existing plugins. And surprising for me I didn’t find them.&lt;br&gt;
The most closest to what I wanted was &lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requireUpperBoundDeps.html" rel="noopener noreferrer"&gt;requireUpperBoundDeps&lt;/a&gt; rule.&lt;/p&gt;

&lt;p&gt;But the &lt;code&gt;&amp;lt;version&amp;gt;LATEST&amp;lt;/version&amp;gt;&lt;/code&gt; not a problem for a verification of this plugin rule. The plugin just verify dependency has the latest version that exists.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;1.5&lt;/code&gt; version of dependecy available and you run &lt;code&gt;mvn install&lt;/code&gt; with this plugin, project’ll be successfully builded. But If you set &lt;code&gt;1.4&lt;/code&gt; explicitly, build’ll failed. &lt;/p&gt;

&lt;p&gt;So it totally is not what I wanted to achieve.  &lt;/p&gt;

&lt;p&gt;And also I found exactly what I was looking for but for plugins. It is a &lt;a href="https://maven.apache.org/enforcer/enforcer-rules/requirePluginVersions.html" rel="noopener noreferrer"&gt;requirePluginVersions&lt;/a&gt; rule.&lt;/p&gt;
&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;Idea is simple. Let's have regexes about incorrect dependencies versions to match. And we will just check every dependency’s version. And if it matches with any regex we’ll throw the exception to fail build.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;To implement this idea we can make maven plugin from scratch. Maven has ability to make it from maven-core library. You can find good tutorial how to make it &lt;a href="https://www.baeldung.com/maven-plugin" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;But there much more actions to make simple strings check by regex. Do we have more simple way to do it or not?&lt;/p&gt;

&lt;p&gt;The answer is yes. We have a maven enforcer plugin where we can add custom rules.&lt;/p&gt;

&lt;p&gt;You can find instructions how to do this &lt;a href="https://maven.apache.org/enforcer/enforcer-api/writing-a-custom-rule.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The following code is just enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;MavenProject&lt;/span&gt; &lt;span class="n"&gt;mavenProject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EnforcerRuleUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMavenProject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dependency&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mavenProject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDependencies&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dependency&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVersion&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;isRestrictedVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;restrictedVersionRegexes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;anyMatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isRestrictedVersion&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;restrictedVersionStrings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EnforcerRuleException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Dependency %s has restricted version %s."&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using relates to surface plugin. You can add specific dependency and custom rule to use this functionality. &lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-enforcer-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.DaniilRoman&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;dependency_restricted_version_checker&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;enforce&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;rules&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;myCustomRule&lt;/span&gt; &lt;span class="na"&gt;implementation=&lt;/span&gt;&lt;span class="s"&gt;"io.github.daniilroman.versionchecker.RestrictDependencyVersionRule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/myCustomRule&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/rules&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;enforce&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's all. Now when you run &lt;code&gt;mvn install&lt;/code&gt; it run enforcer plugin that run this custom rule. &lt;/p&gt;

&lt;p&gt;As a result if your junior developer add such a dangerous change &lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.testng&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;testng&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;RELEASE&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you miss this in code review stage. Such checker’ll help you to protect the codebase. &lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article we showed why checking dependency versions can helpful. How to implement it for maven using enforcer plugin and how to add it in your projects. &lt;/p&gt;

&lt;p&gt;That's all for today, stay tuned and don’t forget to check your code. &lt;/p&gt;

&lt;p&gt;P.S. You can find the whole code on &lt;a href="https://github.com/DaniilRoman/dependency_restricted_version_checker" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>maven</category>
      <category>kotlin</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Designing an application with Redis as a data store. What? Why?</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Sat, 30 Apr 2022 07:57:21 +0000</pubDate>
      <link>https://dev.to/daniilroman/designing-an-application-with-redis-as-a-data-store-what-why-57e3</link>
      <guid>https://dev.to/daniilroman/designing-an-application-with-redis-as-a-data-store-what-why-57e3</guid>
      <description>&lt;h2&gt;
  
  
  1) Introduction
&lt;/h2&gt;

&lt;p&gt;Hello everyone! Many people know what Redis is, and if you don’t know, &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;the official site&lt;/a&gt; can bring you up to date.&lt;br&gt;
For most Redis is a cache and sometimes a message queue.&lt;br&gt;
But what if we go a little crazy and try to design an entire application using only Redis as data storage? What tasks can we solve with Redis?&lt;br&gt;
We will try to answer these questions, in this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we won't see here?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every Redis data structure in detail won't be here. For what purposes you should read special articles or documentation.&lt;/li&gt;
&lt;li&gt;Here will also be no production-ready code that you could use in your work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What we'll see here?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We'll use various Redis data structures to implement different tasks of a dating application.&lt;/li&gt;
&lt;li&gt;Here will be Kotlin + Spring Boot code examples.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwli1nupbwsql7vbqqex6.png" alt="Intro image" width="552" height="391"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  2) Learn to create and query user profiles.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For the first, let's learn how to create user profiles with their names, likes, etc.&lt;/p&gt;

&lt;p&gt;To do this, we need a simple key-value store. How to do it? &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e6y7xr153v9rixg08ye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e6y7xr153v9rixg08ye.png" alt="a0" width="283" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simply. A Redis has a data structure - a hash. In essence, this is just a familiar hash map for all of us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redis query language commands can be found &lt;a href="https://redis.io/commands/hget" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://redis.io/commands/hmset" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
The documentation even has an interactive window to execute these commands right on the page. And the whole commands list can be found &lt;a href="https://redis.io/commands#hash" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;br&gt;
Similar links work for all subsequent commands that we will consider.&lt;/p&gt;

&lt;p&gt;In the code, we use RedisTemplate almost everywhere. This is a basic thing for working with Redis in the Spring ecosystem.&lt;/p&gt;

&lt;p&gt;The one difference from the map here is that we pass "field" as the first argument. The “field” is our hash's name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;addUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hashOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;hashOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not found user by $userId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is an example of how it might look in Kotlin using Spring's libraries.&lt;/p&gt;

&lt;p&gt;All pieces of code from that article you can find on &lt;a href="https://github.com/DaniilRoman/a_tinder_like_app" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Updating user likes using Redis lists.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Great!. We have users and information about likes.&lt;/p&gt;

&lt;p&gt;Now we should find a way how to update that likes. &lt;/p&gt;

&lt;p&gt;We assume events can happen very often. So let's use an asynchronous approach with some queue. And we will read the information from the queue on a schedule.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxq9inog7yf588zugjt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxq9inog7yf588zugjt1.png" alt="a1" width="770" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis has a list data structure with &lt;a href="https://redis.io/commands#list" rel="noopener noreferrer"&gt;such&lt;/a&gt; a set of commands.
You can use Redis lists both as a FIFO queue and as a LIFO stack.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Spring we use the same approach getting ListOperations from RedisTemplate.&lt;/p&gt;

&lt;p&gt;We have to write to the right. Because here we are simulating a FIFO queue from right to left.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;putUserLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userLike&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userFrom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userLikeRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;listOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rightPush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USER_LIKES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're going to run our job on schedule.  &lt;/p&gt;

&lt;p&gt;We are simply transferring information from one Redis data structure to another. This is enough for us as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;processUserLikes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userLikes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserLikesLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;USERS_BATCH_LIMIT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLike&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;userLikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;updateUserLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User updating is really easy here. Give a hi to HashOperation from the previous part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateUserLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userLikeRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForHash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fromUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fromUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromLikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;toUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;UserNotFoundException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toUserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;toUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromLikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;userOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromUserId&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;fromUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toUserId&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;toUser&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we show how to get data from the list. We are getting that from the left. To get a bunch of data from the list we will use a &lt;code&gt;range&lt;/code&gt; method.&lt;br&gt;
And there is an important point. The range method will only get data from the list, but not delete it.&lt;/p&gt;

&lt;p&gt;So we have to use another method to delete data. &lt;code&gt;trim&lt;/code&gt; do it. (And you can have some questions there).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getUserLikesLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ListOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userLikeRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USER_LIKES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt;&lt;span class="nf"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;filterIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;also&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;listOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;USER_LIKES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the questions are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to get data from the list into several threads?&lt;/li&gt;
&lt;li&gt;And how to ensure the data won't lose in case of error?
From the box - nothing. You have to get data from the list in one thread. And you have to handle all the nuances that arise on your own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4) Sending push notifications to users using pub/sub
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Keep moving forward! &lt;br&gt;
We already have user profiles. We figured out how to handle the stream of likes from these users.&lt;/p&gt;

&lt;p&gt;But imagine the case when you wanna send a push notification to a user the moment we got a like.&lt;br&gt;
What are you gonna do?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfbnkhbeeww2vhxq1d8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfbnkhbeeww2vhxq1d8a.png" alt="a2" width="576" height="447"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We already have an asynchronous process for handling likes, so let's just build sending push notifications into there.
We will use WebSocket for that purpose, of course. And we can just send it via WebSocket where we get a like. But what if we wanna execute long-running code before sending? Or what if we wanna delegate work with WebSocket to another component?&lt;/li&gt;
&lt;li&gt;We will take and transfer our data again from one Redis data structure (list) to another (&lt;a href="https://redis.io/commands#pubsub" rel="noopener noreferrer"&gt;pub/sub&lt;/a&gt;).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;processUserLikes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userLikes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserLikesLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;USERS_BATCH_LIMIT&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isLike&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nf"&gt;pushLikesToUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLikes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;userLikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;updateUserLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;pushLikesToUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLikes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;GlobalScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dispatchers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="n"&gt;userLikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;pushProducer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PushProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;redisTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RedisTemplate&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pushTopic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChannelTopic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;objectMapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserLike&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;redisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convertAndSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushTopic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeValueAsString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLike&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The listener binding to the topic is located in the &lt;a href="https://github.com/DaniilRoman/a_tinder_like_app/blob/a2caba4d01a63c8286a8540685013bb6ccddc5a7/src/main/kotlin/com/example/redisdemo/configuration/RedisConfiguration.kt#L46" rel="noopener noreferrer"&gt;configuration&lt;/a&gt;.&lt;br&gt;
Now, we can just take our listener into a separate service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PushListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;objectMapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;MessageListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;log&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KotlinLogging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userLikeMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// websocket functionality would be here&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received: ${objectMapper.readValue(userLikeMessage.body, UserLike::class.java)}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5) Finding the nearest users through geo operations.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We are done with likes. But what about the ability to find the closest users to a given point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79i9ghq1y7n1dvx7o67b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79i9ghq1y7n1dvx7o67b.png" alt="a3" width="261" height="129"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GeoOperations will help us with this. We will store the key-value pairs, but now our value is user coordinate. 
To find we will use the &lt;code&gt;[radius](https://redis.io/commands/georadius)&lt;/code&gt; method. We pass the user id to find and the search radius itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Redis return result including our user id.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getNearUserIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Double&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;geoOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;GeoOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForGeo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;geoOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;USER_GEO_POINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RedisGeoCommands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DistanceUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;KILOMETERS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6) Updating the location of users through streams
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We implemented almost everything that we need. But now we have again a situation when we have to update data that could modify quickly.&lt;/p&gt;

&lt;p&gt;So we have to use a queue again, but it would be nice to have something more scalable. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjynvk8jhpubyueas1tw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjynvk8jhpubyueas1tw.png" alt="a4" width="800" height="235"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis streams can help to solve this problem.&lt;/li&gt;
&lt;li&gt;Probably you know about Kafka and probably you even know about Kafka streams, but it isn't the same as Redis streams. But Kafka itself is a quite similar thing as Redis streams. It is also a log ahead data structure that has consumer group and offset. 
This is a more complex data structure, but it allows us to get data in parallel and using a reactive approach. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the Redis stream &lt;a href="https://redis.io/topics/streams-intro" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Spring has ReactiveRedisTemplate and RedisTemplate for working with Redis data structures. It would be more convenient for us to use RedisTemplate to write the value and ReactiveRedisTemplate for reading. If we talk about streams. But in such cases, nothing will work.&lt;br&gt;
If someone knows why it works this way, because of Spring or Redis, write in the comments.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;publishUserPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userPointRecord&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ObjectRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;USER_GEO_STREAM_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reactiveRedisTemplate&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opsForStream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPointRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Send RecordId: $it"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our listener method will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserPointsConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userGeoService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserGeoService&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;StreamListener&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ObjectRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;userGeoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addUserPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just move our data into a geo data structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  7) Count unique sessions using HyperLogLog.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;And finally, let's imagine that we need to calculate how many users have entered the application per day.&lt;/li&gt;
&lt;li&gt;Moreover, let's keep in mind we can have a lot of users. So, a simple option using a hash map is not suitable for us because it will consume too much memory. 
How can we do this using fewer resources?&lt;/li&gt;
&lt;li&gt;A probabilistic data structure HyperLogLog comes into play there. You can read more about it on the &lt;a href="https://en.wikipedia.org/wiki/HyperLogLog" rel="noopener noreferrer"&gt;Wikipedia page&lt;/a&gt;. 
A key feature is that this data structure allows us to solve the problem using significantly less memory than the option with a hash map.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqteoqlx3q5vpwsom7mg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqteoqlx3q5vpwsom7mg.png" alt="a5" width="470" height="137"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;uniqueActivitiesPerDay&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hyperLogLogOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HyperLogLogOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForHyperLogLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hyperLogLogOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TODAY_ACTIVITIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;userOpenApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hyperLogLogOps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HyperLogLogOperations&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringRedisTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;opsForHyperLogLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hyperLogLogOps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TODAY_ACTIVITIES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo706j6bnao9e4vxj3852.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo706j6bnao9e4vxj3852.png" alt="Conclusion image" width="552" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8) Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at the various Redis data structures. Including not so popular geo operations and HyperLogLog. &lt;br&gt;
We used them to solve real problems.&lt;/p&gt;

&lt;p&gt;We almost designed Tinder, it is possible in FAANG after this)))&lt;br&gt;
Also, we highlighted the main nuances and problems that can be encountered when working with Redis.&lt;/p&gt;

&lt;p&gt;Redis is a very functional data storage. And if you already have it in your infrastructure, it can be worth looking at Redis as a tool to solve your other tasks with that without unnecessary complications.&lt;/p&gt;

&lt;p&gt;PS:&lt;br&gt;
All code examples can be found on &lt;a href="https://github.com/DaniilRoman/a_tinder_like_app" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Write in the comments if you notice a mistake.&lt;br&gt;
Leave a comment below about such a way to describe using some technology. Do you like it or not?&lt;/p&gt;

&lt;p&gt;And follow me at Twitter:🐦&lt;a href="https://twitter.com/de____ro" rel="noopener noreferrer"&gt;@de____ro&lt;/a&gt; &lt;/p&gt;

</description>
      <category>redis</category>
      <category>kotlin</category>
      <category>java</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How we built Elasticsearch index</title>
      <dc:creator>Daniil Roman</dc:creator>
      <pubDate>Thu, 28 Apr 2022 12:31:42 +0000</pubDate>
      <link>https://dev.to/daniilroman/how-we-built-elasticsearch-index-8hh</link>
      <guid>https://dev.to/daniilroman/how-we-built-elasticsearch-index-8hh</guid>
      <description>&lt;h2&gt;
  
  
  What we wanted to do
&lt;/h2&gt;

&lt;p&gt;Hey everyone. I’m Daniil and I work at &lt;a href="https://just-ai.com/en/" rel="noopener noreferrer"&gt;Just AI&lt;/a&gt; where we are building a chatbot development platform. Our platform has its own DSL that enables users to create a chatbot scenario with less effort. That DSL helps describe bot behavior and enrich your bot with complex logic using javascript. Chatbot developers who work on our platform use our web IDE which supports this DSL.&lt;/p&gt;

&lt;p&gt;Bot scenarios can have a lot of files. And of course, you’ll need to search those files to find information.&lt;br&gt;
Let's take a look at what kind of search exactly we’ll get. To put it short, we wanted to get an IDE-like search. Where you can find a result using not only a part of the word but also regex, or a whole word match. And case sensitivity also should be there.&lt;/p&gt;

&lt;p&gt;Here’s what we got:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuskgmvwf71y6fnyqymb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuskgmvwf71y6fnyqymb0.png" alt="How it looks like" width="695" height="316"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;What you will and won’t find in this article&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here you’ll discover how  to build an index we finally built. Some knowledge of Elasticsearch will help  you better  understand this article.&lt;/p&gt;

&lt;p&gt;While I’ll also explain a couple of Elasticsearch concepts, this article is definitely not a tutorial of Elasticsearch.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Elasticsearch?
&lt;/h2&gt;

&lt;p&gt;You’ll probably ask “Why Elasticsearch exactly?”. But the answer is really simple. Our team just didn’t have enough experience with search engines . So the decision to choose the most popular one was really straightforward and obvious. Furthermore, our operations team had experience in maintaining Elasticsearch for Graylog. Almost everyone knows about Elasticsearch. It is the most popular search engine for searching logs and there are a lot of tutorials and articles about that search engine on the Internet.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;How do we store our files in source datastore?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As I mentioned above, originally our data was programming code. For example js files and sc files that you can see above. So let's take a look at our source files in detail. We store our data in MongoDB and every single file is a separate document in the MongoDB collection.&lt;/p&gt;

&lt;p&gt;Let’s look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;theme:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;state:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Start&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;q!:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$regex&amp;lt;/start&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Let's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;start.&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;state:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Hello&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;intent!:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/hello&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Hello&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hello&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;state:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bye&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;intent!:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/bye&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bye&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bye&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;state:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NoMatch&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;event!:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;noMatch&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;understand.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;said:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="err"&gt;$request.query&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB structure looks like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;BinData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And data in the specific document looks like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.sc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ewogICAgInByb2plY3QiO...0KICAgIF0KfQ=="&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we understand our data and our goal. So the main question of this article is: Should we convert our original collection structure for Elasticsearch or not? &lt;/p&gt;

&lt;h2&gt;
  
  
  How to migrate data to Elasticsearch?
&lt;/h2&gt;

&lt;p&gt;We have different tools to help us migrate our data to Elasticsearch. For example, Logstash is a good and time-proven tool to synchronize our data from different sources with Elasticsearch. You can also transform and filter data in this pipeline.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Well-known and time-proven product.&lt;/li&gt;
&lt;li&gt;You have to write only the config file without implementing all sync logic.&lt;/li&gt;
&lt;li&gt;It is an external process that you can scale separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not obvious how complex the logic you can write with a config file.&lt;/li&gt;
&lt;li&gt;Separation sync and transformation logic out of our main codebase could make our whole logic difficult to understand and maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Logstash is a really great tool but in our case, it looked too complicated and had too many uncertainties. It didn’t seem a good fit.&lt;/p&gt;

&lt;p&gt;So we decided to migrate our data and transfer our source collection structure to the index structure by ourselves.   &lt;/p&gt;

&lt;h2&gt;
  
  
  The first try
&lt;/h2&gt;

&lt;p&gt;Since we need to find the line number and match the position in line, we have only two options. The first one: every document is a single line, hence every document is small. The second one: every document is a whole file as origin in MongoDB but this document has a list of lines with the list’s numbers.&lt;/p&gt;

&lt;p&gt;So now let’s start with a quick  explanation on  how Elasticsearch works. The heart of Elasticsearch is an &lt;a href="https://en.wikipedia.org/wiki/Inverted_index" rel="noopener noreferrer"&gt;inverted index&lt;/a&gt;. In other words, if you get a match you’ll get a whole matching document. That’s  why we have to store some additional information to understand what line of the document we have for example.&lt;/p&gt;

&lt;p&gt;Since we store the whole file in MongoDB, we decided to make the same for the first step in Elasticsearch. The main point was it would allow for less memory consumption compared to another case.&lt;/p&gt;

&lt;p&gt;In this case, our Elasticsearch mappings look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nested"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"analyzer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ngram_analyzer"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The&lt;code&gt;lines&lt;/code&gt; field is important for us. This field has a &lt;code&gt;nested&lt;/code&gt; type, which has a warning on google search “don’t use this type” or “don’t make big nested fields”. So... let’s break this rule and do both )&lt;/p&gt;

&lt;p&gt;In Elasticsearch file from the top of the article looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.sc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"require: slotfilling"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"        a: I do not understand. You said: {{$request.query}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it worked! &lt;/p&gt;

&lt;p&gt;But... always there is a “but”. &lt;/p&gt;

&lt;p&gt;Index updating could be quite often a task. And because a document in the index could be huge we had a lot of HTTP calls to Elasticsearch that failed by timeout in 30 seconds.&lt;/p&gt;

&lt;p&gt;So looks like we got that problem from warnings) and we had to fix it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s make our index smaller
&lt;/h2&gt;

&lt;p&gt;The option, where the document is a file, didn't work. So we have to choose the second option and make a document a line of the file.&lt;/p&gt;

&lt;p&gt;In this case, we have another index structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"analyzer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ngram_analyzer"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And our document looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main.sc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"require: slotfilling"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Elasticsearch has started to work faster and more stable. And it helped to maximize the number of calls to Elasticsearch. Our initial fear of memory consumption for this case didn't materialize. Our index size almost hasn’t changed.&lt;/p&gt;

&lt;p&gt;We described only our index structure but not a search query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default search
&lt;/h2&gt;

&lt;p&gt;Let’s look at the example:&lt;/p&gt;

&lt;p&gt;You have a document in Elasticsearch - “hello world”.&lt;/p&gt;

&lt;p&gt;You have default settings and we want to find this document by searching “hel”. It looks like a real case doesn’t it?&lt;/p&gt;

&lt;p&gt;So, in this case you’ll get nothing.&lt;/p&gt;

&lt;p&gt;All because of such things as analyzer and tokenizer. You preprocess query string and index data using those things. And if you have an incorrect analyzer for example you won’t get a match and will get nothing. Default behavior is splitting text by spaces and special characters. So, that’s why you’ll get nothing by “wor”, but you’ll get what you want by “world”.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can we find what we want using part of a word?
&lt;/h2&gt;

&lt;p&gt;There is a way to become familiar with ngrams in Elasticsearch. This &lt;a href="https://about.gitlab.com/blog/2019/03/20/enabling-global-search-elasticsearch-gitlab-com/" rel="noopener noreferrer"&gt;article&lt;/a&gt; on gitlab gave us confidence we found what we were looking for.  &lt;/p&gt;

&lt;p&gt;Ngram - is an ngram analyzer in Elasticsearch. You can set it in mappings for a field.&lt;/p&gt;

&lt;p&gt;Example: &lt;/p&gt;

&lt;p&gt;Save string “hello world” to index. For example we have min=3 and max=5 in our ngram analyzer settings.&lt;br&gt;
It means we split text by 3, 4 and 5 symbols. &lt;br&gt;
hel, ell, llo, lo , o w, ..., rld, ..., o wor,  worl, world&lt;br&gt;
And if we get a match with any of those substrings we’ll get a “hello world”. &lt;/p&gt;

&lt;p&gt;So that’s how we did. Above you can see how we applied the ngran analyzer for the line field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analyzer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ngram_analyzer"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But... Here comes another “but”. &lt;/p&gt;

&lt;p&gt;It is a really good choice. Everything works well and fast. The only issue is that our index is going to take over a really big amount of memory.&lt;/p&gt;

&lt;p&gt;We started looking for a solution to this issue and found it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildcard
&lt;/h2&gt;

&lt;p&gt;Wildcard is a kind of construction: &lt;em&gt;hel&lt;/em&gt;. &lt;br&gt;
It is also searching by substring but it is much faster than a plain regex. It works by special combination of ngram and regex and helps to find a compromise between query speed and index memory consumption. You can use this analyzer for index filed as well as for query strings.&lt;/p&gt;

&lt;p&gt;And our final index structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wildcard"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike ngramm analyzer we reduced our index memory consumption by some 4-5 times!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We tested results that weren't from our production data and production environment. We tested it on synthetic generated data. Data size was about 1 gigabytes.&lt;br&gt;
First of all we executed &lt;code&gt;POST /&amp;lt;index&amp;gt;/_forcemerge&lt;/code&gt; for index.&lt;br&gt;
And after that using &lt;code&gt;GET /_cat/indices/&amp;lt;index&amp;gt;&lt;/code&gt; we could see the size.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We don't have a big index at all (it’s about only &lt;code&gt;10gb&lt;/code&gt;) so it works really well for us. Almost every call to Elasticsearch is less than &lt;code&gt;0.1 second&lt;/code&gt;. But if you have a really huge Elasticsearch index this solution could be somewhat slow for you. But for us and most of such cases it is a perfect match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we went from from an Elasticsearch newbies to understanding pros and cons of different solutions and using it in real cases.&lt;/p&gt;

&lt;p&gt;I hope it was helpful and this article could save someone time and effort. And also I hope this article shows that Elasticsearch isn’t a black box that searches for something but it is more like a Lego constructor.&lt;/p&gt;

&lt;p&gt;We still have so much more to tell t, because here we only talked about the structure of the index, but did not tackle solving all other tasks in order to get a search like in an IDE using Elasticsearch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xtduq4yxqaaa91qser7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xtduq4yxqaaa91qser7.gif" alt="Gif with example how it works" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>elasticsearch</category>
    </item>
  </channel>
</rss>
