<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mario Mainz</title>
    <description>The latest articles on Forem by Mario Mainz (@mmainz).</description>
    <link>https://forem.com/mmainz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F624660%2F570ce5d0-5783-470a-8545-1f0a61558551.jpeg</url>
      <title>Forem: Mario Mainz</title>
      <link>https://forem.com/mmainz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mmainz"/>
    <language>en</language>
    <item>
      <title>Unlearning SOLID: My Path to More Nuanced Code Evaluation</title>
      <dc:creator>Mario Mainz</dc:creator>
      <pubDate>Mon, 05 Aug 2024 18:51:39 +0000</pubDate>
      <link>https://forem.com/mmainz/unlearning-solid-my-path-to-more-nuanced-code-evaluation-3e4m</link>
      <guid>https://forem.com/mmainz/unlearning-solid-my-path-to-more-nuanced-code-evaluation-3e4m</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you are a software engineer, you have probably heard about the &lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="noopener noreferrer"&gt;SOLID principles&lt;/a&gt;. They are a collection of principles meant to guide developers towards high-quality, maintainable code. And they are probably the most well-known principles when it comes to code quality.&lt;/p&gt;

&lt;p&gt;Like many of you, probably, I’ve used those principles in the past to judge both my own and other people’s code. However, I rely less and less on them and have come to realize that they’re not as good as I once thought they were. I want to share my journey of coming to that realization and what I do instead nowadays in this blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I used to use it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Frules_hu5459c0360c2b0cb7a147d2df0eb350ca_870085_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Frules_hu5459c0360c2b0cb7a147d2df0eb350ca_870085_660x0_resize_q75_box.jpg" alt="Similar cubes with RULES inscription on windowsill in building"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although I was a believer in the SOLID principles for a long time, I never followed them religiously. The one I always found the most useful is the single responsibility principle, so I would very deliberately think about what the responsibility of the module I’m working on was. Then I would review the finished module for any &lt;strong&gt;Liskov&lt;/strong&gt; or &lt;strong&gt;Demeter&lt;/strong&gt; violations, check if there are any unnecessary dependencies, whether the interfaces actually make sense, and refactor accordingly. This usually yielded code that I was happy with.&lt;/p&gt;

&lt;p&gt;However, if I ever got into a discussion about my or other people’s code, I always treated arguments based on the SOLID principles as very sound arguments. They are universally accepted principles, after all. In hindsight, that might have been a bit naive. But since I had the tendency to be pragmatic, I didn’t encounter any big issues with this approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realizing the shortcomings of the principles
&lt;/h2&gt;

&lt;p&gt;Then I switched jobs and stumbled upon some code that &lt;em&gt;did&lt;/em&gt; apply the SOLID principles, but somehow it looked really weird and was also really difficult to work with. This is when I slowly started to question how useful those principles actually are. Let me give you two examples of what kind of code made me start down this path of questioning SOLID.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface Segregation
&lt;/h3&gt;

&lt;p&gt;I was working on a large TypeScript codebase at the time. One symptom of the code was that there were many duplicated type definitions. Let me give you some oversimplified examples. You would have code like this in one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// getFullName.ts&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And then code like this in another file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// canUpdateAccountSettings.ts&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isPoweruser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canUpdateAccountSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAdmin&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPoweruser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;What’s interesting about this is that both of these functions will receive &lt;strong&gt;the same object&lt;/strong&gt;. So naturally, I asked why we defined separate types for this and not just one type that describes the user entirely. The answer: &lt;strong&gt;interface segregation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, what does the interface segregation principle say again?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Clients should not be forced to depend upon interfaces that they do not use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first glance, this seems correct. &lt;code&gt;getFullName&lt;/code&gt; doesn’t need the &lt;code&gt;isAdmin&lt;/code&gt; field, so by declaring a &lt;code&gt;User&lt;/code&gt; type that doesn’t have this, we now don’t depend on it. Since TypeScript has a structural type system, I can still pass the full &lt;code&gt;User&lt;/code&gt; object instead, as long as it has at least the fields defined in the type specific to &lt;code&gt;getFullName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But hold on a second. Is there really any benefit to this? The point of doing this is to prevent us from having to change &lt;code&gt;getFullName&lt;/code&gt; if a field in &lt;code&gt;User&lt;/code&gt; changes that is not used by &lt;code&gt;getFullName&lt;/code&gt;. But do we even need the type for that? Imagine just doing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// user.ts&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isPoweruser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// getFullName.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Okay, the function looks the same. We’re just importing a &lt;code&gt;User&lt;/code&gt; type now. What happens if I remove or change the &lt;code&gt;isAdmin&lt;/code&gt; field? Exactly! Absolutely nothing! The code just continues to work, since it’s not using that field anyway. This is the power of &lt;strong&gt;duck-typing&lt;/strong&gt; , as used by JavaScript. Since TypeScript wants to be a strict superset of JavaScript, it also works with duck-typing.&lt;/p&gt;

&lt;p&gt;So in the first example, we duplicated the type without any benefit at all in an attempt to write better code. Doesn’t sound great, huh?&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Inversion
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fcomplexity_hu5459c0360c2b0cb7a147d2df0eb350ca_3472905_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fcomplexity_hu5459c0360c2b0cb7a147d2df0eb350ca_3472905_660x0_resize_q75_box.jpg" alt="A big number of wires that is impossible to follow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have another example, this time about the &lt;strong&gt;dependency inversion principle&lt;/strong&gt;. Just as before, it happened when switching jobs and laying my eyes on a new codebase that I hadn’t seen before.&lt;/p&gt;

&lt;p&gt;Most of my life, I worked in dynamic languages. This means I could mock other classes and modules at runtime for my tests. So I never had a strong need to employ dependency injection just to make something testable. As a result, I only used dependency injection on dependencies that had &lt;em&gt;multiple implementations&lt;/em&gt;, that were &lt;em&gt;an abstraction of something&lt;/em&gt;. Like a logger that can write to either STDOUT or the file system, for example. Just inject a &lt;code&gt;LogWriter&lt;/code&gt; with a &lt;code&gt;write&lt;/code&gt; function, and your logger can be blissfully unaware of where the logs are going.&lt;/p&gt;

&lt;p&gt;However, this new codebase had &lt;em&gt;a lot&lt;/em&gt; of dependency injection going on. It was a TypeScript codebase using Jest, so they &lt;em&gt;did&lt;/em&gt; have the option of just mocking modules at runtime. So I was a bit puzzled about why this was so prevalent in the code. Worst of all, there were several levels of nesting to this dependency injection. You would regularly find code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependencyA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependencyB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependencyC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildServiceA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependencyA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependencyB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildServiceB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependencyB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependencyC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serviceC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildServiceC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependencyC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// many more of these...&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do something with the dependencies&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;doSomething&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Of course, &lt;code&gt;buildServiceA&lt;/code&gt; would in turn also be made up of several &lt;code&gt;build*&lt;/code&gt; functions. This meant when you were working on a lower one of these layers, finding the implementation of one of those dependencies was pure hell. You couldn’t just &lt;em&gt;Go To Definition&lt;/em&gt;, that would just take you to the function parameters. From there, you’d do &lt;em&gt;Find All References&lt;/em&gt; and find where the parameter is injected. Then &lt;em&gt;Go To Definition&lt;/em&gt; on that injected variable, only to arrive at the next function parameter. Sometimes you could do &lt;em&gt;Go To Implementation&lt;/em&gt; to circumvent this, sometimes not. And sometimes that wouldn’t even help you because you wanted to see how that specific dependency is initialized, not what the implementation looks like. Believe me when I say I lost my mind a few times searching for a dependency’s point of origin.&lt;/p&gt;

&lt;p&gt;Naturally, I asked many questions about why the code is structured this way. I wanted to find out what drove them to do this. And also whether they actually thought this was a good idea. And indeed, most of the responses I got were saying “this is good design”, “it’s following SOLID”, “it makes testing easier”. In my eyes, none of these arguments survived being challenged, though. After all, what are the downsides of transforming the example above into just this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serviceA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./serviceA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serviceB&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./serviceB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serviceC&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./serviceC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// many more of these...&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do something with the dependencies&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I personally don’t see any. It certainly is less code. It is also certainly easier to read due to fewer layers of indirection. I can directly go to the implementation of the dependencies since I know the path from the &lt;code&gt;import&lt;/code&gt; statement. In tests, I can employ the module mocking mechanism of my test framework to supply test stubs. If you need to refactor this, you could even switch out the whole implementation of one of those modules. Just change the path to point to a file that exports the same symbols with the same signatures. So it’s still perfectly decoupled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting to reflect on SOLID
&lt;/h3&gt;

&lt;p&gt;Those experiences made me start to reflect on my use of the SOLID principles. Have I been too dogmatic in the past? I don’t think so. Because, as I already described earlier, I never applied the principles religiously. But I certainly felt like I hadn’t questioned the validity of the principles enough.&lt;/p&gt;

&lt;p&gt;Now, you could probably argue that both of these instances are a misunderstanding of the principles. However, you can’t argue with the fact that the principles are what drove someone to write code like this. And there weren’t just one or two examples of code like this, there were numerous instances. Which means there was at least a sizeable group inside the organization that shared this misunderstanding. And how helpful is a principle like that if it can be misunderstood so easily by a large number of people? It makes me question whether vague principles like these are even worth it to write down.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what to do instead?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Ffork_hue7a97ef2e4bd0c55353260d7c7491813_5040925_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Ffork_hue7a97ef2e4bd0c55353260d7c7491813_5040925_660x0_resize_q75_box.jpg" alt="A road leading into a snowy forest that forks in two directions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But if I can’t rely on SOLID anymore, what will I do instead? Sure, these weren’t the only things I was looking out for in code. There is much more to readable and maintainable code than just the SOLID principles. But they were a pretty big part of my foundation of judging good code from bad code. So I was craving more things in my toolbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  CUPID
&lt;/h3&gt;

&lt;p&gt;The first thing I stumbled upon was a blog post by Dan North about &lt;a href="https://dannorth.net/cupid-the-back-story/" rel="noopener noreferrer"&gt;why every single element of SOLID is wrong&lt;/a&gt;. He has some very solid (haha) criticism of the principles that also reflects the experience I outlined above. He also has a blog post proposing &lt;a href="https://dannorth.net/cupid-for-joyful-coding/" rel="noopener noreferrer"&gt;CUPID&lt;/a&gt; as an alternative. While I do agree with his ideas, I was skeptical about replacing my one set of principles by another. So while I keep these in mind and try to think about these properties when I’m judging code, I wanted more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low coupling, high cohesion
&lt;/h3&gt;

&lt;p&gt;If I don’t want just another set of principles, then I need to go back to the properties of the code itself. What are universal, objective qualities of good code? The biggest one is probably that good code has &lt;strong&gt;low coupling and high cohesion&lt;/strong&gt;. We’ve all heard about this before. This still depends on context, though. Decoupling something can make code harder to read, which will only pay off if I &lt;em&gt;actually&lt;/em&gt; benefit from the decoupling. So I still need to be careful to what degree I apply this.&lt;/p&gt;

&lt;p&gt;What else? As I reflected on the qualities that make code truly effective, I realized that there’s another crucial aspect that often gets overshadowed by more technical considerations: readability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Readability
&lt;/h3&gt;

&lt;p&gt;One important aspect about code is that it &lt;strong&gt;will always be read way more often than it is written&lt;/strong&gt;. So we should optimize on code being easy to read and understand, not necessarily easy to write. At least when those two are at odds. However, it’s also highly subjective what is considered readable. Someone used to JavaScript will find Lisp to be a mess. Although, if you get used to the syntax, many people start to appreciate the simplicity of the Lisp syntax (myself included). I don’t think this is a big issue. You should be aware of what environment your teammates are working in. And since you all work in the same or a similar environment, the personal preferences should converge towards each other over time.&lt;/p&gt;

&lt;p&gt;Readability goes beyond just structure. It encompasses how we name our variables and functions, how we format our code, and even how we organize our logic. And just like with coupling and cohesion, the pursuit of readability can sometimes conflict with other goals, requiring us to make thoughtful trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Digging even deeper
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fdeep_hu5459c0360c2b0cb7a147d2df0eb350ca_2328912_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fdeep_hu5459c0360c2b0cb7a147d2df0eb350ca_2328912_660x0_resize_q75_box.jpg" alt="the edge of a swimming pool with a sign that says 'deep water'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started to look for resources that did not come from the usual authors like Uncle Bob. While doing that, I stumbled upon &lt;a href="https://www.amazon.com/dp/173210221X" rel="noopener noreferrer"&gt;A Philosophy of Software Design&lt;/a&gt; by John Ousterhout. This is a really nice, concise book with some excellent insights that I didn’t hear before. It also directly contradicts some of the usual advice you hear from books like &lt;a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882?dib=eyJ2IjoiMSJ9.eUeDq4BPqNYI52SOU-OcJxFxSsYuG75qnwKh2Q1vEf2RPgQp_SsISCnxR9XvBj4k0bwFAdrWX4QvnuOBAKOK7vehMb3OjkBOzCkHPoPEezo8r_wHjeF6PDPmailb8r1bKs_t5-YY6fMgQ98ZDehr1Y9YJK3RtB2Upi95yxSItHf1Ul3KqIRrwISnc592lYJLvP5FCpzqf_l3me1IYEl9tDtShX1ualWgBPsE8HG7EYY.NoifPXqhr2AkzGGJWw91dW30b3DwgaKDdK4ChkRxiL0&amp;amp;dib_tag=se&amp;amp;keywords=clean+code&amp;amp;qid=1722356251&amp;amp;s=books&amp;amp;sr=1-1" rel="noopener noreferrer"&gt;Clean Code&lt;/a&gt;, so it was perfect to achieve a more balanced perspective on the matter of what is good code.&lt;/p&gt;

&lt;p&gt;So, what does the book say? It says a lot of things, but let me give you a recap of the points that I found the most interesting.&lt;/p&gt;

&lt;h4&gt;
  
  
  Complexity
&lt;/h4&gt;

&lt;p&gt;The author defines complexity as anything related to the structure of a software system that makes it &lt;strong&gt;hard to understand or modify&lt;/strong&gt;. When trying to create a software system that will be developed for a long time by a diverse team of engineers, complexity is our biggest enemy. One symptom of a complex system is that it requires a &lt;strong&gt;higher cognitive load&lt;/strong&gt; to understand. Another symptom the author calls &lt;strong&gt;change amplification&lt;/strong&gt; , which means that to change something about the system, you need to make changes in many disconnected places of the code. It is our job as engineers to write code that not only we can understand, but that also all other engineers can understand as easily as possible. This is tricky because complexity is less apparent to the person writing the code, since they have all the context and history in their head at that time. That is why code reviews are so valuable.&lt;/p&gt;

&lt;p&gt;The key property of complexity though is that it is &lt;strong&gt;incremental&lt;/strong&gt;. There isn’t a single source of overwhelming complexity that makes it hard to work with the code. There are lots of small chunks of complexity that add up so that together they make it hard to work with the code. This is essential to understand. How often do you think “doing it this way is not ideal, but it’s a minor flaw, it won’t affect the codebase overall” when reviewing code? Accepting even small amounts of unnecessary complexity means going one more increment towards a hard-to-understand, unmaintainable codebase.&lt;/p&gt;

&lt;p&gt;So what the author says we need to do is to adopt a &lt;strong&gt;zero-tolerance stance&lt;/strong&gt; towards avoidable complexity. While I’m sympathetic towards that, I just established that I’m trying to be more pragmatic in the future and not treat anything as a dogmatic truth. So I’m not sure zero-tolerance is the best idea. But I get what he is trying to say. Even small flaws that look innocent on their own can add up to become an unmaintainable mess. So it pays off to try really hard to eliminate them. But I do think there are rare situations where you can make a compromise without any risks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deep vs shallow modules
&lt;/h4&gt;

&lt;p&gt;One way to manage complexity is to &lt;strong&gt;encapsulate&lt;/strong&gt; it in modules. The user of the module only has to understand the &lt;strong&gt;module interface&lt;/strong&gt; , not how its behavior is implemented. For any given module, you can compare the size of the module’s public interface and the size of the implementation code. If there is a big interface and just small implementation bodies, this is a shallow module. If there’s a small interface with big implementation bodies behind that interface, this is a deep module.&lt;/p&gt;

&lt;p&gt;The author states that &lt;strong&gt;deep modules are better&lt;/strong&gt; than shallow modules. They hide more implementation details and therefore expose the user of the module to less complexity. This is something I personally never thought about in such explicit terms before, but I think it is right.&lt;/p&gt;

&lt;p&gt;And this doesn’t just go for modules, the author argues that it is also true for &lt;strong&gt;every single function&lt;/strong&gt; , be it public or private. And if it’s also true for functions, then this is somewhat contrary to the usual Clean Code advice to craft small functions of just a few lines of code. This was a bit hard for me to accept. I personally like small functions a lot, and I find it increasingly difficult to read functions as soon as they’re too big to fit on a single screen. But I still think the statement is somewhat true.&lt;/p&gt;

&lt;p&gt;I now try to incorporate this perspective into my thinking. I still try to keep my functions small enough to fit on a single screen, but maybe it’s not necessary to extract as many two-line-functions as I usually did. At least when I do, I ask myself whether it is worth it in this case. It might still be reasonable when you’re encapsulating a certain behavior that you don’t want to duplicate. But I also keep in mind that I should try to make my functions deeper if I can.&lt;/p&gt;

&lt;h4&gt;
  
  
  Comments
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fcomments_hue8316ee9515ea3cf46a10bb4c72a36a0_5132387_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fcomments_hue8316ee9515ea3cf46a10bb4c72a36a0_5132387_660x0_resize_q75_box.jpg" alt="a wall with a lot of sticky notes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another point where the author is contrary to Clean Code is comments. While Clean Code almost entirely abolishes comments, John Ousterhout argues &lt;strong&gt;comments drastically improve the system’s design&lt;/strong&gt;. To be clear, he still thinks that if you can make the code obvious itself, then you should do that instead of writing a comment. But there is a lot of information in the engineer’s head at design time that is not possible to encode in source code. Like the meaning of certain values, the rationale for a certain design decision, how a module works at a high level, or conditions under which a method can be called.&lt;/p&gt;

&lt;p&gt;A common argument is that if you want to understand how a module works, you can just read the code. However, a really good counter from the author is that this defeats the purpose of a module, which is to hide complexity from the user. If I have to understand the entire module to be able to use it, that &lt;strong&gt;greatly diminishes the value of having an abstraction&lt;/strong&gt; in the first place. So to be precise, &lt;strong&gt;comments are essential&lt;/strong&gt; for abstractions because only the function signature will rarely be enough to adequately describe an abstraction well enough so that I don’t have to read the implementation code.&lt;/p&gt;

&lt;p&gt;He also addresses common criticism against comments in his book. I won’t go into all the details here. But the chapter on comments definitely made me rethink my stance on comments. I’ve now started looking for good opportunities to use comments in a meaningful way when I write code. I think I’m still learning, but reading this has made me less dogmatic about the whole thing, which is good.&lt;/p&gt;

&lt;h3&gt;
  
  
  The essence of good code
&lt;/h3&gt;

&lt;p&gt;These are great things to carry in your toolbox. But they are still context-dependent. It’s still not what defines the essence of good code. So what if we approach this from a business perspective? Most of us write code to make a living. So what properties make code the most economical? The most economical code has the &lt;strong&gt;highest value&lt;/strong&gt; for the &lt;strong&gt;lowest cost&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What gives our code value? If it solves a problem so that we can sell the product to someone. Assuming that we correctly understood the customer problem (which can be a big if), we could say the code needs to do what it’s supposed to do. It needs to do its job. Ideally, without any bugs or undefined behaviors. So the value of our code is if it &lt;strong&gt;does what it’s supposed to do&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What defines the cost of our code? This will be mostly the &lt;strong&gt;time we spend on it&lt;/strong&gt;. It could also include the costs for the infrastructure that runs the code, but let’s be real, for most of us, that will be a fraction of the cost of the developer’s salaries. So I think we can focus on that.&lt;/p&gt;

&lt;p&gt;Does that mean we should try to spend the minimum amount of time on creating the code? Well, kinda, but not necessarily in the way you think. We know that if we have to go back to some code to fix a bug, that will cost orders of magnitude more time than when write correct code right away. It also pays off to take care to design our code in a way that makes it easy to change. We all know it’s normal for requirements to change. And if adapting our code takes a lot longer because we didn’t design it with care, that will almost always lead to us spending more total time on the code. So what we really want to do is minimize the time spent on the code &lt;strong&gt;over the code’s entire lifecycle&lt;/strong&gt;. This is different from just hacking together something that barely works as quickly as possible. So, being reasonably easy to change is our second property, which relates to the cost of the code.&lt;/p&gt;

&lt;p&gt;Of course, you can also overengineer your code, so we have to find the right balance and identify the things that are most likely to change. This is part of the design process.&lt;/p&gt;

&lt;p&gt;So that leaves us with two essential properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code &lt;strong&gt;does what it is supposed to do&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The code &lt;strong&gt;is easy to change&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are good fundamentals to focus on when judging code. And I think these include enough context so that we can get away from dogmatically applying principles. They also allow us to still use those principles but in a more nuanced way, so the principles themselves still carry value.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fopen-road_hu33965a5ed27d882600f4015e4e29a35a_1850919_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmmainz.dev%2Fposts%2Funlearning-solid-my-path-to-more-nuanced-code-evaluation%2Fopen-road_hu33965a5ed27d882600f4015e4e29a35a_1850919_660x0_resize_q75_box.jpg" alt="a wide open road going into the horizon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So there you have it - my journey from being an acolyte of SOLID to becoming a heretic. It was eye-opening to me, and I hope this article helps you to widen your perspective as well.&lt;/p&gt;

&lt;p&gt;Key takeaways from this journey include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context matters&lt;/strong&gt; : There’s no one-size-fits-all approach to writing good code. What works in one situation might be counterproductive in another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beware of dogma&lt;/strong&gt; : Blindly following principles, even well-established ones like SOLID, can lead to unnecessarily complex or hard-to-maintain code. It’s crucial to consider the actual impact on readability, maintainability, and efficiency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embrace alternative perspectives&lt;/strong&gt; : There are a ton of opinions out there on good software design. They can provide fresh insights and challenge our preconceptions about what constitutes good code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on fundamental qualities&lt;/strong&gt; : At its core, good code &lt;strong&gt;does what it’s supposed to do&lt;/strong&gt; and is &lt;strong&gt;easy to change&lt;/strong&gt;. These fundamental properties should guide our evaluation more than any specific set of principles.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Moving forward, I would encourage you to cultivate a more flexible and nuanced approach to code evaluation. Instead of relying solely on predefined principles, strive to understand the underlying reasons for various coding practices. Regularly question and reevaluate your beliefs about what constitutes good code.&lt;/p&gt;

&lt;p&gt;Remember, we’re not throwing the baby out with the bathwater here. Those principles can still be useful, but think of them more like guidelines than strict rules. Use them when they make sense, ditch them when they don’t.&lt;/p&gt;

&lt;p&gt;By keeping these questions in mind and remaining open to new ideas and perspectives, we can all become better developers and write code that stands the test of time.&lt;/p&gt;

</description>
      <category>solid</category>
      <category>cleancode</category>
      <category>architecture</category>
      <category>oop</category>
    </item>
    <item>
      <title>Microservice Antipatterns: The Shared Client Library</title>
      <dc:creator>Mario Mainz</dc:creator>
      <pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/mmainz/microservice-antipatterns-the-shared-client-library-37j1</link>
      <guid>https://forem.com/mmainz/microservice-antipatterns-the-shared-client-library-37j1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you’re working in an architecture with multiple services talking to each other (call it microservices, SOA, or whatever), one pattern usually emerges quite quickly and also reemerges occasionally. I’m talking about the idea of providing a set of libraries that include clients for talking to the respective services. There are variations of this pattern, like sharing types for domain entities between services by putting them in a library. Just like Sam Newman also writes in &lt;a href="https://samnewman.io/books/building_microservices_2nd_edition/" rel="noopener noreferrer"&gt;Building Microservices&lt;/a&gt;, I also agree that this pattern is harmful. Let’s explore my reasoning by first diving into why this seems like a good idea at first glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promised benefits
&lt;/h2&gt;

&lt;p&gt;The usual arguments in favor of this pattern are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We only have to implement the client once, we are following DRY.&lt;/li&gt;
&lt;li&gt;Engineers find it easier to call the service because they don’t have to write their own client.&lt;/li&gt;
&lt;li&gt;Updating clients is easy this way because we just have to bump the library version.&lt;/li&gt;
&lt;li&gt;It ensures we are calling the other service correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I find some of these are false assumptions. And some of these are true, but come with bigger drawbacks than these advantages. Let me go into detail about each of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  It is DRY
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--THGathAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/dry_hu5459c0360c2b0cb7a147d2df0eb350ca_468202_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--THGathAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/dry_hu5459c0360c2b0cb7a147d2df0eb350ca_468202_660x0_resize_q75_box.jpg" alt="brown desert with dried out brown tree" width="660" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Applying too much DRY&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the uninitiated, DRY stands for Don’t Repeat Yourself. It’s a principle that says you shouldn’t have code implementing the same principle twice, but reuse one implemenation of that code.&lt;/p&gt;

&lt;p&gt;It’s true that this way you only have to maintain one client implementation. However, that implementation must also satisfy the requirements of &lt;em&gt;all&lt;/em&gt; client services. When your architecture evolves and your clients develop different needs, this means you’ll have to write more complex code that supports all those needs at the same time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ease of use
&lt;/h3&gt;

&lt;p&gt;I think this one is the truest statement of the bunch. It’ll always be easier to install a dependency and call a function than it is to call an HTTP endpoint. But on well-structured and documented APIs, I don’t find the difference to be all that big. I’ll present an alternative later in the article that is &lt;em&gt;almost&lt;/em&gt; as easy as using a dependency.&lt;/p&gt;
&lt;h3&gt;
  
  
  Easy updating
&lt;/h3&gt;

&lt;p&gt;If anything changes on the server side, you just have to run a single command on the client to update the package responsible for calling the server. This is easy. However, there are two reasons why I think this is not a very strong argument.&lt;/p&gt;

&lt;p&gt;Firstly, you still can’t do any breaking changes on the server side, because you cannot update the client and the server at the same time. So you still have to be careful about how you introduce changes to your APIs.&lt;/p&gt;

&lt;p&gt;The second is that while this makes it easy to update, it also forces me to accept any and all updates that are introduced to the client, even if my specific service might not benefit from these. If you have a larger number of clients, you will have to make sure that every client’s needs are met by a single implementation. Depending on how diverse your clients are, this might be easy or very hard. In any case, I find it is often underestimated how much simpler a client can be if it is implemented to just specifically support the needs of a single service. Having a client supporting multiple services will inevitably have to be at least a little more abstract.&lt;/p&gt;
&lt;h3&gt;
  
  
  Calling services correctly
&lt;/h3&gt;

&lt;p&gt;This argument is a popular one among people that highly value type-safety. They will claim that having a library ensures that the call made from the client matches the interface that is present on the server. However, I think this is not true at all. The only thing it will guarantee is that the client request will match the API of the server at the time that the client was compiled.&lt;/p&gt;

&lt;p&gt;Now, you could be forgiven for thinking this is practically the same. But remember that you usually have multiple environments like a staging, demo, production. And those can run different versions of your server, for various reasons. Maybe a deployment failed, maybe you update the demo environment less frequently. No matter the reason, having a shared library will not protect you to deploy a client with a version of the library that does not work with what is deployed on the server side.&lt;/p&gt;

&lt;p&gt;If you want to ensure compatibility between client and server, you have to check their interface compatibility before a deployment. The best way to do that in my experience is &lt;a href="https://martinfowler.com/bliki/ContractTest.html" rel="noopener noreferrer"&gt;contract testing&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Disadvantages
&lt;/h2&gt;

&lt;p&gt;Now that we’ve put the advantages into context, I want to highlight the disadvantages I see with the approach of the shared client library. Those are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versioning is difficult&lt;/li&gt;
&lt;li&gt;You are dependent on the technology it is implemented with&lt;/li&gt;
&lt;li&gt;It introduces subtle coupling&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Versioning hell
&lt;/h3&gt;

&lt;p&gt;Depending on how exactly you implement this approach, you can get into quite a bit of versioning hell. If every service has a dedicated library with a client for that service, you have a lot of library versions to check and update regularly. This creates a complex web of dependencies between different services.&lt;/p&gt;

&lt;p&gt;You could decrease the impact of this by putting all client implementations inside a single library. But then you have a single point of failure and a massive shared dependency between all services. Also, you might want to publish the client library from the repository of the service that the client talks to, so that client and service change together. But you can’t do that if all the clients are part of a single library.&lt;/p&gt;
&lt;h3&gt;
  
  
  Technology dependency
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ek97rs9g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/technology_hu5459c0360c2b0cb7a147d2df0eb350ca_1066039_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ek97rs9g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/technology_hu5459c0360c2b0cb7a147d2df0eb350ca_1066039_660x0_resize_q75_box.jpg" alt="green circuit board" width="660" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the most obvious drawback, but also the one depending most on your circumstances. Naturally, if you implement a client in a specific language, other services can only use that client if they are also written in the same language, or at least the same runtime. And I don’t think I have to convince anyone that maintaining multiple different implementations of these clients in different languages is a bad idea.&lt;/p&gt;

&lt;p&gt;This might not be a big issue for you since you write all your frontends and backends in the same language, like is possible with TypeScript. However, how confident are you that it will stay like this forever? That you will never need to write a service in a different language, maybe for performance reasons? Or maybe at some point you want to have an iOS or Android app. Sure, you can write those in TypeScript as well, but if you want an excellent user experience, you’re usually forced to adopt the native stack of the platform instead.&lt;/p&gt;

&lt;p&gt;I think there are situations where you can be quite sure you will not need another language, but don’t be overconfident when deciding this. We humans are historically very bad at predicting the future.&lt;/p&gt;
&lt;h3&gt;
  
  
  Coupling
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qdHNJbQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/coupling_hu5459c0360c2b0cb7a147d2df0eb350ca_5653775_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qdHNJbQp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/coupling_hu5459c0360c2b0cb7a147d2df0eb350ca_5653775_660x0_resize_q75_box.jpg" alt="rusty railway coupling" width="660" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;These have been coupled for a long time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most important problem with sharing clients between services is that it introduces a subtle coupling between the services. Naturally, we try to reduce coupling between microservices to the absolute minimum. So this is a problem for us. But many engineers fail to see that sharing a library with a client introduces coupling, so let me explain further.&lt;/p&gt;

&lt;p&gt;Without a library, the only coupling that exists is that the client depends on the shape of the API of the server. This is not something we can get around, so this is the absolute minimum of coupling we can get away with. Now, if you also introduce a library that includes types/classes for the server’s business entities and logic to translate those entities to HTTP requests, those are now additional things that the client depends on. You might also decide to use the same type/class for the server and the shared client library. But this means that every change to the representation of the business entity also forces a change in the library, which in turn is also forced on the user of the library.&lt;/p&gt;

&lt;p&gt;This might not seem very dangerous when you are starting out, but over time, you will make many changes to your services and to the shared libraries. And there will always be a temptation to put something in the shared library because it is the easiest way to solve a problem at that moment. Maybe you need some caching, maybe a translation function for a specific entity, or maybe add a header with logging information. The amount of responsibilities will inevitably increase over time, increasing the coupling further.&lt;/p&gt;

&lt;p&gt;Each and every of these responsibilities is a potential source for breakage on every change. And it is a decision that you make for every user of the client, taking agency away from them and potentially not presenting the optimal solution for their usage patterns. The more services use one of those client libraries, the more difficult it is to introduce changes to the library.&lt;/p&gt;

&lt;p&gt;I have personal experience with this downside. At one place I worked at, we had a shared client library for a service that used certain small helper libraries to implement the client. One of those helpers had a big breaking change one day and we had to spend quite some time to both update the library and update all the services that used it. And none of those changes had anything to do with the API itself, so it felt like a big waste of time.&lt;/p&gt;
&lt;h2&gt;
  
  
  What to do instead?
&lt;/h2&gt;

&lt;p&gt;But then what should we be doing instead? Should we implement a client in every repository where we need to call another service? While I don’t think that this is actually as bad as it sounds, there is also another solution that avoids this while also avoiding the disadvantages we just talked about so thoroughly.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enter OpenAPI
&lt;/h3&gt;

&lt;p&gt;Have you ever heard of &lt;a href="https://www.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt;? It is a standard for API descriptions. It defines a format for JSON or YAML files that allows you to specify what endpoints exist on an HTTP API, what parameters they accept, what status codes they can return, and so on. Not only that, but it is a wonderful way to document APIs in a way that is both useful for tooling and also for humans, since you can also add descriptions in prose. Nowadays, many APIs publish an OpenAPI specification for their interfaces. For example, here is the &lt;a href="https://github.com/PokeAPI/pokeapi/blob/master/openapi.yml" rel="noopener noreferrer"&gt;OpenAPI specification for the PokéAPI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, the endpoint to list Pokémon on the PokéAPI is described like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;/api/v2/pokemon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pokemon_list&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pokémon are the creatures that inhabit the world of the Pokémon&lt;/span&gt;
      &lt;span class="s"&gt;games. They can be caught using Pokéballs and trained by battling with other&lt;/span&gt;
      &lt;span class="s"&gt;Pokémon. Each Pokémon belongs to a specific species but may take on a variant&lt;/span&gt;
      &lt;span class="s"&gt;which makes it differ from other Pokémon of the same species, such as base&lt;/span&gt;
      &lt;span class="s"&gt;stats, available abilities and typings. See [Bulbapedia](http://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_(species))&lt;/span&gt;
      &lt;span class="s"&gt;for greater detail.&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List pokemon&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;limit&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Number of results to return per page.&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;offset&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The initial index from which to return the results.&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;q&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;locally&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[pokeapi.co](https://pokeapi.co/docs/v2)&lt;/span&gt;&lt;span class="se"&gt;\n\
&lt;/span&gt;          &lt;span class="s"&gt;Case-insensitive&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;applied&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;`name`&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;property.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pokemon&lt;/span&gt;
    &lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cookieAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;basicAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/PaginatedPokemonSummaryList"&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;$ref: "#/components/schemas/PaginatedPokemonSummaryList"&lt;/code&gt; is a reference to another part of the document. You can use those references to avoid repeating the same payload schema several times.&lt;/p&gt;

&lt;p&gt;Now OpenAPI is great for documentation, but it also allows you to generate clients automatically that comply with the spec that you present as an input. So if you create good documentation for your API, you also get a client for that API for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use it
&lt;/h3&gt;

&lt;p&gt;There are a few things we need to do to take full advantage of this approach. First, we need to agree in our organization that all services will publish OpenAPI specifications for their APIs. The specifications need to be in a place where every engineer can easily get them. In organizations where I can grant at least read-access for all repositories to the engineers, I usually just define a conventional path where the spec should be put in the repository of the service that implements the described API.&lt;/p&gt;

&lt;p&gt;Next, we need to make sure that the API is &lt;em&gt;actually&lt;/em&gt; describing the interface of the service. Since the spec is just a JSON or YAML file, this does not enforce anything. And it’s really easy to forget updating the spec after you make a change to your API. An easy way to make sure that the API and the spec match is to use some kind of framework that generates the spec from your API code. In Node.js with TypeScript, &lt;a href="https://github.com/fastify/fastify-swagger" rel="noopener noreferrer"&gt;Fastify has a plugin&lt;/a&gt; that uses the schema of your routes to also generate an OpenAPI spec.&lt;/p&gt;

&lt;p&gt;This works pretty well. But let’s assume your web server or framework of choice does not offer anything like that. Another easy way to achieve this is to leverage your test suite. During the automated tests of the HTTP API, you can just read the OpenAPI spec and check every request and response against the specification, failing the current test if any of them do not match the spec. An easy way to do this in Node.js is &lt;a href="https://github.com/openapi-library/OpenAPIValidators/tree/master/packages/jest-openapi#readme" rel="noopener noreferrer"&gt;jest-openapi&lt;/a&gt;. If you already have tests for your HTTP API and an OpenAPI spec, then this literally adds one line of code per test. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jestOpenAPI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jest-openapi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Here you have to load your OpenAPI spec.&lt;/span&gt;
&lt;span class="nf"&gt;jestOpenAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openapi/spec.yml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET /example/endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should satisfy OpenAPI spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000/api/v2/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// This is what checks the response against your OpenAPI spec.&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toSatisfyApiSpec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now that we know that our spec really describes our implemented API, we only need to solve how the client can call the API. As I already said earlier, we can leverage the specification to generate a client in whatever language we want. For that, we just copy the OpenAPI specification file from the service that defines it into the codebase of the service or client that wants to call the API. In TypeScript, my preferred tool is &lt;a href="https://openapi-ts.dev/" rel="noopener noreferrer"&gt;openapi-typescript&lt;/a&gt;. It really takes advantage of TypeScript’s type system, allows adding middlewares and authentication, and only weighs about 5kb. Generating a client with it is just one line on your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx openapi-typescript ./openapi/spec.yml &lt;span class="nt"&gt;-o&lt;/span&gt; ./openapi/spec.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And using it is similarly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openapi-fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./openapi/spec&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The URL is autocompleted here due to the generated types.&lt;/span&gt;
&lt;span class="c1"&gt;// It also enforces any required headers and returns a discriminated union&lt;/span&gt;
&lt;span class="c1"&gt;// of either the error or the success result.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some-token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And now we are ready to do calls, without having introduced any form of shared library. Success!&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this better?
&lt;/h3&gt;

&lt;p&gt;Since I already spent a lot of time explaining the shared library’s disadvantages, you already probably see how we avoid many of them with this approach. But at the risk of repeating myself, let me explain why this is better than sharing a library with a client implementation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Open for any tech stack
&lt;/h4&gt;

&lt;p&gt;The generator we used to get a client implementation is by far not the only one out there. There is pretty much a generator for every major language. So in a polyglot environment, we can easily generate clients for any of our services in seconds. This also allows each team to choose for themselves which tech stack suits their problem best. While I think there is great benefit in keeping tech stacks across teams consistent, there is usually a need for at least some variation. And you might always have that one really critical service that needs to be really performant, making it a great candidate for a rewrite in Rust.&lt;/p&gt;

&lt;h4&gt;
  
  
  No publishing/updating lifecycle
&lt;/h4&gt;

&lt;p&gt;Obviously, if you don’t have any library, you also don’t need to publish or update it. Depending on the amount of libraries, this saves quite a bit of CI/CD work. And even if you can automate large parts of this lifecycle, it’s still there, it still needs to be maintained, and it can still break. Having no code at all will always be easier to maintain.&lt;/p&gt;

&lt;h4&gt;
  
  
  No single source of truth
&lt;/h4&gt;

&lt;p&gt;This might be a bit counter-intuitive, but I really like that there is no single source of truth. Since we copy-paste the OpenAPI specification from the server, each client ends up with their own version of the specification. You could even delete parts of the specification that you’re not using on your client.&lt;/p&gt;

&lt;p&gt;Many engineers shudder at the thought of this, because it is a violation of DRY, and they need to maintain all of those duplicated specs manually. But the mistake is assuming that those specs need to be maintained. We should be trying to avoid making breaking changes to our APIs anyway. So unless there is either a breaking change or I need to call a new endpoint, why would I update my local specification if I can do everything I need with it? There is no reason at all to do this.&lt;/p&gt;

&lt;p&gt;This creates a situation where every client has their own truth for what the API of the server looks like. They basically only document &lt;em&gt;their&lt;/em&gt; assumptions about the API. As long as the server satisfies every client’s assumptions, it doesn’t really matter that they are different. If one client is missing a field of the response in his spec, but the client doesn’t intend to use that field, then we don’t have a problem.&lt;/p&gt;

&lt;p&gt;Since this is not a single source of truth, I like to call this a “distributed truth”. And as the name implies, I also think this is a very suitable solution for a distributed system, which is what a microservice architecture is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential drawbacks
&lt;/h2&gt;

&lt;p&gt;Every solutions has pros and cons, and this one is not an exception. So while I’m convinced this is the better way to do this, I still want to talk a bit about the challenges with this approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  You need to create the specs
&lt;/h3&gt;

&lt;p&gt;This one is somewhat obvious, but depending on your organization, it can be challenging to make engineers write documentation. For this approach to succeed, you need to convince your engineers to document their APIs using the OpenAPI standard. And also to document it well and thoroughly. For example, if you only document success responses and not the possible error status codes, this greatly diminishes the value of the specification.&lt;/p&gt;

&lt;p&gt;Having said that, getting a client for free is usually a good incentive to write such a spec. And the payoff for good documentation is well worth the effort even when you don’t use it to generate clients from it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only works with REST APIs
&lt;/h3&gt;

&lt;p&gt;OpenAPI is really focused on documenting REST APIs that use JSON or XML. If you use something like GraphQL or gRPC, OpenAPI doesn’t really work for that. So it’s not a solution that you can employ. Having said that, those solutions usually come with their own way of documenting the APIs and generating clients for them. So it’s a better idea anyways to use what they provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where client libraries work well
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D8it8lgL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/monolith_hu6bd78583debc2d36093d61944a904f7f_1523979_660x0_resize_q75_box.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D8it8lgL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://mmainz.dev/posts/microservices-antipatterns-the-shared-client-library/monolith_hu6bd78583debc2d36093d61944a904f7f_1523979_660x0_resize_q75_box.jpg" alt="monolith" width="660" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have mostly worked in environments where there is one service per repository. In this case, I find the OpenAPI approach far superior to the shared client library. However, if you have a monorepo where all clients and the server are in the same codebase, this changes things. You can change the client and server in the same commit, and you can proceed to deploy them entirely together. I can see the approach of sharing a client work very well here. Although this technically wouldn’t be a shared client library, because you just import the same code in both client and server without making it a library.&lt;/p&gt;

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

&lt;p&gt;Now you should have a good overview of the disadvantages of sharing client implementations using a shared library. You should also know how to do it instead by leveraging OpenAPI specifications.&lt;/p&gt;

&lt;p&gt;I want to emphasize that this aims specifically at client libraries. I still think there is a place for shared libraries between microservices if those libraries implement true cross-cutting concerns, like logging, monitoring, or caching. You still have to be careful to stay with a general-purpose implementation of these concerns and not introduce specifics of any service in them, but as long as you do that, shared libraries are a great use case for that.&lt;/p&gt;

&lt;p&gt;If you really bought into this and want to try it yourself, start by searching for OpenAPI integration for your current HTTP server or framework and your test framework. This is usually a good place to start when you don’t yet have any specs. Then search for some generators for clients in your tech stack. There are usually plenty to choose from. Once you found the tools you want to use, just start adding a spec for a single, simple endpoint and see how it feels.&lt;/p&gt;

&lt;p&gt;I hope this was valuable to you and that I could convince you of my perspective on this topic. If not, I’d love to hear from you why you still think shared client libraries are a good solution for this and what your experience with them is.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>antipatterns</category>
      <category>architecture</category>
      <category>openapi</category>
    </item>
  </channel>
</rss>
