<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Kendall Morgan</title>
        <link>https://kendallmorgan.com/posts/</link>
        <description>Recent content in Posts on Kendall Morgan</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Wed, 29 Jul 2020 20:45:47 -0700</lastBuildDate>
        <atom:link href="https://kendallmorgan.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Protocol-Agnostic Design for Scalable and Maintainable Backends</title>
            <link>https://kendallmorgan.com/posts/protocol-agnostic-design-for-scalable-and-maintainable-backends/</link>
            <pubDate>Sat, 07 Jun 2025 22:27:52 -0400</pubDate>
            
            <guid>https://kendallmorgan.com/posts/protocol-agnostic-design-for-scalable-and-maintainable-backends/</guid>
            <description>&lt;p&gt;When building out backend services, I have a general philosophy of isolating the boundaries of applications. This translates to one of the core principles I use when building applications: Abstract the transport layer from your business logic.&lt;/p&gt;
&lt;p&gt;The transport layer is typically responsible for serializing and deserializing data across a network, translating requests, responses, and errors for the business layer, and routing network requests to the business layer. This makes it easy to reuse the business layer across various network protocols, enforces separation of concerns, and improves testability of your application.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>When building out backend services, I have a general philosophy of isolating the boundaries of applications. This translates to one of the core principles I use when building applications: Abstract the transport layer from your business logic.</p>
<p>The transport layer is typically responsible for serializing and deserializing data across a network, translating requests, responses, and errors for the business layer, and routing network requests to the business layer. This makes it easy to reuse the business layer across various network protocols, enforces separation of concerns, and improves testability of your application.</p>
<p>I&rsquo;m being intentionally vague about what happens in the business layer, as that is highly specific to the application you are building and can have its own separate patterns or architecture. However, for transport layer abstraction to work the business layer must consistently use:</p>
<ul>
<li>Clearly defined request objects</li>
<li>Clearly defined response objects</li>
<li>Well-scoped error types</li>
</ul>
<h2 id="a-simple-http-service">A Simple HTTP Service</h2>
<p><a href="https://github.com/kendallm/example-users-service/commit/abe6f679ce601295d4ad5eddd757bc79599984ff"><em>source code</em></a></p>
<p>To illustrate the usefulness of transport layer abstraction, we will use a common example of updating a user in a database. Let&rsquo;s start by implementing an extremely simplified HTTP endpoint in Go using the echo framework.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">User</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`param:&#34;id&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;first_name&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;last_name&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">userDB</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">PUT</span>(<span style="color:#e6db74">&#34;/users/:id&#34;</span>, <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">userDB</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Logger</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">userDB</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>) <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">user</span> <span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Bind</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">user</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">NoContent</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">dbUser</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">userDB</span>[<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">ID</span>]
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">NoContent</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">dbUser</span>.<span style="color:#a6e22e">FirstName</span> = <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">FirstName</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">dbUser</span>.<span style="color:#a6e22e">LastName</span> = <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">LastName</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#a6e22e">dbUser</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now that we have built out the service let&rsquo;s add some tests.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestUpdateUser</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;should update user when user exists&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mockDB</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#34;test_user_id&#34;</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">ID</span>:        <span style="color:#e6db74">&#34;test_user_id&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">FirstName</span>: <span style="color:#e6db74">&#34;Test&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">LastName</span>:  <span style="color:#e6db74">&#34;User&#34;</span>,
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">userJSON</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;first_name&#34;:&#34;UpdatedTest&#34;, &#34;last_name&#34;:&#34;UpdatedUser&#34;}`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRequest</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>, <span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">userJSON</span>))
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HeaderContentType</span>, <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">MIMEApplicationJSON</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rec</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRecorder</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">NewContext</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">rec</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetPath</span>(<span style="color:#e6db74">&#34;/users/:id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamNames</span>(<span style="color:#e6db74">&#34;id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamValues</span>(<span style="color:#e6db74">&#34;test_user_id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">h</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">mockDB</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">NoError</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">h</span>(<span style="color:#a6e22e">c</span>)) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">Code</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">response</span> <span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">NoError</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>(<span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">Body</span>.<span style="color:#a6e22e">Bytes</span>(), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">response</span>)) {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">FailNow</span>()
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">mockDB</span>[<span style="color:#e6db74">&#34;test_user_id&#34;</span>], <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">response</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#e6db74">&#34;UpdatedTest&#34;</span>, <span style="color:#a6e22e">mockDB</span>[<span style="color:#e6db74">&#34;test_user_id&#34;</span>].<span style="color:#a6e22e">FirstName</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#e6db74">&#34;UpdatedUser&#34;</span>, <span style="color:#a6e22e">mockDB</span>[<span style="color:#e6db74">&#34;test_user_id&#34;</span>].<span style="color:#a6e22e">LastName</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;should return 404 when user not found&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mockDB</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#34;test_user_id&#34;</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">ID</span>:        <span style="color:#e6db74">&#34;test_user_id&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">FirstName</span>: <span style="color:#e6db74">&#34;Test&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">LastName</span>:  <span style="color:#e6db74">&#34;User&#34;</span>,
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">userJSON</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;first_name&#34;:&#34;UpdatedTest&#34;, &#34;last_name&#34;:&#34;UpdatedUser&#34;}`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRequest</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>, <span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">userJSON</span>))
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HeaderContentType</span>, <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">MIMEApplicationJSON</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rec</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRecorder</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">NewContext</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">rec</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetPath</span>(<span style="color:#e6db74">&#34;/users/:id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamNames</span>(<span style="color:#e6db74">&#34;id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamValues</span>(<span style="color:#e6db74">&#34;bad_user_id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">h</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">mockDB</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">NoError</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">h</span>(<span style="color:#a6e22e">c</span>)) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>, <span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">Code</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;should return 400 when request is invalid&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">mockDB</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#e6db74">&#34;test_user_id&#34;</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">ID</span>:        <span style="color:#e6db74">&#34;test_user_id&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">FirstName</span>: <span style="color:#e6db74">&#34;Test&#34;</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">LastName</span>:  <span style="color:#e6db74">&#34;User&#34;</span>,
</span></span><span style="display:flex;"><span>			},
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">userJSON</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">`{&#34;first_name&#34;:&#34;UpdatedTest&#34;, &#34;last_name&#34;:300}`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRequest</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>, <span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">NewReader</span>(<span style="color:#a6e22e">userJSON</span>))
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HeaderContentType</span>, <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">MIMEApplicationJSON</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">rec</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRecorder</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">NewContext</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">rec</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetPath</span>(<span style="color:#e6db74">&#34;/users/:id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamNames</span>(<span style="color:#e6db74">&#34;id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetParamValues</span>(<span style="color:#e6db74">&#34;bad_user_id&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">h</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">mockDB</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">NoError</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">h</span>(<span style="color:#a6e22e">c</span>)) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>, <span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">Code</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We now have a working and tested service that allows clients to update users. Unfortunately, requirements have changed and now we need to also expose a GRPC service for updating users. Now we have to:</p>
<ul>
<li>Re-implement the update logic in the gRPC server</li>
<li>Recreate tests for the new server</li>
<li>Risk having divergent behavior between the gRPC service and the HTTP service.</li>
</ul>
<p>What if we wanted to accept updates from a queue like AWS SQS? Let&rsquo;s implement another handler that listens to an SQS queue and reimplement the update logic there.</p>
<p>There is also something subtle about the test we already wrote. We are testing both the business logic and the transport layer logic for each test case. Notice the tags on the user struct? What if we wanted to use XML instead of JSON? We would have to update the tags on the user struct. There is also the logic around the ID being a path param instead of a apart of the request body. These are all things that only matter to the transport layer. Most projects start off simple, but it&rsquo;s not uncommon for requirements or an application&rsquo;s context to change.</p>
<p>Here’s how the architecture evolves when you decouple the transport layer from the business logic:</p>








<div class="theme-image-container optional-css-class">
    <img 
        src="/img/posts/protocol-agnostic-design/pt-agnostic-light.png" 
        alt="diagram showing service architecture" 
        class="theme-image" 
        data-light-src="/img/posts/protocol-agnostic-design/pt-agnostic-light.png" 
        data-dark-src="/img/posts/protocol-agnostic-design/pt-agnostic-dark.png"
        style="opacity: 0; transition: opacity 0.3s ease-in-out;"
        onload="this.style.opacity = '1'; this.src = document.documentElement.getAttribute('data-theme') === 'dark' ? this.dataset.darkSrc : this.dataset.lightSrc"
    >
</div>

<style>
.theme-image-container {
    position: relative;
    display: inline-block;
}
.theme-image {
    display: block;
    max-width: 100%;
    height: auto;
}
</style> 
<h2 id="abstracting-the-transport-layer">Abstracting the Transport Layer</h2>
<p><a href="https://github.com/kendallm/example-users-service/commit/0aca3aa1b3aef0d696ab61f9d8f1cc4d6339a0e2"><em>source code</em></a></p>
<p>The current service example works, but it&rsquo;s hard to change how the service runs because our business logic is tightly coupled to HTTP. If we ever need or want to change how clients interact with the service we have to duplicate both the business logic and the error handling for any new protocols, and re-test both the protocol layer and the business logic. If we instead abstract the transport layer we can minimize the changes needed to the service to support new protocols.</p>
<p>By decoupling the business layer from the transport layer you enable:</p>
<ul>
<li>A single source of truth for business logic</li>
<li>Reusable and isolated components for business logic</li>
<li>Protocol-specific handlers that focus only on mapping requests, responses, and errors from the business layer to the transport layer</li>
</ul>
<p>Let’s look at how to refactor our current example.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">userDB</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">User</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">usersService</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">Service</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">DB</span>: <span style="color:#a6e22e">userDB</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">httpService</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">transport</span>.<span style="color:#a6e22e">NewHTTPServer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">transport</span>.<span style="color:#a6e22e">HTTPConfig</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Port</span>:         <span style="color:#ae81ff">8080</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">UsersService</span>: <span style="color:#a6e22e">usersService</span>,
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">httpService</span>.<span style="color:#a6e22e">Start</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="application-errors">Application Errors</h3>
<p>Define shared application error types that are mapped to protocol-level errors by the transport layer.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">errors</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ErrNotFound</span>   = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;not found&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ErrBadRequest</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;bad request&#34;</span>)
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="users-business-layer">Users Business Layer</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">users</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UpdateUserRequest</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UpdateUserResponse</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Service</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">DB</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Service</span>) <span style="color:#a6e22e">UpdateUser</span>(<span style="color:#a6e22e">request</span> <span style="color:#a6e22e">UpdateUserRequest</span>) (<span style="color:#a6e22e">UpdateUserResponse</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">DB</span>[<span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">ID</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">UpdateUserResponse</span>{}, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;unable to find user in db: %w&#34;</span>, <span style="color:#a6e22e">appErr</span>.<span style="color:#a6e22e">ErrNotFound</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">FirstName</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">FirstName</span> = <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">FirstName</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">LastName</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">LastName</span> = <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">LastName</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">UpdateUserResponse</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ID</span>:        <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">ID</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">FirstName</span>: <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">FirstName</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">LastName</span>:  <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">LastName</span>,
</span></span><span style="display:flex;"><span>	}, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is pure business logic: no HTTP, no network serialization, no JSON binding, no error mapping.</p>
<h3 id="transport-layer">Transport Layer</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">transport</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">HTTPServer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>    <span style="color:#f92672">*</span><span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Echo</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">port</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewHTTPServer</span>(<span style="color:#a6e22e">config</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">HTTPConfig</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">HTTPServer</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">registerHTTPRoutes</span>(<span style="color:#a6e22e">e</span>, <span style="color:#a6e22e">config</span>.<span style="color:#a6e22e">UsersService</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">HTTPServer</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">e</span>:    <span style="color:#a6e22e">e</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">port</span>: <span style="color:#a6e22e">config</span>.<span style="color:#a6e22e">Port</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">server</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">HTTPServer</span>) <span style="color:#a6e22e">Start</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Logger</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;:%d&#34;</span>, <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">port</span>)))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">registerHTTPRoutes</span>(<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Echo</span>, <span style="color:#a6e22e">usersService</span> <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">Service</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">PUT</span>(<span style="color:#e6db74">&#34;/users/:id&#34;</span>, <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">usersService</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">updateUserRequest</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`param:&#34;id&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;first_name&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;last_name&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">updateUserResponse</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;id&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;first_name&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;last_name&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">updateUser</span>(<span style="color:#a6e22e">usersService</span> <span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">Service</span>) <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">request</span> <span style="color:#a6e22e">updateUserRequest</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Bind</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">request</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">NoContent</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">usersService</span>.<span style="color:#a6e22e">UpdateUser</span>(<span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">UpdateUserRequest</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">ID</span>:        <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">ID</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">FirstName</span>: <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">FirstName</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">LastName</span>:  <span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">LastName</span>,
</span></span><span style="display:flex;"><span>		})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">NoContent</span>(<span style="color:#a6e22e">MapErrorToHTTPStatus</span>(<span style="color:#a6e22e">err</span>))
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">JSON</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#a6e22e">updateUserResponse</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">ID</span>:        <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">ID</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">FirstName</span>: <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">FirstName</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">LastName</span>:  <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">LastName</span>,
</span></span><span style="display:flex;"><span>		})
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MapErrorToHTTPStatus</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">appErr</span>.<span style="color:#a6e22e">ErrNotFound</span>):
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">appErr</span>.<span style="color:#a6e22e">ErrBadRequest</span>):
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This layer handles routing, JSON decoding, and error mapping. The business logic doesn’t know or care that it’s being called from HTTP.</p>
<p>If you wanted to expose this over gRPC, you’d just implement another transport layer using the same users.Service interface.</p>
<p>Each transport acts like a plug-in, wiring into the same business logic core.</p>








<div class="theme-image-container optional-css-class">
    <img 
        src="/img/posts/protocol-agnostic-design/plugin-light.png" 
        alt="diagram showing abstractions of the transport layer" 
        class="theme-image" 
        data-light-src="/img/posts/protocol-agnostic-design/plugin-light.png" 
        data-dark-src="/img/posts/protocol-agnostic-design/plugin-dark.png"
        style="opacity: 0; transition: opacity 0.3s ease-in-out;"
        onload="this.style.opacity = '1'; this.src = document.documentElement.getAttribute('data-theme') === 'dark' ? this.dataset.darkSrc : this.dataset.lightSrc"
    >
</div>

<style>
.theme-image-container {
    position: relative;
    display: inline-block;
}
.theme-image {
    display: block;
    max-width: 100%;
    height: auto;
}
</style> 
<h4 id="adding-support-for-sqs">Adding support for SQS</h4>
<p>The same principles apply if you’re receiving data from a message queue like Amazon SQS. Here’s an example that uses a dedicated request object and reuses the exact same business logic. This separation allows each transport to decode, validate, and forward requests independently, keeping business logic untouched.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">transport</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">updateUserSQSRequest</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ID</span>        <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;id&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">FirstName</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;first_name&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">LastName</span>  <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;last_name&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">SQSServer</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">SQSServer</span>) <span style="color:#a6e22e">Start</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">output</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">Client</span>.<span style="color:#a6e22e">ReceiveMessage</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sqs</span>.<span style="color:#a6e22e">ReceiveMessageInput</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">QueueUrl</span>:            <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">QueueURL</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">MaxNumberOfMessages</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">WaitTimeSeconds</span>:     <span style="color:#ae81ff">10</span>,
</span></span><span style="display:flex;"><span>		})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">msg</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">output</span>.<span style="color:#a6e22e">Messages</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">req</span> <span style="color:#a6e22e">updateUserSQSRequest</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#f92672">*</span><span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">Body</span>), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">req</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;failed to unmarshal SQS message: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">UsersService</span>.<span style="color:#a6e22e">UpdateUser</span>(<span style="color:#a6e22e">users</span>.<span style="color:#a6e22e">UpdateUserRequest</span>{
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">ID</span>:        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">ID</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">FirstName</span>: <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">FirstName</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">LastName</span>:  <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">LastName</span>,
</span></span><span style="display:flex;"><span>			})
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;unable to update user: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;updated user from SQS: %+v&#34;</span>, <span style="color:#a6e22e">resp</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">Client</span>.<span style="color:#a6e22e">DeleteMessage</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sqs</span>.<span style="color:#a6e22e">DeleteMessageInput</span>{
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">QueueUrl</span>:      <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">QueueURL</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">ReceiptHandle</span>: <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">ReceiptHandle</span>,
</span></span><span style="display:flex;"><span>			})
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">PollInterval</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="why-this-matters">Why This Matters</h2>
<p>Yes, this adds boilerplate and might seem like overengineering for simple services, but this abstraction makes the service significantly easier to change and test.</p>
<ul>
<li>Want to validate first names? Just update the service logic.</li>
<li>Want to expose gRPC? Add a gRPC handler that uses the same service.</li>
<li>Want to restrict HTTP to only update first names? Change only the HTTP handler.</li>
</ul>
<p>You no longer have to rewrite logic, and you&rsquo;re removing the coupling between the network and your application.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>Most services start off small and focused, but business requirements change frequently. Abstracting away the transport layer from business logic helps keep your services maintainable, testable, and extensible. You don&rsquo;t have to account for every requirement up front, but you make it significantly easier to accommodate changes later. This also gives structure to your applications and makes you intentional about how you build out services.</p>
]]></content>
        </item>
        
        <item>
            <title>Thoughts on Code Reviews</title>
            <link>https://kendallmorgan.com/posts/thoughts-on-code-reviews/</link>
            <pubDate>Tue, 20 Jul 2021 17:54:11 -0700</pubDate>
            
            <guid>https://kendallmorgan.com/posts/thoughts-on-code-reviews/</guid>
            <description>&lt;p&gt;My employer, Indeed.com, hosts engineering discussion panels and recently I have been a part of our code review discussions. Participating in these panels has forced me think intentionally about how I approach code reviews. This is an attempt to synthesize my approach.&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;I learn most efficiently when giving and receiving reviews and it seems to stick better than just reading or hearing advice passively. I see them as a way for me to help better others, help better myself, and help better the software that I build.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>My employer, Indeed.com, hosts engineering discussion panels and recently I have been a part of our code review discussions. Participating in these panels has forced me think intentionally about how I approach code reviews. This is an attempt to synthesize my approach.</p>
<h2 id="overview">Overview</h2>
<p>I learn most efficiently when giving and receiving reviews and it seems to stick better than just reading or hearing advice passively. I see them as a way for me to help better others, help better myself, and help better the software that I build.</p>
<p>I have also learned that code reviews are should not be primarily used to catch bugs, that&rsquo;s just a side effect. Code reviews are effective as a learning and teaching tool. They help elevate the ability of the people you build software with. The constant feedback cycle of doing and receiving code reviews is a useful tool for increasing software quality and readability. When doing code reviews you know someone else needs to understand your code and this helps emphasize clear and expressive code.</p>
<h2 id="receiving-reviews">Receiving Reviews</h2>
<h3 id="review-your-code-first">Review your code first</h3>
<p>Before asking someone else to do a code review, review it yourself. I usually do this in the actual CR tool. This is an easy way to catch small things that you miss when heads down coding.</p>
<h3 id="assign-someone-specific">Assign someone specific</h3>
<p>If there isn&rsquo;t a specific owner then often times code reviews get delayed. It&rsquo;s not that others shouldn&rsquo;t look at it, but you want someone to be responsible for it. This prevents the <a href="https://en.wikipedia.org/wiki/Bystander_effect">bystander effect</a>.</p>
<h3 id="be-kind-to-your-reviewer">Be kind to your reviewer</h3>
<h4 id="give-as-much-context-as-possible">Give as much context as possible</h4>
<p>Usually team members are familiar enough with your work that they have context already, but you can&rsquo;t make that assumption. The intent of the change should be clear. This is easily done by making sure the description in the CR is accurate and updated. If there are diagrams or design docs related to the change, link them in the CR. Make sure the description in the issue tracker is up to date and link that in the CR.</p>
<h4 id="try-to-keep-reviews-small">Try to keep reviews small</h4>
<p>This is one of the hardest things for me, but try to break changes into digestible pieces. If I see a review that is thousands of lines long and touches tens of files I dread it. I try to be conscious of that feeling when asking others to review my code. If a change does become large, break it up into smaller components and review each of those individually.</p>
<h2 id="doing-reviews">Doing Reviews</h2>
<h3 id="be-quick">Be Quick</h3>
<p>Often times when someone asks you for a code review they become blocked on you completing the review to finish work. There is usually a tree of dependencies waiting for each review and getting them done quickly helps the entire team.</p>
<p>I recommend setting aside time for code reviews in 2 ways:</p>
<ol>
<li>Check for assigned code reviews before standup.</li>
<li>Check for assigned code reviews after submitting one myself.</li>
</ol>
<p>When I do see a code review that I am responsible for I ensure that I look at it before I start any new tasks with one exception: large reviews. If a code review is large and will take longer than 15 - 20 minutes then block out time to look at it later that day.</p>
<p>With this approach I usually have a 24hr or less turn around for code reviews. This also give me something productive to do between waiting for my own reviews or starting a new task.</p>
<h3 id="gain-context">Gain Context</h3>
<p>When reviewing code you are likely to have some context around the change already. If you do not have context already  be sure you understand what the change does and why. The goal here is to ensure that you know what you are reviewing and the scope of the change.</p>
<p>If you were involved in the design or conception of a particular feature this step is fast. I start by looking at the ticket or merge request description to get the big picture. Next I will look at the commit messages. These are often smaller than a change description, but helps to understand the scope of a change. If I still don&rsquo;t have enough context then I reach out to the requester and ask for clarification.</p>
<h3 id="automate-the-boring-things">Automate the boring things</h3>
<p>A huge time sink in code reviews is feedback that should be caught with a linter or formatter. It&rsquo;s common to see comments like &ldquo;This variable should be final&rdquo;. This is low hanging fruit and should be caught before a code review starts. Teams should standardize on a style guide and use common linters to analyze code before it is reviewed. If there is something in a code review that seems like it should be caught with a linter then you should update the linter. This prevents noise in a code review that can cost time and increase cognitive load.</p>
<h3 id="start-with-the-tests">Start with the tests</h3>
<p>Once I understand the scope and intent of a change I start by looking at the tests. This usually gives me examples of typical call patterns and implementation expectations. I can also usually tease out assumptions and identify system boundaries based on mocked classes.</p>
<h3 id="watch-the-boundaries">Watch the boundaries</h3>
<p>When looking at a CR I ask myself two questions:</p>
<ol>
<li>Are we changing data coming into the system?</li>
<li>Are we changing data going out of the system?</li>
</ol>
<p>If the answer to any of these are yes I tend to look at things on these boundaries more intently. A short list of things I think about at the boundaries of a system include:</p>
<ol>
<li>Backwards compatibility</li>
<li>API design</li>
<li>Dependency coupling.</li>
</ol>
<p>There is a lot of opportunity at boundaries to introduce errors and changes at boundaries can have a large blast radius.</p>
<h3 id="darins-law-cover-your-ass">Darin&rsquo;s Law: Cover your A.S.S.</h3>
<p>When dealing with large scale systems there is a lot that can go wrong. At Amazon I worked with a principal engineer who distilled this down to 3 topics: Availability, Security, and Support. While doing code reviews I always keep those things in mind.</p>
<p>To ensure you are covering your A.S.S it use useful to ask your self:</p>
<ol>
<li>Can this change cause failure in your service or a dependency?</li>
<li>Is this change doing anything that can impact customer data?</li>
<li>Will I be able to diagnose issues with this change later?</li>
<li>Will I be able to easily update this code path later?</li>
</ol>
<h2 id="summary">Summary</h2>
<p>Code review is a useful tool for helping improve both software quality and technical acumen. The code review process is fun and has been a key component of my growth as an engineer. When requesting code reviews remember to be kind to your reviewer, you want to make the process of getting feedback as painless as possible. While reviewing code pay attention to your boundaries, and don&rsquo;t forget: Cover your A.S.S. in code reviews.</p>
]]></content>
        </item>
        
    </channel>
</rss>
