<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: jorin</title>
    <description>The latest articles on Forem by jorin (@jorinvo).</description>
    <link>https://forem.com/jorinvo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2711%2Fa099278d-a43c-4976-b2fe-4f5c5bd0b938.jpg</url>
      <title>Forem: jorin</title>
      <link>https://forem.com/jorinvo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jorinvo"/>
    <language>en</language>
    <item>
      <title>Getting Started Building a Data Platform</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Fri, 17 Oct 2025 08:42:30 +0000</pubDate>
      <link>https://forem.com/jorinvo/getting-started-building-a-data-platform-2kfe</link>
      <guid>https://forem.com/jorinvo/getting-started-building-a-data-platform-2kfe</guid>
      <description>&lt;p&gt;Ever wonder what a data platform is and if your company needs one?&lt;/p&gt;

&lt;p&gt;If the idea of hiring a data team to build and manage an enterprise data platform feels overwhelming, you're not alone.&lt;/p&gt;

&lt;p&gt;Let me break down how you can get started from zero and &lt;strong&gt;build up data capabilities&lt;/strong&gt; at your company, one step at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Needs a Data Platform?
&lt;/h2&gt;

&lt;p&gt;Nowadays every company is a data company. From marketing and sales to product usage and customer support, all aspects of your business generate data.&lt;/p&gt;

&lt;p&gt;And that data is waiting to be activated. Turn it into reports to drive decisions. Build dashboards to support operations. Offer new product features and services to your customers that are directly driven by data and automation.&lt;/p&gt;

&lt;p&gt;Getting started is much more &lt;strong&gt;a cultural challenge&lt;/strong&gt; than a technical one.&lt;/p&gt;

&lt;p&gt;Start small. Make sure you see &lt;strong&gt;first successes by doing the work manually&lt;/strong&gt; without worrying about big investments in making it scalable.&lt;/p&gt;

&lt;p&gt;But once you see concrete value and it becomes painfully clear that technology is holding you back, you know it's time to build out your data capabilities.&lt;/p&gt;

&lt;p&gt;Where do you go from here? Can you buy an off-the-shelf solution? Do you hire a data engineer? Do you need a dedicated data team or can your existing engineers handle it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking It Down
&lt;/h2&gt;

&lt;p&gt;You can break down data infrastructure into four main layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion:&lt;/strong&gt; Connect data sources, extract data and load it into a central repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; Store data in a structured format that is optimized for analytical workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transformation:&lt;/strong&gt; Clean, enrich, and transform data to make it practical to work with and ensure consistent definitions of key metrics across the organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Intelligence:&lt;/strong&gt; Create dashboards, reports, and alerts to share insights internally and with your customers and partners&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  One Step at a Time
&lt;/h2&gt;

&lt;p&gt;There are many different tools to address all of these layers.&lt;/p&gt;

&lt;p&gt;Focus on solving &lt;strong&gt;concrete problems&lt;/strong&gt; you are experiencing and add new tools only when they directly solve a problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingestion
&lt;/h3&gt;

&lt;p&gt;Start with &lt;strong&gt;querying your data directly where it is&lt;/strong&gt;. Introduce tools to load data into a central repository only when the complexity and volume make this impractical.&lt;/p&gt;

&lt;p&gt;You don't need to address all data sources at once. Focus on the ones creating problems. &lt;strong&gt;Accept manual workarounds&lt;/strong&gt; when practical.&lt;/p&gt;

&lt;p&gt;Your data tooling should be able to query data across different data sources. You don't need to worry about ingestion if directly querying a Postgres database and a Google Sheets gets the job done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;Chances are you already store your data in a database such as PostgreSQL or MySQL. If you're not having performance issues, there's no need to introduce a separate database for analytical workloads.&lt;/p&gt;

&lt;p&gt;Only if &lt;strong&gt;performance or cost becomes an issue&lt;/strong&gt; should you start addressing it.&lt;/p&gt;

&lt;p&gt;Storage is a critical component since it's where the actual data lives. &lt;strong&gt;Data outlives applications&lt;/strong&gt; built on top of it. Pick an established and open standard to store data.&lt;/p&gt;

&lt;p&gt;Keep in mind that there is no one-size-fits-all solution. You might need multiple data stores &lt;strong&gt;optimized for different use cases&lt;/strong&gt;. You'll know what you're looking for when you act on concrete problems instead of trying to find a solution for hypothetical future problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transformation
&lt;/h3&gt;

&lt;p&gt;Start delivering value before adding a separate data transformation step. Introduce a dedicated data transformation layer when &lt;strong&gt;queries start taking too long&lt;/strong&gt;, or &lt;strong&gt;metrics become unreliable&lt;/strong&gt; and hard to maintain because the &lt;strong&gt;same logic is repeated&lt;/strong&gt; many places.&lt;/p&gt;

&lt;p&gt;A few &lt;strong&gt;materialized views&lt;/strong&gt; in your database can take you a long way.&lt;/p&gt;

&lt;p&gt;You'll know it's time to look into real-time stream processing, data lineage and orchestration tools once you experience the issues that these tools are designed to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Intelligence
&lt;/h3&gt;

&lt;p&gt;Many software companies start out by building custom analytics features. As you use data to drive operations and user-facing functionality, building custom solutions for every new workflow and view on the data becomes slow and expensive.&lt;/p&gt;

&lt;p&gt;Introduce a data visualization tool to &lt;strong&gt;quickly build analytics dashboards&lt;/strong&gt; and reports. This is a &lt;strong&gt;great first step&lt;/strong&gt; and enables a single &lt;strong&gt;data analyst&lt;/strong&gt; to deliver a lot of value without introducing any other data infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/taleshape-com/shaper" rel="noopener noreferrer"&gt;Shaper&lt;/a&gt; to help companies in exactly this situation.&lt;/p&gt;

&lt;p&gt;Shaper is a simple interface on top of &lt;a href="https://duckdb.org/" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; that allows you to build analytics dashboards and automate data workflows with &lt;strong&gt;only SQL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thanks to DuckDB, it's easy to query data across various sources ranging from databases to CSV files and Google Sheets.&lt;/p&gt;

&lt;p&gt;You can go a long way before having to add more layers to your data stack.&lt;/p&gt;

&lt;p&gt;Give it a try and let me know what you think.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>database</category>
      <category>sql</category>
    </item>
    <item>
      <title>Clickhouse for Embedded Analytics: First Impressions and Unexpected Challenges</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Mon, 30 Sep 2024 09:40:38 +0000</pubDate>
      <link>https://forem.com/jorinvo/clickhouse-for-embedded-analytics-first-impressions-and-unexpected-challenges-2h4p</link>
      <guid>https://forem.com/jorinvo/clickhouse-for-embedded-analytics-first-impressions-and-unexpected-challenges-2h4p</guid>
      <description>&lt;p&gt;&lt;em&gt;I implemented Clickhouse for an embedded analytics project handling millions of data points, and here I share my first-hand experience with its impressive performance benefits and the unexpected challenges I encountered along the way.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;For a recent project, I got to help a client with the implementation of an embedded analytics system.&lt;/p&gt;

&lt;p&gt;The system collects data from multiple internal services. The data is then prepared to build various dashboards on top of it. The dashboards will be embedded in the main application to provide customers with insights into their operations. The system needs to be flexible enough to support new use cases for using the data in the future.&lt;/p&gt;

&lt;p&gt;In the past, I built similar systems on top of Postgres. Postgres is very flexible and it's a great default option for any data storage needs.&lt;/p&gt;

&lt;p&gt;For this new project, we expect the number of data points to grow into the millions quickly.&lt;/p&gt;

&lt;p&gt;It is certainly possible to handle millions of rows with Postgres. But doing analytical queries over millions of rows gets unfeasibly slow with Postgres. You end up having to deploy different caching strategies such as maintaining different pre-aggregated versions of the same data. This quickly increases the complexity of the system and makes it expensive to evolve it as new needs come up.&lt;/p&gt;

&lt;p&gt;Analytical workloads are not the primary use case of Postgres and that's okay.&lt;/p&gt;

&lt;p&gt;We started to look for alternatives and quickly landed at &lt;a href="https://clickhouse.com/" rel="noopener noreferrer"&gt;Clickhouse&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Many options were ruled out quickly because the client operates in regulated industries which require us to run the system in their own data centers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we were hoping to get out of Clickhouse
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Raw performance:&lt;/strong&gt; The main benefit we get from choosing Clickhouse is the raw performance its design enables for analytical workloads. And with how fast it is by default, we can save a ton of time and keep the system simpler. We can do complex queries over millions of rows and they are fast enough without further optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materialized views:&lt;/strong&gt; Once we do want to cache aggregated data, we can make use of incremental materialized views that are built into Clickhouse. No separate system needed for processing the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Made for analytics:&lt;/strong&gt; Clickhouse comes with a ton of built-in functionality to simplify analytical queries. From supported data formats to helper functions - the available tools make our job simpler and quicker. For example, &lt;code&gt;ArgsMaxIf&lt;/code&gt; simplifies our aggregate queries by a lot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations:&lt;/strong&gt; Clickhouse comes with many integrations out of the box. In our case, we are streaming JSON data via NATS. But Kafka, S3 and many more common systems are supported out of the box. This reduces the number of moving parts needed to build a complete data platform.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  First impressions
&lt;/h2&gt;

&lt;p&gt;I always feel a bit intimidated by technology that is intended to operate at the scale of millions of events per second and petabytes of data.&lt;/p&gt;

&lt;p&gt;But it was surprisingly simple to get started with the &lt;a href="https://hub.docker.com/r/clickhouse/clickhouse-server/" rel="noopener noreferrer"&gt;Clickhouse Docker image&lt;/a&gt;. It's well documented. The whole documentation of Clickhouse is simple to read and navigate. And the CLI tools feel modern and intuitive to interact with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surprises we ran into
&lt;/h2&gt;

&lt;p&gt;When first reading about how fast Clickhouse is for analytical workloads, it feels like magic. Of course, they are not hiding that there are tradeoffs to get the system to be this performant. But reading about a system not having transactional guarantees and encountering it in practice are very different things. The following are what I wish I knew before getting started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Table Engines:&lt;/strong&gt; You have to &lt;a href="https://clickhouse.com/docs/en/engines/table-engines" rel="noopener noreferrer"&gt;select an engine&lt;/a&gt; for each table you create. And this is not only an afterthought. If you want to replace old values with new ones, you need a different engine than if you only append new data. And if you aggregate or summarize values, there are specific engines for that. I can't say I understand all available engines yet. It takes some time to understand which one to pick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encountering duplicate data:&lt;/strong&gt; The &lt;a href="https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree" rel="noopener noreferrer"&gt;ReplacingMergeTree&lt;/a&gt; table engine replaces existing rows and allows you to basically do UPSERT. But the old data will not be removed directly. Duplicate rows will only be removed asynchronously. Also, setting a primary key only affects how data is stored on disk, but doesn't guarantee unique values. There are ways to get unique query results, but it takes some extra care. You can ensure unique rows using &lt;code&gt;GROUP BY&lt;/code&gt; at query time and there are also settings like &lt;code&gt;FINAL&lt;/code&gt; and &lt;code&gt;optimize_on_insert&lt;/code&gt; that remove duplicates for a performance penalty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental Materialized Views are not always updated:&lt;/strong&gt; In Clickhouse, Materialized Views are incrementally updated automatically. This was a main selling point that convinced me that it can simplify the system by a lot. Materialized Views behave like insert triggers. The database client that does an &lt;code&gt;INSERT&lt;/code&gt; on a parent table receives an error if updating any materialized view failed. But there are actually no transactional guarantees around this and the data is still written to the parent table. We work around this by using a &lt;code&gt;ReplacingMergeTree&lt;/code&gt; as parent table to deduplicate data and resend failed events using NATS to retrigger the Materialized View.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Joins in Materialized Views are hard to get right:&lt;/strong&gt; Materialized Views support joins and sub-queries. But Incremental Materialized Views are implemented as triggers on a single parent table. If data in a joined table changes, the view is not updated. This is not directly mentioned in the documentation. I only later found a &lt;a href="https://altinity.com/blog/2020-07-14-joins-in-clickhouse-materialized-views#h-a-dive-into-the-plumbing" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; mentioning this. While the docs don't mention this limitation directly, &lt;a href="https://clickhouse.com/docs/en/guides/developer/cascading-materialized-views#combining-multiple-source-tables-to-single-target-table" rel="noopener noreferrer"&gt;they do mention&lt;/a&gt; a way to work around it: You can create multiple Materialized Views that write to the same table. If you use the &lt;code&gt;AggregatingMergeTree&lt;/code&gt; table engine, you can combine data from multiple parent tables by writing into separate columns. It's not the most intuitive to work with and think through the performance implications of it though.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No NATS Jetstream support:&lt;/strong&gt; I was amazed that we can integrate data from NATS without any additional infrastructure. After wondering why it doesn't work as expected, I found out that &lt;a href="https://github.com/ClickHouse/ClickHouse/issues/39459" rel="noopener noreferrer"&gt;Jetstream is not yet supported&lt;/a&gt;. So for the time being, we have a small ingest service to get the data guarantees we need.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The surprises we encountered are understandable tradeoffs that come with the chosen system design behind Clickhouse and luckily we can work around all of them.&lt;/p&gt;

&lt;p&gt;Overall, getting started with Clickhouse was a smooth experience. Let's see how the system is holding up a few months into the future.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>analytics</category>
      <category>database</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Advance of AI</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Sat, 14 Sep 2024 14:20:34 +0000</pubDate>
      <link>https://forem.com/jorinvo/advance-of-ai-498o</link>
      <guid>https://forem.com/jorinvo/advance-of-ai-498o</guid>
      <description>&lt;p&gt;As someone working in tech, it's hard to talk about anything other than AI these days. The progress we're seeing is intense. While a lot of it is hype, &lt;strong&gt;AI is already impacting many of our jobs and lives.&lt;/strong&gt; These changes are here to stay.&lt;/p&gt;

&lt;p&gt;The core ideas of AI are rooted in simple mathematics for identifying patterns in sample data. However, with sufficient data, these algorithms develop logical reasoning and creative expressions that can &lt;strong&gt;match —and often surpass— the best human experts&lt;/strong&gt;, especially in terms of speed.&lt;/p&gt;

&lt;p&gt;These algorithms are continuously improving their ability to understand and generate various forms of media, including text, images, video, and audio. Working with images and audio is crucial for interacting with the physical world. This capability will play a vital role in advancing robotics.&lt;/p&gt;

&lt;p&gt;For me, AI’s advancements around working with natural language are the most fascinating. &lt;strong&gt;Text and language are direct representations of human reasoning.&lt;/strong&gt; It feels as if machines can think just like a human.&lt;/p&gt;

&lt;p&gt;The direct consequence is that many jobs will look very different — and this transformation has already started. AI can now assist and automate various forms of knowledge work. As a software developer, I must drastically shift my approach to stay relevant. What we've seen so far is just the tip of the iceberg.&lt;/p&gt;

&lt;p&gt;AI is here to stay, whether we like it or not, and the times ahead will bring many changes. &lt;strong&gt;The world has always been changing, but the pace of change is now accelerating more than ever.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's not my place to judge whether the changes are good or bad. However, I know that &lt;strong&gt;trying to stop change is futile.&lt;/strong&gt; We cannot influence what happens but how it happens.&lt;/p&gt;

&lt;p&gt;We must raise awareness about the challenges accompanying AI technology advancements. It's crucial to &lt;strong&gt;educate people&lt;/strong&gt; on both the promises and risks of AI. We should also urge governments and organizations to &lt;strong&gt;support those at risk of being left behind&lt;/strong&gt; and to keep &lt;strong&gt;potential bad actors in check.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most crucial task now isn't to hastily improve and integrate AI into every aspect of life. Instead, we should &lt;strong&gt;focus on humans rather than machines.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The economic and political interests in leading the AI revolution are irresistible. These changes will happen too quickly for most of humanity to keep up. What we truly need are individuals who ensure these advancements happen with everyone's interests in mind.&lt;/p&gt;

&lt;p&gt;In the face of AI, we will gain a completely new understanding of &lt;strong&gt;what it means to be human.&lt;/strong&gt; We need to treat each other with the &lt;strong&gt;respect and dignity&lt;/strong&gt; we deserve, rather than allowing billions of people to become small cogs in machines that only benefit a select few.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Moving the Blog to Elixir</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Mon, 16 Oct 2023 08:47:22 +0000</pubDate>
      <link>https://forem.com/jorinvo/moving-the-blog-to-elixir-58gp</link>
      <guid>https://forem.com/jorinvo/moving-the-blog-to-elixir-58gp</guid>
      <description>&lt;p&gt;After running my &lt;a href="https://jorin.me/" rel="noopener noreferrer"&gt;blog&lt;/a&gt; with &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; hosted on &lt;a href="https://app.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; for nearly a decade, I decided to migrate to a custom solution. I think I resisted the urge to rewrite my personal site for long enough and I deserve to have some fun.&lt;br&gt;
I am not going to find an excuse. I wanted to do this in Elixir. I wanted to build all the details of a static website from scratch once. It feels empowering to understand everything. And everything I learned along the way is generally useful Elixir knowledge. There are no concepts specific to a single static side generator.&lt;/p&gt;

&lt;p&gt;With that out of the way, the reason why I am doing this now is that I plan to build more with Elixir in the near future and there will be more static sites too.&lt;br&gt;
I also wanted to learn the details of generating SEO-friendly meta tags, getting a high pagespeed score and everything else involved in the progress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@description}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Content.site_author()}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/index.xml"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/rss+xml"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;{Content.site_title()}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ROBOTS"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"INDEX, FOLLOW"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@title}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@description}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@og_type}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{"#{Content.site_url()}#{@route}"}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@title}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@description}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@title}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@description}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;og_type =&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;article&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"datePublished"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{format_iso_date(@date)}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"dateModified"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{format_iso_date(@date)}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"wordCount"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{@wordcount}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"keywords"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Enum.join(@keywords,&lt;/span&gt; &lt;span class="err"&gt;",")}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{Content.site_author()}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:section"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Software"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;:for=&lt;/span&gt;&lt;span class="s"&gt;{keyword&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:tag"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{keyword}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:published_time"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{format_iso_date(@date)}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:modified_time"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{format_iso_date(@date)}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the site does
&lt;/h2&gt;

&lt;p&gt;You can find all the code &lt;a href="https://github.com/jorinvo/me" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;.&lt;br&gt;
Elixir renders static HTML pages from Markdown and YAML content. Most pages are blog posts. There are additional pages such as the about page. And XML files are generated for the &lt;a href="///sitemap.xml"&gt;Sitemap&lt;/a&gt; and &lt;a href="///index.xml"&gt;RSS feed&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:urlset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;%{&lt;/span&gt;
     &lt;span class="ss"&gt;xmlns:&lt;/span&gt; &lt;span class="s2"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="s2"&gt;"xmlns:xhtml"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"http://www.w3.org/1999/xhtml"&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:loc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_url&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:lastmod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format_sitemap_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())}]}&lt;/span&gt;
     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:loc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:lastmod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format_sitemap_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)}]}&lt;/span&gt;
       &lt;span class="k"&gt;end&lt;/span&gt;
   &lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;XmlBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;XmlBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;I have to give a huge shout out &lt;a href="https://fly.io/phoenix-files/crafting-your-own-static-site-generator-using-phoenix/" rel="noopener noreferrer"&gt;Jason Stiebs' post on the fly.io blog&lt;/a&gt;. I followed the tutorial to use &lt;a href="https://hexdocs.pm/nimble_publisher/NimblePublisher.html" rel="noopener noreferrer"&gt;NimblePublisher&lt;/a&gt; to read markdown files, render them to HTML using Phoenix LiveView HEEx templates and integrate &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; into the setup.&lt;br&gt;
Having Tailwind available is something I really wanted. I plan to use it to simplify my hardly maintainable CSS.&lt;/p&gt;

&lt;p&gt;I also have to thank Hugo again. Having the original output generated by Hugo helped me to learn about setting proper meta tags, rendering the sitemap and generating the RSS feed.&lt;/p&gt;

&lt;p&gt;The setup includes a tiny Plug server to serve files during development and &lt;a href="https://github.com/falood/exsync/" rel="noopener noreferrer"&gt;ExSync&lt;/a&gt; auto-compiles the site when files change.&lt;/p&gt;

&lt;p&gt;Finally, the site is now deployed to Github pages using a &lt;a href="https://github.com/jorinvo/me/blob/main/.github/workflows/elixir.yml" rel="noopener noreferrer"&gt;Github action&lt;/a&gt;. This means one less separate tool to understand and maintain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;build_pages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_pages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;all_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all_posts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;about_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;reads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_reads&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;assert_uniq_page_ids!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;posts:&lt;/span&gt; &lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_posts&lt;/span&gt;&lt;span class="p"&gt;()}))&lt;/span&gt;
  &lt;span class="n"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"404.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;not_found_page&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
  &lt;span class="n"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;about_page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;about_page&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"archive/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;posts:&lt;/span&gt; &lt;span class="n"&gt;all_posts&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="n"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"reads/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reads_index&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;pages:&lt;/span&gt; &lt;span class="n"&gt;reads&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="n"&gt;write_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"index.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_posts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;write_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sitemap.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Render&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;render_posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;render_redirects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redirects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="n"&gt;render_reads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="ss"&gt;:ok&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the code can be as simple as it is because it is catered to my use case. It doesn't have to support thousands of pages or flexible content hierarchies. It only needs to do exactly what I want, nothing more, nothing less.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>website</category>
      <category>html</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Problem Solving</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Sun, 25 Jun 2023 11:19:25 +0000</pubDate>
      <link>https://forem.com/jorinvo/problem-solving-3jhj</link>
      <guid>https://forem.com/jorinvo/problem-solving-3jhj</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Software creates value by solving problems. But effectively solving problems is hard. How can we get better at it?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I have seen it many times in different teams and projects:&lt;br&gt;
We are certain our users need a new dashboard view. It will solve all their problems.&lt;br&gt;
A month later we are sure the users need 3 more filter options on the dashboard, then all their problems will go away.&lt;br&gt;
A year later users are still loading data into their custom Excel file and barely anyone touches the fancy dashboard we built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We create solutions without actually knowing what the problem is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We all want to create value for our users. We want to create solutions that truly solve their problems. We want happy users. We want to build features that remain useful and do not become a burden down the line.&lt;br&gt;
So how do we create solutions that effectively solve problems?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It all starts with really understanding what the problem is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The problem is not always what it seems and the most obvious solution might not be the best.&lt;/p&gt;

&lt;p&gt;Spend more time understanding the problem, its scope and the potential solutions.&lt;br&gt;
It will pay off many times over.&lt;/p&gt;

&lt;p&gt;Not only do you significantly increase the chance of building the right thing to start with - you enable yourself and your team to improve the product sustainably in the future.&lt;/p&gt;

&lt;p&gt;Capturing your thought also process creates a powerful artifact to work with in the future:&lt;/p&gt;

&lt;p&gt;No one gets everything right the first time. And that's fine.&lt;/p&gt;

&lt;p&gt;But you want to be able to look back and know which problem you tried to solve. What was the known context at the time? Which solutions did you consider? What was the reason to go with the chosen option?&lt;/p&gt;

&lt;p&gt;With answers to these questions at hand you don't have to guess why things are the way they are and you can improve the product with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework
&lt;/h2&gt;

&lt;p&gt;I like to follow a simple framework to guide me through the problem solving process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Problem Statement&lt;/strong&gt;: State the problem in a clear and concise sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt;: Describe the context. What do you know? What do you need to know? What matters? What can you exclude?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options&lt;/strong&gt;: List possible solutions. What options can you think of? What are the pros and cons?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision&lt;/strong&gt;: Decide for a solution. Write down the decision and reasoning behind it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Make sure you talk to actual users when establishing the context.&lt;br&gt;
Spent some extra time on listing options. Discuss options with others. Be creative. Think of combinations of solutions. Doing nothing is also always an option.&lt;br&gt;
Sleep over it for at least a night before making a decision.&lt;/p&gt;

&lt;p&gt;After you made a decision and implemented the solution, make sure to come back to it.&lt;br&gt;
It's fine to get things wrong. You are one step further in any case. You gained new knowledge about what doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem solving is an iterative process.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repeat until you get it right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mindset
&lt;/h2&gt;

&lt;p&gt;A framework can be a helpful tool to guide our thinking.&lt;br&gt;
But to become really good at problems solving we need to change our mindset.&lt;/p&gt;

&lt;p&gt;Create a culture that encourages people to &lt;strong&gt;ask questions&lt;/strong&gt;, challenge assumptions and explore alternatives.&lt;br&gt;
Learning, reflection and active listening need to become part of your daily work.&lt;br&gt;
Establish a team of people with diverse backgrounds and perspectives.&lt;br&gt;
Encourage people to think of interdependencies, feedback loops and long-term implications when choosing a solution.&lt;/p&gt;

&lt;p&gt;All this is not a simple undertaking and takes time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start by making time for it and lead by example.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Software development is problem solving. Focus on understanding problems.&lt;/p&gt;

&lt;p&gt;Define the problem and the context.&lt;/p&gt;

&lt;p&gt;List multiple solution options and document your decision.&lt;/p&gt;




&lt;p&gt;If you like to learn more, I can highly recommend watching the talk &lt;a href="https://www.youtube.com/watch?v=fTtnx1AAJ-c" rel="noopener noreferrer"&gt;Design in Practice&lt;/a&gt; by Rich Hickey.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>development</category>
      <category>learning</category>
    </item>
    <item>
      <title>Migrating Data When You Never Erase History</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Wed, 28 Oct 2020 18:05:08 +0000</pubDate>
      <link>https://forem.com/jorinvo/migrating-data-when-you-never-erase-history-2cdk</link>
      <guid>https://forem.com/jorinvo/migrating-data-when-you-never-erase-history-2cdk</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Let's explore the considerations necessary when evolving temporal data over time.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Almost all actively used software evolves over time. Most software also includes certain data that is stored long-term. The database behind such applications tends to be slower-changing than any particular application logic. However, the underlying data still evolves over time as requirements change. Migrations of data are already a challenge. If you now persist the history of the data changes over time and your requirements evolve in time, you now have to consider these changes not only at a specific point in time but along the whole temporal dimension.&lt;/p&gt;

&lt;p&gt;To make any decision about migration strategies in a particular system, you have to understand the context the data is used in.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to skip sections and jump to the ones relevant to you.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep history?
&lt;/h2&gt;

&lt;p&gt;Many things become possible once you store change events and thus the previous state of a software system.&lt;/p&gt;

&lt;p&gt;One of the most obvious uses of historic data is implementing requirements for auditing and version control in critical applications.&lt;/p&gt;

&lt;p&gt;Having historic data also becomes invaluable for understanding and debuging complex processes. It is not only helpful for developers but also for users trying to understand the processes of the bigger system the software is used in.&lt;/p&gt;

&lt;p&gt;From a technical perspective building solid, fault-tolerant, distributed software that interacts with external systems becomes more feasible once you have a log of change events. You can replay the events at any time to recreate the state of different sub-systems which is a big help keeping everything in sync without running into race conditions.&lt;/p&gt;

&lt;p&gt;The value of storing historic data is huge and often the need for it will only arise later on. I argue that all software should be built this way by default. Ignoring history should be merely a possible performance optimisation for scenarios required that.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to store history?
&lt;/h2&gt;

&lt;p&gt;Storing historic data can be achieved in many ways. In a large-scale architecture it might be interesting to work with raw events. &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt; is a great store for raw events. Having raw events directly accessible as interface for interactions provides a lot of flexibility and control necessary to work at scale. However, it also requires building a lot of logic on top to recreate a view of the current state of the world.&lt;/p&gt;

&lt;p&gt;There are databases such as &lt;a href="https://www.datomic.com/" rel="noopener noreferrer"&gt;Datomic&lt;/a&gt; which is specifically designed for keeping all history of the data around. With Datomic the database takes care of keeping history while you can work with the current state of the system. The same queries you use to query the current state can then be used to query any previous point in time. Datomic also allows you to look at time ranges. It also provides functionality to follow the log of changes to work with the change events continuously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opencrux.com/" rel="noopener noreferrer"&gt;Crux&lt;/a&gt; is a temporal database that allows you to work with historic data in similar ways to Datomic, but Crux is not only a temporal database but a &lt;em&gt;bitemporal database&lt;/em&gt;. Crux not only stores the time you inserted data into the database, also referred to as &lt;em&gt;transaction time&lt;/em&gt;, but also allows you to specify another time dimension called &lt;em&gt;valid time&lt;/em&gt;. By differentiating what the system knew at a certain transaction time and what was the actual state of the world at a given valid time, you can accurately update not only the current state but also knowledge about the state in the past. This can be a crucial tool when you need to be able to correct historic data and at the same time have the requirement of knowing precisely what change was done at what time. I would argue that this is the only way to accurately represent historic data when you want to work at the level of entities and attributes and not at the level of raw change events.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;SQL standard&lt;/strong&gt; also specifies the concept of &lt;em&gt;temporal tables&lt;/em&gt;. &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-ver15" rel="noopener noreferrer"&gt;SQL Server&lt;/a&gt; implements this and allows for automatic versioning of tables in what they call &lt;em&gt;system time&lt;/em&gt;, which we described before as transaction time. This is internally implemented by having a separate history table, plus providing additional syntax to query data at a point in the past. &lt;a href="https://mariadb.com/kb/en/temporal-data-tables/" rel="noopener noreferrer"&gt;MariaDB&lt;/a&gt; is another SQL database that supports temporal tables. MariaDB goes even further and also supports bitemporal data using a second time dimension here referred to as &lt;em&gt;application time&lt;/em&gt;, which matches the earlier introduced valid time.&lt;/p&gt;

&lt;p&gt;Apart from existing implementations of temporal and bitemporal datastores, there are also use cases with needs for scale and operational characteristics, which might be best served by implementing a custom solution on top of another storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep history forever?
&lt;/h2&gt;

&lt;p&gt;Different use cases of historic data demand different strategies for data retention. Ideally you can always keep all data around forever. Unfortunately that can become unfeasible in certain situations. In that case you have different options to reducing the number of data points you have to retain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Completely erase old data after a certain expiration time or through other expiration policies.&lt;/li&gt;
&lt;li&gt;Delete the data itself but keep meta data around.&lt;/li&gt;
&lt;li&gt;Keep old data in lower granularity. For example, only keep one data point per day per entity for data older than a week.&lt;/li&gt;
&lt;li&gt;Merge multiple data points into one using a merge logic specific to your needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An important property to consider when planning a retention strategy is the ability to recreate the current state from the log of historic data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you use historic data?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do you control the code that receives the data? Can you update it in sync with changes to the data schema?&lt;/li&gt;
&lt;li&gt;Are the use cases of the current view of the data and historic data separate from each other?&lt;/li&gt;
&lt;li&gt;Often most parts of an application use only the current state and historic data is only used in a small number of places. Can you require a stricter schema for the current state and a looser one for historic data?&lt;/li&gt;
&lt;li&gt;In case you have two time axis, such as valid time plus transaction time, there might be different features building on each of them. Likely there are more use cases of valid time than transaction time.&lt;/li&gt;
&lt;li&gt;Do you know the data schema version that downstream consumers support? Can you remove support for deprecated data features once all consumers are on a recent version?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changes to the data do you need to make?
&lt;/h2&gt;

&lt;p&gt;The most important thing to consider when changing data is that you want to &lt;strong&gt;avoid breaking changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the logic of your system evolves it sometimes becomes necessary to change the shape and thus the schema of the data the system works with. This can come in different forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new optional attribute is a non-breaking change. Explicit, maybe even namespaced, attribute names help to keep clarity and avoid conflict.&lt;/li&gt;
&lt;li&gt;Adding a required attribute or making a previously optional attribute required, is a breaking change.&lt;/li&gt;
&lt;li&gt;Can you introduce a new attribute instead of renaming an existing one? Mark the old one as deprecated, but leave it there for consumers of historic data.&lt;/li&gt;
&lt;li&gt;If you must change the type of an attribute, can you extend it instead? Instead of changing from &lt;code&gt;string&lt;/code&gt; to &lt;code&gt;integer&lt;/code&gt;, can you use a union type of &lt;code&gt;string or integer&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Removing data will break downstream code that depends on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes we don't have to change the shape of data but the data itself. This is especially common if there has been an error in the system and we have to correct or even delete erroneous data.&lt;br&gt;
Changes to the data itself can be seen as normal updates to the current state of the sytem. A temporal storage then gives you a record of everything for later reference. A bitemporal system also allows you to correct errors in historic data while keeping a record of everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do data changes only apply to current state of data or also historic data?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you introduce a new attribute, do you only need it in the current view of the data or do you need to generate it also for historic data? Is it even possible to reconstruct the attribute for past data points?&lt;/li&gt;
&lt;li&gt;If you work with valid and transaction time, is it enough to introduce a new attribute for historic data in valid time or do you also need to rewrite transaction time?&lt;/li&gt;
&lt;li&gt;When removing data, can you keep meta data or do you need to remove every trace that a certain event ever happened?&lt;/li&gt;
&lt;li&gt;For deletion or modification of historic data, how does that propagate to downstream, maybe external, consumers?&lt;/li&gt;
&lt;li&gt;Do you explicitly need to remove a specific attribute from all of history or can you model data in a way that you can delete whole documents instead? Depending on the system, deleting whole documents might be simpler to implement. How does that affect the schema of the documents in your system and the relations between them?&lt;/li&gt;
&lt;li&gt;Before a data type schema becomes too complex, can you introduce a new data type instead? Or maybe you want a concept of versioning for types? You can save a specific &lt;code&gt;version&lt;/code&gt; attribute with every entity and use that to handle different types through this. Or can you even version attributes themselves?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>crux</category>
      <category>kafka</category>
      <category>sql</category>
    </item>
    <item>
      <title>Two Kinds of Software</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Thu, 20 Feb 2020 21:54:11 +0000</pubDate>
      <link>https://forem.com/jorinvo/two-kinds-of-software-3dg6</link>
      <guid>https://forem.com/jorinvo/two-kinds-of-software-3dg6</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Let's talk about the difference between two kinds of software systems.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This might be controversial but for me it helps to use the terms &lt;em&gt;Software Engineering&lt;/em&gt; and &lt;em&gt;Software Development&lt;/em&gt; to differentiate between two fundamental modes of creating software.&lt;/p&gt;

&lt;p&gt;That said, neither of the two directions is right or wrong and both are necessary to create actually useful software systems. Basically every software system is made up of software of both kinds. The systems are often highly entangled which makes it so easy to ignore the fundamental different modes of thinking they require.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software Engineering
&lt;/h2&gt;

&lt;p&gt;Engineering is about &lt;em&gt;designing, building and working with machines&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Software engineering should strive for the same rigorousness as other engineering disciplines. It should use scientific methods and formalism to ensure correctness of software.&lt;/p&gt;

&lt;p&gt;Engineering is the right approach for creating a solid foundation:&lt;/p&gt;

&lt;p&gt;If you implement internet protocols, build a database, write a compiler, architect a distributed key-value store, create a file system - these systems require an engineering approach and we should strive for perfecting correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software Development
&lt;/h2&gt;

&lt;p&gt;The concept of development is about &lt;em&gt;bringing something into existence&lt;/em&gt; and it entails the notion of &lt;em&gt;growing something into a more advanced form&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I like to see software development as the discipline we use to create software systems that interact with the rest of the world.&lt;/p&gt;

&lt;p&gt;Whenever we can encapsulate and precisely define a problem, an engineering approach will be very effective for producing software of high quality.&lt;/p&gt;

&lt;p&gt;However, once a system interacts with humans or other external systems in the world, things suddenly are much less fixed. It becomes hard to foresee all possible interactions and inputs our system will receive from the outside world. External systems can exhibit faulty behaviour or can even become malicious. Being situated in an ever-evolving world, the requirement we expect our systems to fulfil are in constant flux and change fundamentally over time.&lt;/p&gt;

&lt;p&gt;Rigorous planning a correct solution is not a viable option when requirements are extremely dynamic and it is not something that can be achieved with any reasonable amount of effort.&lt;/p&gt;

&lt;p&gt;We need different methods when working on such systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first step is to accept that there will be mistakes and optimise your system to adapt and learn from errors.&lt;/li&gt;
&lt;li&gt;Keeping your system flexible becomes one of the top priorities when decisions become invalid at any moment.&lt;/li&gt;
&lt;li&gt;Being able to empirically try out different experiments is a useful tool when you cannot foresee the consequences of your system's behaviour upfront.&lt;/li&gt;
&lt;li&gt;Having visibility through monitoring your software in production becomes even more crucial when you are unable to verify correctness upfront.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating social networks and online shops, but also building resource planning systems or working in financial markets - these systems keep on changing and should be development dynamically over time.&lt;/p&gt;




&lt;p&gt;Once we accept that there is such a difference between the kinds of software systems we build, then we can making better decisions on how to build them.&lt;/p&gt;

&lt;p&gt;For the first category, putting effort into upfront planning and all sort of efforts towards proving correctness can be very effective.&lt;/p&gt;

&lt;p&gt;For the second category, instead of finding a perfect solution upfront we start with our best guess. The most important property of such a system is to build it in away that allows us to adapt it quickly and to observe it over time.&lt;/p&gt;

&lt;p&gt;If we take apart our work, identify different sub-systems and judge them separately, then we can make more educated decisions for selecting the most effective methods and tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need a different mindset to build a bridge than you do for planning a city.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>systems</category>
      <category>engineering</category>
      <category>development</category>
      <category>tech</category>
    </item>
    <item>
      <title>Crux as General-Purpose Database</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Thu, 12 Dec 2019 21:53:32 +0000</pubDate>
      <link>https://forem.com/jorinvo/crux-as-general-purpose-database-kk3</link>
      <guid>https://forem.com/jorinvo/crux-as-general-purpose-database-kk3</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;a href="https://opencrux.com/" rel="noopener noreferrer"&gt;Crux&lt;/a&gt; is an innovative database in the Clojure world. What follows is an experience report.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Crux is open source, &lt;a href="https://en.wikipedia.org/wiki/Bitemporal_Modeling" rel="noopener noreferrer"&gt;&lt;em&gt;bitemporal&lt;/em&gt;&lt;/a&gt;, highly scalable and built on solid foundations with &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; and &lt;a href="https://rocksdb.org/" rel="noopener noreferrer"&gt;RocksDB&lt;/a&gt; as default storage.&lt;br&gt;
It is very flexible and provides low-level building blocks to allow you to build exactly the database you need for your use-case.&lt;/p&gt;

&lt;p&gt;I believe that having time as first class concept in your database should be the default.&lt;br&gt;
It is much easier to have the concept of time built-in from the beginning and only build specialized solutions for the parts where performance becomes a problem.&lt;br&gt;
Nowadays computers are powerful enough that in most cases performance is not the issue and the focus should be on simplicity and correctness first.&lt;/p&gt;

&lt;p&gt;By coincidence I started a toy project, &lt;a href="https://github.com/jorinvo/letsdo.events" rel="noopener noreferrer"&gt;letsdo.events&lt;/a&gt;, to write more Clojure in the spring of this year.&lt;br&gt;
Just after I started, Crux announced its public beta. Naturally I had to give it a try.&lt;/p&gt;

&lt;p&gt;Since letsdo.events is mostly a toy project to play with different ideas.&lt;br&gt;
I took this opportunity to test Crux for a normal, standard application.&lt;br&gt;
The goal was to see if Crux hides the overhead of bitemporal storage well enough&lt;br&gt;
to make it viable as a default approach even when it is not an initial requirement of the application.&lt;/p&gt;

&lt;p&gt;To start out I chose to only persist using RocksDB which allows to pack the whole system in a single &lt;em&gt;.jar&lt;/em&gt; file and deploy it as a single process.&lt;br&gt;
Once the storage needs to scale, it would be trivial to switch to Kafka as storage backend.&lt;br&gt;
Since all the data is simply a log of transactions and documents, replaying them into a new storage would be straight forward.&lt;/p&gt;

&lt;p&gt;Because of the flexibility Crux gives you, it does not necessarily have all the tools you would expect from a database built-in.&lt;br&gt;
Surely there will be more tools and patterns in the ecosystem of Crux once it has been around for a bit longer.&lt;br&gt;
But for now, here are a few things I built in &lt;a href="https://github.com/jorinvo/letsdo.events/blob/master/src/lde/core/db.clj" rel="noopener noreferrer"&gt;the application's database layer&lt;/a&gt;:&lt;/p&gt;
&lt;h2&gt;
  
  
  Single Transactor
&lt;/h2&gt;

&lt;p&gt;Enforcing constraints such as uniqueness is built into many databases.&lt;br&gt;
The simplest way to achieve this outside the database is to only allow a single write at a time.&lt;br&gt;
Reads can still happen in parallel.&lt;/p&gt;

&lt;p&gt;I created a &lt;code&gt;tx&lt;/code&gt; helper which is used for all writes to the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;})]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;exists-by-attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:email-taken&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:user/email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;:id&lt;/code&gt; key
&lt;/h2&gt;

&lt;p&gt;Crux expects the ID it uses internally under the namespaced key &lt;code&gt;:crux.db/id&lt;/code&gt;.&lt;br&gt;
This is a good pattern to not conflict with any domain-specific data.&lt;br&gt;
However in my business logic I always ended up wanting to use this same ID without coupling the business logic to Crux.&lt;/p&gt;

&lt;p&gt;So in letsdo.events all database functions convert the ID to &lt;code&gt;:id&lt;/code&gt; transparently using these helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crux-&amp;gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rename-keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:crux.db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id-&amp;gt;crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;rename-keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:crux.db/id&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with document time
&lt;/h2&gt;

&lt;p&gt;It is totally fine in Crux to have normal &lt;code&gt;:created&lt;/code&gt; and &lt;code&gt;:updated&lt;/code&gt; as part of your documents.&lt;br&gt;
And in most cases that is probably a good idea for performance.&lt;br&gt;
But you don't need to have these timestamps since Crux also tracks time in its history.&lt;/p&gt;

&lt;p&gt;It might not always be a good idea to do this but I created a helper which takes a list of document IDs&lt;br&gt;
and returns the documents with additional &lt;code&gt;:created&lt;/code&gt; and &lt;code&gt;:updated&lt;/code&gt; keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list-by-ids-with-timestamps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="no"&gt;:keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;::crux&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crux&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
                 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;assoc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux-&amp;gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:crux.db/valid-time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="no"&gt;:updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:crux.db/valid-time&lt;/span&gt;&lt;span class="p"&gt;)))))))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common database operations
&lt;/h2&gt;

&lt;p&gt;The primitives Crux provides for transactions and the datalog language for querying are simple yet powerful.&lt;br&gt;
Still, for an application to interact with the database I prefer to hide the details of the how the database works behind plain functions.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jorinvo/letsdo.events/blob/master/src/lde/core/db.clj" rel="noopener noreferrer"&gt;&lt;code&gt;lde.core.db&lt;/code&gt;&lt;/a&gt; namespace contains helpers for operations such as create, update, delete, list, get by id, get by attribute, count, exists.&lt;/p&gt;

&lt;p&gt;However, the database layer still allows to do plain datalog queries which is very useful for join-like queries likes this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;defn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get-organizer-names-by-event-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event-id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;db/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;'?name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:organizer/event&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:organizer/user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:user/name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;?name&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="no"&gt;:args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;'e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event-id&lt;/span&gt;&lt;span class="p"&gt;}]})&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="n"&gt;not-empty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Global queries
&lt;/h2&gt;

&lt;p&gt;Another great development feature of Crux has been the ability to easily query across all your data.&lt;/p&gt;

&lt;p&gt;In relational databases this would involve writing out all tables and joining them together in a gigantic query,&lt;br&gt;
but with Crux and datalog there is no strict separation between data types.&lt;/p&gt;

&lt;p&gt;You can easily query attributes across all documents which makes it very simple in development to understand the data in your system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="c1"&gt;; All data in db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:lde.core.db/crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:crux.db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]]})&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:lde.core.db/crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very handy for all sorts of global statistics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight clojure"&gt;&lt;code&gt;&lt;span class="c1"&gt;; All attributes in db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:lde.core.db/crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;:find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="no"&gt;:where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;'&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;:crux.db/id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]]})&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/entity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crux/db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;:lde.core.db/crux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;%&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mapcat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nb"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;It has been fun to work with Crux for this project.&lt;/p&gt;

&lt;p&gt;Getting started with a new application with a schemaless database also made things easier.&lt;br&gt;
I can imagine that for mature projects it would be helpful to enforce more constraints on write in the database or via &lt;a href="https://clojure.org/guides/spec" rel="noopener noreferrer"&gt;clojure.spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So far I haven't tested out Crux at scale but the existing examples look very promising.&lt;/p&gt;

&lt;p&gt;The pluggable architecture is one of biggest strong points of Crux and makes it very flexible to switch to a different storage backend or index implementation.&lt;/p&gt;

&lt;p&gt;The biggest challenge will be to integrate with the world people are familiar with since Clojure, EDN and datalog are still far from mainstream at this point.&lt;br&gt;
But with the HTTP API, integrations such as Kafka Connect, potential plans for an SQL-like frontend and detailed documentation, the future looks bright.&lt;/p&gt;

&lt;p&gt;I do believe having time built-in from the start is a huge advantage for any new project and will become the norm in the future.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://unsplash.com/photos/oyXis2kALVg" rel="noopener noreferrer"&gt;image credit&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>database</category>
      <category>kafka</category>
      <category>functional</category>
    </item>
    <item>
      <title>Fixing the World With Software</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Sun, 30 Jun 2019 15:29:35 +0000</pubDate>
      <link>https://forem.com/jorinvo/fixing-the-world-with-software-5gk9</link>
      <guid>https://forem.com/jorinvo/fixing-the-world-with-software-5gk9</guid>
      <description>&lt;p&gt;There are so many big problems the world faces today. We hear about them in the news every day.&lt;br&gt;
You want to do something good in the world. You want to make a change. But you like building software.&lt;br&gt;
What can a software developer do to &lt;em&gt;make the world a better place&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Probably most of humanity had big problems in their time. In some way or another we might always fight for survival, for our existence. Uniquely nowadays is the global scale of our problems. The challenges we are facing have never been more complex and as part of our information-driven society the media constantly keeps reminding us of the threads we are facing. At the same time our abilities and the reach of our actions has never been greater.&lt;/p&gt;

&lt;p&gt;I mostly see three obvious challenges nowadays:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keeping the global human society in order to prevent fatal conflicts between different groups of humans.&lt;/strong&gt; Even with free and diverse societies we need to find ways to solve conflicts peacefully. Modern warfare has advanced to a point where any wrong action could make the whole planet uninhabitable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finding ways to control human health and prevent epidemics.&lt;/strong&gt; We still understand surprisingly little about the biology we are made of. A single virus could wipe out all of humanity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managing to live in harmony with our environment.&lt;/strong&gt; The impact humans have on the planet has become so big that we have to find ways to keep our planet's ecosystem in balance. We need to find sustainable ways to deal with &lt;a href="http://worrydream.com/ClimateChange/" rel="noopener noreferrer"&gt;carbon emissions&lt;/a&gt;, air pollution, energy consumption, waste, water pollution, food production and many other issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most frightening thing with these issues is that they seem too big for anyone to solve and it is difficult to even know where to start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.johnkay.com/" rel="noopener noreferrer"&gt;John Kay&lt;/a&gt; has some great advice for achieving complex goals:&lt;/p&gt;

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

&lt;p&gt;But how can software help approaching these problems?&lt;/p&gt;

&lt;p&gt;These issues seem to be only solvable by politicians and policy makers, scientists and engineers, company managers and journalists.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And this, I think, is the key:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I don't believe software can change the world.  Humans can.&lt;/strong&gt; I think our role as software developers is to support the ones that can make a change. We build tools and platforms to enable others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are building software and want to tackle critical issues, ask yourself who you are building software for. Think about how you can enable others.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://unsplash.com/photos/Q1p7bh3SHj8" rel="noopener noreferrer"&gt;(cover photo by NASA)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>motivation</category>
      <category>technology</category>
      <category>life</category>
    </item>
    <item>
      <title>Trends of the 21st Century</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Sat, 02 Mar 2019 16:06:00 +0000</pubDate>
      <link>https://forem.com/jorinvo/trends-of-the-21st-century-3e8e</link>
      <guid>https://forem.com/jorinvo/trends-of-the-21st-century-3e8e</guid>
      <description>&lt;p&gt;&lt;em&gt;To define our own role and to know what to do next, it helps to sometimes take a break and get an idea of the bigger picture of what's happening around us.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I collected a few observations of patterns I see happening around me. Nothing here is novel but how often do we stop and question things? Noticing is the first step. Then it is up to us to decide what all this means.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cultures mix more and more.&lt;/strong&gt;
Air travel is common. International tourism keeps growing. Communication is easier than ever thanks to digital technology. Cultures become more similar to each other through global media. Moving to another country is not a big. Cuisine has never had this much variation. Global markets give us access to goods from all across the world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Urbanisation is still happening.&lt;/strong&gt; It is not a new trend. People move to cities. Cities grow. Cities have very different culture to country side. Cities become more international. Major cities of the world become quite similar while culture in countryside still has bigger differences. Some mega cities have bigger political and economic power than whole countries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language consolidation accelerates.&lt;/strong&gt; Everyone has to learn English nowadays. The internet is very dominated by the English language. Sciences and lots of other knowledge is published primarily and often solely in English. Events use English as default language. Other languages like Chinese, Spanish, French, Arabian dominate some local niches, but smaller languages don't even have the vocabulary for many things anymore since they simply borrow from English; they keep being languages for communication in everyday life but not for business and science.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Economy is global by default.&lt;/strong&gt; More and more businesses don't depend on location. Teams can work distributed from any location. The default for companies is to target a global market. Competition also becomes global. Economies of scale make many businesses only feasible when targeting a global audience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Innovation is accelerating at speeds we can't comprehend.&lt;/strong&gt; Computers, the internet and so much other technology has been invented in the last couple of years. And those tools have enabled us to innovate at ever increasing speed. Being connected to all knowledge globally gives us so much leverage and power. We can't even imagine how the world will look like ten or twenty years from now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diversity is increasing.&lt;/strong&gt; Even if we destroy cultures, traditions and whole species, the human-created variety is ever increasing. People with very different lives, cultures and levels wealth are living together. Old and new culture and technology are part of our lives. We need to create an environment that can accommodate all those differences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right wing parties are gaining power.&lt;/strong&gt; With accelerating change and global competition comes a lot of uncertainty. People are afraid of loosing their dominant position in economy and culture and want to feel safe. They start voting for whoever promises to protect them. Big changes don't happen without resistance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The whole globe is completely dominated by humans at this point.&lt;/strong&gt; Humanity keeps growing and expanding. Space is getting more scars. Territorial conflicts are happing. Humans transform the environment to their own will. We already altered our environment up to a point that there is no turning back. We have to figure out how to control the effects of our doings. For us humans to survive on this planet, we have to redefine our relationship with the planet.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>motivation</category>
      <category>career</category>
      <category>technology</category>
      <category>life</category>
    </item>
    <item>
      <title>Not a Craftsmanship</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Thu, 17 Jan 2019 19:31:23 +0000</pubDate>
      <link>https://forem.com/jorinvo/not-a-craftsmanship-6ch</link>
      <guid>https://forem.com/jorinvo/not-a-craftsmanship-6ch</guid>
      <description>&lt;p&gt;&lt;em&gt;There is absolutely nothing new in here. It is simply me slowly understanding the things people have been saying all along. Maybe sharing this does not even help others. But writing things down helps myself. After all we cannot really learn through theory alone. We need to experience for ourselves. There are many ideas mixed up in here. But they are all related. Feel free to get in touch to elaborate on some part.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The longer I am in this profession the more I understand what all those old people are talking about.&lt;br&gt;
One thing I realize more and more, is how much we overvalue code as engineers.&lt;/p&gt;

&lt;p&gt;Code feels very valuable since it is our creation. At some point we need to write code to make something become real. It feels very powerful.&lt;/p&gt;

&lt;p&gt;But code is cheap in terms of the time and energy you have to put into writing code. If you compare actually writing code to planning what to do, understanding a business domain, understanding the technology options, understanding the process we have to go through while developing a product - writing code will take the least of your time.&lt;/p&gt;

&lt;p&gt;If you are working on a product that will be around for a while and not dead right away, chances are that the code you write today is more a liability than an asset to you in the future. Much of the code we write will not be around for too long. Rather soon we will have better understanding of the domain, have found a more suited solutions, requirements changed or what we were creating was not that useful to the world in the first place.&lt;/p&gt;

&lt;p&gt;When starting out programming I felt like it is about perfection. I was intrigued by creating this little perfect world where everything is logical. I felt like when I just search and experiment long enough I will distill the perfect solution. It was a beautiful world to live in.&lt;br&gt;
I guess that others had similar experiences. It is very easy to be tricked by the nature of our site projects, student assignments and so on. They are very different to working on a living, evolving product that interacts with the real world.&lt;/p&gt;

&lt;p&gt;The reality I experienced after is that all choices are compromises. Some part of your project will always be legacy code which you would like to rewrite and improve but you don't have the time to do so since priorities matter and this old, imperfect code still works for now and it is cheaper in terms of time spend to wait before changing it.&lt;br&gt;
How did this code end up in this imperfect state? Didn't you come up with the best solution you could back when you wrote it? I'm sure I tried my best. But requirements might have changed, I might have learned new things about the problem we were solving back then or maybe when writing the code we did a trade-off to invest not too much time here.&lt;br&gt;
There is something very fundamental to this process: As you solve a problem, you will learn from the solution. The first solution is very likely to not be the most suitable one. Sometimes - or rather, too many times, you also only realise after that you solved the wrong problem in the first place.&lt;/p&gt;

&lt;p&gt;The most important measurement of “good” software design is to make it easy to change in the future for yet unknown requirements. Rarely any software is something static we can perfect like creating a sculpture or building a guitar. Creating software is a process. I have the suspicion that by using craftsmanship as metaphor for thinking about software development we might do us more harm than good.&lt;br&gt;
A more fitting image would be politics or economics:&lt;br&gt;
We saw in the soviet union what happens if you try to plan a perfect system. Think about all the failed planned cities out there. Politics and software are too complex to plan ahead. The only way to build solid systems of this complexity is to grow them organically. So instead of focusing on perfecting the result (the code) we should focus on optimizing the process.&lt;br&gt;
Another image I like is a botanists taking care of a garden:&lt;br&gt;
Take inspiration from organic systems out there. Think about how evolution allows nature to come up with such complex organic systems: Create new experiments, keep the good ones, ignore the bad ones.&lt;br&gt;
We must not care too much about each of these experiments. Don't get attached to the code.&lt;br&gt;
Build it in a loosely coupled manner so changing parts is simple. Make throwing away parts simple.&lt;/p&gt;

&lt;p&gt;We need to focus more on understanding and optimizing the process of how we create software.&lt;br&gt;
Literal code, code comments and so on are not enough documentation if the very structure of the code is very temporarily.&lt;br&gt;
We need to learn documenting the process we are going through.&lt;br&gt;
Version control for our code is one step in the right direction. But it is only on the lowest level of granularity.&lt;br&gt;
We need to document what we are doing. If we look at it again in the future, we need to understand the context for why and how we did something. What where our assumptions back then? What where the constrains? How do they differ from where we are now?&lt;/p&gt;

&lt;p&gt;Nowadays programming education is very much focused on the code. And it's not just universities. Education is a life-long process. The education we have in the industry in form of articles, books, conferences - it's all very focused on code. We need more tools for organizing processes. Teach programmers how to write down their reasoning, teach us how to draw diagrams, teach us how to experiment and throw away code, teach us how to call the right trade-offs for decisions. I realize that these things are very difficult to teach in an academy environment. Maybe that's why traditional education is so utterly useless for a job as a programmer in the industry. These trades are best learned in a realistic surrounding where we can gather experience and learn from each other. Share your stories with each other. Don't just show me what you build. Way more interesting is the how. Why did you end up with this solution? What was the process you went through to get there? What else did you consider in the process? These are my favorite kinds of exchange of thoughts.&lt;/p&gt;

&lt;p&gt;Make it easier for us to observe what our systems are doing. Be aware that systems exist over time. Make our systems observable through monitoring, logging, metrics. Collect data to understand changes over time. See how your system evolves. Catch up with the trends. Remove or update outdated parts. What is great today might be utterly useless tomorrow. Focus on what is important at the moment: If you realize an API endpoint is not in use anymore, you know that next time you would have to touch it, you might simply delete it instead of bothering to update it. If you realize a feature is under heavy use, invest in it. Optimize performance when necessary, improve the feature when people are struggling with it.&lt;/p&gt;

&lt;p&gt;Be aware that your processes and your product emerge from your organizational structure. If you have a hierarchical organization where decisions are made top-down, you will be planning systems instead of letting them evolve naturally. Individual actors need to have freedom to make their own decisions independent from each other.  However they still need to communicate with each other and understand each other. Build organizations with flat hierarchies and loosely coupled teams. Build mechanisms for communicating with each other.&lt;/p&gt;

&lt;p&gt;Do this on an organizational level by sharing stories about your work with each other. You can do this privately in your company: Internal blogs and talks are great.&lt;/p&gt;

&lt;p&gt;On a technical level, to enable two parties to build software in a loosely coupled way that works together, the parties need to come together and define a contract for the communication of their software. As long as they stick to that contract, they are free. Have good tools for defining and enforcing contracts.&lt;/p&gt;

&lt;p&gt;All these things are where the complexity in software comes from. Stop caring so much about the code you write.&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>The Cloud Is the New OS - A Developer's Perspective</title>
      <dc:creator>jorin</dc:creator>
      <pubDate>Thu, 29 Nov 2018 18:30:57 +0000</pubDate>
      <link>https://forem.com/jorinvo/the-cloud-is-the-new-os---a-developers-perspective-4mbk</link>
      <guid>https://forem.com/jorinvo/the-cloud-is-the-new-os---a-developers-perspective-4mbk</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;It already happened a few times in the history of computing that the level of abstraction the majority of us work on has been raised.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are, and probably always will be, people that write and understand assembler. But most developers don't worry about the exact instruction they send to their CPU on a day to day basis. Most developers nowadays don't even worry about allocating memory manually. &lt;a href="https://en.wikipedia.org/wiki/System_call" rel="noopener noreferrer"&gt;Syscalls&lt;/a&gt; to talk to our Operation System is the lowest level the majority of developers has in mind. Mostly it's not even syscalls but an API which the language runtime provides.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I think we are close to a point where the general level of abstraction of computing will be raised once more:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My prediction is that in the near future it will be normal to not think about which physical machine the program we are writing is running on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are moving to the cloud.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Of course this is not a new idea.&lt;/em&gt; I never have new ideas. But it took me a while to realise the implications of moving to the cloud.&lt;/p&gt;

&lt;p&gt;Also please forgive me for using such a buzzword - Cloud is the best description for software with invisible hardware that I have heard of.&lt;/p&gt;

&lt;p&gt;From a consumer's point of view this doesn't sound like a new idea. It has become totally normal to have all your files, all your data somewhere in the internet and access it from all your devices whenever you want.&lt;/p&gt;

&lt;p&gt;People stopped caring where the cloud actually physically is already a long time ago &lt;em&gt;(this is not completely true: here in Germany everyone is still worried and trying hard to find the cloud)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Our social and working tools have basically all moved already.  The laptops and phones we own don't need to offer much. They are becoming commodity. They simply provide a window to interact with the cloud.&lt;/p&gt;

&lt;p&gt;In some cases such as gaming the hardware you own is still critical but personal, high-end gaming PCs are also &lt;a href="https://en.wikipedia.org/wiki/Games_as_a_service" rel="noopener noreferrer"&gt;a thing of the past&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For consumers and workplaces the convenience of not having to worry about hardware is such a huge gain, it was an easy move. People just don't care about how technology works. They don't want to know. They don't want to bother. They want their services and tools to simply work and do their job. Let someone else do the maintenance.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Having control over your computer is simply a burden for most people.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But we developers like technology&lt;/strong&gt;. We want to be in full control over our system and our data, right? I doubt it. Not the majority at least. We are also simply humans. We own or work for companies that try to run a business. If there is a more efficient way and the gain is big enough, we also move.&lt;/p&gt;

&lt;p&gt;Our production systems are largely running in the cloud already. We rent virtual servers from Amazon, Google, Microsoft. We push static content to CDN services. More and more of the functionality we need is now available as a service and we don't have to manage them ourselves anymore:&lt;br&gt;databases, search engines, firewalls, message queues, file storage, load balancers, web servers, build servers, test runners, registries, secret management, user authentication, …&lt;br&gt;There is a service for basically every generic piece of software out there &lt;em&gt;(if you see one missing, make sure you are the first one to build it!)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only thing that makes our software unique is our own, custom business logic of how we connect the pieces together.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Surprisingly it is generally still the case that we express business logic as software in the same programming languages and runtimes we used when we were thinking about a physical machines&lt;/em&gt;. We put great efforts into taking the existing environments we have in the form of operation systems such as Windows and Linux, faking a virtual environment that is identical to the OS we have from the past and executing our business logic in there nested in unnecessary layers of indirection with OSes in OSes in OSes, ten layers deep.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What if we let go of the past, let go of the control and create efficient platforms suited for expressing our business logic?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If we want to tackle more complex problems, we need a solid foundation&lt;/strong&gt;. We need to raise the level of abstraction. More complex business domains require us to be able to focus on them exclusively. We need to separate the work of building the foundation from the next layer.&lt;/p&gt;

&lt;p&gt;The foundation is important and critical to get right. &lt;em&gt;There will always be a need for great people to work on the foundation of computing&lt;/em&gt;. But the majority of problems developers are trying to solve today and an even bigger percentage of what we will have to solve in the future is not about technology in itself - it is all the problems in the world where technology has the potential to help us solve them. And there are plenty. We better get good at using technology effectively instead of fighting against the complexity we created through layers of unhelpful abstractions. Let's admit that it's time for a new abstraction. Let's use our existing languages, platforms and tools for what they are good at and build another solid, efficient layer as next abstraction.&lt;/p&gt;

&lt;p&gt;A programming platform for this level of expression has different characteristics than lower-level platforms:&lt;br&gt;It is mostly glue connecting lower-level components. There needs to be a good API of primitives to talk to available components. The concept of starting and stopping the system is handled by the underlying platform which allows this layer to be much more dynamic, loading only what is needed at this time. Performance critical tasks are most likely handled by lower-level primitives.&lt;br&gt;There are many more properties to define for such as system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did I mention before that we are giving up control? And you know that &lt;a href="https://a16z.com/2016/08/20/why-software-is-eating-the-world/" rel="noopener noreferrer"&gt;software is eating the world&lt;/a&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If we don't want all power to end up in the hands of a selected few that run these new platforms, we better make sure we get this right.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s not wait for the big infrastructure companies to come up with a platform. Let’s not wait for them to bind us to their specific ideas and their products. Let’s make sure we create a healthy system with a multitude of options and diversity of ideas.&lt;/p&gt;

&lt;p&gt;It is great to see alternatives to the big cloud providers showing up. Shout out to &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;netlify&lt;/a&gt;, &lt;a href="https://www.fastly.com/" rel="noopener noreferrer"&gt;fastly&lt;/a&gt;, &lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt; and friends! A healthy market needs competition.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Similar to the variety of operating systems and programming languages out there, we should aim for a variety of cloud platforms that are compatible with each other and share standards and protocols.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is how technology like email and the web became this widely adopted.&lt;/p&gt;

&lt;p&gt;The greatest efforts I see at the moment is the work happening under the umbrella of the &lt;a href="https://www.cncf.io/" rel="noopener noreferrer"&gt;Cloud Native Computing Foundation&lt;/a&gt;. We need more initiatives like &lt;a href="https://cloudevents.io/" rel="noopener noreferrer"&gt;cloudevents&lt;/a&gt; and &lt;a href="https://openmetrics.io/" rel="noopener noreferrer"&gt;openmetrics&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;One of the next big steps I see is in &lt;strong&gt;developer tooling&lt;/strong&gt;. Developers like to and need to be in control of their systems to test, monitor and debug them. It cannot be the right way that developers have to emulate a &lt;a href="https://github.com/kubernetes/minikube" rel="noopener noreferrer"&gt;cloud on their laptop&lt;/a&gt;. Instead, &lt;em&gt;let's move development environments into the cloud&lt;/em&gt;. And developers need good tools to be productive. A web interface is not enough. We should have realtime APIs on which we can build tooling on top of.&lt;br&gt;
There is still a lot of room in this space waiting to be filled. &lt;a href="https://codenvy.com/" rel="noopener noreferrer"&gt;Moving&lt;/a&gt; &lt;a href="https://coder.com/" rel="noopener noreferrer"&gt;IDEs&lt;/a&gt; into the &lt;a href="https://c9.io/announcement" rel="noopener noreferrer"&gt;cloud&lt;/a&gt; is only a very first step.&lt;/p&gt;

&lt;p&gt;Fellow developers, beware of the level of abstraction the problem you are trying to solve is on. Be ready to development in the cloud. Let's work together to make our next platforms a great place to work in.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technology</category>
      <category>programming</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
