<?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>Forem: madhead</title>
    <description>The latest articles on Forem by madhead (@madhead).</description>
    <link>https://forem.com/madhead</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%2F402778%2F34c0abf6-47bc-453e-b71e-d783434ace51.png</url>
      <title>Forem: madhead</title>
      <link>https://forem.com/madhead</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/madhead"/>
    <language>en</language>
    <item>
      <title>When ConcurrentHashMap is not concurrent and runBlocking is not blocking</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Sun, 08 Jun 2025 22:01:07 +0000</pubDate>
      <link>https://forem.com/madhead/when-concurrenthashmap-is-not-concurrent-and-runblocking-is-not-blocking-4b8</link>
      <guid>https://forem.com/madhead/when-concurrenthashmap-is-not-concurrent-and-runblocking-is-not-blocking-4b8</guid>
      <description>&lt;p&gt;A (click)bait? Let me explain.&lt;/p&gt;

&lt;p&gt;Take a look at this simple code. Can you predict what will be printed?&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://pl.kotl.in/80S2ebUb8?from=7&amp;amp;theme=darcula"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Before we dive into what’s wrong here, let’s address an obvious concern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this code even make sense? It looks weird. Surely not a real-world code!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But it kind of is. Sure, it’s synthetic and simplified, but it represents a valid real-world scenario I encountered recently. The essence is straightforward: we have a &lt;code&gt;ConcurrentHashMap&lt;/code&gt; and two concurrent computations, both trying to set the value for the same key.&lt;/p&gt;

&lt;p&gt;In the real world, things were a bit more involved. We had to preload some data from a third-party service during the startup of a Spring Boot application. But Spring doesn’t support suspending functions in &lt;code&gt;@PostConstruct&lt;/code&gt;, &lt;code&gt;InitializingBean&lt;/code&gt;, or &lt;code&gt;@EventListener&lt;/code&gt;. So we went with the simplest option: wrapping the logic in &lt;code&gt;runBlocking&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To speed things up, we performed the calls in parallel (using &lt;code&gt;async&lt;/code&gt; in the real code). While each request was loading different data, they all required service-scoped JWTs issued by another token service. These tokens are short-lived and cached using the service name as the key. And that cache (Caffeine) is, essentially, a &lt;code&gt;ConcurrentHashMap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, if two parallel requests for the same service kick off and no token is cached yet, both will try to call the token service and cache the result. Since Java’s &lt;code&gt;computeIfAbsent&lt;/code&gt; doesn’t accept suspend functions, we wrapped that in another &lt;code&gt;runBlocking&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A code smell? Maybe. Nested &lt;code&gt;runBlocking&lt;/code&gt;s, expensive compurations inside &lt;code&gt;computeIfAbsent&lt;/code&gt;… But hey, &lt;code&gt;ConcurrentHashMap&lt;/code&gt; is &lt;em&gt;concurrent&lt;/em&gt;, right? At worst, we’d waste a call or two to the token service. One thread wins and sets the value; the other one moves on.&lt;/p&gt;

&lt;p&gt;Except… The code is completely broken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exception in thread "main" java.lang.IllegalStateException: Recursive update
 at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1763)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s dig in.&lt;/p&gt;

&lt;p&gt;The exception is thrown &lt;a href="https://github.com/openjdk/jdk/blob/890adb6410dab4606a4f26a942aed02fb2f55387/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L1763" rel="noopener noreferrer"&gt;here&lt;/a&gt;. So there is a variable &lt;code&gt;f&lt;/code&gt; which is an instance of a &lt;code&gt;ReservationNode&lt;/code&gt;. And &lt;a href="https://github.com/openjdk/jdk/blob/890adb6410dab4606a4f26a942aed02fb2f55387/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L2265-L2276" rel="noopener noreferrer"&gt;the docs for that class&lt;/a&gt; immediately prove that we’re on a right track:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A place-holder node used in computeIfAbsent and compute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Going back in the flow brings us to &lt;a href="https://github.com/openjdk/jdk/blob/890adb6410dab4606a4f26a942aed02fb2f55387/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L1701-L1703" rel="noopener noreferrer"&gt;these lines&lt;/a&gt;. There’s a strange bit: it synchronizes on a local variable. Weird? Yes. But there’s a &lt;a href="https://stackoverflow.com/a/47753079/750510" rel="noopener noreferrer"&gt;good reason&lt;/a&gt;: that’s how &lt;code&gt;ConcurrentHashMap&lt;/code&gt; avoids locking the entire map. Simplifying a bit: it reserves a slot for the key and synchronizes on that specific entry. Only one thread may enter that block.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IntelliJ IDEA has a neat feature, helping quickly understand how the execution got to a particular point, called “Analyze Data Flow”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s sprinkle in some logging to observe the threads in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun printlnWithThreadName(message: Any?) {
    println("[${Thread.currentThread().name}] $message")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://pl.kotl.in/QOWtV4bPY?from=11&amp;amp;theme=darcula"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[main] Started main
[main @coroutine#1] Entered runBlocking
[main @coroutine#2] Started launch (1)
[main @coroutine#2] Computing key (1)
[main @coroutine#3] Started launch (2)
[main @coroutine#4] Computing key (1), inside runBlocking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turns out, &lt;code&gt;runBlocking&lt;/code&gt;… isn’t truly blocking in the way one might expect. So how is it starting multiple computations? They’re not running in parallel, after all, a single thread can’t do that. But they do appear to be running concurrently. No surprise there: that’s exactly how coroutines work. It’s cooperative multitasking: when a coroutine suspends, the thread can resume another coroutine that’s ready to continue. Multiple coroutines can take turns on a single thread.&lt;/p&gt;

&lt;p&gt;This is when the &lt;code&gt;runBlocking&lt;/code&gt;'s documentation starts to make sense:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default &lt;code&gt;CoroutineDispatcher&lt;/code&gt; for this builder is an internal implementation of event loop that processes continuations in this blocked thread until the completion of this coroutine. See &lt;code&gt;CoroutineDispatcher&lt;/code&gt; for the other implementations that are provided by &lt;code&gt;kotlinx.coroutines&lt;/code&gt;.&lt;br&gt;
When &lt;a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;CoroutineDispatcher&lt;/code&gt;&lt;/a&gt; is explicitly specified in the context, then the new coroutine runs in the context of the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another &lt;code&gt;runBlocking&lt;/code&gt;, then this invocation uses the outer event loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So: &lt;code&gt;runBlocking&lt;/code&gt; does block the calling thread until the coroutine completes. However, within that thread, it runs an event loop to dispatch and resume coroutines created inside the same &lt;code&gt;runBlocking&lt;/code&gt; block. Since all these coroutines run on the same thread, what happens when they hit a synchronized block, like the one inside the &lt;code&gt;computeIfAbsent&lt;/code&gt;? In other words: if a coroutine enters a synchronized block and then suspends, could another coroutine resume on the same thread while the monitor is still held? Is it just… let in?&lt;/p&gt;

&lt;p&gt;Turns out that the answer is "yes". And that’s the problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConcurrentHashMap&lt;/code&gt; is thread-safe, but not coroutine-safe.&lt;/p&gt;

&lt;p&gt;Another finding: those &lt;code&gt;launch&lt;/code&gt; blocks (or &lt;code&gt;async&lt;/code&gt;s) are generally pointless. Of course IO operations like this benefit from suspension, but there’s no actual parallelism if everything is running on a single thread.&lt;/p&gt;

&lt;p&gt;The code had been running like this for ages. What revealed the issue? I was updating our HTTP clients, introducing that exact token cache and discovered a bug in the preload logic!&lt;/p&gt;

&lt;p&gt;Let’s give the outer runBlocking a proper &lt;code&gt;CoroutineDispatcher&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://pl.kotl.in/sKYv6Ehq3?from=11&amp;amp;theme=darcula"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Now, everything works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[main] Started main
[DefaultDispatcher-worker-2 @coroutine#1] Entered runBlocking
[DefaultDispatcher-worker-3 @coroutine#2] Started launch (1)
[DefaultDispatcher-worker-3 @coroutine#2] Computing key (1)
[DefaultDispatcher-worker-1 @coroutine#3] Started launch (2)
[DefaultDispatcher-worker-3 @coroutine#4] Computing key (1), inside runBlocking
value1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even limiting parallelism to one (e.g., &lt;code&gt;Dispatchers.IO.limitedParallelism(1)&lt;/code&gt;, try it!) seems to fix the issue. Is the root cause in the event-loop nature of the default dispatcher in &lt;code&gt;runBlocking&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I still think, that the &lt;code&gt;ConcurrentHashMap&lt;/code&gt; is not, generally, coroutine-safe. Even if you’re using multiple physical threads with a proper dispatcher, there’s still a chance that two &lt;code&gt;computeIfAbsent&lt;/code&gt; operations could be scheduled on the same thread if they’re suspendable. Although this case would be very rare.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;p&gt;P.S. Another folks argued about &lt;code&gt;ConcurrentHashMap&lt;/code&gt;'s safety in coroutine world in Kotlin Slack &lt;a href="https://slack-chats.kotlinlang.org/t/488773/is-there-a-problem-using-concurrenthashmap-with-coroutines-i" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Also, there is a discussion of a very similar case already in the kotlinx.coroutines GitHub: &lt;a href="https://github.com/Kotlin/kotlinx.coroutines/issues/3982" rel="noopener noreferrer"&gt;#3982&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>java</category>
      <category>coroutines</category>
    </item>
    <item>
      <title>No SNAPSHOTs</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Tue, 30 Jul 2024 07:30:00 +0000</pubDate>
      <link>https://forem.com/madhead/no-snapshots-484a</link>
      <guid>https://forem.com/madhead/no-snapshots-484a</guid>
      <description>&lt;p&gt;&lt;strong&gt;SNAPSHOTs are a &lt;a href="https://stackoverflow.com/q/5901378/750510" rel="noopener noreferrer"&gt;confusing&lt;/a&gt; Maven concept.&lt;/strong&gt; Maven is a &lt;a href="https://github.com/apache/maven/commit/3db476f1a2b6826d0aee4e9937cb73ae14cd7fae" rel="noopener noreferrer"&gt;pre-historic&lt;/a&gt; build tool, invented inside The Apache Software Foundation to build its (mostly Java) projects back in the 2000s. It is, thus, very opinionated, just like this post. Those opinions — including SNAPSHOTs, &lt;code&gt;maven-release-plugin&lt;/code&gt;, POM, repository and project layouts, and many more — are still alive today and spoil modern projects.&lt;/p&gt;

&lt;p&gt;Even ASF does not use Maven to build some of its projects anymore: &lt;a href="https://github.com/apache/beam" rel="noopener noreferrer"&gt;Beam&lt;/a&gt;, &lt;a href="https://github.com/apache/groovy" rel="noopener noreferrer"&gt;Groovy&lt;/a&gt;, &lt;a href="https://github.com/apache/lucene" rel="noopener noreferrer"&gt;Lucene&lt;/a&gt;, &lt;a href="https://github.com/apache/geode" rel="noopener noreferrer"&gt;Geode&lt;/a&gt;, &lt;a href="https://github.com/apache/poi" rel="noopener noreferrer"&gt;POI&lt;/a&gt;, and &lt;a href="https://github.com/apache/solr" rel="noopener noreferrer"&gt;Solr&lt;/a&gt; are not built with Maven. Those are not the most popular ASF projects, I know, but still, it is something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Artifacts must be immutable&lt;/strong&gt;, and two artifacts with the same version must be the same. That is not the case with &lt;strong&gt;SNAPSHOTs&lt;/strong&gt;: they &lt;strong&gt;are mutable by definition&lt;/strong&gt; and every new build could change the artifact published under the &lt;code&gt;-SNAPSHOT&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;Because of that, &lt;strong&gt;&lt;code&gt;SNAPSHOT&lt;/code&gt; artifacts require special treatment.&lt;/strong&gt; They have their own &lt;code&gt;updatePolicy&lt;/code&gt;. Do you, fellow Maven users, know off the top of your head, &lt;a href="https://maven.apache.org/ref/3.9.8/maven-settings/settings.html#snapshots" rel="noopener noreferrer"&gt;how often are they updated by default&lt;/a&gt;? &lt;span&gt;Daily&lt;/span&gt;. SNAPSHOTs are usually uploaded to a separate repository and they are not — obviously — accepted by the Maven Central.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SNAPSHOTs are a potential source of errors.&lt;/strong&gt; Hopefully, only during the development and testing process, because no one should use them in production.&lt;/p&gt;

&lt;p&gt;Stick to something like &lt;a href="https://semver.org" rel="noopener noreferrer"&gt;Semantic Versioning&lt;/a&gt;, and use pre-release identifiers and unique build identifiers instead of SNAPSHOTs. BTW, "SNAPSHOT" is a valid Semver pre-release identifier, however to guarantee artifact immutability, you should include additional metadata in the version, like unique build number, commit hash, or timestamp, etc.&lt;/p&gt;

&lt;p&gt;Abandoning SNAPSHOTs doesn’t mean that you cannot use the "latest" or "dynamic" version of the artifact in your project. Modern build tools support &lt;a href="https://docs.gradle.org/current/userguide/dynamic_versions.html" rel="noopener noreferrer"&gt;dynamic versions&lt;/a&gt;, &lt;a href="https://docs.gradle.org/current/userguide/rich_versions.html" rel="noopener noreferrer"&gt;rich versions&lt;/a&gt;, &lt;a href="https://yarnpkg.com/advanced/lexicon#range" rel="noopener noreferrer"&gt;version&lt;/a&gt; &lt;a href="https://python-poetry.org/docs/dependency-specification" rel="noopener noreferrer"&gt;ranges&lt;/a&gt;, &lt;a href="https://yarnpkg.com/features/constraints" rel="noopener noreferrer"&gt;constraints&lt;/a&gt;, and other cool stuff. I am pretty sure your build tool has it.&lt;/p&gt;

&lt;p&gt;Just to be clear: published artifacts must be immutable, and SNAPSHOTs are not, but you could still have a dynamic version (or a dynamic, but locked/pinned, version) in your projects.&lt;/p&gt;

</description>
      <category>maven</category>
      <category>buildtool</category>
    </item>
    <item>
      <title>SSTables and LSM-Trees for a layperson</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Tue, 05 Mar 2024 10:00:00 +0000</pubDate>
      <link>https://forem.com/madhead/sstables-and-lsm-trees-for-a-layperson-1820</link>
      <guid>https://forem.com/madhead/sstables-and-lsm-trees-for-a-layperson-1820</guid>
      <description>&lt;p&gt;This article is my excerpt of Martin Kleppmann's &lt;a href="https://dataintensive.net" rel="noopener noreferrer"&gt;"Designing Data-Intensive Applications"&lt;/a&gt; book. These data structures are explained in chapter 3, "Storage and Retrieval", pages 69 through 79. It's a really great read, I would recommend the whole book!&lt;/p&gt;




&lt;p&gt;Everything starts with a very dumb key-value database implemented as just two Bash functions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

db_set &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; database
&lt;span class="o"&gt;}&lt;/span&gt;

db_get &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"^&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;,"&lt;/span&gt; database | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"s/^&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;,//"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1
&lt;span class="o"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The idea is to store the data in a CSV-like file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;database.sh

&lt;span class="nv"&gt;$ &lt;/span&gt;db_set 1 &lt;span class="s1"&gt;'Anakin Skywalker'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;db_set 2 &lt;span class="s1"&gt;'Luke Skywalker'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;db_set 1 &lt;span class="s1"&gt;'Darth Vader'&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;database
1,Anakin Skywalker
2,Luke Skywalker
1,Darth Vader

&lt;span class="nv"&gt;$ &lt;/span&gt;db_get 1
Darth Vader


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

&lt;/div&gt;

&lt;p&gt;Note that the first value for the key &lt;code&gt;1&lt;/code&gt; is overridden by the subsequent write.&lt;/p&gt;

&lt;p&gt;This database has pretty good write performance: &lt;code&gt;db_set&lt;/code&gt; just appends the data to a file, which is generally fast. But reads are inefficient, especially on huge data sets: &lt;code&gt;db_get&lt;/code&gt; scans the entire file. Thus, writes are O(1) and reads are O(n).&lt;/p&gt;

&lt;p&gt;Next, indices are introduced. An index is a data structure derived from the data itself. Maintaining an index always incurs additional costs, thus, indices always degrade write performance with the benefit of improving the reads.&lt;/p&gt;

&lt;p&gt;One of the simplest possible indices is a hash index. This index is nothing more than a dictionary holding bytes offsets of the records in a database. Continuing previous example, assuming every char is one byte, the hash index would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fi967mplxsrxrz86ov5k6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fi967mplxsrxrz86ov5k6.png" alt="Hash index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever you write data into the database, you also update the index. When you want to read a value for a given key, you could quickly look up an offset in the database file. Having the offset, you could use a "seek" operation to jump straight to the data location. Depending on the particular index implementation you could expect a logarithmic complexity for both reads and writes.&lt;/p&gt;

&lt;p&gt;Next, Martin deals with the storage efficiency. Appending data to a database file exhausts disk space quickly. The fewer distinct keys you have — the more inefficient this append-only storage engine is. The solution to this problem is compaction:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F1q854rv7big7nzco9m3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F1q854rv7big7nzco9m3r.png" alt="Compaction"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a database file grows to a certain size, you stop appending to it, create a new file (called segment) and redirect all the writes to this new file.&lt;/p&gt;

&lt;p&gt;Segments are immutable in that sense that they are never used to append any new data. The only way to modify a segment is to write it's content into a new file, possibly with some transformations in between.&lt;/p&gt;

&lt;p&gt;So, the compaction creates new segments containing only the most recent records for each key. Another possible enhancement at this step is merging multiple segments into a single one. Compaction and merging could be done, of course, in background. Old segments are just thrown away.&lt;/p&gt;

&lt;p&gt;Every segment, including the one being written to, has its own index. So, when you want to find the value for a given key, you search those indices in reverse chronological order: from the most recent, to the oldest.&lt;/p&gt;

&lt;p&gt;So far we have a data structure having these pros:&lt;/p&gt;

&lt;p&gt;✔️ Sequential writes are generally faster than random ones&lt;br&gt;&lt;br&gt;
✔️ Concurrency is easy to control having a single writer process&lt;br&gt;&lt;br&gt;
✔️ Crash recovery is easy to implement: just read all the segments sequentially, and store the offsets in the in-memory index&lt;br&gt;&lt;br&gt;
✔️ Merging and compaction help to avoid data fragmentation  &lt;/p&gt;

&lt;p&gt;However, there are some limitations as well:&lt;/p&gt;

&lt;p&gt;❗ Crash recovery could be time-consuming if segments are large and numerous&lt;br&gt;&lt;br&gt;
❗ Hash index must fit in memory. Implementing on-disk hash tables is much more difficult&lt;br&gt;&lt;br&gt;
❗ Range queries (&lt;code&gt;BETWEEN&lt;/code&gt;) are virtually impossible  &lt;/p&gt;




&lt;p&gt;Now, with this background, let's move to the SSTables and LSM-trees. By the way, these abbreviations mean "Sorted String Tables" and "Log-Structured Merge Trees" accordingly.&lt;/p&gt;

&lt;p&gt;SSTables are very similar to the "database" that we've seen previously. The only improvement is that we require records in segments to be sorted by key. This might seem to break the ability to use append-only writes, but that's what LSM-Trees for. We'll see in a moment!&lt;/p&gt;

&lt;p&gt;SSTables have some advantages over those simple segments we had previously:&lt;/p&gt;

&lt;p&gt;✔️ Merging segments is more efficient due to the records being pre-sorted. All you have to do is to compare segment "heads" on each iteration and choose the lowest one. If multiple segments contain the same key, the value from the most recent segment wins. This compact &amp;amp; merge process also holds the sorting of the keys.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fn7m5ru7myizxoa2ptwta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fn7m5ru7myizxoa2ptwta.png" alt="SSTable Compact &amp;amp; Merge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✔️ With keys sorted, you don't need to have every single key in the index anymore. If the key &lt;code&gt;B&lt;/code&gt; is known to be somewhere between keys &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;C&lt;/code&gt; you could just do a scan. This also means that range queries are possible!&lt;/p&gt;

&lt;p&gt;The final question is: how do you you get the data sorted by key?&lt;/p&gt;

&lt;p&gt;The idea, described by Patrick O’Neil et al. in their &lt;a href="https://www.cs.umb.edu/~poneil/lsmtree.pdf" rel="noopener noreferrer"&gt;"The Log-Structured Merge-Tree (LSM-Tree)"&lt;/a&gt;, is simple: there are in-memory data structures, such as red-black trees or AVL-trees, that are good at sorting data. So, you split writes into two stages. First, you write the data into the in-memory balanced tree. Second, you flush that tree on the disk. Actually, there may be more than two stages, with deeper ones being bigger and "slower" then the upper (as &lt;a href="https://stackoverflow.com/a/58169581/750510" rel="noopener noreferrer"&gt;shown in this answer&lt;/a&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a write comes, you add it to the in-memory balanced tree, called memtable.&lt;/li&gt;
&lt;li&gt;When the memtable grows big, it is flushed to the disk. It is already sorted, so it naturally creates an SSTable segment.&lt;/li&gt;
&lt;li&gt;Meanwhile, writes are processed by a fresh memtable.&lt;/li&gt;
&lt;li&gt;Reads are first being looked up in the memtable, then in the segments, starting from the most recent one to the oldest.&lt;/li&gt;
&lt;li&gt;Segments are compacted and merged from time to time in background as described previously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scheme is not perfect, it could suffer from sudden crashes: the memtable, being an in-memory data structure, is lost. This issue could be solved by maintaining another append-only file that basically duplicates the contents of the memtable. The database only needs to read it after a crash to re-create the memtable.&lt;/p&gt;

&lt;p&gt;And that's it! Note that all the issues of a simple append-only storage, described above, are now solved:&lt;/p&gt;

&lt;p&gt;✔️ Now there is only one file to read in a case of a crash: the memtable backup&lt;br&gt;&lt;br&gt;
✔️ Indices could be sparse, thus fitting the RAM is easier&lt;br&gt;&lt;br&gt;
✔️ Range queries are now possible  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TLDR: An SSTable is a key-sorted append-only key-value storage. An LSM-tree is a layered data structure, based on a balanced tree, that allows SSTables to exist without the controversy of being both sorted and append-only at the same time.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Congrats, you've finished this long read! If you enjoyed the explanation, make sure to check out the whole book!&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>On DynamoDB’s Single Table Design</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Tue, 27 Feb 2024 10:00:00 +0000</pubDate>
      <link>https://forem.com/madhead/on-dynamodbs-single-table-design-3h42</link>
      <guid>https://forem.com/madhead/on-dynamodbs-single-table-design-3h42</guid>
      <description>&lt;h2&gt;
  
  
  What’s a Single Table Design?
&lt;/h2&gt;

&lt;p&gt;The world learned about the idea of Single Table Design (STD) for DynamoDB somewhere in 2019, probably when &lt;a href="https://www.trek10.com/blog/dynamodb-single-table-relational-modeling"&gt;this article&lt;/a&gt; came out. STD wasn’t just a weird idea by some unknown blogger like me, or an AWS DevRel, or any other kind of Internet freak, but it actually has its roots in the official Amazon DynamoDB documentation. &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html"&gt;Here&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should maintain &lt;strong&gt;as few tables as possible&lt;/strong&gt; in a DynamoDB application. Having fewer tables keeps things more scalable, requires less permissions management, and reduces overhead for your DynamoDB application. It can also help keep backup costs lower overall.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then, a few prominent AWS folks spun this recommendation to the extreme into what we now know as STD: &lt;strong&gt;use a single table for all your data&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❕ Of course, &lt;strong&gt;no one meant literally a single table&lt;/strong&gt;, but the suggestion&lt;br&gt;
was to downsize.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the core of STD, as we’ll discover later, is something called Index Overloading. It involves leveraging every available index to its maximum potential — thereby &lt;em&gt;"overloading"&lt;/em&gt; — to store heterogeneous data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❕ DynamoDB had a limit of 5 Global Secondary Indices (those were of a particular interest for the STD) back at those days, now the limit is increased to 20 GSIs per table by default.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a reference implementation, a rather simplistic little application with just a couple of entities was presented to the public, hardly conveying the &lt;strong&gt;potential dangers&lt;/strong&gt; of this approach. A bit later, another article about STD found its home in the official AWS Blog as well. Here are the links: &lt;a href="https://www.trek10.com/blog/dynamodb-single-table-relational-modeling"&gt;1&lt;/a&gt; | &lt;a href="https://www.alexdebrie.com/posts/dynamodb-single-table"&gt;2&lt;/a&gt; | &lt;a href="https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb"&gt;3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How outraged I was!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s wrong with STD?
&lt;/h2&gt;

&lt;p&gt;It wasn’t the STD concept itself that irked me, but the &lt;strong&gt;assertiveness with which it was being pushed&lt;/strong&gt;! I remember I couldn’t hold back and even shot some DMs at our local AWS community champs, demanding answers.&lt;/p&gt;

&lt;p&gt;My emotions rooted in the fear that someone on my project might want to adopt this approach. Ironically enough, considering I was working solo back then. With several dozen tables in our project, merging them all into one was just madness. It might have been efficient, but it would surely enrage anyone who had to work with and maintain it.&lt;/p&gt;

&lt;p&gt;For instance, the article in the AWS Blog mentions a nifty little constraint in the domain model that limits the size of a single record.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❕ The maximum item size in DynamoDB is 400 KB, which includes both attribute names and attribute values.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It goes like this: &lt;em&gt;we’re only storing 300 data points per event, so the data will fit&lt;/em&gt;. Isn’t that fortunate? And if &lt;em&gt;it doesn’t fit, well, just split it somehow into several parts&lt;/em&gt;, you are on your own now! But on our project, we already had entities exceeded that limit. And we had already resorted to tricks like ZIP compression on the application side, which had already complicated maintenance: such tables are impossible to view in the console.&lt;/p&gt;

&lt;p&gt;I wasn’t alone in my frustration. &lt;a href="https://www.reddit.com/r/aws/comments/aimmg7/how_many_people_are_doing_true_single_table"&gt;Here&lt;/a&gt;'s an example of one of the STD discussions on Reddit. Some users found the whole idea &lt;em&gt;"overwhelming"&lt;/em&gt;, though there were just as many enthusiasts.&lt;/p&gt;

&lt;p&gt;For me, it was obvious that &lt;strong&gt;the approach is at least debatable and not suitable for every situation&lt;/strong&gt;. But being pessimistic about developers in general, I was concerned they might start applying it indiscriminately, ignoring the specific and realities of the project. The official documentation, and the hype, were just too persuasive!&lt;/p&gt;

&lt;p&gt;Moreover, this approach is &lt;strong&gt;too complex&lt;/strong&gt; if you try to implement it. And it’s &lt;strong&gt;not flexible&lt;/strong&gt;. It forces you to think far ahead, really far ahead. So much far ahead, that you need to get it right for ages and from the first attempt: modifying an already created STD table, or adding a new type of query to it, is practically impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oh how the tables have turned!
&lt;/h2&gt;

&lt;p&gt;Rick Houlihan, the &lt;em&gt;"Inventor of @DynamoDB #SingleTableDesign"&lt;/em&gt;, has apparently faced so much criticism over time that he felt compelled to defend himself on X. His defence, though, is more like &lt;em&gt;"it was just three of us musketeers, standing agains whole Cardinal’s guard"&lt;/em&gt;. Yesterday, he twitted:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1760469859761029228-129" src="https://platform.twitter.com/embed/Tweet.html?id=1760469859761029228"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1760469859761029228-129');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1760469859761029228&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;But let’s take a closer look at what he’s saying…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I even saw @alexbdebrie apologizing for being an advocate the other day.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh, that’s indeed to much, no one should apologize for great experiments!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anyone who tells you a different story than the one that follows is wrong. I led the team that invented the Single Table Design pattern. I have facts, they have at best half-informed opinions. There are 3 people in this world who could tell this story.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the musketeers part…&lt;/p&gt;

&lt;p&gt;Then follow several paragraphs explaining Index Overloading. And I think that’s exactrly what the problem with Index Overloading / STD is. It’s so complex, that requires explanation after being available for about five years already. Maybe, it was just a little bit too much for the average developer from the very beginning?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The drawback of doing this was that indexes became more and more polluted with unrelated Item types as the number of access patterns they supported increased.&lt;/p&gt;

&lt;p&gt;Because of this it was not easy to drop and recreate indexes without table scans and batch updates which became expensive at scale.&lt;/p&gt;

&lt;p&gt;The pattern also introduced heavy cognitive load on developers as using abstract naming for index attributes meant it was not always immediately apparent when looking at the data how the data was being indexed unless the values assigned to the generic keys were self-explanatory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I couldn’t agree more!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All of these things were tradeoffs for applying the Index Overloading pattern, not core issues with Single Table Design itself. They were often deemed acceptable inconveniences considering the benefit of having effectively unlimited GSI’s. Most of the problems that drove the need for Index Overloading have been resolved over the years as DynamoDB has added support for 25 GSI’s, introduced on demand pricing, and eliminated the need to allocate capacity individually for each index. As a result the pattern should really be considered deprecated today.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah… Here we go again, aren’t we? We’ve just seen that even 5 indices are too much for the average developer, and now we have to deal with 25?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❕ I’m not sure why Rick is talking about 25 GSIs, as the official documentation still mentions 20.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then came excuses…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Additionally, many people over the years have also taken STD to an extreme that was never intended. Mixing configuration and operational data, maintaining a single table across service boundaries, or storing unrelated data that is not accessed together in the same table. Despite the fact that there are some people out there trying very hard to rewrite history around this, none of these things were ever recommended as best practices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here I have to disagree. Did we see STD articles in the AWS Blog? We did! Is it the official source? It is! If the official AWS Blog is not a place for the best practices, then where is the place?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is easy to look at the product as it exists today and criticize the design patterns of yesterday that were invented to deal with API deficiencies that no longer exist. I read some serious garbage every now and then written by people I feel should know better, Those people really had very little exposure to the process of solving the problems we faced when the patterns they criticize were introduced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’m sorry, to hear that, Rick, we failed your expectations. But that’s the nature of the crowd, isn’t it? Remember how developers make cults around some design patterns? Yet the &lt;a href="https://en.wikipedia.org/wiki/Software_design_pattern"&gt;wiki definition&lt;/a&gt; of a design pattern literally says "contextual":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem &lt;strong&gt;within a given context&lt;/strong&gt; in software design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yet patterns are everywhere, often applied out of context. Same as programming languages, databases and lots of other things in the IT world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the moral of this rant?
&lt;/h2&gt;

&lt;p&gt;It’s possible to brilliantly overcome certain technological limitations and propose unconventional and effective solutions to some problems. But it’s important to be cautious and thoughtful when conveying these ideas to a layperson. Architects, come down from your Ivory towers! Don’t overestimate the intellectual capabilities of developers, but also don’t overvalue your own ideas.&lt;/p&gt;

&lt;p&gt;Every problem has its context, which is often lost. But this goes both ways! Just as developers sometimes fail to understand the applicability limits of patterns and solutions, architects sometimes neglect the context in which development occurs. You know: tight schedule, low skill…&lt;/p&gt;

&lt;p&gt;Don’t be sorry for the great idea you gave us and don’t be angry at us for misusing it.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dynamodb</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Declarative Gradle is a cool thing I am afraid of: Maven strikes back</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Sat, 11 Nov 2023 03:25:20 +0000</pubDate>
      <link>https://forem.com/madhead/why-declarative-gradle-is-a-cool-thing-i-am-afraid-of-maven-strikes-back-2jc3</link>
      <guid>https://forem.com/madhead/why-declarative-gradle-is-a-cool-thing-i-am-afraid-of-maven-strikes-back-2jc3</guid>
      <description>&lt;p&gt;Yesterday, JetBrains &lt;a href="https://blog.jetbrains.com/blog/2023/11/09/amper-improving-the-build-tooling-user-experience"&gt;introduced Amper&lt;/a&gt;. Today, Gradle published another related article, named &lt;a href="https://blog.gradle.org/declarative-gradle"&gt;Declarative Gradle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, I can see the direction it is leaning towards, and I am afraid of this future.&lt;/p&gt;

&lt;h2&gt;
  
  
  History of Java Build Tools, simplified and opinionated
&lt;/h2&gt;

&lt;p&gt;NOTE: I won’t mention &lt;a href="https://scala-sbt.org"&gt;SBT&lt;/a&gt; and &lt;a href="https://leiningen.org"&gt;Leiningen&lt;/a&gt; here because, with all due respect, they are niche build tools. I also won’t discuss &lt;a href="https://beust.com/kobalt"&gt;Kobalt&lt;/a&gt; for the same reason (besides, it’s no longer actively maintained). Additionally, I won’t touch upon &lt;a href="https://bazel.build"&gt;Bazel&lt;/a&gt; and &lt;a href="https://buck2.build"&gt;Buck&lt;/a&gt; in this context, mainly because I’m not very familiar with them. If you have insights or comments about these tools, please feel free to share them in the comments 👇&lt;/p&gt;

&lt;p&gt;Let me provide a quick recall of the history of (Java) build tools.&lt;/p&gt;

&lt;p&gt;At the beginning, there was Ant. The fact of its existence is more information than a sensible person would typically need.&lt;/p&gt;

&lt;p&gt;Then arrived Maven, the cool kid on the block around 2005. Maven was a very specific solution to a very specific problem: it was a tool used by Apache to build its projects, mostly Java libraries, back at those days. Notably, the widely adopted &lt;code&gt;src/main/java&lt;/code&gt; and &lt;code&gt;src/test/java&lt;/code&gt; structure finds its roots in Apache’s practices from that era. This narrative somewhat parallels the (mostly fictitious) story &lt;a href="https://www.reddit.com/r/space/comments/k4x1gq/the_connection_between_horses_asses_and_space"&gt;linking horse’s ass to space shuttles&lt;/a&gt;. Following that, there was a Maven Golden Age lasting until the 2010s, largely attributed to the absence of substantial competition and the relatively simple nature of Java projects.&lt;/p&gt;

&lt;p&gt;Yet, the landscape changed. In no particular order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Projects became more complex.&lt;/li&gt;
&lt;li&gt; Apps began to be packaged in Docker images.&lt;/li&gt;
&lt;li&gt; Projects started adopting a multi-module structure, with some extending to include thousands of individual modules.&lt;/li&gt;
&lt;li&gt; Build times increased.&lt;/li&gt;
&lt;li&gt; The evolution of build tools expanded their role beyond traditional building functions.&lt;/li&gt;
&lt;li&gt; Multi-language projects emerged, with a simple example being the development of a JavaScript frontend.&lt;/li&gt;
&lt;li&gt; …and then frontends evolved, the need to build them using tools like Webpack arose, though that’s a different story.&lt;/li&gt;
&lt;li&gt; Truly multi-platform projects unfolded. To clarify: while Java is multi-platform, developing for server, web, desktop, iOS, and Android from a single codebase became hip only recently. Yes, I am talking about Kotlin Multiplatform.&lt;/li&gt;
&lt;li&gt; …these multi-platform projects required the use of multi-platform dependencies.&lt;/li&gt;
&lt;li&gt;And so forth.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Maven struggled to keep pace. Its origins as a build tool for simple Java libraries within Apache left it unprepared for the evolving landscape.&lt;/p&gt;

&lt;p&gt;Consider build caching. Maven lacks a proper mechanism for it, except a local repository, but that’s not the same. Multi-platform dependencies are another challenge. The GAV notation (group, artifact, version), Maven’s Holy Grail, proves inadequate. And it still relies on a — completely fabricated and opinionated — build lifecycle, organized into dozen-or-something phases. Why not a Directed Acyclic Graph (DAG)?&lt;/p&gt;

&lt;p&gt;Enter Gradle, our savior. Despite being ridiculosly complex — sometimes — it was built on a simple idea — a DAG — enabling it to evolve and adapt to new challenges. That’s why…​&lt;/p&gt;

&lt;h2&gt;
  
  
  Gradle is the most advanced build tool in the Java world!
&lt;/h2&gt;

&lt;p&gt;Consider Android projects. They are arguably significantly more challenging than typical Java projects. Their builds involve tasks like processing resources and assets, supporting multiple target SDKs, managing various build flavors, and occasionally dealing with native code and dependencies targeting different CPU architectures. Yet Gradle became the default build tool for Android projects shortly after Android projects even get a build tool! If my memory serves me right, they jumped straight from Ant to Gradle, bypassing Maven, thanks to Google’s influence – a move that I find quite illustrative.&lt;/p&gt;

&lt;p&gt;Now, let’s explore other prominent projects in the Java landscape. Kotlin, Spring, Hibernate, RxJava — you name it, they all rely on Gradle. With two possible exceptions. Oracle isn’t particularly fond of Gradle, so don’t be surprised if you discover that GraalVM is built with Maven (it actually is, but the build process is more complex than just Maven vs. Gradle). And, of course, Apache remains loyal to Maven even in big and complex projects like Hadoop and Spark. After all, it’s their child! Kafka is build with Gradle, though.&lt;/p&gt;

&lt;p&gt;However, virtually every other major Java project uses Gradle — check your own dependencies!&lt;/p&gt;

&lt;p&gt;In essence, Gradle is a superset of Maven. In strict mathematical terms, ponder it for a moment. Everything achievable with Maven can be replicated with Gradle, but the reverse is not true.&lt;/p&gt;

&lt;p&gt;Gradle’s DAG is so simple and powerful that you could probably replicate any other build tool with it. The only limitation I could think of: due to the separation of configuration and execution phases, creating and injecting tasks into the DAG on the fly might not be possible. Or is it? Don’t try that at home!&lt;/p&gt;

&lt;p&gt;If you have any counterarguments at this juncture, please share them in the comments. I’m genuinely interested in understanding any aspects I might be overlooking 👇&lt;/p&gt;

&lt;h2&gt;
  
  
  Why strive for declarativeness?
&lt;/h2&gt;

&lt;p&gt;Alright, let’s establish a boundary here.&lt;/p&gt;

&lt;p&gt;Gradle is one of the most advanced build tools ever, and unquestionably, it is the most advanced build tool in the Java world.&lt;/p&gt;

&lt;p&gt;It’s based on a very simple yet scalable and extensible concept.&lt;/p&gt;

&lt;p&gt;However, this scalability and extensibility come at the cost of complexity, and not everyone enjoys its power at this price tag.&lt;/p&gt;

&lt;p&gt;So, a while back, a new trend emerged: declarative Gradle. I claim that this concept isn’t entirely novel, and the article I referred to at the beginning of this post essentially encapsulates what has already been circulating, rather than initiating the trend.&lt;/p&gt;

&lt;p&gt;The Android Gradle plugin serves as a noteworthy example of declarative Gradle that has been in existence for a considerable time.&lt;/p&gt;

&lt;p&gt;
  Here’s a snippet from the &lt;a href="https://developer.android.com/build"&gt;official documentation&lt;/a&gt;
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;jvmToolchain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;

    &lt;span class="n"&gt;compileSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;

    &lt;span class="nf"&gt;defaultConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp"&lt;/span&gt;
        &lt;span class="n"&gt;minSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;
        &lt;span class="n"&gt;targetSdk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;
        &lt;span class="n"&gt;versionCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;versionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;buildTypes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"release"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;isMinifyEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="nf"&gt;proguardFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nf"&gt;getDefaultProguardFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"proguard-android.txt"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"proguard-rules.pro"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;getByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;…&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;flavorDimensions&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;"tier"&lt;/span&gt;
    &lt;span class="nf"&gt;productFlavors&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="s"&gt;"free"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tier"&lt;/span&gt;
            &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp.free"&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="s"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dimension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tier"&lt;/span&gt;
            &lt;span class="n"&gt;applicationId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.myapp.paid"&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="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":lib"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"androidx.appcompat:appcompat:1.6.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fileTree&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="s"&gt;"dir"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"libs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"include"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*.jar"&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;p&gt;Is it really declarative or imperative?&lt;/p&gt;

&lt;p&gt;Upon closer examination, you’ll notice that it is imperative! Statements like these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flavorDimensions += "tier&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getByName("release")&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create("free")&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…are extremely imperative!&lt;/p&gt;

&lt;p&gt;Or perhaps not? After all, it’s just DSL, isn’t it? And quite eloquent one, I must say. Assignments and maybe a bit of object creation and configuration are inevitable, especially when dealing with software models like this.&lt;/p&gt;

&lt;p&gt;What’s more important, is that we don’t see any conditions and loops here. They are the true evil of imperative programming, and here, there are none!&lt;/p&gt;

&lt;p&gt;Another example of declarative Gradle are so-called convention plugins. While the &lt;a href="https://docs.gradle.org/current/userguide/custom_plugins.html"&gt;documentation about custom plugins&lt;/a&gt; may not explicitly state and emphasize this term (which is a problem itself!), it is used in &lt;a href="https://docs.gradle.org/current/samples/sample_convention_plugins.html"&gt;the samples&lt;/a&gt;. There is also this cool guy on YouTube, advocating for these best practices as well, take a look!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/N95YI-szd78"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The idea behind convention plugins is really interesting, and it is very simililar to the AGP example above (in fact, AGP is a convention plugin!): to conceal all the complexity behind a simple DSL. Consider, for instance, another article of mine: &lt;a href="https://dev.to/madhead/no-bullshit-guide-on-publishing-your-gradle-projects-to-maven-central-3ok4"&gt;No-bullshit guide on publishing your Gradle projects to Maven Central&lt;/a&gt;. Now, instead of cluttering your build script with the cumbersome publishing block, you could create a convention plugin, resulting in a build script that looks something 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="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.acme.publishing"&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;That’s it! You wouldn’t even have to configure the GAV, as the values could be inferred from the project itself. Moreover, this hypothetical plugin could be shared across your company, ensuring uniform publishing configurations for all company’s projects.&lt;/p&gt;

&lt;p&gt;Thats really imperative, and this is already available in Gradle!&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with the "Declarative Gradle"
&lt;/h2&gt;

&lt;p&gt;Kudos to the vigilant readers who spotted the inconsistency: I used the term "Declarative Gradle" with a capitalized 'D' here, but earlier, I employed the "declarative Gradle" term with a lowercase 'd'.&lt;/p&gt;

&lt;p&gt;And there’s a reason for that!&lt;/p&gt;

&lt;p&gt;As demonstrated in the previous section, Gradle is already declarative if you wish it to be! All the tools are at your disposal to conceal complexity behind a straightforward and declarative DSL. Granted, it requires an investment of time: reading the docs, tuning into YouTube, and the like. In larger companies, a dedicated build engineer might be a prudent choice.&lt;/p&gt;

&lt;p&gt;However, &lt;a href="https://blog.gradle.org/declarative-gradle"&gt;the article&lt;/a&gt; I linked at the beginning actually introduces a new term: Declarative Gradle, 'D' capitalized.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This blog post explains the Gradle team’s perspective for what we call a developer-first software definition, or Declarative Gradle for short. This post outlines our plans for making the “elegant and declarative build language” part of our vision a reality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I understand correctly, "Declarative Gradle" is essentially the same concept as described above but perhaps more trademarked. In fact, "Declarative Gradle" seems to be a more manager-friendly term than "convention plugin," and that’s perfectly fine.&lt;/p&gt;

&lt;p&gt;My concern lies in this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We plan to provide a restricted DSL that separates the software definition and build logic so that the build language is fully declarative. This will effectively enforce existing best practices.&lt;/p&gt;

&lt;p&gt;The restricted DSL will allow only a limited set of constructs, such as nesting blocks, assigning values, and selected method invocations. Generic control flow and calls to arbitrary methods will be disallowed. You will be able to write your build logic in any JVM language, such as Java, Kotlin, or Groovy, but that logic will reside in plugins (either local or published).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is disheartening for me because it feels like a step back.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;is Maven&lt;/em&gt;, to be precise.&lt;/p&gt;

&lt;p&gt;Maven is already a tool with a very restricted DSL (XML; funnily enough, XML &lt;a href="https://stackoverflow.com/a/17912073/750510"&gt;could be&lt;/a&gt; Turing-complete), where the entire complexity is hidden behind plugins crafted on top of an ambiguous and controversial API. Tinkering with the Maven API is genuinely challenging, while you could kick-start your Gradle plugin right in your build script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility is the ultimate power of Gradle&lt;/strong&gt;, and I am afraid of losing it. I’m afraid of Gradle loosing its charm. This is the reason why it is a better build tool than Maven (and this is the first time I’m actually saying that). It’s about freedom, a quality that doesn’t exist in Maven.&lt;/p&gt;

&lt;p&gt;Don’t get me wrong: it’s not advisable to incorporate complex, imperative build logic directly into a build script. There are effective ways to circumvent that, as outlined above and in the docs. In my nearly 10 years of using Gradle, I’ve never used a loop in a build script. I’ve used conditions a few times, probably less than 10 times overall, so less than once per year. However, I now know how to eliminate them completely. The last time I utilized &lt;code&gt;allprojects&lt;/code&gt; or &lt;code&gt;subprojects&lt;/code&gt; was likely 3-5 years ago, and I know how to avoid them as well.&lt;/p&gt;

&lt;p&gt;All these things are avoidable by acquainting yourself with the latest Gradle features. Dive into the release notes! Don’t shy away from using the latest Gradle version in your project either (because if you crave for Declarative Gradle™, an upgrade is inevitable). Embrace the best practices available. If you need a refresher on Gradle, watch &lt;a href="https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE"&gt;that guy&lt;/a&gt; on YouTube, he’s brilliant!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I am apprehensive that at the end of this "declarativization", we might end up with a less powerful, restricted version of Gradle. I fear that the Maven mindset — the concept of having a limited but safe tool — might prevail.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why you shouldn’t be too worried
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt; &lt;em&gt;The existing DSL will continue to be fully supported.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; It won’t be XML! 🤞&lt;/li&gt;
&lt;li&gt; No idea or implementation triumphs without community support. Alarmists like me have a chance to resist ✊&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gradle</category>
      <category>maven</category>
      <category>buildtool</category>
      <category>rant</category>
    </item>
    <item>
      <title>PR preview environments with Neon, GitHub Actions, and AWS Lambda</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Wed, 03 May 2023 00:02:27 +0000</pubDate>
      <link>https://forem.com/madhead/pr-preview-environments-with-neon-github-actions-and-aws-lambda-ibn</link>
      <guid>https://forem.com/madhead/pr-preview-environments-with-neon-github-actions-and-aws-lambda-ibn</guid>
      <description>&lt;p&gt;You might have heard about preview environments (AKA dynamic, or ephemeral environments) in the scope of software engineering and, particularly, this term is used when speaking about the SDLC.&lt;/p&gt;

&lt;p&gt;A preview environment — as opposed to a permanent one, like "Production", or "TST", or whatever you call them — is a short-lived environment, usually bound to a pull request. It is created on-demand when the PR is created; and destroyed when the PR is closed. It hosts a copy of the application and is used to test the changes introduced by the pull request.&lt;/p&gt;

&lt;p&gt;If you need more information, I encourage you to jump around a few articles on the Web to grasp the idea before continuing. I won’t go into details here, but instead, I’ll be focusing on the most challenging aspect of preview environments…&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The tragedy of databases in preview environments
&lt;/h2&gt;

&lt;p&gt;Materials about preview environments tend to cast little light on the database, leaving you with the approximate directions, like "create a copy of the database", or "use a database per PR", but no advice on how to achieve that. And for a reason!&lt;/p&gt;

&lt;p&gt;First of all, everyone’s application is indeed different, and there’s no one-size-fits-all solution.&lt;/p&gt;

&lt;p&gt;But the main reason, IMO, is that a database is very different from an application and it’s hard to copy it. Databases are persistent and stateful, while applications are ephemeral and stateless. Copying apps is the same as redeploying or scaling them, maybe with a different configuration. Copying databases is always a trick.&lt;/p&gt;

&lt;p&gt;What’s even worse, is that databases historically combine both storage and compute in them! I mean, a typical database consists of its data, represented as actual files, and a database engine, which is an application process on its own. Thus, copying a database is at least twice as hard as copying an application. Simple math!&lt;/p&gt;

&lt;p&gt;The options here range from cloning the database using dump / restore to using no copies at all, i.e. a shared DB for all the preview environments. Let’s roughly compare them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkhbfreqpgtkcm3b9n36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkhbfreqpgtkcm3b9n36.png" alt="DB copying opotions" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;complexity&lt;/strong&gt; means the amount of effort required to implement the solution. Obviously, a shared environment requires no effort; migration scripts require creating a fresh database and running the migrations; and dump / restore requires creating a fresh database, creating a dump, transferring it to the fresh database, and restoring it. Don’t get me wrong, it’s not impossible, it’s just disproportionately hard and slow.&lt;/p&gt;

&lt;p&gt;Data &lt;strong&gt;isolation&lt;/strong&gt; is high for both dump / restore and migration scripts approaches because each preview environment has its own database. A shared database, on the other hand, is not isolated at all.&lt;/p&gt;

&lt;p&gt;Data isolation is highly correlated with the ability to test and preview &lt;strong&gt;schema modifications&lt;/strong&gt;. Actually, it’s the same thing, I just want to highlight it, because it’s important. If you have a shared database, you can’t easily modify the schema without breaking others. However, if you have a dedicated database, you can do whatever you want with it. The ability to test and preview schema modifications is a huge advantage of preview environments because it allows you to test the migrations before applying them to the production database.&lt;/p&gt;

&lt;p&gt;Isolation is the opposite of &lt;strong&gt;data freshness&lt;/strong&gt;. I call it the ability for the preview environment to have the latest data from the upstream database. A shared database has the freshest data because it’s the same database. Dump / restore approach could provide fresh data as well, especially if you dump and restore the database on every PR. Migration scripts are the worst here because they don’t provide any data fresh data at all. Only the data captured in the migration / seed scripts would be available.&lt;/p&gt;

&lt;p&gt;Migration scripts approach is somewhat &lt;strong&gt;automated&lt;/strong&gt;. In the best-case scenario, you would have a script that creates a fresh database (e.g. a Terraform or Helm), and a script that runs the migrations (e.g. Rails Migrations or Liquibase). Dumping and restoring is less automated because most probably, it would involve a bash scripting with, let’s say, &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt; commands. No copy approach requires no automation at all.&lt;/p&gt;

&lt;p&gt;Compromises, compromises, compromises… As you see, you trade complexity for convenience. And because of that and because deep data isolation is rarely required, and because schema modification could be arranged manually, I suppose most projects start from a shared DB approach and never move further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet Neon
&lt;/h2&gt;

&lt;p&gt;Now, after such a long introduction, this section would be ridiculously (in a good way) short, even in my wordy style.&lt;/p&gt;

&lt;p&gt;Imagine there is a PostgreSQL database, that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully managed&lt;/li&gt;
&lt;li&gt;Serverless&lt;/li&gt;
&lt;li&gt;Autoscalable&lt;/li&gt;
&lt;li&gt;Auto-suspendable&lt;/li&gt;
&lt;li&gt;Separates storage and compute&lt;/li&gt;
&lt;li&gt;Branches in a second with a single button click or API call&lt;/li&gt;
&lt;li&gt;And, yes, provides REST API for everything&lt;/li&gt;
&lt;li&gt;Provides generous free tier to get you started&lt;/li&gt;
&lt;li&gt;Well-documented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It exists, and it is &lt;a href="https://neon.tech" rel="noopener noreferrer"&gt;Neon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because of the unique architecture, with separated storage and compute, Neon enables instant copy-on-write branching. You could create a branch, i.e. a copy of the database, from virtually any point in time in the upstream database, and it would be created in seconds. It means, that you could branch off the production database, with the latest data, and still have full isolation. Being serverless also means that Neon is very elastic, it can scale up and down, and even suspend the compute completely when it’s not used. On top of that, Neon provides a REST API for everything, so you can automate the whole process.&lt;/p&gt;

&lt;p&gt;It’s simple to use, it’s highly isolated, it provides branches off the latest data, and it’s easily automatable. It’s perfect for preview environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryrnelo3ifzscnjct2g4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fryrnelo3ifzscnjct2g4.png" alt="DB copying opotions" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drawbacks? Well, it’s in the technical preview stage yet.&lt;/p&gt;

&lt;p&gt;I don’t see a point in repeating the docs here, so here are some rabbit holes for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/introduction/about" rel="noopener noreferrer"&gt;What is Neon?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/branching" rel="noopener noreferrer"&gt;Instant branching for Postgres&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/introduction/branching" rel="noopener noreferrer"&gt;Branching documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/introduction/architecture-overview" rel="noopener noreferrer"&gt;Neon architecture overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/introduction/compute-lifecycle" rel="noopener noreferrer"&gt;Compute lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/introduction/autoscaling" rel="noopener noreferrer"&gt;Autoscaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/jjRasfbeYHk" rel="noopener noreferrer"&gt;Neon Developer workflow using Vercel and Github Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exploring Neon branching with GitHub Actions and AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Link for the impatient ones: &lt;a href="https://github.com/madhead/neonbranch" rel="noopener noreferrer"&gt;madhead/neonbranch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine an over-engineered rock-paper-scissors game, storing game rules in a database and providing a REST API to calculate the winner. The API is of no interest today, so it’s just an AWS Lambda, implemented in Python and deployed with CDK. Meet the database:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsw9cus49yztzna16neba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsw9cus49yztzna16neba.png" alt="Rock, paper, scissors rules" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, imagine we’re working on two new features. The first one expands the game with Spock and lizard, and the second one adds textual descriptions for the outcomes:&lt;/p&gt;

&lt;p&gt;
  What would the database look like
  &lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99okujy4azj3fpn7irpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99okujy4azj3fpn7irpa.png" alt="Rock, paper, scissors, lizard, Spock rules" width="800" height="877"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;We have these changes captured in Liquibase changelogs. The question is: how do we organize the CI pipelines?&lt;/p&gt;

&lt;h3&gt;
  
  
  Events that trigger workflows
&lt;/h3&gt;

&lt;p&gt;Remember, preview environments are ephemeral and bound to the PRs. So, let’s search for the appropriate triggers in &lt;a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request" rel="noopener noreferrer"&gt;Events that trigger workflows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Three things could happen to a PR in regard to our scenario:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It could be &lt;a href="https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=opened#pull_request" rel="noopener noreferrer"&gt;opened&lt;/a&gt; or &lt;a href="https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=reopened#pull_request" rel="noopener noreferrer"&gt;reopened&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It could be &lt;a href="https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=synchronize#pull_request" rel="noopener noreferrer"&gt;synchronized&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It could be &lt;a href="https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads?actionType=closed#pull_request" rel="noopener noreferrer"&gt;closed&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a PR is opened or reopened, we should create a new Neon branch, when a PR is closed we should delete the associated branch. When a PR is synchronized, we run the same workflow as when a PR is opened or reopened, as it would be made idempotent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflows
&lt;/h3&gt;

&lt;p&gt;So, we’ll need three workflows:&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;.github/workflows/pr-env-create.yml&lt;/strong&gt;
  &lt;br&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;Create PR environment&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;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&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;pr-env-create&lt;/span&gt;&lt;span class="pi"&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;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&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;./.github/actions/deploy&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;Deploy&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pr-${{ github.event.number }}&lt;/span&gt;
          &lt;span class="na"&gt;neon_project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.NEON_PROJECT }}&lt;/span&gt;
          &lt;span class="na"&gt;neon_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NEON_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;.github/workflows/pr-env-sync.yml&lt;/strong&gt;
  &lt;br&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;Synchronize PR environment&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;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;synchronize&lt;/span&gt;

&lt;span class="c1"&gt;# same as above&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;.github/workflows/pr-env-destroy.yml&lt;/strong&gt;
  &lt;br&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;Destroy PR environment&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;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;closed&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;pr-env-create&lt;/span&gt;&lt;span class="pi"&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/destroy&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;Destroy&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pr-${{ github.event.number }}&lt;/span&gt;
          &lt;span class="na"&gt;neon_project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.NEON_PROJECT }}&lt;/span&gt;
          &lt;span class="na"&gt;neon_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NEON_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;I extracted the common logic into two actions, &lt;code&gt;deploy&lt;/code&gt; and &lt;code&gt;destroy&lt;/code&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy action
&lt;/h4&gt;

&lt;p&gt;
  &lt;strong&gt;.github/actions/deploy/action.yml&lt;/strong&gt;
  &lt;br&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;Deploy&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy the lambda&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;neon_project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;neon_token&lt;/span&gt;&lt;span class="pi"&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;URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composite&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.9&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python -m venv .venv&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

    &lt;span class="pi"&gt;-&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;source .venv/bin/activate&lt;/span&gt;
        &lt;span class="s"&gt;python -m pip install -r requirements.txt&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

    &lt;span class="pi"&gt;-&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;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;source .venv/bin/activate&lt;/span&gt;
        &lt;span class="s"&gt;python branch.py&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;NEON_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.neon_token }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_PROJECT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.neon_project }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_BRANCH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.environment }}&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;actions/setup-java@v3&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="s2"&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="m"&gt;17&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./gradlew update&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migrations&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;NEON_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.branch.outputs.db_host }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.neon_database }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.branch.outputs.db_user }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.branch.outputs.db_password }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;The logic is pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We install Python&lt;/li&gt;
&lt;li&gt;We set up a virtual environment and install the dependencies&lt;/li&gt;
&lt;li&gt;We run a branch.py script which we’ll see in a moment&lt;/li&gt;
&lt;li&gt;We install Java to run Liquibase&lt;/li&gt;
&lt;li&gt;We run Liquibase&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Destroy action
&lt;/h4&gt;

&lt;p&gt;
  &lt;strong&gt;.github/actions/destroy/action.yml&lt;/strong&gt;
  &lt;br&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;Destroy&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Destroy the preview environment&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;neon_project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;neon_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composite&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@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;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.9&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python -m venv .venv&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

    &lt;span class="pi"&gt;-&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;source .venv/bin/activate&lt;/span&gt;
        &lt;span class="s"&gt;python -m pip install -r requirements.txt&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;

    &lt;span class="pi"&gt;-&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;source .venv/bin/activate&lt;/span&gt;
        &lt;span class="s"&gt;python unbranch.py&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;NEON_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.neon_token }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_PROJECT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.neon_project }}&lt;/span&gt;
        &lt;span class="na"&gt;NEON_BRANCH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.environment }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;We just run a different script, &lt;code&gt;unbranch.py&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automation scripts
&lt;/h3&gt;

&lt;p&gt;Let’s extract Neon API calls into a separate Python module:&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;neon.py&lt;/strong&gt;
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://console.neon.tech/api/v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;projects&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;branches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_branches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;branches&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;primary_branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&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;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;branches&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&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="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;primary_branch&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;primary_branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_operation_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&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;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/operations/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;operation_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;endpoints&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;read_write&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;parent_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&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="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;operations&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;operation_details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_operation_details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;operation_details&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;finished&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&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;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/endpoints&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;endpoints&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;branch_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/roles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;roles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;protected&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/branches/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/roles/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/reveal_password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&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;p&gt;As you see, just a bunch of functions to call Neon API. Let’s see how we use them in our scripts.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;branch.py&lt;/strong&gt;
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_PROJECT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;primary_branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_branches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_BRANCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_BRANCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db_host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db_user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db_password&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;unbranch.py&lt;/strong&gt;
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;neon&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_branch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;neon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_PROJECT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NEON_BRANCH&lt;/span&gt;&lt;span class="sh"&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;

&lt;p&gt;While &lt;code&gt;unbranch.py&lt;/code&gt; is pretty straightforward, &lt;code&gt;branch.py&lt;/code&gt; is a bit more complicated. First, we find the project by its name. Then we find the primary branch we fork off and the branch for the preview environment. If the branch doesn’t exist, we create it. The creation of a branch is an asynchronous operation, so we have to wait for it to finish. Then we find the endpoint and the role for the new branch and get the password for the role. Finally, we mask the sensitive data and set the output variables for GitHub Actions. They are used later by other scripts, not shown here.&lt;/p&gt;

&lt;p&gt;And that’s it! We now have a fully automated workflow that creates a new database branch for each environment and deletes it when the environment is deleted.&lt;/p&gt;

&lt;p&gt;Explore the demo repository, &lt;a href="https://github.com/madhead/neonbranch" rel="noopener noreferrer"&gt;madhead/neonbranch&lt;/a&gt; to learn more tricks, like this:&lt;/p&gt;

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

&lt;p&gt;Two more things before you go.&lt;/p&gt;

&lt;p&gt;First, there is an official Neon GitHub Action, but with a slightly different logic and use case: &lt;a href="https://github.com/neondatabase/create-branch-action" rel="noopener noreferrer"&gt;neondatabase/create-branch-action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Second, if you’re using Vercel, there is an article for that: &lt;a href="https://neon.tech/blog/branching-with-preview-environments" rel="noopener noreferrer"&gt;A database for every preview environment using Neon, GitHub Actions, and Vercel&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>neon</category>
      <category>database</category>
      <category>postgres</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>From BotFather to 'Hello World'</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Thu, 27 Oct 2022 13:09:15 +0000</pubDate>
      <link>https://forem.com/madhead/from-botfather-to-hello-world-4dah</link>
      <guid>https://forem.com/madhead/from-botfather-to-hello-world-4dah</guid>
      <description>&lt;p&gt;Great news, everybody: a few days ago, Telegram team updated their Bot API documentation! Among other things, they now have an &lt;a href="https://core.telegram.org/bots/tutorial"&gt;end-to-end tutorial&lt;/a&gt; on the bot creation process: starting from bot registration in &lt;a href="https://t.me/BotFather"&gt;@BotFather&lt;/a&gt;, going all the way to database &amp;amp; deployment options.&lt;/p&gt;

&lt;p&gt;Unfortunately, it features Java, Maven, and &lt;a href="https://github.com/rubenlagus/TelegramBots"&gt;rubenlagus/TelegramBots&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;So, I’ve decided to copycat it, upgrading the examples to Kotlin, Gradle, and &lt;a href="https://github.com/InsanusMokrassar/TelegramBotAPI"&gt;InsanusMokrassar/TelegramBotAPI&lt;/a&gt;! Read my version below 👇🏼&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Parts of the text below are shamelessly copy-pasted from the &lt;a href="https://core.telegram.org/bots/tutorial"&gt;original tutorial&lt;/a&gt;. But don’t be tough on me: I respect copyright and authorship and in this case, I’m appealing for the &lt;a href="https://en.wikipedia.org/wiki/Fair_use"&gt;fair use doctrine&lt;/a&gt;, which, I believe, this article would fall into.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide will walk you through everything you need to know to build your first &lt;strong&gt;Telegram Bot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you already know your way around some of the basic steps, you can jump directly to the part you’re missing. Equivalent examples are available in &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.cs"&gt;C#&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.py"&gt;Python&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.go"&gt;Go&lt;/a&gt;, and &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/tree/main/Nodejs"&gt;TypeScript&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;
Basic Tutorial

&lt;ul&gt;
&lt;li&gt;Environment&lt;/li&gt;
&lt;li&gt;First Run&lt;/li&gt;
&lt;li&gt;Echo Bot&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Advanced Tutorial

&lt;ul&gt;
&lt;li&gt;Commands&lt;/li&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Hosting&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Further Reading&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At its core, you can think of the Telegram &lt;a href="https://core.telegram.org/bots/api"&gt;Bot API&lt;/a&gt; as software that provides &lt;a href="https://en.wikipedia.org/wiki/JSON"&gt;JSON-encoded&lt;/a&gt; responses to your queries.&lt;/p&gt;

&lt;p&gt;A bot, on the other hand, is essentially a routine, software or script that queries the API by means of an &lt;a href="https://core.telegram.org/bots/api#making-requests"&gt;HTTPS request&lt;/a&gt; and waits for a response. There are several types of &lt;a href="https://core.telegram.org/bots/api#available-methods"&gt;requests&lt;/a&gt; you can make, as well as many different &lt;a href="https://core.telegram.org/bots/api#available-types"&gt;objects&lt;/a&gt; that you can use and receive as responses.&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;your browser&lt;/strong&gt; is capable of sending HTTPS requests, you can use it to quickly try out the API. After &lt;a href="https://core.telegram.org/bots/tutorial#obtain-your-bot-token"&gt;obtaining your token&lt;/a&gt;, try pasting this string into your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://api.telegram.org/bot&amp;lt;YOUR_BOT_TOKEN&amp;gt;/getMe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In theory, you could interact with the API with &lt;strong&gt;basic requests&lt;/strong&gt; like this, either via your browser or other tailor-made tools like &lt;a href="https://curl.se"&gt;cURL&lt;/a&gt;. While this can work for simple requests like the example above, it’s not practical for larger applications and doesn’t scale well.&lt;/p&gt;

&lt;p&gt;For that reason, this guide will show you how to use &lt;a href="https://core.telegram.org/bots/samples"&gt;libraries and frameworks&lt;/a&gt;, along with some &lt;strong&gt;basic programming skills&lt;/strong&gt;, to build a more robust and scalable project.&lt;/p&gt;

&lt;p&gt;If you know how to code, you’ll fly right through each step in no time — and if you’re just starting out, this guide will show you everything you need to learn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We will use &lt;a href="https://en.wikipedia.org/wiki/Kotlin_(programming_language)"&gt;Kotlin&lt;/a&gt; throughout this guide as it’s one of the most popular programming languages, however, you can follow along with any language as all the steps are fundamentally the same. Since JVM is fully cross-platform, each code example will work with any operating system. If you pick another language, equivalent examples are available in &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.cs"&gt;C#&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.py"&gt;Python&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.go"&gt;Go&lt;/a&gt; and &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/tree/main/Nodejs"&gt;TypeScript&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting Ready
&lt;/h2&gt;

&lt;p&gt;First, we will briefly cover how to &lt;strong&gt;create your first project&lt;/strong&gt;, obtain your &lt;strong&gt;API token&lt;/strong&gt; and download all necessary &lt;strong&gt;dependencies and libraries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For the purposes of this guide, a similar bot from the &lt;a href="https://core.telegram.org/bots/tutorial"&gt;original tutorial&lt;/a&gt; is also live at &lt;a href="https://t.me/tutorialbot"&gt;@TutorialBot&lt;/a&gt; — feel free to check it out along the way to see how your own implementation should look after each step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtain Your Bot Token
&lt;/h3&gt;

&lt;p&gt;In this context, a &lt;strong&gt;token&lt;/strong&gt; is a string that authenticates your bot (not your account) on the bot API. Each bot has a unique token, which can also be revoked at any time via &lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Obtaining a token is as simple as contacting &lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;, issuing the &lt;code&gt;/newbot&lt;/code&gt; command, and following the steps until you’re given a new token. You can find a step-by-step guide &lt;a href="https://core.telegram.org/bots/features#creating-a-new-bot"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your token will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4839574812:AAFD39kkdpWt3ywyRZergyOLMaJhac60qc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Make sure to save your token in a secure place, treat it like a password, and &lt;strong&gt;don’t share it with anyone&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Download an IDE
&lt;/h3&gt;

&lt;p&gt;To program in Kotlin, you’ll need an &lt;a href="https://en.wikipedia.org/wiki/Integrated_development_environment"&gt;IDE&lt;/a&gt; — a special text editor that will let you write, compile and run your code. In this tutorial, we’ll use &lt;a href="https://www.jetbrains.com/idea"&gt;IntelliJ IDEA&lt;/a&gt; — there are several free, open source alternatives like &lt;a href="https://www.eclipse.org/ide"&gt;Eclipse&lt;/a&gt; or &lt;a href="https://netbeans.apache.org/download/index.html"&gt;NetBeans&lt;/a&gt; which work in the exact same way.&lt;/p&gt;

&lt;p&gt;You will also need a &lt;a href="https://en.wikipedia.org/wiki/Java_Development_Kit"&gt;JDK&lt;/a&gt;, a software kit that allows your Kotlin code to run. Most IDEs don’t include a JDK, so you should download a version compatible with your operating system separately. You can find a free, open source version &lt;a href="https://adoptium.net/temurin/releases"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you use another language, the steps are identical. You will just have to download a different IDE and software development kit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pick a Framework or Library
&lt;/h3&gt;

&lt;p&gt;You can think of a framework as software that handles all the low-level logic for you, including the API calls, and lets you focus on your bot-specific logic.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll use &lt;a href="https://github.com/InsanusMokrassar/TelegramBotAPI"&gt;InsanusMokrassar/TelegramBotAPI&lt;/a&gt;, but you can follow along with any equivalent implementation since all the underlying methods are either similar or exactly the same.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find many frameworks, along with code examples, in &lt;a href="https://core.telegram.org/bots/samples"&gt;the dedicated list&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create Your Project
&lt;/h3&gt;

&lt;p&gt;In IntelliJ, go to &lt;code&gt;File &amp;gt; New &amp;gt; Project&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fill in the fields accordingly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; — The name of your project. For example, &lt;em&gt;from-botfather-to-hello-world&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt; — Where to store your project. You can use the default value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt; — &lt;em&gt;Kotlin&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build System&lt;/strong&gt; — The framework that will handle your dependencies. Pick &lt;em&gt;Gradle&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JDK&lt;/strong&gt; — Pick whichever version you downloaded. We’ll be using version &lt;em&gt;17&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradle DSL&lt;/strong&gt; — Select &lt;em&gt;Kotlin&lt;/em&gt; as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Sample Code&lt;/strong&gt; — Leave this &lt;strong&gt;selected&lt;/strong&gt;, it will generate some needed files for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Settings &amp;gt; GroupId&lt;/strong&gt; — Use your reversed domain here or &lt;em&gt;tutorial&lt;/em&gt; for the sake of this guide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Settings &amp;gt; ArtifactId&lt;/strong&gt; — You can use the default value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="//storage.googleapis.com/madheadme-static/posts/from-botfather-to-hello-world/001.png" class="article-body-image-wrapper"&gt;&lt;img src="//storage.googleapis.com/madheadme-static/posts/from-botfather-to-hello-world/001.png" alt='"New Project" options'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After hitting &lt;em&gt;Create&lt;/em&gt;, if you did everything correctly, your &lt;strong&gt;Project&lt;/strong&gt; view in the top left should show a &lt;strong&gt;project structure&lt;/strong&gt; along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from-botfather-to-hello-world
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    ├── main
    │   ├── kotlin
    │   │   └── Main.kt
    │   └── resources
    └── test
        ├── kotlin
        └── resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Other IDEs will follow a similar pattern. Your dependency management system will have a different name (or no name at all if it’s built-in) depending on the language you chose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If this looks scary, don’t worry. We will only be using the &lt;code&gt;Main.kt&lt;/code&gt; file and the &lt;code&gt;build.gradle.kts&lt;/code&gt; file. In fact, to check that everything is working so far, double-click on &lt;em&gt;Main&lt;/em&gt; and click on the small green arrow on the left of &lt;code&gt;fun main&lt;/code&gt;, then select the first option (&lt;code&gt;Run 'MainKt'&lt;/code&gt;). If you followed the steps correctly, &lt;em&gt;Hello World!&lt;/em&gt; should appear in the console below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Framework Dependency
&lt;/h3&gt;

&lt;p&gt;We will now instruct the IDE to download and configure everything needed to work with the API. This is very easy and happens automatically behind the scenes.&lt;/p&gt;

&lt;p&gt;First, locate your &lt;code&gt;build.gradle.kts&lt;/code&gt; file on the left side of the screen. Open it by double-clicking and simply add the library to the &lt;code&gt;dependencies&lt;/code&gt; section:&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="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dev.inmo:tgbotapi:3.2.7"&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;When you’re done, your &lt;code&gt;build.gradle.kts&lt;/code&gt; should look something like &lt;a href="https://github.com/madhead/from-botfather-to-hello-world/blob/2a07c12eb7cc96e4522ef9f87b7feb1f8032b4e8/build.gradle.kts#L15-L18"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Coding
&lt;/h2&gt;

&lt;p&gt;We are ready to start coding. If you’re a beginner, consider that being familiar with your language of choice will greatly help. With this tutorial, you’ll be able to teach your bot basic behaviors, though more advanced features will require some coding experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Bot
&lt;/h3&gt;

&lt;p&gt;There are two mutually exclusive ways of &lt;a href="https://core.telegram.org/bots/api#getting-updates"&gt;receiving updates&lt;/a&gt; in your bot: either by &lt;strong&gt;polling&lt;/strong&gt; for them or by receiving them via a &lt;strong&gt;webhook&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Polling essentially means &lt;a href="https://core.telegram.org/bots/api#getupdates"&gt;asking for the updates&lt;/a&gt; in an endless loop, i.e. you pull the updates from Telegram servers. With &lt;a href="https://core.telegram.org/bots/api#setwebhook"&gt;webhooks&lt;/a&gt;, updates are pushed into your bot by Telegram via HTTPS.&lt;/p&gt;

&lt;p&gt;You decide which way suits you best, and the library provides you several methods to create bots depending on your choice.&lt;/p&gt;

&lt;p&gt;We’ll start with the &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.behaviour_builder/telegram-bot-with-behaviour-and-long-polling.html"&gt;&lt;code&gt;telegramBotWithBehaviourAndLongPolling&lt;/code&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;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&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="c1"&gt;// &amp;lt;1&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&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="c1"&gt;// &amp;lt;2&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="py"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;telegramBotWithBehaviourAndLongPolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;3&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allUpdatesFlow&lt;/span&gt;                                        &lt;span class="c1"&gt;// &amp;lt;4&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEach&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="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="c1"&gt;// &amp;lt;5&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchIn&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="c1"&gt;// &amp;lt;6&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                                     &lt;span class="c1"&gt;// &amp;lt;7&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;suspend&lt;/code&gt; modifier to your &lt;code&gt;main&lt;/code&gt; function, as the library makes heavy use of &lt;a href="https://kotlinlang.org/docs/coroutines-overview.html"&gt;coroutines&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It’s better to pass the token as an argument for your program than hard-coding it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;telegramBotWithBehaviourAndLongPolling&lt;/code&gt; returns a pair of values: &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.bot/index.html#-1167693203%2FClasslikes%2F-1982836883"&gt;the bot itself&lt;/a&gt; and a &lt;a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/"&gt;&lt;code&gt;Job&lt;/code&gt;&lt;/a&gt;. We’re not very interested in the &lt;code&gt;bot&lt;/code&gt; value, so we do not assign it to any variable, that’s what &lt;code&gt;_&lt;/code&gt; means. We’ll use this job later, read below.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.updateshandlers/-flows-updates-filter/all-updates-flow.html"&gt;&lt;code&gt;allUpdatesFlow&lt;/code&gt;&lt;/a&gt; is a &lt;a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/"&gt;&lt;code&gt;Flow&lt;/code&gt;&lt;/a&gt;, i.e. a lazy, potentially endless sequence of &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.update.abstracts/-update/index.html"&gt;&lt;code&gt;Updates&lt;/code&gt;&lt;/a&gt;. Flows are used to listen to or transform the values that &lt;em&gt;flow&lt;/em&gt; through them.&lt;/li&gt;
&lt;li&gt;Here we just listen for the values and print them.&lt;/li&gt;
&lt;li&gt;This listening process should occur in a &lt;a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/"&gt;&lt;code&gt;CoroutineScope&lt;/code&gt;&lt;/a&gt;. For this simple example, we’ll use a &lt;a href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/"&gt;&lt;code&gt;GlobalScope&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Remember the job that we get at step #3? Here we wait for its completion. And this job would never actually complete on its own because the number of updates your bot potentially receives is unbound.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might be confused by all these coroutines, jobs, scopes, flows, and, especially, by the waiting for the completion of an endless loop — it sounds like nonsense! But those are the very basic concepts and patterns of Kotlin Coroutines. After finishing this tutorial you might want to &lt;a href="https://kotlinlang.org/docs/coroutines-overview.html"&gt;read more&lt;/a&gt; about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Run
&lt;/h2&gt;

&lt;p&gt;It’s time to &lt;strong&gt;run your bot&lt;/strong&gt; for the first time. Hit the green arrow to the left of &lt;code&gt;fun main&lt;/code&gt; and select the first option (&lt;code&gt;Run 'MainKt'&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And then there was nothing.&lt;/em&gt; Yes, a bit anticlimactic. This is because your bot &lt;strong&gt;has nothing to print&lt;/strong&gt; – there are &lt;strong&gt;no new updates&lt;/strong&gt; because nobody messaged it yet.&lt;/p&gt;

&lt;p&gt;If you try messaging the bot on Telegram, you’ll then see &lt;strong&gt;new updates&lt;/strong&gt; pop up in the console. At this point, you have your very own Telegram Bot – quite the achievement. Now, on to making it a bit more intelligent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If nothing pops up, make sure you messaged the right bot and that the token you pasted in the code is correct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Receiving Messages
&lt;/h2&gt;

&lt;p&gt;Every time someone sends a &lt;strong&gt;private message&lt;/strong&gt; to your bot, the &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling/on-content-message.html"&gt;&lt;code&gt;onContentMessage&lt;/code&gt;&lt;/a&gt; callback will be called, and you’ll be able to handle the update (named &lt;code&gt;it&lt;/code&gt;, if you don’t override the name of the parameter), which contains the message, along with a great deal of other info which you can see detailed here.&lt;/p&gt;

&lt;p&gt;Let’s focus on two values for now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The user&lt;/strong&gt; — Who sent the message. Access it via &lt;code&gt;it.from&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The message&lt;/strong&gt; — What was sent. Access it via &lt;code&gt;it.text&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Knowing this, we can make it a bit more clear in the &lt;strong&gt;console output&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-this.allUpdatesFlow.onEach { println(it) }.launchIn(GlobalScope)
&lt;/span&gt;
+onContentMessage {
&lt;span class="gi"&gt;+    val user = it.from
+    println("${user?.firstName ?: "Unknown user"} wrote ${it.text}")
+}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just a basic example – you can now play around with all the methods to see everything you can pull out of these objects. You can try &lt;code&gt;it.from.firstName&lt;/code&gt;, &lt;code&gt;it.chat&lt;/code&gt;, and dozens more.&lt;/p&gt;

&lt;p&gt;Knowing how to receive, process, and print &lt;strong&gt;incoming messages&lt;/strong&gt;, now it’s time to learn how to &lt;strong&gt;answer them&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember to stop and re-launch your bot after each change to the code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sending Messages
&lt;/h2&gt;

&lt;p&gt;To send a private text message, you generally need &lt;strong&gt;three things&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user &lt;strong&gt;must&lt;/strong&gt; have contacted your bot first. (Unless the user sent a join request to a group where your bot is an admin, but that’s a more advanced scenario).&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;must&lt;/strong&gt; have previously saved the &lt;strong&gt;User ID&lt;/strong&gt; (&lt;code&gt;it.from.id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;String&lt;/code&gt; object containing the message text, 1–4096 characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that out of the way, let’s send the first message:&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;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&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;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;telegramBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                   &lt;span class="c1"&gt;// &amp;lt;1&amp;gt;&lt;/span&gt;

    &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;// &amp;lt;2&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;As long as we don’t build a complex behavior for the bot here, a simple &lt;code&gt;telegramBot&lt;/code&gt; is enough for this example.&lt;/li&gt;
&lt;li&gt;For this example, we’ll assume your ID is &lt;code&gt;1234&lt;/code&gt;. You could get your real ID from the updates received in the previous steps, or by contacting &lt;a href="https://t.me/myidbot"&gt;@myidbot&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you did everything correctly, your bot should text you &lt;em&gt;Hello World!&lt;/em&gt; every time you launch your code. Sending messages to groups or channels – assuming you have the relevant permissions – is as simple as replacing &lt;code&gt;1234&lt;/code&gt; with the ID of the respective chat.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Try experimenting with other types of messages, like &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.api.send.media/send-photo.html"&gt;&lt;code&gt;sendPhoto&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.api.send.media/send-sticker.html"&gt;&lt;code&gt;sendSticker&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.api.send/send-dice.html"&gt;&lt;code&gt;sendDice&lt;/code&gt;&lt;/a&gt;… A full list is available starting &lt;a href="https://core.telegram.org/bots/api#sendmessage"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Echo Bot
&lt;/h2&gt;

&lt;p&gt;Let’s practice everything we tried so far by coding an &lt;strong&gt;Echo Bot&lt;/strong&gt;. Its functionality will be rather simple: every text message it receives will be sent right back to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copying Text
&lt;/h3&gt;

&lt;p&gt;The most intuitive way of coding this is just replying to any message… with itself.&lt;/p&gt;

&lt;p&gt;In other words:&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;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&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;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;telegramBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildBehaviourWithLongPolling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                            &lt;span class="c1"&gt;// &amp;lt;1&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;onContentMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                         &lt;span class="c1"&gt;// &amp;lt;2&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                      &lt;span class="c1"&gt;// &amp;lt;3&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&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;ol&gt;
&lt;li&gt;Here we’ll explore just another way to set up long polling and bot behavior: &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.behaviour_builder/build-behaviour-with-long-polling.html"&gt;&lt;code&gt;buildBehaviourWithLongPolling&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling/on-content-message.html"&gt;&lt;code&gt;onContentMessage&lt;/code&gt;&lt;/a&gt; will be called for every message with &lt;em&gt;content&lt;/em&gt;, e.g. text, stickers, and pictures. There are messages with no content as well, like &lt;a href="https://core.telegram.org/bots/api#chatjoinrequest"&gt;join requests&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.api.send/reply.html"&gt;&lt;code&gt;reply&lt;/code&gt;&lt;/a&gt; simply replies to a message.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;This tutorial assumes that updates always contain messages for the sake of simplicity. This may not always be true – be sure to implement all the proper checks in your code to handle every type of update with the appropriate methods.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Executing Commands
&lt;/h2&gt;

&lt;p&gt;To learn what a command is and how it works, we recommend reading this &lt;a href="https://core.telegram.org/bots/features#commands"&gt;dedicated summary&lt;/a&gt;. In this guide, we’ll focus on the technical side of things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Your Command
&lt;/h3&gt;

&lt;p&gt;Begin by opening &lt;a href="https://t.me/botfather"&gt;@BotFather&lt;/a&gt;. Type &lt;code&gt;/mybots&lt;/code&gt; &amp;gt; &lt;em&gt;Your_Bot_Name&lt;/em&gt; &amp;gt; Edit Bot &amp;gt; Edit Commands.&lt;/p&gt;

&lt;p&gt;Now send a new command, followed by a brief description. For the purpose of this tutorial, we’ll implement two simple commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scream — Speak, I'll scream right back
whisper — Shhhhhhh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Command Logic
&lt;/h3&gt;

&lt;p&gt;We want the &lt;strong&gt;Echo Bot&lt;/strong&gt; to reply in uppercase when it’s in &lt;strong&gt;scream mode&lt;/strong&gt; and normally otherwise.&lt;/p&gt;

&lt;p&gt;First, let’s &lt;strong&gt;create a variable&lt;/strong&gt; to store the current mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;suspend fun main(args: Array&amp;lt;String&amp;gt;) {
&lt;/span&gt;    val token = args[0]
    val bot = telegramBot(token)
&lt;span class="gi"&gt;+   var screaming = false
&lt;/span&gt;
    …
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let’s add a logic to &lt;strong&gt;switch the mode&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;bot.buildBehaviourWithLongPolling {
&lt;/span&gt;    …

+   onCommand("scream") {
&lt;span class="gi"&gt;+       screaming = true
+   }
+   onCommand("whisper") {
+       screaming = false
+   }
&lt;/span&gt;
    …
&lt;span class="err"&gt;}.join()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling/on-command.html"&gt;&lt;code&gt;onCommand&lt;/code&gt;&lt;/a&gt; is called whenever the bot receives a command that is passed as a parameter to the &lt;code&gt;onCommand&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s add an &lt;a href="https://kotlinlang.org/docs/extensions.html"&gt;extension&lt;/a&gt; for the &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.message.abstracts/-common-message/index.html"&gt;&lt;code&gt;CommonMessage&lt;/code&gt;&lt;/a&gt; to check if it contains any commands. We’ll use this extension as a &lt;em&gt;filter&lt;/em&gt; in the next step.&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;CommonMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;*&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasCommands&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;textSources&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;any&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="nf"&gt;botCommandTextSourceOrNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;CommonMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;*&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNoCommands&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasCommands&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Probably when you’re reading these lines, these extensions are already &lt;a href="https://github.com/InsanusMokrassar/TelegramBotAPI/pull/669"&gt;included&lt;/a&gt; in the library.&lt;/p&gt;

&lt;p&gt;Finally, let’s tune our &lt;code&gt;onContentMessage&lt;/code&gt; to support the modality.&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="nf"&gt;onContentMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;initialFilter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CommonMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MessageContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;hasNoCommands&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;text&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;text&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;screaming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;text&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="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it checks if the message is a text. If it is, the bot additionally checks if it is in a screaming mode before it replies.&lt;/p&gt;

&lt;p&gt;And that’s it. Now the bot can &lt;strong&gt;execute commands&lt;/strong&gt; and change its behavior accordingly.&lt;/p&gt;

&lt;p&gt;Naturally, this simplified logic will change the bot’s behavior for &lt;strong&gt;everyone&lt;/strong&gt; – not just the person who sent the command. This can be fun for this tutorial but &lt;strong&gt;won’t work in a production environment&lt;/strong&gt; – consider using a Map, dictionary, or equivalent data structure to assign settings for individual users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember to always implement a few basic &lt;a href="https://core.telegram.org/bots/features#global-commands"&gt;global commands&lt;/a&gt;. You can practice by implementing simple feedback to the &lt;code&gt;/start&lt;/code&gt; command, which we intentionally left out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Buttons and Keyboards
&lt;/h2&gt;

&lt;p&gt;To streamline and simplify user interaction with your bot, you can replace many text-based exchanges with handy buttons. These buttons can perform a wide variety of actions and can be customized for each user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Button Types
&lt;/h3&gt;

&lt;p&gt;There are two main types of buttons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reply Buttons&lt;/strong&gt; — are used to provide a list of predefined text &lt;a href="https://core.telegram.org/bots/features#keyboards"&gt;reply options&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline Buttons&lt;/strong&gt; — are used to offer quick navigation, shortcuts, URLs, games and &lt;a href="https://core.telegram.org/bots/features#inline-keyboards"&gt;so much more&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using these buttons is as easy as attaching a &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.buttons/-reply-keyboard-markup/index.html"&gt;&lt;code&gt;ReplyKeyboardMarkup&lt;/code&gt;&lt;/a&gt; or an &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.buttons/-inline-keyboard-markup/index.html"&gt;&lt;code&gt;InlineKeyboardMarkup&lt;/code&gt;&lt;/a&gt; to your message.&lt;/p&gt;

&lt;p&gt;This guide will focus on &lt;strong&gt;inline buttons&lt;/strong&gt; since they only require a few extra lines of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Buttons
&lt;/h3&gt;

&lt;p&gt;First of all, let’s create some buttons.&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;next&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CallbackDataInlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callbackData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"next"&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;back&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CallbackDataInlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Back"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callbackData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"back"&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;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;URLInlineKeyboardButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Tutorial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://madhead.me/posts/from-botfather-to-hello-world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s go back through the fields we specified:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text&lt;/strong&gt; — This is what the user will see, the text that appears on the button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Callback Data&lt;/strong&gt; — This will be sent back to the code instance as part of a new &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.update/-callback-query-update/index.html"&gt;&lt;code&gt;CallbackQueryUpdate&lt;/code&gt;&lt;/a&gt;, so we can quickly identify what button was clicked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Url&lt;/strong&gt; — A button that specifies a URL doesn’t specify callbackdata since its behavior is predefined – it will open the given link when tapped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating Keyboards
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;buttons&lt;/strong&gt; we created can be assembled into two &lt;strong&gt;keyboards&lt;/strong&gt;, which will then be used to navigate back and forth between two &lt;strong&gt;sample menus&lt;/strong&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;firstMenuMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;flatInlineKeyboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;next&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;secondMenuMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inlineKeyboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;url&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;blockquote&gt;
&lt;p&gt;You can place this code wherever you prefer, the important thing is to make sure that keyboard variables are accessible from the method call that will send the new menu. If you’re confused by this concept and don’t know where to put them, just paste them above the command processing flow. Or just look at the final code &lt;a href="https://github.com/madhead/from-botfather-to-hello-world/blob/main/src/main/kotlin/Main.kt"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Sending Keyboards
&lt;/h3&gt;

&lt;p&gt;Sending a keyboard only requires specifying a reply markup for the message.&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="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;someChat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;someText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;replyMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;someReplyMarkup&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Menu Trigger
&lt;/h3&gt;

&lt;p&gt;We could send a new menu for each new user, but for simplicity let’s add a new command that will spawn a menu. We can achieve this by adding a new &lt;code&gt;onCommand("menu")&lt;/code&gt; clause to the previous &lt;em&gt;behavior block&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="nf"&gt;onCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"menu"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chat&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;chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;b&amp;gt;Menu 1&amp;lt;/b&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;parseMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTMLParseMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;replyMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstMenuMarkup&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;blockquote&gt;
&lt;p&gt;You may have noticed that we also added a new parameter, &lt;code&gt;parseMode = HTMLParseMode&lt;/code&gt;. This is called a formatting option and will allow us to use HTML tags and add formatting to the text later on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Try sending &lt;code&gt;/menu&lt;/code&gt; to your bot now. If you did everything correctly, you should see a brand new menu pop up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a production environment, commands should be handled with an appropriate design pattern that isolates them into different executor classes – modular and separated from the main logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Navigation
&lt;/h2&gt;

&lt;p&gt;When building complex bots, navigation is essential. Your users must be able to move seamlessly from one menu to the next.&lt;/p&gt;

&lt;p&gt;In this example, we want the &lt;code&gt;Next&lt;/code&gt; button to lead the user to the second menu. The &lt;code&gt;Back&lt;/code&gt; button will send us back. To do that, we will start processing incoming &lt;a href="https://tgbotapi.inmo.dev/docs/dev.inmo.tgbotapi.types.queries.callback/-message-data-callback-query/index.html"&gt;&lt;code&gt;MessageDataCallbackQueries&lt;/code&gt;&lt;/a&gt;, which are the results we get after the user taps on a button.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;MessageDataCallbackQuery&lt;/code&gt; is essentially composed of three main parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;id&lt;/strong&gt; — Needed to close the query. You &lt;strong&gt;must always&lt;/strong&gt; close new queries after processing them – if you don’t, a loading symbol will keep showing on the user’s side on top of each button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;from&lt;/strong&gt; — The user who pressed the button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;data&lt;/strong&gt; — This identifies which button was pressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Processing in this context just means &lt;strong&gt;executing the action&lt;/strong&gt; uniquely identified by the button, then &lt;strong&gt;closing the query&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A very basic button handler could look something 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="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildBehaviourWithLongPolling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;onMessageDataCallbackQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;chatId&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messageId&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secondMenu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;parseMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTMLParseMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;replyMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secondMenuMarkup&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="nf"&gt;onMessageDataCallbackQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"back"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&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;message&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ContentMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstMenu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;parseMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTMLParseMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;replyMarkup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstMenuMarkup&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="nf"&gt;onDataCallbackQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answerCallbackQuery&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these handlers, whenever a button is tapped, your bot will automatically navigate between inline menus. Expanding on this concept allows for endless combinations of navigable submenus, settings, and dynamic pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database
&lt;/h2&gt;

&lt;p&gt;Telegram &lt;strong&gt;does not&lt;/strong&gt; host an update database for you – once you process and consume an update, it will no longer be available. This means that features like user lists, message lists, current user inline menus, settings, etc. &lt;strong&gt;have to be implemented and maintained&lt;/strong&gt; by bot developers.&lt;/p&gt;

&lt;p&gt;If your bot needs one of these features and you want to get started on &lt;strong&gt;data persistence&lt;/strong&gt;, we recommend that you look into &lt;a href="https://en.wikipedia.org/wiki/Serialization"&gt;serialization&lt;/a&gt; practices and libraries for your language of choice, as well as available databases.&lt;/p&gt;

&lt;p&gt;Implementing a database is out of scope for this guide, however, several guides are available online for simple embedded &lt;strong&gt;open source&lt;/strong&gt; software solutions like &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt;, &lt;a href="https://hsqldb.org"&gt;HyperSQL&lt;/a&gt;, &lt;a href="https://db.apache.org/derby"&gt;Derby&lt;/a&gt;, and many more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your language of choice will also influence which databases are available and supported – the list above assumes you followed this Kotlin tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hosting
&lt;/h2&gt;

&lt;p&gt;So far, your bot has been running on your &lt;strong&gt;local machine&lt;/strong&gt; – your PC. While this may be good for &lt;strong&gt;developing&lt;/strong&gt;, &lt;strong&gt;testing&lt;/strong&gt;, and &lt;strong&gt;debugging&lt;/strong&gt;, it is not ideal for a production environment. You’ll want your bot to be available and responsive at all times, but your computer might not always be online.&lt;/p&gt;

&lt;p&gt;This can be done in four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Package your code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Making your bot &lt;strong&gt;easy to move&lt;/strong&gt; and &lt;strong&gt;runnable&lt;/strong&gt; outside of an IDE is essential to &lt;strong&gt;host it elsewhere&lt;/strong&gt;. If you followed this tutorial, this &lt;a href="https://www.jetbrains.com/help/idea/compiling-applications.html#run_packaged_jar"&gt;standard guide&lt;/a&gt; will work for you. If you didn’t, look into &lt;strong&gt;export or packaging guides&lt;/strong&gt; for your IDE and language of choice – procedures may vary but the end result is the same.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Purchase a VPS or equivalent service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A server is essentially a machine that is always online and running, without you having to worry about anything. To host your bot, you can opt for a &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_server"&gt;VPS&lt;/a&gt; which serves this purpose and can be rented from several different providers. Another option would be to purchase a network-capable &lt;a href="https://en.wikipedia.org/wiki/Microcontroller"&gt;microcontroller&lt;/a&gt;, which come in all different specs and sizes depending on your needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You should ensure that all user data remains &lt;strong&gt;heavily encrypted at all times&lt;/strong&gt; in your database to guarantee the privacy of your users. The same concept applies to your local instance, however, this becomes especially important once you transfer your database to a remote server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Upload your executable/package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you have a working &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell"&gt;ssh&lt;/a&gt; connection between your machine and your new server, you should upload your executable and all associated files. We will assume the runnable jar &lt;code&gt;TutorialBot.jar&lt;/code&gt; and its database &lt;code&gt;dbase.db&lt;/code&gt; are currently in the &lt;code&gt;/TBot&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;scp &lt;span class="nt"&gt;-r&lt;/span&gt; /TBot/ username@server_ip:/bots/TBotRemote/
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run your application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Depending on which language you chose, you might have to configure your server environment differently. If you chose Kotlin, you just need to install a compatible JDK.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;openjdk-17-jre
&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If you did everything correctly, you should see a Java version as the output, along with a few other values. This means you’re ready to run your application.&lt;/p&gt;

&lt;p&gt;Now, to run the executable:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /bots/TBotRemote/
&lt;span class="nv"&gt;$ &lt;/span&gt;java &lt;span class="nt"&gt;-jar&lt;/span&gt; TutorialBot.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your bot is now online and users can interact with it at any time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To streamline and modularize this process, you could employ a specialized &lt;a href="https://www.docker.com/resources/what-container/"&gt;docker container&lt;/a&gt; or equivalent service. If you followed along in one of the equivalent examples (&lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.cs"&gt;C#&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.py"&gt;Python&lt;/a&gt;, &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/blob/main/TutorialBot.go"&gt;Go&lt;/a&gt;, and &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/tree/main/Nodejs"&gt;TypeScript&lt;/a&gt;) you can find a detailed set of instructions to export and run your code &lt;a href="https://gitlab.com/Athamaxy/telegram-bot-tutorial/-/tree/main"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If you got this far, you might be interested in these additional guides and docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots"&gt;General Bot Platform Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots/features"&gt;Detailed List of Bot Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots/api"&gt;Full API Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you encounter any issues while following this guide, you can contact us on Telegram at &lt;a href="https://t.me/botsupport"&gt;@BotSupport&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>kotlin</category>
      <category>bots</category>
    </item>
    <item>
      <title>Answer: What is the differences between the term SSTable and LSM Tree</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Thu, 29 Sep 2022 10:55:49 +0000</pubDate>
      <link>https://forem.com/madhead/answer-what-is-the-differences-between-the-term-sstable-and-lsm-tree-4o67</link>
      <guid>https://forem.com/madhead/answer-what-is-the-differences-between-the-term-sstable-and-lsm-tree-4o67</guid>
      <description>&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AoTUKOcU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/stackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
          &lt;a href="https://stackoverflow.com/questions/58168809/what-is-the-differences-between-the-term-sstable-and-lsm-tree/71106360#71106360" rel="noopener noreferrer"&gt;
            &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: What is the differences between the term SSTable and LSM Tree
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Feb 14 '22&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/58168809/what-is-the-differences-between-the-term-sstable-and-lsm-tree/71106360#71106360" rel="noopener noreferrer"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oeieW07A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/stackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          47
        &lt;/div&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h2-sXgSn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/stackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;Probably one of the best explanations of SSTables and LSM-Trees for mortals is given by &lt;a href="https://stackoverflow.com/users/121436"&gt;Martin Kleppmann&lt;/a&gt; in his &lt;a href="https://dataintensive.net" rel="noreferrer"&gt;"Designing Data-Intensive Applications"&lt;/a&gt; book. These data structures are explained in chapter 3, "Storage and Retrieval", pages 69 through 79. It's a really great read, I would recommend the whole book!&lt;/p&gt;
&lt;p&gt;Impatient…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://stackoverflow.com/questions/58168809/what-is-the-differences-between-the-term-sstable-and-lsm-tree/71106360#71106360" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>database</category>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>datastructures</category>
    </item>
    <item>
      <title>Docker-based GitHub Actions in orphan branches</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Mon, 11 Oct 2021 00:51:19 +0000</pubDate>
      <link>https://forem.com/madhead/docker-based-github-actions-in-orphan-branches-3ajd</link>
      <guid>https://forem.com/madhead/docker-based-github-actions-in-orphan-branches-3ajd</guid>
      <description>&lt;p&gt;GitHub Actions are great for sharing &amp;amp; reusing logic across your CI/CD pipelines. However, sometimes sharing is not the highest priority. Sometimes you only want to hide the complexity by exctacting &amp;amp; encapsulating multiple build steps and replacing them with a single action.&lt;/p&gt;

&lt;p&gt;Anyway, you have to create a GitHub action.&lt;/p&gt;

&lt;p&gt;You could use actions &lt;a href="https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#example-using-an-action-in-the-same-repository-as-the-workflow"&gt;stored in the same repository as the workflow&lt;/a&gt;. Such actions must be either written in JavaScript or be Docker actions, i.e. have a &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With JavaScript, at the time of the writing, actions must be written literally in JavaScript, no processing like compilation or transpilation allowed. In other words, it cannot be TypeScript: although you could use TypeScript to write an action, you’ll have to commit the &lt;code&gt;dist&lt;/code&gt; then.&lt;/p&gt;

&lt;p&gt;Docker actions are slow because the caching doesn’t work well at the time of the writing. Images are built from scratch every time you run a pipeline.&lt;/p&gt;

&lt;p&gt;Finally, you may be against of polluting the main codebase with action’s code, and this is a valid argument too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1EdR5gsd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d90666e189gi3x2nh3zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1EdR5gsd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d90666e189gi3x2nh3zs.png" alt="alt text"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Because of this issues — polluting main codebase, forcing you to use vanilla JavaScript or being slow with other languages and runtimes — in-repository actions might not look appealing to you.&lt;/p&gt;

&lt;p&gt;So you might want to take the hard way: to extract your action into a separate repository. But even then caching won’t work well with Docker actions and you’ll have to commit &lt;code&gt;dist&lt;/code&gt; with JavaScript actions. However, in a separate repository you could at least have action’s own lifecycle and mitigate that with automation.&lt;/p&gt;

&lt;p&gt;😔 That’s some extra work…&lt;/p&gt;

&lt;p&gt;Try &lt;a href="https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---orphanltnewbranchgt"&gt;orphan branches&lt;/a&gt; instead!&lt;/p&gt;

&lt;p&gt;The idea is to create an orphan branch in your repository. Orphan means it wont have any intersections or common commits with the main branch, a completely separate one. GitHub won’t even allow you to make a PR to the main branch from the orphan one.&lt;/p&gt;

&lt;p&gt;Thinking about Git as a tree was a mistake, it’s actually a forest!&lt;/p&gt;

&lt;p&gt;Now you could store your whole action, written in any language, in this branch. It would have a completely different lifecycle by having completely different workflows in this branch’s &lt;code&gt;.github/workflows&lt;/code&gt; directory. If it’s a Docker action, you could built and publish it to the GitHub packages of the same repository and use it the main branch’s workflows without rebuilding every time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ungyxDzX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyx145bhufc10gvk6h3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ungyxDzX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyx145bhufc10gvk6h3h.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the issues are solved!&lt;/p&gt;

&lt;p&gt;✅ The codebase wouldn’t have any traces of the auxiliary actions code.&lt;br&gt;&lt;br&gt;
✅ Actions would have a completely independent lifecycle.&lt;br&gt;&lt;br&gt;
✅ Docker actions would be prebuilt.  &lt;/p&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Two design patterns for Telegram Bots </title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Mon, 13 Sep 2021 23:08:01 +0000</pubDate>
      <link>https://forem.com/madhead/two-design-patterns-for-telegram-bots-59f5</link>
      <guid>https://forem.com/madhead/two-design-patterns-for-telegram-bots-59f5</guid>
      <description>&lt;p&gt;I've just launched my next Telegram Bot: &lt;a href="https://t.me/TyzenhausBot" rel="noopener noreferrer"&gt;Tyzenhaus&lt;/a&gt;. It is a shared expenses tracking bot. Whether you are a group of travelers ✈️, roommates 🏠, or just friends 👫, Tyzenhaus will help you settle up your debts. Just like Splitwise, but simpler and directly in Telegram. This half-minute video describes it:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SDbE_OeLqno"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So, with a dozen bots written in Kotlin for the past two years, I want to sum up my experience and name the two most important design/architecture patterns for Telegram Bots. You'll definitely find them useful when making your own bot.&lt;/p&gt;

&lt;h1&gt;
  
  
  Chain of Responsibility
&lt;/h1&gt;

&lt;p&gt;When writing web apps, we, the backend developers, are used to have different handlers for different URLs. Those are configured in a framework-specific manner, like annotations in Java, URLconf in Django or routing in Node.js. Let's use this name — routing — to describe this way of configuring the logic.&lt;/p&gt;

&lt;p&gt;But this is not the case with Telegram Bot API. If you're using webhooks, all the updates will be sent to a single destination in your application. It also works the same way with long polling: you'll have a single place in your codebase, calling &lt;code&gt;getUpdates&lt;/code&gt; in a loop. There is no routing and you have to implement it yourself.&lt;/p&gt;

&lt;p&gt;The best way to do that is by employing a &lt;a href="https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern" rel="noopener noreferrer"&gt;chain of responsibility pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To do that, you should separate your bot logic and enclose it into small bits. Let's name them handlers. Handlers could be functions or classes, it doesn't really matter. Every handler is completely independent in a way that it solely decides whether it processes a particular incoming update or not. This is crucial and this is what makes chain of responsibility different from routing. So let me repeat: a handler decides to process or skip the update on its own, not an external router.&lt;/p&gt;

&lt;p&gt;Finally, you combine your handlers in a list and dispatch every incoming update through it.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-Wtxe_cP-FI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is a general concept, actual implementations may vary. This chain shouldn't even be sequential: handlers could run in parallel. Although make sure that at most one handler will process an update, otherwise you may have flaky replies or replies out of order.&lt;/p&gt;

&lt;h1&gt;
  
  
  Finite State Machine
&lt;/h1&gt;

&lt;p&gt;Another thing we're used to are smarts client for our APIs. Even the so-called &lt;a href="https://en.wikipedia.org/wiki/Thin_client" rel="noopener noreferrer"&gt;thin clients&lt;/a&gt; are smarter than Telegram! Although we don't trust the input and always validate it on the backend (don't we?), clients may have forms grouping multiple fields and even wizard-like workflows, combining multiple forms. I mean, usually, you get a whole JSON object with multiple fields as an input. You never have to process the input one field at a time, which is exactly the case with Telegram.&lt;/p&gt;

&lt;p&gt;Thus, you have to store all the state on a server. You may have heard about &lt;a href="https://en.wikipedia.org/wiki/State_pattern" rel="noopener noreferrer"&gt;state pattern&lt;/a&gt;, but what I'm using is more like &lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine" rel="noopener noreferrer"&gt;FSM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;FSM is just a fancy way of saying "a graph". FSM is a directed graph of states with well-known transitions between them. "Well-known" means that for every possible state you know the exact rules for transitions to other states. This predictability makes FSMs very easy to understand, maintain and extend.&lt;/p&gt;

&lt;p&gt;A lot of processes could be described with FSMs. Look at this one, describing the expense workflow in my bot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fm1croa03n3kle9tk8vmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fm1croa03n3kle9tk8vmj.png" alt="FSM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Green edges are "success paths", when the input is valid and the logic state could be transitioned. Red edges are unsuccessful attempts of user to provide input. E.g. passing arbitrary non-numeric strings as monetary amounts.&lt;br&gt;
You've got the idea in a few seconds, don't you?&lt;/p&gt;

&lt;h1&gt;
  
  
  Combining chain of responsibility with FSM
&lt;/h1&gt;

&lt;p&gt;And here is the beauty of those two patterns combined in three simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Process updates with a chain of responsibility. Handlers should have two inputs: incoming Telegram Bot API update and current state. It could be a state for a single user in a private chat, a group state, or even a particular user state in a particular group — it doesn't matter;&lt;/li&gt;
&lt;li&gt;Save the resulting state;&lt;/li&gt;
&lt;li&gt;Repeat;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You could implement literally any bot for any messenger with this simple recipe. Don't you believe me? Go and try yourself, I bet you have some great ideas!&lt;/p&gt;

&lt;p&gt;🤑 &lt;a href="https://t.me/TyzenhausBot" rel="noopener noreferrer"&gt;Try Tyzenhaus out&lt;/a&gt; | ⭐ &lt;a href="https://github.com/madhead/tyzenhaus" rel="noopener noreferrer"&gt;Star it on GitHub&lt;/a&gt; | 🤬 &lt;a href="https://github.com/madhead/tyzenhaus/issues/new" rel="noopener noreferrer"&gt;Create an issue on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>showdev</category>
      <category>design</category>
    </item>
    <item>
      <title>Reading Java properties in GitHub Actions</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Tue, 06 Apr 2021 09:32:38 +0000</pubDate>
      <link>https://forem.com/madhead/reading-java-properties-in-github-actions-2c2k</link>
      <guid>https://forem.com/madhead/reading-java-properties-in-github-actions-2c2k</guid>
      <description>&lt;p&gt;I just want to share another GitHub Action (create by me) that you may find useful in your Java projects.&lt;/p&gt;

&lt;p&gt;Java projects often use &lt;code&gt;.properties&lt;/code&gt; files to store configuration. Personally, I hate them in favor of YML or even XML (I beg you: use XML to configure Log4j, not &lt;code&gt;.properties&lt;/code&gt;), but still, I have to work with them. There are existing actions in the Marketplace able to parse those, but they usually use &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;bash&lt;/code&gt;. Although it may work, I think it's more robust to use special facilities, like &lt;code&gt;java.util.Properties&lt;/code&gt; to read values from &lt;code&gt;.properties&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;So, I wrote a simple Kotlin program that reads Java &lt;code&gt;.properties&lt;/code&gt; and wrapped it into a GitHub Action. This Action makes values from &lt;code&gt;.properties&lt;/code&gt; available in your GitHub Actions workflows.&lt;/p&gt;

&lt;p&gt;Returning a single property is as simple as:&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="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;madhead/read-java-properties@latest&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;version&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;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gradle.properties&lt;/span&gt;
    &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;version&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.1&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ steps.version.outputs.value }}&lt;/span&gt; &lt;span class="c1"&gt;# Project's version from gradle.properties or 0.0.1 if it is not defined there&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you could return all the values from the &lt;code&gt;.properties&lt;/code&gt; file by employing the &lt;code&gt;all&lt;/code&gt; flag:&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="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;madhead/read-java-properties@latest&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;all&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;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gradle.properties&lt;/span&gt;
    &lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ steps.all.outputs.version }}&lt;/span&gt; &lt;span class="c1"&gt;# Project's version from gradle.properties&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo ${{ steps.all.outputs.groupId }}&lt;/span&gt; &lt;span class="c1"&gt;# Project's groupId from gradle.properties&lt;/span&gt;
  &lt;span class="s"&gt;…&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all for today, just save the link: &lt;a href="https://github.com/madhead/read-java-properties"&gt;madhead/read-java-properties&lt;/a&gt;, and, as always, feel free to open issues if something is not working as needed or expected.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>github</category>
      <category>actionshackaton</category>
    </item>
    <item>
      <title>One-stop shop for working with semantic versions in your GitHub Actions workflows</title>
      <dc:creator>madhead</dc:creator>
      <pubDate>Tue, 30 Mar 2021 08:21:56 +0000</pubDate>
      <link>https://forem.com/madhead/one-stop-shop-for-working-with-semantic-versions-in-your-github-actions-workflows-327a</link>
      <guid>https://forem.com/madhead/one-stop-shop-for-working-with-semantic-versions-in-your-github-actions-workflows-327a</guid>
      <description>&lt;p&gt;GitHub Actions are so cool!&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://youtu.be/-sr9_AItFQQ"&gt;our recording&lt;/a&gt; about them @ &lt;a href="https://www.youtube.com/channel/UCFMIGfAAUXUqnnNcWpdezJQ"&gt;Ministry of Testing Abu Dhabi&lt;/a&gt; if you want a quick sneak peek, but today I want to self-advertise one of the reusable Actions I made for you. And it wasn’t hard at all, thanks to the way they work.&lt;/p&gt;

&lt;p&gt;You have heard about &lt;a href="https://semver.org"&gt;Semantic Versioning&lt;/a&gt;, haven’t you? If you are a developer you probably know a lot of libraries using Semantic Versioning. Chances are, you even use it for your projects.&lt;/p&gt;

&lt;p&gt;Sometimes during CI/CD in such projects, you need to take simple actions with versions: compare and parse them. E.g. you may want to check that a PR to the &lt;code&gt;main&lt;/code&gt; branch increases the version (&lt;code&gt;$new_version&lt;/code&gt; &amp;gt; &lt;code&gt;$old_version&lt;/code&gt;) or you may want to create or update a &lt;code&gt;v$major.$minor&lt;/code&gt; / &lt;code&gt;v$major&lt;/code&gt; tag whenever you release your project.&lt;/p&gt;

&lt;p&gt;This is what &lt;a href="https://github.com/madhead/semver-utils"&gt;&lt;code&gt;madhead/semver-utils&lt;/code&gt;&lt;/a&gt; is for! Just drop it into your workflow and use it for various operations with versions:&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="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;madhead/semver-utils@latest&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;version&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# A version to work with&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.2.3+42.24&lt;/span&gt;

    &lt;span class="c1"&gt;# A version to compare against&lt;/span&gt;
    &lt;span class="na"&gt;compare-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.1.0&lt;/span&gt;

    &lt;span class="c1"&gt;# A range to check agains&lt;/span&gt;
    &lt;span class="na"&gt;satisfies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.x&lt;/span&gt;
&lt;span class="pi"&gt;-&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 "${{ steps.version.outputs.major }}"             # 1&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.minor }}"             # 2&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.patch }}"             # 3&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.build }}"             # 42.24&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.build-parts }}"       # 2&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.build-0 }}"           # 42&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.build-1 }}"           # 24&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.comparison-result }}" # &amp;lt;&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.satisfies }}"         # true&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-major }}"         # 2.0.0&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-premajor }}"      # 2.0.0-0&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-minor }}"         # 1.3.0&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-preminor }}"      # 1.3.0-0&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-patch }}"         # 1.2.4&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-prepatch }}"      # 1.2.4-0&lt;/span&gt;
    &lt;span class="s"&gt;echo "${{ steps.version.outputs.inc-prerelease }}"    # 1.2.4-0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah, it's basically a wapper around &lt;a href="https://www.npmjs.com/package/semver"&gt;semver package&lt;/a&gt;, so the outputs may look familiar to you. But if you need more in your workflows — feel free to &lt;a href="https://github.com/madhead/semver-utils/issues/new"&gt;open an issue&lt;/a&gt; with a feature you’re missing.&lt;/p&gt;

&lt;p&gt;I want this action to become a one-stop shop for working with semantic versions in your GitHub Actions workflows one day!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>github</category>
      <category>actionshackathon</category>
    </item>
  </channel>
</rss>
