<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Denis Isidoro</title>
      <link>https://denisidoro.github.io</link>
      <description>My blog</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://denisidoro.github.io/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Mon, 13 May 2024 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Trying to land an offer at a big tech company? These YouTubers will help you</title>
          <pubDate>Mon, 13 May 2024 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/youtubers-big-tech-interview/</link>
          <guid>https://denisidoro.github.io/posts/youtubers-big-tech-interview/</guid>
          <description xml:base="https://denisidoro.github.io/posts/youtubers-big-tech-interview/">&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve recently landed an offer I was really looking forward (yay! 🎉)&lt;&#x2F;p&gt;
&lt;p&gt;The least I could do was to give some shoutouts to the YouTubers which made this possible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;system-design&quot;&gt;System design&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@jordanhasnolife5163&#x2F;featured&quot;&gt;Jordan&lt;&#x2F;a&gt; is the GOAT! Seriously.&lt;&#x2F;p&gt;
&lt;p&gt;He goes way beyond the &amp;quot;design Twitter&#x27;s feed&amp;quot;-kind of questions. For more senior roles, the topics he covers are a must.&lt;&#x2F;p&gt;
&lt;p&gt;He also has a playlist of videos for more junior engineers.&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;fUpYLwzGtW0&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;In this video, he quickly talks about how timeseries database are designed&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;If a picture is worth a thousand words, than &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@ByteByteGo&quot;&gt;ByByteGo&lt;&#x2F;a&gt;&#x27;s short animations are worth a million.&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;V3pzxngeLqw&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;No need to read an article about bloom filters. This 5min-video is all you need to know&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;If you speedrun his videos at 2x speed, you can watch all his videos in a day or two.&lt;&#x2F;p&gt;
&lt;p&gt;In some big tech companies, the system design round isn&#x27;t about drawing boxes -- it&#x27;s about writing a doc. With that in mind, &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@irtizahafiz&quot;&gt;Irtiza&lt;&#x2F;a&gt;&#x27;s videos helped me shift my mental model for these interviews. In each video, he ends up with a Markdown file with all his discussions, basically.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;coding&quot;&gt;Coding&lt;&#x2F;h2&gt;
&lt;p&gt;No mystery here: &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@NeetCode&quot;&gt;Neetcode&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;mBNrRy2_hVs&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;The dreaded dynamic programming questions&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;He solves Leetcode problems, which is all you&#x27;ll need.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;behavioral&quot;&gt;Behavioral&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve only ever watched one video from &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@JeffSu&quot;&gt;Jeff Su&lt;&#x2F;a&gt;, but it made the whole difference for me:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;gZ2354BH0a0&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;Ever heard about the CARL method?&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;In this video, he covers an alternative to the famous STAR method. He describes why the CARL method is better, and after testing it out, I can confirm he was right.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@jeffhsipepi&#x2F;videos&quot;&gt;Jeff Sipe&lt;&#x2F;a&gt;&#x27;s videos also were key for me to understand the importance of the subjective&#x2F;subconcious parts of this round.&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;TWFs3dxfiOc&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;Don&#x27;t say something was &quot;difficult&quot; -- say &quot;challenging&quot;. This is the kind of tips Jeff gives&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Hope this post was helpful!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>A memory-efficient data structure for geolocation history (rev2)</title>
          <pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/data-structure-geolocation-history-rev2/</link>
          <guid>https://denisidoro.github.io/posts/data-structure-geolocation-history-rev2/</guid>
          <description xml:base="https://denisidoro.github.io/posts/data-structure-geolocation-history-rev2/">&lt;p&gt;In this blog post I&#x27;ll cover how I built a memory-efficient data structure for storing location history data.&lt;&#x2F;p&gt;
&lt;p&gt;Some Rust details will also be explained.&lt;&#x2F;p&gt;
&lt;p&gt;This article is a rewrite of my &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;data-structure-geolocation-history&#x2F;&quot;&gt;previous one&lt;&#x2F;a&gt;, after taking into account &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;yil0i8&#x2F;blog_post_a_memoryefficient_data_structure_for&quot;&gt;feedback from Reddit&lt;&#x2F;a&gt; and further researching the topic.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;&#x2F;h2&gt;
&lt;p&gt;My DSLR camera has no GPS built-in. Evidently, photos from it don&#x27;t include geolocation-based EXIF tags.&lt;&#x2F;p&gt;
&lt;p&gt;If only my location history were stored somewhere, so that I could update photos with the correct data...&lt;&#x2F;p&gt;
&lt;p&gt;Unsurprisingly, Google knows where I am, where I&#x27;ve been &lt;del&gt;and where I&#x27;m going to be&lt;&#x2F;del&gt;. They even allow me to export this data.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that the exported JSON weighs ~1GB. My computer has 16GB of RAM so it can all fit in memory, but we certainly can store it more efficiently.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;json-parsing&quot;&gt;JSON parsing&lt;&#x2F;h2&gt;
&lt;p&gt;For reference, here&#x27;s what the JSON looks like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;locations&amp;quot;: [{
    &amp;quot;latitudeE7&amp;quot;: 435631892,
    &amp;quot;longitudeE7&amp;quot;: 26848689,
    &amp;quot;accuracy&amp;quot;: 56,
    &amp;quot;activity&amp;quot;: [{
      &amp;quot;activity&amp;quot;: [{
        &amp;quot;type&amp;quot;: &amp;quot;STILL&amp;quot;,
        &amp;quot;confidence&amp;quot;: 100
      }],
      &amp;quot;timestamp&amp;quot;: &amp;quot;2014-01-09T00:48:24.424Z&amp;quot;
    }],
    &amp;quot;source&amp;quot;: &amp;quot;WIFI&amp;quot;,
    &amp;quot;deviceTag&amp;quot;: 1348159918,
    &amp;quot;timestamp&amp;quot;: &amp;quot;2014-01-09T00:48:24.751Z&amp;quot;
  }, {
    &amp;quot;latitudeE7&amp;quot;: 435631881,
    &amp;#x2F;&amp;#x2F; ...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Deserializing a JSON file into a struct results in something that is certainly lighter than the JSON string.&lt;&#x2F;p&gt;
&lt;p&gt;The reason for this is that a string with the value &lt;code&gt;&amp;quot;2022-10-22&amp;quot;&lt;&#x2F;code&gt; needs more bytes than, say, its equivalent &lt;code&gt;Date&lt;&#x2F;code&gt;  object.&lt;&#x2F;p&gt;
&lt;p&gt;The latter can simply throw away the hyphens, for example. Additionally, the number 10 can be represented with 4 bits instead of using two chars, which occupy at least 8 bits each.&lt;&#x2F;p&gt;
&lt;p&gt;So moving away from a textual representation is the first step in our journey.&lt;&#x2F;p&gt;
&lt;p&gt;The simplest way to deserialize this data is to put its contents into a &lt;code&gt;String&lt;&#x2F;code&gt; and then pass it to your Json deserializer or choice. &lt;&#x2F;p&gt;
&lt;p&gt;The problem of this approach is that the &lt;code&gt;String&lt;&#x2F;code&gt; will occupy ~1GB of RAM, even if for just a couple of seconds. &lt;&#x2F;p&gt;
&lt;p&gt;Ideally, we should have a stream of data and keep deserializing small chunks at a time. &lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m pretty sure there are some libraries out there capable of that but I decided to simply iterate over each line of the file, extract relevant data and ignore the lines I wasn&#x27;t interested in.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;do-we-really-need-to-store-everything&quot;&gt;Do we really need to store everything?&lt;&#x2F;h2&gt;
&lt;p&gt;The most straightforward structure for our purposes is a sequence of &lt;code&gt;DataPoint&lt;&#x2F;code&gt;s, where &lt;code&gt;DataPoint&lt;&#x2F;code&gt; has &lt;code&gt;datetime&lt;&#x2F;code&gt;, &lt;code&gt;latitude&lt;&#x2F;code&gt; and &lt;code&gt;longitude&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;[
   (DateTime(2022,10,22,10,45,32), 27.5226335, 43.552225),  
   (DateTime(2022,10,22,10,46,21), 27.5226382, 43.552237),  
   &amp;#x2F;&amp;#x2F; ...
]  
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But isn&#x27;t there some redundancy? The data is clearly not random.&lt;&#x2F;p&gt;
&lt;p&gt;There are some assumptions we can make to help us design a better data structure:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Data points are sorted by time&lt;&#x2F;li&gt;
&lt;li&gt;Neighbor data points are very similar
&lt;ul&gt;
&lt;li&gt;unless I&#x27;m on a plane, my speed is at most 100 km&#x2F;h, so in a minute I&#x27;ll travel less 2 km&lt;&#x2F;li&gt;
&lt;li&gt;on foot I&#x27;ll travel just a few meters &lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Most of the days I stay inside a circle of 3km of radius&lt;&#x2F;li&gt;
&lt;li&gt;One data point per minute is more than enough&lt;&#x2F;li&gt;
&lt;li&gt;Errors in position up to 15m is something I&#x27;m OK with&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;estimating-size-of-straightforward-solution&quot;&gt;Estimating size of straightforward solution&lt;&#x2F;h2&gt;
&lt;p&gt;Throughout this post, let&#x27;s consider 2.5 years worth of data, including 345k data points. &lt;&#x2F;p&gt;
&lt;p&gt;Each data point consumes 64 bits for the timestamp and we can store each coordinate with a &lt;code&gt;f64&lt;&#x2F;code&gt;. &lt;&#x2F;p&gt;
&lt;p&gt;In total we need ~11.8 MB (if you did the math and think this off by ~50% please see &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;data-structure-geolocation-history-rev2&#x2F;#oversized-vecs&quot;&gt;this section&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-many-decimal-places-do-we-need-to-represent-a-coordinate&quot;&gt;How many decimal places do we need to represent a coordinate?&lt;&#x2F;h2&gt;
&lt;p&gt;First let&#x27;s try to understand how much each decimal place contributes to precision.&lt;&#x2F;p&gt;
&lt;p&gt;A quick search on Google gives us &lt;a href=&quot;https:&#x2F;&#x2F;gis.stackexchange.com&#x2F;questions&#x2F;8650&#x2F;measuring-accuracy-of-latitude-and-longitude&quot;&gt;the following&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;- The first decimal place is worth up to 11.1 km
   - it can distinguish the position of one large city from a neighboring large city
- The second decimal place is worth up to 1.1 km
   - it can separate one village from the next
- The third decimal place is worth up to 110 m
   - it can identify a large agricultural field or institutional campus
- The fourth decimal place is worth up to 11 m
   - it can identify a parcel of land
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we want to have errors less than 15 m, we need to be able to correctly represent the latitude and longitude up to the 4th decimal place.&lt;&#x2F;p&gt;
&lt;p&gt;If both coordinates are off by 0.0001° then the error will be close to &lt;code&gt;sqrt(11^2+11^2) ~ 15 m&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-many-bits-do-we-need-to-represent-time&quot;&gt;How many bits do we need to represent time?&lt;&#x2F;h2&gt;
&lt;p&gt;For our purposes, &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;data-structure-geolocation-history&#x2F;#how-many-bits-do-we-need-to-represent-time&quot;&gt;24 bits would be enough&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, we don&#x27;t need to store timestamps. &lt;&#x2F;p&gt;
&lt;p&gt;Instead of having at most one data point per minute, we can have exactly one data point per minute.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s cheaper to just duplicate data for missing timestamps. The duplication of data is surely unfortunate, but having to spend 24 bits for every minute of data is worse.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;The plan&lt;&#x2F;h2&gt;
&lt;p&gt;Suppose our data looks like this:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Timestamp&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Latitude&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Longitude&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;2022-10-15 16h13&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;26.1234&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;43.5678&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;2022-10-15 16h14&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;26.1235&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;43.5677&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;2022-10-15 16h15&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;26.1236&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;43.5678&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;We could delta-encode it so that all points (except the first one) are just the difference from the previous value:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Timestamp&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Latitude&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Longitude&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;+1 min&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;+0.0001&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;-0.0001&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;+1 min&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;+0.0001&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;+0.0001&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The data clearly becomes easier to reason about and is arguably &amp;quot;smaller&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;But when using an &lt;code&gt;f32&lt;&#x2F;code&gt; to represent a number, it doesn&#x27;t matter if it&#x27;s &lt;code&gt;26.1234&lt;&#x2F;code&gt; or &lt;code&gt;0.0001&lt;&#x2F;code&gt;: they will both occupy 32 bits.  So we need a way to make small numbers occupy less memory.&lt;&#x2F;p&gt;
&lt;p&gt;Finally it would be nice to somehow merge the latitude and longitude columns, so that we can keep track of a single value instead of having to store 2 deltas for each minute.&lt;&#x2F;p&gt;
&lt;p&gt;To recap, the plan is to:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;merge latitude and longitude into a single value&lt;&#x2F;li&gt;
&lt;li&gt;delta-encode the data&lt;&#x2F;li&gt;
&lt;li&gt;store small numbers efficiently&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;representing-2d-data-with-a-single-number&quot;&gt;Representing 2D data with a single number&lt;&#x2F;h2&gt;
&lt;p&gt;The objective here is to come up with a function &lt;code&gt;f(pos: (f32, f32)) -&amp;gt; u32&lt;&#x2F;code&gt; and an &amp;quot;inverse&amp;quot; function &lt;code&gt;g(n: u32) -&amp;gt; (f32, f32)&lt;&#x2F;code&gt; such that &lt;code&gt;error(pos, g(f(pos))) &amp;lt; ε&lt;&#x2F;code&gt;, for any valid &lt;code&gt;pos&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In our case, &lt;code&gt;ε&lt;&#x2F;code&gt; = 15 m.&lt;&#x2F;p&gt;
&lt;p&gt;The most straightforward way to do it is like so:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;geo-snakelike.webp&quot; width=&quot;70%&quot; &#x2F;&gt;
    &lt;figcaption&gt;In this approach, we travel a full row, move one step up&#x2F;down, travel another full row and so on. Source: 3b1b&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;p&gt;The problem with this algorithm is that the distance between neighbour points isn&#x27;t necessarily small.&lt;&#x2F;p&gt;
&lt;p&gt;Suppose we move one step to the right. The distance traveled will be one unit. Great!&lt;&#x2F;p&gt;
&lt;p&gt;Now suppose we move one step up. The distance traveled will be &lt;code&gt;grid.cols&lt;&#x2F;code&gt;, which is certainly more than we&#x27;d like. This means that moving &lt;code&gt;+0.0001&lt;&#x2F;code&gt; in latitude could result in a distance of thousands of units, yet we want deltas to be as small as possible.&lt;&#x2F;p&gt;
&lt;p&gt;A more elegant solution would be to use &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Hilbert_curve&quot;&gt;Hilbert curves&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;geo-hilbert.webp&quot; width=&quot;70%&quot; &#x2F;&gt;
    &lt;figcaption&gt;The Hilbert curve is a continuous fractal space-filling curve. Source: 3b1b&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;&#x2F;figure&gt; 
&lt;p&gt;Now, if we move one unit, the traveled distance will generally be small, regardless of the movement being horizontal or vertical.&lt;&#x2F;p&gt;
&lt;p&gt;For resolutions big enough, this will remain true for most points, except the ones at &amp;quot;boundaries&amp;quot; (e.g. going from a red cell to a green one).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=3s7h2MHQtxc&quot;&gt;This video from 3b1b&lt;&#x2F;a&gt; explains the concept of space-filling in detail. It&#x27;s worth checking it out!&lt;&#x2F;p&gt;
&lt;p&gt;That said, I decided to go with a simpler strategy, which is close to the concept of &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Binary_space_partitioning&quot;&gt;binary space partitioning&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the idea:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;is our point in the left hemisphere or right?
&lt;ul&gt;
&lt;li&gt;if left, set first bit to 0; otherwise, 1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;is our point in the south hemisphere or north? 
&lt;ul&gt;
&lt;li&gt;if south, set second bit to 0; otherwise, 1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;is our point in the left half of the region in &lt;code&gt;1&lt;&#x2F;code&gt; or right? 
&lt;ul&gt;
&lt;li&gt;if left, set third bit to 0; otherwise, 1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;is our point in the south half of the region in &lt;code&gt;2&lt;&#x2F;code&gt; or north? 
&lt;ul&gt;
&lt;li&gt;if south, set fourth bit to 0; otherwise, 1&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;...&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Here&#x27;s an image depicting the process of representing the yellow circle:&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;geo-worldmap.webp&quot;&gt;
    &lt;figcaption&gt;Whenever we decide on a region in the horizontal space, the selected region becomes greener; likewise, pinker for vertical regions.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;p&gt;One nice property of this algorithm is that, as we take more decisions (i.e. as resolution gets more fine-grained), we&#x27;re simply adding more bits.&lt;&#x2F;p&gt;
&lt;p&gt;This means we can throw away less significant bits as we see fit.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;storing-small-numbers-efficiently&quot;&gt;Storing small numbers efficiently&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;pdf&#x2F;1209.2137.pdf&quot;&gt;This paper&lt;&#x2F;a&gt; describes a lot of different methods.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to use a modified version of the Simple8b algorithm because of, as you&#x27;ve probably guessed, its simplicity.&lt;&#x2F;p&gt;
&lt;p&gt;The idea is to divide a &lt;code&gt;u64&lt;&#x2F;code&gt; into 2 parts: a &lt;code&gt;selector&lt;&#x2F;code&gt; of 4 bits, and a &lt;code&gt;data&lt;&#x2F;code&gt; region of 60 bits.&lt;&#x2F;p&gt;
&lt;p&gt;The selector determines how many bits each number inside &lt;code&gt;data&lt;&#x2F;code&gt; will take.&lt;&#x2F;p&gt;
&lt;p&gt;For example, for &lt;code&gt;selector&lt;&#x2F;code&gt;=15, we store 60 numbers of 1 bit each; for &lt;code&gt;selector&lt;&#x2F;code&gt;=14 (= 1110 in binary), we store 30 numbers of 2 bits each:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;      4 bits                             60 bits
=== selector === | ====================== data ========================
       1110        01  10  00  10  11              ...               01
  (= 14 in dec)    |    |   └ third number                            | 
                   |    └ second number                               |
                   first number                             30th number
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I made 2 modifications to this algorithm that allowed me to compress data even further.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;modification-1-representing-a-lot-of-zeros&quot;&gt;Modification 1: representing (a lot of) zeros&lt;&#x2F;h3&gt;
&lt;p&gt;As mentioned above, we&#x27;re adding one data point to our data structure for every minute. But what if there&#x27;s simply no data for an entire month? &lt;&#x2F;p&gt;
&lt;p&gt;The good news is that the deltas will be a lot of zeros, if we decide to simply copy the latest data point over and over.&lt;&#x2F;p&gt;
&lt;p&gt;The bad news is that we&#x27;ll have a lot of unnecessary zeros.&lt;&#x2F;p&gt;
&lt;p&gt;So I changed the algorithm so that when &lt;code&gt;selector&lt;&#x2F;code&gt;=1, &lt;code&gt;data&lt;&#x2F;code&gt; is the count of zeros we&#x27;re trying to represent. So let&#x27;s say that we have 1 billion consecutive zeros. Instead of needing 1 billion numbers to represent this data, we can simply create a Simple8b-compressed &lt;code&gt;u64&lt;&#x2F;code&gt; with &lt;code&gt;selector&lt;&#x2F;code&gt;=1 and &lt;code&gt;data&lt;&#x2F;code&gt;=1 billion.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;modification-2-not-tracking-the-count-of-elements&quot;&gt;Modification 2: not tracking the count of elements&lt;&#x2F;h3&gt;
&lt;p&gt;Unfortunately, the Simple8b-compressed &lt;code&gt;u64&lt;&#x2F;code&gt; doesn&#x27;t contain the number of elements compressed.&lt;&#x2F;p&gt;
&lt;p&gt;For example, this test fails:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;let input0 = [2, 76, 3, 5, 7, 2, 0, 0];
let input1 = [2, 76, 3, 5, 7, 2];
let inverse0 = simple8b::decompress(simple8b::compress(input0));
let inverse1 = simple8b::decompress(simple8b::compress(input1));

&amp;#x2F;&amp;#x2F; passes
assert_eq!(input0, inverse0);

&amp;#x2F;&amp;#x2F; fails: inverse1 = input0, with the extra zeros
assert_eq!(input1, inverse1);
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is also unfortunate because keeping track of this count (which requires at least 8 bits), defeats the purpose of compressing: we may save a few bits here and there but, with this cost of 8 bits per &lt;code&gt;u64&lt;&#x2F;code&gt;, perhaps the final size will be bigger than the original.&lt;&#x2F;p&gt;
&lt;p&gt;In Simple8b, a zero means a lack of data, so there&#x27;s no way to distinguish between trailing zeros or absence of data.&lt;&#x2F;p&gt;
&lt;p&gt;My change was really simple: whenever we try to store a number &lt;code&gt;n&lt;&#x2F;code&gt;, we actually store &lt;code&gt;n+1&lt;&#x2F;code&gt;. Conversely, when retrieving a number &lt;code&gt;m&lt;&#x2F;code&gt;, we actually return &lt;code&gt;m-1 = n&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This way any zeros we find during decompression indicate that the actual data has already ended.&lt;&#x2F;p&gt;
&lt;p&gt;The downside of this approach is that &lt;code&gt;selector&lt;&#x2F;code&gt;=15 (60 numbers of 1 bit each) becomes useless: normally this could only happen if we had a bunch of 0s and 1s. But now a 1 becomes a 2, so this can only be possible if we have a lot of 0s only. In this case &lt;code&gt;selector&lt;&#x2F;code&gt;=1 will be used.&lt;&#x2F;p&gt;
&lt;p&gt;But we would probably not have this scenario of multiple 0s and 1s to begin with: unless I kept moving ~1 cm per minute for a long period of time. 😅&lt;&#x2F;p&gt;
&lt;h2 id=&quot;representing-deltas&quot;&gt;Representing deltas&lt;&#x2F;h2&gt;
&lt;p&gt;Alright, so we can now represent both coordinates with a single number and store a bunch of these numbers efficiently.&lt;&#x2F;p&gt;
&lt;p&gt;A position at time &lt;code&gt;i&lt;&#x2F;code&gt; can then be represented as &lt;code&gt;position_i = position_0 + sum(delta_i, i = 0 to i)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But &lt;code&gt;delta_i&lt;&#x2F;code&gt; is a &lt;code&gt;u64&lt;&#x2F;code&gt;, which is an unsigned integer. How can we represent negative numbers?&lt;&#x2F;p&gt;
&lt;p&gt;This is easy. We can simply bit shift to the left and use the least-significant bit to represent the sign:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;fn to_unsigned_delta(signed_delta: i128) -&amp;gt; u64 {
   if signed_delta &amp;gt;= 0 {
      (signed_delta &amp;lt;&amp;lt; 1) as u64
   } else {
      ((signed_delta &amp;lt;&amp;lt; 1) + 1) as u64
   }
}

fn delta(unsigned_delta: u64) -&amp;gt; (u64, bool) {
   let is_positive = unsigned_delta % 2 == 0;
   let signed_delta = unsigned_delta &amp;gt;&amp;gt; 1;
   (signed_delta, is_positive)
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;getting-our-hands-dirty&quot;&gt;Getting our hands dirty&lt;&#x2F;h2&gt;
&lt;p&gt;Finally, at its core, our data structure will do the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;fn add(&amp;amp;mut self, time: DateTime, pos: (f32, f32)) -&amp;gt; Result&amp;lt;()&amp;gt; {  
   self.add_missing_data(time, pos);
   let delta = calculate_delta(self.last_pos, pos);
   self.fail_if_error_is_too_big(delta, pos)?; &amp;#x2F;&amp;#x2F; optional
   self.push_point(delta);
   self.last_pos = pos;
   Ok(())
}  
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Just to be safe, before pushing the latest delta, I&#x27;m calculating if the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Haversine_formula&quot;&gt;haversine distance&lt;&#x2F;a&gt; is below &lt;code&gt;ε&lt;&#x2F;code&gt; = 15 meters. This is more precise because the the error varies with the distance to the Equator. &lt;&#x2F;p&gt;
&lt;h2 id=&quot;benchmarking&quot;&gt;Benchmarking&lt;&#x2F;h2&gt;
&lt;p&gt;After finishing everything, we can measure how many megabytes our data structure needs.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s insert data for the last couple of years into it and measure memory consumption:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;input points: 345 k
packed u64s: 56 k
total size: 512 KB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;oversized-vecs&quot;&gt;Oversized Vecs&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s something odd with the numbers above: the &lt;code&gt;Vec&lt;&#x2F;code&gt; should be smaller. &lt;&#x2F;p&gt;
&lt;p&gt;If we have 56k elements of 64 bits, the total sequence should have ~440KB, not 512KB.&lt;&#x2F;p&gt;
&lt;p&gt;After some experiments, I realized that the &lt;code&gt;Vec&lt;&#x2F;code&gt;s were bigger than expected by 5% to 100%. In average, they were ~55% bigger.&lt;&#x2F;p&gt;
&lt;p&gt;I then remembered that such data structures usually double their inner buffers when there&#x27;s no room for a new element.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, Rust offers a &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;vec&#x2F;struct.Vec.html#method.shrink_to_fit&quot;&gt;Vec::shrink_to_fit()&lt;&#x2F;a&gt; operation, which we can call after finishing mutating our database.&lt;&#x2F;p&gt;
&lt;p&gt;After applying this change, the &lt;code&gt;Vec&lt;&#x2F;code&gt; occupies the expected size.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;final-results&quot;&gt;Final results&lt;&#x2F;h2&gt;
&lt;p&gt;After fiddling with some thresholds I noticed that, to achieve a 5m precision instead of 15m, the memory overhead is negligible:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;markdown&quot; class=&quot;language-markdown &quot;&gt;&lt;code class=&quot;language-markdown&quot; data-lang=&quot;markdown&quot;&gt;input points: 345 k
packed u64s: 62 k
total size: 485 KB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So I decided to set the max error to 5m instead.&lt;&#x2F;p&gt;
&lt;p&gt;Overall we reduced memory footprint by 24x, compared to the most straightforward solution (using a stream for JSON deserialization).&lt;&#x2F;p&gt;
&lt;p&gt;Had we used a &lt;code&gt;String&lt;&#x2F;code&gt; for storing the whole JSON first, the maximum RAM used would be 1900x bigger!&lt;&#x2F;p&gt;
&lt;p&gt;The final code can be found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;blog&#x2F;tree&#x2F;master&#x2F;gists&#x2F;geolocation-history&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;I didn&#x27;t need to invest so much bandwidth in this optimization: my PC could use 1GB of RAM for this data anyway.&lt;&#x2F;p&gt;
&lt;p&gt;But I learned a lot as part of the process and I ended up with results very efficient memory-wise.&lt;&#x2F;p&gt;
&lt;p&gt;That said, as &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;yil0i8&#x2F;blog_post_a_memoryefficient_data_structure_for&quot;&gt;mentioned on Reddit&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;blog.logrocket.com&#x2F;rust-compression-libraries&#x2F;&quot;&gt;general-purpose compression algorithms&lt;&#x2F;a&gt; achieve similar results and simplify our code a lot. Querying is relatively more expensive, though.&lt;&#x2F;p&gt;
&lt;p&gt;But, depending on your needs, perhaps you can just &lt;code&gt;gzip&lt;&#x2F;code&gt; everything and you&#x27;re good to go!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>A memory-efficient data structure for geolocation history</title>
          <pubDate>Mon, 31 Oct 2022 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/data-structure-geolocation-history/</link>
          <guid>https://denisidoro.github.io/posts/data-structure-geolocation-history/</guid>
          <description xml:base="https://denisidoro.github.io/posts/data-structure-geolocation-history/">&lt;p&gt;&lt;em&gt;Note: please check &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;data-structure-geolocation-history-rev2&#x2F;&quot;&gt;this other post&lt;&#x2F;a&gt; instead, which is a more recent revision on this topic.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In this blog post I&#x27;ll cover how I built a memory-efficient data structure for storing location history data.&lt;&#x2F;p&gt;
&lt;p&gt;Some Rust details will also be explained.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;&#x2F;h3&gt;
&lt;p&gt;My DSLR camera has no GPS built-in. Evidently, photos from it don&#x27;t include geolocation-based EXIF tags.&lt;&#x2F;p&gt;
&lt;p&gt;If only my location history were stored somewhere, so that I could update photos with the correct data...&lt;&#x2F;p&gt;
&lt;p&gt;Unsurprisingly, Google knows where I am, where I&#x27;ve been &lt;del&gt;and where I&#x27;m going to be&lt;&#x2F;del&gt;. They even allow me to export this data.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that the exported JSON weighs ~1GB. My computer has 16GB of RAM so it can all fit in memory, but we certainly can store it more efficiently.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;json-parsing&quot;&gt;JSON parsing&lt;&#x2F;h3&gt;
&lt;p&gt;For reference, here&#x27;s what the JSON looks like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;locations&amp;quot;: [{
    &amp;quot;latitudeE7&amp;quot;: 435631892,
    &amp;quot;longitudeE7&amp;quot;: 26848689,
    &amp;quot;accuracy&amp;quot;: 56,
    &amp;quot;activity&amp;quot;: [{
      &amp;quot;activity&amp;quot;: [{
        &amp;quot;type&amp;quot;: &amp;quot;STILL&amp;quot;,
        &amp;quot;confidence&amp;quot;: 100
      }],
      &amp;quot;timestamp&amp;quot;: &amp;quot;2014-01-09T00:48:24.424Z&amp;quot;
    }],
    &amp;quot;source&amp;quot;: &amp;quot;WIFI&amp;quot;,
    &amp;quot;deviceTag&amp;quot;: 1348159918,
    &amp;quot;timestamp&amp;quot;: &amp;quot;2014-01-09T00:48:24.751Z&amp;quot;
  }, {
    &amp;quot;latitudeE7&amp;quot;: 435631881,
    &amp;#x2F;&amp;#x2F; ...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Deserializing a JSON file into a struct results in something that is certainly lighter than the JSON string.&lt;&#x2F;p&gt;
&lt;p&gt;The reason for this is that a string with the value &lt;code&gt;&amp;quot;2022-10-22&amp;quot;&lt;&#x2F;code&gt; needs more bytes than, say, its equivalent &lt;code&gt;Date&lt;&#x2F;code&gt;  object.&lt;&#x2F;p&gt;
&lt;p&gt;The latter can simply throw away the hyphens, for example. Additionally, the number 10 can be represented with 4 bits instead of using two chars, which occupy at least 8 bits each.&lt;&#x2F;p&gt;
&lt;p&gt;So moving away from a textual representation is the first step in our journey.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of using a JSON deserializer per se, I decided to iterate over each line of the file and ignore the lines I wasn&#x27;t interested in.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;do-we-really-need-to-store-everything&quot;&gt;Do we really need to store everything?&lt;&#x2F;h3&gt;
&lt;p&gt;A naive structure for our purposes is a sequence of &lt;code&gt;DataPoint&lt;&#x2F;code&gt;s, where &lt;code&gt;DataPoint&lt;&#x2F;code&gt; has &lt;code&gt;datetime&lt;&#x2F;code&gt;, &lt;code&gt;latitude&lt;&#x2F;code&gt; and &lt;code&gt;longitude&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;[
 (DateTime(2022,10,22,10,45,32), 27.5226335, 43.552225),  
 (DateTime(2022,10,22,10,46,21), 27.5226382, 43.552237),  
 &amp;#x2F;&amp;#x2F; ...
]  
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But isn&#x27;t there some redundancy? The data is clearly not random.&lt;&#x2F;p&gt;
&lt;p&gt;There are some assumptions we can make to help us design a better data structure:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Data points are sorted by time&lt;&#x2F;li&gt;
&lt;li&gt;Neighbor data points are very similar
&lt;ul&gt;
&lt;li&gt;unless I&#x27;m on a plane, my speed is at most 100 km&#x2F;h, so in a minute I&#x27;ll travel less 2 km&lt;&#x2F;li&gt;
&lt;li&gt;on foot I&#x27;ll travel just a few meters &lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Most of the days I stay inside a circle of 3km of radius&lt;&#x2F;li&gt;
&lt;li&gt;One data point per minute is more than enough&lt;&#x2F;li&gt;
&lt;li&gt;Errors in position up to 15m is something I&#x27;m OK with&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;how-many-bits-do-we-need-to-represent-a-position&quot;&gt;How many bits do we need to represent a position?&lt;&#x2F;h3&gt;
&lt;p&gt;First let&#x27;s try to understand how much each decimal place contributes to precision.&lt;&#x2F;p&gt;
&lt;p&gt;A quick search on Google gives us &lt;a href=&quot;https:&#x2F;&#x2F;gis.stackexchange.com&#x2F;questions&#x2F;8650&#x2F;measuring-accuracy-of-latitude-and-longitude&quot;&gt;the following&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;- The first decimal place  is worth up to 11.1 km: it can distinguish the position of one large city from a neighboring large city.
- The second decimal place is worth up to 1.1 km: it can separate one village from the next.
- The third decimal place is worth up to 110 m: it can identify a large agricultural field or institutional campus.
- The fourth decimal place is worth up to 11 m: it can identify a parcel of land. It is comparable to the typical accuracy of an uncorrected GPS unit with no interference.
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we want to have errors less than 15 m, then we need to be able to correctly represent the latitude and longitude up to the 4th decimal place.&lt;&#x2F;p&gt;
&lt;p&gt;If both coordinates are off by 0.0001° then the error will be close to &lt;code&gt;sqrt(10^2+10^2) = 15 m&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Given that neighbor data points are close to each other, we can represent deltas in position instead of absolute positions.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s first try using a single byte to represent a delta in latitude.&lt;&#x2F;p&gt;
&lt;p&gt;Given that we must represent the 4th decimal place precisely, we can have a range that goes from 0 to &lt;code&gt;2^7-1&lt;&#x2F;code&gt; = 127, where 0 translates to 0°; 1 to 0.0001°; and, as a consequence, 127 to 0.0127°. The remaining bit can be used to indicate if the difference is positive or negative.&lt;&#x2F;p&gt;
&lt;p&gt;0.0127° of difference in both coordinates results in a distance of ~2.1km. This is good because, from assumption number 3, we can conclude that, for most days, most data points can be represented with deltas of only one byte for each coordinate.&lt;&#x2F;p&gt;
&lt;p&gt;This conversion simply consists of a rule of three, so I&#x27;ll skip further details.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-many-bits-do-we-need-to-represent-time&quot;&gt;How many bits do we need to represent time?&lt;&#x2F;h3&gt;
&lt;p&gt;From assumption number 4, we can discard seconds from timestamps.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s say that we&#x27;re looking at an interval of 20 years. 20 years is roughly equal to 10.5 million minutes.&lt;&#x2F;p&gt;
&lt;p&gt;To represent this number we need &lt;code&gt;ceiling(log2(10.5 million))&lt;&#x2F;code&gt; = 24 bits.&lt;&#x2F;p&gt;
&lt;p&gt;Rust doesn&#x27;t have a &lt;code&gt;u24&lt;&#x2F;code&gt; type. Even though we could implement a &lt;code&gt;u24&lt;&#x2F;code&gt; type with a tuple of &lt;code&gt;(u8, u16)&lt;&#x2F;code&gt;, in the end our  &lt;code&gt;u24&lt;&#x2F;code&gt; would end up using 4 bytes instead of 3:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;type struct u24 {
  msb: u8,
  lsb: u8
}

fn main() {
   let x = u24 { msb: 1, lsb: 1 };
   println!(&amp;quot;{} bytes&amp;quot;, x.deep_size_of()); &amp;#x2F;&amp;#x2F; 4 bytes
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This happens because of &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;reference&#x2F;type-layout.html&quot;&gt;memory alignment&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In Rust, we can circumvent this &amp;quot;limitation&amp;quot; by using the &lt;code&gt;repr&lt;&#x2F;code&gt; directive:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;#[repr(packed(1))]
type struct u24 {
  msb: u8,
  lsb: u8
}

fn main() {
   let x = u24 { msb: 1, lsb: 1 };
   println!(&amp;quot;{} bytes&amp;quot;, x.deep_size_of()); &amp;#x2F;&amp;#x2F; 3 bytes
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But even &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-lang&#x2F;rust-clippy&quot;&gt;clippy&lt;&#x2F;a&gt; complains about this, with the &lt;code&gt;unaligned_references&lt;&#x2F;code&gt; warning.&lt;&#x2F;p&gt;
&lt;p&gt;So let&#x27;s stick with a &lt;code&gt;u32&lt;&#x2F;code&gt; for timestamps instead. Hey, at least we can now represent 8000+ years worth of data.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;getting-our-hands-dirty&quot;&gt;Getting our hands dirty&lt;&#x2F;h3&gt;
&lt;p&gt;At its core, our database will do the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;fn add(&amp;amp;mut self, time: DateTime, lat: f32, lng: f32) {  
   let point = self.low_precision_point(lat, lng);  
   if error(point, lat, lng) &amp;gt; threshold {  
      self.store_high_precision_point(time, lat, lng)  
   } else {  
      self.store_low_precision_point(point)  
   }  
}  
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Simply using the 0.0001°-based rule in the &lt;code&gt;error()&lt;&#x2F;code&gt; function isn&#x27;t enough because the error varies with your distance to the Equator. To be more precise, this function must calculate the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Haversine_formula&quot;&gt;haversine distance&lt;&#x2F;a&gt; instead.&lt;&#x2F;p&gt;
&lt;p&gt;This is more CPU-intensive but it&#x27;s a cost we need to pay at the time of writing only.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;defining-our-structs&quot;&gt;Defining our structs&lt;&#x2F;h3&gt;
&lt;p&gt;Our data structure for low-precision datapoints can be a sequence of 2 1-byte positions, as mentioned above. We also need to have a reference to a high-precision point so that the final position is reference + delta.&lt;&#x2F;p&gt;
&lt;p&gt;Our data structure for high-precision can be a sequence of minutes plus both coordinates.&lt;&#x2F;p&gt;
&lt;p&gt;In the end we&#x27;ll have something like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;type Minutes = u32
type LatLng = (f32, f32)
type LatLngDelta = (u8, u8)
  
struct Db {
   high_precision_points: Vec&amp;lt;Minutes, LatLng&amp;gt;,  
   low_precision_points: HashMap&amp;lt;Minutes, Vec&amp;lt;LatLngDelta&amp;gt;&amp;gt;  
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;ll skip the implementation details but you can check the full code &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;denisidoro&#x2F;c79282fa44aab10f5a33e838b8b1811f&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;benchmarking&quot;&gt;Benchmarking&lt;&#x2F;h3&gt;
&lt;p&gt;After finishing everything, we can measure how many megabytes our data structure needs.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s insert data for the last couple of years into it and measure memory consumption:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;input points: 345 k
high-precision data points: 42 k
low-precision data points: 280 k
high-precision structure size: 768 KB
low-precision structure size: 2583 KB
sum size: 3351 KB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In the end, less than 4 MB were occupied. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;oversized-vecs&quot;&gt;Oversized Vecs&lt;&#x2F;h3&gt;
&lt;p&gt;There&#x27;s something odd with the numbers above: the &lt;code&gt;Vec&lt;&#x2F;code&gt;s should be smaller. &lt;&#x2F;p&gt;
&lt;p&gt;If we have 42k elements of &lt;code&gt;32*3 bits&lt;&#x2F;code&gt; = 12 bytes each, the total sequence should have ~492KB, not 768KB.&lt;&#x2F;p&gt;
&lt;p&gt;After some experiments, I realized that the &lt;code&gt;Vec&lt;&#x2F;code&gt;s were bigger than expected by 5 to 100%. In average, they were ~55% bigger.&lt;&#x2F;p&gt;
&lt;p&gt;I then remembered that such data structures usually double their inner buffers when there&#x27;s no room for a new element.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, Rust offers a &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;vec&#x2F;struct.Vec.html#method.shrink_to_fit&quot;&gt;Vec::shrink_to_fit()&lt;&#x2F;a&gt; operation, which we can call after finishing mutating our database.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;benchmarking-1&quot;&gt;Benchmarking&lt;&#x2F;h3&gt;
&lt;p&gt;Finally, here are our results:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;input points: 345 k
high-precision data points: 42 k
low-precision data points: 280 k
high-precision structure size: 499 KB
low-precision structure size: 2339 KB
sum size: 2838 KB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s compare this to a naive &lt;code&gt;Vec&amp;lt;Timestamp, f64, f64&amp;gt;&lt;&#x2F;code&gt; implementation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;input_points * (size_of_timestamp + 64 + 64) * average vec overhead
354k * (64+64+64) * 1.5 bits
11.8 MB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Overall we saved around ~80% in memory!&lt;&#x2F;p&gt;
&lt;p&gt;After fiddling with some thresholds, I noticed that, to achieve a 5m precision instead of 15m, the memory overhead is negligible:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;input points: 345 k
high-precision data points: 46 k
low-precision data points: 273 k
high-precision structure size: 546 KB
low-precision structure size: 2326 KB
sum size: 2872 KB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So I decided to set the max error to 5m instead.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;future-improvements&quot;&gt;Future improvements&lt;&#x2F;h3&gt;
&lt;p&gt;A high-precision data point occupies 32 bits for a timestamp and 32 bits for each coordinate. &lt;&#x2F;p&gt;
&lt;p&gt;Timestamps could be represented with 24 bits, as mentioned above. 32 bits for each coordinate is unnecessary.&lt;&#x2F;p&gt;
&lt;p&gt;We could use a &lt;code&gt;u64&lt;&#x2F;code&gt; for everything instead: with some bit shifting, 24 bits could be reserved for the timestamp and each coordinate could use 20 bits.&lt;&#x2F;p&gt;
&lt;p&gt;This would decrease the size of these data points by &lt;code&gt;1-64&#x2F;(32*3)&lt;&#x2F;code&gt; = 33%.&lt;&#x2F;p&gt;
&lt;p&gt;In addition, I wonder if we could use &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Quadtree#:~:text=A%20quadtree%20is%20a%20tree,into%20four%20quadrants%20or%20regions.&quot;&gt;QuadTrees&lt;&#x2F;a&gt; somehow...&lt;&#x2F;p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;This journey was clearly over-engineered.&lt;&#x2F;p&gt;
&lt;p&gt;But I learned a lot as part of the process and I ended up with results very efficient memory-wise.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of asking &amp;quot;why?&amp;quot;, I asked &amp;quot;why not?&amp;quot; 😂  &lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Faster monorepo workflow with materialized views</title>
          <pubDate>Mon, 13 Dec 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/faster-monorepo-workflow-with-materialized-views/</link>
          <guid>https://denisidoro.github.io/posts/faster-monorepo-workflow-with-materialized-views/</guid>
          <description xml:base="https://denisidoro.github.io/posts/faster-monorepo-workflow-with-materialized-views/">&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;Monorepos have their pros and cons. &lt;&#x2F;p&gt;
&lt;p&gt;A plethora of in-depth articles have already been written on this subject so I won&#x27;t bother writing yet another one. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joelparkerhenderson&#x2F;monorepo-vs-polyrepo&quot;&gt;This one&lt;&#x2F;a&gt; summarizes the trade-offs very well.&lt;&#x2F;p&gt;
&lt;p&gt;In this post, I propose a solution for improving the dev experience for monorepos, by using microrepos as materialized views for subprojects and a bot as orchestrator.&lt;&#x2F;p&gt;
&lt;p&gt;A &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;git-monorepo&quot;&gt;proof-of-concept CLI&lt;&#x2F;a&gt; for managing repositories is also provided, which can be used as base for a real-world tool set. &lt;&#x2F;p&gt;
&lt;h2 id=&quot;assumptions&quot;&gt;Assumptions&lt;&#x2F;h2&gt;
&lt;p&gt;To keep this post concise, I&#x27;ll list some assumptions under which this solution was designed. &lt;&#x2F;p&gt;
&lt;p&gt;Depending on your workflow or the requirements of the code base you&#x27;re developing, alternative solutions should be considered. &lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;git must be used, as changing the SVN is too disruptive&lt;&#x2F;li&gt;
&lt;li&gt;the main drawback of monorepos are slow git operations in dev machines&lt;&#x2F;li&gt;
&lt;li&gt;if git were performant for large repos, monorepos would clearly be superior to microrepos, with few, if any, downsides &lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;proposed-solution&quot;&gt;Proposed solution&lt;&#x2F;h2&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;git-monorepo-architecture.png&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;In a nutshell, devs work with materialized views an the bot propagates changes&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;h3 id=&quot;repo-setup&quot;&gt;Repo setup&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;0.a. Create a monorepo &lt;code&gt;mono&lt;&#x2F;code&gt; as the source of truth of our codebase. It should include all subprojects. Let&#x27;s say it contains &lt;code&gt;proj1&lt;&#x2F;code&gt; and &lt;code&gt;proj2&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;0.b. Create microrepos for &lt;code&gt;proj1&lt;&#x2F;code&gt; and &lt;code&gt;proj2&lt;&#x2F;code&gt;. They&#x27;ll act as materialized views&lt;&#x2F;li&gt;
&lt;li&gt;0.c. Protect the &lt;code&gt;master&lt;&#x2F;code&gt; branch of all microrepos. Only the bot should be able to commit to &lt;code&gt;master&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;workflow&quot;&gt;Workflow&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;Instead of cloning &lt;code&gt;mono&lt;&#x2F;code&gt;, the dev should clone microrepos of interest. Let&#x27;s say only &lt;code&gt;proj2&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The dev creates a branch &lt;code&gt;newfeature&lt;&#x2F;code&gt; in &lt;code&gt;proj2&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;After making the necessary changes, the dev pushes this branch to &lt;code&gt;proj2&lt;&#x2F;code&gt;&#x27;s remote, not &lt;code&gt;mono&lt;&#x2F;code&gt;&#x27;s&lt;&#x2F;li&gt;
&lt;li&gt;A bot creates a PR in &lt;code&gt;mono&lt;&#x2F;code&gt;, reflecting the changes of &lt;code&gt;proj2&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;newfeature&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The dev hits the &amp;quot;merge pull request&amp;quot; button for the PR in &lt;code&gt;mono&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;For each commit to &lt;code&gt;mono&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;master&lt;&#x2F;code&gt;, the bot commits to the microrepos&#x27; &lt;code&gt;master&lt;&#x2F;code&gt; accordingly. In this case, &lt;code&gt;proj2&lt;&#x2F;code&gt;&#x27;s master will eventually include the changes from &lt;code&gt;newfeature&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;making-changes-to-multiple-repos-at-once&quot;&gt;Making changes to multiple repos at once&lt;&#x2F;h2&gt;
&lt;p&gt;The bot that creates the PR in &lt;code&gt;mono&lt;&#x2F;code&gt; must be able to aggregate related branches from multiple microrepos.&lt;&#x2F;p&gt;
&lt;p&gt;In order for the bot to know if two branches are related or not, an identifier can be used. For example, if &lt;code&gt;newfeature&lt;&#x2F;code&gt; was used as a branch name in &lt;code&gt;proj1&lt;&#x2F;code&gt; and the feature requires changes to both projects, a namesake branch in &lt;code&gt;proj2&lt;&#x2F;code&gt; must be created.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-this-solution-is-good&quot;&gt;Why this solution is good&lt;&#x2F;h2&gt;
&lt;p&gt;In a nutshell, because the advantages of monorepos are kept. The only practical difference is that devs don&#x27;t need to run slow git operations on their machines. &lt;&#x2F;p&gt;
&lt;p&gt;The good news is that all this can be abstracted away by a CLI. &lt;&#x2F;p&gt;
&lt;h2 id=&quot;does-this-solution-need-to-be-so-complex&quot;&gt;Does this solution need to be so complex?&lt;&#x2F;h2&gt;
&lt;p&gt;I think so. &lt;&#x2F;p&gt;
&lt;p&gt;Multiple multi-billion dollar companies struggle with this problem. If there were a simple solution, I&#x27;m sure someone would already have figured it out.&lt;&#x2F;p&gt;
&lt;p&gt;The only simple solution (from end-user&#x27;s perspective) I can think of is to have an SVN performatic for monorepos out-of-the-box. &lt;&#x2F;p&gt;
&lt;p&gt;Perhaps that&#x27;s the case already, but a different SVN rejects our assumption &lt;code&gt;1.&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rejected-solution&quot;&gt;Rejected solution&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;having-the-microrepos-as-source-of-truth-and-the-monorepo-as-materialized-view&quot;&gt;Having the microrepos as source of truth and the monorepo as materialized view&lt;&#x2F;h3&gt;
&lt;p&gt;The problem with this solution is that changes to &lt;code&gt;proj1&lt;&#x2F;code&gt; and &lt;code&gt;proj2&lt;&#x2F;code&gt; must be atomic, assuming a feature requires changes to both: we either want to commit a change to both projects or drop the commit altogether. &lt;&#x2F;p&gt;
&lt;p&gt;git currently doesn&#x27;t provide a solution for such transactions, so a mechanism for simulating atomicity would need to be designed, rendering the solution even more complex. &lt;&#x2F;p&gt;
&lt;p&gt;For example, a change to &lt;code&gt;proj1&lt;&#x2F;code&gt; would need to be reverted in case we&#x27;re not able to commit to &lt;code&gt;proj2&lt;&#x2F;code&gt;. &lt;&#x2F;p&gt;
&lt;p&gt;As we all know, distributed systems can fail or become inconsistent for all sorts of reasons. If somehow &lt;code&gt;proj2&lt;&#x2F;code&gt; got corrupted or inconsistent, it&#x27;s much easier and less error-prone to fix or reconstruct its materialized view than trying to agree upon the source of truth.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;&#x2F;h2&gt;
&lt;p&gt;To illustrate, I could&#x27;ve &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;probot&#x2F;probot&quot;&gt;created a GitHub bot&lt;&#x2F;a&gt;. That would exceed the time budget I set for putting this article together, though. &lt;&#x2F;p&gt;
&lt;p&gt;For demo purposes I&#x27;ve created a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;git-monorepo&quot;&gt;proof-of-concept CLI&lt;&#x2F;a&gt; that simulates the flow locally. This won&#x27;t simulate the interactions with PRs, as they don&#x27;t exist in a local machine, but will give us a clear idea of how this flow works. &lt;&#x2F;p&gt;
&lt;p&gt;In this example, all folders inside &lt;code&gt;~&#x2F;github&lt;&#x2F;code&gt; represent repositories you would normally have hosted on GitHub; all folders inside &lt;code&gt;~&#x2F;dev&lt;&#x2F;code&gt; represent the local clones. &lt;&#x2F;p&gt;
&lt;p&gt;Once this CLI is available in your &lt;code&gt;$PATH&lt;&#x2F;code&gt; as git-monorepo, you can invoke it by running &lt;code&gt;git monorepo&lt;&#x2F;code&gt;. &lt;&#x2F;p&gt;
&lt;p&gt;You can execute the commands below in your local machine if you want to follow along. The CLI prints all commands it&#x27;s running for you to understand what&#x27;s happening under the hood. &lt;&#x2F;p&gt;
&lt;p&gt;Without further ado, let&#x27;s get to it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;0-a-setting-up-the-monorepo&quot;&gt;0.a. Setting up the monorepo&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s create the remote &lt;code&gt;mono&lt;&#x2F;code&gt; repository:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;mkdir -p ~&amp;#x2F;github&amp;#x2F;mono
cd ~&amp;#x2F;github&amp;#x2F;mono
git init
mkdir proj{1,2}
for i in 1 2; do echo &amp;quot;console.log(&amp;#x27;proj${i}&amp;#x27;)&amp;quot; &amp;gt; proj${i}&amp;#x2F;file${i}.js; done
git add . 
git commit -am &amp;#x27;First commit&amp;#x27; 
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By the end of these steps, GitHub would host a monorepo like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;mono&amp;#x2F;
   proj1&amp;#x2F;file1.js
   proj2&amp;#x2F;file2.js
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;0-b-setting-up-the-microrepos&quot;&gt;0.b. Setting up the microrepos&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s create the remote microrepos:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;for i in 1 2; do git monorepo extract proj${i} ~&amp;#x2F;github&amp;#x2F;proj${i}; done
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first argument is the path to the project inside &lt;code&gt;mono&lt;&#x2F;code&gt;; the second argument is where the remote microrepo will live.&lt;&#x2F;p&gt;
&lt;p&gt;Normally, the second argument would look like &lt;code&gt;https:&#x2F;&#x2F;github.com&#x2F;username&#x2F;proj1&lt;&#x2F;code&gt; or &lt;code&gt;git@github.com:username&#x2F;proj1&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;By the end of these steps, GitHub would host repositories like this:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;mono&amp;#x2F;
   .gitmonorepo
   proj1&amp;#x2F;file1.js
   proj2&amp;#x2F;file2.js
proj1&amp;#x2F;
   file1.js
proj2&amp;#x2F;
   file2.js
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;.gitmonorepo&lt;&#x2F;code&gt; was automatically created to keep track of the microrepos. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-cloning-a-microrepo&quot;&gt;1. Cloning a microrepo&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s clone &lt;code&gt;proj1&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;mkdir -p ~&amp;#x2F;dev
cd ~&amp;#x2F;dev
git clone -b master ~&amp;#x2F;github&amp;#x2F;proj1
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;2-making-changes&quot;&gt;2. Making changes&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s develop a new feature. &lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;cd ~&amp;#x2F;dev&amp;#x2F;proj1
git checkout master
git pull origin master
git checkout -b newfeature
echo &amp;quot;console.log(&amp;#x27;newchange&amp;#x27;)&amp;quot; &amp;gt;&amp;gt; file1.js
git add .
git commit -am &amp;quot;proj1&amp;#x2F;newfeature: change file1.js&amp;quot;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;3-pushing-a-change-to-the-microrepo&quot;&gt;3. Pushing a change to the microrepo&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s push our changes to the remote &lt;code&gt;proj1&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;git push origin newfeature
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;4-propagating-the-changes-to-the-monorepo&quot;&gt;4. Propagating the changes to the monorepo&lt;&#x2F;h3&gt;
&lt;p&gt;The bot would automatically propagate the changes to &lt;code&gt;mono&lt;&#x2F;code&gt;, by running something like the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;cd ~&amp;#x2F;github&amp;#x2F;mono
git monorepo pull newfeature
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, &lt;code&gt;~&#x2F;github&#x2F;mono&#x2F;proj1&#x2F;file1.js&lt;&#x2F;code&gt; should have the &lt;code&gt;newchange&lt;&#x2F;code&gt; line on the &lt;code&gt;newfeature&lt;&#x2F;code&gt; branch, but not in &lt;code&gt;master&lt;&#x2F;code&gt;. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-merging-a-pr&quot;&gt;5. Merging a PR&lt;&#x2F;h3&gt;
&lt;p&gt;Let&#x27;s merge our branch:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;git checkout master 
git merge newfeature
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, &lt;code&gt;~&#x2F;github&#x2F;mono&#x2F;proj1&#x2F;file1.js&lt;&#x2F;code&gt; should have the &lt;code&gt;newchange&lt;&#x2F;code&gt; line on &lt;code&gt;master&lt;&#x2F;code&gt;. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-propagating-the-change-back-to-the-microrepo&quot;&gt;6. Propagating the change back to the microrepo&lt;&#x2F;h3&gt;
&lt;p&gt;Finally, the bot would automatically update all microrepos accordingly, by running something like the following:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;git monorepo push
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, &lt;code&gt;~&#x2F;dev&#x2F;proj1&#x2F;file1.js&lt;&#x2F;code&gt; should also have the &lt;code&gt;newchange&lt;&#x2F;code&gt; line on &lt;code&gt;master&lt;&#x2F;code&gt;, ending the loop cycle. &lt;&#x2F;p&gt;
&lt;p&gt;Please note that we were able to make changes to the remote monorepo having only cloned &lt;code&gt;proj1&lt;&#x2F;code&gt;. &lt;code&gt;proj2&lt;&#x2F;code&gt; and &lt;code&gt;mono&lt;&#x2F;code&gt; weren&#x27;t cloned locally.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;future-work&quot;&gt;Future work&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ve only covered the simple, happy path so far. &lt;&#x2F;p&gt;
&lt;p&gt;Ideally, this system should also include:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;a dev-friendly git wrapper for working with multiple microrepos at once&lt;&#x2F;li&gt;
&lt;li&gt;a UI for displaying how the microrepos and the monorepo are interacting with each other &lt;&#x2F;li&gt;
&lt;li&gt;different resolution strategies, in case one of the propagation changes fails for some reason&lt;&#x2F;li&gt;
&lt;li&gt;merge queues&lt;&#x2F;li&gt;
&lt;li&gt;a mechanism for replicating the monorepo locally, but using the microrepos of interest instead &lt;&#x2F;li&gt;
&lt;li&gt;cleanup routines, for deleting branches in microrepos whose PR in the monorepo is closed &lt;&#x2F;li&gt;
&lt;li&gt;fixes to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;git-monorepo&#x2F;search?q=TODO&quot;&gt;these TODOs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;and much more. &lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;The purpose of this article was to simply brainstorm what a more performant workflow could look like. &lt;&#x2F;p&gt;
&lt;p&gt;I hope that this will motivate someone out there willing to implement a system ready for real-world scenarios. &lt;&#x2F;p&gt;
&lt;p&gt;In case you do, I&#x27;d really appreciate if you could add a link to this post somewhere in your README.md file! :) &lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Working around Google Photos limitation</title>
          <pubDate>Mon, 07 Jun 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/working-around-google-photos-limitation/</link>
          <guid>https://denisidoro.github.io/posts/working-around-google-photos-limitation/</guid>
          <description xml:base="https://denisidoro.github.io/posts/working-around-google-photos-limitation/">&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;As of June 1, 2021, Google Photos &lt;a href=&quot;https:&#x2F;&#x2F;www.theverge.com&#x2F;2020&#x2F;11&#x2F;11&#x2F;21560810&#x2F;google-photos-unlimited-cap-free-uploads-15gb-ending&quot;&gt;started limiting&lt;&#x2F;a&gt; the storage for new photos and videos to 15GB&#x2F;account.&lt;&#x2F;p&gt;
&lt;p&gt;15GB isn&#x27;t much nowadays, so I looked for alternatives. &lt;&#x2F;p&gt;
&lt;p&gt;Signing up for paid storage is an option which I discarded: &lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;first, because I don&#x27;t want to yet another lifetime subscription&lt;&#x2F;li&gt;
&lt;li&gt;second, because I don&#x27;t want to be tied to a ecosystem. What if prices double overnight? What if I reach the storage limit and need to upgrade to the next tier?&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That said, I love Google Photos and its machine learning wizardry.&lt;&#x2F;p&gt;
&lt;p&gt;A suboptimal experience, yet better than nothing, would be to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;upload low quality photos to Google Photos&lt;&#x2F;li&gt;
&lt;li&gt;upload high&#x2F;original quality photos to alternative storages&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This way, Google Photos will still keep me reminding me of past trips or allow me to search for &amp;quot;bridge&amp;quot; or &amp;quot;Rio de Janeiro&amp;quot; against my 1000s of photos. Given an image ID&#x2F;date, I can download the high&#x2F;original quality photo elsewhere.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;alternatives&quot;&gt;Alternatives&lt;&#x2F;h3&gt;
&lt;p&gt;Whatever the solution would turn out to be, I wanted it to last at least 3.5 years. If we assume 10 photos&#x2F;day in average, we&#x27;re talking about ~13k photos here.&lt;&#x2F;p&gt;
&lt;p&gt;I ended up creating&#x2F;reusing the following accounts:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: center&quot;&gt;Provider&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Available storage&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Photo size&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: center&quot;&gt;Trust?&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;photos.google.com&quot;&gt;Google Photos&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;6GB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;450KB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;box.com&quot;&gt;Box&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;7GB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;550KB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;pcloud.com&quot;&gt;pCloud&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;10GB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;750KB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;No&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;degoo.com&quot;&gt;Degoo&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;100GB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;7MB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;No&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;terabox.com&quot;&gt;Terabox&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;1TB&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;irrelevant&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;No&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center&quot;&gt;&lt;a href=&quot;https:&#x2F;&#x2F;telegram.org&quot;&gt;Telegram&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;∞&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;irrelevant&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: center&quot;&gt;Yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I&#x27;ve never heard about &lt;a href=&quot;https:&#x2F;&#x2F;pcloud.com&quot;&gt;pCloud&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;degoo.com&quot;&gt;Degoo&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;terabox.com&quot;&gt;Terabox&lt;&#x2F;a&gt; before so I&#x27;m not uploading my photos in a &amp;quot;human-readable&amp;quot; format to these services.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;solution&quot;&gt;Solution&lt;&#x2F;h3&gt;
&lt;p&gt;I won&#x27;t get into too much implementation details, but I basically used &lt;a href=&quot;https:&#x2F;&#x2F;play.google.com&#x2F;store&#x2F;apps&#x2F;details?id=net.dinglisch.android.taskerm&amp;amp;hl=en&amp;amp;gl=US&quot;&gt;Tasker&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;play.google.com&#x2F;store&#x2F;apps&#x2F;details?id=com.termux&amp;amp;hl=en&amp;amp;gl=US&quot;&gt;Termux&lt;&#x2F;a&gt;. I also installed the packages for &lt;a href=&quot;https:&#x2F;&#x2F;linux.die.net&#x2F;man&#x2F;1&#x2F;file&quot;&gt;file&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;exiftool.org&quot;&gt;exiftool&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;rclone.org&quot;&gt;rclone&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The pseudo-code is as follows:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;every day at 2am:
    for each photo in &amp;#x2F;sdcard&amp;#x2F;DCIM:
        compress photo to &amp;#x2F;sdcard&amp;#x2F;Cloud&amp;#x2F;GooglePhotos&amp;#x2F;⋯.jpg
        compress and zip photo to &amp;#x2F;sdcard&amp;#x2F;Cloud&amp;#x2F;pCloud&amp;#x2F;⋯.jpg.7z
        ...
        zip photo to &amp;#x2F;sdcard&amp;#x2F;Cloud&amp;#x2F;Terabox&amp;#x2F;⋯.jpg.7z
        move photo to &amp;#x2F;sdcard&amp;#x2F;Cloud&amp;#x2F;Telegram&amp;#x2F;⋯.jpg

every day at 3am, if wifi is connected:
    for each file in &amp;#x2F;sdcard&amp;#x2F;Cloud&amp;#x2F;&amp;lt;provider&amp;gt;:
        move file to &amp;lt;provider&amp;gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;compression&quot;&gt;Compression&lt;&#x2F;h3&gt;
&lt;p&gt;A photo is compressed for each storage provider accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;The output image will be limited by &lt;code&gt;Photo size&lt;&#x2F;code&gt; and, if &lt;code&gt;Trust?&lt;&#x2F;code&gt; is &lt;code&gt;false&lt;&#x2F;code&gt;, it will be 7zipped using a password.&lt;&#x2F;p&gt;
&lt;p&gt;The output image size is limited by playing with the properties for image resizing inside Tasker (&lt;code&gt;max dimension&lt;&#x2F;code&gt; and &lt;code&gt;compression quality&lt;&#x2F;code&gt;). These are estimated based on the &lt;code&gt;Photo size&lt;&#x2F;code&gt; input and the original image properties, retrived by &lt;code&gt;file&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;One caveat is that, for some reason, Tasker doesn&#x27;t preserve the EXIF attributes when resizing images, so I needed to use &lt;code&gt;exiftool&lt;&#x2F;code&gt; to overwrite the new file EXIF attributes with the original ones.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;uploading-files-to-decent-real-storage-providers&quot;&gt;Uploading files to decent, real storage providers&lt;&#x2F;h3&gt;
&lt;p&gt;This was very straightforward and was automated using &lt;a href=&quot;https:&#x2F;&#x2F;rclone.org&quot;&gt;rclone&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;uploading-files-to-bad-real-storage-providers&quot;&gt;Uploading files to bad, real storage providers&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;degoo.com&quot;&gt;Degoo&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;terabox.com&quot;&gt;Terabox&lt;&#x2F;a&gt; don&#x27;t have support for WebDAV or similar protocols. In other words, I need to open their app and manually move files.&lt;&#x2F;p&gt;
&lt;p&gt;I plan to do that once a month.&lt;&#x2F;p&gt;
&lt;p&gt;The good news is that all files will already be easily located in &lt;code&gt;&#x2F;sdcard&#x2F;Cloud&#x2F;Degoo&lt;&#x2F;code&gt;, for example.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;uploading-files-to-telegram&quot;&gt;Uploading files to Telegram&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;telegram.org&quot;&gt;Telegram&lt;&#x2F;a&gt; isn&#x27;t a storage provider but, to my knowledge, a given chat can have infinite attachments. 🤷&lt;&#x2F;p&gt;
&lt;p&gt;Its API allows file uploads using a simple POST request. The response body contains a &lt;code&gt;file_id&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We can download the attachment by making a request to &lt;code&gt;https:&#x2F;&#x2F;⋯.telegram.org&#x2F;⋯?file_id=&amp;lt;file_id&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We thus need to keep this &lt;code&gt;file_id&lt;&#x2F;code&gt; in a database. My Tasker task stores this in &lt;code&gt;&#x2F;sdcard&#x2F;Tasker&#x2F;db&#x2F;telegram_uploads.txt&lt;&#x2F;code&gt; and its contents look like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;txt&quot; class=&quot;language-txt &quot;&gt;&lt;code class=&quot;language-txt&quot; data-lang=&quot;txt&quot;&gt;&amp;#x2F;Pictures&amp;#x2F;IMG_00.jpg;123456
&amp;#x2F;Pictures&amp;#x2F;IMG_01.jpg;789012
&amp;#x2F;Pictures&amp;#x2F;IMG_02.jpg;654321
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Having this database in hand, we can even design a primitive web-based file manager, for example.&lt;&#x2F;p&gt;
&lt;p&gt;The database is replicated across all other storage providers, using the aforementioned methods.&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;gphotos_telegram.jpg&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;My chat with my bot will ultimately contain thousands of messages like this&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;m happy with the solution because Google Photos will still keep doing its magic and photos are replicated. If one storage provider goes down (I&#x27;m sure that down the road at least one of them will), I won&#x27;t lose my photos.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;code&quot;&gt;Code&lt;&#x2F;h3&gt;
&lt;p&gt;The Tasker tasks aren&#x27;t public because they rely on a very specific setup (and because I&#x27;ve never met anyone in person who uses Tasker - or Termux, for that matter).&lt;&#x2F;p&gt;
&lt;p&gt;In the unexpected scenario I get requests to do it, I&#x27;ll share them in &lt;a href=&quot;https:&#x2F;&#x2F;taskernet.com&#x2F;shares&#x2F;&quot;&gt;Taskernet&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Using a spreadsheet as a timeseries database for finance</title>
          <pubDate>Wed, 12 May 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/spreadsheet-tsdb-finance/</link>
          <guid>https://denisidoro.github.io/posts/spreadsheet-tsdb-finance/</guid>
          <description xml:base="https://denisidoro.github.io/posts/spreadsheet-tsdb-finance/">&lt;h3 id=&quot;context&quot;&gt;Context&lt;&#x2F;h3&gt;
&lt;p&gt;I have already tried using personal finance apps but there&#x27;s always a feature missing or an inconsistency in results. &lt;&#x2F;p&gt;
&lt;p&gt;Unhappy with the experience, I created a simple spreadsheet half a decade ago. It lacked features and quickly became unmaintainable.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to build my own tool instead. I started with a React (Native) Clojurescript web&#x2F;mobile app. I never finished it. &lt;&#x2F;p&gt;
&lt;p&gt;I created a &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;grafana-personal-finance&#x2F;&quot;&gt;system using Clojure and Python, paired with Grafana&lt;&#x2F;a&gt;. It worked pretty well, actually, but I never managed to deploy it in a free&#x2F;cheap hosting service, because of JVM&#x27;s and pandas&#x27; requirements.&lt;&#x2F;p&gt;
&lt;p&gt;I converted the codebase to Rust as a pet project. The memory and processing footprints became negligible. Cool! &lt;&#x2F;p&gt;
&lt;p&gt;But every once in a while I need to perform a simple calculation on the data, or plot an ad-hoc graph, or explore something new. Cloning repositories, editing the source code and recompiling everything isn&#x27;t fun. &lt;&#x2F;p&gt;
&lt;p&gt;Spreadsheets are good at that. Ironically, I decided to go back to Google Sheets. But this time for real. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;architecture-diagram&quot;&gt;Architecture diagram&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s the diagram of the complete system:&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_diagram.png&quot; style=&quot;margin-top: -2em; margin-bottom: -0.5em&quot;&gt;
&lt;&#x2F;figure&gt; 
&lt;p&gt;I&#x27;ll cover each section in more details.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;using-google-sheets-for-input&quot;&gt;Using Google Sheets for input&lt;&#x2F;h1&gt;
&lt;p&gt;One of the requirements was that, as before, input should be minimal. In this case, it should basically be an event log of money transfers: &lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_log.png&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;The first 2 and last 2 columns are filled automatically&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;An extra sheet was necessary to completely define some investments. &lt;code&gt;NASDAQ:APPL&lt;&#x2F;code&gt; is all the input you need to define Apple&#x27;s stock -- provided you get data externally --, but some fixed income investments have their own criteria -- e.g. rate of interest. &lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_config.png&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;This sheet only contains data which isn&#x27;t possible to obtain otherwise&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;h3 id=&quot;using-google-sheets-for-processing&quot;&gt;Using Google sheets for processing&lt;&#x2F;h3&gt;
&lt;p&gt;Handling input was the easy part. Fetching data from APIs and doing the math is &lt;strike&gt;complicated&lt;&#x2F;strike&gt; laborious.&lt;&#x2F;p&gt;
&lt;p&gt;I can&#x27;t get my head around &lt;code&gt;=ARRAYFORMULA(IF..., VLOOKUP(...FILTER(...(INDEX...(MATCH...)))))&lt;&#x2F;code&gt; formulae. Fortunately, Google Sheets allows using Javascript for manipulating sheets. It&#x27;s not &lt;strike&gt;Rust&lt;&#x2F;strike&gt; my favorite language but at least it has &lt;code&gt;.map()&lt;&#x2F;code&gt;, &lt;code&gt;.filter()&lt;&#x2F;code&gt; and alike.&lt;&#x2F;p&gt;
&lt;p&gt;After so many attempts building the same system, I came up with a mental model which I didn&#x27;t want to abandon so I replicated it once again.&lt;&#x2F;p&gt;
&lt;p&gt;To implement a timeseries database, a single spreadsheet was used. The y-axis represents time; the x-axis, different vectors:&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_tsdb.jpg&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;This sheet contains all numeric data&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;p&gt;In Javascript we can read from and write to this sheet as if we were using a real timeseries database. Effectively, this made my new code look very similar to the previous ones:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&amp;#x2F;&amp;#x2F; old 
influx.emit(today, 3.12)
&amp;#x2F;&amp;#x2F; new
tsdbSheet.setValue(&amp;#x27;D67&amp;#x27;) = 3.12
 
&amp;#x2F;&amp;#x2F; old 
config = readFromFile(&amp;#x27;.&amp;#x2F;config.yaml&amp;#x27;)
foo = config.foo
&amp;#x2F;&amp;#x2F; new
foo = configSheet.getValue(&amp;#x27;A2&amp;#x27;)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;abstractions&quot;&gt;Abstractions&lt;&#x2F;h3&gt;
&lt;p&gt;These low-level spreadsheet operations were abstracted away, in order to make the rest of the code agnostic to Google Sheets. Should I decide to port it elsewhere, I&#x27;ll only need to edit some parts from the &lt;code&gt;TSDB&lt;&#x2F;code&gt;, &lt;code&gt;EventLog&lt;&#x2F;code&gt; and &lt;code&gt;Config&lt;&#x2F;code&gt; classes.&lt;&#x2F;p&gt;
&lt;p&gt;For example, calculating balances looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;config.assets.forEach(asset -&amp;gt; {
   const balance = range(startDate, today).map(date =&amp;gt; {
      const value = someMath(asset, date, tsdb)
      return [date, value] 
   }
   tsdb.save(asset.id, &amp;#x27;balance&amp;#x27;, balance)
})
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Getting historical data for stocks isn&#x27;t any different:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;config.assets
   .filter(asset =&amp;gt; asset.kind == &amp;#x27;stock&amp;#x27;) 
   .forEach(stock =&amp;gt; {
      const price = callSomeApi(asset.id)
      tsdb.save(asset.id, &amp;#x27;price&amp;#x27;, price) 
   }) 
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Similarly, to prevent some input, the following is executed on startup:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;config.assets
   .filter(asset -&amp;gt; asset.reachedMaturity())
   .forEach(asset -&amp;gt; eventLog.save(&amp;#x27;sell&amp;#x27;, asset.id, asset.maturityDate))
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, this allowed me to achieve what I already had in the early designs but with 3 benefits:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I can access it from anywhere, as long as the device supports Google Sheets&lt;&#x2F;li&gt;
&lt;li&gt;I edit tabular data, instead of a difficult-to-maintain &lt;code&gt;.yml&lt;&#x2F;code&gt; file versioned in a private Github repository&lt;&#x2F;li&gt;
&lt;li&gt;I can quickly run an ad-hoc &lt;code&gt;=SUM(FILTER(...)))&lt;&#x2F;code&gt; against my data if necessary &lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;dashboards&quot;&gt;Dashboards&lt;&#x2F;h1&gt;
&lt;p&gt;Spreadsheet formulae are cool but I still like looking at dashboards and plotting graphs on the fly, so I reused the architecture I already had on top of Grafana. I don&#x27;t want to share screenshots here but the dashboard looks like any regular Grafana one:&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;grafana.com&#x2F;static&#x2F;img&#x2F;grafana&#x2F;showcase_visualize-954.jpg&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;Grafana dashboards normally look like this&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;p&gt;For example, I can quickly write a query to compare the portfolio performance against the &lt;a href=&quot;https:&#x2F;&#x2F;br.investing.com&#x2F;indices&#x2F;bovespa&quot;&gt;Ibovespa index&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_grafana.jpg&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;To get the performance of assets broken down by broker, &lt;code&gt;sum(...) by (broker)&lt;&#x2F;code&gt; would do the trick&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt; 
&lt;h3 id=&quot;real-timeseries-database&quot;&gt;(Real) timeseries database&lt;&#x2F;h3&gt;
&lt;p&gt;I decided to &lt;a href=&quot;https:&#x2F;&#x2F;victoriametrics.com&quot;&gt;VictoriaMetrics&lt;&#x2F;a&gt; this time. It&#x27;s fast and it has support for a much needed feature: backfilling. In other words, I&#x27;m able to emit values for timestamps in the past. &lt;&#x2F;p&gt;
&lt;p&gt;Its query engine is called MetricsQL and is a superset of &lt;a href=&quot;https:&#x2F;&#x2F;prometheus.io&#x2F;docs&#x2F;prometheus&#x2F;latest&#x2F;querying&#x2F;basics&#x2F;&quot;&gt;PromQL&lt;&#x2F;a&gt;. &lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d like to highlight 3 features from MetricsQL:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;keep_last_value&lt;&#x2F;code&gt;: only one data point per day per series is emitted. Anything above this would be redundant, since I don&#x27;t day trade. However, Grafana asks for &lt;code&gt;n&lt;&#x2F;code&gt; data points per day, &lt;code&gt;n&lt;&#x2F;code&gt; function of the width of the panel in pixels and is normally greater than 1. Without &lt;code&gt;keep_last_value&lt;&#x2F;code&gt; the plots would contain gaps. Summing metrics with gaps in different positions is erratic, because gaps are interpreted as 0.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WITH&lt;&#x2F;code&gt; statement: this lets query aliasing, which is pretty handy. By the way, if you like &lt;code&gt;WITH&lt;&#x2F;code&gt; too, you may like &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;pipers&#x2F;&quot;&gt;pipers&lt;&#x2F;a&gt;. &lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;range_first&lt;&#x2F;code&gt;: with this function I was able to normalize investment performances so that they all start with 1, making comparisons a breeze, as seen above. &lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;exporting-importing-data&quot;&gt;Exporting&#x2F;importing data&lt;&#x2F;h1&gt;
&lt;p&gt;I added an &lt;code&gt;Export&lt;&#x2F;code&gt; button to Google Sheets which exports the contents from the spreadsheets to Grafana:&lt;&#x2F;p&gt;
&lt;figure style=&quot;margin-left: 0; margin-right: 0&quot;&gt;
    &lt;img src=&quot;&#x2F;posts&#x2F;sheet_button.png&quot;&gt;
    &lt;figcaption style=&quot;font-size: 0.8em&quot;&gt;It&#x27;s easy to call custom scripts like this&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;  
&lt;p&gt;It simply serializes the in-memory data structures as JSON text and sends it in a POST request.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;notes-on-performance&quot;&gt;Notes on performance&lt;&#x2F;h3&gt;
&lt;p&gt;The Rust implementation was able to read the config file, make all requests in parallel, calculate everything and export all data to Grafana in ~5 seconds.&lt;&#x2F;p&gt;
&lt;p&gt;I expected my first implementation in Google Sheets to be slower, or course. But I didn&#x27;t expect it to be SO SLOW. The first implementation took 45~60min to finish processing. At least that&#x27;s what I estimate, because I wasn&#x27;t patient enough to wait for it. &lt;&#x2F;p&gt;
&lt;p&gt;The problem is that &lt;code&gt;sheet.setValue(...)&lt;&#x2F;code&gt; takes 500ms~3s for each cell. Also, switching between read and write modes takes about the same time. If you consider that each investment generates ~10 timeseries -- average price, balance, etc. -- and that each timeseries has 365 data points per year, then you&#x27;ll see that this quickly explodes.&lt;&#x2F;p&gt;
&lt;p&gt;This reminded me of the problem that frameworks like React faced. Manipulating the DOM is slow, so they ended up creating a &lt;a href=&quot;https:&#x2F;&#x2F;reactjs.org&#x2F;docs&#x2F;faq-internals.html&quot;&gt;virtual representation of the DOM&lt;&#x2F;a&gt;. I did the same, but for spreadsheets. It sounds complicated, but it isn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;Basically, &lt;code&gt;tsdb.save(...)&lt;&#x2F;code&gt; doesn&#x27;t manipulate the sheet directly but mutates an in-memory data structure instead. Later on, it sets the values for ranges instead of cells, in batch. The &amp;quot;public&amp;quot; APIs remained the same -- only the implementation details changed.&lt;&#x2F;p&gt;
&lt;p&gt;After these optimizations, the script takes less than a minute to complete.&lt;&#x2F;p&gt;
&lt;p&gt;This could be improved even further, but I don&#x27;t want to make the code more complex just to save a few seconds once a month. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;result&quot;&gt;Result&lt;&#x2F;h3&gt;
&lt;p&gt;To be honest, it&#x27;s likely that a couple of &lt;strike&gt;months&lt;&#x2F;strike&gt; years from now I&#x27;ll decide to write everything from scratch yet again. &lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not you, project of mine. It&#x27;s me. &lt;&#x2F;p&gt;
&lt;p&gt;But I&#x27;m sure of one thing: the system has never been so flexible and portable.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;codebase&quot;&gt;Codebase&lt;&#x2F;h3&gt;
&lt;p&gt;The spreadsheet and the source code are currently private because it would require some effort to make them shareable -- from privacy, security and usability standpoints. &lt;&#x2F;p&gt;
&lt;p&gt;Please reach out to me if you&#x27;re interested in it, though. Maybe I can come up with something! 👍&lt;&#x2F;p&gt;
&lt;p&gt;In the meantime, I highly recommend &lt;a href=&quot;https:&#x2F;&#x2F;dlombelloplanilhas.com&#x2F;&quot;&gt;dlombello&#x27;s spreadsheets&lt;&#x2F;a&gt;, which should fit most people&#x27;s needs.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Creating templates for CLIs</title>
          <pubDate>Wed, 13 Jan 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/cli-templates/</link>
          <guid>https://denisidoro.github.io/posts/cli-templates/</guid>
          <description xml:base="https://denisidoro.github.io/posts/cli-templates/">&lt;h3 id=&quot;inspiration&quot;&gt;Inspiration&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ve been using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yarpc&#x2F;yab&quot;&gt;yab&lt;&#x2F;a&gt; —a curl-like CLI— recently and I was surprised by how elegantly it solves a common problem.&lt;&#x2F;p&gt;
&lt;p&gt;yab calls can get quite verbose. As an example, to get a &lt;code&gt;Customer&lt;&#x2F;code&gt; by its &lt;code&gt;id&lt;&#x2F;code&gt; from a &lt;code&gt;customer&lt;&#x2F;code&gt; microservice:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;yab customer Customer::get \
    -t &amp;#x2F;path&amp;#x2F;to&amp;#x2F;idl&amp;#x2F;some.company&amp;#x2F;customer&amp;#x2F;customer.thrift \
    -P 131.144.23.5&amp;#x2F;customer:tchannel \
    -r &amp;#x27;{&amp;quot;request&amp;quot;: {&amp;quot;id&amp;quot;: 11}}&amp;#x27;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The call is pretty convoluted, but it can be simplified by storing params in a yaml file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# customer_by_id.yaml
service: customer
procedure: &amp;quot;Customer::get&amp;quot;
thrift: &amp;#x2F;path&amp;#x2F;to&amp;#x2F;idl&amp;#x2F;some.company&amp;#x2F;customer&amp;#x2F;customer.thrift
peer-list: 131.144.23.5&amp;#x2F;customer:tchannel
request:
  request:
    id: ${number:11}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The file takes a set of CLI flags in their long form, e.g. &lt;code&gt;--thrift&lt;&#x2F;code&gt; instead of &lt;code&gt;-t&lt;&#x2F;code&gt;, and their associated values.&lt;&#x2F;p&gt;
&lt;p&gt;Then, the following call is equivalent to the beforementioned one:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;yab -y customer_by_id.yaml
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Additional flags can be passed to yab to override the default values specified in the yaml file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;yab -y customer_by_id.yaml --number 42
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I think this is pretty cool!&lt;&#x2F;p&gt;
&lt;p&gt;It can save us lots of time. It also helps sharing knowledge between team members: we could have a git repo with a bunch of these &lt;code&gt;.yaml&lt;&#x2F;code&gt; files and a simple &lt;code&gt;git pull&lt;&#x2F;code&gt; would allow everyone to be on the same page.&lt;&#x2F;p&gt;
&lt;p&gt;My immediate thought was &amp;quot;could we easily replicate this for other commands?&amp;quot;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;solution&quot;&gt;Solution&lt;&#x2F;h1&gt;
&lt;p&gt;For the sake of simplicity, let&#x27;s try to write a complex &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;httpie&#x2F;httpie&quot;&gt;httpie&lt;&#x2F;a&gt; command. It could be an involved &lt;code&gt;awk&lt;&#x2F;code&gt;, &lt;code&gt;grep&lt;&#x2F;code&gt; or &lt;code&gt;jq&lt;&#x2F;code&gt; command, though.&lt;&#x2F;p&gt;
&lt;p&gt;The final call should look like this:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;http -v POST https:&amp;#x2F;&amp;#x2F;jsonplaceholder.typicode.com&amp;#x2F;posts title=foo body=bar userId=11
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The tool I&#x27;m going to use is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;navi&quot;&gt;navi&lt;&#x2F;a&gt;. It allows you to browse through cheatsheets —that you may write yourself or download from maintainers— and execute commands. &lt;&#x2F;p&gt;
&lt;p&gt;navi encourages you to write &lt;code&gt;.cheat&lt;&#x2F;code&gt; files which break commands down into smaller, reusable pieces:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;% httpie

# make a request to a typicode microservice
http -v &amp;lt;method&amp;gt; &amp;quot;https:&amp;#x2F;&amp;#x2F;&amp;lt;service&amp;gt;.typicode.com&amp;#x2F;&amp;lt;endpoint&amp;gt;&amp;quot; &amp;lt;http-body&amp;gt;

$ method: echo -e &amp;#x27;GET\nPOST\nPUT&amp;#x27;
$ service: echo -e &amp;#x27;jsonplaceholder\nanotherservice&amp;#x27;
$ endpoint: case &amp;quot;${service}:${method}&amp;quot; in; &amp;quot;jsonplaceholder:post&amp;quot;) echo -e &amp;#x27;e1\ne2\ne3&amp;#x27;;; esac
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;endpoint&lt;&#x2F;code&gt; values here are incomplete for briefness and, in a real-world scenario, would probably be fetched dynamically, instead of being harcoded.&lt;&#x2F;p&gt;
&lt;p&gt;This &lt;code&gt;.cheat&lt;&#x2F;code&gt; enables us to very quickly make a request to any endpoint in our company, given that we somehow mapped all possible values to the corresponding variables. &lt;&#x2F;p&gt;
&lt;p&gt;But an engineer who recently joined the team still wouldn&#x27;t know what service&#x2F;endpoint to request for creating a new &lt;code&gt;post&lt;&#x2F;code&gt;. We could then add another cheatsheet entry for more granular commands:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;% httpie, endpoints

# create a post
http -v &amp;lt;method&amp;gt; &amp;quot;https:&amp;#x2F;&amp;#x2F;&amp;lt;service&amp;gt;.typicode.com&amp;#x2F;&amp;lt;endpoint&amp;gt;&amp;quot; title=&amp;lt;title&amp;gt; body=&amp;lt;body&amp;gt; userId=&amp;lt;userId&amp;gt;

$ method: echo &amp;#x27;POST&amp;#x27;
$ service: echo &amp;#x27;jsonplaceholder&amp;#x27;
$ endpoint: echo &amp;#x27;posts&amp;#x27;
$ title: echo &amp;#x27;foo&amp;#x27;
$ body: echo &amp;#x27;bar&amp;#x27;
$ userId: echo &amp;#x27;11&amp;#x27;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are a multitude of ways to invoke this cheatsheet. One of them is like this:&lt;&#x2F;p&gt;
&lt;script id=&quot;asciicast-Su5eUYFHn7M5A6Yccvcv7WH7k&quot; src=&quot;https:&#x2F;&#x2F;asciinema.org&#x2F;a&#x2F;Su5eUYFHn7M5A6Yccvcv7WH7k.js&quot; async&gt;&lt;&#x2F;script&gt;&lt;h3 id=&quot;preventing-user-interaction&quot;&gt;Preventing user interaction&lt;&#x2F;h3&gt;
&lt;p&gt;If no interaction is wanted we could override values using environment variables and autoselect the desired command by using the &lt;code&gt;--best-match&lt;&#x2F;code&gt; flag:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;userId=12 navi --query &amp;#x27;http endpoints create post&amp;#x27; --best-match
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s say that we want to skip the &lt;code&gt;--query&lt;&#x2F;code&gt; and &lt;code&gt;--best-match&lt;&#x2F;code&gt; boilerplate and we know that we&#x27;re always gonna use this tool for &lt;code&gt;http endpoints&lt;&#x2F;code&gt;. A simple bash script  come to the rescue:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;export_var() {
   local -r var=&amp;quot;${1&amp;#x2F;&amp;#x2F;-&amp;#x2F;_}&amp;quot;
   export &amp;quot;$var&amp;quot;=&amp;quot;$2&amp;quot;
}

endpoint() {
   local var
   for v in $@; do
    case $v in
        --*) var=&amp;quot;${v:2}&amp;quot; ;;
        *) export_var &amp;quot;$var&amp;quot; &amp;quot;$v&amp;quot; ;;
    esac
   done

   navi --query &amp;quot;http endpoints ${query}&amp;quot; --best-match
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And we could call it as follows:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh &quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;endpoint &amp;#x27;create post&amp;#x27;
endpoint &amp;#x27;create post&amp;#x27; --userId 12
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;I hope that, with these tips, using the terminal becomes easier for you. &lt;&#x2F;p&gt;
&lt;p&gt;Creating templates may speed up day-to-day tasks and improve knowledge sharing —either with other team members or with your future self.&lt;&#x2F;p&gt;
&lt;p&gt;By the way, if you have any feature requests for navi, feel free to leave an issue &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;navi&#x2F;issues&quot;&gt;here&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Mind maps</title>
          <pubDate>Tue, 17 Nov 2020 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/mind-maps/</link>
          <guid>https://denisidoro.github.io/posts/mind-maps/</guid>
          <description xml:base="https://denisidoro.github.io/posts/mind-maps/">&lt;p&gt;Since high school I&#x27;ve never had a paper notebook. Don&#x27;t get me wrong: I&#x27;m not one or these people with very good memory and don&#x27;t need any notes whatsoever. On the contrary.&lt;&#x2F;p&gt;
&lt;p&gt;The thing is I&#x27;ve never seen much value in note taking. At least in the traditional way, with paragraphs and blocks of text. I can&#x27;t memorize these chunks of text and, if I want to dive deep into a subject, I&#x27;m much better off reading text books or browsing Wikipedia articles. &lt;&#x2F;p&gt;
&lt;p&gt;In fact, I&#x27;ve never trusted my own notes. Maybe I got it wrong in the first place. Maybe the way I wrote is ambiguous. Ultimately, the bigger the notes, more likely they can lead me to wrong conclusions. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-need&quot;&gt;The need&lt;&#x2F;h3&gt;
&lt;p&gt;However, sometimes I want to refresh my memory on a particular subject and reading books and articles is too time consuming. I just want to recap the absolute essential — things I shouldn&#x27;t forget. Some minimal notes surely would come in handy. &lt;&#x2F;p&gt;
&lt;p&gt;I researched for cheap yet effective note taking methods. I tried &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Cornell_Notes&quot;&gt;Cornell notes&lt;&#x2F;a&gt; but they didn&#x27;t do the trick for me. Then I tried &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Mind_map&quot;&gt;mind maps&lt;&#x2F;a&gt; and I really liked it. &lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;user-images.githubusercontent.com&#x2F;3226564&#x2F;99436292-1389eb80-28f0-11eb-8fff-19f085e23c41.png&quot;&#x2F;&gt;
    &lt;figcaption&gt;A typical mind map. Source: &lt;a href=&quot;https:&#x2F;&#x2F;mindmapping.com&quot; target=&quot;_blank&quot;&gt;mindmapping.com&lt;&#x2F;a&gt;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;What I fancy about mind maps is that they are extremely minimalist and favor connection of ideas. It&#x27;s also possible to adjust the level of details you want to write&#x2F;read by determining how deep into the nodes you want to dive. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;first-experience&quot;&gt;First experience&lt;&#x2F;h3&gt;
&lt;p&gt;The first time I used mind maps for real was when I was studying for a software engineering interview in a big tech company. &lt;&#x2F;p&gt;
&lt;p&gt;For an Algorithm question in general you need to know beforehand what&#x27;s the better strategy for solving it. Should it use a graph? Or a linked list? When faced with such a problem it isn&#x27;t trivial to know how to solve it but if you ask yourself &amp;quot;would graphs work here?&amp;quot; you generally have an immediate yes&#x2F;no. A mind map —either in paper or in your brain— with all the possibilities makes things much easier. &lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-objective&quot;&gt;The objective&lt;&#x2F;h3&gt;
&lt;p&gt;After the good experience I had with mind maps, I decided to digitally register part of my knowledge using this method. Not only about algorithms but also Biology, History, Grammar and all these other fields I barely know a thing. &lt;&#x2F;p&gt;
&lt;p&gt;I established some criteria. The tool for this job should:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;have a very low time-wise cost for writing &lt;&#x2F;li&gt;
&lt;li&gt;have basic search functionality&lt;&#x2F;li&gt;
&lt;li&gt;have a way to connect different mind maps &lt;&#x2F;li&gt;
&lt;li&gt;be portable — it should work at least on my PC and my phone&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I didn&#x27;t find any tool that fits these criteria, so I decided to build my own. That said, one additional requirement was:&lt;&#x2F;p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;the tool should be so simple that the time to build it should be at most a few hours — because &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;posts&#x2F;tips-for-faster-development&#x2F;&quot;&gt;I&#x27;m lazy&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This requirement made me reject some complex ideas I had that involved graph databases such as &lt;a href=&quot;https:&#x2F;&#x2F;neo4j.com&#x2F;&quot;&gt;Neo4j&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For writing, I went with the most simple solution possible: an indented plain text document. &lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;Kingdoms
   Animalia
   Plantae
   Arthropoda
      Insects
   Fungi
   Porifera
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;the-result&quot;&gt;The result&lt;&#x2F;h3&gt;
&lt;p&gt;After some work, here&#x27;s what I came up with:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;user-images.githubusercontent.com&#x2F;3226564&#x2F;99435931-9199c280-28ef-11eb-8bb6-ef0b5450b92d.gif&quot;&#x2F;&gt;
    &lt;figcaption&gt;Jumping between nodes and different mind maps is easy!&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The UI is smart enough to show any relevant info related to the root node, so the more mind maps you write that include the query, the more data the UI will be able to connect. &lt;&#x2F;p&gt;
&lt;p&gt;Since it uses pretty standard web technologies, it works on my phone without any hiccups. &lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;user-images.githubusercontent.com&#x2F;3226564&#x2F;99438276-5e0c6780-28f2-11eb-8771-d2516ed86273.jpg&quot;&#x2F;&gt;
    &lt;figcaption&gt;The UI is fully functional on Chromium-based browsers on Android&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;I also added buttons to quickly go the dictionary definition or Wikipedia article related to the root node. &lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;user-images.githubusercontent.com&#x2F;3226564&#x2F;99437720-99f2fd00-28f1-11eb-9e89-f421b831e3a0.png&quot;&#x2F;&gt;
    &lt;figcaption&gt;Clicking on the globe icon leads to the Wikipedia article that best matches &quot;gauss&quot;&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;It&#x27;s hosted on Github Pages so you can try it out &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.com&#x2F;move-reminder&quot;&gt;here&lt;&#x2F;a&gt;. &lt;&#x2F;p&gt;
&lt;p&gt;The app is capable of downloading mind map definitions from the web so it should work for everyone. Please refer to help section for more info.&lt;&#x2F;p&gt;
&lt;p&gt;Do you have suggestions for this tool? If so please open an issue in its git repo! &lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>On data-driven programming</title>
          <pubDate>Wed, 02 Sep 2020 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/data-driven-programming/</link>
          <guid>https://denisidoro.github.io/posts/data-driven-programming/</guid>
          <description xml:base="https://denisidoro.github.io/posts/data-driven-programming/">&lt;figure class=&quot;medium-cover&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;t u v gh aj&quot; src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;11014&#x2F;0*43gbACYn_imJxYko&quot; srcSet=&quot; https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;0*43gbACYn_imJxYko 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;0*43gbACYn_imJxYko 552w,
    https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;0*43gbACYn_imJxYko 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;0*43gbACYn_imJxYko 700w&quot; sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Photo by
        &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com&#x2F;@aaronburden?utm_source=medium&amp;utm_medium=referral&quot;&gt;aaronburden&lt;&#x2F;a&gt;
        on &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com?utm_source=medium&amp;utm_medium=referral&quot;&gt;Unsplash&lt;&#x2F;a&gt; 
    &lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;If there’s one thing that &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Functional_programming&quot;&gt;functional programming&lt;&#x2F;a&gt; has taught me is &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=-6BsiVyC1kM&quot;&gt;the importance of values&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Say we’re part of a startup’s founding team and are responsible for designing and building the core of its backend services. At its core, our job is to extract, transform and load values.&lt;&#x2F;p&gt;
&lt;p&gt;In general, the backend services must:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;receive values from the mobile app. e.g., a JSON payload in a REST endpoint&lt;&#x2F;li&gt;
&lt;li&gt;perform pure logic over values. e.g., apply &lt;em&gt;f(balance, purchase)&lt;&#x2F;em&gt; → &lt;em&gt;new_balance&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;persist values in the database&lt;&#x2F;li&gt;
&lt;li&gt;and so on&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Everything else is overhead. &lt;a href=&quot;https:&#x2F;&#x2F;www.w3schools.com&#x2F;Java&#x2F;java_encapsulation.asp&quot;&gt;Getters and setters&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;springframework.guru&#x2F;gang-of-four-design-patterns&#x2F;&quot;&gt;Gang of Four patterns&lt;&#x2F;a&gt; and whatnot. Sure, these abstractions may allow you to scale code or team sizes. But they are overhead, still. They’re neither inherently part of our startup’s business model nor the reason the CEO decided to hire us.&lt;&#x2F;p&gt;
&lt;p&gt;I guess that this philosophy behind values as first-class citizens led to the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Code_as_data&quot;&gt;code as data&lt;&#x2F;a&gt; movement. Or &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Data-driven_programming&quot;&gt;data-driven programming&lt;&#x2F;a&gt;. Think of DSLs and configuration files. If we want to change our app’s behavior, there’s no need to launch IntellijJ and dive into the code: we can fiddle with some JSON-like file using a notepad app. The code parses it and acts accordingly. Profit.&lt;&#x2F;p&gt;
&lt;p&gt;The benefits of such approach are undeniable, but they introduce new problems. The main ones, that I’m gonna cover in this post, are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;entry barrier&lt;&#x2F;li&gt;
&lt;li&gt;no type-safety&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Let’s take a &lt;a href=&quot;https:&#x2F;&#x2F;kubernetes.io&#x2F;&quot;&gt;Kubernetes&lt;&#x2F;a&gt; config as an example:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;10a79e0d5b5591a5da733f07bcd25233&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;
    Is this code correct? Is there a typo? Should &lt;code&gt;containers&lt;&#x2F;code&gt; be a direct child of &lt;code&gt;spec&lt;&#x2F;code&gt; or &lt;code&gt;selector&lt;&#x2F;code&gt;? I know that &lt;code&gt;ports&lt;&#x2F;code&gt; has a &lt;code&gt;containerPort&lt;&#x2F;code&gt; field, but does it have any other one? If so, is it a string or a number?
&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The &lt;em&gt;entry barrier&lt;&#x2F;em&gt; is the time we need to take to understand this syntax. It’s a new language, in some sense. It’s good, old YAML but there are rules we need to understand prior to reading or writing such pieces of text.&lt;&#x2F;p&gt;
&lt;p&gt;By &lt;em&gt;no type-safety&lt;&#x2F;em&gt; I mean all the goodies we lose that we otherwise would have in an IDE for a strongly typed language. Autocomplete, Intellisense and references to previously declared values.&lt;&#x2F;p&gt;
&lt;p&gt;For Kubernetes &lt;a href=&quot;https:&#x2F;&#x2F;dockerlabs.collabnix.com&#x2F;kubernetes&#x2F;kubetools&#x2F;&quot;&gt;I bet there are&lt;&#x2F;a&gt; 3761 tools to make your life easier. But what about the DSLs we create?&lt;&#x2F;p&gt;
&lt;p&gt;To illustrate, let’s suppose our job is to create a DSL&#x2F;config file for a &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;movile-tech&#x2F;backend-driven-development-ios-d1c726f2913b&quot;&gt;backend-driven UI&lt;&#x2F;a&gt; framework. i.e., we want to develop a system that allows front-end applications with screens and flows to be built based on backend responses. &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=vuCfKjOwZdU&quot;&gt;Spotify does that&lt;&#x2F;a&gt;, by the way.&lt;&#x2F;p&gt;
&lt;p&gt;With this system, we theoretically only need one person to know the inner machinery of this code. But if a new intern wants to build a new screen, how will she know what to write in our DSL, in the first place?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;creating-an-editor-ui&quot;&gt;Creating an editor UI&lt;&#x2F;h3&gt;
&lt;p&gt;This is in my opinion the best approach. We could have a web editor with drag-and-drop elements, drop-downs and even a live preview window that shows us how the final result would be.&lt;&#x2F;p&gt;
&lt;p&gt;Discoverability is addressed by these UI elements that pop in front of us. Correctness is guaranteed by the UI itself — by preventing us from clicking on stuff that would result into an invalid file.&lt;&#x2F;p&gt;
&lt;p&gt;But this has some cons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;it’s expensive to write — in terms of time, at least&lt;&#x2F;li&gt;
&lt;li&gt;it requires a full-stack team&lt;&#x2F;li&gt;
&lt;li&gt;it doesn’t solve the problem with code review — in case the UI spits a file such as the Kubernetes one, the code review in general is limited to &lt;em&gt;LGTM&lt;&#x2F;em&gt; or 🙈, as in &lt;em&gt;I trust that you used the UI correctly and I trust that the UI is correct&lt;&#x2F;em&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;making-use-of-our-ides&quot;&gt;Making use of our IDEs&lt;&#x2F;h3&gt;
&lt;p&gt;What if we wrote a plugin to VSCode, for example, that somehow helps other developers write our DSLs? But wait, we’re already using a popular format such as JSON or YAML. We could instead write a plugin that helps users write JSONs that follows some convention.&lt;&#x2F;p&gt;
&lt;p&gt;But there are millions of other developers out there. I bet someone has already thought about this and took the time to do exactly what we want. I’m lazy and our job in the startup is to persist values in the database and whatnot. I, for one, don’t want to focus efforts into this.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out a solution does exist out there. The first page for the Google search &lt;em&gt;typed YAML&lt;&#x2F;em&gt; led me to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;crdoconnor&#x2F;strictyaml&quot;&gt;strictyaml&lt;&#x2F;a&gt;, which led me to &lt;a href=&quot;https:&#x2F;&#x2F;json-schema.org&#x2F;&quot;&gt;JSON Schema&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;By using a battle-tested solution we get everything for free plus support for all major IDEs.&lt;&#x2F;p&gt;
&lt;p&gt;There are a lot of articles on the Internet about the benefits and capabilities of JSON Schema, so I won’t bother you with details. I’ll simply demonstrate it with a GIF:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*Em1xMQgnAbMLMU9-jnAHAg.gif&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*Em1xMQgnAbMLMU9-jnAHAg.gif 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*Em1xMQgnAbMLMU9-jnAHAg.gif 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*Em1xMQgnAbMLMU9-jnAHAg.gif 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*Em1xMQgnAbMLMU9-jnAHAg.gif 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    

    
    &lt;figcaption&gt;The intern has just joined the company, but she already knows that someone, at some point in time, wrote a &lt;code&gt;billing_address&lt;&#x2F;code&gt; widget.&lt;&#x2F;figcaption&gt;
    

&lt;&#x2F;figure&gt;
&lt;p&gt;The schema used in this demo was extracted from &lt;a href=&quot;https:&#x2F;&#x2F;rjsf-team.github.io&#x2F;react-jsonschema-form&#x2F;&quot;&gt;react-jsonschema-form&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I know that having a popup window when we type sounds like a small benefit but, if you stop to think about it, the intern didn’t even have to leave the file buffer! She didn’t need to browse through the code implementation, or read documents — that are likely outdated or incomplete — or contact the people that contributed to the DSL.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;redhat-developer&#x2F;vscode-yaml&quot;&gt;There are ways&lt;&#x2F;a&gt; to extend this to YAML as well.&lt;&#x2F;p&gt;
&lt;p&gt;Oh, one suggestion: don’t write your JSON Schema &lt;em&gt;and&lt;&#x2F;em&gt; your code structs&#x2F;classes by hand. Let one generate the other so that there’s a single source of truth.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;using-data-as-code-as-data&quot;&gt;Using data as code (as data)&lt;&#x2F;h3&gt;
&lt;p&gt;The cool thing about having a DSL inside a JSON file is that in general it is parseable at runtime: we don’t need to deploy a new server instance and stop the previous one. We can &lt;code&gt;curl -d &amp;quot;@config.json&amp;quot; -XPOST localhost:3000&#x2F;apply&lt;&#x2F;code&gt; to update the app&#x27;s behavior.&lt;&#x2F;p&gt;
&lt;p&gt;But if we don’t need such dynamism — if e.g. in order to update this config file we need to open a PR in the same repository as the code, which leads to a new image build — then why not stay inside the code realm?&lt;&#x2F;p&gt;
&lt;p&gt;We’ll need to refactor packages a little bit so that, by convention, a given set of files will be considered as data, in contrast to implementation detail code. It seems like a small price to pay, though.&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a href=&quot;https:&#x2F;&#x2F;kotlinlang.org&#x2F;&quot;&gt;Kotlin&lt;&#x2F;a&gt; it would be &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@fabiomirgo&#x2F;kotlins-power-build-a-dsl-1fcf215b7bb0&quot;&gt;straightforward&lt;&#x2F;a&gt; to come up with a file such as this one:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;5932b86b92fc520c57fd7e07730f59eb&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;In IntelliJ, if you typed BillingAddress, an Intellisense window would show us all possible fields. If we forgot the street field, the editor would suggest us to add it.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;This is exactly what &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Kotlin&#x2F;anko&quot;&gt;Anko&lt;&#x2F;a&gt; does in order to build Android UIs, for example.&lt;&#x2F;p&gt;
&lt;p&gt;Chances are that — assuming we wrote this in Kotlin — we have other developers in the company already familiar with Kotlin, so they will feel at home with this syntax. No extra level of indirection introduced.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;on-data-driven-programming-925e450525e3&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Quick prototyping with a Golang REPL</title>
          <pubDate>Tue, 02 Jun 2020 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/golang-repl/</link>
          <guid>https://denisidoro.github.io/posts/golang-repl/</guid>
          <description xml:base="https://denisidoro.github.io/posts/golang-repl/">&lt;p&gt;I miss a REPL when coding in Golang. It turns out we can have a Go REPL connected to our IDE of choice.&lt;&#x2F;p&gt;
&lt;p&gt;After developing in &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;&quot;&gt;Clojure&lt;&#x2F;a&gt; for some time, I miss a &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Read%E2%80%93eval%E2%80%93print_loop&quot;&gt;REPL&lt;&#x2F;a&gt; when coding in &lt;a href=&quot;https:&#x2F;&#x2F;golang.org&#x2F;&quot;&gt;Golang&lt;&#x2F;a&gt;. It turns out we can have a Go REPL connected to our IDE of choice.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*PYNPylp2BS_XSKYIlNrXyw.gif&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*PYNPylp2BS_XSKYIlNrXyw.gif 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*PYNPylp2BS_XSKYIlNrXyw.gif 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*PYNPylp2BS_XSKYIlNrXyw.gif 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*PYNPylp2BS_XSKYIlNrXyw.gif 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;why-use-a-repl&quot;&gt;Why use a REPL?&lt;&#x2F;h3&gt;
&lt;p&gt;Because it allows rapid experimentation and gives feedbacks instantaneously. Will my complex regex work? Instead of double-checking everything let’s just eval one case. When selecting a substring is the last index exclusive or not? Instead of reading the documentation let’s just try it out with an example.&lt;&#x2F;p&gt;
&lt;p&gt;Data scientists usually do this with &lt;a href=&quot;https:&#x2F;&#x2F;jupyter.org&#x2F;&quot;&gt;Jupyter Notebooks&lt;&#x2F;a&gt;, for example, but this is a practice which still isn’t ubiquitous in the backend&#x2F;frontend&#x2F;devops scene.&lt;&#x2F;p&gt;
&lt;p&gt;For more use cases, please check one video about &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;results?search_query=repl+driven+development&quot;&gt;REPL driven development&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-to-configure-it&quot;&gt;How to configure it?&lt;&#x2F;h3&gt;
&lt;p&gt;First install &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;motemen&#x2F;gore&quot;&gt;gore&lt;&#x2F;a&gt;. Then integrate it with your IDE.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll highlight my configuration with VSCode but other setups should be similar.&lt;&#x2F;p&gt;
&lt;p&gt;I simply added the following keybinding to “vim.visualModeKeyBindings” and “vim.normalModeKeyBindings”:&lt;&#x2F;p&gt;
&lt;p&gt;This way, whenever I type &lt;code&gt;&amp;lt;space&amp;gt;+l+t&lt;&#x2F;code&gt; the highlighted text will be sent to the REPL session, provided that I run &lt;code&gt;gore&lt;&#x2F;code&gt; in VSCode’s terminal window. If there’s no selection, the current line will be sent.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;caveats&quot;&gt;Caveats&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;gore is somewhat slow. But still is faster than running an adhoc test case + &lt;code&gt;go test&lt;&#x2F;code&gt; for the same purpose or setting up an adhoc entrypoint + &lt;code&gt;go run&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;depending on the modules imported by your project it may not be trivial to replicate the same imports to your gore session.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;does-this-work-for-other-languages&quot;&gt;Does this work for other languages?&lt;&#x2F;h3&gt;
&lt;p&gt;Yes! Your shell is a REPL in some sense. When writing long &lt;code&gt;for&lt;&#x2F;code&gt;s or commands with many pipes this may come in handy as well.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;quick-prototyping-with-a-golang-repl-547703885bd8&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Using dictionaries in shell scripts</title>
          <pubDate>Mon, 23 Sep 2019 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/dictionaries-in-shell-scripts/</link>
          <guid>https://denisidoro.github.io/posts/dictionaries-in-shell-scripts/</guid>
          <description xml:base="https://denisidoro.github.io/posts/dictionaries-in-shell-scripts/">&lt;p&gt;Sometimes you need to script in bash. And it’ll probably be a pain in the neck. Dictionaries to the rescue!&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, chances are that, if you have ever written some scripts, you have already come up with something like this:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;5d98ee3cdf10c08c400a399798d4e63c&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Bash doesn’t allow returning two values so we need to return a string which represents multidimensional data, using &lt;code&gt;;&lt;&#x2F;code&gt; as delimiter.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;can-we-do-better&quot;&gt;Can we do better?&lt;&#x2F;h3&gt;
&lt;p&gt;I think so!&lt;&#x2F;p&gt;
&lt;p&gt;With a set of scripts such as &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;navi&#x2F;blob&#x2F;7be9353a41d5ae1e56ef60d4761863e73cef3d89&#x2F;src&#x2F;dict.sh&quot;&gt;this one&lt;&#x2F;a&gt;, we can write:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;070d7b1ac6cf8676465e4d3f85795b03&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;We’re using textual data to represent a dictionary&#x2F;map in this case.&lt;&#x2F;p&gt;
&lt;p&gt;The script isn’t smaller, but that’s not our main objective. We’re trying to achieve legibility.&lt;&#x2F;p&gt;
&lt;p&gt;If you only read &lt;code&gt;cut -d&#x27;;&#x27; -f1&lt;&#x2F;code&gt; you have no idea what&#x27;s going on unless you debug the code. If you read &lt;code&gt;dict::get foo&lt;&#x2F;code&gt; at least you can expect it to return something foo-like.&lt;&#x2F;p&gt;
&lt;p&gt;Also, it composes very nicely:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;6843d26ac9ed1fc38333f772844ceda1&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;There are more examples &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;navi&#x2F;blob&#x2F;7be9353a41d5ae1e56ef60d4761863e73cef3d89&#x2F;test&#x2F;dict_test.sh&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-not-json-or-yaml&quot;&gt;Why not JSON? Or YAML?&lt;&#x2F;h3&gt;
&lt;p&gt;If you’re sure that the platform which will run the script has something like &lt;a href=&quot;https:&#x2F;&#x2F;stedolan.github.io&#x2F;jq&#x2F;&quot;&gt;jq&lt;&#x2F;a&gt; then JSON is a good candidate!&lt;&#x2F;p&gt;
&lt;p&gt;But if you want the script to be extremely portable, then a custom solution is required. Besides, the core of the library has ~30 lines of code, anyway.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;dictionaries-in-shell-scripts-61d34e1c91c6&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Writing multi-module, monolithic apps with graph APIs</title>
          <pubDate>Mon, 01 Jul 2019 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/monolithic-apps-graph-apis/</link>
          <guid>https://denisidoro.github.io/posts/monolithic-apps-graph-apis/</guid>
          <description xml:base="https://denisidoro.github.io/posts/monolithic-apps-graph-apis/">&lt;figure class=&quot;medium-cover&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;t u v gh aj&quot; src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;11014&#x2F;0*sbUufEe2m6a0hwbN&quot; srcSet=&quot; https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;0*sbUufEe2m6a0hwbN 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;0*sbUufEe2m6a0hwbN 552w,
    https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;0*sbUufEe2m6a0hwbN 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;0*sbUufEe2m6a0hwbN 700w&quot; sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Honeycombs have many cells but they act together to fulfill the same purpose by
        &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com&#x2F;@matthew_t_rader?utm_source=medium&amp;utm_medium=referral&quot;&gt;matthew_t_rader&lt;&#x2F;a&gt;
        on &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com?utm_source=medium&amp;utm_medium=referral&quot;&gt;Unsplash&lt;&#x2F;a&gt; 
    &lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Last year, I talked about monolithic architecture that enables easy microservice splitting later.&lt;&#x2F;p&gt;
&lt;p&gt;After applying it for a large codebase, I’ve started using another approach: &lt;a href=&quot;https:&#x2F;&#x2F;zapier.com&#x2F;engineering&#x2F;graph-apis&#x2F;&quot;&gt;graph APIs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;recap&quot;&gt;Recap&lt;&#x2F;h3&gt;
&lt;p&gt;In a nutshell, I believe it’s fundamental to isolate different parts of an application into different modules, even if they are deployed as a single monolith at the end of the day.&lt;&#x2F;p&gt;
&lt;p&gt;Limiting the required cognitive load to navigate the code base is a must.&lt;&#x2F;p&gt;
&lt;p&gt;Writing scalable and readable code in the first shot is tough. If you have time and effort constraints (as I have for personal projects: I don’t want to spend hundreds of hours developing them), it’s even harder.&lt;&#x2F;p&gt;
&lt;p&gt;Unreadable, difficult to maintain code is a red flag. But if it works, is isolated and no one needs to look at it anymore, it’s less bad. Sure, one day it will stop working or will need to be updated, but until then people will have better context and know what works and what doesn’t. If it’s contained, it may even be plausible to rewrite it from scratch without huge costs.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;past-proposal&quot;&gt;Past proposal&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;microservice-size-and-splitting-dd9fc98a262e&quot;&gt;In my past article&lt;&#x2F;a&gt; I proposed this isolation via interfaces&#x2F;objects&#x2F;providers. I’ve been using it in &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;using-grafana-for-personal-financial-management-ac0e4d0cb43&quot;&gt;my personal financial platform&lt;&#x2F;a&gt; and it has been great!&lt;&#x2F;p&gt;
&lt;p&gt;I needed to change the provider for stock historical value and I only had to look at a single package, because there was no implementation details leakage to other parts of the code. Even if the codebase had 1MM LOC for all sorts of features, I knew that I (or anyone else) would only need to look at, and try to understand, ~50 LOC.&lt;&#x2F;p&gt;
&lt;p&gt;However, using these providers has some downsides:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;it-s-relatively-too-verbose&quot;&gt;It’s relatively too verbose&lt;&#x2F;h4&gt;
&lt;p&gt;One module of mine only had pure functions, summing up ~20 LOC. However, in order to isolate it, I had to create a &lt;code&gt;[defprotocol](https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.core&#x2F;defprotocol)&lt;&#x2F;code&gt;, a &lt;code&gt;[defrecord](https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.core&#x2F;defrecord)&lt;&#x2F;code&gt;, implement some lifecycle and so on, even though it was stateless.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;the-interfaces-don-t-scale-well&quot;&gt;The interfaces don’t scale well&lt;&#x2F;h4&gt;
&lt;p&gt;Having a &lt;code&gt;defprotocol&lt;&#x2F;code&gt; with &lt;code&gt;get-stocks&lt;&#x2F;code&gt; and &lt;code&gt;get-stock-history&lt;&#x2F;code&gt; is fine. But when you start pushing it towards &lt;code&gt;get-new-stocks-with-high-volatility&lt;&#x2F;code&gt;, things can get out of control.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;explicit-dependencies&quot;&gt;Explicit dependencies&lt;&#x2F;h4&gt;
&lt;p&gt;I wanted to get the historical value of all the investments available at my bank. One module was able to fetch my bank and get the name of the investments. Another one, which queries another API, was able to convert the name to an ID. Finally, a third module was able to get the history given an ID.&lt;&#x2F;p&gt;
&lt;p&gt;Where to put this composition? I wanted to make things transparent (the fact that my bank doesn’t expose the IDs shouldn’t leak to outer modules), so I made the bank module depend on the second one.&lt;&#x2F;p&gt;
&lt;p&gt;That worked. But in another flow the second module relied on the bank one. If we’re not careful enough, circular dependency may happen. This interdependency is difficult to reason about.&lt;&#x2F;p&gt;
&lt;p&gt;Another solution is to have a higher-level module that knows everyone else. A sort of &lt;a href=&quot;https:&#x2F;&#x2F;alexandreesl.com&#x2F;2016&#x2F;03&#x2F;18&#x2F;backend-for-frontends-a-microservices-pattern&#x2F;&quot;&gt;BFF&#x2F;façade&lt;&#x2F;a&gt;. That’s better, but it still needs to know that, e.g., the bank module doesn’t expose the IDs so and additional query is needed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;new-approach&quot;&gt;New approach&lt;&#x2F;h3&gt;
&lt;p&gt;What if we could have looser dependencies? What if no one needed to know how to surgically compose different modules to deliver a single response? With Graph APIs we can.&lt;&#x2F;p&gt;
&lt;p&gt;For &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;&quot;&gt;Clojure&lt;&#x2F;a&gt;, you can use the awesome &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;wilkerlucio&#x2F;pathom&quot;&gt;Pathom library&lt;&#x2F;a&gt;, which will do all the heavy work for you.&lt;&#x2F;p&gt;
&lt;p&gt;To implement my example, the bank module could register a resolver like the one below (I’m stripping away some boilerplate for readability):&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;e259a93747d73caeb584feb07820f925&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;For the second module:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;c6803b999adae0fb703268b7bc5cd2be&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Finally, for the last module:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;0860d8729e08705a6785f296032494c9&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I now have a decentralized system. The graph parser knows by itself that, for each element in &lt;code&gt;:bank&#x2F;investments&lt;&#x2F;code&gt;, it needs to go from &lt;code&gt;:investment&#x2F;name&lt;&#x2F;code&gt; to &lt;code&gt;:investment&#x2F;id&lt;&#x2F;code&gt; then &lt;code&gt;:investment&#x2F;history&lt;&#x2F;code&gt; and how to resolve it.&lt;&#x2F;p&gt;
&lt;p&gt;As for the namespace organization, each module has the following structure:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;64a9111749ced25ac06a6ebba0e6c0a1&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;logic&#x2F;&lt;&#x2F;code&gt; contains pure functions&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;http&#x2F;&lt;&#x2F;code&gt; and &lt;code&gt;db&#x2F;&lt;&#x2F;code&gt; are one of many ports in the &lt;a href=&quot;https:&#x2F;&#x2F;dzone.com&#x2F;articles&#x2F;hexagonal-architecture-what-is-it-and-how-does-it&quot;&gt;hexagonal architecture&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;graph&#x2F;&lt;&#x2F;code&gt; has the resolvers above&lt;&#x2F;li&gt;
&lt;li&gt;when the resolvers get complicated, helper functions are extracted to &lt;code&gt;controllers&#x2F;&lt;&#x2F;code&gt;, which orchestrate function calls&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;definition.clj&lt;&#x2F;code&gt; is a file I already had to bootstrap the server, but has now been extended to each module&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;module-definition&quot;&gt;Module definition&lt;&#x2F;h3&gt;
&lt;p&gt;An example of &lt;code&gt;definition.clj&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;f44af804265590d3aa5e51689146076a&amp;#x2F;raw&amp;#x2F;5b16fe38fbbdfbf7d0d77ad27c5dcd67ac6a07cb&amp;#x2F;definition.clj&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:http&lt;&#x2F;code&gt; and &lt;code&gt;:bank&lt;&#x2F;code&gt; have immutable data that can be propagated to other components&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;:components&lt;&#x2F;code&gt; has some dependency-injection declarations&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;:entry-point&lt;&#x2F;code&gt; is the function to be called in case this module is to be used standalone or as the routing hub&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Finally, I have a &lt;code&gt;[defmethod](https:&#x2F;&#x2F;clojuredocs.org&#x2F;clojure.core&#x2F;defmethod)&lt;&#x2F;code&gt; that’s able to reduce a config vector into a final, single config. For &lt;code&gt;:http&lt;&#x2F;code&gt;, for example, it merges all the bookmarks; for &lt;code&gt;:resolvers&lt;&#x2F;code&gt; it concatenates all vectors; for &lt;code&gt;:entry-point&lt;&#x2F;code&gt;, it keeps the last one.&lt;&#x2F;p&gt;
&lt;p&gt;In the end, to start the system, I call:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;5a1a4b9b7db59a54e1d9afc2c6f34001&amp;#x2F;raw&amp;#x2F;75b2f2e804988c0c49cb3560ef82eabcd39db059&amp;#x2F;boot.clj&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;&lt;code&gt;(:entry-point rest-server.definition&#x2F;config)&lt;&#x2F;code&gt;, for instance, is what spawns the server listening on port 80. If I want to use the system as a CLI, there&#x27;s no need to spawn the server and curl it. I could simply swap the last config with &lt;code&gt;cli.definition&#x2F;config&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;splitting-into-microservices-libraries&quot;&gt;Splitting into microservices&#x2F;libraries&lt;&#x2F;h3&gt;
&lt;p&gt;If for any reason the need arises, deploying a module as a microservice is trivial from a code perspective.&lt;&#x2F;p&gt;
&lt;p&gt;For the new microservice:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;clone the monolith into a different repository&lt;&#x2F;li&gt;
&lt;li&gt;remove all undesired modules&lt;&#x2F;li&gt;
&lt;li&gt;edit the &lt;code&gt;bootstrap-app!&lt;&#x2F;code&gt; call accordingly&lt;&#x2F;li&gt;
&lt;li&gt;expose the resolvers it has (Pathom call this &lt;code&gt;index&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For the original monolith:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;remove the module folder except &lt;code&gt;definition.clj&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;change &lt;code&gt;definition.clj&lt;&#x2F;code&gt; in such a way that the monolith is able to merge its &lt;code&gt;index&lt;&#x2F;code&gt; with the one in the new microservice (Pathom allows merging &lt;code&gt;index&lt;&#x2F;code&gt;es as well)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Steps for extracting a module to a library would be very similar (in case the module only has pure logic, for example).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;downsides-of-the-new-approach&quot;&gt;Downsides of the new approach&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;for my particular case, having the graph find out the edge traversal is perfectly fine, but if I wanted maximum performance, calling the functions directly could be faster and less resource intensive (pending benchmark, though)&lt;&#x2F;li&gt;
&lt;li&gt;since edges are loose, it’s difficult to &lt;code&gt;find usages&lt;&#x2F;code&gt; given a function or field. The IDE won&#x27;t be able to know in what flows a resolver may be used&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;This has given my code huge scalability at low costs and I’m fine with the downsides I could think of.&lt;&#x2F;p&gt;
&lt;p&gt;At the moment I have no open-source code to show, but if you’re really interested, contact me and we can work something out.&lt;&#x2F;p&gt;
&lt;p&gt;If you’ve liked the ideas highlighted here regarding graph APIs, please check &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=r3zywlNflJI&quot;&gt;this talk&lt;&#x2F;a&gt; from Pathom’s author.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;writing-multi-module-monolithic-apps-with-graph-apis-1c095cdaccdf&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Using Grafana for personal financial management</title>
          <pubDate>Thu, 04 Apr 2019 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/grafana-personal-finance/</link>
          <guid>https://denisidoro.github.io/posts/grafana-personal-finance/</guid>
          <description xml:base="https://denisidoro.github.io/posts/grafana-personal-finance/">&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*tfGEp0dvIJ1_L3-D0rJSdw.jpeg&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*tfGEp0dvIJ1_L3-D0rJSdw.jpeg 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*tfGEp0dvIJ1_L3-D0rJSdw.jpeg 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*tfGEp0dvIJ1_L3-D0rJSdw.jpeg 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*tfGEp0dvIJ1_L3-D0rJSdw.jpeg 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;One of the dashboards provided by the system&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;This post is intended to be a brain dump with quick highlights of some technologies. If you’re somewhat familiar with the stack I’ve used, hopefully there will be something useful for you to learn or to base on. It will be fast paced.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;objective&quot;&gt;Objective&lt;&#x2F;h3&gt;
&lt;p&gt;To develop an investment tracker with the following requirements:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;everything must be determined by a plain-text file;&lt;&#x2F;li&gt;
&lt;li&gt;the only recurring input needed is sparse balance data;&lt;&#x2F;li&gt;
&lt;li&gt;it must infer data as good as possible;&lt;&#x2F;li&gt;
&lt;li&gt;it must maintain historic data;&lt;&#x2F;li&gt;
&lt;li&gt;it must draw beautiful visualizations.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In other words, if I write down today the balance of some investments of mine, and 1 month from now I write down the balance of other investments, the system should be able to interpolate data so that it can plot smooth, realistic curves.&lt;&#x2F;p&gt;
&lt;p&gt;This is different from a ledger system, for which there are plenty of open source solutions.&lt;&#x2F;p&gt;
&lt;p&gt;It could be done with some Excel wizardry, I suppose. But I didn’t want to learn that much of Excel.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;file-syntax&quot;&gt;File syntax&lt;&#x2F;h3&gt;
&lt;p&gt;I’ve decided to use the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edn-format&#x2F;edn&quot;&gt;edn format&lt;&#x2F;a&gt;, because I was determined to code the system in &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;&quot;&gt;Clojure&lt;&#x2F;a&gt;. Here is some simplified, mock data:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;d9c38c04feb8e69c27d706adf435043a&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;It has as little information as possible yet it still is human-readable. Nice!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;internalizing-data&quot;&gt;Internalizing data&lt;&#x2F;h3&gt;
&lt;p&gt;The first step was to convert the file contents to internal models with namespaced keys. In particular, I converted date strings to a numeric format so that I could apply traditional math over it. After some maps and reduces, we have a collection of elements such as:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;8ffe9d8009d712a500ba1fbe79c8e93c&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Initially, I had written functions for integrals, derivatives, curve aggregation and so on. But I was reinventing the wheel. Now, I’m leveraging the robustness of time-series ecosystems.&lt;&#x2F;p&gt;
&lt;p&gt;The competitors were &lt;a href=&quot;https:&#x2F;&#x2F;prometheus.io&#x2F;&quot;&gt;Prometheus&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;www.influxdata.com&#x2F;&quot;&gt;InfluxDB&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;graphiteapp.org&#x2F;&quot;&gt;Graphite&lt;&#x2F;a&gt;. As of the time of writing, there’s no way to fetch past data into Prometheus. InfluxDB has too much SQL for my taste. Graphite was the chosen one, then.&lt;&#x2F;p&gt;
&lt;p&gt;With a simple pure function, we can export our internal models as Graphite-compatible data:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;citigroup.rf.cdb_paribas 1554325794 1000.00
citigroup.rf.cdb_paribas 1554378794 1050.33
citigroup.rf.cdb_paribas 1564325312 1097.44
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we can perform queries such as &lt;code&gt;sumSeries(some.bucket.*)&lt;&#x2F;code&gt;, instead of hand crafting it directly in our code.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;visualizing-balances&quot;&gt;Visualizing balances&lt;&#x2F;h3&gt;
&lt;p&gt;Graphite has a built-in renderer for data:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*nLHgAwVpSW9veGmMp_-Rbg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*nLHgAwVpSW9veGmMp_-Rbg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*nLHgAwVpSW9veGmMp_-Rbg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*nLHgAwVpSW9veGmMp_-Rbg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*nLHgAwVpSW9veGmMp_-Rbg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;This is really ugly&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;But we aren’t in the 90s anymore. I wanted to use something easier on the eyes: &lt;a href=&quot;https:&#x2F;&#x2F;grafana.com&#x2F;&quot;&gt;Grafana&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, Grafana works out of the box with Graphite, so it was a piece of cake to build beautiful dashboards:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*FFPJW-tIF3967y3BcEoSoQ.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*FFPJW-tIF3967y3BcEoSoQ.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*FFPJW-tIF3967y3BcEoSoQ.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*FFPJW-tIF3967y3BcEoSoQ.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*FFPJW-tIF3967y3BcEoSoQ.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;The main dashboard&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;tabular-and-scalar-data&quot;&gt;Tabular and scalar data&lt;&#x2F;h3&gt;
&lt;p&gt;Even though piping data to Graphite made my life easier, it limited possibilities. There’s no simple way to display in a table all the info for yielding investments. Or adding a single stat with the next investment maturity date.&lt;&#x2F;p&gt;
&lt;p&gt;It became clear I had to add a second data source. Considering that some steps before I already had all the data in Clojure code, I decided to upgrade the Clojure scripts to an HTTP server.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*274rN8NGm-TdaKmZXEwquA.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*274rN8NGm-TdaKmZXEwquA.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*274rN8NGm-TdaKmZXEwquA.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*274rN8NGm-TdaKmZXEwquA.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*274rN8NGm-TdaKmZXEwquA.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;InfluxDB can’t provide this tabular data&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Grafana has support for &lt;a href=&quot;https:&#x2F;&#x2F;grafana.com&#x2F;plugins&#x2F;grafana-simple-json-datasource&quot;&gt;JSON APIs as data source&lt;&#x2F;a&gt;. Its serialization format is different from Graphite’s but I only had to write yet another pure, adapter function to get a result like this:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;f2cb104d0c47aec5e93b81d84568ef67&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;h3 id=&quot;extras&quot;&gt;Extras&lt;&#x2F;h3&gt;
&lt;p&gt;After finishing all the groundwork, it became easy peasy to add new features, such as consuming external APIs for displaying stock values or currency history, to name a few.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*7TJ_w-ThefC6aVi4Yy4J9g.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*7TJ_w-ThefC6aVi4Yy4J9g.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*7TJ_w-ThefC6aVi4Yy4J9g.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*7TJ_w-ThefC6aVi4Yy4J9g.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*7TJ_w-ThefC6aVi4Yy4J9g.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Currencies and stock information scraped from third-party APIs&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;rosebud&#x2F;blob&#x2F;e68671b&#x2F;server&#x2F;resources&#x2F;example_log.edn&quot;&gt;This&lt;&#x2F;a&gt; is a complete edn file specifying what currencies and stocks we should keep track of.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deploying&quot;&gt;Deploying&lt;&#x2F;h3&gt;
&lt;p&gt;The easiest way to install Grafana on OSX is to use &lt;a href=&quot;https:&#x2F;&#x2F;brew.sh&#x2F;&quot;&gt;brew&lt;&#x2F;a&gt;. Then you run &lt;code&gt;brew service start&lt;&#x2F;code&gt; and have to remember to stop it because the service persists even after a reboot. Then there&#x27;s Graphite and Clojure.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe there’s a better way to do it but I rage quit it and started using &lt;a href=&quot;https:&#x2F;&#x2F;www.docker.com&#x2F;&quot;&gt;docker&lt;&#x2F;a&gt; for everything instead.&lt;&#x2F;p&gt;
&lt;p&gt;By mounting volumes, we can start Grafana pre-configured with the default data sources and dashboards in a such way that we don’t have to set it up manually everytime.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;adapting-it-to-your-needs&quot;&gt;Adapting it to your needs&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;rosebud&quot;&gt;The code is available on Github&lt;&#x2F;a&gt; for you to fork. It’s a little bit oriented to my needs and the Brazilian financial system. However, smalls adjustments should be enough to adapt it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h3&gt;
&lt;p&gt;I’m very happy with the result because I was able to develop it quickly, without having to learn new tools. &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;microservice-size-and-splitting-dd9fc98a262e&quot;&gt;The way the namespaces and models are organized&lt;&#x2F;a&gt; facilitates swapping backends or splitting the code into microservices, if the future demands it.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;using-grafana-for-personal-financial-management-ac0e4d0cb43&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>On microservice splitting and code refactoring</title>
          <pubDate>Sun, 06 Jan 2019 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/microservice-splitting-code-refactoring/</link>
          <guid>https://denisidoro.github.io/posts/microservice-splitting-code-refactoring/</guid>
          <description xml:base="https://denisidoro.github.io/posts/microservice-splitting-code-refactoring/">&lt;figure class=&quot;medium-cover&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;t u v gh aj&quot; src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;11014&#x2F;0*YzywaYKyaA6w-HcY&quot; srcSet=&quot; https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;0*YzywaYKyaA6w-HcY 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;0*YzywaYKyaA6w-HcY 552w,
    https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;0*YzywaYKyaA6w-HcY 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;0*YzywaYKyaA6w-HcY 700w&quot; sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Photo by
        &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com&#x2F;@leliejens?utm_source=medium&amp;utm_medium=referral&quot;&gt;leliejens&lt;&#x2F;a&gt;
        on &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com?utm_source=medium&amp;utm_medium=referral&quot;&gt;Unsplash&lt;&#x2F;a&gt; 
    &lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Let’s say that you work for a company that offers a platform for personal finance management. You were requested to add a feature whose input from the customer is a list of company × amount of stocks × investment date and the output is the customer’s total balance for a given point in time.&lt;&#x2F;p&gt;
&lt;p&gt;How big or small should the microservice(s) be?&lt;&#x2F;p&gt;
&lt;p&gt;At one end of the spectrum, you have a single &lt;a href=&quot;https:&#x2F;&#x2F;www.thoughtworks.com&#x2F;insights&#x2F;blog&#x2F;monoliths-are-bad-design-and-you-know-it&quot;&gt;monolith&lt;&#x2F;a&gt;, with scalability issues.&lt;&#x2F;p&gt;
&lt;p&gt;At the other end, you have one service for:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;stock value scraping (&lt;code&gt;stockScraper&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;company info store (&lt;code&gt;companyStore&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;stock value history store (&lt;code&gt;stockHistory&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;customer input stock store (&lt;code&gt;stockStore&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This approach gives us a nice separation of concerns and makes it easy to develop and scale each part independently. However, it takes more time to have an &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Minimum_viable_product&quot;&gt;MVP&lt;&#x2F;a&gt; this way, and &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Time_to_market&quot;&gt;time to market&lt;&#x2F;a&gt; may be crucial. And it costs more, as well, because of overheads (such as multiple JVMs, databases, message streaming, etc).&lt;&#x2F;p&gt;
&lt;p&gt;Given your resource constraints, maybe it makes sense to start as a single service (&lt;code&gt;newFeature&lt;&#x2F;code&gt;). But what if you want or need to split it later?&lt;&#x2F;p&gt;
&lt;p&gt;Here are my tips for designing a service easy to refactor, at a low cost during development.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;0-divide-your-application-into-layers&quot;&gt;0. Divide your application into layers&lt;&#x2F;h3&gt;
&lt;p&gt;You can choose from &lt;a href=&quot;https:&#x2F;&#x2F;dzone.com&#x2F;articles&#x2F;onion-architecture-is-interesting&quot;&gt;onion&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;dzone.com&#x2F;articles&#x2F;layered-architecture-is-good&quot;&gt;layered&lt;&#x2F;a&gt;, and &lt;a href=&quot;https:&#x2F;&#x2F;dzone.com&#x2F;articles&#x2F;hexagonal-architecture-is-powerful&quot;&gt;hexagonal&lt;&#x2F;a&gt; architectures, among others. As always, prioritize immutability, state management, pure logic extraction and all the other best practices.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-don-t-be-afraid-of-using-many-namespaces&quot;&gt;1. Don’t be afraid of using many namespaces&lt;&#x2F;h3&gt;
&lt;p&gt;Don’t keep all namespaces (packages, files) centralized in a root one. Instead of &lt;code&gt;newFeature.logic.stockScraper&lt;&#x2F;code&gt;, use &lt;code&gt;stockScraper.logic&lt;&#x2F;code&gt;. Have you created a pure function that may be useful for both &lt;code&gt;stockScraper&lt;&#x2F;code&gt; and &lt;code&gt;stockStore&lt;&#x2F;code&gt;? Put it inside &lt;code&gt;stock.logic&lt;&#x2F;code&gt;. Have you written a function for linear interpolation? This math concept isn&#x27;t restricted to a specific feature so it doesn&#x27;t have to be inside &lt;code&gt;newFeature.logic.math&lt;&#x2F;code&gt;, for example. Put it inside &lt;code&gt;common.math&lt;&#x2F;code&gt;. This way you aren&#x27;t tempted to put &lt;code&gt;linearInterpolate&lt;&#x2F;code&gt; and &lt;code&gt;stockSum&lt;&#x2F;code&gt; in the same file.&lt;&#x2F;p&gt;
&lt;p&gt;When the time comes for service splitting, you can deploy the &lt;code&gt;common&lt;&#x2F;code&gt; and &lt;code&gt;stock&lt;&#x2F;code&gt; folders as libraries and start the &lt;code&gt;stockScraper&lt;&#x2F;code&gt; service by cut and pasting the respective folder. Much easier than traversing all the codebase later to extract everything you need!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-import-non-common-namespaces-with-care&quot;&gt;2. Import non-common namespaces with care&lt;&#x2F;h3&gt;
&lt;p&gt;Does &lt;code&gt;stockHistory&lt;&#x2F;code&gt; have to import &lt;code&gt;stockScraper&lt;&#x2F;code&gt; a lot? Can&#x27;t it be minimized? Remember that when splitting this will translate into HTTP calls or message streaming. Try to at least isolate this dependency into a single namespace, which will be the precursor of the API between the two. This step is the most subjective, because it can quickly introduce overheads in the codebase for an MVP. You must consider the trade-offs.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-isolate-implementation-details-into-higher-level-components&quot;&gt;3. Isolate implementation details into higher level components&lt;&#x2F;h3&gt;
&lt;p&gt;Let’s now implement the handler for the endpoint that returns information about all the companies a customer has stocks from. One common approach is the following (we’re exposing components via dependency injection and middlewares):&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;d77a550d25bd6a77d35b5b57d40cf4b8&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;After splitting our service, not all data will be available via the &lt;code&gt;db&lt;&#x2F;code&gt; argument, of course. We&#x27;ll need to make some HTTP calls. &lt;code&gt;customerCompaniesHandler&lt;&#x2F;code&gt; will have to get an HTTP component and pass it to &lt;code&gt;getCustomerCompanies&lt;&#x2F;code&gt;, which will propagate it to the domain-specific helper functions and so on...&lt;&#x2F;p&gt;
&lt;p&gt;But why does the handler have to know this in the first place? We have divided our application into layers and, more importantly, we have already separated our code into &lt;code&gt;companyStore&lt;&#x2F;code&gt;, &lt;code&gt;stockStore&lt;&#x2F;code&gt; and other namespaces. Yet, we clearly have an implementation detail leak such that all our higher level, integration API code has to know about low level dependency management. These layers should be agnostic to where we store the &lt;code&gt;company&lt;&#x2F;code&gt; entity...&lt;&#x2F;p&gt;
&lt;p&gt;Ideally, our handler should only know that it has to fetch data that depends on the &lt;code&gt;stock&lt;&#x2F;code&gt; and &lt;code&gt;company&lt;&#x2F;code&gt; entities. What if we created a component that abstracts this away, per entity? Let&#x27;s call one of them as &lt;code&gt;CompanyRepository&lt;&#x2F;code&gt; (you can come up with the name you like):&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;335efcab949ddaa3f40c78a9fec4666e&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;You can skip the interface&#x2F;protocol definition if you’re not into that. The important thing is to avoid leaking lower level components.&lt;&#x2F;p&gt;
&lt;p&gt;After adding this component to our dependency graph, our code would become:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;36dd706464e5cbb223fd6debdfba24a9&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;For the service split, we could create:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;36dd706464e5cbb223fd6debdfba24a9&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;The only thing we need to do is update the dependency graph! All the rest of the code remains the same.&lt;&#x2F;p&gt;
&lt;p&gt;As a plus, we can move &lt;code&gt;CompanyDbRepository&lt;&#x2F;code&gt; to &lt;code&gt;companyStore&lt;&#x2F;code&gt;. Code reuse!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wrap-up&quot;&gt;Wrap up&lt;&#x2F;h3&gt;
&lt;p&gt;Suppose you’re absolutely certain that you won’t have to split your recently created microservice or that the process will be easy enough such that you won’t curse your past self. Then, you may not see much value in my article.&lt;&#x2F;p&gt;
&lt;p&gt;However, this isn’t just about spatial organization or isolation. It’s about making domains arguably easier to reason about. Establishing higher level boundaries reduces the cognitive requirement to fiddle with a codebase. You only need to traverse the code as deep as required. I find it easier to handle new abstractions than reaching a mental stack overflow when browsing lines of code.&lt;&#x2F;p&gt;
&lt;p&gt;And even if you decide to keep a single service, it’s much easier for a newcomer to improve &lt;code&gt;stockParser&lt;&#x2F;code&gt;, for example, if he or she only has to read a root namespace and not the whole microservice code.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, no one is able to perfectly measure the ideal microservice size, because that varies in time, the company size, the feature success and many other factors. So it’s nice to be flexible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;clojure-sidenotes&quot;&gt;Clojure sidenotes&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;duct-framework&#x2F;duct&quot;&gt;Duct&lt;&#x2F;a&gt; follows this pattern and calls it boundary.&lt;&#x2F;li&gt;
&lt;li&gt;You can skip the component creation altogether and define boundaries with resolvers using a library such as &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;wilkerlucio&#x2F;pathom&quot;&gt;Pathom&lt;&#x2F;a&gt;: you define a graph edge that enables you to go from a &lt;code&gt;customerId&lt;&#x2F;code&gt; to a &lt;code&gt;company&lt;&#x2F;code&gt;, for example.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;front-end-sidenote&quot;&gt;Front-end sidenote&lt;&#x2F;h3&gt;
&lt;p&gt;Even though I focused on the back-end, this applies to front-end as well. Why make the &lt;code&gt;CompanyDetailsPage&lt;&#x2F;code&gt; receive an HTTP component and perform a request? It can simply receive a &lt;code&gt;CompanyRepository&lt;&#x2F;code&gt; and you can call &lt;code&gt;repository.getDetails()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;microservice-size-and-splitting-dd9fc98a262e&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Moving from Medium</title>
          <pubDate>Mon, 01 Oct 2018 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/moving-from-medium/</link>
          <guid>https://denisidoro.github.io/posts/moving-from-medium/</guid>
          <description xml:base="https://denisidoro.github.io/posts/moving-from-medium/">&lt;p&gt;I plan to stop posting on &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&quot;&gt;Medium&lt;&#x2F;a&gt; and start posting on &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;&quot;&gt;my own blog&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It uses zola under the hood and will allow me to write posts faster and with more features.&lt;&#x2F;p&gt;
&lt;p&gt;Hopefully I&#x27;ll be able to post more frequently.&lt;&#x2F;p&gt;
&lt;p&gt;The website includes an RSS feed as well.&lt;&#x2F;p&gt;
&lt;p&gt;Again, if you want to check new posts, &lt;a href=&quot;https:&#x2F;&#x2F;denisidoro.github.io&#x2F;&quot;&gt;click here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;new-posts-moving-from-medium-f4e9ae4acc19&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Using Clojure + GraalVM for shell scripting</title>
          <pubDate>Fri, 14 Sep 2018 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/clojure-graalvm-shell-scripting/</link>
          <guid>https://denisidoro.github.io/posts/clojure-graalvm-shell-scripting/</guid>
          <description xml:base="https://denisidoro.github.io/posts/clojure-graalvm-shell-scripting/">&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*gJm3BZcNrKoPOa2QO69p3Q.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*gJm3BZcNrKoPOa2QO69p3Q.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*gJm3BZcNrKoPOa2QO69p3Q.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*gJm3BZcNrKoPOa2QO69p3Q.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*gJm3BZcNrKoPOa2QO69p3Q.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Part of a shell script written in Clojure&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;&#x2F;h3&gt;
&lt;p&gt;Objective: develop a shell script to ease the writing of XML files.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s say that the output to be generated is:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;xml&quot; class=&quot;language-xml &quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;grid&amp;gt;   
  &amp;lt;row&amp;gt;   
     &amp;lt;label id=&amp;quot;info&amp;quot; &amp;#x2F;&amp;gt;   
     &amp;lt;text&amp;gt;Now playing&amp;lt;&amp;#x2F;text&amp;gt;   
  &amp;lt;&amp;#x2F;row&amp;gt;   
  &amp;lt;row&amp;gt;   
     &amp;lt;button onTap=&amp;quot;mute&amp;quot; &amp;#x2F;&amp;gt;   
  &amp;lt;&amp;#x2F;row&amp;gt;   
&amp;lt;&amp;#x2F;grid&amp;gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’d like to write it as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;clojure&quot; class=&quot;language-clojure &quot;&gt;&lt;code class=&quot;language-clojure&quot; data-lang=&quot;clojure&quot;&gt;[:grid [:row [:label {:id :info}]   
             [:text &amp;quot;Now playing&amp;quot;]]   
       [:row [:button :onTap &amp;quot;mute&amp;quot;]]]
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By the way, this syntax is a subset of &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;edn-format&#x2F;edn&quot;&gt;EDN&lt;&#x2F;a&gt; and called &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;weavejester&#x2F;hiccup&quot;&gt;hiccup&lt;&#x2F;a&gt;, used by frameworks such as &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Day8&#x2F;re-frame&quot;&gt;re-frame&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This way, in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;dotfiles&quot;&gt;my dotfiles&lt;&#x2F;a&gt;, I can store, versionate and compose my configs as hiccup files and not as XML ones.&lt;&#x2F;p&gt;
&lt;p&gt;This is no simple task for Bash and would feel natural to use &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;&quot;&gt;Clojure(Script)&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;using-clojurescript&quot;&gt;Using Clojurescript&lt;&#x2F;h3&gt;
&lt;p&gt;Until some months ago, I would write this script using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anmonteiro&#x2F;lumo&quot;&gt;lumo&lt;&#x2F;a&gt;, a cross-platform, standalone ClojureScript environment.&lt;&#x2F;p&gt;
&lt;p&gt;It runs on Node.js and the V8 JavaScript engine. Also, the scripts are relatively fast: a “hello world” takes ~1s to run without caching on my machine; with caching enabled, ~0.3s.&lt;&#x2F;p&gt;
&lt;p&gt;You can find example scripts and helpers for lumo &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;dotfiles&#x2F;tree&#x2F;ce5cfac70858966687986614443a7e805b60df76&#x2F;scripts&#x2F;clojure&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;using-clojure&quot;&gt;Using Clojure&lt;&#x2F;h3&gt;
&lt;p&gt;With the advent of &lt;a href=&quot;https:&#x2F;&#x2F;clojure.org&#x2F;guides&#x2F;deps_and_cli&quot;&gt;Clojure CLI&lt;&#x2F;a&gt; I stopped using lumo and I’m simply using the &lt;code&gt;clj&lt;&#x2F;code&gt; command now.&lt;&#x2F;p&gt;
&lt;p&gt;Migrating from lumo to clj is &lt;a href=&quot;https:&#x2F;&#x2F;clojurescript.org&#x2F;about&#x2F;differences&quot;&gt;trivial&lt;&#x2F;a&gt; and gives us more features, such as better support for macros and multithreading.&lt;&#x2F;p&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clojure&#x2F;tools.deps.alpha&quot;&gt;tools.deps&lt;&#x2F;a&gt; for dependency graph expansion and using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;RickMoynihan&#x2F;lein-tools-deps&quot;&gt;lein-tools-deps&lt;&#x2F;a&gt; for &lt;a href=&quot;https:&#x2F;&#x2F;leiningen.org&#x2F;&quot;&gt;leiningen&lt;&#x2F;a&gt; compatibility.&lt;&#x2F;p&gt;
&lt;p&gt;The downside is the startup time: a simple “hello world” takes around ~2.5s and my XML→hiccup script, which depends on some libraries, needs more than 3s to finish.&lt;&#x2F;p&gt;
&lt;p&gt;This is fine for one-time only scripts but if I call a clj script in a Bash for-loop I’ll probably want to grab some coffee while it runs.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;speeding-up-with-graalvm&quot;&gt;Speeding up with GraalVM&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.graalvm.org&#x2F;&quot;&gt;GraalVM&lt;&#x2F;a&gt; is a universal virtual machine for running applications written in JavaScript, Ruby, JVM-based languages and more.&lt;&#x2F;p&gt;
&lt;p&gt;By using &lt;a href=&quot;https:&#x2F;&#x2F;www.graalvm.org&#x2F;docs&#x2F;reference-manual&#x2F;aot-compilation&#x2F;&quot;&gt;AOT compilation&lt;&#x2F;a&gt; we can compile our clj scripts to native code, which doesn’t rely on the JVM. The benefit? Let’s compare startup times:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;λ echo &amp;#x27;[:table]&amp;#x27; | time clj -m xml 
&amp;lt;table &amp;#x2F;&amp;gt; 
clj -m xml 9.88s user 0.68s system 293% cpu 3.593 total 

λ echo &amp;#x27;[:table]&amp;#x27; | time native-binary 
&amp;lt;table &amp;#x2F;&amp;gt; 
native-binary 0.01s user 0.01s system 79% cpu 0.019 total
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s 200x faster!&lt;&#x2F;p&gt;
&lt;p&gt;In addition, I can simply copy&#x2F;paste this binary to any other machine with the same architecture + OS and it will work regardless of JVM or Node.js being installed or not.&lt;&#x2F;p&gt;
&lt;p&gt;Example scripts and helpers for clj and GraalVM can be found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;dotfiles&#x2F;tree&#x2F;c4f656d6c83f34c106afc61f44568fcd4e3ea1b9&#x2F;scripts&#x2F;clojure&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For other people’s experiences, click &lt;a href=&quot;https:&#x2F;&#x2F;www.innoq.com&#x2F;en&#x2F;blog&#x2F;native-clojure-and-graalvm&#x2F;&quot;&gt;here&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;www.astrecipes.net&#x2F;blog&#x2F;2018&#x2F;07&#x2F;20&#x2F;cmd-line-apps-with-clojure-and-graalvm&#x2F;&quot;&gt;there&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;using-clojure-graalvm-for-shell-scripting-da0bcc8955d4&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Save 15min a day: tips for faster development</title>
          <pubDate>Tue, 07 Aug 2018 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/tips-for-faster-development/</link>
          <guid>https://denisidoro.github.io/posts/tips-for-faster-development/</guid>
          <description xml:base="https://denisidoro.github.io/posts/tips-for-faster-development/">&lt;figure class=&quot;medium-cover&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;t u v gh aj&quot; src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;11014&#x2F;0*v2N2qF1dh5l_Gqgr&quot; srcSet=&quot; https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;0*v2N2qF1dh5l_Gqgr 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;0*v2N2qF1dh5l_Gqgr 552w,
    https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;0*v2N2qF1dh5l_Gqgr 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;0*v2N2qF1dh5l_Gqgr 700w&quot; sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;An hourglass with most of its sand on its bottom by
        &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com&#x2F;@neonbrand?utm_source=medium&amp;utm_medium=referral&quot;&gt;neonbrand&lt;&#x2F;a&gt;
        on &lt;a href=&quot;https:&#x2F;&#x2F;unsplash.com?utm_source=medium&amp;utm_medium=referral&quot;&gt;Unsplash&lt;&#x2F;a&gt; 
    &lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;I’m lazy!&lt;&#x2F;p&gt;
&lt;p&gt;Working is satisfying but if I were to choose between, say, fixing broken integration tests vs watching Netflix, swimming or spending some quality time with my girlfriend, I would probably choose the latter.&lt;&#x2F;p&gt;
&lt;p&gt;However, the job market is increasingly competitive. Companies expect you to deliver results as most as possible. In order to catch up with colleagues, employees tend to work until late at night. &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Impostor_syndrome&quot;&gt;Impostor syndrome&lt;&#x2F;a&gt; isn’t unusual. Even so, people are fired for performance reasons. There’s simply no space for not shipping at the end of the day.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, as much as I don’t want to work 10h&#x2F;day, it’s expected from me to get the job done. So what I try to do is to code the most I can during the hours I’m in the office.&lt;&#x2F;p&gt;
&lt;p&gt;Do I know the secrets for productivity? Absolutely not! &lt;br &#x2F;&gt;
Nevertheless, during these years I have developed production-ready code, my productivity has evolved a little bit.&lt;br &#x2F;&gt;
The purpose of this post isn’t simply about showing what I learned. Instead, it’s about showing examples that will possibly trigger your mind so that you yourself can improve your workflow.&lt;&#x2F;p&gt;
&lt;p&gt;If after this post you save 15 minutes at the end of the day, my objective here will be accomplished.&lt;&#x2F;p&gt;
&lt;p&gt;Without further ado, let’s get started with the first tip!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;file-folder-navigation&quot;&gt;File&#x2F;folder navigation&lt;&#x2F;h3&gt;
&lt;p&gt;I think it’s fundamental to navigate around your file system.&lt;br &#x2F;&gt;
As an exercise, let’s &lt;em&gt;cd&lt;&#x2F;em&gt; to a mobile app from work. &lt;br &#x2F;&gt;
This is a simple, tedious and time-consuming way to do it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;cd ~&amp;#x2F;dev&amp;#x2F;work&amp;#x2F;mobile
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Creating a simple &lt;strong&gt;alias function&lt;&#x2F;strong&gt; already saves us some keystrokes:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;cdw() { cd &amp;quot;$HOME&amp;#x2F;dev&amp;#x2F;work&amp;#x2F;$1&amp;quot;; }  
cdw mobile
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But we still can do much better. An improved version of this could be achieved using the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;junegunn&#x2F;fzf&quot;&gt;&lt;strong&gt;fzf command&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. fzf is a fuzzy-finder that allows you to interactively choose an input from &lt;em&gt;stdin&lt;&#x2F;em&gt;. I use it for everything and folder browsing isn’t an exception.&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;5485fe0f1d51922559366775f2f2904d&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;So, in a single command we get for free: listing, a nice interface and matching by substrings. We also added another arity to our command that _cd_s to the best match directly. Nice, isn’t it?&lt;&#x2F;p&gt;
&lt;p&gt;A more generic approach would be using the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;clvv&#x2F;fasd&quot;&gt;&lt;strong&gt;fasd command&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. Fasd keeps track of every folder you &lt;em&gt;cd&lt;&#x2F;em&gt; into and scores them by frecency: a metric based on frequency and recency, so it knows where you probably wanna go to.&lt;br &#x2F;&gt;
This way, if I have already visited the mobile folder, I can simply type ‘’mob’’ and I’m done.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;~&amp;#x2F;Downloads  
λ j mob

~&amp;#x2F;dev&amp;#x2F;work&amp;#x2F;mobile  
λ
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From this, we can come up with a lot of interesting commands.&lt;br &#x2F;&gt;
Using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;dotfiles&#x2F;blob&#x2F;bebfcbd6c32e7a5a9dc151d6a9153ae010d8d32f&#x2F;shell&#x2F;aliases&#x2F;browsing.zsh#L100&quot;&gt;this command&lt;&#x2F;a&gt;, for example, I can quickly open in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;neovim&#x2F;neovim&quot;&gt;vim&lt;&#x2F;a&gt; any file that is inside any subfolder of the folder I’m currently in. Likewise, if I input a substring to it, it opens the best match directly.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*IeUQKixUrBM5xHp1vC_gog.gif&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*IeUQKixUrBM5xHp1vC_gog.gif 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*IeUQKixUrBM5xHp1vC_gog.gif 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*IeUQKixUrBM5xHp1vC_gog.gif 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*IeUQKixUrBM5xHp1vC_gog.gif 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;With simple scripts you don’t even need to launch a project in an IDE to quickly edit a file&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;A nice addition is &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ranger&#x2F;ranger&quot;&gt;&lt;strong&gt;ranger&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;, a file explorer for the terminal: we can quickly switch between folders, see previews and much more.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*NILX79uV_jiYHh7AYeI-Dw.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*NILX79uV_jiYHh7AYeI-Dw.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*NILX79uV_jiYHh7AYeI-Dw.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*NILX79uV_jiYHh7AYeI-Dw.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*NILX79uV_jiYHh7AYeI-Dw.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Ranger displays multiple columns so that you can view parent folders as well as file contents&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Finally, I really recommend having an easy way to &lt;strong&gt;switch between&lt;&#x2F;strong&gt; the &lt;strong&gt;terminal&lt;&#x2F;strong&gt; and your OS’s &lt;strong&gt;explorer.&lt;&#x2F;strong&gt; Here, I’m using &lt;a href=&quot;https:&#x2F;&#x2F;www.alfredapp.com&#x2F;&quot;&gt;Alfred&lt;&#x2F;a&gt; to open the current folder in Finder in the terminal.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*Pf1KlZXto2hEQe1lGZlPQg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*Pf1KlZXto2hEQe1lGZlPQg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*Pf1KlZXto2hEQe1lGZlPQg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*Pf1KlZXto2hEQe1lGZlPQg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*Pf1KlZXto2hEQe1lGZlPQg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;With Alfred, I can quickly launch a terminal window in the same folder as Finder&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;git&quot;&gt;Git&lt;&#x2F;h3&gt;
&lt;p&gt;The most straightforward tip is to use &lt;a href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;mwhite&#x2F;6887990&quot;&gt;&lt;strong&gt;shell aliases&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
So, instead of typing the following…&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git checkout master  
git pull  
git checkout feature  
git merge master  
git commit -a
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…we can save lots and lots of keystrokes (and time) by using:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;gcom  
gl  
gco -  
gmm  
gca
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Another tip is to use a &lt;a href=&quot;https:&#x2F;&#x2F;help.github.com&#x2F;articles&#x2F;ignoring-files&#x2F;&quot;&gt;&lt;strong&gt;global .gitignore&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
This way, you have the guarantee that you’ll never ever commit again &lt;em&gt;node_modules&lt;&#x2F;em&gt;, IDE files or sensitive data, regardless of your repository’s .gitignore.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;book&#x2F;en&#x2F;v2&#x2F;Customizing-Git-Git-Hooks&quot;&gt;&lt;strong&gt;Git hooks&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; are very powerful as well. They are scripts that are run before you commit or push, for example.&lt;br &#x2F;&gt;
How many times have you committed a bad JSON, for example, then committed a “fix typo” immediately after? Git hooks for the rescue!&lt;br &#x2F;&gt;
Simply validate JSON files in a pre-commit hook: if anything is incorrect, the commit may be aborted.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*UrZP9iLjSNNClCvahNV3qg.gif&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*UrZP9iLjSNNClCvahNV3qg.gif 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*UrZP9iLjSNNClCvahNV3qg.gif 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*UrZP9iLjSNNClCvahNV3qg.gif 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*UrZP9iLjSNNClCvahNV3qg.gif 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;A git hook may prevent us from commiting bad code&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;I also use this for other file syntax validation as well as for preventing me from committing sensitive data, such as AWS keys.&lt;&#x2F;p&gt;
&lt;p&gt;Write your &lt;strong&gt;own git helpers&lt;&#x2F;strong&gt;. This is how I git checkout:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*Q2yCC9Dp730GcpXDXJsQ7A.gif&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*Q2yCC9Dp730GcpXDXJsQ7A.gif 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*Q2yCC9Dp730GcpXDXJsQ7A.gif 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*Q2yCC9Dp730GcpXDXJsQ7A.gif 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*Q2yCC9Dp730GcpXDXJsQ7A.gif 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Remember fzf? I’m using it to select available branches&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Using &lt;a href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;book&#x2F;en&#x2F;v2&#x2F;Git-Tools-Rewriting-History&quot;&gt;&lt;strong&gt;git squash&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;  really changed my daily work.&lt;br &#x2F;&gt;
First, let’s see what many people consider a nice, organized PR:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;6c96a Add logic  
84f15 Replace login screen  
7960c Add button callbacks to new login screen  
22cd3 Add ...  
6c96a Add unit tests  
34fa5 Fix unit tests  
12cd4 Bump  
bc96a Merge branch master  
a3b5a Bump again
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;First commit: ‘’Add logic’’. Fair enough.&lt;br &#x2F;&gt;
Then, “Replace login screen’’. Ok.&lt;br &#x2F;&gt;
‘’Add button callbacks to new login screen’’. Wait a minute! The previous commit was replacing a working screen by an incomplete one! Possibly it wouldn’t even compile. What good is this previous commit for, then? &lt;br &#x2F;&gt;
If we were to &lt;a href=&quot;https:&#x2F;&#x2F;robots.thoughtbot.com&#x2F;git-bisect&quot;&gt;git bisect&lt;&#x2F;a&gt; these commits would only fool us. &lt;br &#x2F;&gt;
I think that the master branch should only have commits with which you would be fairly comfortable to have in prod. &lt;br &#x2F;&gt;
‘’Add unit tests’’. Was everything untested, then? The previous commits are potentially buggy code?&lt;&#x2F;p&gt;
&lt;p&gt;You see, we’ve put a lot of effort into carefully hand-picking chunks of code and writing commit messages with arguably no benefit. Rarely anyone reviews a PR commit by commit, anyway.&lt;&#x2F;p&gt;
&lt;p&gt;Ideally, we could select chunks of code so that every commit has nice test coverage and the expected behavior. But that takes too much time. Instead, I do the following:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;bc96a wip  
54be0 code compilable again  
ab4fc fixes
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When I open the PR I try to write a nice PR description. Finally, I simply squash everything into a single commit and merge it.&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*tFBVxqAGr-yOTX5G3aX5Tw.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*tFBVxqAGr-yOTX5G3aX5Tw.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*tFBVxqAGr-yOTX5G3aX5Tw.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*tFBVxqAGr-yOTX5G3aX5Tw.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*tFBVxqAGr-yOTX5G3aX5Tw.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Even though the commit messages aren’t helpful, the PR description should be enough to give the necessary context&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*wIsFO0z5cXTo9Te2LABH6g.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*wIsFO0z5cXTo9Te2LABH6g.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*wIsFO0z5cXTo9Te2LABH6g.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*wIsFO0z5cXTo9Te2LABH6g.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*wIsFO0z5cXTo9Te2LABH6g.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;GitHub offers a quick way to squash and merge&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;It’s time saving and we get a nice history for free, even on terminal!&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*xDRcpV31kRmAkSQgM7WZeg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*xDRcpV31kRmAkSQgM7WZeg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*xDRcpV31kRmAkSQgM7WZeg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*xDRcpV31kRmAkSQgM7WZeg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*xDRcpV31kRmAkSQgM7WZeg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;By squashing, we don’t have useless commits&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;shell-scripting&quot;&gt;Shell scripting&lt;&#x2F;h3&gt;
&lt;p&gt;Another fundamental skill to be efficient is to write shell scripts hassle-free.We studied programming and are usually hired to develop scalable, high-quality microservices or applications, not for performing tedious, easy tasks.&lt;br &#x2F;&gt;
If it’s common for you to think that ‘’it would be nice to have a script for X, but the time it’ll take to write it isn’t worth it’’, then let’s think about it as a chemistry reaction:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*K00n4FRXv0D7gpSeOOdMBg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*K00n4FRXv0D7gpSeOOdMBg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*K00n4FRXv0D7gpSeOOdMBg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*K00n4FRXv0D7gpSeOOdMBg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*K00n4FRXv0D7gpSeOOdMBg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;If the barrier for writing shell scripts is high, then you’ll stay on the left side, spending high energy to perform tasks.&lt;br &#x2F;&gt;
However, if you have a catalyst, the equilibrium state will be on the right side. In other words, if it becomes cheaper or easier to write unit scripts, then you’ll spend less energy performing the tasks.&lt;&#x2F;p&gt;
&lt;p&gt;The following practices help me write shell scripts without a hiccup:&lt;&#x2F;p&gt;
&lt;p&gt;Use &lt;strong&gt;set -euo pipefail&lt;&#x2F;strong&gt;. This way you won’t have tricky surprises such as using undefined variables or having failed intermediate commands.&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;fd18de88ee0a3450905af4a3ce2697be&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;&lt;strong&gt;Namespace your functions&lt;&#x2F;strong&gt;. Let’s understand why:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;161df2dfeeccc03cde546e24b17bf1a6&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;What is &lt;code&gt;is_valid&lt;&#x2F;code&gt;? We don’t even know where it comes from. If we search for it in the whole project there will be possibly multiple results. Had we namespaced our functions the story would be different. &lt;br &#x2F;&gt;
If instead it was named as &lt;code&gt;json::is_valid&lt;&#x2F;code&gt;, we wouldn’t even need to go to its definition to understand it. But if we wanted to, it would be easy. Also, refactoring without an IDE is easy like this because there’s a single match so a search and replace will safely do the job.&lt;br &#x2F;&gt;
 &lt;br &#x2F;&gt;
Using &lt;a href=&quot;http:&#x2F;&#x2F;docopt.org&#x2F;&quot;&gt;&lt;strong&gt;docopt&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; is a must-have, so that you don’t spend time parsing arguments:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;9ab91e30c9ae277072d1740b710e6d43&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;With docopt, you don’t need to write parsers. Instead, you just write the script documentation&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;&lt;strong&gt;Don’t throw away your scripts&lt;&#x2F;strong&gt;, even if they are simple. Instead, put them in a folder, document them and make them available to your disposal whenever you need. I never remember the command for spawning a new Android emulator, for example, but I can always remember how to type &lt;code&gt;dot android emu start&lt;&#x2F;code&gt;. In particular, I divide all my scripts into non-nested folders and make them callable by a &lt;code&gt;dot&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ $ANDROID_HOME&amp;#x2F;tools&amp;#x2F;emulator start     # is it like this?  
$ $ANDROID_SDK&amp;#x2F;tools&amp;#x2F;emulator -avd nexus # maybe like this?  
# checks StackOverflow, other references or simply...  
$ dot android emu start                  # there you go!
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, my biggest tip for bash is… &lt;strong&gt;don’t use bash&lt;&#x2F;strong&gt;.&lt;br &#x2F;&gt;
Use a language you feel comfortable with or that makes sense to your project.&lt;br &#x2F;&gt;
Use Python, Ruby or NodeJS.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;misc&quot;&gt;Misc&lt;&#x2F;h3&gt;
&lt;p&gt;Use a &lt;strong&gt;window manager&lt;&#x2F;strong&gt;. Seriously, the time saved by not resizing, moving, minimizing and maximizing windows is huge. Let’s see a simple demonstration of what it can do:&lt;&#x2F;p&gt;
&lt;figure&gt;
    &lt;div&gt;
   &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;U17CLayt_aA&quot; webkitallowfullscreen
      mozallowfullscreen allowfullscreen&gt;
   &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;

    &lt;figcaption&gt;A demonstration showcasing bspwm&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;For Linux, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;i3&#x2F;i3&quot;&gt;i3wm&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;baskerville&#x2F;bspwm&quot;&gt;bspwm&lt;&#x2F;a&gt; are some options; for OSX, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;koekeishiya&#x2F;chunkwm&quot;&gt;chunkwm&lt;&#x2F;a&gt; and spectacle; for Windows, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;fuhsjr00&#x2F;bug.n&quot;&gt;bugn&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Have a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;dotfiles&quot;&gt;&lt;strong&gt;dotfiles repository&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; on Github. This way, it’s easy to share your setup with your colleagues. But most importantly: all your setup becomes centralized and you’re always two commands away (or some minutes away) from having your ideal config:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;denisidoro&amp;#x2F;dotfiles.git ~&amp;#x2F;.dotfiles  
bash ~&amp;#x2F;.dotfiles&amp;#x2F;scripts&amp;#x2F;environment&amp;#x2F;init
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Use &lt;strong&gt;consistent keybindings&lt;&#x2F;strong&gt; across applications. My memory is limited, so it’s nice to memorize commands only once and extend them to other usages. &lt;code&gt;HJKL&lt;&#x2F;code&gt;, for example, is reserved for directions in my setup: cursor position and buffer selection on text editors, window selection and resizing on my window manager, scrolling on Chrome, moving between folders on ranger, etc. By the way, &lt;strong&gt;sequential keybindings&lt;&#x2F;strong&gt; help a lot. I can never remember commands like &lt;code&gt;cmd + option + shift + whatever&lt;&#x2F;code&gt;, but commands that follow a pattern are pretty straightforward:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;Focus window left:   CapsLock + H  
Focus window right:  CapsLock + L  
Resize window right: CapsLock + R, L  
Swap window right:   CapsLock + S, L  
Warp window right:   CapsLock + W, L
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Navigate through your shell &lt;strong&gt;history with ease.&lt;&#x2F;strong&gt; This becomes trivial with fzf:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*NE-Mw0yyHJ19EmEq1UeAjg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*NE-Mw0yyHJ19EmEq1UeAjg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*NE-Mw0yyHJ19EmEq1UeAjg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*NE-Mw0yyHJ19EmEq1UeAjg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*NE-Mw0yyHJ19EmEq1UeAjg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;fzf gives Ctrl+R steroids&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Have a &lt;strong&gt;clipboard manager&lt;&#x2F;strong&gt;. Have you ever tried to paste some text just to realize that you overrode the clipboard with something else then you have to go back to the original application to copy it again? Well, if a clipboard manager this problem is past!&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*M4FsZ1eGCrIABCeusSY9lA.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*M4FsZ1eGCrIABCeusSY9lA.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*M4FsZ1eGCrIABCeusSY9lA.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*M4FsZ1eGCrIABCeusSY9lA.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*M4FsZ1eGCrIABCeusSY9lA.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;With a clipboard manager you have at your fingertips all your recently copied content&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;&lt;strong&gt;Master&lt;&#x2F;strong&gt; whatever tools you use: terminal emulators, text editors, IDEs, OS, etc.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sum-up&quot;&gt;Sum up&lt;&#x2F;h3&gt;
&lt;p&gt;Of course all I talked about is low level stuff which will save you some minutes or at most a couple of hours at the end of the week. The highest benefits come from high level changes, which may save you entire days of work.&lt;&#x2F;p&gt;
&lt;p&gt;That’s very subjective, though. Maybe what saves you time the most is doing more whiteboards, or avoiding overgeneralizing code, or not checking your emails every single minute. It may even be purely soft skill-related, such as communicating beforehand to your peers what you’re doing. So keep an eye on this kind of optimization as well.&lt;&#x2F;p&gt;
&lt;p&gt;But what I really recommend is… &lt;strong&gt;excel at being lazy&lt;&#x2F;strong&gt;!&lt;br &#x2F;&gt;
If you’re lazy, you’ll get uncomfortable with time-demanding processes and it will become natural to optimize your workflow.&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;save-15min-a-day-tips-for-faster-development-67a31f3498bf&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
      <item>
          <title>Writing multi-platform mobile apps with React Native and KotlinJS</title>
          <pubDate>Mon, 23 Apr 2018 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://denisidoro.github.io/posts/react-native-kotlinjs/</link>
          <guid>https://denisidoro.github.io/posts/react-native-kotlinjs/</guid>
          <description xml:base="https://denisidoro.github.io/posts/react-native-kotlinjs/">&lt;figure class=&quot;medium-cover&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;t u v gh aj&quot; src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;11014&#x2F;0*EnXaEGp3rgRz7NS4.&quot; srcSet=&quot; https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;0*EnXaEGp3rgRz7NS4. 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;0*EnXaEGp3rgRz7NS4. 552w,
    https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;0*EnXaEGp3rgRz7NS4. 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;0*EnXaEGp3rgRz7NS4. 700w&quot; sizes=&quot;700px&quot; &#x2F;&gt;

    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Kotlin is an awesome language. It is functional and offers &lt;a href=&quot;https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;reference&#x2F;extensions.html&quot;&gt;extension functions&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;reference&#x2F;null-safety.html&quot;&gt;null safety&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;www.callicoder.com&#x2F;kotlin-type-checks-smart-casts&#x2F;&quot;&gt;smart casting&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;reference&#x2F;type-safe-builders.html&quot;&gt;type-safe builders&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;www.callicoder.com&#x2F;kotlin-functions&#x2F;&quot;&gt;default and named arguments&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;www.callicoder.com&#x2F;kotlin-functions&#x2F;&quot;&gt;data classes&lt;&#x2F;a&gt; and much more. It is arguably the language of choice for Android developers and &lt;a href=&quot;http:&#x2F;&#x2F;nilhcem.com&#x2F;swift-is-like-kotlin&#x2F;&quot;&gt;very familiar&lt;&#x2F;a&gt; to iOS developers.&lt;&#x2F;p&gt;
&lt;p&gt;React Native enables us to write only once for multiple platforms, has a simpler API than native development and a smooth learning curve. Steve Yegg, a former Google engineer, conjectures in &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@steve.yegge&#x2F;who-will-steal-android-from-google-af3622b6252e&quot;&gt;his blog post&lt;&#x2F;a&gt; that hybrid frameworks are likely to become even more popular. And the framework from Facebook is the major player at the moment.&lt;&#x2F;p&gt;
&lt;p&gt;So why not mixing both? &lt;br &#x2F;&gt;
Standing on the shoulder of giants (&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JetBrains&#x2F;kotlin-wrappers&#x2F;tree&#x2F;master&#x2F;kotlin-react&quot;&gt;JetBrains&#x2F;kotlin-wrappers&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ScottPierce&#x2F;kotlin-react-native&quot;&gt;ScottPierce&#x2F;kotlin-react-native&lt;&#x2F;a&gt;), we can. &lt;br &#x2F;&gt;
An example repository can be found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;korn&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;react-in-a-nutshell&quot;&gt;React in a nutshell&lt;&#x2F;h3&gt;
&lt;p&gt;You can learn the basics of React Native very quickly reading the &lt;a href=&quot;https:&#x2F;&#x2F;facebook.github.io&#x2F;react-native&#x2F;docs&#x2F;tutorial.html&quot;&gt;official tutorial&lt;&#x2F;a&gt;, but three important takeaways are:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;view = f(state) &lt;&#x2F;li&gt;
&lt;li&gt;React has a virtual representation of the view, and it calculates the smallest diff between renders&lt;&#x2F;li&gt;
&lt;li&gt;development should be component-driven&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;hello-world-in-kotlin&quot;&gt;Hello world in Kotlin&lt;&#x2F;h3&gt;
&lt;p&gt;After cloning the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;korn&quot;&gt;example repository&lt;&#x2F;a&gt; and following the README, you can simply edit the render() function to immediately see the result on the device.&lt;&#x2F;p&gt;
&lt;p&gt;For example, writing the following code…&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;64a189c08b57e50440c7e5bc9bcdc1a2&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;…would render the following output:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*QGZmQ_Zq7_jTQXXiBWFILg.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*QGZmQ_Zq7_jTQXXiBWFILg.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*QGZmQ_Zq7_jTQXXiBWFILg.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*QGZmQ_Zq7_jTQXXiBWFILg.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*QGZmQ_Zq7_jTQXXiBWFILg.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Our first application with Kotlin and React Native&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;
&lt;p&gt;Thanks to the Kotlin language features, we can write the view using a builder DSL similar to &lt;a href=&quot;https:&#x2F;&#x2F;reactjs.org&#x2F;docs&#x2F;jsx-in-depth.html&quot;&gt;JSX&lt;&#x2F;a&gt;, instead of manually calling React.createElement(…, React.createElement(…)).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;creating-our-first-component&quot;&gt;Creating our first component&lt;&#x2F;h3&gt;
&lt;p&gt;Let’s say you have the following view architecture and you want to make the code for headers reusable:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;3264c01ad43aa051893d6937a22aa850&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;For this task, you could create a Header component:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;bbe3b31572c52acfecc76f2ecc50bd03&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;And call it from the render() function as such:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;bd46d12af1fd4188393bdde48f2c02a1&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Alternatively, if you want the extend the DSL so that you can call this component directly, just create an extension function:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;2ab29ea9159c3f55285bd52ac8e5ac95&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;h3 id=&quot;managing-state&quot;&gt;Managing state&lt;&#x2F;h3&gt;
&lt;p&gt;React doesn’t dictate how state management should be. For the sake of this example, let’s write our own simplified, naive implementation of &lt;a href=&quot;https:&#x2F;&#x2F;redux.js.org&quot;&gt;Redux&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In this pattern, we have three main elements:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;store: holds the state&lt;&#x2F;li&gt;
&lt;li&gt;actions: describe the changes&lt;&#x2F;li&gt;
&lt;li&gt;reducers: return an updated state based on the requested action&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Here is the full implementation:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;c60df8ce463f92e03d74926ad6bd2328&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Evidently, on a real world application, you should prevent race conditions and implement the subscriptions in an atomic fashion, possibly using &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;reactivex&#x2F;rxjs&quot;&gt;RxJS&lt;&#x2F;a&gt;. But that’s good for now.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-less-contrived-example&quot;&gt;A less contrived example&lt;&#x2F;h3&gt;
&lt;p&gt;Now let’s write a simple application which consists of a header, and two rating widgets, for giving scores to two different programming languages.&lt;&#x2F;p&gt;
&lt;p&gt;First we create the models:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;c9b8b14b9f43500ad243339fe78fb792&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Then the actions that can be dispatched:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;fddebcf58c3b6330213b74868cd8fec8&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Later on, we define the reducer:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;c3c46f8e78e30d476e6ca9b17c46995e&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Then we define a view model, mapping state to view abstractions:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;f7bf0931eaa4acc382386faf804a3fc4&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;We connect everything with a store:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;f308d63f310d6f52cfd85e7a0d706263&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Finally, we create a stateless, dumb component:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;bc52bf9cc2c899cde5f0ad4b383190bc&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;And another component that keeps track of the state:&lt;&#x2F;p&gt;
&lt;div&gt;
   &lt;script src=&quot;https:&amp;#x2F;&amp;#x2F;gist.github.com&amp;#x2F;denisidoro&amp;#x2F;cefc050a3e2e5798c53ddd387d3ae94f&quot;&gt;&lt;&#x2F;script&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;And the final result is:&lt;&#x2F;p&gt;
&lt;figure class=&quot;medium-image&quot;&gt;

    &lt;img alt=&quot;Image for post&quot; class=&quot;medium-image&quot;
        src=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;2160&#x2F;1*EDiObUxyPdrQpEFEKovdKQ.png&quot;
        srcSet=&quot;https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;552&#x2F;1*EDiObUxyPdrQpEFEKovdKQ.png 276w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1104&#x2F;1*EDiObUxyPdrQpEFEKovdKQ.png 552w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1280&#x2F;1*EDiObUxyPdrQpEFEKovdKQ.png 640w, https:&#x2F;&#x2F;miro.medium.com&#x2F;max&#x2F;1400&#x2F;1*EDiObUxyPdrQpEFEKovdKQ.png 700w&quot;
        sizes=&quot;700px&quot; &#x2F;&gt;

    
    &lt;figcaption&gt;Our final application&lt;&#x2F;figcaption&gt;
    

    

&lt;&#x2F;figure&gt;&lt;h3 id=&quot;so-is-kotlin-with-react-native-production-ready&quot;&gt;So… Is Kotlin with React Native production-ready?&lt;&#x2F;h3&gt;
&lt;p&gt;Possibly, but there are still some improvements to be made to the whole ecosystem. The interoperability with JavaScript isn’t effortless and has some pitfalls. For example, passing the store via props to the component won’t work because the state will become immutable. Also, using data classes for the props directly will return an error from React because apparently the serialization contains some metadata. In addition, you’re supposed to write your own type definitions, which is a huge productivity killer.&lt;&#x2F;p&gt;
&lt;p&gt;I think JetBrains is to blame for the unpopularity of KotlinJS, by not giving the language the love it deserves. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Kotlin&#x2F;ts2kt&quot;&gt;Kotlin&#x2F;ts2kt&lt;&#x2F;a&gt;, for instance, could solve the type definition problem. However, most of the attempts for converting types fail, and the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Kotlin&#x2F;ts2kt&#x2F;issues&quot;&gt;issues remain open&lt;&#x2F;a&gt;. Actually, we don’t see many examples targeting JS at all, despite the fact that this a working platform for more than a year. Maybe they are focused on &lt;a href=&quot;https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;reference&#x2F;native-overview.html&quot;&gt;Kotlin&#x2F;Native&lt;&#x2F;a&gt;…&lt;&#x2F;p&gt;
&lt;p&gt;In the meantime, another alternative to JavaScript is &lt;a href=&quot;https:&#x2F;&#x2F;clojurescript.org&#x2F;&quot;&gt;Clojurescript&lt;&#x2F;a&gt;, which is awesome with &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Day8&#x2F;re-frame&quot;&gt;re-frame&lt;&#x2F;a&gt; and in my opinion offers the best development experience out there. However, it’s not for the faint-hearted.&lt;&#x2F;p&gt;
&lt;p&gt;I hope that with this article and the example repository, the community can reconsider Kotlin as a language for React Native. Feel free to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denisidoro&#x2F;korn&quot;&gt;fork the code&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;div class=&quot;medium-first&quot;&gt;

    This post first appeared on
    &lt;a href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@den.isidoro&#x2F;writing-multi-platform-mobile-apps-with-react-native-and-kotlin-50f486b53462&quot; target=&quot;_blank&quot;&gt;Medium&lt;&#x2F;a&gt;.

&lt;&#x2F;div&gt;</description>
      </item>
    </channel>
</rss>
