<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Greg Navis</title>
    <description>The latest articles on DEV Community by Greg Navis (@gregnavis).</description>
    <link>https://dev.to/gregnavis</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%2F127044%2F61384bd0-6bb0-4405-ac42-099d4da8d36e.jpg</url>
      <title>DEV Community: Greg Navis</title>
      <link>https://dev.to/gregnavis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gregnavis"/>
    <language>en</language>
    <item>
      <title>Elixir-style Pipelines in 9 Lines of Ruby</title>
      <dc:creator>Greg Navis</dc:creator>
      <pubDate>Mon, 24 Apr 2023 10:39:38 +0000</pubDate>
      <link>https://dev.to/gregnavis/elixir-style-pipelines-in-9-lines-of-ruby-2hmk</link>
      <guid>https://dev.to/gregnavis/elixir-style-pipelines-in-9-lines-of-ruby-2hmk</guid>
      <description>&lt;p&gt;Elixir pipelines are an elegant construct for sequencing operations in a readable way. Fortunately, 9 lines is all it takes to implement them in Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background: &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Ruby offers some pipelining primitives. &lt;code&gt;Proc&lt;/code&gt; and &lt;code&gt;Method&lt;/code&gt; respond to &lt;code&gt;#&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;#&amp;gt;&amp;gt;&lt;/code&gt;, which can be used in pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;FindByLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="no"&gt;ConfirmUserAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FindByLogin&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;ConfirmUserAccount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"gregnavis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach has several drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Proc&lt;/code&gt; returned by &lt;code&gt;#&amp;gt;&amp;gt;&lt;/code&gt; cannot be called using &lt;code&gt;result(...)&lt;/code&gt;, but only via &lt;code&gt;proc.call(...)&lt;/code&gt; or &lt;code&gt;proc.(...)&lt;/code&gt; or &lt;code&gt;proc[...]&lt;/code&gt;, which is inconsistent with regular method calls. Admittedly, this is unavoidable, but can be made to matter less.&lt;/li&gt;
&lt;li&gt;The pipeline argument comes at the end, but having it at the front would be more readable and more consistent with the pipeline structure.&lt;/li&gt;
&lt;li&gt;Operations taking more than one parameter must be implemented as higher-order procs or use currying. Introducing or eliminating additional parameters entails switching between regular procs and higher-order or curried procs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To illustrate the last problem, let's make &lt;code&gt;SendConfirmationNotification&lt;/code&gt; take an argument determining the type of notification: e-mail or SMS. It has to be rewritten as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using a higher-order proc.&lt;/span&gt;
&lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Using currying; notice .curry after the block&lt;/span&gt;
&lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, the pipeline still suffers from the problem of argument coming at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(FindByLogin &amp;gt;&amp;gt;
  ConfirmUserAccount &amp;gt;&amp;gt;
  SendConfirmationNotification[:sms])["gregnavis"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the article shows how to use Ruby refinements, a built-in but relatively obscure facility, to make the code below work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"gregnavis" &amp;gt;&amp;gt;
  FindByLogin &amp;gt;&amp;gt;
  ConfirmUserAccount &amp;gt;&amp;gt;
  SendConfirmationNotification[:sms]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start with operator definitions. Monkey-patching will be used initially, but will be replaced with refinements by the end of the article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Defining Parameterized Operations
&lt;/h2&gt;

&lt;p&gt;Parameterized and parameterless operations should be defined the same way. A new &lt;code&gt;Kernel&lt;/code&gt; method will help here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module Kernel
  def operation(...) = proc(...).curry
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, &lt;code&gt;operation&lt;/code&gt; is an automatically curried &lt;code&gt;proc&lt;/code&gt;. The pipeline can now be defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FindByLogin = operation { |login| ... }
ConfirmUserAccount = operation { |user| ... }

# Notice user comes last.
SendConfirmationNotification = operation { |method, user| ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to currying, the first argument to &lt;code&gt;SendConfirmationNotification&lt;/code&gt; can be provided in the pipeline, while the execution is "paused" until &lt;code&gt;user&lt;/code&gt; is provided, too. The code below now works as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(FindByLogin &amp;gt;&amp;gt;
  ConfirmUserAccount &amp;gt;&amp;gt;
  SendConfirmationNotification[:sms])["gregnavis"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next goal is moving the pipeline to the front.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Piping Arguments into Callables
&lt;/h2&gt;

&lt;p&gt;The following two expressions should be equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# When we write this:&lt;/span&gt;
&lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callable&lt;/span&gt;

&lt;span class="c1"&gt;# we actually mean this:&lt;/span&gt;
&lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet hints at what needs to be done: &lt;em&gt;all&lt;/em&gt; objects must respond to &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; and that method must call &lt;code&gt;call&lt;/code&gt;. A top-level class (&lt;code&gt;Object&lt;/code&gt; or &lt;code&gt;BasicObject&lt;/code&gt;) must be modified to make &lt;code&gt;#&amp;gt;&amp;gt;&lt;/code&gt; available on all objects, resulting in the following patch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we're able to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"gregnavis"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;FindUserByLogin&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;ConfirmUserAccount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The patches on &lt;code&gt;Kernel&lt;/code&gt; and &lt;code&gt;Object&lt;/code&gt; must be turned into a refinement to avoid global monkey patching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Introducing Refinements
&lt;/h2&gt;

&lt;p&gt;Refinements are a topic for a separate article, but in short they are monkey-patches that can enabled inside a specific module or class by calling &lt;code&gt;Module#using&lt;/code&gt;. Let's approach the problem outside in by starting with how we want the code to be used.&lt;/p&gt;

&lt;p&gt;Suppose we're working inside a Rails controller. We'd like to be able to write code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="no"&gt;Pipelines&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;confirm&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:login&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;FindUserByLogin&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;ConfirmUserAccount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
      &lt;span class="no"&gt;SendConfirmationNotification&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;using&lt;/code&gt; is built into Ruby, and &lt;code&gt;Pipelines&lt;/code&gt; is the refinement to be defined. It's an ordinary Ruby module that refines (i.e. patches) &lt;code&gt;Kernel&lt;/code&gt; and &lt;code&gt;Object&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Pipelines&lt;/span&gt;
  &lt;span class="n"&gt;refine&lt;/span&gt; &lt;span class="no"&gt;Kernel&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;curry&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;refine&lt;/span&gt; &lt;span class="no"&gt;Object&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Pipelines are now enabled only in &lt;code&gt;UsersController&lt;/code&gt;, and no other code will be affected. Keep in mind you need to be &lt;code&gt;using&lt;/code&gt; the refinement when defining operations too (so that &lt;code&gt;operation&lt;/code&gt; is available).&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That pipeline implementation would fit on a napkin. Let's have a critical look at this approach.&lt;/p&gt;

&lt;p&gt;First, calling a curried proc with no arguments keeps the execution "paused", so missing an argument can make the pipeline return a curried proc, instead of the expected return value. This will likely lead to difficult to understand errors later in the program.&lt;/p&gt;

&lt;p&gt;Second, procs are difficult to inspect. Seeing &lt;code&gt;#&amp;lt;Proc:0x...&amp;gt;&lt;/code&gt; in the terminal is unhelpful when debugging. It &lt;em&gt;is&lt;/em&gt; possible to inspect parameters passed to an operation via &lt;code&gt;operation_object.binding.local_variables&lt;/code&gt; and &lt;code&gt;operation_object.binding.local_variable_get(name)&lt;/code&gt; for parameters of interest. It'd be more helpful if inspecting an operation produced something along the lines of &lt;code&gt;SendConfirmationNotification[method: :sms]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The above was my &lt;em&gt;second&lt;/em&gt; approach to implementing pipelines. The first approach was object-oriented and didn't have the drawbacks mentioned above at the expense of &lt;em&gt;slightly&lt;/em&gt; more complex implementation. I'll cover it an an upcoming article.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>elixir</category>
      <category>coding</category>
    </item>
    <item>
      <title>API Integrations: Client Classes and Error Handling</title>
      <dc:creator>Greg Navis</dc:creator>
      <pubDate>Tue, 23 Aug 2022 13:41:38 +0000</pubDate>
      <link>https://dev.to/gregnavis/api-integrations-client-classes-and-error-handling-33p9</link>
      <guid>https://dev.to/gregnavis/api-integrations-client-classes-and-error-handling-33p9</guid>
      <description>&lt;p&gt;The way API clients signal results and errors is critical to integration quality, especially for under-documented APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gregnavis.com/articles/api-integrations-implementing-client-classes.html"&gt;The previous article&lt;/a&gt; introduced the concept of the client class - the class implementing all interactions with a specific third-party API. A dedicated class is a step in the right direction, but is insufficient to guarantee quality unless properly implemented.&lt;/p&gt;

&lt;p&gt;In this and following articles, we'll discuss how to build robust client classes. Let's start with the problem of error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;A robust API integration &lt;strong&gt;must&lt;/strong&gt; be prepared to handle errors that absent from integrating with own code. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unavailability due to network or service disruption.&lt;/li&gt;
&lt;li&gt;Unexpected results or errors as there's much more things that can go wrong: wrong HTTP headers, wrong encoding, invalid payloads and many others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consequently, error handling and recovery is central to a quality API integration. We'll cover four approaches, ranging from familiar (exceptions) to exotic (result monads).&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Exceptions
&lt;/h2&gt;

&lt;p&gt;The most familiar approach is using a return value on success and raising an exception on error. This behavior is prevalent in Ruby and other languages. Simplicity and familiarity are its advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyone knows how to raise exceptions, which makes the client class code easy to read and write.&lt;/li&gt;
&lt;li&gt;Everyone knows how to rescue exceptions, which makes error handling code easy to write (but not necessarily read).&lt;/li&gt;
&lt;li&gt;Unhandled errors cannot go unnoticed, as unhandled exceptions will result in a server error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regrettably, there are subtle downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exception-based control flow is non-idiomatic and less readable.&lt;/li&gt;
&lt;li&gt;Exceptions caused by API errors and internal client class bugs are lumped together; distinguishing them requires extra care.&lt;/li&gt;
&lt;li&gt;When working in the console, exceptions caused by API errors must be rescued and assigned to a variable for further inspection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is especially bothersome when working with under-documented APIs. They require much more exploration and experimentation, so developer convenience becomes crucial.&lt;/p&gt;

&lt;p&gt;When using this method, it makes sense to define a dedicated exception class for API errors to make it easier to distinguish them from client class bugs. For example, the What Weather API from the previous article could define &lt;code&gt;WhatWeather::ServiceError&lt;/code&gt; and use it like in the snippet below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;WhatWeather&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ServiceError&lt;/span&gt;
    &lt;span class="c1"&gt;# If the API returns an error then return a 200 and an error message.&lt;/span&gt;
    &lt;span class="c1"&gt;# Exceptions unrelated to the API will propagate up the stack and likely&lt;/span&gt;
    &lt;span class="c1"&gt;# cause an internal server error (as they should!).&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"unavailable"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Happy path continues here&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This brings us to the next method which addresses these drawbacks elegantly and idiomatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Tuples and Pattern Matching
&lt;/h2&gt;

&lt;p&gt;The Go-inspired alternative is to return &lt;code&gt;[:ok, result]&lt;/code&gt; on success and &lt;code&gt;[:error, details]&lt;/code&gt; on error, with exceptions left for truly exceptions situations, like inconsistent internal client state.&lt;/p&gt;

&lt;p&gt;This method coupled with pattern matching, which landed in Ruby 2.7, results in idiomatic and readable control flow. The snippet below calls the imaginary What Weather API and uses pattern matching to decide what to do next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="vg"&gt;$weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_weather&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:timeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:unavailable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"unavailable"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Continue with the happy path.&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flow control for successes and errors is unified and it's &lt;strong&gt;still&lt;/strong&gt; possible to raise an exception when needed. Let's discuss two concerns related to the method, followed by its drawbacks.&lt;/p&gt;

&lt;p&gt;First, if the client return value matches no cases then &lt;code&gt;NoMatchingPatternError&lt;/code&gt; will be raised. It's helpful in identifying all errors occurring in the wild, but may be too radical. If that's the case then a generic &lt;code&gt;[:error, _]&lt;/code&gt; catch-all pattern and a log statement with error details should be suffice.&lt;/p&gt;

&lt;p&gt;Second, raising an exception on API errors may sometimes be preferable. In those cases, the client class can offer bang methods, like &lt;code&gt;#find_by_name!&lt;/code&gt;, that call the non-bang version under the hood but raise on error.&lt;/p&gt;

&lt;p&gt;Evidently, all drawbacks of method 1 were address but there's a new one: if an API call isn't wrapped in &lt;code&gt;case&lt;/code&gt; then execution will continue along the happy path even upon errors. Fortunately, this problem can be solved with static code analysis which leads us to the third method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: Tuples, Pattern Matching, and Linting
&lt;/h2&gt;

&lt;p&gt;Given the API client is available via a global variable, it's likely that most, if not all, API calls will be made through that variable. This makes it easy for a custom linter rule to inspect all method calls on that global and ensure they're wrapped in &lt;code&gt;case&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The linter could work like that: a configuration setting lists all global variables to check. Whenever a method is called with a global from the list as the recipient then the linter should ensure it's inside a &lt;code&gt;case&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;If building a custom linter rule is infeasible (thought, it's not as difficult as it sounds) then a makeshift approach based on &lt;code&gt;grep&lt;/code&gt; may be good enough. For example, detecting all method calls on &lt;code&gt;$weather&lt;/code&gt; that aren't wrapped in &lt;code&gt;case&lt;/code&gt; could be accomplished with &lt;code&gt;grep -r '$weather.' . | grep -v 'case $weather'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: Result Monads
&lt;/h2&gt;

&lt;p&gt;Monads deserve a separate article, so we'll describe them briefly and use &lt;a href="https://dry-rb.org/gems/dry-monads/1.3/"&gt;&lt;code&gt;dry-monads&lt;/code&gt;&lt;/a&gt; in examples that follow.&lt;/p&gt;

&lt;p&gt;A result monad resembles a polymorphic implementation of booleans with a more sophisticated interface. There are two types of results: successes and failures. A success wraps the result of a computation (an API call in our case); a failure wraps an error description. A result can be transformed into another result via a method called &lt;code&gt;#bind&lt;/code&gt;. When it's called on a successful result, it passes the wrapped value to a block and returns another result (a success or failure). When it's called on a failure then &lt;strong&gt;it does nothing&lt;/strong&gt; and lets that failure propagate forward. There's similarity to short-circuit logic of boolean operators.&lt;/p&gt;

&lt;p&gt;Result moands shine when composition is required, so let's enhance our example controller action: it geolocates us via the &lt;code&gt;$geolocation&lt;/code&gt; monad-based service and then returns the weather at our location. A monad-based version of the weather API client class could look like in the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherService::WhatWeather&lt;/span&gt;
  &lt;span class="c1"&gt;# Required to access monad-specific features.&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Monads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"/api/weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
      &lt;span class="c1"&gt;# Build the weather object and wrap it in a success monad.&lt;/span&gt;
      &lt;span class="no"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;build_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# Extract error details from the response and wrap it in a failure monad.&lt;/span&gt;
      &lt;span class="no"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller action would use &lt;code&gt;$geolocation.locate_request&lt;/code&gt; followed by &lt;code&gt;$weather.find_by_location&lt;/code&gt;. Both methods return result monads that can be combined using &lt;code&gt;#bind&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="n"&gt;weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;# locate_request returns either a success or failure.&lt;/span&gt;
    &lt;span class="vg"&gt;$geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locate_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# #bind on a success unwraps the value and processes them further via this&lt;/span&gt;
      &lt;span class="c1"&gt;# block. $weather.find_by_location returns another result which becomes&lt;/span&gt;
      &lt;span class="c1"&gt;# the result of the whole computation.&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# However, if geolocation fails then #bind will be called on a failure and&lt;/span&gt;
      &lt;span class="c1"&gt;# would NOT call this block. It'd simply return the original failure.&lt;/span&gt;
      &lt;span class="vg"&gt;$weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by_location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# weather is a result monad, NOT the actual value.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success?&lt;/span&gt;
    &lt;span class="c1"&gt;# Forcibly extract the weather value.&lt;/span&gt;
    &lt;span class="vi"&gt;@weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value!&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"unavailable"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monads may look unfamiliar and may require some time to learn. However, frequent composition of operations (not only third-party API calls) can make them a worthwhile investment.&lt;/p&gt;

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

&lt;p&gt;We've discussed four different approaches to error handling. Methods 3 and 4 may be my personal favorites, but the real point is to broaden the inventory of techniques at our disposal, so that we're capable of making the right choice after taking the project state, goals, and team into consideration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article has originally appear on &lt;a href="https://www.gregnavis.com/articles/api-integrations-client-classes-and-error-handling.html"&gt;my website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>API Integrations: Building Client Classes</title>
      <dc:creator>Greg Navis</dc:creator>
      <pubDate>Fri, 22 Jul 2022 09:59:34 +0000</pubDate>
      <link>https://dev.to/gregnavis/api-integrations-building-client-classes-3bih</link>
      <guid>https://dev.to/gregnavis/api-integrations-building-client-classes-3bih</guid>
      <description>&lt;p&gt;Code speaks louder than words, so we'll go through an example integration with a weather service, called WhatWeather, that returns current weather conditions by latitude and longitude or city name. Let's describe that imaginary service briefly and then proceed with the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  WhatWeather API Overview
&lt;/h2&gt;

&lt;p&gt;WhatWeather API is accessible over HTTP and offers &lt;strong&gt;one&lt;/strong&gt; endpoint that can be used in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/weather?lat=...&amp;amp;lon=...&lt;/code&gt; returns weather conditions at the given coordinates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/weather?name=...&lt;/code&gt; returns weather conditions in the given city.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The API responds with a 200 OK and a JSON object containing two keys: &lt;code&gt;"temperature"&lt;/code&gt; and &lt;code&gt;"humidity"&lt;/code&gt;. Authentication is handled via a token passed in a custom header named &lt;code&gt;X-API-Key&lt;/code&gt;. Any other status code, even in the 200-299 range, can be assumed to be an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build Client Class
&lt;/h2&gt;

&lt;p&gt;First, we must define the client class interface. Even though the WhatWeather API offers one endpoint it makes sense to have two separate methods: &lt;code&gt;#weather_by_coordinates&lt;/code&gt; and &lt;code&gt;#weather_by_name&lt;/code&gt;. We should strive for maximum code clarity, not mirroring the structure of the underlying API.&lt;/p&gt;

&lt;p&gt;Second, we need a namespace (a Ruby module) for all weather-related code. Its name shouldn't refer to a specific API provider to keep the code generic and avoid coupling to a specific vendor. &lt;code&gt;WeatherService&lt;/code&gt; seems like a reasonable name.&lt;/p&gt;

&lt;p&gt;Third, we need a class representing results returned by the weather service. In simple cases, a &lt;code&gt;Struct&lt;/code&gt; should be enough, like the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Weather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:humidity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In more complex cases, a custom class might be needed. It's important to &lt;strong&gt;make them independent from the actual API provider&lt;/strong&gt; -- otherwise, it might become an octopus whose tentacles reach to the very depths of the app. For example, the client class should parse and convert HTTP responses into instances of &lt;code&gt;WeatherService::Weather&lt;/code&gt; and similar classes so that if their API change there's only one place in our code base that needs to be adapted.&lt;/p&gt;

&lt;p&gt;We're ready for the fourth and final step: implementing the actual client class. We can use a third-party client gem or interact with the API via HTTP, GraphQL or another protocol. Whatever method we use, it's important to keep the point from the previous paragraph in mind -- don't let the actual client implementation leak through the client class and flood the rest of the code.&lt;/p&gt;

&lt;p&gt;The example client below takes an HTTP client object via the constructor. Keep in mind it's not production ready as its input validation, error handling, and response parsing is rudimentary but is enough to illustrate the idea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherService::WhatWeather&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="vi"&gt;@http_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_client&lt;/span&gt;
    &lt;span class="vi"&gt;@api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weather_by_coordinates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"/api/weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;lat: &lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;lon: &lt;/span&gt;&lt;span class="n"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reason: :invalid_response&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="n"&gt;build_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weather_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;"/api/weather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reason: :invalid_response&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="n"&gt;build_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:http_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:api_key&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;temperature: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;humidity: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"humidity"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;KeyError&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reason: :invalid_response&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much more can be said about implementing client classes but the above example is enough to proceed to the next step: integrating the client with the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Integrate Client into App
&lt;/h2&gt;

&lt;p&gt;For simplicity, we assume there's one client instance in the whole app. This assumption is safe when the class is stateless but might need to be revisited in more complex scenarios. It doesn't affect the gist of the method, though.&lt;/p&gt;

&lt;p&gt;The client must be instantiated early in the boot process and be accessible from various places throughout the code. Ideally, it'd be possible to inject it as a dependency to each controller it but sadly it doesn't seem to be possible (or at least it's non-obvious). We're left with using some kind of global state.&lt;/p&gt;

&lt;p&gt;I used to rely on module attributes (like &lt;code&gt;WeatherService.client&lt;/code&gt;) but have changed to using actual global variables, e.g. &lt;code&gt;$weather_service&lt;/code&gt;. I find it much cleaner than a de facto global masquerading as a method call.&lt;/p&gt;

&lt;p&gt;The best place to instantiate the client is a Rails initializer. The weather service client can be instantiated in &lt;code&gt;config/initializers/weather_service.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="vg"&gt;$weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WhatWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;http_client: &lt;/span&gt;&lt;span class="no"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;api_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WHATEVER_WEATHER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now use &lt;code&gt;$weather_service&lt;/code&gt; whenever we need to make an API call. That's not the end of the story, though, as there are benefits to be gained in testing and development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Testing
&lt;/h2&gt;

&lt;p&gt;The test suite shouldn't depend on any third-party APIs as this would make it flaky and dependent on Internet access. Fortunately, with a global client instance it's &lt;strong&gt;trivial&lt;/strong&gt; to mock it.&lt;/p&gt;

&lt;p&gt;The first step is mocking the client before each test case and cleaning it up afterwards. We're using MiniTest and &lt;code&gt;Minitest::Mock&lt;/code&gt; below but the same idea applies to other test and mocking frameworks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class="n"&gt;setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@old_weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$weather_service&lt;/span&gt;
    &lt;span class="vg"&gt;$weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Minitest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;teardown&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vg"&gt;$weather_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;
    &lt;span class="vg"&gt;$weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@old_weather_service&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, each test case can declare weather service methods it needs along with their desired return values. For example, the code below will make the weather service return a predefined value for NYC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vg"&gt;$weather_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;:weather_by_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;temperature: &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;humidity: &lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"New York City"&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;Creating test cases for edge cases or errors is a matter of making the mock service return an appropriate value or an exception.&lt;/p&gt;

&lt;p&gt;Let's turn our attention to the benefit this approach can yield in development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Development
&lt;/h2&gt;

&lt;p&gt;Interacting with the app is an essential component of the development feedback loop. However, hitting a real API in development can make it difficult to explore edge cases (unlikely return values or errors). For example, we can't force WhatWeather to lie about the temperature in New York. That's another situation where our approach comes in handy.&lt;/p&gt;

&lt;p&gt;The solution is a development-specific client class returning stubbed responses. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weather_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"New York City"&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;temperature: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;humidity: &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="no"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;temperature: &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;humidity: &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other edge cases, including errors and exceptions, can be implemented in the same way.&lt;/p&gt;

&lt;p&gt;Switching between the real and stub API client can be a matter of configuration. For example, the client initializer file can be modified to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WHATWEATHER_CLIENT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
    &lt;span class="vg"&gt;$weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WhatWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;http_client: &lt;/span&gt;&lt;span class="no"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;api_key: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"WHATEVER_WEATHER_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt;
    &lt;span class="vg"&gt;$weather_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;WeatherService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StubWhatWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the client can be chosen by setting &lt;code&gt;WHATWEATHER_CLIENT&lt;/code&gt; to &lt;code&gt;"production"&lt;/code&gt; or &lt;code&gt;"development"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Steps
&lt;/h2&gt;

&lt;p&gt;We outlined a high-level approach but have skipped many implementation details that need to be addressed in a real production system. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establishing client configuration patterns. What variables are needed? How are we going to configure stub and production clients?&lt;/li&gt;
&lt;li&gt;Making it easy to switch between stub and production clients.&lt;/li&gt;
&lt;li&gt;Client input validation, response parsing and error reporting.&lt;/li&gt;
&lt;li&gt;Creating a set of specialized client tests that make use of the real API to ensure the client is working correctly.&lt;/li&gt;
&lt;li&gt;Maintaining multiple clients (or service objects in general) and their relationships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These decisions can only be made after taking a specific project's circumstances into account. We also need to be aware of the drawbacks of this approach in order to manage the risks and challenges they introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global state makes it possible to use the client from anywhere without an explicit dependency. It's easy to use the client from a model which, in general, should be discouraged.&lt;/li&gt;
&lt;li&gt;Complex integrations, like Stripe, may require &lt;strong&gt;a lot&lt;/strong&gt; of custom result parsing and conversion code. It may make sense to build some kind of a DSL to accomplish that, increasing implementation complexity, &lt;em&gt;or&lt;/em&gt; let it flow through the client to the rest of the app, increasing coupling. It can be a tough call.&lt;/li&gt;
&lt;li&gt;Mocking the API can result in a situation where the test suite is green but production is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Calling third-party APIs is always going to be more difficult than ordinary method calls. However, hiding the API behind a well-defined interface that's entirely within our control makes it much easier to manage the complexity resulting from the integration. Its bulk will live in the client class and won't spill over to the rest of the code base making testing and stubbing easy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you enjoyed reading the article then I recommend you take a look at &lt;a href="https://www.gregnavis.com/articles.html"&gt;other articles on my website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Architecture No One Needs</title>
      <dc:creator>Greg Navis</dc:creator>
      <pubDate>Fri, 11 Jan 2019 17:40:36 +0000</pubDate>
      <link>https://dev.to/gregnavis/the-architecture-no-one-needs-2jf0</link>
      <guid>https://dev.to/gregnavis/the-architecture-no-one-needs-2jf0</guid>
      <description>&lt;p&gt;Single-page apps are all the rage nowadays. Many praise their vague technical benefits while ignoring  tremendous development costs.&lt;/p&gt;

&lt;p&gt;In this article, we'll discuss why a single-page app is almost always worse than a multi-page app and briefly cover alternatives that can yield similar benefits without the costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business of Software
&lt;/h2&gt;

&lt;p&gt;Every business has two sides: revenues and costs. Whether an SPA is a good investment compared to alternatives depends on how it affects the bottom line.&lt;/p&gt;

&lt;p&gt;Revenue depends on value delivered to users which in turn depends primarily on the feature set. Architectural choices don't provide value to users per se. The promise of SPAs is better user experience which may translate into higher revenue. This increase must be compared with the corresponding increase in costs to assess whether the investment is worthwhile.&lt;/p&gt;

&lt;p&gt;The article attempts to prove the costs of an SPA are tremendous compared to an MPA mainly because of greater incidental complexity. However, many companies blindly assume user experience is improved enough to justify the extra expense. Others base their choice on some vague sense of engineering purity without considering business factors.&lt;/p&gt;

&lt;p&gt;There are two key takeaways from the article:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not consider the SPA architecture unless there's evidence user experience is the number one problem of your app and even in that case consider alternatives. For example, if you need to make the app snappier then you may be able to reap the bulk of the benefit by tuning your database, caching, using a CDN, etc.&lt;/li&gt;
&lt;li&gt;An MPA is a competitive advantage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's take a look at the cost side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Price of Single-Page Apps
&lt;/h2&gt;

&lt;p&gt;Architectural choices affect different aspects of development in different ways. That's why I compiled a list of areas negatively affected by the SPA architecture. You can use it to assess the impact an SPA has or would have on your project.&lt;/p&gt;

&lt;p&gt;Let's emphasize a clear pattern: &lt;strong&gt;an SPA negatively affects most items on the list and requires extra work to regain capabilities present in MPAs by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's the list starting with the most expensive items:&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;Statefulness&lt;/dt&gt;
  &lt;dd&gt;
    I think this is a very underappreciated aspect of SPAs. Stateful software is always more difficult to work with than stateless. The frontend state is added on top of the already-existing backed state. This requires more development time, increases the risk of bugs, and makes troubleshooting more difficult.
  &lt;/dd&gt;

  &lt;dt&gt;Testing&lt;/dt&gt;
  &lt;dd&gt;
    The stateful nature of the frontend greatly increases the number of test cases we need to write and maintain. Additionally, the test setup is more complicated because we need to make the backend and frontend talk to each other.
  &lt;/dd&gt;

  &lt;dt&gt;Performance&lt;/dt&gt;
  &lt;dd&gt;
    It's frequently claimed SPAs offer better performance but it's more complicated than commonly thought. An API-only backend renders and sends less data than an MPA but the network latency is still there and the app won't be faster than that. We could work around the issue by implementing optimistic updates but this greatly increases the number of failure modes and makes the app more complex.
  &lt;/dd&gt;

  &lt;dt&gt;Slow First-Time Load&lt;/dt&gt;
  &lt;dd&gt;
    This is a well-known problem that isn't fully understood. The usual claim is that after the browser caches the asset bundle everything will be snappy. The implicit assumption is we've stopped development and don't update the bundle. If we do then users may experience quite a lot of first-time loads in a single day.
  &lt;/dd&gt;

  &lt;dt&gt;Authentication&lt;/dt&gt;
  &lt;dd&gt;
    This is optional for an SPA but it seems JWTs are a frequent choice for authentication. The claimed benefit is statelessness. It's all true but has a serious downside: we can't invalidate sessions unless we identify them on the backend which makes the system stateful. I think we always should be able to invalidate sessions. Therefore, because we need server-side state, we can simply use bearer tokens. They're simpler to understand, implement and troubleshoot.
  &lt;/dd&gt;

  &lt;dt&gt;Session Information&lt;/dt&gt;
  &lt;dd&gt;
    Again, this is optional but SPAs often use local storage instead of cookies. Its tremendous downside is lack of a mechanism similar to HTTP-only cookies. Given web apps often include scripts from third-party domains and CDNs a successful attack against them can leak session IDs and other secrets.
  &lt;/dd&gt;

  &lt;dt&gt;State Updates&lt;/dt&gt;
  &lt;dd&gt;
    Let's illustrate this with an example: we're building an e-commerce site that has a list of categories. We need to update the list from time to time. In an MPA, the list is updated on every page load. That's not the case in an SPA though. We need to think about an algorithm and implement it. It's not rocket science but it's busywork that users don't care about.
  &lt;/dd&gt;

  &lt;dt&gt;Error Handling&lt;/dt&gt;
  &lt;dd&gt;
    An MPA renders a 500 page upon error and that's it. However, an SPA needs to detect errors in the client code and then update the user interface accordingly. Again, busywork required to regain what MPAs offer out of the box.
  &lt;/dd&gt;

  &lt;dt&gt;Server-side Rendering&lt;/dt&gt;
  &lt;dd&gt;
    We may need server-side rendering so that users are able to discover the app. This is yet another area where you need to do work to match the capabilities of an MPA.
  &lt;/dd&gt;

  &lt;dt&gt;Protocols and Serialization&lt;/dt&gt;
  &lt;dd&gt;
    In an MPA, we can simply pass models to views and render attributes we need. This isn't the case in an SPA. We need to define data formats and implement serialization. Naturally, there are tools that can help but it's extra work and dependencies whose only effect is regaining the convenience of an MPA.
  &lt;/dd&gt;

  &lt;dt&gt;Tooling&lt;/dt&gt;
  &lt;dd&gt;
    Our build system may become more complicated because of the additional tooling and dependencies required to build an SPA.
  &lt;/dd&gt;

  &lt;dt&gt;Shared Metadata&lt;/dt&gt;
  &lt;dd&gt;
    We may need to share data between the frontend and the backend. For example, if the SPA consumes a REST API then we would like routing information to be derived from the same source. Again, this is unnecessary in an MPA.
  &lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;If you look at the SPA architecture from a business perspective then your costs will go up but you won't see more money coming in because &lt;strong&gt;users don't care about technical choices&lt;/strong&gt;. The result is a negative return on investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do Instead
&lt;/h2&gt;

&lt;p&gt;My advice is simple: if it hurts then stop doing it. Even better: don't start doing it in the first place.&lt;/p&gt;

&lt;p&gt;A multi-page app is much simpler to implement and has many advantages that can only be replicated in an SPA at a huge cost. Naturally, more complicated components are sometimes unavoidable but there are more sensible approaches.&lt;/p&gt;

&lt;p&gt;First, start using &lt;a href="https://github.com/turbolinks/turbolinks"&gt;Turbolinks&lt;/a&gt;. It'll make the app feel snappier without injecting a ton of incidental complexity. It's often associated with Ruby on Rails but can be easily used independently with other technologies.&lt;/p&gt;

&lt;p&gt;Second, use &lt;a href="https://stimulusjs.org/"&gt;Stimulus.js&lt;/a&gt; for simpler components. It's a relatively new development but I had a chance to implement a dozen Stimulus controllers and the experience was great.&lt;/p&gt;

&lt;p&gt;Third, if you're implementing a very complicated component then you can use React &lt;strong&gt;just for that component&lt;/strong&gt;. For instance, if you're building a chat box then there's really no need to implement your login page in React. The same applies to Vue.js and the rest of the pack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Single-page apps are &lt;strong&gt;much&lt;/strong&gt; more expensive to build than multi-page apps. In most cases, there is no business reason to choose this architecture. The problems it's trying to solve can be address in simpler ways without excessive costs and complexity. There &lt;em&gt;are&lt;/em&gt; cases where an SPA makes sense but this is a topic for another article.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
