<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dominic Black</title>
    <description>The latest articles on DEV Community by Dominic Black (@domblack).</description>
    <link>https://dev.to/domblack</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%2F857659%2Fa7d134e8-095b-4d55-84e3-f76fe4e3cef8.jpeg</url>
      <title>DEV Community: Dominic Black</title>
      <link>https://dev.to/domblack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/domblack"/>
    <language>en</language>
    <item>
      <title>Designing a config API for microservices applications built using Go</title>
      <dc:creator>Dominic Black</dc:creator>
      <pubDate>Thu, 10 Nov 2022 13:28:58 +0000</pubDate>
      <link>https://dev.to/encore/designing-a-config-api-for-microservices-applications-built-using-go-41ce</link>
      <guid>https://dev.to/encore/designing-a-config-api-for-microservices-applications-built-using-go-41ce</guid>
      <description>&lt;p&gt;&lt;strong&gt;We have a saying here at Encore: “Encore says yes”.&lt;/strong&gt;&lt;br&gt;
For us, this means no matter what an end-user wants to build, it should be possible with Encore. Even if the framework does not yet natively support that. For example, if you want to&lt;br&gt;
use a No-SQL database you should easily be able to wire that into your application.&lt;/p&gt;

&lt;p&gt;As Encore builds a standard Go binary and deploys it into your cloud account, you can easily connect to infrastructure&lt;br&gt;
already in your account. However, this raises a question: How do you configure your application to connect to different instances of that resource depending on if you are running locally, in a PR preview environment, or&lt;br&gt;
deployed to production?&lt;/p&gt;

&lt;p&gt;In May this year, we released our &lt;a href="https://encore.dev/changelog/app-metadata-api"&gt;metadata API&lt;/a&gt;. It allowed a running&lt;br&gt;
system to interrogate its environment, thus allowing for the code to switch between infrastructure instances. However,&lt;br&gt;
this was only an MVP to enable this. We knew we wanted a much more robust system, and started thinking about what a&lt;br&gt;
native configuration experience would look like.&lt;/p&gt;
&lt;h2&gt;
  
  
  What do developers want from a configuration system?
&lt;/h2&gt;

&lt;p&gt;As mentioned above, our primary use case was to allow different environments to have different integration setups. A typical example of this would be in a payment system to have your development and testing environments connect to a testing account with your card processor.&lt;/p&gt;

&lt;p&gt;But we also started thinking about other use cases people have for configuration in a backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storing very slow-moving metadata, such as metadata on support currencies and the number of decimal places for them.&lt;/li&gt;
&lt;li&gt;Being able to configure library code differently for different services. For instance, having rate limiters installed
on a per-service basis with different limits per service.&lt;/li&gt;
&lt;li&gt;Providing the ability to switch between code paths, so you can perform A/B tests &amp;amp; slow rollouts of new behaviour
without having to commit 100% of your traffic to the new code.&lt;/li&gt;
&lt;li&gt;Being able to turn things off during incidents or in tests, such as putting the system into a read-only mode or in
tests not sending emails to the test customer accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with everything we build into Encore, we wanted to ensure there was minimal cognitive overhead when using&lt;br&gt;
configuration. This means it should be easy to identify how the configuration was derived and where the settings came&lt;br&gt;
from. Config should feel natural, be easy to read, and never surprise you at runtime.&lt;/p&gt;

&lt;p&gt;For us, it was a prerequisite to ensure whatever we designed had compile-time safety, both in terms of type safety and completeness (i.e. config values couldn’t be missing in production).&lt;/p&gt;

&lt;p&gt;We wanted the ability for descriptive comments, to explain what the valid options are, and what the impact of those&lt;br&gt;
options is without having to read the code which uses that config field.&lt;/p&gt;

&lt;p&gt;Auditability and controls are essential. Many teams need to know who changed what and why, as well as potentially&lt;br&gt;
have an approval process for making config changes. Luckily as developers, we already have great tools for this! If&lt;br&gt;
we store configuration alongside the code in our source control, we get this for free. This necessitates that Encore reads the configuration from the source code, rather than a database.&lt;/p&gt;

&lt;p&gt;Finally, to support incident management, we knew we wanted to support live updating configuration, without having to&lt;br&gt;
commit, rebuild and redeploy your applications. This override would be time-limited, before either being reverted to&lt;br&gt;
the values committed, or the override being committed to the source repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  Choosing a config language
&lt;/h2&gt;

&lt;p&gt;One of the key, non-reversible decisions to make was how users should write their configuration. There are many options&lt;br&gt;
to pick from; JSON, YAML, Dhall, HOCON, INI, XML etc; the options are endless.&lt;/p&gt;

&lt;p&gt;Most of the options do not support conditionals, which leads to the question of how to support different environments.&lt;br&gt;
For instance, if we picked JSON, we’d have to introduce a filename suffix system where &lt;code&gt;config.json&lt;/code&gt; is applied&lt;br&gt;
everywhere, but &lt;code&gt;config.env-prod.json&lt;/code&gt; is only applied to the environment named “prod”, and &lt;code&gt;config.dev.json&lt;/code&gt; is applied&lt;br&gt;
to all development environments. Then a question of the merge order arrives, and how do overrides work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does a specified environment name override a value defined in the environment type?&lt;/li&gt;
&lt;li&gt;what if a field is null or undefined in the override, does that mean unset it or leave the previous value?.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another disadvantage is IDE warnings around type safety, and auto-completion would not be automatic; we’d have to add it&lt;br&gt;
into the &lt;a href="https://plugins.jetbrains.com/plugin/20010-encore/"&gt;Encore plugin&lt;/a&gt; to provide a good development experience.&lt;/p&gt;

&lt;p&gt;To be complete, we briefly considered the option of writing the configuration directly into Go using regular Go code, as this would give the ability to easily have expressions to control config. However, we quickly ruled it out as the advantages of type safety and IDE support were far outweighed by the disadvantages. Using Go would mean having to convert static data into Go code, and initializing structs or updating config values would result in a recompile and relink of the app, which would slow down iteration times.&lt;/p&gt;

&lt;p&gt;Finally, we settled on the same configuration language that we use internally for our own systems: &lt;a href="https://cuelang.org/"&gt;CUE Lang&lt;/a&gt;.&lt;br&gt;
As it has many of the properties we where after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It’s a strict superset of JSON, which means all valid JSON is valid CUE, so users do not have to learn CUE to get started&lt;/li&gt;
&lt;li&gt;CUE has full type safety meaning CUE tooling can report incomplete configuration or invalid types being used&lt;/li&gt;
&lt;li&gt;It supports expressions such as if statements and loops&lt;/li&gt;
&lt;li&gt;CUE has inline comments&lt;/li&gt;
&lt;li&gt;Best of all; it is deterministic no matter which order you process files, the final output will always be the same.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Designing the API
&lt;/h2&gt;

&lt;p&gt;As with most of our features, this is where we spent a lot of time going back and forth. Encore takes backward&lt;br&gt;
compatibility extremely seriously. Similar to the Go compatibility promise, Encore aims to never break working code in future versions of Encore. That means any API we provide must be designed both for what we want to do in the future with that feature, and how the API may interact with other features of Encore.&lt;/p&gt;

&lt;p&gt;We needed to be able to statically read all configuration keys and return types, such that we could generate a CUE file&lt;br&gt;
with the required constraints on types, thus gaining our compile-time type safety and completeness checks.&lt;/p&gt;

&lt;p&gt;We landed on four main contenders for the design of how to access the configuration from the Go code.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Implicit loading
&lt;/h3&gt;

&lt;p&gt;This would be consistent with how secrets are currently defined in Encore. The user would write a package-level variable&lt;br&gt;
with an inline struct type, but not provide an instance of that config. Then at compile time, Encore would inject the&lt;br&gt;
instance against that variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ReadOnlyMode&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

    &lt;span class="n"&gt;Currencies&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Documentation of the configuration options would come naturally to Go programmers, as they could just document the struct,&lt;br&gt;
and we would replicate those comments into the generated CUE file.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Explicit loading
&lt;/h3&gt;

&lt;p&gt;In this approach, we took the first approach and removed the “magic” by introducing an explicit &lt;code&gt;Load&lt;/code&gt; function call.&lt;br&gt;
Under the hood, this is exactly what Encore would have rewritten your code to be in the first option, but due to your&lt;br&gt;
explicitly calling Load, it’s less confusing as to how that variable came to be set in the first place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"encore.dev/config"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ReadOnlyMode&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

    &lt;span class="n"&gt;Currencies&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;Code&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Reference by Path
&lt;/h3&gt;

&lt;p&gt;We considered an XPath style API, where instead of defining a config struct, you would explicitly read into the&lt;br&gt;
configuration, and we would infer the structure of the configuration from the access pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"encore.dev/config"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;SomeCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;ReadOnlyMode&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;Currencies&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this API design had some significant disadvantages against the first two;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typo’s would not be detected (&lt;code&gt;ReadOnlyMode&lt;/code&gt; vs &lt;code&gt;ReadOnly&lt;/code&gt;) as they would cause two separate required booleans
to be created.&lt;/li&gt;
&lt;li&gt;The IDE could not autocomplete the path for you.&lt;/li&gt;
&lt;li&gt;Refactoring would not be possible inside the IDE (i.e. renaming a field and automatically updating all callsites).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Generated Go from CUE
&lt;/h3&gt;

&lt;p&gt;The final approach we considered, was to take the types from the CUE configuration and generate a matching Go struct&lt;br&gt;
type with package-level variables for accessing the config, along the lines of the implicit load and explicit load&lt;br&gt;
options - just with the Go file being fully automatically generated.&lt;/p&gt;

&lt;p&gt;This design meant that we’d be guessing the Go types from the CUE types and might use a too-generic type, forcing the&lt;br&gt;
user to typecast the configuration value when it is used. That generic type could then result in an invalid configuration&lt;br&gt;
which wasn’t caught at compile time; for example, we generated an &lt;code&gt;int&lt;/code&gt; field, when the app only wanted &lt;code&gt;uint&lt;/code&gt;’s.&lt;/p&gt;
&lt;h2&gt;
  
  
  Updating Config at Runtime &amp;amp; for Tests
&lt;/h2&gt;

&lt;p&gt;After evaluating the options, we decided that the explicit loading made the most sense. However, it left us with a problem: How do we update config values due to live updates and allow tests to override the configuration?&lt;/p&gt;

&lt;p&gt;At the end of the day, we only saw two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the values in the struct or sub-structs directly in memory.&lt;/li&gt;
&lt;li&gt;Provide a wrapper type which wraps a value from &lt;code&gt;T&lt;/code&gt; into &lt;code&gt;func () T&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the first option initially feels the best, it leads to some unexpected behavior: Do all call sites that&lt;br&gt;
reference (or have the struct passed in) understand the type is dynamic and might change? If not, there are going to be some really hard-to-debug issues. Especially if the values are passed into a third-party library that isn’t aware it’s running inside an Encore application.&lt;/p&gt;

&lt;p&gt;The other impact, which is due to Encore’s unique test tracking &amp;amp; isolation capabilities, is that two parallel running tests could read different values from the same &lt;code&gt;cfg&lt;/code&gt; variable. While most people would never even notice this, it could still lead to some WTF moments of confusion.&lt;/p&gt;

&lt;p&gt;Going with the second option means people will naturally expect that the returned value could change between calls to&lt;br&gt;
the wrapper function, which leads to easier to pick up bugs during reviews.&lt;/p&gt;

&lt;p&gt;Then by providing a function to override the values in these wrapper functions for unit tests, we end up with no&lt;br&gt;
unexpected behaviour, while giving us the ability to have a typesafe override API for the testing framework&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;svc_test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"encore.dev/et"&lt;/span&gt; &lt;span class="c"&gt;// Encore's test helpers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;et&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetCfg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// This will always print "TestA: Foo"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TestA:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;et&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetCfg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// This will always print "TestB: Bar"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TestB:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// This will always print the original config "TestC: System under test"&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TestC:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;

    &lt;span class="s"&gt;"encore.dev/config"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ReadOnlyMode&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;   &lt;span class="c"&gt;// Are we in read only mode?&lt;/span&gt;
    &lt;span class="n"&gt;SystemName&lt;/span&gt;   &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="c"&gt;// The name of our system&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;NameResponse&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//encore:api public&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NameResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NameResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemName&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;myconfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cue&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SystemName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"System under test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SystemName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"My System"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  Writing the Code Generators
&lt;/h2&gt;

&lt;p&gt;Many of Encore’s capabilities come down to generating code based on your intent, removing the boilerplate you normally need to write. For this project, we had two separate languages we needed to generate code in, Go and CUE.&lt;/p&gt;

&lt;p&gt;We started with the CUE generator, we took the statically parsed call to &lt;code&gt;config.Load&lt;/code&gt;, recursively resolved the types&lt;br&gt;
required, and used CUE’s own AST package to generate an AST of a CUE file, which contained both the CUE definitions of&lt;br&gt;
the CUE types, but also definitions about the metadata, such that the configuration can perform conditions based on&lt;br&gt;
the environment.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Code generated by encore. DO NOT EDIT.
//
// The contents of this file are generated from the structs used in
// conjunction with Encore's `config.Load[T]()` function. This file
// automatically be regenerated if the data types within the struct
// are changed.
//
// For more information about this file, see:
// https://encore.dev/docs/develop/config
package svc

// #Meta contains metadata about the running Encore application.
// The values in this struct will be injected by Encore upon deployment and can be
// referenced from other config values for example when configuring a callback URL:
//    CallbackURL: "\(#Meta.APIBaseURL)/webhooks.Handle`"
#Meta: {
    APIBaseURL: string @tag(APIBaseURL) // The base URL which can be used to call the API of this running application.
    Environment: {
        Name:  string                                              @tag(EnvName)   // The name of this environment
        Type:  "production" | "development" | "ephemeral" | "test" @tag(EnvType)   // The type of environment that the application is running in
        Cloud: "aws" | "azure" | "gcp" | "encore" | "local"        @tag(CloudType) // The cloud provider that the application is running in
    }
}

// #Config is the top level configuration for the application and is generated
// from the Go types you've passed into `config.Load[T]()`. Encore uses a definition
// of this struct which is closed, such that the CUE tooling can any typos of field names.
// this definition is then immediately inlined, so any fields within it are expected
// as fields at the package level.
#Config: {
    ReadOnlyMode: bool   // Are we in read only mode?
    SystemName:   string // The name of our system
}
#Config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The design of the wrapper functions meant we couldn’t easily unmarshal the CUE value into the wrapper functions.&lt;br&gt;
This meant we needed to generate unmarshalled functions for the config types. We use the excellent Jennifer library by&lt;br&gt;
Dave (no really; &lt;a href="https://github.com/dave/jennifer"&gt;github.com/dave/jennifer&lt;/a&gt;) for generating Go files.&lt;/p&gt;

&lt;p&gt;For each Go type used within the config, we generate a separate unmarshaller function. The unmarshallers use&lt;br&gt;
&lt;a href="https://github.com/json-iterator/go"&gt;json-iterator&lt;/a&gt; to process the output from CUE, while tracking the path within the&lt;br&gt;
config to the unmarshalled value. This path tracking will allow the function to check if live overrides have been&lt;br&gt;
provided on that path and return the override instead.&lt;/p&gt;

&lt;p&gt;The root level unmarshaller is then injected as a parameter into your call to &lt;code&gt;config.Load&lt;/code&gt;, such that the Go compiler&lt;br&gt;
is able to statically verify that our generated code will return the exact types that you expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;We launched the initial version of config a couple of weeks ago in&lt;br&gt;
&lt;a href="https://github.com/encoredev/encore/releases/tag/v1.9.0"&gt;version 1.9.0&lt;/a&gt;. However, this is only the start of our journey&lt;br&gt;
of improving the state of the configuration of backend developers. As discussed above one of the key features we want to add next is the ability to allow applications to have parts of their configuration updated in realtime on a&lt;br&gt;
time-limited basis for dealing with incidents. This means adding a new API to give you the ability to get a channel&lt;br&gt;
from each wrapper to watch for changes in the value, allowing your application to react to configuration changes, such as resetting rate limiters to the new rates.&lt;/p&gt;

&lt;p&gt;We also want to provide a UI in the Encore platform, listing the current configuration as it is deployed into each environment, as well as the locations in the source CUE files where that configuration is defined.&lt;/p&gt;

&lt;p&gt;Following that we want to integrate config with our secrets management system, such that you can load in secrets as part&lt;br&gt;
of your configuration and use CUE’s conditionals to switch between secrets depending on what you need.&lt;/p&gt;

&lt;p&gt;Is there something you’d love to see in a configuration system? Come chat with us on &lt;a href="https://encore.dev/slack"&gt;Slack&lt;/a&gt;, or the &lt;a href="https://community.encore.dev/"&gt;Community Forums&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How we used Go 1.18 when designing our Identifiers</title>
      <dc:creator>Dominic Black</dc:creator>
      <pubDate>Wed, 01 Jun 2022 11:40:24 +0000</pubDate>
      <link>https://dev.to/encore/how-we-used-go-118-when-designing-our-identifiers-597h</link>
      <guid>https://dev.to/encore/how-we-used-go-118-when-designing-our-identifiers-597h</guid>
      <description>&lt;p&gt;When building any system, distributed or not, you end up dealing with many identifiers for your data. From database rows all the way to identifiers for the version of your system in production. Unsurprisingly Encore's own systems are no different. We need to uniquely identify and track things from individual &lt;a href="http://localhost:3010/docs/develop/services-and-apis#defining-apis"&gt;API endpoints&lt;/a&gt; in your applications, &lt;a href="https://dev.to/docs/observability/tracing"&gt;traces from runtime calls&lt;/a&gt;, to the various&lt;br&gt;
&lt;a href="https://dev.to/docs/deploy/infra"&gt;infrastructure resources&lt;/a&gt; provisioned in one of our supported clouds, such as an AWS security group.&lt;/p&gt;

&lt;p&gt;Deciding how to generate your identifiers can sometimes be very simple. For instance, you might just put an auto-incrementing number as your primary key in your database. Boom, you have your type of identifier and way to generate it, just like magic.&lt;/p&gt;

&lt;p&gt;However, it's much harder in a distributed system to just have a number start at 1 and slowly increase. You could build a system that elects a leader, and that leader is in charge of incrementing the number - but this adds a lot of complexity to your system design, it doesn't scale indefinitely as you will still be limited by the throughput of the leader. You could still suffer from a &lt;a href="https://en.wikipedia.org/wiki/Split-brain_(computing)"&gt;split-brain issue&lt;/a&gt; where the same number is generated twice by two different "leaders".&lt;/p&gt;

&lt;p&gt;Getting this decision right early on in your project is always important, as it's one of the hardest things to change in a system once you're in production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting from our requirements
&lt;/h2&gt;

&lt;p&gt;When we started to think about how we wanted to represent identifiers in Encore's platform, we wrote down what we wanted as core requirements from an identifier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sortable&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to be able to order the identifiers, such that an &lt;code&gt;A&lt;/code&gt; &amp;lt; &lt;code&gt;B&lt;/code&gt; when &lt;code&gt;A&lt;/code&gt; was created first. However, we don't need total sorting, being &lt;a href="https://en.wikipedia.org/wiki/K-sorted_sequence"&gt;k-sortable&lt;/a&gt; is acceptable.&lt;/p&gt;

&lt;p&gt;Being sortable allows us better indexing performance in the database, will enable us to easily iterate through records in order and improves our ability to debug as the order of events can be determined from the event identifiers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want a system that will scale with us without bottlenecks. We'll be using this system to generate identifiers for traces and spans in which we'll be creating a vast number of them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Collision Risk&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because we run a distributed system, we don't want the risk of two of our processes creating the same identifier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero Configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don't want to have to do any level of configuration when using this system on either a per-machine or per-process level.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want a system where an identifier for one resource can't accidentally be passed or returned as an identifier for another type of resource.&lt;/p&gt;

&lt;p&gt;As a bonus, we also wanted identifiers to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reasonably Small&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both in memory and on the wire.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Human-readable&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is related to type safety. However, we want the string representation to allow a human (us 🤖) to understand what the identifier was created for.&lt;/p&gt;

&lt;p&gt;This seems like quite a list of asks, but it's pretty manageable when we break it down.&lt;/p&gt;
&lt;h2&gt;
  
  
  Existing Options
&lt;/h2&gt;

&lt;p&gt;If we temporarily ignore the type safety and human readability requirements, then what we're asking for has been solved a thousand times before, and we don't need to re-invent the wheel here. Let's look at some existing battled-tested options.&lt;/p&gt;
&lt;h3&gt;
  
  
  Database powered auto-incrementing keys ✘
&lt;/h3&gt;

&lt;p&gt;The first port of call for most people is an auto-incrementing primary key. This solves being sortable and has no collision risk. However, it can only scale as far as your database server will process writes. It also adds a new requirement that every identifier you generate &lt;em&gt;must&lt;/em&gt; have a matching database row. Given we wanted to use this system for things we don't store in our database, we ruled this out pretty quickly.&lt;/p&gt;
&lt;h3&gt;
  
  
  UUIDs ✘
&lt;/h3&gt;

&lt;p&gt;There are various versions of &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUIDs&lt;/a&gt;, and they are all 128 bits. At first glance, versions 1, 2, 4 seem perfect from a scalability, zero configuration and collision risk angle. However, when you dig deeper, the different versions of UUIDs start to show warts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Versions 1 &amp;amp; 2&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though they can generate approximately 160 billion identifiers per second, they use the machine's MAC address which means two processes on the same machine &lt;em&gt;could&lt;/em&gt; generate the same identifier if called simultaneously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version 4&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generates completely random identifiers with 2&lt;sup&gt;122&lt;/sup&gt; bits of randomness. This means we're very unlikely to have a collision and can infinitely scale-out without a bottleneck. However, we lose the ability to sort the identifiers chronologically.&lt;/p&gt;
&lt;h3&gt;
  
  
  Snowflake ✘
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Snowflake_ID"&gt;Snowflake&lt;/a&gt; identifiers were first developed by Twitter and typically are&lt;br&gt;
64 bits (although some &lt;a href="https://github.com/boundary/flake"&gt;variants use 128 bits&lt;/a&gt;). This scheme encodes the &lt;code&gt;time&lt;/code&gt; the ID was generated as the first 41 bits, then encodes an &lt;code&gt;instance&lt;/code&gt; ID for the next 10 bits, and finally a &lt;code&gt;sequence&lt;/code&gt; number for the final 12 bits.&lt;/p&gt;

&lt;p&gt;This gives us pretty good scalability as the &lt;code&gt;instance&lt;/code&gt; set of bits allows us to run &lt;code&gt;1024&lt;/code&gt; different processes simultaneously while knowing they can not generate the same identifier, as the process's own identifier is encoded within! It gives us our k-sorting, as the upper bits are the time the ID was generated. Finally, we have a &lt;code&gt;sequence&lt;/code&gt; number within the process, which allows us to generate &lt;code&gt;4096&lt;/code&gt; identifiers per second per process.&lt;/p&gt;

&lt;p&gt;However, Snowflake misses our zero-configuration requirement because the &lt;code&gt;instance&lt;/code&gt; ID must be configured per process.&lt;/p&gt;
&lt;h3&gt;
  
  
  KSUID ✘
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/segmentio/ksuid"&gt;KSUID's&lt;/a&gt; are sort of a cross between UUID Version 4 and Snowflake. They are 160 bits where the first 32 bits are the &lt;code&gt;time&lt;/code&gt; the identifier was generated (to the second) and then 128 bits of &lt;code&gt;random&lt;/code&gt; data. This makes them almost ideal for us. They are k-sortable, require no configuration, and have no collision risk because of the large amount of entropy in the random part of the id.&lt;/p&gt;

&lt;p&gt;However, we discovered something interesting during our research of KSUID. The string encoding of KSUID uses Base-62 encoding, and so has both uppercase and lowercase letters. This means &lt;em&gt;depending on your string sorting&lt;/em&gt;, you might sort the identifiers differently - i.e. we lose our requirement for sortability depending on the system. For instance, Postgres sorts lowercase before uppercase, whereas most algorithms sort uppercase before lowercase, which could lead to some very nasty &amp;amp; hard-to-identity bugs. (It's worth noting that this impacts any encoding scheme which uses both upper and lower case letters, so it isn't just limited to KSUID)&lt;/p&gt;
&lt;h3&gt;
  
  
  XID ✓
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/rs/xid"&gt;XID&lt;/a&gt;'s are 96 bits. The first 32 bits are the &lt;code&gt;time&lt;/code&gt;, which means we get our k-sorting immediately. The next 40 bits are a &lt;code&gt;machine identifier&lt;/code&gt; and a &lt;code&gt;process identifier&lt;/code&gt;. However, unlike the other systems, these are calculated automatically using the library and don't require us to configure anything ourselves. The final 24 bits are a &lt;code&gt;sequence&lt;/code&gt; number, which allows a single process to generate 16,777,216 identifiers per second!&lt;/p&gt;

&lt;p&gt;XID gives us all our core requirements, and its string encoding uses base 32 (no upper case letters to break our sorting!). This string encoding is always 20 characters, which means we can use that fact for validation purposes in any marshalling code (such as a Postgres &lt;a href="https://www.postgresql.org/docs/9.4/ddl-constraints.html"&gt;&lt;code&gt;CHECK&lt;/code&gt; constraint&lt;/a&gt;&lt;br&gt;
on the database type).&lt;/p&gt;
&lt;h2&gt;
  
  
  Our Identifiers
&lt;/h2&gt;

&lt;p&gt;Once we had settled on using XID as the basis for our ID types, we switched focus back to our last two requirements: type safety and human readability.&lt;/p&gt;

&lt;p&gt;For the former requirement, we could have solved this as simply as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/rs/xid"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TraceID&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewAppID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTraceID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;TraceID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TraceID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And thanks to Go's type system, both &lt;code&gt;AppID&lt;/code&gt; and &lt;code&gt;TraceID&lt;/code&gt; would concretely be different types, such that the following&lt;br&gt;
would become a compile error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewTraceID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// this won't compile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would have worked. However, we would have to implement all the marshalling functions (&lt;code&gt;encoding.TextMarshaler&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;json.Marshaler&lt;/code&gt;, &lt;code&gt;sql.Scanner&lt;/code&gt;, etc.) for each concrete type like &lt;code&gt;AppID&lt;/code&gt;. To minimise boilerplate writing for our team, this would mean using a &lt;a href="https://go.dev/blog/generate"&gt;code generator&lt;/a&gt; to write it for us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We could have written the marshalling functions once using type aliases (&lt;code&gt;type AppID = xid.ID&lt;/code&gt;), but the compiler would have treated the ID types as interchangeable.&lt;/p&gt;

&lt;p&gt;Another downside here is our system isn't just Go. We also have a Typescript frontend and a Postgres database. This means once we encode the ID into a wire format, we've lost all guarantees of type safety, and now in another system, it's possible to mistakenly use one form of ID for another.&lt;/p&gt;

&lt;p&gt;Before Go 1.18, we could have solved this by adding a wrapper struct containing the type information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/rs/xid"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;EncoreID&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceType&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;           &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EncoreID&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TraceID&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EncoreID&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewAppID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;AppID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EncoreID&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewTraceID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;TraceID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EncoreID&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can update our marshalling functions to prefix the &lt;code&gt;EncoreID.ResourceType&lt;/code&gt; at the beginning of the string. One&lt;br&gt;
slight downside here is that aside from passing around a lovely 20 byte array (which is how &lt;code&gt;xid&lt;/code&gt; is encoded in memory),&lt;br&gt;
we would also be passing around a struct with string instances. Not super inefficient, but not as nice!&lt;/p&gt;
&lt;h3&gt;
  
  
  Enter Go Generics
&lt;/h3&gt;

&lt;p&gt;With Go 1.18, we can create a single abstract ID type and then create different concrete types based on a &lt;code&gt;ResourceType&lt;/code&gt;,&lt;br&gt;
but really are just &lt;code&gt;xid&lt;/code&gt;'s. So conceptually, we started with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/rs/xid"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Trace&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gave us an excellent starting point, as the memory format of these identifiers had not changed from XID's underlying &lt;code&gt;[12]byte&lt;/code&gt;.&lt;br&gt;
However, the compiler will treat &lt;code&gt;ID[App]&lt;/code&gt; and &lt;code&gt;ID[Trace]&lt;/code&gt; as two distinct types. We also don't need to use code generation for all the marshalling functions as we can write them once for the generic type.&lt;/p&gt;

&lt;p&gt;The one requirement we've not solved with this generic version is our type safety for both the wire formats and the human&lt;br&gt;
readability. (i.e. it's still possible for us to &lt;code&gt;json.Marshal&lt;/code&gt; an application ID and &lt;code&gt;json.Unmarshal&lt;/code&gt; that into a trace ID). To solve this, we can utilise the ability for generics in Go to create the default instance of a type. Then we can call a method on it. For example, our string method looks a little like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resourceType&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="c"&gt;// create the default value for the resource type&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"%s_%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;resourceType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c"&gt;// Extract the "prefix" we want from the resource type&lt;/span&gt;
        &lt;span class="n"&gt;xid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c"&gt;// Use XID's string marshalling&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we've skipped a tiny bit: how do we make our &lt;code&gt;App&lt;/code&gt; and &lt;code&gt;Trace&lt;/code&gt; resource types have a Prefix method we can call?&lt;br&gt;
Well, we need to change &lt;code&gt;ResourceType&lt;/code&gt; into an interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ResourceType&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Trace&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"trace"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of our marshalling and unmarshalling code can now include and verify the identifiers type prefix, which means we&lt;br&gt;
can easily validate if due to a typo, we pass &lt;code&gt;trace_0ncid27rirfkbch0hld0&lt;/code&gt; as an argument to an API expecting an application ID.&lt;/p&gt;
&lt;h3&gt;
  
  
  PS. Postgres is here
&lt;/h3&gt;

&lt;p&gt;As I alluded to earlier, these strongly typed wire formats mean we can create matching types in other systems without losing any of the type safety guarantees we wanted. For instance, we use code generation to automatically create new&lt;br&gt;
&lt;a href="https://www.postgresql.org/docs/13/domains.html"&gt;Postgres domain types&lt;/a&gt; for each of our &lt;code&gt;ResourceTypes&lt;/code&gt; in our database so we can strongly type the database columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DOMAIN&lt;/span&gt; &lt;span class="n"&gt;application_id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'^app_[a-z0-9]{20}$'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then use &lt;a href="https://sqlc.dev/"&gt;SQLC&lt;/a&gt; to generate type safe Go code to read and write to our database, taking our custom ID types and passing them through all the layers to the database without losing our type safety. We'll talk about how we use SQLC and other code generation in a future blog post to give us type safety into and out of the database. I'll leave you with a little sneak peek of a generated helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetMembersForApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Querier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;eid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;eid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppMembers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;While generics have been a long time coming in Go, there has been excellent code generation tooling to fill that gap. The two are not mutually exclusive and can be used simultaneously to reduce the cognitive load and the boilerplate code you have to write. It's easy to overuse and abuse generics, but if you take your time and use them carefully, you'll be thinking with Generics!&lt;/p&gt;

&lt;p&gt;You can use all the Go 1.18 features, including Generics, with Encore. We'd love it if you'd &lt;a href="https://dev.to/docs/quick-start"&gt;try out Encore&lt;/a&gt; and &lt;a href="https://dev.to/slack"&gt;join Slack&lt;/a&gt; to give us feedback!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://encore.dev/blog"&gt;Encore Blog&lt;/a&gt; on 25-03-2022.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>backend</category>
      <category>programming</category>
      <category>encore</category>
    </item>
  </channel>
</rss>
