<?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: Mihai Bojin</title>
    <description>The latest articles on DEV Community by Mihai Bojin (@mihaibojin).</description>
    <link>https://dev.to/mihaibojin</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%2F332664%2Feddf82e8-a1a0-432b-9ff6-0ca503b4cdea.jpg</url>
      <title>DEV Community: Mihai Bojin</title>
      <link>https://dev.to/mihaibojin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mihaibojin"/>
    <language>en</language>
    <item>
      <title>Build products, skip the non-functional requirements</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Sun, 17 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/build-products-skip-the-non-functional-requirements-3nd</link>
      <guid>https://dev.to/mihaibojin/build-products-skip-the-non-functional-requirements-3nd</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L_-Ytgz5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/fed9fcdf6b8d2a67d7e2b1050bbcac54/073a4/001-junior-ferreira-7esRPTt38nI-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L_-Ytgz5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/fed9fcdf6b8d2a67d7e2b1050bbcac54/073a4/001-junior-ferreira-7esRPTt38nI-unsplash.jpg" alt="Photo: Lightbulb moment" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"Photo by Júnior Ferreira on Unsplash"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/building-products-vs-non-functional-requirements?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;I asked myself, am I overengineering my pet projects? Are you!?&lt;/p&gt;

&lt;p&gt;I like solving problems! I like finding problems more...&lt;/p&gt;

&lt;p&gt;Call me negative! That's how I work; like anything, it's both good and bad, depending on perspective.&lt;/p&gt;

&lt;p&gt;Almost every time I find a problem, I also get ideas. Ideas on how that particular experience could be better! And my hands start itching. I've had plenty of these moments during my career—plenty of ideas seemingly simple to implement and turn into a successful product.&lt;/p&gt;

&lt;p&gt;But in 17 years, I barely shipped something (outside of my day job, that is).&lt;/p&gt;

&lt;p&gt;I often ask myself, WHY!?&lt;/p&gt;

&lt;p&gt;Has it got to do with time? Sure, I have a full-time job that keeps me busy - I always had. And I also have small kids who take up a lot of my time and energy.&lt;/p&gt;

&lt;p&gt;But I have some spare time; spare time can be a &lt;a href="https://buffer.com/resources/7-examples-of-how-creative-constraints-can-lead-to-amazing-work/"&gt;self-imposed constraint&lt;/a&gt;. It took me a while to realize the power of constraints - but I am here now!&lt;/p&gt;

&lt;p&gt;Ok, so given some limited amount of time that I can put towards personal projects, what do I do?&lt;/p&gt;

&lt;p&gt;Well, the thing is that I'm also indecisive. I have too many ideas, and it's not reasonable that I can do everything. I started many projects but finished none. This challenge took longer to grasp and digest, but I'm also here now!&lt;/p&gt;

&lt;p&gt;Good! So &lt;strong&gt;I need to apply focus to limited availability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This leads me to the topic at hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Am I building products or engineering for fun!?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building products means moving fast and lean, releasing new software, testing it against your user base, and then rinsing and repeating.&lt;/p&gt;

&lt;p&gt;(Cue your favorite buzzword here: Lean, Agile, SDLC, etc.)&lt;/p&gt;

&lt;p&gt;So I know what to do, but I just &lt;a href="https://mihaibojin.com/personal-site/building-a-personal-site?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=rss"&gt;don't do it&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I recently started thinking of a fun data project I'd like to build. It requires a few data collection jobs running in perpetuity.&lt;/p&gt;

&lt;p&gt;I started thinking about where I would run these and how I could optimize my costs. Cloud providers are a no go since the costs scale up fast, and I also want to avoid vendor lock-in.&lt;/p&gt;

&lt;p&gt;You get the most bang for the buck by going low-level. Rent a bare-metal server, deploy a few VMs (i.e., with the KVM hypervisor), and operate your software.&lt;/p&gt;

&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;p&gt;But this model is also very old-school, not to mention unreliable. What if your VMs crash?&lt;/p&gt;

&lt;p&gt;It's better to use a scheduler such as Kubernetes. Installing it, however, is non-trivial; you need backups, automation, you need to factor in resiliency, control plane upgrades, etc. The list goes on and on!&lt;/p&gt;

&lt;p&gt;It sounds like a fun project, and once I finished automating all of the above, I could safely deploy and operate my data collector jobs.&lt;/p&gt;

&lt;p&gt;That I would have had not written a single line of code for...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If that's not crazy, I don't know what is!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This has been my existential crisis all along. I use my short and valuable time to &lt;strong&gt;solve the wrong problem&lt;/strong&gt; and optimize because that's what I'd do professionally at scale, and that's what I am most comfortable with.&lt;/p&gt;

&lt;p&gt;I'm starting to get it now. As the famous saying goes: "You have to spend money to make money!"&lt;/p&gt;

&lt;p&gt;I have to find a way to start these projects more expensive than I'm comfortable with because the cost of missed opportunities is way higher than a few hundred coins a month!&lt;/p&gt;

&lt;p&gt;Taking this approach is a forcing function of its own. I can easily afford to pay 50 quid a month for a server I don't use and will unlikely feel any pressure to deploy something on it instead of watching the next "promising" Netflix show...&lt;/p&gt;

&lt;p&gt;This is a loser's game.&lt;/p&gt;

&lt;p&gt;I can do better.&lt;/p&gt;

&lt;p&gt;TBC!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>product</category>
      <category>nonfunctional</category>
    </item>
    <item>
      <title>Aiming for object-oriented design elegance</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Tue, 15 Mar 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/aiming-for-object-oriented-design-elegance-4jc2</link>
      <guid>https://dev.to/mihaibojin/aiming-for-object-oriented-design-elegance-4jc2</guid>
      <description>&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/oop-design?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;One of my goals for this project was to create a library that is elegant and easy to use while feeling Java idiomatic.&lt;/p&gt;

&lt;p&gt;Let's talk &lt;a href="https://en.wikipedia.org/wiki/SOLID"&gt;SOLID&lt;/a&gt;. They're a set of five principles aimed at making object-oriented design implementations flexible and maintainable. I am designing a library that I hope will be a joy to use and will make developers want to adopt it, for which reason I interpreted these principles in the best possible form I could think of.&lt;/p&gt;

&lt;p&gt;My initial thought was to offer a base &lt;code&gt;Prop&lt;/code&gt; interface, abstracting away lower-level implementation details that are not relevant to users of this class.&lt;/p&gt;

&lt;p&gt;However, I settled on using a few abstract classes, for several reasons, inspired by a few of the SOLID principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all &lt;code&gt;Prop&lt;/code&gt; objects need a few common traits&lt;/li&gt;
&lt;li&gt;a class should have a single responsibility; since I was building multiple themes, sticking them all into a single class didn't feel elegant&lt;/li&gt;
&lt;li&gt;it should be easy to build on top of each layer&lt;/li&gt;
&lt;li&gt;not all methods need to be exposed in the final public contract; unfortunately, Java interfaces do not support non-public methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break it down; here is the high-level end-result class design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@FunctionalInterface
public interface Subscribable&amp;lt;T&amp;gt; {
  void subscribe(Consumer&amp;lt;T&amp;gt; onUpdate, Consumer&amp;lt;Throwable&amp;gt; onError);
}

public abstract class SubscribableProp&amp;lt;T&amp;gt; implements Subscribable&amp;lt;T&amp;gt; {
  /* Processes a value update event. */
  protected void onValueUpdate(@Nullable T value, long epoch) {}
  /* Processes an exception encountered during an update. */
  protected void onUpdateError(Throwable error, long epoch) {}
}

public abstract class Prop&amp;lt;T&amp;gt; extends SubscribableProp&amp;lt;T&amp;gt; implements Supplier&amp;lt;T&amp;gt; {
    /* Identifies the Prop */
    public abstract String key();

    /* Returns the Prop's value */
    public abstract T get();
}

public abstract class BoundableProp&amp;lt;T&amp;gt; extends Prop&amp;lt;T&amp;gt; {
  /* Allows the Registry to update a Prop's value */
  protected abstract boolean setValue(@Nullable String value);
}

public class Registry {
  /* Binds a Prop object to the Registry object, allowing it to process update events and set the Prop's value */
  public &amp;lt;T, PropT extends BoundableProp&amp;lt;T&amp;gt;&amp;gt; PropT bind(PropT prop) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Subscribable&lt;/code&gt; denotes that a &lt;code&gt;Prop&lt;/code&gt; can be subscribed to. The result of a prop update is either success or an error.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SubscribableProp&lt;/code&gt; is a partial implementation that hosts the logic necessary to process updates/errors and notify clients safely.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Prop&lt;/code&gt; is the absolute minimum public contract that a consumer/client should care about. It defines an identifier (&lt;code&gt;key&lt;/code&gt;) and a way to get the prop's &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;BoundableProp&lt;/code&gt; encompasses all of the above and also includes a mechanism that allows the &lt;code&gt;Registry&lt;/code&gt; to update prop values when the underlying sources are updated.&lt;/p&gt;

&lt;p&gt;However, in practice, relying on a key and value alone, is not enough of a reason to adopt this library.&lt;/p&gt;

&lt;p&gt;For that reason, the &lt;code&gt;CustomProp&lt;/code&gt; class provides an almost complete implementation, par the corresponding &lt;code&gt;Converter.decode()&lt;/code&gt; method, which requires a knowledge of the Prop's type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public abstract class CustomProp&amp;lt;T&amp;gt; extends BoundableProp&amp;lt;T&amp;gt; implements Converter&amp;lt;T&amp;gt; {
    /* Identifies the Prop */
    public String key() {};
    /* Returns the Prop's value */
    public String get() {};
    /* Describes the prop */
    public String description() {};
    /* true, if the prop is required */
    public boolean isRequired() {};
    /* true, if the prop is a secret */
    public boolean isSecret() {};
}

@FunctionalInterface
public interface Converter&amp;lt;T&amp;gt; {
  /* Decodes a String into the desired type; must be implemented */
  T decode(@Nullable String value);

  /* Encodes the value into a String, defaulting to using Object.toString() */
  default String encode(@Nullable T value) {
    return value == null ? null : value.toString();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One way to extend &lt;code&gt;CustomProp&lt;/code&gt; is to provide an implementation for &lt;code&gt;Converter.decode&lt;/code&gt;, thus completing the class, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class LongProp extends CustomProp&amp;lt;Long&amp;gt; {
  public Long decode(String value) {
    Number number = safeParseNumber(value);
    try {
      return NumberFormat.getInstance().parse(value).longValue();
    } catch (ParseException e) {
      log.log(SEVERE, e);
      return null;
    }
  }    
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we can do a bit better. Since one can assume that most props will be of common Java datatypes, I have provided a series of default converters that can be composed into a final implementation. The above code can be rewritten as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class LongProp extends CustomProp&amp;lt;Long&amp;gt; implements LongConverter {  
}

public interface LongConverter extends Converter&amp;lt;Long&amp;gt; {
   @Override
   public Long decode(String value) {
    Number number = safeParseNumber(value);
    try {
      return NumberFormat.getInstance().parse(value).longValue();
    } catch (ParseException e) {
      log.log(SEVERE, e);
      return null;
    }
  }  
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How would we use this in production? Here's a small complete excerpt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Source source = new PropertyFile(PATH_TO_PROP_FILE);
    Registry registry = new RegistryBuilder(source).build();

    Prop prop = registry.bind(new LongProp("a.key"));

    prop.get(); // will return the value corresponding to a.key
    prop.subscribe(updatedValue -&amp;gt; {/* process updates */}, 
                   error -&amp;gt; {/* process any errors */});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Hopefully, this article serves as a good high-level introduction to the contract one can expect from the &lt;a href="http://github.com/props-sh/props"&gt;props&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;In future series I'd like to explore the props library's API a bit more and show a few real-world examples of how it could be used to simplify application settings/property management in Java projects.&lt;/p&gt;

&lt;p&gt;As always, any feedback is welcome; feel free to ping me on &lt;a href="https://twitter.com/mihaibojin"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>oop</category>
      <category>java</category>
      <category>objectorientation</category>
      <category>solid</category>
    </item>
    <item>
      <title>Subscribing to concurrent property updates</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Tue, 26 Oct 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/subscribing-to-concurrent-property-updates-19pj</link>
      <guid>https://dev.to/mihaibojin/subscribing-to-concurrent-property-updates-19pj</guid>
      <description>&lt;p&gt;Concurrency is hard. I know this is a truism, but it had to be said!&lt;/p&gt;

&lt;p&gt;I spent some time in the past week implementing a mechanism for synchronizing multiple &lt;a href="https://github.com/props-sh/props/blob/c14dfcebb4a41bab91d227c6219dbd48328e70d7/java-props-core/src/main/java/sh/props/interfaces/Prop.java"&gt;Prop&lt;/a&gt;s and returning them to the caller, all at once.&lt;/p&gt;

&lt;p&gt;On the surface, this felt like an easy task.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;Prop&amp;lt;T&amp;gt;&lt;/code&gt; is backed by an &lt;code&gt;AtomicReference&amp;lt;T&amp;gt;&lt;/code&gt;, which enables the registry to safely (and concurrently) update the Prop's value.&lt;/p&gt;

&lt;p&gt;One of the features I wanted to build in this library was to allow users to subscribe to updates. Whenever the registry receives a new value for a Prop, it should update the bound Prop and also notify any subscribers of the new value.&lt;/p&gt;

&lt;p&gt;Since atomic references back the Props, I assumed the implementation would be straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create two Props and bind them to a registry&lt;/li&gt;
&lt;li&gt;pass the two Props to a "Pair" implementation&lt;/li&gt;
&lt;li&gt;for each of those Props, subscribe to any updates&lt;/li&gt;
&lt;li&gt;the subscriber would retrieve both values and send an event containing both props&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This implementation would work well when all the calls are synchronous. However, that is not an option. To increase this library's performance, I chose to offload the processing part of sending updates to Java's &lt;code&gt;ForkJoinPool&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Asynchronously processing these events means there are no guarantees regarding the processing order.&lt;/p&gt;

&lt;p&gt;Imagine the following scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events:
- set value A
- notify subscribers of value A
- set value B
- notify subscribers of value B

ForkJoinPool:
- notifies subscribers of value B
- notifies subscribers of value A

Outcome:
- the Prop's value is set to B
- but the subscribers were last sent A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need a mechanism for ensuring that both the Prop's value and all the subscribers receive the same value.&lt;/p&gt;

&lt;p&gt;One option for achieving this goal would be to synchronize the two actions and only allow for a single value to be updated and one notification to be sent. This would solve the problem but would be incredibly slow. All future value updates would need to wait until the subscribers are notified. If a slow subscriber is registered, the Prop's value would not be updateable, and any code accessing the Prop would receive the old value - not great.&lt;/p&gt;

&lt;p&gt;A better choice is to mark down the epoch at which the value was received and track the last processed epoch when notifying subscribers. When a value is updated, we'd also increment the epoch. Any ForkJoin tasks looking to send updates to subscribers will be able to check the last processed epoch and discard any expired updates.&lt;/p&gt;

&lt;p&gt;Ensuring the implementation worked as expected took some effort and required a bit of trial and error. Here are a few lessons I learned along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write tests to reproduce your scenario and expected results, then run them repeatedly (think Junit + repeat until failure, tens of thousands of times)&lt;/li&gt;
&lt;li&gt;Sometimes, you need extra info to debug problems; you'd be tempted to use &lt;code&gt;System.out.print()&lt;/code&gt;, but that can be misleading; it adds a few millis of lag which can totally derail your repro when the scenario you're trying to debug executes in nanoseconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;System.out.print()&lt;/code&gt; lies: the output stream is buffered and can be printed in the wrong order; calling &lt;code&gt;System.out.flush()&lt;/code&gt; helps, but is not perfect and still suffers from the orders of magnitude problem explained above&lt;/li&gt;
&lt;li&gt;Two independent atomic operations do not make for one atomic operation; this is counterintuitive at first but makes total sense the more you think of it; two threads executing the same two operations could order them in 4 different ways; find a way to update both values in a single atomic operation&lt;/li&gt;
&lt;li&gt;Syncing multiple props means the updates need to be ordered; I found one solution: queue the update operations and pass them to &lt;code&gt;AtomicReference.updateAndGet()&lt;/code&gt; as &lt;code&gt;UnaryOperator&amp;lt;Pair&amp;lt;T, U&amp;gt;&lt;/code&gt;. This method uses &lt;code&gt;weakCompareAndSetVolatile&lt;/code&gt; memory effects, specified in the &lt;a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5"&gt;JLS&lt;/a&gt; as:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;... defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since we don't care about sending each and every value, we can simply trigger an event operation when an underlying Prop is updated and only pull the latest value at the time of processing. This implementation will at least ensure some order to the logged operations, but it can't guarantee how many updates the subscriber sees (it could be one, or could be each and every one). That's acceptable for our use case.&lt;/p&gt;

&lt;p&gt;Another limitation that I had to build in was that the library needs to ensure an update is consumed before sending another update. Imagine a case where two ordered operations update multiple consumers. Due to nondeterministic thread execution, a subset of the consumers may be concurrently receiving a value while others may be receiving a different value.&lt;/p&gt;

&lt;p&gt;This could have been avoided by having a dedicated processing thread for each Prop polling a blocking queue and then sending any updates. Apart from being slower due to locking, creating a Thread has a cost (memory usage but also in terms of the maximum number of props the system can handle at once). The ForkJoinPool does not create new threads for each task and seems like the right tool for the job since, in most cases, we want a set-and-forget type situation; we want to get an update event as fast as possible to any subscribing consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;don't assume ordering&lt;/li&gt;
&lt;li&gt;test your code until breaking point&lt;/li&gt;
&lt;li&gt;avoid implementing concurrency primitives from scratch unless there's a real need&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>concurrency</category>
      <category>java</category>
      <category>opensource</category>
      <category>subscribers</category>
    </item>
    <item>
      <title>Microbenchmarking Java code with JMH</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Thu, 23 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/microbenchmarking-java-code-with-jmh-39o9</link>
      <guid>https://dev.to/mihaibojin/microbenchmarking-java-code-with-jmh-39o9</guid>
      <description>&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/micro-benchmarking-java-code-with-jmh?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;As a general best practice, it's a good idea to benchmark your code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/openjdk/jmh"&gt;JMH&lt;/a&gt;, or Java Microbenchmark Harness, is a tool that can be used to analyze the performance of JVM languages.&lt;/p&gt;

&lt;p&gt;Since I wanted to profile the performance of the &lt;a href="https://github.com/props-sh/props"&gt;Props&lt;/a&gt; library, I integrated &lt;em&gt;JMH&lt;/em&gt; into the codebase.&lt;/p&gt;

&lt;p&gt;The following is a simple step-by-step tutorial about integrating the JMH Gradle plugin in a Java codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gradle configuration
&lt;/h2&gt;

&lt;p&gt;First, add the &lt;a href="https://github.com/melix/jmh-gradle-plugin"&gt;JMH Gradle plugin&lt;/a&gt; in your &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    id("me.champeau.jmh").version("0.6.6")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing so will add a few tasks to your Gradle project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gradle jmh&lt;/code&gt;: Runs all benchmarks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gradle jmhJar&lt;/code&gt;: Generates a portable JAR that you can run on a different machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second target is helpful for running the benchmarks on a dedicated machine (that is not your developer laptop), resulting in predictable, comparable, and reproducible results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a JMH benchmark
&lt;/h2&gt;

&lt;p&gt;The plugin expects all the benchmark code to exist in &lt;code&gt;src/jmh/java&lt;/code&gt; and &lt;code&gt;src/jmh/resources&lt;/code&gt;. This avoids having to create a separate project and importing all the code while at the same time avoiding shipping the benchmark code with the main library in &lt;code&gt;src/main/java&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's create the first benchmark. Save this file as &lt;code&gt;src/jmh/java/Benchmark.java&lt;/code&gt; in your module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Fork(value = 1, warmups = 1)
@Warmup(iterations = 1)
@Measurement(iterations = 1)
@OutputTimeUnit(TimeUnit.SECONDS)
public class Benchmark {
  @Benchmark
  public static void oneBenchmark() {
    // do something
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above is just scaffolding. Let's look at each annotation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;@Fork&lt;/em&gt;: configures how many times the current benchmark is forked. If &lt;code&gt;value=0&lt;/code&gt;, the benchmark will be run in the same JVM. The &lt;code&gt;warmups&lt;/code&gt; parameter defines how many times the benchmark is forked (but the results discarded).&lt;/p&gt;

&lt;p&gt;The main benefit of warming up is to load all classes and cache them. Unfortunately, since the JVM uses lazy loading and Just In Time compiling, the first iteration of our benchmark would incur the cost of all these actions and skew the results.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;@Warmup&lt;/em&gt; determines how many warmups are performed and discarded per fork.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;@Measurement&lt;/em&gt; allows us to specify how many iterations to execute per benchmark.&lt;/p&gt;

&lt;p&gt;And finally, &lt;em&gt;@OutputTimeUnit&lt;/em&gt; allows us to specify the unit reported in the results.&lt;/p&gt;

&lt;p&gt;There are more annotations and parameters, but I won't get into the weeds of it just yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Consuming" results
&lt;/h2&gt;

&lt;p&gt;There is a small caveat when writing benchmarking code in that the JVM is smart enough to optimize code that is not actually used.&lt;/p&gt;

&lt;p&gt;For example, in the following code, the result of &lt;code&gt;tested.get()&lt;/code&gt; is never used (consumed) so the running JVM may decide to simply skip the call altogether, making the benchmark invalid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Benchmark {
  @Benchmark
  public static void oneBenchmark() {
    // assume an object under test
    tested.get();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JMH introduces the concept of a &lt;code&gt;Blackhole&lt;/code&gt;. The code above can be rewritten, ensuring the results are always used and the code being benchmarked is executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Benchmark {
  @Benchmark
  public static void oneBenchmark(Blackhole blackhole) {f
    // the code under test is always executed
    blackhole.consume(tested.get());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now run all the benchmarks with the &lt;code&gt;gradle jmh&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Et voila! A super simple intro to JMH in a Gradle project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;For additional details, see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a slightly more in-depth &lt;a href="https://www.baeldung.com/java-microbenchmark-harness"&gt;JMH tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;a description of &lt;a href="https://www.baeldung.com/java-jvm-warmup"&gt;JVM warmup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;extra configuration options for the &lt;a href="https://github.com/melix/jmh-gradle-plugin#configuration-options"&gt;JMH Gradle plugin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://github.com/openjdk/jmh"&gt;JMH project&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until next time!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>benchmark</category>
      <category>java</category>
      <category>gradle</category>
      <category>jmh</category>
    </item>
    <item>
      <title>An update on efficient multi-layered key ownership</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Tue, 21 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/an-update-on-efficient-multi-layered-key-ownership-50m7</link>
      <guid>https://dev.to/mihaibojin/an-update-on-efficient-multi-layered-key-ownership-50m7</guid>
      <description>&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/system-design/algorithms-for-effective-key-ownership?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;In the &lt;a href="https://mihaibojin.com/application-settings/system-design/read-key-from-multiple-sources#how-do-we-establish-key-ownership?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=rss"&gt;previous article&lt;/a&gt;, I introduced the concept of &lt;em&gt;key ownership&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/props-sh/props"&gt;Props&lt;/a&gt; is a library that allows users to load settings from multiple sources, layering them in a predetermined order.&lt;/p&gt;

&lt;p&gt;Given two sources (i.e., layers) added to the Props registry in order, if both define a key (&lt;code&gt;X&lt;/code&gt;), the second one wins (we say &lt;em&gt;owns&lt;/em&gt;) the key and will provide its value, which we call an &lt;em&gt;effective value&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;My original design was to keep a map of keys to values for each &lt;em&gt;Layer&lt;/em&gt; and further have the &lt;em&gt;Registry&lt;/em&gt; keep a synchronized map of keys to layers, enabling it to determine which &lt;em&gt;Layer&lt;/em&gt; owns a &lt;em&gt;Key&lt;/em&gt; and then query it to retrieve its effective value.&lt;/p&gt;

&lt;p&gt;As I was implementing this algorithm, I realized it was allowing a race condition that would require a severe decrease in throughput to fix.&lt;/p&gt;

&lt;p&gt;Imagine the following scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;given that both Layers 1 and 2 define values for X&lt;/li&gt;
&lt;li&gt;when the client requests X&lt;/li&gt;
&lt;li&gt;then the registry determines Layer 2 owns X&lt;/li&gt;
&lt;li&gt;however, before retrieving a value, Layer 2's source updates itself and unsets X&lt;/li&gt;
&lt;li&gt;in this scenario, the client will receive a wrong response (&lt;code&gt;X=null&lt;/code&gt;) instead of the correct value from Layer 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This situation is similar to a &lt;a href="https://en.wikipedia.org/wiki/Isolation_(database_systems)#Dirty_reads"&gt;dirty read&lt;/a&gt; in a transactional database system.&lt;/p&gt;

&lt;p&gt;To prevent this edge case, &lt;strong&gt;we'd need a global, per-key lock&lt;/strong&gt; to ensure that the &lt;em&gt;Layer&lt;/em&gt; that owns &lt;em&gt;X&lt;/em&gt; cannot unset it during a &lt;code&gt;get(X)&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Since this would play out over multiple objects (&lt;em&gt;Registry&lt;/em&gt; and &lt;em&gt;Layer&lt;/em&gt;) and data structures (&lt;code&gt;Map&amp;lt;Key, Layer&amp;gt;&lt;/code&gt;, &lt;code&gt;Map&amp;lt;Key,Value&amp;gt;&lt;/code&gt;), we would need another data structure to indicate that &lt;code&gt;Key&lt;/code&gt; is currently being retrieved.&lt;/p&gt;

&lt;p&gt;When any layer is updated, we'd check this map and ensure we only update &lt;code&gt;X&lt;/code&gt; when it is not being read. &lt;strong&gt;In high QPS applications, this would quickly become a bottleneck!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are ways to optimize this flow, but ultimately I realized that this design was flawed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's most interesting to me is that I had this epiphany while writing this very article, showing the value of &lt;a href="https://twitter.com/hashtag/buildinginpublic"&gt;building in public&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you have to organize your thoughts well enough to write them down, &lt;strong&gt;magic happens!&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;This situation prompted me to rethink the implementation.&lt;/p&gt;

&lt;p&gt;Instead of mapping &lt;em&gt;keys&lt;/em&gt; to &lt;em&gt;layers&lt;/em&gt;, I could instead map &lt;em&gt;keys&lt;/em&gt; to &lt;em&gt;effective values&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This would allow the registry to keep a single synchronized &lt;code&gt;Map&amp;lt;Key,EffectiveValue&amp;gt;&lt;/code&gt;, guaranteeing atomic operations and acting similarly to the &lt;a href="https://en.wikipedia.org/wiki/Isolation_(database_systems)#Read_committed"&gt;read committed&lt;/a&gt; isolation level in a DBMS. This &lt;em&gt;feels&lt;/em&gt; like a better implementation!&lt;/p&gt;

&lt;p&gt;Let's examine some code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// first we need an object to hold a value/layer pair
class ValueLayer {
    final String value;
    final Layer layer;

    // compares this object with deconstructed properties (value, layer) to avoid creating a new instance
    boolean equalTo(@Nullable String value, Layer layer) {
        return Objects.equals(value, this.value) &amp;amp;&amp;amp; Objects.equals(layer, this.layer);
    }
}

// then we can implement reads/writes
public class Registry {
  protected final ConcurrentHashMap&amp;lt;String, ValueLayer&amp;gt; effectiveValues = new ConcurrentHashMap&amp;lt;&amp;gt;();

  // reads are straighforward, since the map is synchronized
  public ValueLayer get(String key) {
    return this.effectiveValues.get(key);
  }

  // writes are a bit more complex, since we need to determine how the value changes
  public ValueLayer put(String key, @Nullable String value, Layer layer) {
    return this.effectiveValues.compute(
        key,
        (k, current) -&amp;gt; {
          if (current == null) {
            // if we had no previous value for this key

            // and if the new value is null
            if (value == null) {
              // do not map the key
              return null;
            }

            // otherwise map the effective value and its owning layer
            try {
              return new ValueLayer(value, layer);
            } finally {
              // and send an update to any objects following this key
              this.sendUpdate(key, value, layer);
            }
          }

          // if nothing has changed
          if (current.equalTo(value, layer)) {
            // simply return the current value
            return current;
          }

          // if the layer's priority is lower than the current owner
          int oldPriority = current.layer.priority();
          int priority = layer.priority();
          if (priority &amp;lt; oldPriority) {
            // do nothing as this layer does not hold ownership over the key
            return current;
          }

          // if the value has not changed
          if (Objects.equals(current.value, value)) {
            // we need to modify the layer/value mapping
            // but not send an update to any objects following this key
            return new ValueLayer(value, layer);
          }

          ValueLayer ret;

          // but if the value has changed

          if (value == null &amp;amp;&amp;amp; priority == oldPriority) {
            // it was either unset from the owning layer
            // in which case we need to find a lower priority layer that defines this key
            ret = findPrevious(key, current);

          } else if (value == null) {
            // it was unset in a higher priority layer
            // in which case, this is a no-op
            return current;

          } else {
            // or it was legitimately updated
            // and we need to modify the layer/value mapping
            ret = new ValueLayer(value, layer);
          }

          try {
            return ret;
          } finally {
            // and send an update to any objects following this key
            this.sendUpdate(key, ret != null ? ret.value : null, layer);
          }
        });
  }

  // sends updates to any objects following the key
  public abstract void sendUpdate(String key, String value, Layer layer);

  // finds the first lower priority layer that defines the key
  // or returns null if one does not exist
  private static ValueLayer findPrevious(String key, ValueLayer current);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After I fully implement this solution, I will do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write a few unit tests to verify that the behavior of the system will not change after further refactorings.&lt;/li&gt;
&lt;li&gt;write a few benchmarks and compare this new design with the previous version, for which I'll use&lt;a href="https://github.com/openjdk/jmh"&gt;Java Microbenchmark Harness (jmh)&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Until next time, stay tuned and if you want to follow this series by email don't forget to &lt;a href="https://motivated-founder-807.ck.page/4c5fb25380"&gt;subscribe&lt;/a&gt;!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>ownership</category>
      <category>concurrency</category>
      <category>isolationlevel</category>
    </item>
    <item>
      <title>Hosting my site on Vercel</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Thu, 09 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/hosting-my-site-on-vercel-4a85</link>
      <guid>https://dev.to/mihaibojin/hosting-my-site-on-vercel-4a85</guid>
      <description>&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/personal-site/hosting-on-vercel?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;Today I switched my site's hosting from &lt;a href="https://gatsbyjs.com"&gt;Gatsby Cloud&lt;/a&gt; to &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After writing my &lt;a href="https://MihaiBojin.com/application-settings/system-design/read-key-from-multiple-sources?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=rss"&gt;latest post on application design&lt;/a&gt;, I ran into an issue I couldn't quite solve.&lt;/p&gt;

&lt;p&gt;So I decided to take this opportunity and try out Vercel!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we go any further, take a moment to check your &lt;code&gt;package.json&lt;/code&gt; file. It should contain the &lt;code&gt;engine&lt;/code&gt; section, enabling Vercel to use the same version as on your local development environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "engines": {
    "node": "&amp;gt;=14.17 &amp;lt;15"
  },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hosting on Vercel
&lt;/h2&gt;

&lt;p&gt;Porting my site over was super easy:&lt;/p&gt;

&lt;p&gt;First, I went to &lt;a href="https://vercel.com/new"&gt;https://vercel.com/new&lt;/a&gt;, where I clicked &lt;em&gt;Add GitHub Org or Account&lt;/em&gt;, authenticated with my GitHub credentials, and imported my site's repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/4b21a430607655928be3c4f3eca3176b/b12f7/023-vercel-new-site.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GDNkbMIw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/4b21a430607655928be3c4f3eca3176b/e9beb/023-vercel-new-site.png" alt='"adding a new site in Vercel"' title='"adding a new site in Vercel"'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vercel automatically detected I was using &lt;em&gt;GatsbyJS&lt;/em&gt; and preconfigured the project for me.&lt;/p&gt;

&lt;p&gt;All I had to do was to click &lt;strong&gt;Deploy!&lt;/strong&gt; Vercel promptly built my site and started serving it on a subdomain. Done (or not quite)!&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom domain
&lt;/h2&gt;

&lt;p&gt;Since I wanted to use my &lt;a href="https://MihaiBojin.com"&gt;own domain&lt;/a&gt;, I had to do a bit of extra config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/dashboard/domains"&gt;Vercel's domains page&lt;/a&gt; is a good starting point, but you can also achieve the same from your project's dashboard.&lt;/p&gt;

&lt;p&gt;I manage all my domains using Cloudflare and maintain DNS records using Terraform, so I had to update my zone config and reapply them.&lt;/p&gt;

&lt;p&gt;Here is the Terraform HCL I used, which can be adapted and applied with &lt;code&gt;terraform plan &amp;amp;&amp;amp; terraform apply&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "cloudflare_record" "com_mihaibojin_apex" {
  zone_id = var.zone_id_com_mihaibojin
  name = "mihaibojin.com"
  value = "76.76.21.21"
  type = "A"
}

resource "cloudflare_record" "com_mihaibojin_vercel_cname" {
  zone_id = var.zone_id_com_mihaibojin
  name = "www"
  value = "cname.vercel-dns.com"
  type = "CNAME"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  That's all, folks
&lt;/h2&gt;

&lt;p&gt;And that was it! After a few minutes of simple configurations, my site is now hosted on Vercel.&lt;/p&gt;

&lt;p&gt;What's even cooler is that its performance (judging by the Lighthouse report) seems to be a bit better!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/b3e7a386584c43133a2d5b9b243651b6/55b2a/023-vercel-lighthouse.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WWqScAS1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/b3e7a386584c43133a2d5b9b243651b6/e9beb/023-vercel-lighthouse.png" alt='"Lighthouse report for my site hosted on Vercel"' title='"Lighthouse report for my site hosted on Vercel"'&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>hosting</category>
      <category>personalsite</category>
      <category>gatsbycloud</category>
    </item>
    <item>
      <title>System Design goal: efficient property reads from multiple sources</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Mon, 06 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/system-design-goal-efficient-property-reads-from-multiple-sources-4n4o</link>
      <guid>https://dev.to/mihaibojin/system-design-goal-efficient-property-reads-from-multiple-sources-4n4o</guid>
      <description>&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/system-design/read-key-from-multiple-sources?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;.  Since it contains a few diagrams, it cannot be correctly displayed on DevTo.  I highly recommend reading it on &lt;a href="https://MihaiBojin.com/application-settings/system-design/read-key-from-multiple-sources?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;my site!&lt;/a&gt; 🔔&lt;/p&gt;




&lt;h2&gt;
  
  
  A problem of ownership
&lt;/h2&gt;

&lt;p&gt;I set out to build the &lt;a href="https://github.com/props-sh/props"&gt;props&lt;/a&gt; library to standardize retrieving &lt;code&gt;(key,value)&lt;/code&gt; pairs from multiple &lt;em&gt;sources&lt;/em&gt; while also allowing a simple mechanism to establish &lt;em&gt;key&lt;/em&gt; precedence when more than one source is aware of the same key.&lt;/p&gt;

&lt;p&gt;Let's consider the following scenario, where multiple &lt;em&gt;source_s define _property X&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source (Layer)&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prop X is defined by source 1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop X is defined by source 3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop X is defined by source 4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Effective value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sources can be property files, system properties, or they can be coming from a key-value store or database.&lt;/p&gt;

&lt;p&gt;Since each &lt;em&gt;(key,value)&lt;/em&gt; pair can change independently on each source, we need to establish a key's precedence, basically answering the &lt;strong&gt;"Who provides the value for this key?"&lt;/strong&gt; question.&lt;/p&gt;

&lt;p&gt;We do so by introducing the concept of &lt;strong&gt;layers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each added layer can retrieve data from a source. As we add more layers, each takes precedence over the previous layer, for any keys it defines.&lt;/p&gt;

&lt;p&gt;We call this concept &lt;strong&gt;ownership.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources vs. layers
&lt;/h2&gt;

&lt;p&gt;Let's think for a moment about sources and layers.&lt;/p&gt;

&lt;p&gt;A naïve approach would be to model a 1:1 relationship between Sources and Layers, basically creating a single entity to represent both, then passing a list of these entities to a &lt;em&gt;registry&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We see that both sources point to the &lt;em&gt;same file on disk&lt;/em&gt;. However, we have two objects that independently read the same data and create a potential for contention and consistency problems (e.g., SourceLayer 1 reads data, the file is updated on disk, SourceLayer2 reads different data).&lt;/p&gt;

&lt;p&gt;Consistency is not always possible and cannot always be a property of a system, but in this case, we can ensure that a &lt;em&gt;source&lt;/em&gt; has the same view of its data in a running JVM process.&lt;/p&gt;

&lt;p&gt;Rather than have &lt;em&gt;source_s and _layer_s be the same (1:1), let's instead have a single _source&lt;/em&gt; representing a dataset and multiple &lt;em&gt;layers&lt;/em&gt; (1:N) which receive updates when the dataset changes. An added benefit of this design is that our system only needs to do &lt;strong&gt;one read operation per dataset&lt;/strong&gt; instead of &lt;em&gt;one per layer&lt;/em&gt;. Since I/O is generally more "expensive" than in-memory operations, &lt;strong&gt;this is a win!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are the components of our system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Source&lt;/code&gt;: loads the data from its origin (e.g., filesystem), reloads it using user-defined logic (e.g., an interval), and notifies registered layers of any reload events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Layer&lt;/code&gt;: holds copies of the data for each registry&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Registry&lt;/code&gt;: queries data from each of its layers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Retrieving the effective value
&lt;/h2&gt;

&lt;p&gt;But what if a layer adds a new key or removes an existing one!?&lt;/p&gt;

&lt;p&gt;Between two reads of &lt;em&gt;X&lt;/em&gt;, the owner could change from Layer 3 to Layer 2.&lt;/p&gt;

&lt;p&gt;A simple algorithm to solve this problem is always to query all layers, e.g.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask Layer 1-3: Do you define &lt;em&gt;X&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Layers 2 and 3 respond &lt;em&gt;yes&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Layer 3 has precedence and &lt;em&gt;owns&lt;/em&gt; the key&lt;/li&gt;
&lt;li&gt;X is unset from Layer 3&lt;/li&gt;
&lt;li&gt;we repeat the first three steps and establish Layer 2 as the &lt;em&gt;owner of X&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This choice is not ideal. First, we'd have to repeat this &lt;strong&gt;on every read&lt;/strong&gt; , which would result in &lt;code&gt;O(K)&lt;/code&gt; time-complexity (where K is the number of layers in the registry) for every single read property. This doesn't sound performant!&lt;/p&gt;

&lt;p&gt;A side objective here is to deal with a high frequency of ownership changes; for example, a source dataset may repeatedly set and unset one or more keys in a short interval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type casting values
&lt;/h2&gt;

&lt;p&gt;Before we talk about ownership, let's quickly segue into data types. The values will always be serialized as strings as we're reading them from property files, system properties, and the environment. (The library will support loading values from other data stores, e.g., databases, but the problem of casting strings from property files remains)&lt;/p&gt;

&lt;p&gt;Here's an example of a standard Java property file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string.prop=some value
int.prop=42
float.prop=1.23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though we have integer and float values, the relevant Java APIs will still return a &lt;code&gt;Map&amp;lt;String,String&amp;gt;&lt;/code&gt;, leaving the job of casting these values to the appropriate types to the developer.&lt;/p&gt;

&lt;p&gt;In practice, while developers "come and go" in projects, this results in many ways to, not-so-optimally, load strings and repeatedly convert them to the desired type.&lt;/p&gt;

&lt;p&gt;When I set out to build the &lt;code&gt;props&lt;/code&gt; library, I wanted to give developers "one" way of loading the values corresponding to each key without worrying about types, simple validations, or caching. My goal is to implement a no-hassle, efficient library for dealing with application properties!&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we establish key ownership?
&lt;/h2&gt;

&lt;p&gt;Ok, back to ownership!&lt;/p&gt;

&lt;p&gt;The registry needs to keep track of a large enough number of keys (let's say in the 10,000s range), and retrieve values from the appropriate layer.&lt;/p&gt;

&lt;p&gt;Developers will be able to rely on a &lt;strong&gt;registry&lt;/strong&gt; to retrieve values cast to their desired type.&lt;/p&gt;

&lt;p&gt;Let's examine the internals of a &lt;code&gt;get(key, type)&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;A client queries the registry (1), which in turn checks all its layers (2), awaiting responses (3), then determining the &lt;em&gt;owner&lt;/em&gt; and returning the &lt;em&gt;effective value&lt;/em&gt; to the client (4).&lt;/p&gt;

&lt;p&gt;In practical applications, we can assume a &lt;em&gt;registry&lt;/em&gt; will have a few layers (&lt;code&gt;K&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If our application handles &lt;code&gt;N&lt;/code&gt; properties (keys), then reading all the properties at least once would be an &lt;code&gt;O(N*K)&lt;/code&gt; operation. Given that ultimately a key is &lt;em&gt;owned&lt;/em&gt; by a single layer, the best we can hope for is &lt;code&gt;O(N)&lt;/code&gt; for N keys, or &lt;code&gt;O(1)&lt;/code&gt;, constant time, per key.&lt;/p&gt;

&lt;p&gt;This time-complexity is not only more efficient but also totally achievable!&lt;/p&gt;




&lt;p&gt;Internally, the &lt;em&gt;registry&lt;/em&gt; needs to keep track of which layers own each key.&lt;/p&gt;

&lt;p&gt;Since layers can be updated concurrently, we need a thread-safe data structure.&lt;/p&gt;

&lt;p&gt;Since we need to &lt;em&gt;"map"&lt;/em&gt; (pun intended) keys to layers, we need to use a &lt;code&gt;Map&lt;/code&gt; (duh!)&lt;/p&gt;

&lt;p&gt;The Java SDK provides a very efficient, thread-safe map: &lt;a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html"&gt;ConcurrentHashMap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how this could look in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// retrieves the value for the specified key&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;clz&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// finds the owner&lt;/span&gt;
        &lt;span class="nc"&gt;Layer&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// retrieves the value&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;effectiveValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// casts it&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;effectiveValue&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// retrieves the value for the specified key, from this layer alone&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last piece of the puzzle is updating ownership. Below, we examine the &lt;em&gt;new key&lt;/em&gt; case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Source&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// reloads data periodically, at the specified interval&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the trigger will be periodically scheduled at an interval&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// data is reloaded from source&lt;/span&gt;
            &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...;&lt;/span&gt;

            &lt;span class="c1"&gt;// and then sent to all registered layers for this source&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Layer&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;layers&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onReload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// we may also want a sync stage here&lt;/span&gt;
            &lt;span class="c1"&gt;// to make the new dataset available to all sources at the same time&lt;/span&gt;
            &lt;span class="c1"&gt;// but it's beyond the scope, for the moment&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// processes the reloaded data&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onReload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// for each key in the dataset&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entrySet&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// updates the value&lt;/span&gt;
            &lt;span class="n"&gt;updateValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="c1"&gt;// and notifies the registry that a new owner might be available&lt;/span&gt;
            &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;bindKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// decides the priority of this layer, in the current registry&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// reference to the registry that owns this layer&lt;/span&gt;
    &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt; &lt;span class="nf"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// stores the (key,value) pair&lt;/span&gt;
    &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Registry&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Layer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// registers ownership of a layer over a key&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;bindKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Layer&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// finds the current owner&lt;/span&gt;
        &lt;span class="nc"&gt;Layer&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// determines if ownership change is required&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// change the current owner&lt;/span&gt;
            &lt;span class="n"&gt;owners&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// from this point, all "get"s of "key" will be routed to "layer"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scenario above details how the system will handle new keys appearing in a layer.&lt;/p&gt;

&lt;p&gt;A full implementation also needs to consider &lt;em&gt;deleted keys&lt;/em&gt; and &lt;em&gt;updated keys&lt;/em&gt;. For those cases, we'd want to avoid calling &lt;code&gt;registry().bindKey(...)&lt;/code&gt; since it would result in a no-op anyway.&lt;/p&gt;




&lt;p&gt;Overall, I am willing to bet that ownership changes will be less frequent than value changes in real-world scenarios. So, the upfront cost of &lt;em&gt;adding&lt;/em&gt; and &lt;em&gt;deleting&lt;/em&gt; keys, represented by registering and unregistering keys from a layer, will be worth it to achieve constant time (&lt;code&gt;O(1)&lt;/code&gt;) reads per key.&lt;/p&gt;

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

&lt;p&gt;At this point, &lt;strong&gt;I am genuinely excited to implement this code!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm sure I will learn more as I go along and may change my mind on some of these choices, but this is the plan for now!&lt;/p&gt;

&lt;p&gt;Until next time...&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>java</category>
      <category>threadsafe</category>
      <category>datastructures</category>
    </item>
    <item>
      <title>Create a new open-source Java project using the Gradle build tool</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Thu, 02 Sep 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/create-a-new-open-source-java-project-using-the-gradle-build-tool-5abj</link>
      <guid>https://dev.to/mihaibojin/create-a-new-open-source-java-project-using-the-gradle-build-tool-5abj</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8TABVfPe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/db0fa6a41af61721f6326d3a5d922574/4968a/002-danist-soh-8Gg2Ne_uTcM-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8TABVfPe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/db0fa6a41af61721f6326d3a5d922574/4968a/002-danist-soh-8Gg2Ne_uTcM-unsplash.jpg" alt="Photo: Building"&gt;&lt;/a&gt;"Photo by &lt;a href="https://unsplash.com/@danist07?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Danist Soh&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/build?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/starting-an-open-source-java-project-with-gradle?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;When building a house, you should start with a strong foundation. Similarly, creating a new open-source project requires a certain level of organization!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Roughly, here is a base minimum that makes for a decent project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A version control system: having it is a must-have, especially if you're planning to publish an open-source project!&lt;/li&gt;
&lt;li&gt;A README: help your visitors understand what the project is about and how they can find out more&lt;/li&gt;
&lt;li&gt;A LICENSE: choosing one early informs potential users if they can avail of the project&lt;/li&gt;
&lt;li&gt;A CONTRIBUTING guide: this is not precisely day one stuff, but starting one and updating it as your project develops, encourages outside contributions&lt;/li&gt;
&lt;li&gt;A build tool: regardless of the chosen programming language, you will need a way to manage dependencies and build or redistribute your project as binaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any decent build tool should be able to support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subprojects or modules&lt;/li&gt;
&lt;li&gt;code formatting&lt;/li&gt;
&lt;li&gt;static code analysis&lt;/li&gt;
&lt;li&gt;global versioning scheme&lt;/li&gt;
&lt;li&gt;generating sources and documentation&lt;/li&gt;
&lt;li&gt;running tests&lt;/li&gt;
&lt;li&gt;enforcing a coding standard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at all of these in detail below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Version Control System
&lt;/h2&gt;

&lt;p&gt;Most projects today use &lt;code&gt;git&lt;/code&gt; hosted on GitHub. Other options exist, but they are beyond the scope of this article.&lt;/p&gt;

&lt;p&gt;To create a new repository, take the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new organization: &lt;a href="https://github.com/account/organizations/new?coupon=&amp;amp;plan=team_free"&gt;https://github.com/account/organizations/new?coupon=&amp;amp;plan=team_free&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new repository&lt;/li&gt;
&lt;li&gt;Choose a license (I chose MIT because it's permissive; doing so will create a &lt;code&gt;LICENSE&lt;/code&gt; file in your repository)&lt;/li&gt;
&lt;li&gt;Here's the result: &lt;a href="https://github.com/props-sh/props"&gt;https://github.com/props-sh/props&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Basic repository information
&lt;/h2&gt;

&lt;p&gt;At the very least, provide essential information, in a &lt;code&gt;README&lt;/code&gt;, that helps your users understand what the project is about, how it can help them if they can use it, and how they can contribute to it.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;LICENSE&lt;/code&gt; file informs potential users about the conditions under which they can use your project. This article doesn't aim to inform about all differences between various types of licenses, but you can &lt;a href="https://choosealicense.com/"&gt;read more about it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, a &lt;code&gt;CONTRIBUTING&lt;/code&gt; guide will help potential developers open PRs and collaborate in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build tool
&lt;/h2&gt;

&lt;p&gt;And now, let's get to the 'meaty' part.&lt;/p&gt;

&lt;p&gt;Since I'm writing a Java project, this is very opinionated. For the &lt;a href="https://github.com/MihaiBojin/props"&gt;first iteration&lt;/a&gt; of this project, I used &lt;a href="https://bazel.build/"&gt;Bazel&lt;/a&gt; but, since I'm starting from scratch, I thought I'd have some fun and use &lt;a href="https://gradle.org/"&gt;Gradle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I started by installing &lt;a href="https://sdkman.io/usage"&gt;SDKman!&lt;/a&gt; and then Gradle: &lt;code&gt;sdk install gradle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since I'd never used it before, I started by reading the &lt;a href="https://gradle.org/guides/#getting-started"&gt;getting started guide&lt;/a&gt;. Another helpful page is &lt;a href="https://docs.gradle.org/7.2/userguide/building_java_projects.html"&gt;how to configure java projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://www.jetbrains.com/idea/"&gt;IntelliJ&lt;/a&gt; as an IDE, and it has full support for Gradle using Kotlin DSL.&lt;/p&gt;

&lt;p&gt;Next, let's configure a sound basis for a Java library made up of multiple submodules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root directory configuration
&lt;/h2&gt;

&lt;p&gt;I've set up this project as a &lt;a href="https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:creating_multi_project_builds"&gt;multi-project build&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I started by creating a &lt;code&gt;settings.gradle.kts&lt;/code&gt; file, which defines a name, and the &lt;code&gt;pluginManagement/repositories&lt;/code&gt; section, which represents a set of artifact repositories to use in all (sub) projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rootProject.name = "props"

pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define a global version for all dependencies
&lt;/h3&gt;

&lt;p&gt;I created a &lt;code&gt;build.gradle.kts&lt;/code&gt; file and defined a &lt;code&gt;group&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;group = "sh.props"
version = project.version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version is implicitly loaded from the project and can be specified in a &lt;code&gt;gradle.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version=0.2.0-SNAPSHOT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or by passing it via the command-line, e.g., &lt;code&gt;gradle -Pversion=0.3.0 ...&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a subproject
&lt;/h2&gt;

&lt;p&gt;IntelliJ can easily create subprojects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;right-click the project and select New Module&lt;/li&gt;
&lt;li&gt;or press Cmd-N&lt;/li&gt;
&lt;li&gt;select Gradle Java module&lt;/li&gt;
&lt;li&gt;fill in the details and you're good to go!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, create a new directory and inside of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a &lt;code&gt;build.gradle.kts&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;create a Maven-like directory structure: &lt;code&gt;src/main/java&lt;/code&gt;, &lt;code&gt;src/main/resources&lt;/code&gt;, &lt;code&gt;src/test/java&lt;/code&gt;, &lt;code&gt;src/test/resources&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Include this subproject in the &lt;code&gt;settings.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;include("java-props-core")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set a java toolchain version
&lt;/h2&gt;

&lt;p&gt;Edit the subproject's &lt;code&gt;build.gradle.kts&lt;/code&gt; file and define the following block to set the Java version to 11.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    `java-library`
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_11.toString()))
    }
}

// additionally also allow incremental builds, speeding up build speed
tasks.compileJava {
    options.isIncremental = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate source JARs and Javadocs
&lt;/h2&gt;

&lt;p&gt;Since &lt;code&gt;props&lt;/code&gt; is a library, I wanted to generate source and documentation (Javadoc) JARs.&lt;/p&gt;

&lt;p&gt;Luckily, this is super easy to do in Gradle. Again, edit the &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java {
    withJavadocJar()
    withSourcesJar()
}

tasks.create&amp;lt;Zip&amp;gt;("docZip") {
    archiveFileName.set("doc.zip")
    from("doc")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running tests
&lt;/h2&gt;

&lt;p&gt;I chose the JUnit 5 (Jupiter) framework, for which we need to set up a few things:&lt;/p&gt;

&lt;p&gt;First, define a new task that will run the tests when called&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tasks.test {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()

    // limit heap usage to 1G (can we tweaked later)
    // https://docs.gradle.org/7.2/userguide/java_testing.html#sec:test_execution
    maxHeapSize = "1G"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As well as the necessary dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    testImplementation(libs.junit.jupiter.api)
    testRuntimeOnly(libs.junit.jupiter.engine)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;libs.junit.jupiter.api&lt;/code&gt; notation represents &lt;em&gt;type-safe project dependencies&lt;/em&gt; in Gradle. These are defined in the root project settings; see below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define a version catalog
&lt;/h3&gt;

&lt;p&gt;Version catalogs are an incubating feature and must be explicitly enabled.&lt;/p&gt;

&lt;p&gt;Edit the &lt;code&gt;settings.gradle.kts&lt;/code&gt; file and add the following section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// enable the incubating feature
enableFeaturePreview("VERSION_CATALOGS")

dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            // define the version once
            version("junit", "5.7.2")

            // then create aliases to each component, referencing the version above
            alias("junit-jupiter-api").to("org.junit.jupiter", "junit-jupiter-api").versionRef("junit")
            alias("junit-jupiter-engine").to("org.junit.jupiter", "junit-jupiter-engine").versionRef("junit")
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the above is done, any tests deployed in &lt;code&gt;**/src/test/java&lt;/code&gt; can be run by executing &lt;code&gt;gradle test&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code formatting
&lt;/h2&gt;

&lt;p&gt;As a codebase grows, it's essential for the code to 'look' the same, as it helps give developers a consistent experience.&lt;/p&gt;

&lt;p&gt;The chosen format (style) is less important than doing this from early on. After all, you can always change the style and reformat the codebase in one go later on!&lt;/p&gt;

&lt;p&gt;This is, in my opinion, best done by a tool that indiscriminately auto-formats your code (as you write it)!&lt;/p&gt;

&lt;p&gt;Gradle has a great plugin for this: &lt;a href="https://github.com/diffplug/spotless"&gt;spotless&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Define the plugin's version in your &lt;code&gt;settings.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pluginManagement {
    plugins {
        id("com.diffplug.spotless").version("5.14.3")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add the following configuration to your &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spotless {
    // generic formatting for miscellaneous files
    format("misc") {
        target("*.gradle", "*.md", ".gitignore")

        trimTrailingWhitespace()
        indentWithSpaces()
        endWithNewline()
    }

    // chose the Google java formatter, version 1.9
    java {
        googleJavaFormat("1.9").aosp()

        // and apply a license header
        licenseHeaderFile(rootProject.file("props.license.kt"))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see in the comments, it formats Java files as well as other extensions.&lt;/p&gt;

&lt;p&gt;For Java, it uses the &lt;a href="https://github.com/google/google-java-format"&gt;google java formatter&lt;/a&gt; and also applies a license header to every file. The &lt;code&gt;props.license.kt&lt;/code&gt; contains a copy of the &lt;code&gt;LICENSE&lt;/code&gt; file with a few tweaks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the text is a comment block: (&lt;code&gt;/* ... */&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;and the year is programmatically filled in: (&lt;code&gt;$YEAR&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
MIT License

Copyright (c) $YEAR Mihai Bojin

Permission is hereby granted...
*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check formatting with &lt;code&gt;gradle spotlessJavaCheck&lt;/code&gt; or apply it with &lt;code&gt;gradle spotlessJavaApply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding standard
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Checking that a project's coding standard meets minimal quality criteria results in better code!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this I use &lt;code&gt;checkstyle&lt;/code&gt;, which is also very easy to set up with Gradle:&lt;/p&gt;

&lt;p&gt;Add the following to &lt;strong&gt;settings.gradle.kts&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            // make a global version available
            version("checkstyle", "9.0")
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then edit the subproject's &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins {
    checkstyle
}

checkstyle {
    // will use the version declared in the catalog
    toolVersion = libs.versions.checkstyle.get()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the check with &lt;code&gt;gradle checkstyleMain&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smarter static code analysis
&lt;/h2&gt;

&lt;p&gt;Finally, the last component I'll be talking about in this article is &lt;code&gt;errorprone&lt;/code&gt;, a library built by Google that performs smarter static code analysis. Apart from being a great standalone mistake finder, it also integrates with other projects such as &lt;a href="https://github.com/uber/NullAway"&gt;NullAway&lt;/a&gt;, a tool that helps developers find and fix &lt;code&gt;NullPointerException&lt;/code&gt;s in their Java code.&lt;/p&gt;

&lt;p&gt;Start by adding the plugin to your &lt;code&gt;settings.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pluginManagement {
    plugins {
        id("net.ltgt.errorprone").version("2.0.2")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then add the following configuration to your subproject's &lt;code&gt;build.gradle.kts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import net.ltgt.gradle.errorprone.errorprone
plugins {
    id("net.ltgt.errorprone")
}

dependencies {
    errorprone(libs.errorprone.core)
    errorprone(libs.nullaway)
}

// hook up the checker to the compilation target
tasks.withType&amp;lt;JavaCompile&amp;gt;().configureEach {
    options.errorprone.disableWarningsInGeneratedCode.set(true)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Hopefully, this tutorial gave you an idea of the first steps you should take towards starting an open-source Java library on GitHub.&lt;/p&gt;

&lt;p&gt;So far, I only spent a few hours learning how to do all of these from scratch and had a lot of fun doing it!&lt;/p&gt;

&lt;p&gt;Since I haven't ported over any code from the v1 repo, I have probably yet to smooth out all the rough edges. If you end up using it, ping me &lt;a href="https://twitter.com/mihaibojin"&gt;on Twitter&lt;/a&gt; and tell me how you got on! ( &lt;strong&gt;I'd appreciate it!&lt;/strong&gt; )&lt;/p&gt;

&lt;p&gt;You can find all the code referenced above &lt;a href="https://github.com/props-sh/props/commit/8243ea7c9c27c47be49dec70a3479dcd131ae2b0"&gt;on GitHub in the props project&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Stay tuned for more articles in this series, and don't forget to &lt;a href="https://motivated-founder-807.ck.page/4c5fb25380"&gt;subscribe to my newsletter&lt;/a&gt;! I'll be sending regular updates as I write more code.&lt;/p&gt;

&lt;p&gt;Thank you and until next time!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>java</category>
      <category>gradle</category>
      <category>build</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Designing a library for reading layered application settings in Java</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Sun, 29 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/designing-a-library-for-reading-layered-application-settings-in-java-2egf</link>
      <guid>https://dev.to/mihaibojin/designing-a-library-for-reading-layered-application-settings-in-java-2egf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--obXNlBFO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/e1ac4dd65e02c65689d331a94ef78196/4968a/001-sigmund-f0dJjQMhfXo-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--obXNlBFO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/e1ac4dd65e02c65689d331a94ef78196/4968a/001-sigmund-f0dJjQMhfXo-unsplash.jpg" alt="Photo: Clock mechanism"&gt;&lt;/a&gt;"Photo by &lt;a href="https://unsplash.com/@sigmund?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Sigmund&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/settings?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/application-settings/reading-application-settings-in-java?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;For the past year, I've been on and off thinking of a common problem: handling properties in Java. While the problem seems simple, dealing with multiple sources, optionality, secrets, etc., is not trivial.&lt;/p&gt;

&lt;p&gt;I started by looking for existing implementations, but I couldn't find many.&lt;/p&gt;

&lt;p&gt;The closest I found was &lt;a href="https://github.com/Netflix/archaius/wiki/Overview"&gt;Netflix's Archaius&lt;/a&gt;, but it didn't do everything I needed and had features I didn't need. However, what sealed the deal was that the library seemed abandoned, with the last minor release having happened in 2019.&lt;/p&gt;

&lt;p&gt;I decided to build my own (&lt;a href="https://www.bmc.com/blogs/not-invented-here-syndrome/"&gt;#NIH&lt;/a&gt;, I know 😝).&lt;/p&gt;

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

&lt;p&gt;My first attempt resulted in the &lt;a href="https://github.com/mihaibojin/props"&gt;props&lt;/a&gt; library. For this, I had a few goals.&lt;/p&gt;

&lt;p&gt;I wanted the ability to &lt;strong&gt;load properties from multiple sources&lt;/strong&gt; : &lt;a href="https://docs.oracle.com/javase/tutorial/essential/environment/properties.html"&gt;Java property files&lt;/a&gt;, &lt;a href="https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html"&gt;System properties&lt;/a&gt;, &lt;a href="https://docs.oracle.com/javase/tutorial/essential/environment/env.html"&gt;OS environment variables&lt;/a&gt;, etc.&lt;/p&gt;

&lt;p&gt;Since these sources are text-based, the values are first loaded as strings. However, strings can be deserialized into many types, which is what the application would expect. Hence, we want such a library to type cast values into the appropriate (standard or user-defined) types. We could do that every time a value is read. However, doing so on every read would result in many unnecessary type conversions. A better approach is to cast once and memoize the typed value until and if it changes.&lt;/p&gt;

&lt;p&gt;Apart from its value, &lt;strong&gt;a &lt;em&gt;prop&lt;/em&gt; has other attributes (metadata)&lt;/strong&gt;. The metadata includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if a &lt;em&gt;prop&lt;/em&gt; is required or optional&lt;/li&gt;
&lt;li&gt;if it has a default value&lt;/li&gt;
&lt;li&gt;if it represents a secret (in which case extra care needs to be taken)&lt;/li&gt;
&lt;li&gt;a description of what the property is used for, aiding with documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A key could be defined in more than one source, so the library &lt;strong&gt;needs to determine which source owns which key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then, the values could change at the source; to tackle that, we'd need a mechanism to check for updates and refresh any updated keys periodically.&lt;/p&gt;

&lt;p&gt;We also want to account for &lt;strong&gt;extendability&lt;/strong&gt;. For example, a client of this library might need to define new sources (e.g., read properties from a &lt;a href="https://www.mongodb.com/"&gt;MongoDB database&lt;/a&gt; or an HTTP endpoint). To that end, we provide an interface that users can implement to add extra functionality.&lt;/p&gt;

&lt;p&gt;Another use case is &lt;strong&gt;managing secrets&lt;/strong&gt;. While it is best to use production-ready solutions (e.g., a &lt;a href="https://en.wikipedia.org/wiki/Key_Management_Interoperability_Protocol"&gt;KMIP&lt;/a&gt;-based server), some applications require keys to be defined in property files. In that case, it is best to encrypt them at rest. Encrypting generates binary output; a common way to represent binary data as text (e.g., in a property file) is to base64-encode it. Our library would need to be able to deserialize a base64-encoded, encrypted value as a string seamlessly.&lt;/p&gt;

&lt;p&gt;Since I wanted to use this library in Java projects, I set up a CD pipeline to release it to &lt;a href="https://search.maven.org/artifact/com.mihaibojin.props/props-core/0.0.4/jar"&gt;Maven central&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have built &lt;a href="https://props.mihaibojin.com/"&gt;Props v1&lt;/a&gt; with the above in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architectural problem
&lt;/h2&gt;

&lt;p&gt;However, I was too focused on building a proof of concept and didn't think too much about performance. I also built this first version with a static use-case in mind (the client is responsible for refreshing a &lt;em&gt;prop&lt;/em&gt;'s value.)&lt;/p&gt;

&lt;p&gt;There are, however, cases where a client would also benefit from being notified when values change. Upon realizing that, I added support for notifying subscribers of updates. I also started looking at where the bottlenecks were. While the library is by no means slow, a few original decisions make it difficult to improve.&lt;/p&gt;

&lt;p&gt;This is a common problem in software engineering. For established libraries with significant user adoption, it is costly to rearchitect from scratch. Usually, the code would be updated slowly, taking great care to avoid breaking changes.&lt;/p&gt;

&lt;p&gt;Since this is a personal project with no current production usage - I have a bit of flexibility.&lt;/p&gt;

&lt;p&gt;I have decided to rewrite the code with performance in mind. However, I also want to focus on asynchronous primitives, which are a better choice for today's software.&lt;/p&gt;

&lt;p&gt;Imagine the following scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A.&lt;/strong&gt; A property is loaded once from a configuration file and used to configure the maximum number of requests allowed by the application in a given interval. When the application needs to be reconfigured, the file is updated, and the application restarted. Restarting is not instantaneous, and it can result in downtime for users. To avoid it, multiple instances need to be run and managed by an external orchestration service (e.g., Kubernetes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B.&lt;/strong&gt; The same property is aware of its origin and can notify the implementing code of any updates. The application can then reconfigure itself without fully restarting.&lt;/p&gt;

&lt;p&gt;There is, of course, no 100% right solution. If a platform already manages the application, and multiple instances are deployed and running, and if restarting the service is a fast enough endeavor, Solution A may be perfectly viable. In most cases, however, at least having the option of using Solution B is a positive net benefit: it's there, but you don't have to use it. Not having it, though, could be limiting. This logic could be extended to replacing secrets, enabling a new feature for an increasing subset of users, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  How would a better implementation look like?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Glossary of terms
&lt;/h3&gt;

&lt;p&gt;Let's define a few common terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;source&lt;/strong&gt; : where a key=value pair originates from&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prop&lt;/strong&gt; : an object that can return a type cast value for its registered &lt;em&gt;key&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;registry&lt;/strong&gt; : an object that keeps track of all &lt;strong&gt;sources&lt;/strong&gt; and &lt;strong&gt;props&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;layers&lt;/strong&gt; : a mechanism that helps to establish the priority in which sources own a particular &lt;em&gt;key&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;effective value&lt;/strong&gt; : the final value of a key, taken from the source that owns it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design considerations
&lt;/h3&gt;

&lt;p&gt;Let's outline a few design considerations.&lt;/p&gt;

&lt;p&gt;Sources must be layered in the sense that they should establish &lt;strong&gt;clear precedence over which source ultimately owns which key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a &lt;em&gt;value&lt;/em&gt; is updated on a &lt;em&gt;source&lt;/em&gt;, and that source owns the &lt;em&gt;prop&lt;/em&gt;, the prop needs to be (eventually) updated with the new value. "Eventually" means that a prop may not see all the state transitions (writes) between reads. It's important to note that &lt;strong&gt;I am not designing an event streaming solution&lt;/strong&gt;. Therefore, some updates may never reach the client.&lt;/p&gt;

&lt;p&gt;We need to choose a &lt;strong&gt;1:1 or 1:many mapping between keys and &lt;em&gt;prop&lt;/em&gt; objects&lt;/strong&gt; in the &lt;em&gt;registry&lt;/em&gt;. On the one hand, allowing more than one prop for any given key could be flexible. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var prop1 = new StringProp("my.key");
var prop2 = new StringProp("my.key");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple callers could bind and use new &lt;em&gt;prop&lt;/em&gt; objects without any limitations. However, I believe this choice would enable users to write inefficient code.&lt;/p&gt;

&lt;p&gt;The next question is, &lt;strong&gt;should we allow &lt;em&gt;props&lt;/em&gt; of different types to be bound to the same &lt;em&gt;key&lt;/em&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var prop1 = new DurationProp("my.key");
var prop2 = new LongProp("my.key");
var prop3 = new StringProp("my.key");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could argue that all three forms are needed, but the same could be achieved in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var prop = new DurationProp("my.key");
Duration result = prop.get();
Long seconds = result.toSeconds();
String stringValue = result.toString();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asynchronous updates would need to be propagated fast. There is no point in implementing such a library only to support a few updates per second or only a few tens of keys defined in the system.&lt;/p&gt;

&lt;p&gt;Let's focus on performance by having the library &lt;strong&gt;restrict one &lt;em&gt;prop&lt;/em&gt; object per &lt;em&gt;key&lt;/em&gt;&lt;/strong&gt; to keep the number of prop objects small and the implementation lean.&lt;/p&gt;

&lt;p&gt;Since sources can specify custom update intervals, &lt;strong&gt;we can assume values could change, and keys could appear or disappear&lt;/strong&gt; , on multiple sources, in quick succession.&lt;/p&gt;

&lt;p&gt;Let's look at a few scenarios for a given &lt;em&gt;key&lt;/em&gt; defined on two sources:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Ownership&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prop is defined by source 1&lt;/td&gt;
&lt;td&gt;Source 1, val=1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is defined by source 2&lt;/td&gt;
&lt;td&gt;Source 2, val=2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is updated by source 1&lt;/td&gt;
&lt;td&gt;Source 2, val=3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is updated by source 2&lt;/td&gt;
&lt;td&gt;Source 2, val=4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is deleted from source 1&lt;/td&gt;
&lt;td&gt;Source 2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is defined by source 1&lt;/td&gt;
&lt;td&gt;Source 2, val=5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is deleted from source 2&lt;/td&gt;
&lt;td&gt;Source 1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is defined by source 2&lt;/td&gt;
&lt;td&gt;Source 2, val=6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is deleted from source 2&lt;/td&gt;
&lt;td&gt;Source 1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prop is deleted from source 1&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our implementation needs to handle all cases correctly. These could happen in fast succession for a large number of keys, so it's essential to handle these state transitions as efficiently as possible.&lt;/p&gt;

&lt;p&gt;Some of them are easier than others; for example, updating a key's value without changing source ownership is straightforward. However, changing the source that owns a key is more involved since &lt;strong&gt;we need first to determine the correct &lt;em&gt;source&lt;/em&gt; that should provide the &lt;em&gt;effective value&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is a real possibility of &lt;strong&gt;update storms&lt;/strong&gt; ; for example, a source could repeatedly own and disown a key over a short interval. This is probably more of an edge case than a legit need. In any case, the implementation should be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;effectively deal with ownership transitions in quick succession&lt;/li&gt;
&lt;li&gt;flag when this behavior occurs so that a human operator can investigate the correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to note that this case is different than everchanging values (e.g., a boolean flag that keeps flipping from true to false and back, or a counter that is incremented thousands of times per second.)&lt;/p&gt;




&lt;p&gt;Another question is if &lt;strong&gt;we should eagerly or lazily update each key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even though several sources could potentially define thousands of key-value pairs, that doesn't necessarily imply that those values are always read or that they are refreshed at the same interval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eagerly&lt;/strong&gt; establishing source-ownership and updating the &lt;em&gt;effective value&lt;/em&gt; of every key would probably be costly (performance-wise).&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;lazily&lt;/strong&gt; determining which source owns each key at reading time, retrieving the &lt;em&gt;effective value&lt;/em&gt;, and finally, type casting it would result in slower reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The implementation will need an efficient way to keep track of &lt;em&gt;key&lt;/em&gt; ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, when a value is required, it would be best if the &lt;em&gt;registry&lt;/em&gt; could go directly to the &lt;em&gt;source&lt;/em&gt; that owns that key, instead of having first to find (iterate over all sources, in O(N) time) the appropriate source. Respectively, when a &lt;em&gt;source&lt;/em&gt;'s values are updated, it also needs to efficiently publish updates to all &lt;em&gt;prop&lt;/em&gt; objects it owns while skipping ones it does not.&lt;/p&gt;

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

&lt;p&gt;I could write more, but for now, this article would have hopefully set the stage for what this library will do.&lt;/p&gt;

&lt;p&gt;Rather than outlining every possible feature ahead of time, I prefer developing a series of articles as I get more and more into the code. Who knows, I may even change my mind about some of these things along the way!&lt;/p&gt;

&lt;p&gt;I plan on (re)writing this Java library from scratch and documenting it all, including topics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setting up a GitHub repository&lt;/li&gt;
&lt;li&gt;configuring a build system&lt;/li&gt;
&lt;li&gt;configuring a CI/CD pipeline&lt;/li&gt;
&lt;li&gt;writing performant, well-tested code&lt;/li&gt;
&lt;li&gt;pushing releases to a public Maven repo (e.g., Maven Central)&lt;/li&gt;
&lt;li&gt;formatting the code to a common standard&lt;/li&gt;
&lt;li&gt;linting the code&lt;/li&gt;
&lt;li&gt;benchmarking and documenting a performance baseline&lt;/li&gt;
&lt;li&gt;and others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for more, and don't forget to &lt;a href="https://motivated-founder-807.ck.page/4c5fb25380"&gt;subscribe to my newsletter&lt;/a&gt; if you want to receive all updates about this series by email!&lt;/p&gt;

&lt;p&gt;Until next time...&lt;/p&gt;

&lt;p&gt;Mihai&lt;/p&gt;

</description>
      <category>settings</category>
      <category>properties</category>
      <category>java</category>
      <category>deserialization</category>
    </item>
    <item>
      <title>Crossposting articles from Gatsby to Medium, Dev.to, and Hashnode</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Sat, 21 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/crossposting-articles-from-gatsby-to-medium-dev-to-and-hashnode-4m13</link>
      <guid>https://dev.to/mihaibojin/crossposting-articles-from-gatsby-to-medium-dev-to-and-hashnode-4m13</guid>
      <description>&lt;p&gt;As sole authors, especially when starting up, we don't have established audiences. Without a critical mass of people to reach, the content we put so much love and care into ends up being ignored and not helping anyone. This is tough to deal with from a psychological level, but also what many people call ' &lt;strong&gt;the grind&lt;/strong&gt;.'&lt;/p&gt;

&lt;p&gt;With all that, it's essential to take any opportunity to reach a broader audience. One solution is to crosspost our content on various distribution platforms.&lt;/p&gt;

&lt;p&gt;Since I write for software developers, my go-to places are &lt;a href="https://medium.com"&gt;Medium.com&lt;/a&gt;, &lt;a href="https://dev.to"&gt;Dev.To&lt;/a&gt;, and &lt;a href="https://hashnode.com"&gt;Hashnode.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Generally, this is a two-step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create an &lt;a href="https://www.gatsbyjs.com/docs/how-to/adding-common-features/adding-an-rss-feed/"&gt;RSS feed&lt;/a&gt; containing your most recent articles&lt;/li&gt;
&lt;li&gt;(a) have the destination monitor the feed and pull new articles or (b) use automation tools to push new content using an API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dig in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing an RSS feed from GatsbyJS
&lt;/h2&gt;

&lt;p&gt;Start by installing one of Gatsby's plugins:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install gatsby-plugin-feed&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once installed, add it to your &lt;code&gt;gatsby-config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-feed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;feeds&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RSS feed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/rss.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration above is not enough to generate a usable feed.&lt;/p&gt;

&lt;p&gt;Let me break it down, option-by-option.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: when I first implemented the RSS feed on my site, I struggled to find examples that explained how I could achieve everything I wanted. I hope this article will help others avoid my experience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Retrieve site metadata
&lt;/h3&gt;

&lt;p&gt;First, each feed will need to access the site's metadata. The format of your site's metadata can be different, but generally, the GraphQL query to select it will look as follows. If it doesn't work, you can always start your GatsbyJS server in development mode (&lt;code&gt;gatsby develop&lt;/code&gt;) and debug the query using the &lt;a href="http://localhost:8000/___graphql"&gt;GraphiQL explorer&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-feed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
          {
            site {
              siteMetadata {
                title
                description
                author {
                  name
                }
                siteUrl
              }
            }
          }        
        `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;gatsby-plugin-feed&lt;/code&gt; relies on a few pre-defined (but not well-documented) field names to build the resulting RSS.&lt;/p&gt;

&lt;p&gt;Below, you can see a snippet with line-by-line explanations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-feed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// make the markdown available to each feed&lt;/span&gt;
            &lt;span class="na"&gt;allMarkdownRemark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allMarkdownRemark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// note the &amp;lt;generator&amp;gt; field (optional)&lt;/span&gt;
            &lt;span class="na"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SITE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// publish the site author's name (optional)&lt;/span&gt;
            &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// publish the site's base URL in the RSS feed (optional)&lt;/span&gt;
            &lt;span class="na"&gt;site_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;custom_namespaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// support additional RSS/XML namespaces (see the feed generation section below)&lt;/span&gt;
              &lt;span class="na"&gt;cc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;dc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://purl.org/dc/elements/1.1/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://search.yahoo.com/mrss/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Select articles to include in the RSS feed
&lt;/h3&gt;

&lt;p&gt;This and the next sections are highly specific to your site and are based on how you set up your posts' frontmatter. You will likely need to customize these, but they should hopefully serve as an example of how to generate a feed will all the necessary article data.&lt;/p&gt;

&lt;p&gt;The provided GraphQL query filters articles with &lt;code&gt;includeInRSS: true&lt;/code&gt; in their frontmatter and sorts the results by publishing date (&lt;code&gt;frontmatter___date&lt;/code&gt;), most recent first.&lt;/p&gt;

&lt;p&gt;Since articles on my site support a featured (cover) image (&lt;code&gt;featuredImage {...}&lt;/code&gt;), we want to select and include it in the feed, along with the &lt;code&gt;alt&lt;/code&gt; and &lt;code&gt;title&lt;/code&gt; fields.&lt;/p&gt;

&lt;p&gt;We also select several other fields from frontmatter and some made available by &lt;code&gt;allMarkdownRemark&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-feed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;feeds&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="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
              {
                allMarkdownRemark(
                  filter: { frontmatter: { includeInRSS: { eq: true } } }
                  sort: { order: DESC, fields: [frontmatter___date] },
                ) {
                  nodes {
                    excerpt
                    html
                    rawMarkdownBody
                    fields {
                      slug
                    }
                    frontmatter {
                      title
                      description
                      date
                      featuredImage {
                        image {
                          childImageSharp {
                            gatsbyImageData(layout: CONSTRAINED)
                          }
                        }
                        imageAlt
                        imageTitleHtml
                      }
                      category {
                        title
                      }
                      tags
                    }
                  }
                }
              }            
            `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="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;h3&gt;
  
  
  Serializing the necessary data
&lt;/h3&gt;

&lt;p&gt;The last step in the generation process is to merge all the available data and generate complete feed entries. This is achieved during serialization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&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="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gatsby-plugin-feed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;feeds&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="na"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;allMarkdownRemark&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// iterate and process all nodes (articles)&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allMarkdownRemark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// store a few shorthands that we'll need multiple times&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// populate the canonical URL&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articleUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// retrieve the URL (src=...) of the article's cover image&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;featuredImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                  &lt;span class="nx"&gt;siteUrl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;featuredImage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;childImageSharp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gatsbyImageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fallback&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="c1"&gt;// augment each node's frontmatter with extra information&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="c1"&gt;// if a description isn't provided,&lt;/span&gt;
                  &lt;span class="c1"&gt;// use the auto-generated excerpt&lt;/span&gt;
                  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="c1"&gt;// article link, used to populate canonical URLs&lt;/span&gt;
                  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articleUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="c1"&gt;// trick: you also need to specify the 'url' attribute so that the feed's&lt;/span&gt;
                  &lt;span class="c1"&gt;// guid is labeled as a permanent link, e.g.: &amp;lt;guid isPermaLink="true"&amp;gt;&lt;/span&gt;
                  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articleUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="c1"&gt;// specify the cover image&lt;/span&gt;
                  &lt;span class="na"&gt;enclosure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;featuredImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="c1"&gt;// process local tags and make them usable on Twitter&lt;/span&gt;
                  &lt;span class="c1"&gt;// note: we're publishing tags as categories, as per the RSS2 spec&lt;/span&gt;
                  &lt;span class="c1"&gt;// see: https://validator.w3.org/feed/docs/rss2.html#ltcategorygtSubelementOfLtitemgt&lt;/span&gt;
                  &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;makeTwitterTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="c1"&gt;// only include the 5 top-most tags (most platforms support 5 or less)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="na"&gt;custom_elements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="c1"&gt;// advertise the article author's name&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="c1"&gt;// supply an image to be used as a thumbnail in your RSS (optional)&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media:thumbnail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;_attr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;featuredImage&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="c1"&gt;// specify your content's license&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cc:license&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://creativecommons.org/licenses/by-nc-sa/4.0/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="c1"&gt;// advertise the site's primary author&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dc:creator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;renderHtmlLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                        &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SITE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorName&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="c1"&gt;// the main article body&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content:encoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;// prepend the feature image as HTML&lt;/span&gt;
                        &lt;span class="nx"&gt;generateFeaturedImageHtml&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                          &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;featuredImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="na"&gt;imageAlt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;featuredImage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;imageAlt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="na"&gt;imageTitleHtml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;featuredImage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;imageTitleHtml&lt;/span&gt;
                        &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                        &lt;span class="c1"&gt;// append the content, fixing any relative links&lt;/span&gt;
                        &lt;span class="nx"&gt;fixRelativeLinks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                          &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="na"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;}),&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few caveats here:&lt;/p&gt;

&lt;p&gt;I'm using a few custom functions I wrote, here is the source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generates HTML for the featured image, to prepend it to the node's HTML&lt;/span&gt;
&lt;span class="c1"&gt;// so that sites like Medium/Dev.to can include the image by default&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateFeaturedImageHtml&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;imageAlt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;imageTitleHtml&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageTitleHtml&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;figcaption&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageTitleHtml&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;/figcaption&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;figure&amp;gt;&amp;lt;img src="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" alt="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;imageAlt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" /&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/figure&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Takes a tag that may contain multiple words and&lt;/span&gt;
&lt;span class="c1"&gt;// returns a concatenated tag, with every first letter capitalized&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;makeTwitterTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;upperFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Invalid tag, cannot create empty slug from: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Prepends the siteURL on any relative links&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fixRelativeLinks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// fix static links&lt;/span&gt;
  &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\s)\/&lt;/span&gt;&lt;span class="sr"&gt;static&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\/static\/`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// fix relative links&lt;/span&gt;
  &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;=href=&lt;/span&gt;&lt;span class="se"&gt;\")\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&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;Most of the extra tags are optional but can be helpful if your RSS gets redistributed further, in which case you'd want users to have as much metadata about you and your site as possible!&lt;/p&gt;

&lt;p&gt;We prepend the cover image using &lt;code&gt;generateFeaturedImageHtml&lt;/code&gt;, because &lt;a href="https://github.com/Medium/medium-api-docs"&gt;Medium's API&lt;/a&gt; does not support the cover image as a field. However, it parses the first image of the submitted raw content and stores it as a cover. This qualifies as a 'trick' 😊.&lt;/p&gt;

&lt;p&gt;Since GatsbyJS uses React routes, all internal links will end up as relative links in the RSS. We fix this by prepending the site URL on any such links (this includes images), using the &lt;code&gt;fixRelativeLinks&lt;/code&gt; function. (Shoutout to &lt;a href="https://markshust.com/2020/06/25/fixing-images-in-gatsby-rss-feeds/"&gt;Mark Shust&lt;/a&gt;, who documented how to fix this, first!)&lt;/p&gt;

&lt;p&gt;When specifying tags in your frontmatter, define the most relevant ones up-top. For example, Medium only supports five tags, and DevTo supports four, which is why we truncate the number of tags(categories) included in the RSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading articles to various distribution platforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dev.to
&lt;/h3&gt;

&lt;p&gt;Dev.to supports &lt;a href="https://dev.to/settings/extensions"&gt;publishing to DEV Community from RSS&lt;/a&gt;. This is very easy to set up once you've done all the above. They will parse your RSS feed and create draft articles from new content. You can then review and publish these in the DEV community!&lt;/p&gt;

&lt;h3&gt;
  
  
  Medium.com
&lt;/h3&gt;

&lt;p&gt;Medium does not support reading RSS feeds, so in this case, &lt;a href="https://www.mariokandut.com/how-to-automatically-post-from-gatsby-to-medium-and-dev-to/"&gt;we have to rely on Zapier to crosspost&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I won't repeat the same content here; please see the tutorial linked above for details on how to configure &lt;a href="https://zapier.com"&gt;Zapier&lt;/a&gt;, and have it crosspost your content, for free!&lt;/p&gt;

&lt;h3&gt;
  
  
  Hashnode.com
&lt;/h3&gt;

&lt;p&gt;The more recent platform I'm crossposting to is &lt;a href="https://Hashnode.com"&gt;Hashnode.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since I've been using Zapier for Medium for a while, I thought it would be fun to build a &lt;a href="https://platform.zapier.com/quickstart/create-integration"&gt;Zapier integration&lt;/a&gt;. I started with their UI and later exported and completed the code, so that &lt;a href="https://github.com/MihaiBojin/zapier-integration-create-hashnode-story"&gt;everyone can use it!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're a developer, feel free to clone or fork &lt;a href="https://github.com/MihaiBojin/zapier-integration-create-hashnode-story"&gt;my Zapier integration&lt;/a&gt;, deploy it in your account, and use it to crosspost on Zapier.com.&lt;/p&gt;

&lt;p&gt;Unfortunately, Zapier requires any officially published integrations to be supported by the destination platform (in this case, &lt;a href="https://hashnode.com"&gt;Hashnode&lt;/a&gt;). I've reached out to ask them to take over this code and make it available for their community, but so far, I haven't heard back.&lt;/p&gt;

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

&lt;p&gt;I hope this helps GatsbyJS users properly define RSS feeds and successfully crosspost their articles to reach a wider audience. If you follow this tutorial and struggle, please reach out via &lt;a href="https://twitter.com/mihaibojin"&gt;DM&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>crosspost</category>
      <category>gatsby</category>
      <category>medium</category>
      <category>devto</category>
    </item>
    <item>
      <title>Configuring social sharing cards in GatsbyJS</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Wed, 11 Aug 2021 22:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/configuring-social-sharing-cards-in-gatsbyjs-1loo</link>
      <guid>https://dev.to/mihaibojin/configuring-social-sharing-cards-in-gatsbyjs-1loo</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2FMihaiBojin.com%2Fstatic%2F697c8dcd2f260c25e73f4835bde8fe9d%2Feed2b%2Fnathan-dumlao-kLmt1mpGJVg-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2FMihaiBojin.com%2Fstatic%2F697c8dcd2f260c25e73f4835bde8fe9d%2Feed2b%2Fnathan-dumlao-kLmt1mpGJVg-unsplash.jpg" alt="Photo: Social media apps"&gt;&lt;/a&gt;"Photo by &lt;a href="https://unsplash.com/@nate_dumlao?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Nathan Dumlao&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/twitter?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/personal-site/social-sharing-cards?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo" rel="noopener noreferrer"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;When a user shares a web link, most applications parse a standard set of &lt;a href="https://www.w3schools.com/tags/tag_meta.asp" rel="noopener noreferrer"&gt;meta tags&lt;/a&gt; and use the provided information to render a 'card', including an image, a title, and a summary.&lt;/p&gt;

&lt;p&gt;A few competing standards exist; the most relevant are Twitter &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started" rel="noopener noreferrer"&gt;Cards&lt;/a&gt; and Facebook's &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;Open Graph Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When the necessary meta tags are missing, applications such as Twitter, Facebook, Whatsapp, Signal, Telegram, etc., will be unable to render the card. In such cases, they will either display the link as-is or try to infer some parts of the card metadata; in the latter case, your mileage may vary.&lt;/p&gt;




&lt;p&gt;Here's an example of a site without social sharing cards metadata:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/5ec94c0a59244b23316a52c9723e2d3e/e9c9b/without-card.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2FMihaiBojin.com%2Fstatic%2F5ec94c0a59244b23316a52c9723e2d3e%2Fe9c9b%2Fwithout-card.png" title="Missing card meta-data, inferred title and summary" alt="Screenshot of a shared link to a page without social card metadata"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's how my site's homepage is rendered:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/3cd93860f5b308b164a6c4e766ef8018/379c3/with-card.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2FMihaiBojin.com%2Fstatic%2F3cd93860f5b308b164a6c4e766ef8018%2F379c3%2Fwith-card.png" title="Twitter card with front image, title, summary, and link" alt="Screenshot of a rendered Twitter card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I wanted my site to display correctly in as many apps as possible, I provided both Open Graph and Twitter Card tags!&lt;/p&gt;




&lt;p&gt;I use Gatsby for my site, and unfortunately, it does not have native support for social cards. Also, &lt;a href="https://twitter.com/mihaibojin/status/1396612428075606027" rel="noopener noreferrer"&gt;when I implemented this feature&lt;/a&gt;, I was pretty new to &lt;a href="https://www.gatsbyjs.com/" rel="noopener noreferrer"&gt;GatsbyJS&lt;/a&gt; and had to figure out how to load files and process images through GraphQL (childImageSharp, gatsbyImageData, GatsbyImage, StaticImage).&lt;/p&gt;

&lt;p&gt;It was rough, to say the least (unclear documentation, competing advice for Gatsby v2 and v3, and my general lack of knowledge).&lt;/p&gt;

&lt;p&gt;Fortunately, I prevailed! Here's how:&lt;/p&gt;

&lt;h2&gt;
  
  
  Load any image via GraphQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const { myImage } = useStaticQuery(graphql`
    query {
      myImage: file(relativePath: { eq: "name-of-file.jpg" }) {
        childImageSharp {
          gatsbyImageData(layout: CONSTRAINED)
        }
      }
    }
  `);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works with images placed under &lt;code&gt;src/images/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get a link to a processed image
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getImage, getSrc } from 'gatsby-plugin-image';

// ...

const img = getImage(image);
const imgSrc = SITE_URL + getSrc(img);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create an SEO component
&lt;/h2&gt;

&lt;p&gt;Ok, I'll admit, Gatsby does have good SEO support, including some &lt;a href="https://www.gatsbyjs.com/tutorial/seo-and-social-sharing-cards-tutorial/" rel="noopener noreferrer"&gt;general advice on social cards&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This formed the basis of my configuration, although I had to heavily customize it!&lt;/p&gt;

&lt;p&gt;I configured the SEO component to accept a number of inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function SEO({ title, description, lastUpdated, tags, image, imageAlt, absoluteURL }) {
  const img = getImage(image);
  const imgSrc = SITE_URL + getSrc(img);

  let meta = [];

  //...
  return (
    &amp;lt;Helmet
      htmlAttributes={{prefix: 'og: http://ogp.me/ns#'}}
      title={title}
      meta={meta}
      //...
    /&amp;gt;
  );
}

Seo.propTypes = {
  title: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired,
  lastUpdated: PropTypes.string.isRequired,
  tags: PropTypes.arrayOf(PropTypes.string),
  image: PropTypes.object.isRequired,
  imageAlt: PropTypes.string.isRequired,
  absoluteURL: PropTypes.string.isRequired,
};
export default SEO

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

&lt;/div&gt;



&lt;p&gt;And generated the required meta tags:&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Twitter Card meta tags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;meta.push({
  name: "twitter:card",
  content: "summary_large_image",
});
meta.push({
  name: `twitter:title`,
  content: title,
});
meta.push({
  name: `twitter:description`,
  content: pageDescription,
});
meta.push({
  property: `twitter:image`,
  content: imgSrc,
});
meta.push({
  name: `twitter:image:alt`,
  content: imageAlt,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate Open Graph meta tags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
meta.push({
  property: `og:type`,
  content: `website`,
});
meta.push({
  property: `og:url`,
  content: absoluteURL,
});
meta.push({
  property: `og:title`,
  content: title,
});
meta.push({
  property: `og:description`,
  content: pageDescription,
});
meta.push({
  property: `og:image`,
  content: imgSrc,
});
meta.push({
  name: `og:image:alt`,
  content: imageAlt,
});
meta.push({
  property: `og:updated_time`,
  content: lastUpdated,
});
meta.push({
  property: "og:image:width",
  content: parseInt(img.width),
});
meta.push({
  property: "og:image:height",
  content: parseInt(img.height),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bringing it all together
&lt;/h2&gt;

&lt;p&gt;And that was that! All that was left was for me to send all the required variables via my page layouts, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Seo title="My article" description="Short summary of my article" etc="..."&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But is it working?&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing social sharing card rendering
&lt;/h3&gt;

&lt;p&gt;To test your feature, use the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cards-dev.twitter.com/validator" rel="noopener noreferrer"&gt;Twitter card validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.facebook.com/tools/debug/" rel="noopener noreferrer"&gt;Facebook sharing debugger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus: social sharing buttons
&lt;/h2&gt;

&lt;p&gt;No site would be complete without a feature to allow users &lt;a href="https://twitter.com/mihaibojin/status/1403468240047706129" rel="noopener noreferrer"&gt;to share content on social media easily&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I found an &lt;a href="https://www.codewithlinda.com/blog/social-share-buttons-with-react-share/" rel="noopener noreferrer"&gt;excellent tutorial for integrating social sharing buttons&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;It goes a little bit like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a component and import media from react-share
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  TwitterShareButton,
  TwitterIcon,
} from 'react-share';

const ShareButtons = ({
  title,
  url,
  tags,
  twitterHandle,
}) =&amp;gt; {

  // remove any non-alphanumeric characters
  const hashtags = tags.map((t) =&amp;gt; t.replace(/[^a-zA-Z0-9]+/g, ''));

  return (
    &amp;lt;&amp;gt;
      &amp;lt;TwitterShareButton
        title={title}
        url={url}
        via={twitterHandle}
        hashtags={hashtags}
      &amp;gt;
        &amp;lt;TwitterIcon size={40} round={true} /&amp;gt;
      &amp;lt;/TwitterShareButton&amp;gt;
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Of course, the above is greatly simplified and only displays Twitter. For my site, I defined more.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Include the component on your pages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ShareButtons
  url={SITE_URL + location.pathname}
  title={...}
  description={...}
  tags={[...]}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;And here is the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/8614555ce644b0409e1d0651f8a8f073/f5df4/social-sharing-buttons.jpg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2FMihaiBojin.com%2Fstatic%2F8614555ce644b0409e1d0651f8a8f073%2F1a144%2Fsocial-sharing-buttons.jpg" title="Social sharing buttons on my site" alt="Screenshot of social sharing buttons"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I hope you found this short tutorial helpful! As I'm writing it now, it feels pretty straightforward, but at the time, I struggled for hours trying to piece together confusing or incomplete information from various articles and blogs.&lt;/p&gt;

&lt;p&gt;Hopefully, this can help you avoid my experience and integrate your social sharing features much faster!&lt;/p&gt;

&lt;p&gt;In an ideal world, Gatsby would have come with better, out-of-the-box, social sharing features!&lt;/p&gt;

&lt;p&gt;Maybe one day...&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf" rel="noopener noreferrer"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>socialsharingcards</category>
      <category>tutorial</category>
      <category>introductory</category>
      <category>twitter</category>
    </item>
    <item>
      <title>Debugging social sharing cards</title>
      <dc:creator>Mihai Bojin</dc:creator>
      <pubDate>Wed, 11 Aug 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/mihaibojin/debugging-social-sharing-cards-3lh8</link>
      <guid>https://dev.to/mihaibojin/debugging-social-sharing-cards-3lh8</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TaxcX7Z8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/b863610b47cb2ade27d4bf032c53ec21/4968a/sigmund-QuusekRfTI8-unsplash.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TaxcX7Z8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/b863610b47cb2ade27d4bf032c53ec21/4968a/sigmund-QuusekRfTI8-unsplash.jpg" alt="Photo: Debugging"&gt;&lt;/a&gt;"Photo by &lt;a href="https://unsplash.com/@sigmund?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Sigmund&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/debug?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;🔔 This article was originally posted on my site, &lt;a href="https://MihaiBojin.com/personal-site/debugging-social-cards?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=top-promo"&gt;MihaiBojin.com&lt;/a&gt;. 🔔&lt;/p&gt;




&lt;p&gt;I am currently working on the &lt;a href="https://mihaibojin.com/tools?utm_source=DevTo&amp;amp;utm_medium=organic&amp;amp;utm_campaign=rss"&gt;Tools section&lt;/a&gt; of my site. While implementing it, I had to reconfigure Tailwind to include CSS classes for additional colors.&lt;/p&gt;

&lt;p&gt;Just as I &lt;a href="https://twitter.com/mihaibojin/status/1425073010030022656"&gt;posted this on Twitter&lt;/a&gt;, I noticed the &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started"&gt;Twitter card&lt;/a&gt; no longer worked.&lt;/p&gt;

&lt;p&gt;Since these used to work, I started investigating.&lt;/p&gt;

&lt;p&gt;The first step was to check the &lt;a href="https://cards-dev.twitter.com/validator"&gt;Twitter card validator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/6ece1171d6a5633ad10df2d852f20b34/f4281/twitter-card-validation-error.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vXWNhH1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/6ece1171d6a5633ad10df2d852f20b34/e9beb/twitter-card-validation-error.png" alt="Twitter card validation error" title="Twitter card fails to validate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output was not at all helpful. So, I started scouring the web and found &lt;a href="https://twittercommunity.com/t/error-no-card-found-card-error/130123"&gt;various advice on robots.txt&lt;/a&gt;, &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started"&gt;rules for setting up meta tags&lt;/a&gt;, and general &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/troubleshooting-cards"&gt;troubleshooting advice&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nothing made sense, though, as the problems described in these resources did not affect my site.&lt;/p&gt;

&lt;p&gt;However, I found the &lt;a href="https://developers.facebook.com/tools/debug/"&gt;Facebook debug tool&lt;/a&gt; &lt;a href="https://stackoverflow.com/questions/41740110/twitter-twitter-cards-not-showing-image-after-tweet-post"&gt;during my searches&lt;/a&gt; and promptly used it to test my site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/9e7263e3cb932d45b346275000d0aa79/51ed8/facebook-debug-error.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tUeMUvOV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/9e7263e3cb932d45b346275000d0aa79/e9beb/facebook-debug-error.png" alt="Facebook validation error" title="Facebook debug shows missing og: tags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tool was better than Twitter's one and showed me more information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing &lt;code&gt;og:image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and other properties: &lt;code&gt;og:url&lt;/code&gt;, &lt;code&gt;og:type&lt;/code&gt;, &lt;code&gt;og:title&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, I had a WTF moment since, as you can see below, my site included valid meta tags...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/6ca96c1448f434ff7f259fb0b6677257/5bf79/valid-card-tags.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x5YI83R---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/6ca96c1448f434ff7f259fb0b6677257/e9beb/valid-card-tags.png" alt="Valid meta tags for social cards" title="My site's markup included valid meta tags for cards"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing jumped at me - the HTTP response code was &lt;code&gt;206&lt;/code&gt;. This was unexpected, but in retrospect, quite logical. Twitter and Facebook deal with a large majority of the open web. Allowing their crawlers to process an unlimited input size means opening them up to abuse. Status code 206 means Partial Content, a.k.a that the requested data range was received (but not ALL of the page's content!)&lt;/p&gt;

&lt;p&gt;Immediately, I looked at where the meta tags were placed on my site's HTML and discovered that the &lt;a href="https://www.gatsbyjs.com/docs/how-to/styling/global-css/"&gt;global CSS&lt;/a&gt; preceded my meta tags... all 2 MB of it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://MihaiBojin.com/static/c625d3d6ec924d4d2681dabaaeaf3abb/3996e/2mb-css.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EJWpOSsY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://MihaiBojin.com/static/c625d3d6ec924d4d2681dabaaeaf3abb/3996e/2mb-css.png" alt="2MB CSS before social tags" title="Two MB of CSS preceding the social tags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My next thought was to configure where &lt;code&gt;react-helmet&lt;/code&gt; places its tags, but &lt;a href="https://github.com/nfl/react-helmet/issues/514"&gt;no luck&lt;/a&gt; 😔.&lt;/p&gt;

&lt;p&gt;I thought &lt;code&gt;Gatsby&lt;/code&gt; would surely have this feature, but &lt;a href="https://stackoverflow.com/questions/59975501/gatsby-puts-all-css-in-head"&gt;no go&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I did, however, find the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-split-css/"&gt;gatsby-plugin-split-css&lt;/a&gt; plugin that brought down the CSS to 600K, which is low enough that I no longer run into this issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SCORE!&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I am not entirely happy with this fix - something is off, almost like a code smell. I'm no frontend expert, but I'm starting to suspect why &lt;a href="https://cssinjs.org/"&gt;CSS-in-JS&lt;/a&gt; and &lt;a href="https://emotion.sh/docs/styled"&gt;Emotion&lt;/a&gt; are a thing...&lt;/p&gt;

&lt;p&gt;Long term, I want to try a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get rid of global CSS styles, or at least find a way to include them as a separate file&lt;/li&gt;
&lt;li&gt;reorder the head tags&lt;/li&gt;
&lt;li&gt;move Tailwind's &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; at the very end of &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for now, this will have to do.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

&lt;p&gt;Mihai&lt;/p&gt;




&lt;p&gt;If you liked this article and want to read more like it, &lt;a href="https://motivated-founder-807.ck.page/db1cf284bf"&gt;please subscribe to my newsletter&lt;/a&gt;; I send one out every few weeks!&lt;/p&gt;

</description>
      <category>socialsharingcards</category>
      <category>twitter</category>
      <category>personalsite</category>
      <category>gatsby</category>
    </item>
  </channel>
</rss>
