<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title></title>
    <description></description>
    <link>https://blog.davidfuhr.de/</link>
    <atom:link href="https://blog.davidfuhr.de/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 13 Jun 2026 00:08:31 +0000</pubDate>
    <lastBuildDate>Sat, 13 Jun 2026 00:08:31 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Implement Java’s compareTo() the right way</title>
        <description>&lt;p&gt;Java’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable#compareTo()&lt;/code&gt; looks simple, but small mistakes can create issues that are difficult to find — especially with sorted collections like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeSet&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeMap&lt;/code&gt;, which rely on ordering to determine uniqueness.&lt;/p&gt;

&lt;h2 id=&quot;why-do-you-need-compareto&quot;&gt;Why do you need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt;?&lt;/h2&gt;

&lt;p&gt;Java’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt; interface defines a method for comparing two objects. This can be used to sort your objects in their &lt;strong&gt;natural order&lt;/strong&gt;. Typical examples for natural ordering are&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; → alphabetical order&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDate&lt;/code&gt; → chronological order&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In business code, natural ordering should represent the &lt;strong&gt;most common and expected meaning&lt;/strong&gt; of “sorted”. For example, sorting line items by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt; in an order is usually what humans expect.&lt;/p&gt;

&lt;h2 id=&quot;how-to-implement-compareto&quot;&gt;How to implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;A correct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; must follow these rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Anti-symmetric&lt;/strong&gt;:
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;signum(a.compareTo(b)) == -signum(b.compareTo(a))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transitive&lt;/strong&gt;:
If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a &amp;gt; b&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b &amp;gt; c&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a &amp;gt; c&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Consistent&lt;/strong&gt;:
If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.compareTo(b) == 0&lt;/code&gt;, they are considered &lt;strong&gt;equal in ordering&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also: it is &lt;strong&gt;recommended&lt;/strong&gt; (but not required) that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; is consistent with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equals()&lt;/code&gt;. We will see why that is important later.&lt;/p&gt;

&lt;p&gt;So let’s take the same domain example as before: an order with multiple items. Imagine a type representing a line item in an order:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; → database identity (unique)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt; → business ordering inside the order (1, 2, 3, …)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Natural ordering&lt;/strong&gt;: by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt; (ascending)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Alternative ordering&lt;/strong&gt;: by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; (ascending)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A naive implementation could look like this:&lt;/p&gt;

&lt;figure id=&quot;figure-1&quot;&gt;
  &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comparable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compareTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// risky: compareTo==0 for different ids&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 1: Naive implementation of `compareTo()` for `LineItem`&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Looks reasonable — but this innocent one-liner can silently lose data from your collections without a single exception or warning. Can you spot the flaw?&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-treeset-bug-missing-elements&quot;&gt;The TreeSet bug: “missing elements”&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeSet&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equals()&lt;/code&gt; to decide duplicates.
It uses ordering: if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;, the element is treated as a duplicate.&lt;/p&gt;

&lt;figure id=&quot;figure-2&quot;&gt;
  &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.TreeSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Demo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TreeSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1001L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1002L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// same position, different id&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1003L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;size = &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 2: TreeSet drops elements when compareTo() returns 0&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With the broken &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt;, the output will effectively be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;One of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position=1&lt;/code&gt; entries is dropped&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;size&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a TreeSet bug. It is a &lt;strong&gt;compareTo implementation bug&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-correct-implementation-for-natural-ordering&quot;&gt;A correct implementation for natural ordering&lt;/h2&gt;

&lt;p&gt;If your natural ordering is “position first”, the safe version is:&lt;/p&gt;

&lt;figure id=&quot;figure-3&quot;&gt;
  &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comparable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compareTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byPosition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byPosition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;byPosition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Tie-breaker: ensure a total order (important for TreeSet/TreeMap)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 3: Correct `compareTo()` with `id` tie-breaker for a total order&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;why-the-tie-breaker-matters&quot;&gt;Why the tie-breaker matters&lt;/h3&gt;

&lt;p&gt;This makes ordering:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;sort by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;if same position, sort by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the ordering is &lt;strong&gt;total&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeSet&lt;/code&gt; can safely store multiple items with the same position&lt;/li&gt;
  &lt;li&gt;you avoid accidental “duplicates”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;bonus-use-comparator-for-alternative-ordering&quot;&gt;Bonus: Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparator&lt;/code&gt; for alternative ordering&lt;/h2&gt;

&lt;p&gt;Natural ordering should reflect the &lt;strong&gt;business meaning&lt;/strong&gt;. For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LineItem&lt;/code&gt;, sorting by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt; is usually what humans expect. But you will often also need sorting by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; (for stable output, debugging, or database work). That should be done with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparator&lt;/code&gt;, not by changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt;&lt;/p&gt;

&lt;figure id=&quot;figure-4&quot;&gt;
  &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LineItemOrderings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BY_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;LineItem:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BY_POSITION_THEN_ID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;LineItem:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;thenComparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;LineItem:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 4: Comparators for alternative orderings&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This way you can easily sort by different criteria:&lt;/p&gt;

&lt;figure id=&quot;figure-5&quot;&gt;
  &lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SortingDemo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2002L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2001L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LineItem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2003L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// natural ordering (position, then id)&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Natural: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LineItemOrderings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BY_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ordering by id&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;By id:   &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 5: Sorting with natural order vs comparator&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;common-pitfalls&quot;&gt;Common Pitfalls&lt;/h2&gt;

&lt;p&gt;A common design mistake is to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; sort by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; just because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; is unique. That is usually wrong because:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it does not represent business meaning&lt;/li&gt;
  &lt;li&gt;it surprises readers of the code&lt;/li&gt;
  &lt;li&gt;it breaks UI expectations (“why is position ignored?”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;keep natural ordering for business meaning (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;use comparators for alternative views (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another common flaw is to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; inconsistent with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equals()&lt;/code&gt;. Java recommends consistency:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.compareTo(b) == 0&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.equals(b)&lt;/code&gt; should also be true&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But it is not required. For records, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equals()&lt;/code&gt; uses all components (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt;). If your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; only compares &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt;, it becomes inconsistent with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equals()&lt;/code&gt;. This is often a source of subtle bugs in sorted collections like we explored before with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TreeSet&lt;/code&gt;. Adding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; tie-breaker solves that problem in a clean way.&lt;/p&gt;

&lt;h2 id=&quot;takeaways&quot;&gt;Takeaways&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo()&lt;/code&gt; defines &lt;strong&gt;natural ordering&lt;/strong&gt;, not “any ordering”.&lt;/li&gt;
  &lt;li&gt;Never implement comparison using subtraction (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a - b&lt;/code&gt;): it overflows for large negative values and returns a wrong result silently.&lt;/li&gt;
  &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo() == 0&lt;/code&gt;, sorted collections treat elements as duplicates.&lt;/li&gt;
  &lt;li&gt;For business ordering like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;position&lt;/code&gt;, add a tie-breaker (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;) to create a total order.&lt;/li&gt;
  &lt;li&gt;Use explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparator&lt;/code&gt;s for alternative sorting (like ordering by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you follow these rules, your ordering logic stays predictable, safe, and easy to maintain.&lt;/p&gt;
</description>
        <pubDate>Mon, 27 Apr 2026 09:33:07 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2026/04/27/implement-compareto-the-right-way.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2026/04/27/implement-compareto-the-right-way.html</guid>
        
        <category>java</category>
        
        <category>software-development</category>
        
        
      </item>
    
      <item>
        <title>Using the Web Share API</title>
        <description>&lt;p&gt;I recently learned of the existance of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;Web Share API&lt;/a&gt;: this browser feature provides a mechanism for sharing text, links, files, and other content to an arbitrary share target selected by the user. I have adapted the &lt;em&gt;Share&lt;/em&gt; feature here in this blog to use the Web Share API where available. The &lt;a href=&quot;https://caniuse.com/?search=web-share&quot;&gt;API is currently supported by Edge, Safari and most mobile browsers.&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 03 Feb 2024 18:40:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2024/02/03/using-the-web-share-api.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2024/02/03/using-the-web-share-api.html</guid>
        
        <category>internet</category>
        
        <category>twitter</category>
        
        <category>facebook</category>
        
        <category>share</category>
        
        <category>www</category>
        
        
      </item>
    
      <item>
        <title>Leaving Twitter</title>
        <description>&lt;p&gt;I’ve been a passive user of Twitter for over two years now. My last tweet dates from 8 August 2021. Since the pandemic began, the number of tweets from conspiracy theorists has risen sharply and finally reached an unbearable level for me. I removed my Twitter client (Fenix) from the home page of my phone and ignored Twitter for months. I never really went back. After a while, I did check my timeline from time to time, but that too ended with the third-party client ban in January 2023.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;It&amp;#39;s officially over.&lt;br /&gt;No more Fenix on Android, iOS or macOS.&lt;br /&gt;&lt;br /&gt;Fenix for iOS was suspended earlier today and I&amp;#39;ve removed it from sale.&lt;br /&gt;&lt;br /&gt;I&amp;#39;ve had a great time working on these apps for the past 10 years and I&amp;#39;m very greatful for all your messages and support. &lt;a href=&quot;https://t.co/ibVZng78Ra&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;pic.twitter.com/ibVZng78Ra&lt;/a&gt;&lt;/p&gt;&amp;mdash; Fenix (@fenix_app) &lt;a href=&quot;https://twitter.com/fenix_app/status/1616577597042728961?ref_src=twsrc%5Etfw&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;January 20, 2023&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;A few months later I started using Mastodon. It’s still a very small community, and so far it has none of the toxicity that X has developed. On Mastodon, I also came across a post describing how to host your exported tweets as your own archive. This is even more relevant as X forces users to sign in to browse tweets since June 2023.&lt;/p&gt;

&lt;blockquote class=&quot;mastodon-post&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Since I&apos;m seeing a new wave of people saying they plan to leave Twitter, you may like my browser tool that takes the zip file of your Twitter export and returns a zip file of a web site that lets people browse and search your tweets. You can just upload the files wherever you normally host html and it&apos;ll work just fine.&lt;/p&gt;
&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;&lt;a href=&quot;https://tinysubversions.com/twitter-archive/make-your-own/&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;https://tinysubversions.com/twitter-ar…&lt;/a&gt;&lt;/p&gt;
&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Example archive: &lt;a href=&quot;https://tinysubversions.com/twitter-archive/&quot; rel=&quot;noopener&quot; target=&quot;_blank&quot;&gt;https://tinysubversions.com/twitter-ar…&lt;/a&gt;&lt;/p&gt;&amp;mdash; Darius Kazemi (@darius@friend.camp) &lt;a href=&quot;https://friend.camp/@darius/110635044885680806&quot;&gt;June 30, 2023&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;As I did not like the look and feel of the suggested web application I did a little research and found &lt;a href=&quot;https://github.com/tweetback&quot; rel=&quot;noopener&quot;&gt;tweetback&lt;/a&gt;. I exported my data from X, extracted my tweets, and published my archive at &lt;a href=&quot;https://tweets.davidfuhr.de&quot;&gt;tweets.davidfuhr.de&lt;/a&gt;. I also removed the X widgets integration and replaced the links to my X profile with a link to my personal archive. I’d be happy to see more media outlets join the Fediverse to fuel discussions there and make X less relevant.&lt;/p&gt;

</description>
        <pubDate>Tue, 02 Jan 2024 09:00:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2024/01/02/leaving-twitter.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2024/01/02/leaving-twitter.html</guid>
        
        <category>internet</category>
        
        <category>twitter</category>
        
        <category>mastodon</category>
        
        <category>www</category>
        
        
      </item>
    
      <item>
        <title>Automate Dependency Updates with Renovate</title>
        <description>&lt;h2 id=&quot;software-ages&quot;&gt;Software ages&lt;/h2&gt;

&lt;p&gt;&lt;figure class=&quot;alignright&quot;&gt;
&lt;img src=&quot;https://blog.davidfuhr.de/uploads/2023/renovatebot.svg&quot; width=&quot;100rem&quot; alt=&quot;The Renovate Logo: a paint roller, rolling from top left to bottom right inside a blue circle.&quot; /&gt;
&lt;figcaption&gt;Renovate Logo&lt;/figcaption&gt;
&lt;/figure&gt;&lt;strong&gt;Keeping your dependencies up-to-date is a chore.&lt;/strong&gt; Updating dependencies often gets neglected while working on new features and busy improving your application. But it is necessary to keep your application safe and secure. &lt;strong&gt;&lt;a href=&quot;https://www.mend.io/renovate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Renovate&lt;/a&gt; reminds you of new dependency releases and makes upgrading a bliss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Renovate is a standalone node application. It scans your repositories for dependencies and creates Pull Requests (&lt;abbr title=&quot;also known as&quot;&gt;aka&lt;/abbr&gt;. Merge Requests) for the dependency updates. It supports &lt;a href=&quot;https://docs.renovatebot.com/modules/manager/&quot; rel=&quot;noopener&quot;&gt;a wide range of “package managers”&lt;/a&gt;, including &lt;a href=&quot;https://maven.apache.org/&quot; rel=&quot;noopener&quot;&gt;Maven&lt;/a&gt;, &lt;a href=&quot;https://gradle.org/&quot; rel=&quot;noopener&quot;&gt;Gradle&lt;/a&gt;, and &lt;a href=&quot;https://www.npmjs.com/&quot; rel=&quot;noopener&quot;&gt;npm&lt;/a&gt;, but also &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/&quot; rel=&quot;noopener&quot;&gt;Dockerfile&lt;/a&gt; and &lt;a href=&quot;https://support.atlassian.com/bitbucket-cloud/docs/bitbucket-pipelines-configuration-reference/&quot; rel=&quot;noopener&quot;&gt;bitbucket-pipelines.yml&lt;/a&gt;. &lt;strong&gt;Each update is proposed in a separate Pull Request&lt;/strong&gt; containing information like Changelogs of the dependency and Renovate’s instructions on how to work with the &lt;abbr title=&quot;Pull Requests&quot;&gt;PR&lt;/abbr&gt;. Your build pipeline with your automated tests will automatically verify if your application still works with the updated dependency. If you have enough confidence in your build pipeline, you can configure Renovate to &lt;a href=&quot;https://docs.renovatebot.com/configuration-options/#automerge&quot; rel=&quot;noopener&quot;&gt;auto-merge&lt;/a&gt; the PRs. Otherwise, you can merge the PRs yourself.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Renovate has tons of configuration options allowing to define version ranges for certain dependencies, grouping of dependencies, or telling Renovate to ignore the dependency altogether. It supports &lt;a href=&quot;https://docs.renovatebot.com/configuration-options/#hostrules&quot; rel=&quot;noopener&quot;&gt;private package registries&lt;/a&gt;, &lt;a href=&quot;https://docs.renovatebot.com/configuration-options/#schedule&quot; rel=&quot;noopener&quot;&gt;schedules&lt;/a&gt;, and limits for the number of PR it creates. These configurations can be &lt;a href=&quot;https://docs.renovatebot.com/key-concepts/presets/&quot; rel=&quot;noopener&quot;&gt;shared via repositories&lt;/a&gt; and nested and combined as you like. Additionally, it provides a &lt;a href=&quot;https://docs.renovatebot.com/key-concepts/dashboard/&quot; rel=&quot;noopener&quot;&gt;Dependency Dashboard&lt;/a&gt; that shows an overview of the state of your repositories’ dependencies.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;The easiest way to get started is to use the &lt;a href=&quot;https://hub.docker.com/r/renovate/renovate/&quot; rel=&quot;noopener&quot;&gt;Renovate Docker Container&lt;/a&gt;. But &lt;strong&gt;installing it as node package gives you more control over the Renovate version used.&lt;/strong&gt; So let’s start in an empty folder and install Renovate (&lt;a href=&quot;#figure-1&quot;&gt;Fig. 1&lt;/a&gt;). I assume you have a current version of node and npm installed.&lt;/p&gt;

&lt;figure id=&quot;figure-1&quot;&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;renovate-runner
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;renovate-runner
npm i &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; renovate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 1: Install Renovate node package&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This will generate a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package-lock.json&lt;/code&gt; file. Now you can proceed to create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.js&lt;/code&gt; configuration file (&lt;a href=&quot;#figure-2&quot;&gt;Fig. 2&lt;/a&gt;).&lt;/p&gt;

&lt;figure id=&quot;figure-2&quot;&gt;
  &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// see https://docs.renovatebot.com/self-hosted-configuration/#platform&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;RENOVATE_PLATFORM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;repositories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;davidfuhr/davidfuhr-jekyll&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- Add your repositories here&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;timezone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Europe/Berlin&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;onboardingConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;$schema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://docs.renovatebot.com/renovate-schema.json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;extends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;config:base&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 2: config.js&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;For the following steps I will assume your repositories are hosted on &lt;a href=&quot;https://bitbucket.org&quot; rel=&quot;noopener&quot;&gt;Bitbucket Cloud&lt;/a&gt;. For other platforms, see the &lt;a href=&quot;https://docs.renovatebot.com/modules/platform/&quot; rel=&quot;noopener&quot;&gt;Renovate Platforms Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, you need to &lt;a href=&quot;https://docs.renovatebot.com/modules/platform/bitbucket/&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;create an app password&lt;/strong&gt; for your Bitbucket account&lt;/a&gt;. It is ok to use your personal account for testing purposes, but I recommend creating a dedicated user for the bot later when you automate things. Now all left to do is to export your credentials as environment variables and are ready to run Renovate for the first time (&lt;a href=&quot;#figure-3&quot;&gt;Fig. 3&lt;/a&gt;).&lt;/p&gt;

&lt;figure id=&quot;figure-3&quot;&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RENOVATE_PLATFORM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;bitbucket
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RENOVATE_USERNAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;your_username&amp;gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;RENOVATE_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;your_app_password&amp;gt;

npx renovate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 3: Run Renovate for the first time&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If everything works, then Renovate should have created a Pull Request titled “Configure Renovate” in the configured repositories. The PR’s description contains the packages Renovate discovered, which Update-PRs it will create, and on what configuration it is running. After you merged this PR and run renovate again, it will create the first Update-PRs.&lt;/p&gt;

&lt;h2&gt;Set up on Bitbucket Pipelines&lt;/h2&gt;

&lt;p&gt;The next step is to automate Renovate and run it on a reliable environment, and not your local machine. An easy way to automate Renovate is to run it in Bitbucket Pipeline. A good starting configuration can you see in &lt;a href=&quot;#figure-4&quot;&gt;figure 4&lt;/a&gt;.&lt;/p&gt;

&lt;figure id=&quot;figure-4&quot;&gt;
  &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node:18&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;pipelines&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Lint&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm ci&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx renovate-config-validator&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;custom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;renovate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Renovate&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;caches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;node&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# See https://stackoverflow.com/a/74528937/501248&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;deb&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;http://deb.debian.org/debian&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bullseye-backports&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;main&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;gt;/etc/apt/sources.list.d/bullseye-backports.list&apos;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apt-get update &amp;amp;&amp;amp; apt -t bullseye-backports install -y git&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm ci&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npx renovate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;Fig. 4: bitbucket-pipelines.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Next, you should add the three environment variables we used before (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENOVATE_PLATFORM&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENOVATE_USERNAME&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RENOVATE_PASSWORD&lt;/code&gt;) to the &lt;em&gt;Repository Variables&lt;/em&gt; in your &lt;em&gt;Repository settings&lt;/em&gt;. After that, you can try to &lt;a href=&quot;https://support.atlassian.com/bitbucket-cloud/docs/pipeline-triggers/&quot; rel=&quot;noopener&quot;&gt;run the pipeline manually&lt;/a&gt; (&lt;a href=&quot;#figure-5&quot;&gt;Fig. 5&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;
  &lt;figure class=&quot;aligncenter&quot; id=&quot;figure-5&quot;&gt;
    &lt;a href=&quot;https://blog.davidfuhr.de/uploads/2023/renovate-run-pipeline.png&quot;&gt;
      &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2023/renovate-run-pipeline.png&quot; alt=&quot;Screenshot of the Dialog to run a Bitbucket Pipeline. It offers input fields to select the Branch (“main”), the Pipeline (“custom: renovate”), and buttons to “Cancel” and “Run”.&quot; /&gt;
    &lt;/a&gt;
    &lt;figcaption&gt;Fig. 5: Bitbucket Pipelines: Run Pipeline&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;If everything works as expected, you can proceed to &lt;a href=&quot;https://confluence.atlassian.com/bitbucket/scheduled-builds-for-pipelines-933078702.html&quot; rel=&quot;noopener&quot;&gt;create a schedule&lt;/a&gt; (&lt;a href=&quot;#figure-6&quot;&gt;Fig. 6&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;
  &lt;figure class=&quot;aligncenter&quot; id=&quot;figure-6&quot;&gt;
    &lt;a href=&quot;https://blog.davidfuhr.de/uploads/2023/renovate-create-a-schedule.png&quot;&gt;
      &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2023/renovate-create-a-schedule.png&quot; alt=&quot;Screenshot of the Dialog to create a schedule for a Bitbucket Pipeline. It offers input fields to select the Branch (“main”), the Pipeline (“custom: renovate”), a Schedule (“Daily”, “02:00 - 03:00”), and buttons to “Cancel” and “Create”.&quot; /&gt;
    &lt;/a&gt;
    &lt;figcaption&gt;Fig. 6: Bitbucket Pipelines: Create a schedule&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations! You’re done!&lt;/strong&gt; Now Renovate will run periodically and help you keep your dependencies up-to-date!&lt;/p&gt;

&lt;h2 id=&quot;further-improvements&quot;&gt;Further Improvements&lt;/h2&gt;

&lt;h3 id=&quot;github-token-for-release-notes&quot;&gt;GitHub Token for Release Notes&lt;/h3&gt;

&lt;p&gt;To further improve the description of the PRs, you can &lt;a href=&quot;https://github.com/renovatebot/renovate/blob/main/docs/usage/getting-started/running.md#githubcom-token-for-release-notes&quot; rel=&quot;noopener&quot;&gt;enable Renovate to &lt;strong&gt;pull in public Release Notes from GitHub&lt;/strong&gt;&lt;/a&gt;. To that, you need to &lt;a href=&quot;https://github.com/settings/tokens&quot; rel=&quot;noopener&quot;&gt;create a public API Token&lt;/a&gt; and add it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GITHUB_COM_TOKEN&lt;/code&gt; to your &lt;em&gt;Repository variables&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id=&quot;autodiscover&quot;&gt;Autodiscover&lt;/h3&gt;

&lt;p&gt;Renovate has a powerful feature called “&lt;a href=&quot;https://docs.renovatebot.com/self-hosted-configuration/#autodiscover&quot; rel=&quot;noopener&quot;&gt;autodiscover&lt;/a&gt;”. When you enable autodiscover, by default, &lt;strong&gt;Renovate runs on every repository that the bot account can access&lt;/strong&gt;. You can limit which repositories Renovate can access by using the autodiscoverFilter config option. This releases you from remembering to add every new repository you create to the Renovate configuration.&lt;/p&gt;

&lt;h3 id=&quot;other-tips-and-resources&quot;&gt;Other tips and resources&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.renovatebot.com/&quot; rel=&quot;noopener&quot;&gt;Renovate Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jerrynsh.com/12-tips-to-self-host-renovate-bot/&quot; rel=&quot;noopener&quot;&gt;12 Tips to Self-host Renovate Bot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jintechflow.wordpress.com/2020/10/14/getting-started-with-renovate-using-github/&quot; rel=&quot;noopener&quot;&gt;Getting Started With Renovate Using GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;If your code is hosted on GitHub, it is probably easier to just use &lt;strong&gt;Dependabot&lt;/strong&gt;. You can &lt;a href=&quot;https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates&quot; rel=&quot;noopener&quot;&gt;enable Dependabot&lt;/a&gt; in your repository settings. According to the Dependabot Documentation, it should be possible to run Dependabot for some other Git providers as well, but I didn’t test it:&lt;/p&gt;

&lt;blockquote cite=&quot;https://github.com/dependabot/dependabot-core#how-to-run-dependabot&quot;&gt;
However, if you want to run a custom version of Dependabot or run it on another platform, you&apos;re not left out in the cold. This repo provides the logic necessary for hosting your own standalone Dependabot, as long as you&apos;re not re-selling Dependabot to others. It currently supports opening Pull Requests against repositories hosted on GitHub, Github Enterprise, Azure DevOps, GitLab, BitBucket, and AWS CodeCommit.&lt;br /&gt;
– &lt;a href=&quot;https://github.com/dependabot/dependabot-core#how-to-run-dependabot&quot; rel=&quot;noopener&quot;&gt;github.com/dependabot/dependabot-core&lt;/a&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other alternatives which I didn’t test are &lt;a href=&quot;https://depfu.com/&quot; rel=&quot;noopener&quot;&gt;Depfu&lt;/a&gt;, or &lt;a href=&quot;https://snyk.io/&quot; rel=&quot;noopener&quot;&gt;snyk&lt;/a&gt;. Are there any other alternatives I missed? What do you do to keep your dependencies up-to-date?  Let me know!&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Apr 2023 19:30:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2023/04/17/automate-dependency-updates-with-renovate.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2023/04/17/automate-dependency-updates-with-renovate.html</guid>
        
        <category>software</category>
        
        <category>cloud</category>
        
        <category>bitbucket</category>
        
        <category>development</category>
        
        
      </item>
    
      <item>
        <title>Composing Micro Frontends Server-Side</title>
        <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href=&quot;https://www.esentri.com/composing-micro-frontends-server-side/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;esentri.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;Scaling development&lt;/h2&gt;
&lt;p&gt;Microservices are well-known now for some years and are widely adopted. But the &lt;strong&gt;frontend oftentimes is still monolithic&lt;/strong&gt;. This application structure often reflects on the team structure: there are several independent backend teams and one single frontend team. This also prevents backend development teams from delivering complete usable features - they always lack the frontend. To implement the frontend all changes must be done by the one frontend team. This makes it hard to scale development. By implementing micro frontends we can form &lt;strong&gt;full-stack teams that can ship complete features on their own&lt;/strong&gt;. This is one of the reasons why ThoughtWorks &lt;a href=&quot;https://www.thoughtworks.com/de/radar/techniques/micro-frontends&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;placed micro frontends in the “Adopt” section of their famous Technology Radar&lt;/a&gt; for the third time in a row!&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2&gt;Composing Micro Frontends&lt;/h2&gt;
&lt;p&gt;For our use case, a micro frontend consists of markup (&lt;abbr title=&quot;Hypertext Markup Language&quot;&gt;HTML&lt;/abbr&gt;), styles (&lt;abbr title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/abbr&gt;), scripts (JavaScript), and images. We want to compose the different micro frontends, or “fragments” as they are usually called, in our page. Some fragments are interactive. Some might have dependencies on some fragments. And some might want to interact with other fragments.&lt;/p&gt;

&lt;p&gt;There are two ways to integrate the fragments from your micro frontends: client-side and server-side. Both have their pros and cons. We are comparing different ways of server-side composing because we focus on search engine optimization. &lt;a href=&quot;https://developers.google.com/search/docs/guides/javascript-seo-basics?hl=en&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Google supports lots of JavaScript&lt;/a&gt; nowadays but takes longer to index which is bad for a frequently changing site and some other crawlers don’t support JavaScript at all.&lt;/p&gt;

&lt;p&gt;Also, the support for slow mobile clients, like smartphones, is better with server-side composition. Mobile clients usually have a slower internet connection so they benefit from the smaller payload to download. They also have less computing power so they profit from the pre-rendered HTML.&lt;/p&gt;

&lt;h2&gt;Server Side Includes (SSI)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Server_Side_Includes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SSI&lt;/a&gt; is a &lt;strong&gt;simple server-side scripting language&lt;/strong&gt; that allows conditional statements and the inclusion of other fragments from files and URLs.&lt;/p&gt;

&lt;p&gt;To include another fragment in your page add the and SSI tag like &lt;code&gt;&amp;lt;!--#include virtual=&quot;/url/to/include&quot; --&amp;gt;&lt;/code&gt;. The web server fetches the fragment and replaces the SSI tag with the fragment’s content. The combined output is returned to the client.&lt;/p&gt;

&lt;p&gt;For a more in-depth explanation about composition with SSI take a look at this &lt;a href=&quot;https://web.archive.org/web/20201020035409/https://www.esentri.com/wp-content/uploads/2020/06/2019-02-22-Microfrontends-SSI.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;presentation by my colleague Artun Subasi&lt;/a&gt; from the &lt;em&gt;duesentrieb Camp&lt;/em&gt; last year.&lt;/p&gt;

&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Proven and well-tested&lt;/li&gt;
 	&lt;li&gt;Well supported in Nginx, Apache, and other web servers&lt;/li&gt;
 	&lt;li&gt;Support for timeouts and fallback content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Fragments are fetched in parallel but delivered only after the last fragment is fetched (Nginx)&lt;/li&gt;
 	&lt;li&gt;No built-in support for asset handling. &lt;a href=&quot;https://www.modpagespeed.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Apache PageSpeed&lt;/a&gt; (formerly known as modpagespeed by Google) can do some CSS processing and &lt;a href=&quot;https://www.modpagespeed.com/doc/filter-css-combine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;combine the CSS&lt;/a&gt; from the fragments, but did not receive any updates since early 2018 and seems to be dead&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Edge Side Includes (ESI)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Edge_Side_Includes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ESI&lt;/a&gt; is a &lt;strong&gt;simple markup language&lt;/strong&gt; that allows the inclusion of fragments from other URLs.&lt;/p&gt;

&lt;p&gt;ESI works very similar to SSI. ESI is more focussed on the reverse proxy or &lt;abbr title=&quot;Content Delivery Network&quot;&gt;CDN&lt;/abbr&gt; layer, while SSI targets the web server itself. Of course, these boundaries are somewhat blurry because Nginx is often used as a reverse proxy as well. ESI has no conditional logic itself. But Varnish, for example, allows conditional logic in its own &lt;abbr title=&quot;Domain Specific Languague&quot;&gt;DSL&lt;/abbr&gt; called “&lt;abbr title=&quot;Varnish Configuration Language&quot;&gt;VCL&lt;/abbr&gt;”.&lt;/p&gt;

&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Proven and well-tested&lt;/li&gt;
 	&lt;li&gt;Well supported in &lt;a href=&quot;https://varnish-cache.org/docs/6.4/users-guide/esi.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Varnish&lt;/a&gt; (parallel fetching requires the commercial edition). Squid (project web site is outdated, but there are new releases) and Mongrel (no new releases since 2010) do support it as well&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;No built-in support for a timeout. Proprietary extensions do exist.&lt;/li&gt;
 	&lt;li&gt;Mutual exclusive with use of CDN-based ESI for caching&lt;/li&gt;
 	&lt;li&gt;No built-in support for asset handling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Zalando Tailor&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/zalando/tailor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tailor&lt;/a&gt; is a Node.js based fragment service open-sourced by Zalando and runs as stand-alone service. It is part of &lt;a href=&quot;https://web.archive.org/web/20201009123655/https://www.mosaic9.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Project Mosaic&lt;/a&gt; - Zalando’s umbrella project their frontend modularization.&lt;/p&gt;

&lt;p&gt;Zalando Tailor uses &lt;strong&gt;custom markup to compose the page&lt;/strong&gt;. They define &lt;code&gt;&amp;lt;fragment src=&quot;http://www.example.com&quot;&amp;gt;&lt;/code&gt;-tags to specify the fragments to load. Assets, like styles and scripts, can be populated by the fragment itself via Link header.&lt;/p&gt;

&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Built-in support for asset handling&lt;/li&gt;
 	&lt;li&gt;Fragments as first-party-concept&lt;/li&gt;
 	&lt;li&gt;&lt;span&gt;Asynchronously fetches multiple fragments and streams immediately&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Proprietary solution, needs custom Link-Headers with styles and AMD JS bundling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Podium&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://podium-lib.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Podium&lt;/a&gt; is also implemented in JavaScript and runs on Node.js. It is developed by the norwegian company Finn.no for their classified ads site. Podium is a library to integrate fragments (they call them “podlets”) into the page (they call the page “layout”).&lt;/p&gt;

&lt;p&gt;To run podium you need at least two instances: a layout server and at least one podlet server. The podlet server exposes the fragment, the layout server integrates it into the page. Every fragment needs to expose a “Podlet Manifest” which contains JSON data assets and may contain a fallback URL. The layout server initially fetches the fallback content and caches it in case the podlet server becomes unavailable.&lt;/p&gt;

&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Built-in support for asset handling (fragments need to expose metadata-endpoint)&lt;/li&gt;
 	&lt;li&gt;Fragments as first-party-concept&lt;/li&gt;
 	&lt;li&gt;Built-in proxy-support to hide fragment servers&lt;/li&gt;
 	&lt;li&gt;Fragement servers can define fallback content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
 	&lt;li&gt;Proprietary solution, tight coupling with library&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Final Thoughts and Recommendations&lt;/h2&gt;
&lt;p&gt;ESI and SSI are &lt;strong&gt;well-tested, stable technologies, and are easy to learn&lt;/strong&gt;. Custom additions like Varnish’s VCL add complexity but introduce lots of new possibilities. In general, these solutions offer no good solution for asset handling and &lt;strong&gt;struggle with the requirements of modern web applications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More modern solutions like Tailor and Zalando &lt;strong&gt;solve many problems&lt;/strong&gt; the old-fashioned tools have. But the introduce their &lt;strong&gt;fair share of complexity and have their own learning curve&lt;/strong&gt;. Both tools are more or less a one-man-show (if you look at the contribution logs). So you should be prepared to step in and continue development or fork if Zalando or Finn.no drop their solutions if you make any of these products a cornerstone of your application.&lt;/p&gt;

&lt;p&gt;For a broader overview of composition techniques (includes client-side composition) I can recommend this &lt;a href=&quot;https://bluesoft.com/micro-frontends-the-missing-piece-of-the-puzzle-in-feature-teams/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;article about Micro Frontends by bluesoft&lt;/a&gt;. If you are interested in micro frontends you should take a look at &lt;a href=&quot;https://livebook.manning.com/book/micro-frontends-in-action/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Micro Frontends in Action&lt;/a&gt; by Michael Geers.&lt;/p&gt;

&lt;p&gt;Do you have a better of idea how to compose micro frontends server-side? &lt;a href=&quot;https://twitter.com/DavidFuhr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Contact me via Twitter&lt;/a&gt; and let me know!&lt;/p&gt;
</description>
        <pubDate>Fri, 12 Jun 2020 07:37:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2020/06/12/composing-micro-frontends-server-side.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2020/06/12/composing-micro-frontends-server-side.html</guid>
        
        <category>architecture</category>
        
        <category>frontend</category>
        
        <category>www</category>
        
        
      </item>
    
      <item>
        <title>Maintenance Mode for your API</title>
        <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href=&quot;https://www.esentri.com/maintenance-mode-for-your-api/&quot;&gt;esentri.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently implemented a simple maintenance mode for a &lt;abbr title=&quot;Representational State Transfer&quot;&gt;REST&lt;/abbr&gt; API. Letting the web server return a &lt;code class=&quot;highlighter-rouge&quot;&gt;503 Service Unavailable&lt;/code&gt; sounds easy enough. But there was a catch displaying a maintenance page in our Angular client application.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Our setup is nothing fancy. We have an AngularJS frontend application serviced by a REST API. &lt;a href=&quot;https://nginx.org/en/&quot;&gt;nginx&lt;/a&gt; is used as reverse proxy in front of our backend application servers. It does SSL offloading and simple round-robin load balancing. This makes nginx the central point to toggle the maintenance mode.&lt;/p&gt;

&lt;figure class=&quot;aligncenter&quot;&gt;
    &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2017-07-05_angular-nginx-java.png&quot; alt=&quot;Communication Diagram: Angular, nginx, Java&quot; /&gt;
    &lt;figcaption&gt;Communication Diagram&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My first approach for returning the maintenance status from nginx looked like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (-f $error_root/maintenance.html) {
  return 503;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It checks if the &lt;code class=&quot;highlighter-rouge&quot;&gt;maintenance.html&lt;/code&gt; file is present, and if it is there, the server returns &lt;a href=&quot;https://httpstatuses.com/503&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;503 Service Unavailable&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also implemented an &lt;a href=&quot;https://docs.angularjs.org/api/ng/service/$http#interceptors&quot;&gt;HTTP Interceptor for our AngularJS&lt;/a&gt; frontend to display a nice maintenance page if any request returns a &lt;code class=&quot;highlighter-rouge&quot;&gt;503&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$httpProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;interceptors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;responseError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;503&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// display maintenance page&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Strangely enough, the maintenance page wasn’t triggered.&lt;/p&gt;

&lt;p&gt;After a while of debugging, I found out that it was some kind of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS&quot;&gt;CORS&lt;/a&gt; issue. Although I added the &lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Control-Allow-Origin: \*&lt;/code&gt;-Header to all responses (with and without maintenance mode for debugging purposes) the client interpreted the CORS request with the status code &lt;code class=&quot;highlighter-rouge&quot;&gt;503&lt;/code&gt; as failed and denied access to all response information to JavaScript. Even to the response status code. JavaScript always reported a status code of &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This left me with two options: either display the maintenance page on status code &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt; as well or making the CORS request pass. Triggering the maintenance page on every status code &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt; could have unwanted side effects, like maintenance page on other failing requests, so I rejected this solution and opted for making the CORS request pass.&lt;/p&gt;

&lt;p&gt;I fixed this by adding an extra check in the maintenance switch in the nginx configuration. For the CORS preflight request (which is a &lt;code class=&quot;highlighter-rouge&quot;&gt;OPTIONS&lt;/code&gt; request) I always return &lt;a href=&quot;https://httpstatuses.com/200&quot;&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;200&lt;/code&gt;&lt;/a&gt;. Even in maintenance mode.&lt;/p&gt;

&lt;p&gt;This way the preflight request succeeds and JavaScript has access to the status code of the following request. Now my interceptor was triggered and displayed the maintenance page as expected.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (-f $error_root/maintenance.html) {
  if ($request_method = OPTIONS) {
    return 200;
  }
  return 503;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Did you encounter a similar problem? How did you solve it? Is there a better/cleaner approach to this problem?&lt;/p&gt;
</description>
        <pubDate>Wed, 05 Jul 2017 12:08:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2017/07/05/maintenance-mode-for-your-api.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2017/07/05/maintenance-mode-for-your-api.html</guid>
        
        <category>angularjs</category>
        
        <category>nginx</category>
        
        <category>www</category>
        
        
      </item>
    
      <item>
        <title>Git Workflow: rebase and merge</title>
        <description>&lt;p&gt;I am a fan of a clean git history. It should be easy to spot which commits belong to which feature, which feature was merged when, and what changes were made by whom. I’ve seen several git histories that look more like a pile of spaghetti and it takes a lot of effort to understand the history. I will explain how you can keep things neat and clean by using rebase and how to avoid the pitfalls I ran into.&lt;/p&gt;

&lt;figure class=&quot;alignleft&quot;&gt;
    &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2016-10-20_git-history-with-pull-merges.png&quot; alt=&quot;Git History with Pull Merges&quot; /&gt;
    &lt;figcaption&gt;Git History with Pull Merges&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If your git history looks like this and this bothers you this article is for you. With a better understanding of git and it’s features you can make your history look like this.&lt;/p&gt;
&lt;figure class=&quot;alignleft&quot;&gt;
    &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2016-10-20_git-history-with-rebase.png&quot; alt=&quot;Git History with Rebase&quot; /&gt;
    &lt;figcaption&gt;Git History with Rebase&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you want to know how read more!&lt;/p&gt;

&lt;h3 id=&quot;understand-merging-with---ff-or---no-ff&quot;&gt;Understand merging with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;In git there are two strategies to merge branches. &lt;em&gt;Fast-Forward&lt;/em&gt; merges and &lt;em&gt;Non-Fast-Forward&lt;/em&gt; merges. Let’s take a look at those two strategies, when you can use which strategy and how the result differs.&lt;/p&gt;

&lt;p&gt;Fast-forward merges are possible if you have a situation like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;          D---E---F topic
         /
A---B---C master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch is ahead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. But the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch does not have any additional commits.&lt;/p&gt;

&lt;p&gt;If you merge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;…&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout master
git merge &lt;span class=&quot;nt&quot;&gt;--ff&lt;/span&gt; topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…you will have the following result:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;A---B---C---D---E---F topic/master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; are identical. If you delete the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch nobody can tell anymore that there was a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch once.&lt;/p&gt;

&lt;p&gt;Let’s take a look what the result looks like if you merge with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt; strategy:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout master
git merge &lt;span class=&quot;nt&quot;&gt;--no-ff&lt;/span&gt; topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Git will create an extra commit for the merge itself:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;          D---E---F topic
         /         \
A---B---C---------- G master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way it is transparent for everyone what commits belonged to the feature branch and what was merged. It also makes it easier to un-merge the branch. Because you only need to revert the merge commit and you are done.&lt;/p&gt;

&lt;p&gt;So my recommendation is &lt;strong&gt;if you merge towards the main line use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt;.&lt;/strong&gt; This means merges from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;develop&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;develop&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; should always be done with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Merges away from the main line &lt;em&gt;could&lt;/em&gt; be done using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff&lt;/code&gt; but continue reading to learn about an even cleaner approach.&lt;/p&gt;

&lt;h3 id=&quot;understand-rebasing&quot;&gt;Understand rebasing&lt;/h3&gt;

&lt;p&gt;With &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rebase&lt;/code&gt; you can &lt;em&gt;move&lt;/em&gt; commits. Strictly speaking you do not move the commits, but you &lt;em&gt;reapply&lt;/em&gt; the commits somewhere else. New commits (with new commit ids) are created, your old commits are removed. This can be used to “catch up” on the changes of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; or any other branch.&lt;/p&gt;

&lt;p&gt;Let’s assume the following situation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      A---B---C topic
     /
D---E---F---G master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you want to update your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch to include the latest changes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. You could merge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; but this would create a useless merge commit (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt; is not possible because both branches have additional commits). A cleaner solution is to rebase your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch onto the current &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout topic
git rebase master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result will be:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;              A&apos;--B&apos;--C&apos; topic
             /
D---E---F---G master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your old commits &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt; have been reapplied on top of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. They have new commit ids and your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch contains now the latest changes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. If your commits don’t apply cleanly on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; you have to resolve your conflicts &lt;em&gt;while rebasing&lt;/em&gt;. Afterwards you can merge your rebased &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch conflict free with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. Your merge will cause no conflicts and you don’t have to fix several errors in a big, complicated, bloated merge commit. This will make the history cleaner and better to read.&lt;/p&gt;

&lt;p&gt;My second recommendation is to &lt;strong&gt;rebase your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branches regularly to stay up to date and solve conflicts as soon as possible.&lt;/strong&gt; The fresher the changes are the more likely it is that you still remember why you introduced a change and this will make resolving rebase conflicts easier.&lt;/p&gt;

&lt;h3 id=&quot;working-with-a-shared-repository&quot;&gt;Working with a shared repository&lt;/h3&gt;

&lt;p&gt;When you work with a shared repository and you start to rebase branches you need to communicate some extra knowledge to your team. Otherwise you clutter you git history with a lot of useless merge commits and it will blow up in your face. That’s basically the reason why people tell you “don’t rebase pushed branches”. Let’s narrow this principle down to the first rule: &lt;strong&gt;“don’t rebase your pushed main branches&lt;/strong&gt;”. It depends on your branching model what your main branches are called but commonly these are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;develop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By default, when pulling from a remote branch and both, your local and remote branch have additional commits, git performs a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--no-ff-&lt;/code&gt; merge. This creates an additional merge commit as you learned earlier. To avoid this you can use rebasing while pulling:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout topic
git pull &lt;span class=&quot;nt&quot;&gt;--rebase&lt;/span&gt; origin topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Remembering to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--rebase&lt;/code&gt; for every pull is annoying so you can set the default strategy for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;topic&lt;/code&gt; branch to rebase:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config branch.topic.rebase &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can even tell git to set this automatically for every new branch you create:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; branch.autosetuprebase &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; always
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After rebasing your branch locally you need to push it back to the remote repository. By default git will reject the push as “diverged”. But you can force the push with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-with-lease&lt;/code&gt;&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push &lt;span class=&quot;nt&quot;&gt;--force-with-lease&lt;/span&gt; origin topic
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a potentially destructive operation because it will overwrite the remote branch and you will loose any changes not included in your local copy of the branch. So always be careful which branch you push.&lt;/p&gt;

&lt;h3 id=&quot;common-pitfalls&quot;&gt;Common Pitfalls&lt;/h3&gt;

&lt;p&gt;Make sure you do &lt;strong&gt;not&lt;/strong&gt; rebase your main branches. If you do this it will almost certainly cause unwanted results. Like inlined merges (like with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ff&lt;/code&gt;). So make sure rebase is disabled for your main branches:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config branch.master.rebase &lt;span class=&quot;nb&quot;&gt;false
&lt;/span&gt;git config branch.develop.rebase &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you rebase on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; (or any other main branch) make sure your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; is up to date. So usually you run the following command sequence:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout master
git pull origin master
git checkout topic
git rebase master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;tldr&quot;&gt;TL;DR&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Merge topic branches with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git merge --no-ff topic&lt;/code&gt; towards the mainline.&lt;/li&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git rebase master topic&lt;/code&gt; to catch up with the latest changes from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Update your topic branch from remote with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull --rebase origin topic&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Push your topic branches with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push --force-with-lease origin topic&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Read the TL;DR version of this post at &lt;a href=&quot;http://stevenharman.net/git-pull-with-automatic-rebase&quot;&gt;http://stevenharman.net/git-pull-with-automatic-rebase&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;

&lt;p&gt;If you want to dive deeper into git here are some recommendations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ctoinsights.wordpress.com/2012/06/29/git-flow-with-rebase/&quot;&gt;https://ctoinsights.wordpress.com/2012/06/29/git-flow-with-rebase/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.atlassian.com/git/articles/git-team-workflows-merge-or-rebase/&quot;&gt;https://www.atlassian.com/git/articles/git-team-workflows-merge-or-rebase/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.atlassian.com/git/tutorials/merging-vs-rebasing/&quot;&gt;https://www.atlassian.com/git/tutorials/merging-vs-rebasing/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;provide-feedback&quot;&gt;Provide Feedback!&lt;/h3&gt;

&lt;p&gt;This workflow is what works well for me and my teams and after some initial explaining is well accepted. What is your workflow? How do you deal with feature branches and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; diverging? Do you care about the transparency of your git history?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Learn more about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force-with-lease&lt;/code&gt;, how it works, and why you should use it at &lt;a href=&quot;https://developer.atlassian.com/blog/2015/04/force-with-lease/&quot;&gt;https://developer.atlassian.com/blog/2015/04/force-with-lease/&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 25 Nov 2016 12:06:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2016/11/25/git-workflow-rebase-and-merge.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2016/11/25/git-workflow-rebase-and-merge.html</guid>
        
        <category>git</category>
        
        <category>gitflow</category>
        
        
      </item>
    
      <item>
        <title>Implement a DateTimeInterface Handler for JMSSerializer</title>
        <description>&lt;p&gt;While working on an API today I realized the &lt;a href=&quot;https://jmsyst.com/libs/serializer&quot;&gt;JMSSerializer&lt;/a&gt; does not support neither &lt;a href=&quot;https://php.net/datetimeinterface&quot;&gt;DateTimeInteface&lt;/a&gt; nor &lt;a href=&quot;https://php.net/datetimeimmutable&quot;&gt;DateTimeImmutable&lt;/a&gt;. I am going to walk you through the necessary steps to serialize &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTimeImmutable&lt;/code&gt; objects using JMSSerializer.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Wow... &lt;a href=&quot;https://twitter.com/hashtag/jmsserializer?src=hash&quot;&gt;#jmsserializer&lt;/a&gt; does not support DateTimeImmutable (&lt;a href=&quot;https://t.co/cbkWc3MdqY&quot;&gt;https://t.co/cbkWc3MdqY&lt;/a&gt;)... Looks like I have to build my own handler... &lt;a href=&quot;https://twitter.com/hashtag/symfony?src=hash&quot;&gt;#symfony&lt;/a&gt;&lt;/p&gt;&amp;mdash; David Fuhr (@DavidFuhr) &lt;a href=&quot;https://twitter.com/DavidFuhr/status/781102555325661185&quot;&gt;September 28, 2016&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;Building an own handler requires not much code. The only problem is that documentation is scattered to many places and sometimes hard to find. I’m working on a full-stack &lt;a href=&quot;https://symfony.com/&quot;&gt;Symfony&lt;/a&gt; 2.8 with &lt;a href=&quot;https://jmsyst.com/bundles/JMSSerializerBundle&quot;&gt;JMSSerializerBundle&lt;/a&gt; installed. So make sure you are on the same page before starting.&lt;/p&gt;

&lt;p&gt;The first step is to create a Handler:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AppBundle\JmsSerializer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateTimeInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JMS\Serializer\VisitorInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateTimeInterfaceHandler&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serializeToJson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;VisitorInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$visitor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;DateTimeInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DATE_ATOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;DateTimeInterfaceHandler.php&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You could also create a Subscriber, but I think the Handler is more straight forward. Read the &lt;a href=&quot;http://jmsyst.com/libs/serializer/master/handlers&quot;&gt;JMSSerializer Documentation on Handlers&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;Next we have to register our Handler as a service:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# services.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;app.jmsserializer.datetimeinterface_handler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;AppBundle\JmsSerializer\DateTimeInterfaceHandler&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;jms_serializer.handler&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DateTimeImmutable&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;direction&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;serialization&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;serializeToJson&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;figcaption&gt;services.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is documented in the &lt;a href=&quot;https://jmsyst.com/bundles/JMSSerializerBundle/master/configuration#handlers&quot;&gt;JMSSerializerBundle Documentation&lt;/a&gt;, &lt;strong&gt;but&lt;/strong&gt; setting my Service to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private&lt;/code&gt; did not work (learn more about &lt;a href=&quot;https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private&quot;&gt;Private Services in Symfony&lt;/a&gt;), and setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTimeInterface&lt;/code&gt; did not work either.&lt;/p&gt;

&lt;p&gt;So if you use want to format &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; as well you have to adjust the config value in the Bundle’s configuration:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jms_serializer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;handlers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;default_format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;c&quot;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ISO8601&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;config.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That’s it. Now your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTimeImmutable&lt;/code&gt; objects should be serialized to string similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2016-09-28T12:54:07+00:00&lt;/code&gt;. If you encounter problems or have further questions find me on Twitter.&lt;/p&gt;
</description>
        <pubDate>Wed, 28 Sep 2016 15:18:00 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2016/09/28/implement-a-datetimeinterface-handler-for-jmsserializer.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2016/09/28/implement-a-datetimeinterface-handler-for-jmsserializer.html</guid>
        
        <category>php</category>
        
        <category>symfony</category>
        
        <category>api</category>
        
        
      </item>
    
      <item>
        <title>Documentation Driven API Development with Swagger 2.0</title>
        <description>&lt;p&gt;Good documentation is the key to utilize an API. But it is difficult to keep documentation and development of you API in sync. Differences between implementation and documentation are often hard to spot. Learn how you can use Swagger to document you API first and profit by testing your implementation against your documentation and vice versa. No duplication needed!&lt;/p&gt;

&lt;figure class=&quot;alignleft&quot;&gt;
    &lt;img src=&quot;https://blog.davidfuhr.de/uploads/swagger-logo.png&quot; alt=&quot;Swagger Logo&quot; /&gt;
    &lt;figcaption&gt;Swagger Logo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I recently started planning a new RESTful API for my company. After some initial discussion on the scope and which API endpoints are needed I set out to write the specification in &lt;a href=&quot;https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md&quot;&gt;Swagger 2.0&lt;/a&gt;. This provided for a good starting point to the further refinement discussion. It also proved very useful to exchange information with our frontend developers in charge of implementing the first API consumer.&lt;/p&gt;

&lt;figure class=&quot;alignright&quot;&gt;
    &lt;a href=&quot;https://blog.davidfuhr.de/uploads/2016-07-12_Swagger-Editor.png&quot;&gt;
        &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2016-07-12_Swagger-Editor_w300.png&quot; alt=&quot;Swagger Editor&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;Swagger Editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At first we started using &lt;a href=&quot;http://editor.swagger.io/&quot;&gt;Swagger Editor&lt;/a&gt; and &lt;a href=&quot;http://yaml.org/&quot;&gt;YAML&lt;/a&gt; as format (because it is the lingua franca of Swagger Editor). Swagger Editor is a great tool to get started using Swagger. It runs directly in your browser, validates your YAML input and renders a beautiful interactive web interface. We were sending the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swagger.yaml&lt;/code&gt; file around and I was maintaining the authoritative version of the file. This worked well for the start but it was obvious to us that we needed a tighter coupling to the application and track the file in sync with the implementation.&lt;/p&gt;

&lt;p&gt;I had worked with &lt;a href=&quot;https://github.com/nelmio/NelmioApiDocBundle&quot;&gt;NelmioApiDocBundle&lt;/a&gt; before to integrate Swagger with a Symfony application and it is a wonderful tool to document your existing API code. But as NelmioApiDocBundle generates the Swagger file from the annotated source code the source must exist prior to the documentation. This did not match our desired workflow. What’s more, the future of NelmioApiDocBundle seems to be a bit uncertain as the William Durand, the current maintainer, thinks of deprecating the bundle&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I am thinking about slowly deprecating &lt;a href=&quot;https://twitter.com/hashtag/NelmioApiDocBundle?src=hash&quot;&gt;#NelmioApiDocBundle&lt;/a&gt; (feature-freeze first, then EOL). Thoughts?&lt;/p&gt;&amp;mdash; William Durand (@couac) &lt;a href=&quot;https://twitter.com/couac/status/742266704575758336&quot;&gt;June 13, 2016&lt;/a&gt;&lt;/blockquote&gt;

&lt;p&gt;As I only needed a view component for my Swagger file and had no need for the annotation stuff &lt;a href=&quot;https://github.com/activelamp/swagger-ui-bundle&quot;&gt;SwaggerUIBundle&lt;/a&gt; was good enough for the job. That made it necessary to convert my Swagger file from YAML to &lt;abbr title=&quot;JavaScript Object Notation&quot;&gt;JSON&lt;/abbr&gt;, but that’s no big deal as the Swagger Editor has an export option for JSON. For the Bundle configuration take a look at the &lt;a href=&quot;https://github.com/activelamp/swagger-ui-bundle/blob/master/Resources/doc/installation-and-usage.md&quot;&gt;installation instructions for static JSON files&lt;/a&gt;. This is what my routing configuration looked like:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/config/routing_dev.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;al_swagger_ui&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@ALSwaggerUIBundle/Resources/config/routing.yml&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;/api-docs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;app/config/routing_dev.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As I started implementing the first endpoints I also created the first Test Cases in parallel with development. And because I am lazy, I did not want to repeat myself and write the same definitions I already had in my Swagger file for my test assertions again. So I was looking for a way to reuse my Swagger specification in my test’s assertion.&lt;/p&gt;

&lt;figure class=&quot;alignright&quot;&gt;
    &lt;a href=&quot;https://blog.davidfuhr.de/uploads/2016-07-12_SwaggerUI.png&quot;&gt;
        &lt;img src=&quot;https://blog.davidfuhr.de/uploads/2016-07-12_SwaggerUI_w300.png&quot; alt=&quot;Swagger Editor&quot; /&gt;&lt;/a&gt;
    &lt;figcaption&gt;Swagger UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The model definitions of Swagger 2.0 are valid &lt;a href=&quot;http://json-schema.org/examples.html&quot;&gt;JSON Schema&lt;/a&gt; which aims to be similar as &lt;abbr title=&quot;XML Schema Definition&quot;&gt;XSD&lt;/abbr&gt; or &lt;abbr title=&quot;Document Type Definition&quot;&gt;DTD&lt;/abbr&gt; for XML. And fortunately there is already a library for &lt;a href=&quot;https://github.com/estahn/phpunit-json-assertions&quot;&gt;PHPUnit JSON Schema Assertions&lt;/a&gt;. The only problem was I had a single huge Swagger file and I found no way to validate my API response only against a part of the big Swagger definition. JSON Schema allows splitting up the definition into multiple files and referencing these files using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ref&lt;/code&gt;. For more information about Referencing take a look at the &lt;a href=&quot;https://github.com/OAI/OpenAPI-Specification/blob/master/guidelines/REUSE.md#guidelines-for-referencing&quot;&gt;OpenAPI Guidelines for Referencing&lt;/a&gt;, &lt;a href=&quot;http://azimi.me/2015/07/16/split-swagger-into-smaller-files.html&quot;&gt;How to split a Swagger spec into smaller files&lt;/a&gt; and &lt;a href=&quot;https://spacetelescope.github.io/understanding-json-schema/structuring.html&quot;&gt;Structuring a complex schema&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After splitting up the file SwaggerUIBundle did not work anymore because it has problems handling and resolving the JSON references correctly. But that’s not a problem a of SwaggerUI, it’s only a problem of the bundle. As workaround I moved my Swagger files to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/AppBundle/Resources/public/swagger&lt;/code&gt; so that Symfony takes care of populating my files in the web root and I adjusted the SwaggerUIBundle configuration to just take that static entry point.&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/config/config_dev.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;al_swagger_ui&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;resource_list&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/bundles/app/swagger/swagger.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;app/config/routing_dev.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now that the UI was working again I could set up my test cases and validate the controller’s response against my Swagger definition.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AppBundle\Tests\Controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnricoStahn\JsonAssert\Extension\Symfony&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonAssert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Bundle\FrameworkBundle\Test\WebTestCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CartsControllerTest&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebTestCase&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonAssert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getSwaggerDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/../../Resources/public/swagger&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testPostCartsResponseSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/carts&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assertJsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assertJsonMatchesSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getSwaggerDir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/definitions/CartsResponse.json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One somewhat questionable thing with JSON Schema is, that &lt;a href=&quot;https://github.com/json-schema/json-schema/wiki/Additionalproperties&quot;&gt;additional properties on object are allowed by default&lt;/a&gt;. You have to explicitly set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;additionalProperties&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;. Your schema should something like that&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cart_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;item_count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;additionalProperties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If your JSON Schema uses several nesting levels you might need to adjust the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$maxDepth&lt;/code&gt; of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RefResover&lt;/code&gt; (the component that resolves your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ref&lt;/code&gt;).&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;JsonSchema\RefResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$maxDepth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This got me what I want. Whenever there is a change either in the documentation or the implementation it will break the tests and the developer has to make sure both align. This ensures that the documentation is updated on code change and is maintained and also provides easy tests for new endpoints as a bonus!&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;If you are using NelmioApiDocBundle and looking for alternatives, read this &lt;a href=&quot;https://blog.liip.ch/archive/2016/05/11/convert-nelmioapidocbundle-to-swagger-php.html&quot;&gt;insightful article by David Buchman&lt;/a&gt; who coverted his NemlioApiDocBundle annotations to &lt;a href=&quot;https://github.com/zircote/swagger-php&quot;&gt;swagger-php&lt;/a&gt; annotations. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 12 Jul 2016 21:05:31 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2016/07/12/documentation-driven-api-development-with-swagger.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2016/07/12/documentation-driven-api-development-with-swagger.html</guid>
        
        <category>api</category>
        
        <category>ddd</category>
        
        <category>json</category>
        
        <category>php</category>
        
        <category>rest</category>
        
        <category>symfony</category>
        
        <category>www</category>
        
        
      </item>
    
      <item>
        <title>Track Memory Usage and Runtime of Symfony Commands</title>
        <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href=&quot;https://www.flagbit.de/blog/track-memory-usage-and-runtime-of-symfony-commands/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;flagbit.de&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you write lots of Console Commands in your Symfony application you sure want
to know more about their memory and time consumption. To collect the necessary
data you can make use of the &lt;a href=&quot;https://symfony.com/doc/current/components/stopwatch.html&quot;&gt;Stopwatch
Component&lt;/a&gt;.&lt;/p&gt;

&lt;!--more--&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Stopwatch\Stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;my event&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// do something important&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;my event&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getMemory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First we need to define a service for our Stopwatch in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;services.yml&lt;/code&gt; to
have it available where we want it:&lt;/p&gt;

&lt;figure&gt;
  &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;flagbit_core.stopwatch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Symfony\Component\Stopwatch\Stopwatch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;figcaption&gt;services.yml&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You could implement the Stopwatch in each and every command by hand but this is
tiresome and error-prone. So we make use of the &lt;a href=&quot;https://symfony.com/doc/current/components/console/events.html&quot;&gt;Console
Events&lt;/a&gt; and
attach to them:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;flagbit_core.console_stopwatch.command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Flagbit\CoreBundle\EventListener\ConsoleStopwatchListener&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@flagbit_core.stopwatch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;kernel.event_listener&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;console.command&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;flagbit_core.console_stopwatch.terminate&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Flagbit\CoreBundle\EventListener\ConsoleStopwatchListener&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@flagbit_core.stopwatch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;kernel.event_listener&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;console.terminate&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ok, let’s walk through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConsoleStopwatchListener&lt;/code&gt;. First we start with the
Constructor to inject the Stopwatch into our Service:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Quantum\CoreBundle\EventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Console\Event\ConsoleCommandEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Console\Event\ConsoleTerminateEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Console\Output\OutputInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Stopwatch\Stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConsoleStopwatchListener&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @var Stopwatch
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cd&quot;&gt;/**
     * @param Stopwatch $stopwatch
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Stopwatch&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stopwatch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.command&lt;/code&gt; Event is dispatched just before command execution. So it
is the perfect place to start our Stopwatch:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;cd&quot;&gt;/**
     * @param ConsoleCommandEvent $event
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;onConsoleCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConsoleCommandEvent&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stopwatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second Event we attached to is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.terminate&lt;/code&gt; which is triggered after
the command has been executed. So we attach our stats to the Output there:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;cd&quot;&gt;/**
     * @param ConsoleTerminateEvent $event
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;onConsoleTerminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ConsoleTerminateEvent&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stopwatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getOutput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OutputInterface&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VERBOSITY_VERBOSE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getVerbosity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;writeln&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;&amp;lt;comment&amp;gt;&apos;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;formatDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; / &apos;&lt;/span&gt;
                &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;formatMemory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stopwatchEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getMemory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&amp;lt;/comment&amp;gt;&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cd&quot;&gt;/**
     * @param int $bytes Memory in bytes
     * @return string
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatMemory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; MB&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cd&quot;&gt;/**
     * @param int $microseconds Time in microseconds
     * @return string
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$microseconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$microseconds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; s&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You might also have noticed the Verbosity check which causes our stats only to
be printed if the command is run with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v&lt;/code&gt;. Symfony &amp;gt;= 2.4 allows to &lt;a href=&quot;https://symfony.com/doc/current/components/console/introduction.html#verbosity-levels&quot;&gt;do this a
little bit cleaner&lt;/a&gt;
with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$output-&amp;gt;isVerbose()&lt;/code&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 14 Feb 2014 16:28:48 +0000</pubDate>
        <link>https://blog.davidfuhr.de/2014/02/14/track-memory-usage-and-runtime-of-symfony-commands.html</link>
        <guid isPermaLink="true">https://blog.davidfuhr.de/2014/02/14/track-memory-usage-and-runtime-of-symfony-commands.html</guid>
        
        <category>php</category>
        
        <category>symfony</category>
        
        
      </item>
    
  </channel>
</rss>
