Getaround EngineeringThis is the tech blog of getaround.com
https://getaround.tech/
Tue, 19 Aug 2025 10:32:32 +0000Tue, 19 Aug 2025 10:32:32 +0000Jekyll v4.4.1JPEG and EXIF Data Manipulation in JavascriptCédric Patchane<p>The <strong>E</strong>xchangeable <strong>I</strong>mage <strong>F</strong>ile <strong>F</strong>ormat (EXIF) is a standard that specifies formats for images and sounds. It stores technical details through metadata, data that describes other data, such as the camera make and model and the date and time the image was taken.</p>
<p>Initially, EXIF was used for two image formats, JPEG and TIFF. But today, other file formats such as PNG, WEBP, or HEIC also support EXIF for metadata.</p>
<p>This article will focus on the JPEG format. In the first part, we will explore its structure before seeing how to read and update associated metadata through Javascript in a browser environment.</p>
<p>Before moving on, it is essential to review some key concepts:</p>
<p><strong>📌 What is the <code class="language-plaintext highlighter-rouge">0x</code> notation?</strong>
<code class="language-plaintext highlighter-rouge">0x</code> indicates that the following number is in hexadecimal format, which uses a base-16 number system (as opposed to the base-10 decimal system). This notation is case-insensitive, meaning that <code class="language-plaintext highlighter-rouge">0XFF</code> and <code class="language-plaintext highlighter-rouge">0xff</code> are exactly the same.</p>
<p><strong>📌 What is a bit or a byte?</strong>
In computer science, a bit is the smallest and the most basic unit of information. It is a binary digit (base 2) representing 0 or 1. A byte (or octet) is a group of eight bits. Since there are 256 possible combinations of 8 bits, a byte can be expressed as a hexadecimal number. For example:</p>
<ul>
<li>The byte <code class="language-plaintext highlighter-rouge">0x00</code> represents <code class="language-plaintext highlighter-rouge">0</code> in decimal and corresponds to <code class="language-plaintext highlighter-rouge">0000 0000</code> in binary, which is the minimum 8-bit value.</li>
<li>The byte <code class="language-plaintext highlighter-rouge">0xD8</code> represents <code class="language-plaintext highlighter-rouge">216</code> and corresponds to <code class="language-plaintext highlighter-rouge">1101 1000</code>.</li>
<li>The byte <code class="language-plaintext highlighter-rouge">0xFF</code> represents <code class="language-plaintext highlighter-rouge">255</code> and corresponds to <code class="language-plaintext highlighter-rouge">1111 1111</code>, which is the maximum 8-bit value.</li>
</ul>
<p>For multiple-byte words, the hex numbers are just combined: <code class="language-plaintext highlighter-rouge">0xFFD8</code> is a two-byte word, and <code class="language-plaintext highlighter-rouge">0x45786966</code> is a four-byte word.</p>
<p><strong>📌 What is Endianness?</strong>
This is how a set of bytes is stored in memory. In big-endian, the most significant byte (leftmost) comes first, while in little-endian, the least significant byte (rightmost) comes first.</p>
<p>For example, let’s consider the two-byte word <code class="language-plaintext highlighter-rouge">0x0124</code>. In a big-endian system, it will be written as <code class="language-plaintext highlighter-rouge">01 24</code>, whereas in a little-endian one, it will be written as <code class="language-plaintext highlighter-rouge">24 01</code>. Knowing whether an image has been written on a big or little-endian device is essential to read its data correctly.</p>
<h2 id="the-exif-segment-in-the-jpeg-structure">The EXIF segment in the JPEG structure</h2>
<h3 id="segment-delimitations">Segment delimitations</h3>
<p>The structure of a JPEG image is divided into parts marked by two-byte markers, always starting with a <code class="language-plaintext highlighter-rouge">0xFF</code> byte. Below is a list of key markers found in the pages 20/21 of the <a href="https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf">JPEG compression specification</a>:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">0xFFD8</code>: <strong>SOI</strong> (Start of Image); indicates the beginning of the image structure.</li>
<li><code class="language-plaintext highlighter-rouge">0xFFE*n*</code>: <strong>APPn</strong> (Application-related tags); following the <strong>SOI</strong> marker, with <code class="language-plaintext highlighter-rouge">n</code> between <code class="language-plaintext highlighter-rouge">0</code> and <code class="language-plaintext highlighter-rouge">F</code> (<a href="https://exiftool.org/TagNames/JPEG.html">full list</a>). For example, <strong>APP11</strong> (or <code class="language-plaintext highlighter-rouge">0xFFEB</code>) is for HDR data, <strong>APP13</strong> (or <code class="language-plaintext highlighter-rouge">0xFFED</code>) for Photoshop and <strong>APP1</strong> (or <code class="language-plaintext highlighter-rouge">0xFFE1</code>) for EXIF.</li>
<li><code class="language-plaintext highlighter-rouge">0xFFDA</code>: <strong>SOS</strong> (Start of Scan); indicates the beginning of the image-related data.</li>
<li><code class="language-plaintext highlighter-rouge">0xFFD9</code>: <strong>EOI</strong> (End of Image); indicates the end of the image.</li>
</ul>
<p>The first four file bytes, here <code class="language-plaintext highlighter-rouge">FF D8 FF E0</code> for JPEG, are also known as <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">magic numbers</a> and are used by software to identify the file type.</p>
<h3 id="segment-size">Segment size</h3>
<p>The size of a segment can be determined by reading the two bytes following its marker. For example, if the segment starts with <code class="language-plaintext highlighter-rouge">FFE1 0124 XXXXXXX</code>, then the APP1 segment size is 292 bytes, with <code class="language-plaintext highlighter-rouge">0124</code> being the size’s hexadecimal representation.</p>
<h3 id="ifd-image-file-directory">IFD: Image File Directory</h3>
<p>Data in JPEG structure is grouped into directories called IFDs. For example, <code class="language-plaintext highlighter-rouge">IDF0</code> is located in the <code class="language-plaintext highlighter-rouge">APP1</code> segment, and <code class="language-plaintext highlighter-rouge">IFDExif</code> is a sub-IFD of <code class="language-plaintext highlighter-rouge">IDF0</code>.</p>
<p>The IFD dataset includes a two-byte word indicating the number of tags, followed by the tags data and ending with the four-byte offset of the next IFD (or 0 if none).</p>
<h3 id="ifd-tag">IFD Tag</h3>
<p>A tag, like all EXIF tags, is a twelve-byte length sequence made up of:</p>
<ul>
<li>Tag ID (bytes 0-1): A two-byte word identifying the tag</li>
<li>Tag type (bytes 2-3): A two-byte word indicating the type. For example, a value of <code class="language-plaintext highlighter-rouge">1</code> for a <code class="language-plaintext highlighter-rouge">BYTE</code> (one-byte integer), <code class="language-plaintext highlighter-rouge">3</code> for a <code class="language-plaintext highlighter-rouge">SHORT</code> (two-byte integer), or <code class="language-plaintext highlighter-rouge">4</code> for a <code class="language-plaintext highlighter-rouge">LONG</code> (four-byte integer). For further details, see the pages 25 and 26 of the <a href="https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf">JPEG compression specification</a>.</li>
<li>Tag count (bytes 4-7): A four-byte word indicating the number of values (usually 1)</li>
<li>Tag value or value offset (bytes 8-11): For <code class="language-plaintext highlighter-rouge">SHORT</code> values, two bytes are read; for <code class="language-plaintext highlighter-rouge">LONG</code> values, four bytes are read. If the value is longer than four bytes (e.g., <code class="language-plaintext highlighter-rouge">RATIONAL</code> type), these four bytes store the offset needed to reach the actual value.</li>
</ul>
<figure>
<img alt="IFD tag example: the ExifImageWidth tag" src="/assets/posts/2023-09-11-exif-data-manipulation-javascript/exif_ifd_tag.png" />
<figcaption>
IFD tag example: the ExifImageWidth tag
</figcaption>
</figure>
<h2 id="locate-the-exif-part">Locate the EXIF part</h2>
<h3 id="from-image-to-bytes">From image to bytes</h3>
<p>Time to code! The <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader"><code class="language-plaintext highlighter-rouge">FileReader</code></a> API is here used to read the image as a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer"><code class="language-plaintext highlighter-rouge">buffer</code></a>. Then it is transformed into a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView"><code class="language-plaintext highlighter-rouge">DataView</code></a> for easier byte manipulation.</p>
<p>The next step is to examine the start of the JPEG structure, which should be the <strong>SOI</strong> marker:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// Where the final image with updated metadata will be stored</span>
<span class="kd">let</span> <span class="nx">finalImageBlob</span> <span class="o">=</span> <span class="kc">null</span>
<span class="kd">const</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileReader</span><span class="p">()</span>
<span class="nx">reader</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">load</span><span class="dl">"</span><span class="p">,</span> <span class="p">({</span> <span class="nx">target</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">target</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">no blob found</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">{</span> <span class="na">result</span><span class="p">:</span> <span class="nx">buffer</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">target</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">buffer</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">buffer</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not a valid JPEG</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">view</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataView</span><span class="p">(</span><span class="nx">buffer</span><span class="p">)</span>
<span class="kd">let</span> <span class="nx">offset</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">const</span> <span class="nx">SOI</span> <span class="o">=</span> <span class="mh">0xFFD8</span>
<span class="k">if </span><span class="p">(</span><span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="o">!==</span> <span class="nx">SOI</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not a valid JPEG</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// Here will happen the image metadata manipulation</span>
<span class="p">})</span>
<span class="c1">// Image given as a Blob, but readAsArrayBuffer can also take a File</span>
<span class="nx">reader</span><span class="p">.</span><span class="nf">readAsArrayBuffer</span><span class="p">(</span><span class="nx">imageBlob</span><span class="p">)</span></code></pre></figure>
<p><strong>Note:</strong> The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16"><code class="language-plaintext highlighter-rouge">getUint16</code></a> function in Javascript is used to read two bytes (2*8 = 16bits), and there is a similar function for four bytes, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32"><code class="language-plaintext highlighter-rouge">getUint32</code></a>.</p>
<h3 id="segments-reading">Segments reading</h3>
<p>From here can begin the loop through the image data to locate the EXIF section. The EXIF segment uses the <code class="language-plaintext highlighter-rouge">APP1</code> marker followed by a special six-byte ASCII code <code class="language-plaintext highlighter-rouge">Exif</code> (<code class="language-plaintext highlighter-rouge">0x457869660000</code>) immediately following the <code class="language-plaintext highlighter-rouge">APP1</code> size data.</p>
<p>Reaching <strong>SOS</strong> marker is reached means reaching the start of the image data so the end of the metadata section.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">SOS</span> <span class="o">=</span> <span class="mh">0xFFDA</span>
<span class="kd">const</span> <span class="nx">APP1</span> <span class="o">=</span> <span class="mh">0xFFE1</span>
<span class="c1">// Skip the last two bytes 0000 and just read the four first bytes</span>
<span class="kd">const</span> <span class="nx">EXIF</span> <span class="o">=</span> <span class="mh">0x45786966</span>
<span class="kd">let</span> <span class="nx">marker</span> <span class="o">=</span> <span class="kc">null</span>
<span class="c1">// The first two bytes (offset 0-1) was the SOI marker</span>
<span class="nx">offset</span> <span class="o">+=</span> <span class="mi">2</span>
<span class="k">while </span><span class="p">(</span><span class="nx">marker</span> <span class="o">!==</span> <span class="nx">SOS</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">marker</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">size</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">if </span><span class="p">(</span><span class="nx">marker</span> <span class="o">===</span> <span class="nx">APP1</span> <span class="o">&&</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint32</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">===</span> <span class="nx">EXIF</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// EXIF segment found!</span>
<span class="c1">// Following code will be here</span>
<span class="p">}</span>
<span class="c1">// Skip the entire segment (header of 2 bytes + size of the segment)</span>
<span class="nx">offset</span> <span class="o">+=</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">size</span>
<span class="p">}</span></code></pre></figure>
<p>The last thing to do here is to determine which is the endianness used to encode that image. In the JPEG structure, the endianness is provided thanks to the two-bytes word following the <code class="language-plaintext highlighter-rouge">Exif</code> special word. If the word is <code class="language-plaintext highlighter-rouge">0x4949</code>, it means it’s little endian, otherwise it is <code class="language-plaintext highlighter-rouge">0x4D4D</code> for big endian. This endianness data must be followed by the two bytes <code class="language-plaintext highlighter-rouge">0x002A</code> (42 in decimal).</p>
<p><strong>Note:</strong> From now on, always provide the endianness to the <code class="language-plaintext highlighter-rouge">getUint16</code>/<code class="language-plaintext highlighter-rouge">getUint32</code> functions to correctly read the bytes.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">LITTLE_ENDIAN</span> <span class="o">=</span> <span class="mh">0x4949</span>
<span class="kd">const</span> <span class="nx">BIG_ENDIAN</span> <span class="o">=</span> <span class="mh">0x4d4d</span>
<span class="c1">// The APP1 here is at the very beginning of the file</span>
<span class="c1">// So at this point offset = 2,</span>
<span class="c1">// + 10 to skip to the bytes after the Exif word</span>
<span class="nx">offset</span> <span class="o">+=</span> <span class="mi">10</span>
<span class="kd">let</span> <span class="nx">isLittleEndian</span> <span class="o">=</span> <span class="kc">null</span>
<span class="k">if </span><span class="p">(</span><span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="o">===</span> <span class="nx">LITTLE_ENDIAN</span><span class="p">)</span> <span class="nx">isLittleEndian</span> <span class="o">=</span> <span class="kc">true</span>
<span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span><span class="p">)</span> <span class="o">===</span> <span class="nx">BIG_ENDIAN</span><span class="p">)</span> <span class="nx">isLittleEndian</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">else</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">invalid endian</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// From now, the endianness must be specify each time bytes are read</span>
<span class="c1">// The 42 word</span>
<span class="k">if </span><span class="p">(</span><span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span> <span class="o">!==</span> <span class="mh">0x2a</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">invalid endian</span><span class="dl">"</span><span class="p">)</span></code></pre></figure>
<p>If APP1 appears at the very beginning of the image structure (which is usually the case), then the structure should be as follows:</p>
<figure>
<img alt="JPEG starting structure" src="/assets/posts/2023-09-11-exif-data-manipulation-javascript/exif_markers.png" />
<figcaption>
JPEG starting structure
</figcaption>
</figure>
<h2 id="read-and-replace-exif-tags">Read and replace EXIF tags</h2>
<p>All the necessary information are now known to search for the EXIF tags:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Orientation</code>, located in the IFD <code class="language-plaintext highlighter-rouge">IFD0</code></li>
<li><code class="language-plaintext highlighter-rouge">ExifImageWidth</code> or <code class="language-plaintext highlighter-rouge">PixelXDimension</code> tag, located in the IFD <code class="language-plaintext highlighter-rouge">IFDExif</code>, provided by the <code class="language-plaintext highlighter-rouge">ExifOffset</code> tag of <code class="language-plaintext highlighter-rouge">IFD0</code></li>
<li><code class="language-plaintext highlighter-rouge">ExifImageHeight</code> or <code class="language-plaintext highlighter-rouge">PixelYDimension</code> tag, also located in <code class="language-plaintext highlighter-rouge">IFDExif</code></li>
</ul>
<h3 id="ifd0">IFD0</h3>
<p>To locate the <code class="language-plaintext highlighter-rouge">IFD0</code>, its offset is given by the 4-byte word immediately after the endianness <code class="language-plaintext highlighter-rouge">42</code> number.</p>
<p>This sequence that includes the endianness two-byte word, <code class="language-plaintext highlighter-rouge">42</code>, and the <code class="language-plaintext highlighter-rouge">IFD0</code> offset four-byte word is commonly referred to as the “TIFF (<strong>T</strong>agged <strong>I</strong>mage <strong>F</strong>ile <strong>F</strong>ormat) Header”:</p>
<figure>
<img alt="The TIFF header" src="/assets/posts/2023-09-11-exif-data-manipulation-javascript/exif_TIFF_header.png" />
<figcaption>
The TIFF header
</figcaption>
</figure>
<p>At this point, there are two tags that need to be found through the <code class="language-plaintext highlighter-rouge">IFD0</code> data:</p>
<ul>
<li>The <a href="https://www.awaresystems.be/imaging/tiff/tifftags/orientation.html"><code class="language-plaintext highlighter-rouge">Orientation</code></a> tag (hex <code class="language-plaintext highlighter-rouge">0x0112</code>) which is a <code class="language-plaintext highlighter-rouge">SHORT</code> value that must be replaced by <code class="language-plaintext highlighter-rouge">1</code></li>
<li>The EXIF specific IFD offset provided by the <a href="https://www.awaresystems.be/imaging/tiff/tifftags/exififd.html"><code class="language-plaintext highlighter-rouge">ExifOffset</code></a> tag (hex <code class="language-plaintext highlighter-rouge">0x8769</code>) which is a <code class="language-plaintext highlighter-rouge">LONG</code> value allowing to find the EXIF IFD tags</li>
</ul>
<p>As mentioned earlier, the first two-byte word of the IFD indicates the number of tags in the IFD. Since each tag is 12 bytes long, multiplying the number of tags by 12 gives the size of all the IFD tags, allowing for looping through them.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">TAG_ID_EXIF_SUB_IFD_POINTER</span> <span class="o">=</span> <span class="mh">0x8769</span>
<span class="kd">const</span> <span class="nx">TAG_ID_ORIENTATION</span> <span class="o">=</span> <span class="mh">0x0112</span>
<span class="kd">const</span> <span class="nx">newOrientationValue</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1">// Here offset = 12</span>
<span class="c1">// IFD0 offset given by the 4 bytes after 42</span>
<span class="kd">const</span> <span class="nx">ifd0Offset</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint32</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="mi">4</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">ifd0TagsCount</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">ifd0Offset</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="c1">// IFD0 ends after the two-byte tags count word + all the tags</span>
<span class="kd">const</span> <span class="nx">endOfIFD0TagsOffset</span> <span class="o">=</span> <span class="nx">offset</span> <span class="o">+</span> <span class="nx">ifd0Offset</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">+</span> <span class="nx">ifd0TagsCount</span> <span class="o">*</span> <span class="mi">12</span>
<span class="k">for </span><span class="p">(</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">offset</span> <span class="o">+</span> <span class="nx">ifd0Offset</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">i</span> <span class="o"><</span> <span class="nx">endOfIFD0TagsOffset</span><span class="p">;</span>
<span class="nx">i</span> <span class="o">+=</span> <span class="mi">12</span>
<span class="p">)</span> <span class="p">{</span>
<span class="c1">// First 2 bytes = tag type</span>
<span class="kd">const</span> <span class="nx">tagId</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="c1">// If Orientation tag</span>
<span class="k">if </span><span class="p">(</span><span class="nx">tagId</span> <span class="o">===</span> <span class="nx">TAG_ID_ORIENTATION</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Skipping the 2 bytes tag type and 4 bytes tag count</span>
<span class="c1">// Type is SHORT, so 2 bytes to write</span>
<span class="nx">view</span><span class="p">.</span><span class="nf">setUint16</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">newOrientationValue</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// If ExifIFD offset tag</span>
<span class="k">if </span><span class="p">(</span><span class="nx">tagId</span> <span class="o">===</span> <span class="nx">TAG_ID_EXIF_SUB_IFD_POINTER</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Type is LONG, so 4 bytes to read</span>
<span class="nx">exifSubIfdOffset</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint32</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p><strong>Note:</strong> Following the same logic as for reading, the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16"><code class="language-plaintext highlighter-rouge">setUint16</code></a>/<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32"><code class="language-plaintext highlighter-rouge">setUint32</code></a> functions are used to respectively write two or four bytes.</p>
<h3 id="exif-sub-ifd">EXIF Sub-IFD</h3>
<p>Once the offset of the EXIF sub-IFD is found, a new loop must be executed through that IFD’s data to find the remaining height and width tags.</p>
<p>Here is information about the two tags that need to be replaced:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ExifImageWidth</code> or <code class="language-plaintext highlighter-rouge">PixelXDimension</code> tag (hex <code class="language-plaintext highlighter-rouge">0xa002</code>), a <code class="language-plaintext highlighter-rouge">LONG</code> value that must be replaced by <code class="language-plaintext highlighter-rouge">1920</code></li>
<li><code class="language-plaintext highlighter-rouge">ExifImageHeight</code> or <code class="language-plaintext highlighter-rouge">PixelYDimension</code> tag (hex <code class="language-plaintext highlighter-rouge">0xa003</code>), a <code class="language-plaintext highlighter-rouge">LONG</code> value that must be replaced by <code class="language-plaintext highlighter-rouge">1080</code></li>
</ul>
<p>As a reminder of what was previously stated, the IFD tag is composed of 2 bytes for the type, 4 for the count, and 4 for the value.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">TAG_ID_EXIF_IMAGE_WIDTH</span> <span class="o">=</span> <span class="mh">0xa002</span>
<span class="kd">const</span> <span class="nx">TAG_ID_EXIF_IMAGE_HEIGHT</span> <span class="o">=</span> <span class="mh">0xa003</span>
<span class="kd">const</span> <span class="nx">newWidthValue</span> <span class="o">=</span> <span class="mi">1920</span>
<span class="kd">const</span> <span class="nx">newHeightValue</span> <span class="o">=</span> <span class="mi">1080</span>
<span class="k">if </span><span class="p">(</span><span class="nx">exifSubIfdOffset</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">exifSubIfdTagsCount</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">offset</span> <span class="o">+</span> <span class="nx">exifSubIfdOffset</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="c1">// This IFD also ends after the two-byte tags count word + all the tags</span>
<span class="kd">const</span> <span class="nx">endOfExifSubIfdTagsOffset</span> <span class="o">=</span>
<span class="nx">offset</span> <span class="o">+</span>
<span class="nx">exifSubIfdOffset</span> <span class="o">+</span>
<span class="mi">2</span> <span class="o">+</span>
<span class="nx">exifSubIfdTagsCount</span> <span class="o">*</span> <span class="mi">12</span>
<span class="k">for </span><span class="p">(</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">offset</span> <span class="o">+</span> <span class="nx">exifSubIfdOffset</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="nx">i</span> <span class="o"><</span> <span class="nx">endOfExifSubIfdTagsOffset</span><span class="p">;</span>
<span class="nx">i</span> <span class="o">+=</span> <span class="mi">12</span>
<span class="p">)</span> <span class="p">{</span>
<span class="c1">// First 2 bytes = tag type</span>
<span class="kd">const</span> <span class="nx">tagId</span> <span class="o">=</span> <span class="nx">view</span><span class="p">.</span><span class="nf">getUint16</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="c1">// Skipping the 2 bytes tag type and 4 bytes tag count</span>
<span class="c1">// The two types are LONG, so 4 bytes to write</span>
<span class="k">if </span><span class="p">(</span><span class="nx">tagId</span> <span class="o">===</span> <span class="nx">TAG_ID_EXIF_IMAGE_WIDTH</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">view</span><span class="p">.</span><span class="nf">setUint32</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">newWidthValue</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">tagId</span> <span class="o">===</span> <span class="nx">TAG_ID_EXIF_IMAGE_HEIGHT</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">view</span><span class="p">.</span><span class="nf">setUint32</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">8</span><span class="p">,</span> <span class="nx">newHeightValue</span><span class="p">,</span> <span class="nx">isLittleEndian</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="write-back-the-image">Write back the image</h3>
<p>Getting the final image is as simple as building a new Blob from the updated buffer data:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">finalImageBlob</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Blob</span><span class="p">(</span><span class="nx">view</span><span class="p">)</span></code></pre></figure>
<p>In the end, the updated blob can be converted to a file or downloaded, depending on the application’s needs.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This article covers the basics of reading and updating tags, but you can expand the code by adding more tags. All the hex codes for the tags can be found on <a href="https://exiftool.org/TagNames/EXIF.html">exiftools.org</a> or <a href="https://www.awaresystems.be/imaging/tiff/tifftags.html">this tags reference site</a>.</p>
<p>It’s worth noting that there are existing libraries like <a href="https://github.com/exif-js/exif-js/issues/266#issuecomment-1333686536"><code class="language-plaintext highlighter-rouge">exif-js</code></a> or <a href="https://github.com/hMatoba/piexifjs/issues/78"><code class="language-plaintext highlighter-rouge">piexifjs</code></a> for manipulating EXIF data, but they may be larger than what is needed here and seems not being actively maintained.</p>
<p>If you want to see the full code used to write this article, feel free to check out <a href="https://gist.github.com/CPatchane/bcd523298e64b1fa813cfae82b0f2b42">this gist</a>.</p>
<p>The <strong>E</strong>xchangeable <strong>I</strong>mage <strong>F</strong>ile <strong>F</strong>ormat (EXIF) is a standard that specifies formats for images and sounds. It stores technical details through metadata, data that describes other data, such as the camera make and model and the date and time the image was taken.</p>
Mon, 11 Sep 2023 00:00:00 +0000
https://getaround.tech/exif-data-manipulation-javascript/
https://getaround.tech/exif-data-manipulation-javascript/Babel, JavaScript Transpiling And PolyfillsCédric Patchane<p><a href="https://babeljs.io/">BabelJS or Babel</a> is a prevalent tool in the JavaScript ecosystem. Most developers know that it is essential when developing using brand new JavaScript features. But how does this system work?</p>
<p>In this article, we’ll explain what Babel is doing under the hood to allow the use of state-of-the-art JavaScript features, and even TypeScript, without manually dealing with older browsers’ version compatibility.</p>
<h2 id="state-of-the-art-javascript-features">State-of-the-art JavaScript Features</h2>
<p>Let’s take a step back and look at the context.</p>
<p>JavaScript is a language that has evolved and is still evolving, especially in the last few years. It is based on a specification named <strong>ECMAScript</strong>, provided by <a href="https://www.ecma-international.org/memento/tc39.htm">TC39 (Technical Committee 39)</a>.</p>
<p><strong>Note:</strong> <em>It’s a common mistake to think that ECMAScript is the “new JavaScript” or a “standardized JavaScript”. ECMAScript is a specification for creating a scripting language when JavaScript is a scripting language. It may even happen that rare features are not following the ECMAScript specification in the experimental versions of some browsers.</em></p>
<p>To be included in the ECMAScript specification, a new feature <a href="https://tc39.es/process-document/">passes through a specific process with five phases</a>. So it could take some time before it is integrated into the specifications and then implemented in the browsers.</p>
<p>While we, developers, can’t wait to use new exciting features that improve our <del>life</del> code, most browsers don’t support them yet, so we can’t just deliver the code as we wrote it.</p>
<h2 id="babel-to-the-rescue">Babel To The Rescue</h2>
<p><a href="https://babeljs.io/">Babel</a> is a tool that allows you to write code in the latest (or even experimental) version of JavaScript. Because not all of the browsers currently support those hot features, it will transform the cutting-edge source code down to a code supported by older browsers. Babel’s primary purpose is about two things: <strong>JavaScript transpiling</strong> and <strong>polyfills handling</strong>.</p>
<p>Let’s take a look at the key points of interest regarding Babel:</p>
<ul>
<li>Its configuration is defined in a <code class="language-plaintext highlighter-rouge">babel.config.js</code> file located at the project’s root.</li>
<li>Babel uses <strong>plugins</strong> to be as modular as possible (example: <a href="https://babeljs.io/docs/en/babel-plugin-transform-arrow-functions">@babel/plugin-transform-arrow-functions</a>). Each plugin is often related to one functionality or a minimal scope of functionalities.</li>
<li>You can create a <strong>preset</strong> from a configuration to easily share it between projects, like <code class="language-plaintext highlighter-rouge">@babel/preset-env</code> to manage Babel plugins, <code class="language-plaintext highlighter-rouge">@babel/preset-typescript</code> for TypeScript usage, or <code class="language-plaintext highlighter-rouge">@babel/preset-react</code> for React applications.</li>
<li>By providing a list of targeting node environments or browsers (using <a href="https://github.com/browserslist/browserslist">browserslist</a> syntax) as an option to <a href="https://babeljs.io/docs/en/babel-preset-env"><code class="language-plaintext highlighter-rouge">@babel/preset-env</code></a>, it will automatically decide which plugins and polyfills (thanks to <a href="https://github.com/zloirock/core-js"><code class="language-plaintext highlighter-rouge">core-js</code></a>) have to be applied when processing the code.</li>
</ul>
<p>Below is a simple Babel configuration file that we will use as an example throughout this article:</p>
<figure>
<figcaption class="highlight-caption">babel.config.js</figcaption>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kr">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">presets</span><span class="p">:</span> <span class="p">[</span>
<span class="c1">// Babel plugin/preset options are passing using an array syntax</span>
<span class="p">[</span>
<span class="dl">"</span><span class="s2">@babel/preset-env</span><span class="dl">"</span><span class="p">,</span>
<span class="p">{</span>
<span class="na">targets</span><span class="p">:</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">> 0.25%</span><span class="dl">"</span><span class="p">,</span>
<span class="p">],</span>
<span class="c1">// Specify the version of core-js used, the last minor version is [email protected]</span>
<span class="na">corejs</span><span class="p">:</span> <span class="dl">"</span><span class="s2">3.26</span><span class="dl">"</span><span class="p">,</span>
<span class="c1">// Specify how to handle polyfills, see polyfills handling section below</span>
<span class="na">useBuiltIns</span><span class="p">:</span> <span class="dl">"</span><span class="s2">usage</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// "entry", "usage" or false by default</span>
<span class="p">}]</span>
<span class="dl">"</span><span class="s2">@babel/preset-react</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">@babel/preset-typescript</span><span class="dl">"</span>
<span class="p">],</span>
<span class="c1">// Specify some plugins enabled in any cases</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">dynamic-import-webpack</span><span class="dl">"</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">}</span></code></pre></figure>
</figure>
<p><strong>Note:</strong> <a href="https://github.com/zloirock/core-js"><code class="language-plaintext highlighter-rouge">core-js</code></a> is an NPM package that contains all polyfills for every possible ECMAScript feature. It must be installed for Babel to work with this latter. We’ll learn more about its usage in the polyfills handling section below.</p>
<h3 id="javascript-transpiling">JavaScript Transpiling</h3>
<p>A <strong>code transpiler</strong>, slightly different from a compiler, will read the source code written in one language (here, modern JavaScript code) to produce the equivalent code in another language (here, an older and more supported JavaScript code). Afterward, a compiler, like Webpack, is still needed to collect, optimize and build a project’s final output(s).</p>
<p><strong>Example:</strong> Let’s say we are writing a piece of code using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">arrow functions</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">template literals</a>:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">MyFunc</span> <span class="o">=</span> <span class="nx">arg</span> <span class="o">=></span> <span class="s2">`Using my argument: </span><span class="p">${</span><span class="nx">arg</span><span class="p">}</span><span class="s2">`</span></code></pre></figure>
<p>With the configuration from the previous section, Babel will output the code as follows:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">MyFunc</span> <span class="o">=</span> <span class="kd">function</span> <span class="nf">MyFunc</span><span class="p">(</span><span class="nx">arg</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="dl">"</span><span class="s2">Using my argument: </span><span class="dl">"</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="nx">arg</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>As a result, arrow functions are transformed to the basic function syntax supported by every browser. Same thing for <code class="language-plaintext highlighter-rouge">.concat()</code>, which is more widely supported by browsers than template literals. Finally, <code class="language-plaintext highlighter-rouge">const</code> is transformed to <code class="language-plaintext highlighter-rouge">var</code>, mainly for IE 11.</p>
<p>To do this transformation, Babel creates an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST (Abstract syntax tree)</a>, a tree representation of the code structure. Then it applies plugins that use this AST to transform and output the code. In this example, the plugin <code class="language-plaintext highlighter-rouge">@babel/plugin-transform-arrow-functions</code> was used to transform arrow functions, but there are a lot of other Babel plugins to handle any transformation.</p>
<p>The good news is that <strong>it’s not necessary to know all of them to transform the code correctly, thanks to the <code class="language-plaintext highlighter-rouge">@babel/preset-env</code> preset</strong>.</p>
<p>Indeed, this preset <a href="https://github.com/babel/babel/blob/master/packages/babel-compat-data/data/plugins.json">has a built-in list of plugins matching browser versions</a>. So, according to the browser versions list provided, it knows precisely which plugins need to be applied.</p>
<p>Now that we know how to transform the code, there is still something to tackle: how to add not supported yet implementations of very recent JavaScript functions.</p>
<h3 id="polyfills-handling">Polyfills Handling</h3>
<p>According to the <a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill">MDN</a>:</p>
<blockquote>
<p>A polyfill is a piece of code used to provide modern functionality on older browsers that do not natively support it.</p>
</blockquote>
<p>Let’s take an example. Here is a code using <code class="language-plaintext highlighter-rouge">Array.prototype.find</code> to find the first element matching a condition in an array:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">garage</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Model 3</span><span class="dl">'</span><span class="p">,</span> <span class="na">electric</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span>
<span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Punto</span><span class="dl">'</span><span class="p">,</span> <span class="na">electric</span><span class="p">:</span> <span class="kc">false</span><span class="p">},</span>
<span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">208</span><span class="dl">'</span><span class="p">,</span> <span class="na">electric</span><span class="p">:</span> <span class="kc">false</span><span class="p">}</span>
<span class="p">];</span>
<span class="kd">function</span> <span class="nf">isElectric</span><span class="p">(</span><span class="nx">car</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">car</span><span class="p">.</span><span class="nx">electric</span> <span class="o">===</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">myFirstElectricCar</span> <span class="o">=</span> <span class="nx">garage</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nx">isElectric</span><span class="p">);</span></code></pre></figure>
<p>This code works well on recent browsers, but when running on Internet Explorer 11, it throws an error <code class="language-plaintext highlighter-rouge">Object doesn't support property or method 'find'</code>. Indeed, the <code class="language-plaintext highlighter-rouge">find()</code> method for arrays doesn’t exist for this browser and won’t exist since this browser is not updated anymore.</p>
<p>The solution is to <del>drop IE 11 support</del> provide a <strong>polyfill</strong>. In this case, it could be as simple as copying/pasting <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find#Polyfill">this polyfill from the MDN</a> directly into the code to make it work.</p>
<p>But it is more complicated to do that for every feature used in a codebase. It’s easy to forget or duplicate too many of them in the code, while it’s complicated to test and monitor. This is where the <a href="https://www.npmjs.com/package/core-js">NPM package <code class="language-plaintext highlighter-rouge">core-js</code></a> full of ECMAScript polyfills, comes in.</p>
<p>As for the code transpiling, the preset <code class="language-plaintext highlighter-rouge">@babel/preset-env</code> has a built-in list of <code class="language-plaintext highlighter-rouge">core-js</code> polyfills names that match browsers versions. According to the targeting environments, it knows which polyfills to include. From this point on, you have three ways to do that using <a href="https://babeljs.io/docs/en/babel-preset-env#usebuiltins">the <code class="language-plaintext highlighter-rouge">useBuiltIns</code> option</a> of <code class="language-plaintext highlighter-rouge">@babel/preset-env</code>.</p>
<h4 id="usebuiltins-entry"><code class="language-plaintext highlighter-rouge">useBuiltIns: "entry"</code></h4>
<p>This option requires the <code class="language-plaintext highlighter-rouge">core-js</code> module to be imported (and <strong>only once</strong>) at the entry point of the project. According to the standard level targeted, many import options are available:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// Must be at the root, the very beginning of the code, before anything else</span>
<span class="c1">// polyfill all `core-js` features</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js</span><span class="dl">"</span>
<span class="c1">// OR polyfill only stable `core-js` features - ES and web standards</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js/stable</span><span class="dl">"</span>
<span class="c1">// OR polyfill only stable ES features</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js/es</span><span class="dl">"</span>
<span class="c1">// OR any other module/folder from core-js</span></code></pre></figure>
<p>Then Babel will parse the code, and when it finds the <code class="language-plaintext highlighter-rouge">core-js</code> import, it will transform this one-line import into multiple imports of unit modules from <code class="language-plaintext highlighter-rouge">core-js</code>. As a result, it’ll <strong>only import polyfills necessary for the targeting environments whether or not the features are used</strong>. Here’s what that looks like by importing <code class="language-plaintext highlighter-rouge">core-js/es</code>:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.description</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.async-iterator</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.has-instance</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.is-concat-spreadable</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.iterator</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.match</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.replace</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es.symbol.search</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// ... and all other polyfills that exist in core-js/es...</span></code></pre></figure>
<h4 id="usebuiltins-usage"><code class="language-plaintext highlighter-rouge">useBuiltIns: "usage"</code></h4>
<p>This option tells Babel to <strong>automatically write</strong> the polyfill imports related to a feature each time it encounters it.</p>
<p>Thus, this code:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="cm">/* We keep the previous example with the garage of cars */</span>
<span class="kd">const</span> <span class="nx">myFirstElectricCar</span> <span class="o">=</span> <span class="nx">garage</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nx">isElectric</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">haveMyElectricCar</span> <span class="o">=</span> <span class="nx">garage</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="nx">myFirstElectricCar</span><span class="p">);</span></code></pre></figure>
<p>will be transformed by Babel to this:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es7.array.includes</span><span class="dl">"</span><span class="p">);</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">core-js/modules/es6.array.find</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">myFirstElectricCar</span> <span class="o">=</span> <span class="nx">garage</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nx">isElectric</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">haveElectricCar</span> <span class="o">=</span> <span class="nx">garage</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="nx">myFirstElectricCar</span><span class="p">);</span></code></pre></figure>
<p>It’s important to understand that <strong>it’s no longer needed to write <code class="language-plaintext highlighter-rouge">core-js</code> imports</strong>. Polyfills imports will <strong>automatically be added at every part of the code that needs one or many polyfills</strong>. It also means that if a modern feature is used multiple times at different places, it will result in multiple imports of the same polyfills. Indeed, it assumes that a bundler (like Webpack) will collect and deduplicate imports so that polyfills are only included once in the final output(s).</p>
<p>This is the most optimized and automatic way to include only the polyfills that are needed and remove them when they become unnecessary (whether they are not used anymore or the targeting environments list evolved to more recent ones).</p>
<h4 id="usebuiltins-false"><code class="language-plaintext highlighter-rouge">useBuiltIns: false</code></h4>
<p>This will tell Babel not to handle polyfills at all. Every polyfill from the different <code class="language-plaintext highlighter-rouge">core-js</code> imports will be included without fine selections according to the targeted environments. It will be still possible to import <code class="language-plaintext highlighter-rouge">core-js</code> manually. There won’t be any filtering but the selected modules imports:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// polyfill everything from `core-js`</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js</span><span class="dl">"</span>
<span class="c1">// polyfill only array ES features</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js/es/array</span><span class="dl">"</span>
<span class="c1">// polyfill only array.includes ES feature</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">core-js/es/array/includes</span><span class="dl">"</span>
<span class="p">...</span><span class="nx">Your</span> <span class="nx">highly</span> <span class="nx">up</span> <span class="nx">to</span> <span class="nx">date</span> <span class="nx">JavaScript</span> <span class="nx">code</span> <span class="nx">here</span><span class="p">...</span></code></pre></figure>
<p>Using this way, be sure <strong>never to import polyfill twice</strong>; otherwise, it will throw an error.</p>
<h3 id="transpiling-the-case-of-typescript">Transpiling: The Case Of TypeScript</h3>
<p><a href="https://www.typescriptlang.org/index.html">The documentation</a> states, “TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.” This means that any JavaScript code is a valid TypeScript code, but TypeScript is not necessarily valid JavaScript and, therefore, not supported by browsers.</p>
<p>At Getaround, we use TypeScript to develop our front-end features. Consequently, we need to transform our TypeScript code to “classical JavaScript” before deploying it.</p>
<p>To do so, TypeScript comes with a code transpiler. This latter will transform the TypeScript code to an ECMAScript 3 code, so it could be wrongly thought that we don’t need to transpile with Babel anymore.</p>
<p>But there are some points that we need to highlight here:</p>
<ul>
<li>You’ll have two different configurations to handle JavaScript-related files in the project. Babel for <code class="language-plaintext highlighter-rouge">.js</code> files and Typescript for <code class="language-plaintext highlighter-rouge">.ts</code> files.</li>
<li>TypeScript doesn’t handle polyfills as Babel does, and Babel doesn’t do type-checking as TypeScript does.</li>
<li>Babel is much more extensible and has a more extensive plugin ecosystem than TypeScript.</li>
<li>There are some incompatibilities between the two tools (you can read <a href="https://devblogs.microsoft.com/typescript/typescript-and-babel-7/">this article from Microsoft</a>).</li>
</ul>
<p>So a solution here would be to keep using Babel for both cases, thanks to a dedicated preset named <code class="language-plaintext highlighter-rouge">@babel/preset-typescript</code> that allows Babel to transform TypeScript code correctly. And for the type-checking, we can still rely on the <code class="language-plaintext highlighter-rouge">tsc</code> CLI provided by TypeScript.</p>
<h2 id="our-open-source-preset-configuration">Our open-source preset configuration</h2>
<p>At Getaround, we use a custom preset to share our configuration across all front-end apps. It is publicly available on our <a href="https://github.com/drivy/frontend-configs/blob/main/packages/babel-preset-app/index.js"><code class="language-plaintext highlighter-rouge">drivy/frontend-configs</code> Github repository</a>, along with all of our other front-end configurations.</p>
<p>You can also find it on <a href="https://www.npmjs.com/package/@getaround-eu/babel-preset-app">NPM</a>: <code class="language-plaintext highlighter-rouge">@getaround-eu/babel-preset-app</code>.</p>
<p>Don’t hesitate to take a look and to use it!</p>
<p><a href="https://babeljs.io/">BabelJS or Babel</a> is a prevalent tool in the JavaScript ecosystem. Most developers know that it is essential when developing using brand new JavaScript features. But how does this system work?</p>
Thu, 17 Nov 2022 00:00:00 +0000
https://getaround.tech/babel-transpiling-polyfills/
https://getaround.tech/babel-transpiling-polyfills/Building a modular multiple flows wizard in RubyRémy Hannequin<p>Wizards are a common component in a lot of applications. Either for signing up new users, creating products, purchasing items and many more.</p>
<p>They can be tricky to manage once they get bigger and more complex. At Getaround, we have several wizards which don’t share their architecture. A common architecture cannot fit every use case with different needs, flow, and user experience.</p>
<p>To list new cars on our platform, hosts provide multiple pieces of information on the vehicle, themselves, and their needs. We may ask for more information or skip some steps. Such constraints lead to complexity and difficulty in handling and testing every variation.</p>
<p>After multiple iterations, we ended up with a modular architecture that was less strict than a decision tree and allowed us to design wizards with complex or simple logic.</p>
<p>In this article, I’ll try to guide you through building such a modular architecture. We’ll use a <a href="https://getaround.tech/sanitize-your-attributes/">form object</a> for each step and a <code class="language-plaintext highlighter-rouge">Manager</code> to orchestrate everything.</p>
<h2 id="form-object-interface">Form object interface</h2>
<p><code class="language-plaintext highlighter-rouge">ActiveModel</code> provides convenient modules to create custom form objects and manipulate attributes. We are going to use the following modules:</p>
<ul>
<li><a href="https://api.rubyonrails.org/classes/ActiveModel/Model.html"><code class="language-plaintext highlighter-rouge">ActiveModel::Model</code></a> so that our form object behaves like a regular model</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveModel/Attributes/ClassMethods.html"><code class="language-plaintext highlighter-rouge">ActiveModel::Attributes</code></a> to access submitted fields as attributes</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveModel/Validations.html"><code class="language-plaintext highlighter-rouge">ActiveModel::Validations</code></a> to benefit from convenient attribute validations</li>
</ul>
<p>Using these modules will also allow us to use Rails form helpers as if the manipulated object were an actual model. Many thanks to <a href="https://github.com/Intrepidd">Intrepidd</a> for sharing code that led to this base form.</p>
<p>Our <code class="language-plaintext highlighter-rouge">BaseForm</code> would then look like this:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s2">"active_model"</span>
<span class="k">class</span> <span class="nc">BaseForm</span>
<span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Model</span>
<span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Attributes</span>
<span class="kp">include</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">Validations</span>
<span class="n">attribute</span> <span class="ss">:car</span>
<span class="k">def</span> <span class="nf">complete?</span>
<span class="k">raise</span> <span class="no">NotImplementedError</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next_step</span>
<span class="k">raise</span> <span class="no">NotImplementedError</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">submit</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
<span class="n">params</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span> <span class="n">public_send</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">="</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="p">}</span>
<span class="n">perform</span> <span class="k">if</span> <span class="n">valid?</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="k">raise</span> <span class="no">NotImplementedError</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Let’s take the time to explain this code. First, we’re declaring <code class="language-plaintext highlighter-rouge">attribute :car</code> because our form objects will be initialized with a <code class="language-plaintext highlighter-rouge">Car</code> model. This object will be the source of truth, the one we will fill with new data and rely on to determine what’s missing from it.</p>
<p><code class="language-plaintext highlighter-rouge">complete?</code> will be the method called to know if a step has been successfully completed. In this method we can for example check if a particular attribute has been filled in on our car record.</p>
<p><code class="language-plaintext highlighter-rouge">next_step</code> handles the logic to compute what the next step will be. A step knows what the next one is because it will rely on what was submitted previously.</p>
<p><code class="language-plaintext highlighter-rouge">submit</code> is the method to submit our params from the associated form. It will only call <code class="language-plaintext highlighter-rouge">perform</code>, supposed to be implemented on each form object, if all validations passed.</p>
<p>With this public interface, we can create as many steps as we want and they will create the flow by themselves using <code class="language-plaintext highlighter-rouge">perform</code> to save data, and then <code class="language-plaintext highlighter-rouge">complete?</code> and <code class="language-plaintext highlighter-rouge">next_step</code> to handle going from one step to another.</p>
<h2 id="manager">Manager</h2>
<p>Having steps handling themselves is great, but we still need some logic to initiate the wizard and determine which step the user is currently on.</p>
<p>Our <code class="language-plaintext highlighter-rouge">Manager</code> object will handle this logic. It also can manage having available steps, and non-available steps. For instance, if we have steps <code class="language-plaintext highlighter-rouge">A</code>, <code class="language-plaintext highlighter-rouge">B</code> and <code class="language-plaintext highlighter-rouge">C</code>, with step <code class="language-plaintext highlighter-rouge">A</code> already being submitted. The next step to submit is <code class="language-plaintext highlighter-rouge">B</code>, but I should be allowed to access step <code class="language-plaintext highlighter-rouge">A</code> again if I want to correct what I submitted. Step <code class="language-plaintext highlighter-rouge">C</code> is not accessible as long as step <code class="language-plaintext highlighter-rouge">B</code> is not complete, it shouldn’t even be visible to the user.</p>
<p>Our <code class="language-plaintext highlighter-rouge">Manager</code> could then look like this:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Manager</span>
<span class="no">FIRST_STEP</span> <span class="o">=</span> <span class="ss">:country</span>
<span class="no">STEP_FORMS</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">country: </span><span class="no">CountryForm</span><span class="p">,</span>
<span class="ss">insurance_provider: </span><span class="no">InsuranceProviderForm</span><span class="p">,</span>
<span class="ss">mileage: </span><span class="no">MileageForm</span><span class="p">,</span>
<span class="p">}.</span><span class="nf">freeze</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">car</span><span class="p">:)</span>
<span class="vi">@car</span> <span class="o">=</span> <span class="n">car</span>
<span class="vi">@instantiated_forms</span> <span class="o">=</span> <span class="p">{}</span>
<span class="vi">@possible_steps</span> <span class="o">=</span> <span class="n">compute_possible_steps</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">current_step</span>
<span class="vi">@possible_steps</span><span class="p">.</span><span class="nf">find</span> <span class="k">do</span> <span class="o">|</span><span class="n">step</span><span class="o">|</span>
<span class="k">return</span> <span class="kp">nil</span> <span class="k">if</span> <span class="n">step</span><span class="p">.</span><span class="nf">nil?</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">find_or_instantiate_form_for</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="o">!</span><span class="n">form</span><span class="p">.</span><span class="nf">complete?</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">form_for</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="no">STEP_FORMS</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">compute_possible_steps</span>
<span class="n">steps</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">steps_path</span><span class="p">(</span><span class="no">FIRST_STEP</span><span class="p">,</span> <span class="n">steps</span><span class="p">)</span>
<span class="n">steps</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">steps_path</span><span class="p">(</span><span class="n">starting_step</span><span class="p">,</span> <span class="n">steps_acc</span><span class="p">)</span>
<span class="n">steps_acc</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">starting_step</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="n">starting_step</span><span class="p">.</span><span class="nf">nil?</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">find_or_instantiate_form_for</span><span class="p">(</span><span class="n">starting_step</span><span class="p">)</span>
<span class="n">steps_path</span><span class="p">(</span><span class="n">form</span><span class="p">.</span><span class="nf">next_step</span><span class="p">,</span> <span class="n">steps_acc</span><span class="p">)</span> <span class="k">if</span> <span class="n">form</span><span class="p">.</span><span class="nf">complete?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">find_or_instantiate_form_for</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="vi">@instantiated_forms</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">step</span><span class="p">)</span> <span class="k">do</span>
<span class="n">form_for</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">car: </span><span class="vi">@car</span><span class="p">)</span>
<span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="o">|</span><span class="n">form</span><span class="o">|</span> <span class="vi">@instantiated_forms</span><span class="p">[</span><span class="n">step</span><span class="p">]</span> <span class="o">=</span> <span class="n">form</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Every step is declared with its associated form object. At initialization, all possible steps are computed using the public <code class="language-plaintext highlighter-rouge">complete?</code>, calculating one step after another with <code class="language-plaintext highlighter-rouge">next_step</code>. The method <code class="language-plaintext highlighter-rouge">form_for</code> will allow the controller to manipulate the right form object from the manager.</p>
<p>As we support multiple flows, the last step may not be the last one defined in the list. We then expect <code class="language-plaintext highlighter-rouge">next_step</code> to return <code class="language-plaintext highlighter-rouge">nil</code> when there’s no step left.</p>
<h2 id="steps">Steps</h2>
<p>In the manager, I mentioned three steps, <code class="language-plaintext highlighter-rouge">country</code>, <code class="language-plaintext highlighter-rouge">insurance_provider</code> and <code class="language-plaintext highlighter-rouge">mileage</code>. Let’s build them and see how with only 3 steps we can already have multiple flows.</p>
<h3 id="country">Country</h3>
<p>This step will simply save the selected country on the car record. However, its next step will depend on what country was selected.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CountryForm</span> <span class="o"><</span> <span class="no">BaseForm</span>
<span class="no">ALLOWED_COUNTRIES</span> <span class="o">=</span> <span class="sx">%w[CA ES PK JP]</span><span class="p">.</span><span class="nf">freeze</span>
<span class="no">COUNTRY_REQUIRING_INSURANCE_PROVIDER</span> <span class="o">=</span> <span class="sx">%w[ES]</span><span class="p">.</span><span class="nf">freeze</span>
<span class="n">attribute</span> <span class="ss">:country</span><span class="p">,</span> <span class="ss">:string</span>
<span class="n">validates_inclusion_of</span> <span class="ss">:country</span><span class="p">,</span> <span class="ss">in: </span><span class="no">ALLOWED_COUNTRIES</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="n">car</span><span class="p">.</span><span class="nf">update!</span><span class="p">(</span><span class="ss">country: </span><span class="n">country</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">complete?</span>
<span class="o">!</span><span class="n">car</span><span class="p">.</span><span class="nf">country</span><span class="p">.</span><span class="nf">nil?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next_step</span>
<span class="k">if</span> <span class="no">COUNTRY_REQUIRING_INSURANCE_PROVIDER</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="n">car</span><span class="p">.</span><span class="nf">country</span><span class="p">)</span>
<span class="ss">:insurance_provider</span>
<span class="k">else</span>
<span class="ss">:mileage</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>First we can see how readable the attributes and validations are thanks to <code class="language-plaintext highlighter-rouge">ActiveModel</code>. <code class="language-plaintext highlighter-rouge">country</code> is a string attribute and we expect it to be one of the allowed countries, defined in a constant. The <code class="language-plaintext highlighter-rouge">perform</code> method will only be called if the requirements are met, if <code class="language-plaintext highlighter-rouge">valid?</code> returns <code class="language-plaintext highlighter-rouge">true</code>.</p>
<p><code class="language-plaintext highlighter-rouge">complete?</code> only checks if <code class="language-plaintext highlighter-rouge">country</code> has been successfully saved on the car record.</p>
<p>Finally, <code class="language-plaintext highlighter-rouge">next_step</code> depends on the country selected. If an insurance provider is required in the country, then we’ll need the user to provide this data. If not, we decide to go directly to the next one, <code class="language-plaintext highlighter-rouge">mileage</code>.</p>
<p>Of course we could improve things here, especially in <code class="language-plaintext highlighter-rouge">perform</code>. We probably don’t want to use <code class="language-plaintext highlighter-rouge">update!</code> which raises when it fails, but we still want to be sure what’s inside this method is properly executed. For the sake of simplicity I didn’t add such a logic here, but we can easily play with <code class="language-plaintext highlighter-rouge">errors</code> available thanks to <code class="language-plaintext highlighter-rouge">ActiveModel::Validations</code>.</p>
<h3 id="insurance-provider">Insurance provider</h3>
<p>Nothing particular to say about this one, except that it will be displayed only if the car’s country requires an insurance provider.</p>
<p>The next step is defined as <code class="language-plaintext highlighter-rouge">mileage</code>, but from here we could imagine another branch in the decision tree, multiple flows, multiple possibilities.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">InsuranceProviderForm</span> <span class="o"><</span> <span class="no">BaseForm</span>
<span class="n">attribute</span> <span class="ss">:insurance_provider</span><span class="p">,</span> <span class="ss">:string</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="n">car</span><span class="p">.</span><span class="nf">update!</span><span class="p">(</span><span class="ss">insurance_provider: </span><span class="n">insurance_provider</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">complete?</span>
<span class="o">!</span><span class="n">car</span><span class="p">.</span><span class="nf">insurance_provider</span><span class="p">.</span><span class="nf">nil?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next_step</span>
<span class="ss">:mileage</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h3 id="mileage">Mileage</h3>
<p>In our example, <code class="language-plaintext highlighter-rouge">mileage</code> is the last step. Once a mileage integer is submitted, validated and saved, there’s no other step to go to. <code class="language-plaintext highlighter-rouge">next_step</code> returns <code class="language-plaintext highlighter-rouge">nil</code> to announce that the wizard is finished.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MileageForm</span> <span class="o"><</span> <span class="no">BaseForm</span>
<span class="n">attribute</span> <span class="ss">:mileage</span><span class="p">,</span> <span class="ss">:integer</span>
<span class="n">validates</span> <span class="ss">:mileage</span><span class="p">,</span> <span class="ss">numericality: </span><span class="p">{</span> <span class="ss">greater_than: </span><span class="mi">0</span> <span class="p">}</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="n">car</span><span class="p">.</span><span class="nf">update!</span><span class="p">(</span><span class="ss">mileage: </span><span class="n">mileage</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">complete?</span>
<span class="o">!</span><span class="n">car</span><span class="p">.</span><span class="nf">mileage</span><span class="p">.</span><span class="nf">nil?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">next_step</span>
<span class="kp">nil</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="controller-and-routes">Controller and routes</h2>
<p>We only need two routes to support this wizard: <code class="language-plaintext highlighter-rouge">show</code> and <code class="language-plaintext highlighter-rouge">update</code>. <code class="language-plaintext highlighter-rouge">show</code> will display the step to the user while <code class="language-plaintext highlighter-rouge">update</code> will handle the step submission.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/routes.rb</span>
<span class="n">resources</span> <span class="ss">:car_wizards</span><span class="p">,</span> <span class="ss">only: </span><span class="sx">%i[show update]</span>
</code></pre></div></div>
<p>On the controller side, we’re supposed to let all the logic come from the manager and only handle rendering, form submission and redirections.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CarWizardController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="n">before_action</span> <span class="ss">:initialize_variables</span><span class="p">,</span> <span class="ss">only: </span><span class="sx">%i[show update]</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="k">if</span> <span class="vi">@step</span> <span class="o">==</span> <span class="ss">:current</span>
<span class="n">redirect_to</span> <span class="n">car_wizard_path</span><span class="p">(</span><span class="vi">@car</span><span class="p">,</span> <span class="vi">@step_manager</span><span class="p">.</span><span class="nf">current_step</span><span class="p">)</span>
<span class="k">elsif</span> <span class="vi">@step_manager</span><span class="p">.</span><span class="nf">possible_step?</span><span class="p">(</span><span class="vi">@step</span><span class="p">)</span>
<span class="n">set_form</span>
<span class="n">render</span> <span class="vi">@step</span>
<span class="k">else</span>
<span class="n">render</span> <span class="s2">"errors/not_found"</span><span class="p">,</span> <span class="ss">status: :not_found</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">update</span>
<span class="k">if</span> <span class="o">!</span><span class="vi">@step_manager</span><span class="p">.</span><span class="nf">possible_step?</span><span class="p">(</span><span class="vi">@step</span><span class="p">)</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="s2">"errors/not_found"</span><span class="p">,</span> <span class="ss">status: :not_found</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">set_form</span>
<span class="k">if</span> <span class="vi">@form</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:car</span><span class="p">])</span>
<span class="n">redirect_to</span> <span class="n">car_wizard_path</span><span class="p">(</span><span class="vi">@car</span><span class="p">,</span> <span class="vi">@form</span><span class="p">.</span><span class="nf">next_step</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">render</span> <span class="vi">@step</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">initialize_variables</span>
<span class="vi">@car</span> <span class="o">=</span> <span class="n">current_user</span><span class="p">.</span><span class="nf">cars</span><span class="p">.</span><span class="nf">incomplete</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:car_id</span><span class="p">])</span>
<span class="vi">@step</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">].</span><span class="nf">to_sym</span>
<span class="vi">@step_manager</span> <span class="o">=</span> <span class="no">Manager</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">car: </span><span class="vi">@car</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">RecordNotFound</span>
<span class="n">redirect_to</span> <span class="n">root_path</span><span class="p">,</span> <span class="ss">error: </span><span class="s2">"Car not found"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">set_form</span>
<span class="vi">@form</span> <span class="o">||=</span> <span class="vi">@step_manager</span><span class="p">.</span><span class="nf">form_for</span><span class="p">(</span><span class="vi">@step</span><span class="p">).</span><span class="nf">new</span><span class="p">(</span><span class="ss">car: </span><span class="vi">@car</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="view">View</h2>
<p>Finally, each step has its associated view. A step view only needs a form helper instance, based on the form object, to display form fields. At Getaround, we also have an associated presenter for each step, which allows us to share information between web, web mobile and mobile apps.</p>
<div class="language-haml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>simple_form_for @form, as: :car, url: car_wizard_path(@car, @step), method: :put do |f|
<span class="p">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">select</span> <span class="ss">:mileage</span><span class="p">,</span> <span class="n">car_mileage_options_for_select</span><span class="p">,</span> <span class="ss">label: </span><span class="n">t</span><span class="p">(</span><span class="s2">"car_wizard.steps.mileage.attributes.mileage.label"</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="pros-and-cons">Pros and cons</h2>
<p>With this architecture, we can build a complex wizard, with multiple flows. A user can stop and resume it any time, and it is possible to have many flows with many rules without having to write the entire logic in one single file, which would be much harder to understand and maintain.</p>
<p>Each step having its own logic allows us to test the flow step by step, independently. The simple public API helps us to test service perfoming logic and attribute validation separately.</p>
<p>It is easy to integrate into our MVC pattern with a very simple controller and basic views. The wizard manager itself is only a simple algorithm to compute possible steps.</p>
<p>However, having most logic inside the form objects forces us to read each step to understand how the flows work. If the whole wizards gets too complicated, computing each step could begin to take some time, so this is something to watch out on the long term.</p>
<p>Also, the step completion is based on saving things on database records. Having informational steps is a challenge to handle because we need to find other ways to store state to indicate that they have been seen, or rely on the state of adjacent steps.</p>
<p>For the simplicity of the article we haven’t show all the features we have based on the car wizard. For instance, we have a logic to handle tracking on each step automatically. Also, the mobile wizard is driven by the backend, based on a dedicated API. With this in mind, such an architecture allows us to reorder, add or remove any step without having to deploy a new version of our mobile apps. Well, to be honest, it’s a little bit more complicated than that depending on what the mobile app supports, but you get the idea.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This modular flow is one solution to the wizard problem. It won’t suit every need, but a similar architecture has its advantages if you seek to manage multiple flows with complex decision trees.</p>
<p>Feel free to comment and let us know what you think of this architecture and how we could improve it.</p>
<p>Cheers.</p>
<p>Wizards are a common component in a lot of applications. Either for signing up new users, creating products, purchasing items and many more.</p>
Mon, 12 Sep 2022 00:00:00 +0000
https://getaround.tech/multiple-flows-wizard/
https://getaround.tech/multiple-flows-wizard/JavaScript smooth API with named-arguments and TypeScriptThibaud Esnouf<p>As a JavaScript developer, you surely have encountered some functions that require a lot of arguments to be called.
Because the argument list is an Array-like object, all the values need to be set and so it may have given you a headache to understand the order and purpose of each argument.</p>
<p>Let see an approach to define developer-friendly function signatures with the named-arguments pattern and TypeScript.</p>
<h2 id="the-original-issues-with-functions-and-arguments">The original issues with functions and arguments</h2>
<p>Passing multiples arguments to a function can leads to several issues:</p>
<figure>
<figcaption class="highlight-caption">Example with the infamous null in the middle</figcaption>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"> <span class="nf">getItem</span><span class="p">(</span><span class="nx">itemId</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span></code></pre></figure>
</figure>
<ul>
<li>Unless some variables with good naming are used, you have no clue of what the values stand for.<br />
<code class="language-plaintext highlighter-rouge">itemId</code> is fine but what are behind the <code class="language-plaintext highlighter-rouge">null</code> and <code class="language-plaintext highlighter-rouge">true</code> values ?</li>
<li>The order is important. Miss it and your code is broken.</li>
<li>You can’t skip optional parameter if they are defined in the middle of the argument list.<br />
You’ll have to pass the full requested list of arguments, using default values like <code class="language-plaintext highlighter-rouge">null</code>.</li>
</ul>
<p><strong>Those issues apply for both the developer calling the function and the one reviewing the resulting code</strong>.</p>
<p>The more arguments you pass to a function, the faster it will become a nightmare to call it.</p>
<p>
<strong>TypeScript</strong> can help you by providing an IDE integration displaying the function input naming.<br /> Type checking can also help you in some cases with the order.
</p>
<figure>
<img alt="TypeScript IDE documentation" src="/assets/posts/2022-05-04-javascript-named-arguments-pattern/typescript-ide-documentation.jpg" />
<figcaption>
Example of a documentation based on TypeScript in VSCode
</figcaption>
</figure>
<p>But you still have to pass the full argument list even though you only want to use 2 of them.
Plus the TypeScript « auto documentation » may not be available when performing a read-only review of some code, on GitHub for example</p>
<p>So let’s jump in a solution that resolve all those issues.</p>
<h2 id="named-arguments-pattern-with-typescript"><code class="language-plaintext highlighter-rouge">Named-arguments</code> pattern (with TypeScript)</h2>
<p>
The trick is to "replace" the argument list by a single javascript object that will embed all the arguments as properties.
This pattern is called `named-arguments`
</p>
<blockquote>
<p>This pattern has always been available but it has been made easy with es6 <strong>Object destructuring</strong> and <strong>TypeScript</strong></p>
</blockquote>
<h3 id="single-object-as-an-argument">Single Object as an argument</h3>
<p>Using a single object as a wrapper and using properties to pass arguments will resolve all the issues:</p>
<ul>
<li>If a parameter is optional and you don’t want to define it, just omit the related property</li>
<li>You can define the properties in any order. It doesn’t matter.</li>
<li>with nicely named-properties, you’ll always have a clue of the purpose of a value</li>
</ul>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">getItem</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">itemId</span><span class="p">,</span>
<span class="na">disabled</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">true</code> value makes perfect sense now!
Plus, we don’t need anymore to pass extra values for optional parameters. No more extra <code class="language-plaintext highlighter-rouge">null</code> values !</p>
<figure>
<figcaption class="highlight-caption">Calling the function with some extra parameters</figcaption>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">getItem</span><span class="p">({</span>
<span class="na">id</span><span class="p">:</span> <span class="nx">itemId</span><span class="p">,</span>
<span class="na">createdAfter</span><span class="p">:</span> <span class="nx">date1</span><span class="p">,</span>
<span class="na">createdBefore</span><span class="p">:</span> <span class="nx">date2</span><span class="p">,</span>
<span class="na">disabled</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">})</span></code></pre></figure>
</figure>
<p>If we want to add some optional parameters, we just need to define the related properties (<code class="language-plaintext highlighter-rouge">createdAfter</code> & <code class="language-plaintext highlighter-rouge">createdAfter</code>)</p>
<p>So now, let’s have a look of such a function declaration</p>
<p>Using a single object as an argument will make it more obtrusive to guess the input and harder to retrieve the values?</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">function</span> <span class="nf">getItem</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// no idea of the data structure :(</span>
<span class="c1">// we have to retrieve the values one by one….</span>
<span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">id</span>
<span class="kd">const</span> <span class="nx">createdAfter</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">createdAfter</span> <span class="o">||</span> <span class="kc">null</span> <span class="c1">// setting a default value</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
<p>Not at all, thanks to ES6 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring">object destructuring</a> that can be used straight to function inputs</p>
<h3 id="object-destructuring">Object destructuring</h3>
<figure>
<figcaption class="highlight-caption">Object destructuring to the rescue</figcaption>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kd">function</span> <span class="nf">getItem</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">createdAfter</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">createdBefore</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">disabled</span> <span class="o">=</span> <span class="kc">false</span> <span class="p">})</span> <span class="p">{</span>
<span class="c1">// our arguments are listed back, with some default values !</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">my input id</span><span class="dl">"</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
</figure>
<p>It looks like the classic Array-like argument list (positional arguments) with some extra brackets around <code class="language-plaintext highlighter-rouge">{}</code>.</p>
<p>Notice that we are using default parameters to define optional arguments:</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nx">createdAfter</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">createdBefore</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">disabled</span> <span class="o">=</span> <span class="kc">false</span></code></pre></figure>
<p>If the <code class="language-plaintext highlighter-rouge">createdAfter</code> or <code class="language-plaintext highlighter-rouge">createdBefore</code> property is missing (<code class="language-plaintext highlighter-rouge">undefined</code>) the <code class="language-plaintext highlighter-rouge">null</code> value will be set automatically.<br />
<code class="language-plaintext highlighter-rouge">disabled</code> argument if omitted will be set to <code class="language-plaintext highlighter-rouge">false</code></p>
<p>If you are familiar with <strong>React</strong> you have notice that this mechanism is used for <strong>Component props</strong>, taking advantage that adding attributes to a HTML( JSX) element has the same behavior that adding properties to an object:</p>
<ul>
<li>No order.</li>
<li>Optional argument can be omitted.</li>
<li>Defining the attribute name allow you to understand the nature & purpose of the data.</li>
</ul>
<h3 id="typescript-enhancement">TypeScript enhancement</h3>
<p>
Although all the issues as been resolved using our object, the developer experience will be really smooth when coupled with TypeScript.
Your function API will be auto-documented showing to the developer the available property names and their related types.
</p>
<p>Sometimes, the name of a property is not enough to understand the type of the value that should be used. Should a date argument be a Date object ? A formatted string ? Or a timestamp ?<br /> TypeScript will give you this information.</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="kr">interface</span> <span class="nx">ApiInterface</span> <span class="p">{</span>
<span class="nl">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="c1">// oh ! So the id should be a string and not a number</span>
<span class="nx">createdAfter</span><span class="p">?:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">null</span> <span class="c1">// date is a Date object ! +optional</span>
<span class="nx">createdBefore</span><span class="p">?:</span> <span class="nb">Date</span> <span class="o">|</span> <span class="kc">null</span> <span class="c1">//optional</span>
<span class="nx">disabled</span><span class="p">?:</span> <span class="nx">boolean</span> <span class="c1">// optional</span>
<span class="p">}</span>
<span class="c1">// just by reading the interface we get a clear understanding of the argument natures</span>
<span class="kd">function</span> <span class="nf">getItem</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">createdAfter</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">createdBefore</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">disabled</span> <span class="o">=</span> <span class="kc">false</span> <span class="p">}</span> <span class="p">:</span> <span class="nx">ApiInterface</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
<p>The input interface gives a good understanding of the arguments used by the function.<br />
At a glance we can see which arguments are optionals thanks to the <code class="language-plaintext highlighter-rouge">?</code></p>
<p>If you omit a mandatory argument, TypeScript will notify you.</p>
<figure>
<img alt="TypeScript IDE documentation" src="/assets/posts/2022-05-04-javascript-named-arguments-pattern/typescript-missing-property.jpg" />
</figure>
<p>So TypeScript will help both the developer calling the function and a reviewer reading the code from the function itself</p>
<h3 id="shorthand-property-names">Shorthand property names</h3>
<p>
Cherry on the cake, shortand property names can ease the process of calling such a function.
</p>
<p>If you’re passing values using variable names matching the expected properties, you’ll just have to call the function wrapping the variables inside brackets</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">getItem</span><span class="p">({</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">disabled</span> <span class="p">})</span></code></pre></figure>
<p>It is the same thing than</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">getItem</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">id</span><span class="p">,</span> <span class="na">disabled</span><span class="p">:</span> <span class="nx">disabled</span> <span class="p">})</span></code></pre></figure>
<p>If one of your variable name doesn’t match an expected property name, you can of course mix shorthand property names with classic declarations.</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">getItem</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">itemId</span><span class="p">,</span> <span class="nx">disabled</span> <span class="p">})</span></code></pre></figure>
<p>The nice thing with the <code class="language-plaintext highlighter-rouge">named-arguments</code> pattern is that the more arguments are required for a function, the more sense it will make to use it.</p>
<h2 id="use-it-wisely">Use it wisely</h2>
<p>That’s not because this pattern has some advantages that you should use it everywhere:</p>
<p><strong>If your function accepts a single argument, it doesn’t make sense to wrap it in an object.</strong><br />
You should just ensure that the naming give a good hint of the input nature. Using TypeScript won’t even make it an issue.
<br />
You could be tempted to use named-argument because of potential future evolution of your API but you shouldn’t forecast it by sacrifying simplicity.
If you need additional arguments later, let’s refactor it at this moment.</p>
<p>
In my opinion, if your function only accepts 2 arguments, it is still excessive to use this pattern, especially if the second one is optional.
<br />
<strong>But it could be a good call to use named-arguments if the 2 properties have the same types</strong>.
</p>
<p>For example building a range of values (min/max) or a range of dates (start/end)<br />
The order may be important and TypeScript won’t help in this case.</p>
<figure class="highlight"><pre><code class="language-typescript" data-lang="typescript"><span class="nf">isValidRange</span><span class="p">(</span><span class="nx">startDate</span><span class="p">,</span> <span class="nx">endDate</span><span class="p">)</span> <span class="c1">// should startDate be the first argument ?</span>
<span class="nf">isValidRange</span><span class="p">({</span> <span class="na">start</span><span class="p">:</span> <span class="nx">startDate</span><span class="p">,</span> <span class="na">end</span><span class="p">:</span> <span class="nx">endDate</span><span class="p">})</span> <span class="c1">// no more ambiguity</span></code></pre></figure>
<p>Keep in mind that if you have a lot of properties in your argument object, this can be a good hint that you are doing too many things at once.<br />
For example, if you return an object using the <code class="language-plaintext highlighter-rouge">RORO (Receive Object / Return Object) pattern</code>, you could split your function into multiple sub tasks using composition.<br />
As always, when your single input and output share the same type, it can be a good hint that you can enable composing, splitting the manipulation on your input data on multiple functions.</p>
<p>As a JavaScript developer, you surely have encountered some functions that require a lot of arguments to be called.
Because the argument list is an Array-like object, all the values need to be set and so it may have given you a headache to understand the order and purpose of each argument.</p>
Wed, 04 May 2022 00:00:00 +0000
https://getaround.tech/javascript-named-arguments-pattern/
https://getaround.tech/javascript-named-arguments-pattern/GDPR compliance and account deletionEric Favre<p>The GDPR has been around for several years now, and as advocates of data privacy, we are convinced by the legitimacy of such a regulation. However, as good as this measure is from a user’s perspective, it comes with its own puzzles and challenges for an online service provider… Here we’ll try to describe the solution we implemented to deal with the user’s data deletion, which is one of the rights granted by the GDPR (<a href="https://ec.europa.eu/info/law/law-topic/data-protection_en">General Data Protection Regulation</a>) to any European user of a service collecting personal data. As a result, this piece does not try to cover all the implications of the GDPR, nor does it pretend to bring a one size fits all solution deal with user data deletion.</p>
<hr />
<h2 id="gdpr-in-a-nutshell">GDPR In A Nutshell</h2>
<p>GDPR specifies different roles and responsibilities. As an online service provider directly dealing with the end users, Getaround falls into the <em>controller</em> category. And as such we must comply with some obligations, notably:</p>
<ul>
<li>Collect data limited to what is necessary for the purposes for which they are processed, and <em>keep it for the time strictly necessary</em></li>
<li>Collect explicit consent over personal data collection and usage</li>
<li>Ensure data security</li>
<li>Upon user request, provide:
<ul>
<li>Access to personal data (right of access)</li>
<li><em>Means to delete personal data (right to erasure)</em></li>
<li>Export of personal data (right to data portability)</li>
<li>Change inaccurate or incomplete personal data (right to rectification)</li>
<li>Means to object to the data treatment (Right to object)</li>
<li>Enable limitation of personal data treatment (Right to restriction of processing)</li>
</ul>
</li>
<li>Address and communicate data breaches</li>
</ul>
<h2 id="data-retention">Data Retention</h2>
<p>In the description of the data usage, we define its processing and why the data is needed. The data retention is subject to 3 phases:</p>
<ul>
<li>active database</li>
<li>intermediate archiving</li>
<li>deletion</li>
</ul>
<p>We can keep the data in an active database for the time necessary to execute the specific purpose for which it was collected. The data can be afterwards kept in an intermediate archive for a legitimate purpose (essentially to serve a legal obligation or when it corresponds to a user’s legal right) providing the archived data is only the one which is necessary for that purpose and access is strictly limited. Afterwards, we have to erase the data and can do it through full anonymization. These principles applying to data archiving and deletion prevent abusive data retention for an undetermined period “just in case”, and this also allows damage control in case of data leaks. Once data is fully removed from the system, it is “unleakable”. Similarly, the users are the primary owners of their data, so they can spontaneously ask for their personal data deletion.</p>
<p>This basically means the system must offer a way to delete a user’s personal details, whether upon their own request, or according to a time-based data expiration rule. While a manual solution can be acceptable for smaller systems, a large scale product such as ours requires a real technical solution.</p>
<h2 id="getaround-context">Getaround Context</h2>
<p>Our service requires personal data from our users. We collect names, birth dates, id documents for driver vetting, etc. This is legitimate data to collect in a car rental context, and can be kept as long as it’s relevant (user is active, has some recent or upcoming rentals, has an ongoing claim, etc), but we must make sure that it’s thoroughly deleted when it’s no longer deemed relevant.</p>
<p>We also rely on 3rd parties (as per GDPR terminology) which process some of this data (for email campaigns, identity document authentication, customer support, etc.). The personal data communicated to these 3rd parties needs to be deleted when the user’s data is removed from our platform. Fortunately, most of these services also provide an API to automatically remove all data related to a user.</p>
<p>Finally, in the case where a user requests their account deletion, and some legal constraints force us to delay the actual data deletion, we must still make their account unusable and invisible to the other users or administrators.</p>
<h2 id="the-user-lifecycle">The User Lifecycle</h2>
<p>Now that the legal context is laid out, let’s dive into the implementation. First, we tried to materialize the different needs under a formalized user account lifecycle.</p>
<figure>
<img alt="User lifecycle: Active - Inactive - Archived - Deleted" src="/assets/posts/2021-12-02-gdpr-account-deletion/user_lifecycle.png" />
<figcaption>
User lifecycle
</figcaption>
</figure>
<p>As illustrated, we have 2 paths to a user account’s deletion. A main passive one, and a spontaneous one when the user requests their own account deletion. The passive one is the nominal user lifecycle path:</p>
<ul>
<li>User is <strong>active</strong>: they use our service</li>
<li>User is <strong>inactive</strong>: in the database, the user status is still active, however they haven’t had any recent activity logged in our system. When the latest monitored activity reaches a certain age (in our current configuration, 3 years), we send a notification to warn the user that their account will be automatically deleted if they do not log back in soon. If they do come back and create new activity, then they’re back to the active stage.</li>
<li>User gets <strong>archived</strong> if the user didn’t create any new activity when the time comes. Once archived, an account is unusable. From any user’s perspective, the personal data of an archived user (and their car, reviews…) are not viewable. At the time of archival, a deletion date is also determined. Most of the time (unless specific criteria apply) this deletion date is the next day. The date is stored along with the user account.</li>
<li>The user account becomes <strong>deleted</strong> when the deletion date is reached. The anonymization of the user data is performed. We chose to keep a fully anonymized record rather than completely deleting the record out of concern for referential integrity and for statistical analysis.</li>
</ul>
<p>The spontaneous account deletion happens upon the user’s own request. When they do so, the inactivity phase is skipped, and they are directly processed through the archiving phase, with the determination of the deletion date. Then the same process applies.</p>
<p>Once this lifecycle logic is laid out, the only remaining matter is the technical implementation.</p>
<h2 id="technical-implementation">Technical Implementation</h2>
<h3 id="flow-management">Flow Management</h3>
<p>We chose to define a dedicated model that holds the archiving and deletion logic. Let’s call it <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code>, and define its attributes like this:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">create_table</span> <span class="ss">:user_deletion_flows</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span>
<span class="n">t</span><span class="p">.</span><span class="nf">references</span> <span class="ss">:user</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="ss">:state</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span><span class="p">,</span> <span class="ss">index: </span><span class="kp">true</span>
<span class="n">t</span><span class="p">.</span><span class="nf">datetime</span> <span class="ss">:archive_notice_email_sent_at</span>
<span class="n">t</span><span class="p">.</span><span class="nf">datetime</span> <span class="ss">:archive_after</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">datetime</span> <span class="ss">:archived_at</span>
<span class="n">t</span><span class="p">.</span><span class="nf">datetime</span> <span class="ss">:delete_after</span><span class="p">,</span> <span class="ss">null: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">datetime</span> <span class="ss">:deleted_at</span>
<span class="n">t</span><span class="p">.</span><span class="nf">timestamps</span> <span class="ss">null: </span><span class="kp">false</span>
<span class="n">t</span><span class="p">.</span><span class="nf">index</span> <span class="p">[</span><span class="ss">:archive_after</span><span class="p">,</span> <span class="ss">:state</span><span class="p">]</span>
<span class="n">t</span><span class="p">.</span><span class="nf">index</span> <span class="p">[</span><span class="ss">:delete_after</span><span class="p">,</span> <span class="ss">:state</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>
<p>A <code class="language-plaintext highlighter-rouge">User</code> has many <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code>, but only one can be active at any time. The <code class="language-plaintext highlighter-rouge">state</code> column stores the state machine step where the <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> is. It applies the following sequence:</p>
<figure>
<img alt="UserDeletionFlow state machine: archive_eligible - deletion_eligible - completed - discarded" src="/assets/posts/2021-12-02-gdpr-account-deletion/user_deletion_flow_state_machine.png" width="270" />
<figcaption>
UserDeletionFlow state machine
</figcaption>
</figure>
<ul>
<li>When a user has been inactive for 3 years, a related <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> is created with a state <code class="language-plaintext highlighter-rouge">archive_eligible</code>. An archive notice is sent to the user, and the current date is stored in <code class="language-plaintext highlighter-rouge">archive_notice_email_sent_at</code>. The <code class="language-plaintext highlighter-rouge">archive_after</code> date is set to 1 month later and stored.</li>
<li>When the <code class="language-plaintext highlighter-rouge">archive_after</code> date is passed, the user’s latest activity is reassessed. If there was new activity, the <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> is <code class="language-plaintext highlighter-rouge">discarded</code>. Otherwise, the <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> state is set to <code class="language-plaintext highlighter-rouge">deletion_eligible</code>, and the <code class="language-plaintext highlighter-rouge">delete_after</code> date is computed based on several parameters and stored in the <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code>.</li>
<li>Once the <code class="language-plaintext highlighter-rouge">delete_after</code> date is passed, the <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> state is set to <code class="language-plaintext highlighter-rouge">completed</code>, and an anonymization process takes over to erase the user’s data.</li>
</ul>
<p>These steps are preformed by nightly cron jobs that query the database to retrieve all impacted accounts. If a user spontaneously requests the deletion of their data, their related <code class="language-plaintext highlighter-rouge">UserDeletionFlow</code> is immediately created in the <code class="language-plaintext highlighter-rouge">deletion_eligible</code> state, and the <code class="language-plaintext highlighter-rouge">delete_after</code> column is populated similarly to the passive deletion flow.</p>
<h3 id="anonymization-process">Anonymization Process</h3>
<p>When a user account gets deleted, we immediately create N <code class="language-plaintext highlighter-rouge">DataDeletionAttempt</code> for N user “areas”, and trigger asynchronous jobs to actually perform these <code class="language-plaintext highlighter-rouge">DataDeletionAttempt</code>. We have designed several data erasers, each taking care of anonymizing a specific area of the user’s data. They fall into 2 categories:</p>
<ul>
<li><strong>Internal erasers</strong></li>
</ul>
<p>Each of these bears the responsibility to anonymize one specific area of the user data stored in our database. For instance, there is an eraser for the user’s identity (<code class="language-plaintext highlighter-rouge">users.first_name</code>, <code class="language-plaintext highlighter-rouge">users.last_name</code>, <code class="language-plaintext highlighter-rouge">users.birth_date</code>,…), another one for the user’s cars (<code class="language-plaintext highlighter-rouge">cars.registration_plate</code>, <code class="language-plaintext highlighter-rouge">cars.vehicle_identification_number</code>, etc.). The erasers anonymize the data by replacing them with placeholders, either static or randomly generated, so that the database constraints and referential integrity are respected.</p>
<p>And since we’re using <a href="https://github.com/paper-trail-gem/paper_trail">PaperTrail</a> on some models to keep an audit trail of the changes that are applied, these erasers also have the responsibility to anonymize the versions that tracked some personal data changes.</p>
<p>Finally, some of these erasers remove the possible files that were stored for the deleted account.</p>
<ul>
<li><strong>Third party erasers</strong></li>
</ul>
<p>These erasers are clients to our 3rd party providers’ APIs, and request their users endpoint to request the user’s data deletion.</p>
<p>All erasers are run asynchronously, and some of the 3rd party erasers need a personal identifier from our database. For instance, to erase a user’s history on Zendesk, we first need their Zendesk identifier, which we can get by searching for the user’s email on Zendesk API. But it can happen that the user’s email has already been erased when the Zendesk eraser runs. To address this situation, we denormalize some deletion arguments into the related <code class="language-plaintext highlighter-rouge">DataDeletionAttempt</code>. When the eraser succeeds, this denormalized data is of course nullified to guarantee the full removal of personal data.</p>
<p>If any data eraser fails for any reason, we are notified on our bug tracking system, and we make sure to address the situation.</p>
<hr />
<p>This solution took some time to implement and still has room for improvement, but we are satisfied with the upside it already brings. It’s fully automated, flexible and easy to maintain. More importantly, we take some pride in continuously working on improving our compliance with European regulation requirements and make sure we provide a platform which is respectful of our users’ privacy.</p>
<p>The GDPR has been around for several years now, and as advocates of data privacy, we are convinced by the legitimacy of such a regulation. However, as good as this measure is from a user’s perspective, it comes with its own puzzles and challenges for an online service provider… Here we’ll try to describe the solution we implemented to deal with the user’s data deletion, which is one of the rights granted by the GDPR (<a href="https://ec.europa.eu/info/law/law-topic/data-protection_en">General Data Protection Regulation</a>) to any European user of a service collecting personal data. As a result, this piece does not try to cover all the implications of the GDPR, nor does it pretend to bring a one size fits all solution deal with user data deletion.</p>
Thu, 02 Dec 2021 00:00:00 +0000
https://getaround.tech/gdpr-account-deletion/
https://getaround.tech/gdpr-account-deletion/MySQL 8 FeaturesMichael Bensoussan<p>MySQL 8 was released in 2018 and is the next release after 5.7.<br />
Since 2018 and as of today there was 27 minor versions bringing the last version to 8.0.27. It’s important because MySQL did bring a lot of feature enhancements in these minor versions as we’ll see in the next part.</p>
<h2 id="common-table-expressions-ctes">Common Table Expressions (CTEs)</h2>
<p>A Common Table Expression (also known as <code class="language-plaintext highlighter-rouge">WITH</code> query) is a named temporary result set.<br />
It exists only in the scope of a single statement and can be later referred within that statement.<br />
You create a CTE using a <code class="language-plaintext highlighter-rouge">WITH</code> query, then reference it within a <code class="language-plaintext highlighter-rouge">SELECT</code>, <code class="language-plaintext highlighter-rouge">INSERT</code>, <code class="language-plaintext highlighter-rouge">UPDATE</code>, or <code class="language-plaintext highlighter-rouge">DELETE</code> statement.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494098-cb47d22c-3ee5-4f5b-b0a1-a4806418e636.png" alt="image" /></p>
<p>CTEs make a query <strong>more readable</strong>, allow to better <strong>organize long queries</strong> and better reflects human logic (like <strong>functions</strong> does). It’s particularly useful when you need to <strong>reference a derived table multiple times</strong> in a single query. There is also a specific category of CTEs called <strong>recursive CTEs</strong> that are allowed to reference themselves. These CTEs can solve problems that cannot be addressed with other queries.</p>
<p>This feature is available as of MySQL 8.0 and some edge cases have been handled in 8.0.19 (recursive <a href="https://dev.mysql.com/doc/refman/8.0/en/select.html">SELECT</a> with <code class="language-plaintext highlighter-rouge">LIMIT</code> clause).<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/with.html">here</a>.</p>
<h2 id="window-functions">Window Functions</h2>
<p>PostgreSQL’s documentation does an excellent job of <a href="http://www.postgresql.org/docs/9.1/static/tutorial-window.html">introducing the concept of Window Functions</a>:</p>
<blockquote>
<p>A <em>window function</em> performs a calculation across a set of table rows that are somehow related to the current row. This is comparable to the type of calculation that can be done with an aggregate function. But unlike regular aggregate functions, use of a window function does not cause rows to become grouped into a single output row — the rows retain their separate identities. Behind the scenes, the window function is able to access more than just the current row of the query result.</p>
</blockquote>
<p><img src="https://user-images.githubusercontent.com/10755/140494248-2f011038-ac39-480d-8848-0f60049512e4.png" alt="Untitled" /></p>
<p>Here is an example that shows how to compare each employee’s salary with the average salary in his or her department:</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494298-eec44fe9-a0f2-4594-a316-b0e765686e65.png" alt="image" /></p>
<p>MySQL comes with the following <code class="language-plaintext highlighter-rouge">WINDOW</code> functions:</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494455-1ee6bbd7-8712-40a3-8aed-087842a2b5e9.png" alt="Untitled" /></p>
<p><code class="language-plaintext highlighter-rouge">WINDOW</code> functions probably deserves an article on their own. You can find one <a href="https://www.mysqltutorial.org/mysql-window-functions/">here</a>.
This feature is available as of MySQL 8.0.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/window-functions.html">here</a>.</p>
<h2 id="expressions-as-default-values">Expressions as Default Values</h2>
<p>MySQL now supports use of “expressions” as default values. Expressions are distinguished by the use of parenthesis. <code class="language-plaintext highlighter-rouge">BLOB</code>, <code class="language-plaintext highlighter-rouge">TEXT</code>, <code class="language-plaintext highlighter-rouge">GEOMETRY</code>, and <code class="language-plaintext highlighter-rouge">JSON</code> data types can be assigned a default value only if the value is written as an expression, even if the expression value is a literal.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494552-be95bdb3-8561-44b3-bbea-0d131c351f14.png" alt="image" /></p>
<p>This feature is available starting MySQL 8.0.13. Before 8.0.13, the only expression supported was <a href="https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_current-timestamp">CURRENT_TIMESTAMP</a>.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html#data-type-defaults-explicit">here</a>.</p>
<h2 id="indexing-key-parts">Indexing key parts</h2>
<p>MySQL now supports indexing expression values referencing other keys rather than column values or column prefixes. Using parts of a function key allows you to index values that are not directly stored in the table.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494606-01083b5a-a2f4-45f9-bcb8-db24a311fbd0.png" alt="image" /></p>
<p>This feature is available starting MySQL 8.0.13. Prior to 8.0.13, you could achieve the same result by using virtual columns and indexing them but this is clearly way more straightforward.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-functional-key-parts">here</a>.</p>
<h2 id="descending-indexes">Descending Indexes</h2>
<p>MySQL now supports descending indexes (<code class="language-plaintext highlighter-rouge">DESC</code>). Previously, indexes could be scanned in reverse order but at a performance penalty. A descending index can be scanned in forward order, which is more efficient.</p>
<p>Descending indexes also make it possible for the optimizer to use multiple-column indexes when the most efficient scan order mixes ascending order for some columns and descending order for others.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494645-c0286532-d0a7-4bd1-8fe9-9e6f7f106737.png" alt="image" /></p>
<p>This feature is available as of MySQL 8.0.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html">here</a>.</p>
<h2 id="invisible-indexes">Invisible indexes</h2>
<p>MySQL now supports invisible indexes. An invisible index is not used by the optimizer, but is otherwise maintained normally.</p>
<p>It’s basically a toggle for indexes.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494817-54fc6a7f-3208-4230-bff0-959da39adb49.png" alt="image" /></p>
<p>Hidden index can be used to quickly test the impact of index deletion or index creation on query performance without index deletion and reconstruction. If the index is needed, it is good to set it visible again. This is undoubtedly very useful in large table testing, because it consumes performance for index deletion and addition of large table, and even affects the normal operation of the table.</p>
<p>This feature is available as of MySQL 8.0.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html">here</a>.</p>
<h2 id="explain-analyze-statement">EXPLAIN ANALYZE Statement</h2>
<p>This statement provides expanded information about the execution of <a href="https://dev.mysql.com/doc/refman/8.0/en/select.html">SELECT</a> statements in <code class="language-plaintext highlighter-rouge">TREE</code> format. This includes startup cost, total cost, number of rows returned by iterator, and the number of loops executed.</p>
<p><img src="https://user-images.githubusercontent.com/10755/140494857-854bfb84-bec7-4bc6-84b9-c9e127a578f1.png" alt="image" /></p>
<p>The cost is an arbitrary unit but it is consistent between queries and usually a good proxy to answer the question “is this query faster than this other query?”.</p>
<p>This feature is available in 8.0.18.<br />
Documentation is <a href="https://dev.mysql.com/doc/refman/8.0/en/explain.html#explain-analyze">here</a>.</p>
<h2 id="what-else">What else?</h2>
<p>MySQL 8 comes with tons of other features like:</p>
<ul>
<li>Better UTF8 support</li>
<li>New “role” system allowing to give/remove permissions to groups of people</li>
<li>User comments and user attributes</li>
<li>JSON enhancements ( <code class="language-plaintext highlighter-rouge">->></code> operator, <code class="language-plaintext highlighter-rouge">JSON_PRETTY()</code>, merge function, aggregation functions, JSON schema validation draft 4 …)</li>
<li>Better regexp support</li>
<li>…</li>
</ul>
<p>And of course performance and stability improvements.</p>
<p>The full list of changes is <strong><a href="https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html">here</a></strong>.</p>
<p>MySQL 8 was released in 2018 and is the next release after 5.7.<br />
Since 2018 and as of today there was 27 minor versions bringing the last version to 8.0.27. It’s important because MySQL did bring a lot of feature enhancements in these minor versions as we’ll see in the next part.</p>
Fri, 05 Nov 2021 00:00:00 +0000
https://getaround.tech/mysql-8-features/
https://getaround.tech/mysql-8-features/Your job is not just to write good codeMichael Bensoussan<p>This is an opinion piece.</p>
<p>I usually write about technical subjects but I recently wanted to formalize my opinion on the role of the software engineer and decided to publish it in the hope it could benefit some readers or trigger interesting discussions.</p>
<p><strong>TLDR;</strong></p>
<p>You have two jobs:</p>
<ul>
<li>Write good and maintainable code</li>
<li>Make it easy to work with you</li>
</ul>
<p>Writing good code is the easy part; it has challenges of course but most engineers struggle with the second part. That’s what I’ll be talking about.</p>
<p>There’s a famous quote <a href="https://skeptics.stackexchange.com/questions/19836/has-phil-karlton-ever-said-there-are-only-two-hard-things-in-computer-science">attributed to Phil Karlton</a>:</p>
<blockquote>
<p>There are only two hard things in Computer Science: cache invalidation and naming things.</p>
</blockquote>
<p>That quote is certainly true while purely discussing software engineering but if we expand to areas that <em>use</em> computer science, it’s clear that communication wins as the hardest part.</p>
<p>Let’s try to illustrate that point with some examples.</p>
<h2 id="code-reviews">Code reviews</h2>
<p>Reviewing someone’s work is a personal process, and criticism, whether it’s constructive or not, can be difficult to digest.
But it’s not just about practicing good “etiquette”—this is the easy part.</p>
<p>It’s also important to convey intention and adapt to your audience.
Let’s imagine a scenario with <em>Junior Billy</em> writing some code and <em>Senior Bob</em> doing a review using the following code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="n">ok</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">32</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">36</span>
<span class="p">}</span></code></pre></figure>
<p><strong>Senior Bob:</strong> These numbers are a bit puzzling!
And to which Junior Bob replies with the updated code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="n">ok</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">"OK"</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="s2">"NOT_OK"</span>
<span class="p">}</span></code></pre></figure>
<p>And a whole bunch of updates everywhere the function was called.</p>
<p>Was it necessary? Maybe. Was it needed for this PR? Probably not.</p>
<p>Senior Bob could have replied with:</p>
<p><strong>Senior Bob:</strong> These numbers are a bit puzzling. Maybe to make it a bit clearer you could introduce either a named variable to convey the meaning in this function or a constant to be used elsewhere in the code at a later stage or in a follow-up PR if you have the time.</p>
<p>Senior Bob could even give a code example; it would cost him less than a minute and would help Junior Bob grow and not lose hour(s) of his/her time on a wrong rewrite.</p>
<p>Going the extra mile in code reviews always pays and is another example where making sure everyone is on the same page is beneficial.</p>
<p><em>Note: Some people like to use an <a href="https://github.com/erikthedeveloper/code-review-emoji-guide">Emoji legend</a> to convey intention. This is especially useful when you or the person being reviewed have not completely mastered the language you’re doing the review in. In this case, codifying intention is a good approach to avoid misunderstandings.</em></p>
<h2 id="meetings">Meetings</h2>
<p>A lot of things can go wrong in a meeting, so communication is key here.
I won’t create a list of all the mistakes we, as developers, make. Instead, let’s focus on three points I see a lot of people struggling with:</p>
<h3 id="your-audience-matters">Your audience matters</h3>
<p>The importance of identifying the target audience is true for all kinds of meetings. But as developers, we have to learn to simplify concepts and keep things simple.</p>
<p>The next time you are in a meeting, take a step back and put yourself in the other person’s shoes. Think about how someone with no technical knowledge would perceive the information you are about to share with them.</p>
<p>🙅♂️ Don’t say, “This will probably raise the mean time by 2000ms for the whole website because the transaction will generate a table lock.”</p>
<p>👍 Say, “This is going to lead to a significant performance issue, which could slow down the system.”</p>
<p>This comes with some practice but you can practice with your spouse, friends, and family. This is key to being an effective communicator.</p>
<p>You can and probably should still teach your non-tech coworkers some “crucial” tech terms and, in some companies it is part of the company glossary or part of the onboarding (terms like “front-end”, “backend”, “iterate” or “mvp”, for example). Similarly developers should probably learn some business vocabulary to understand their coworkers’ jobs and challenges like “churn” or “ltv”.</p>
<h3 id="listening">Listening</h3>
<p>This may sound ridiculous to some but communication is not just about presenting ideas.
Careful listening requires energy and can be difficult and, in certain situations, boring or unpleasant, that doesn’t mean you can’t do it.</p>
<p>Avoid multitasking even if you can multitask or think you can.
Be patient and hold your tongue.<br />
<strong>And try to interpret what’s being said.</strong></p>
<p>Based on the purpose of your discussion, what the speaker cares most about, or what’s been said before, what does this mean? Use what you know to interpret what they’re trying to tell you, and ask questions if you need more details.</p>
<p><strong>Rephrase your understanding if needed.</strong></p>
<p>Also, assume the best. Assume that the intention of the person opposite to you is good and clever, even if you understand it at first as stupid and offensive. Again, repeat and rephrase.</p>
<p><strong>Meetings would go faster if people took the time to listen.</strong></p>
<p><em>Careful listening also applies to written communication; read twice before answering, put the message in context and don’t hesitate to ask questions before having a (strong) opinion.</em><br />
<em>Double check what you wrote before hitting send as well, disambiguation is often more painful on a written discussion.</em></p>
<h3 id="making-sure-everyone-is-on-the-same-page">Making sure everyone is on the same page</h3>
<p>Because of a failure to take the first two points into account, but also for countless other reasons, I have often witnessed people leaving a meeting with a different understanding of the next steps. And you might discover this days, weeks or even months later!</p>
<p>A good reflex is to always have a “Scribe”, someone taking notes during a meeting with a specific section for action items and their owners in the meeting notes.
I also like to keep the last part of meetings to focus on the recap and the next steps:</p>
<blockquote>
<p>So we only have five minutes left, let’s stop here. What are the next steps?</p>
</blockquote>
<p>And enumerate each of them with the people who own them.</p>
<p>Sometimes, it also makes sense to repeat and rephrase something to make sure everyone has the same understanding.</p>
<p>Let’s imagine a scenario with <em>Developer Jim</em> and <em>Product Manager Laura</em>:</p>
<p><strong>Developer Jim:</strong> It should take two days.<br />
<strong>Product manager Laura:</strong> Alright, so it’s Tuesday, I guess it should be ready by Thursday night for a release Friday morning?<br />
<strong>Developer Jim:</strong> Ah, no, I meant two days of full work. But Wednesday afternoon I do pair programming with Junior Billy on his assigned task and Thursday afternoon I have my performance review. I’d therefore like to avoid releasing on Friday. It should probably be released Monday morning!</p>
<p>Here, Laura had the right reflex by not assuming two days meant two calendar days and to make sure Laura and Jim had the same understanding of the situation.</p>
<hr />
<p>Communication is hard but going the extra mile always pays. Communication helps to build strong and healthy relationships, so don’t underestimate its power.</p>
<p>Most of the difficult problems engineers face include both technical and human components, and the greatest engineers can address both.</p>
<p>This is an opinion piece.</p>
Fri, 25 Jun 2021 00:00:00 +0000
https://getaround.tech/your-job-not-just-to-write-code/
https://getaround.tech/your-job-not-just-to-write-code/What's a good team process?Eric Favre<p>You may have heard about the <a href="https://intersol.ca/news/organizational-culture-and-the-5-monkeys-experiment/">five monkeys experiment</a>, a cautionary tale sometimes used to illustrate how we can get locked up in an organisational harness without sufficient hindsight, power or leeway to change the way things are.</p>
<p>This comparison may sound unflattering, but I have seen similar situations in previous lives, where a process I don’t understand is enforced, and I simply end up complying and assimilating it as normal. No-one is really responsible for that process, it may not even be relevant anymore, but it’s still there and is so much part of the habits that nobody thinks of questioning it, or the ones who do get dismissed with a “that’s how it is”.</p>
<p>Of course not all processes are like this. All organisations have frictions, and introducing processes or rituals is a sensible answer to some of them. When well thought out, good processes can avoid outage, enforce continuous improvement, ease up newcomers’ onboarding, pay out tech debt… Bottom line, they can help save time, secure the future and improve customer satisfaction.</p>
<p>So we’ve tried to formalise below a few basic rules that should help building a good team process.</p>
<h2 id="a-good-process-is-fully-understood">A good process is fully understood</h2>
<p>People will apply it more easily if they understand where it comes from. Making sure everyone knows what the process achieves, for whom, and the context in which it emerged will ease up a great deal the team’s adhesion to it. It’s therefore the manager’s responsibility to carefully onboard a newcomer on the existing processes, so they can adhere to them.</p>
<p>For instance, in a consulting company, when you’ve gotten a glimpse at the work of an accountant who needs to bill the clients at the end of each month, you’ll be much more inclined to fill your timesheets thoroughly.</p>
<h2 id="a-good-process-emerges-from-the-team-wholl-apply-it">A good process emerges from the team who’ll apply it</h2>
<p>A corollary to the previous point is that a process will be even better understood if the team that ends up applying it contributes to its definition. Even if the initial need is not theirs, it’s much more empowering and deemed to succeed if the team members actually comes up with the solution by themselves. The underlying need must first be stripped from any assumed solution. It can then be well explained and discussed beforehand, so that the team can fully appreciate what it’s about, and come up with the best solution to address it in their day to day context. Sometimes the solution may even appear to be much easier and more definitive than expected.</p>
<p>Team retrospectives are often a good opportunity to identify a friction that could be addressed by a new or updated process. It’s also ok to get inspiration from elsewhere, but beware of <a href="https://business-digest.eu/are-you-guilty-of-cargo-cult-thinking-without-even-knowing-it/?lang=en">cargo cult</a>. Don’t parachute new tools or processes into your organisation because you’ve heard about the results they’ve achieved somewhere else. Understand what they’re trying to achieve and how to adapt them to your specific context.</p>
<h2 id="a-good-process-is-challengeable">A good process is challengeable</h2>
<p>A process addresses a need at a given time. This need, and its context, will very likely evolve, possibly to the point where the process is no longer the best answer to a changed situation. So every now and then, it’s always healthy to discuss it, reassess its relevance, improve it or remove it altogether. A newcomer’s arrival in the team, providing a fresh perspective and some different experience, is often a good opportunity to challenge the status quo and make sure the team still has the best fit processes with regards to their tasks.</p>
<p>For instance, we have daily stand-ups with the whole dev team, so we can keep up with the other squad’s ongoing works, identify mutualisable effort, and ask for / offer help. This used to be done in a big conference room with the few remote workers connected to a meet, and that worked pretty well at that time. After the first Covid lockdown and related furlough, though, some of us were working part time, most of us fully remotely, and this format soon proved unadapted to this new situation. Instead of forcibly maintaining this ritual as is, we iterated on new formats until we reached the current one where we’re having synchronous fully online stand-ups twice a week, and async written stand-ups every other days. That works well now, and may change again as the situation evolves.</p>
<h2 id="a-good-process-is-well-tooled">A good process is well tooled</h2>
<p>It can already feel cumbersome enough to comply with some processes, so you should make sure you do everything possible to make it as seamless as possible. Automate everything you can, make sure the team members are reminded of the process when the time is right, document all useful resources, links and details in the reminder, and provide all the possible tooling that can help complete the process in an automated fashion. The automation also saves the manager or the stakeholder the painful task of manually chasing up the people involved in the process. Ideally the tooling is also flexible and accessible enough for the team to update and improve it by themselves as they get more familiar with the process.</p>
<p>The tooling must be helpful, and thought out to support the process ; the process must not be bent to suit the tool. Most recent softwares expose APIs to ease custom tooling. As an example, we built many custom integrations between Slack, Github, Bugsnag, New Relic, Google Suite… so that most unnecessary overhead is automated, and only the human added value is required from the teams.</p>
<hr />
<p>The key take-away here is for the team to <em>own</em> the processes, instead of letting them own the team. Start with the need, get the people who will end up addressing this need to help, figure out a solution and its tooling together, and iterate over it whenever the necessity arises.</p>
<p>You may have heard about the <a href="https://intersol.ca/news/organizational-culture-and-the-5-monkeys-experiment/">five monkeys experiment</a>, a cautionary tale sometimes used to illustrate how we can get locked up in an organisational harness without sufficient hindsight, power or leeway to change the way things are.</p>
Thu, 03 Jun 2021 00:00:00 +0000
https://getaround.tech/team-processes/
https://getaround.tech/team-processes/What I learned in two years at GetaroundRémy Hannequin<p>I joined Getaround, which was still named Drivy back then, two years ago. My previous and most extended professional experience had an internal organization that did not allow me to code full time, so many of my technical projects were actually side projects working alone.</p>
<p>Although I could choose my topics and constraints, working alone does not always help to learn good practices and tips that make a developer efficient and aware of the different technical challenges.</p>
<p>A few weeks ago, I took the time to understand how working in a (brilliant) team made me progress so much, not only as a Ruby developer but as a “Tech”. Here are a few topics that I learned or progressed on in the past two years.</p>
<h1 id="ruby-and-rails-related-apis">Ruby and Rails-related APIs</h1>
<h2 id="tap-and-then"><code class="language-plaintext highlighter-rouge">tap</code> and <code class="language-plaintext highlighter-rouge">then</code></h2>
<p>I love <a href="https://rubyapi.org/3.0/o/kernel#method-i-tap"><code class="language-plaintext highlighter-rouge">Kernel#tap</code></a> because it lets me compose objects with conditions without having to add multiple conditional blocks.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">elements</span>
<span class="n">arr</span> <span class="o">=</span> <span class="p">[</span><span class="n">first_element</span><span class="p">]</span>
<span class="n">arr</span> <span class="o"><<</span> <span class="n">second_element</span> <span class="k">if</span> <span class="n">available?</span>
<span class="n">arr</span>
<span class="k">end</span>
<span class="c1"># versus</span>
<span class="k">def</span> <span class="nf">elements</span>
<span class="p">[</span><span class="n">first_element</span><span class="p">]</span>
<span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="o">|</span><span class="n">arr</span><span class="o">|</span> <span class="n">arr</span> <span class="o"><<</span> <span class="n">second_element</span> <span class="k">if</span> <span class="n">available?</span> <span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>We can argue that this is neither necessary nor more performant, but most of us love Ruby because it makes us write concise and straightforward code. I am feeling more comfortable with less procedural code.</p>
<p>On the same topic, we also have <a href="https://rubyapi.org/3.0/o/kernel#method-i-then"><code class="language-plaintext highlighter-rouge">Kernel#then</code></a> which is comparable to <code class="language-plaintext highlighter-rouge">tap</code> but returns the result of the block. This is very helpful when building conditional requests without having to add big <code class="language-plaintext highlighter-rouge">if</code> blocks:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Order</span>
<span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">country: :fr</span><span class="p">)</span>
<span class="p">.</span><span class="nf">then</span> <span class="p">{</span> <span class="o">|</span><span class="n">relation</span><span class="o">|</span> <span class="n">completed?</span> <span class="p">?</span> <span class="n">relation</span><span class="p">.</span><span class="nf">order</span><span class="p">(</span><span class="ss">:completed_at</span><span class="p">)</span> <span class="p">:</span> <span class="n">relation</span> <span class="p">}</span>
<span class="p">.</span><span class="nf">last</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">then</code> is just an alias for <code class="language-plaintext highlighter-rouge">yield_self</code> introduced in Ruby 2.5.</p>
<h2 id="active-record-transactions">Active Record Transactions</h2>
<p><a href="https://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html">Transactions</a> enforce the integrity of the database by wrapping several SQL statements into one atomic action. I find them not only useful but sometimes even essential. In some cases, you have to ensure several changes were made successfully or to cancel them all.</p>
<p>The following example is quite explicit about how convinient transactions are:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">cancel!</span>
<span class="vi">@car</span><span class="p">.</span><span class="nf">available!</span>
<span class="n">create_ticket</span><span class="p">(</span><span class="vi">@car</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>If ticket creation were to fail, I am sure not to leave the car available or the order canceled, since the transaction will roll back.</p>
<h1 id="design-patterns">Design Patterns</h1>
<h2 id="command-pattern">Command pattern</h2>
<p>A lot of articles exist about this topic on the web. We even wrote about it a while ago in our <a href="https://getaround.tech/code_simplicity_command_pattern/">Code Simplicity series</a> by Nicolas.</p>
<p>The command pattern is a great way to extract business logic from controllers or even models, stay tied to the <a href="https://en.wikipedia.org/wiki/Single-responsibility_principle">Single-responsibility principle</a> and share a common API for service objects.</p>
<p>Although as a pattern, this one must be used carefully because it cannot resolve every situation. Jason Swett has even <a href="https://www.codewithjason.com/code-without-service-objects/">an interesting point of view</a> about using this pattern in the Rails community.</p>
<h2 id="form-objects-pattern">Form objects pattern</h2>
<p>A form object is a simple class that handles logic from a form submission. This class can be associated with the command pattern to share a common API with multiple form objects in your app.</p>
<p>Not only does this pattern allow you to extract business code from the controller and make it more testable, but it is also a great way to have different validations for the same model. You cannot always share a common form or even common validations depending on your action, for instance, when handling a user account. The rules applied to form parameters in a user registration are not the same as an account update.</p>
<p>Take the terms of service for instance. You probably want to ensure a <code class="language-plaintext highlighter-rouge">terms_of_service</code> parameter is present and <code class="language-plaintext highlighter-rouge">true</code> when signing up, but this requirement is unnecessary for a user updating her account. Having multiple form objects depending on the feature is a great help for this.</p>
<p>Jean also <a href="https://getaround.tech/sanitize-your-attributes/">wrote about it</a> on our blog a few years ago.</p>
<h2 id="facade-pattern">Facade pattern</h2>
<p>The <a href="https://refactoring.guru/design-patterns/facade/ruby/example">Facade pattern</a> is proper when (but not only) decoupling business code from third-party code.</p>
<p>Let’s take the example of a third-party web <abbr title="Application Programming Interface">API</abbr> prividing its own gem.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">car</span> <span class="o">=</span> <span class="no">GreatApi</span><span class="o">::</span><span class="no">Car</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span></code></pre></figure>
<p>Using it directly sometimes can be less maintainable as you don’t own its public API and are vulnerable to changes. What if you need to update this <code class="language-plaintext highlighter-rouge">great_api</code> gem for security reasons, but the gem changed its <code class="language-plaintext highlighter-rouge">Car::fetch</code> method to <code class="language-plaintext highlighter-rouge">Vehicle::get</code>? You would need to change every occurrence of <code class="language-plaintext highlighter-rouge">GreatApi::Car::fetch</code> in your business code to handle this breaking change.</p>
<p>Building a gateway around the gem ensures you to own it and encapsulate third-party code in one single place.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Getaround::Gateway::GreatApi</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">get_car</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
<span class="no">GreatApi</span><span class="o">::</span><span class="no">Car</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">car</span> <span class="o">=</span> <span class="no">Getaround</span><span class="o">::</span><span class="no">Gateway</span><span class="o">::</span><span class="no">GreatApi</span><span class="p">.</span><span class="nf">get_car</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span></code></pre></figure>
<h2 id="tell-dont-ask">Tell, don’t ask</h2>
<p>I try to remember the “Tell, don’t ask” principle when designing a brand new object to keep in mind what <abbr title="Object-oriented programming">OOP</abbr> is about: designing objects being able to interact. Therefore an object should describe itself its behavior rather than having a program asking it what it is composed of to predict its behavior.</p>
<p><a href="https://thoughtbot.com/blog/tell-dont-ask">This example of Thoughbot’s blog</a> is quite explicit:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Instead of asking the system monitor for temperature</span>
<span class="c1"># in order to trigger an alarm</span>
<span class="k">def</span> <span class="nf">check_for_overheating</span><span class="p">(</span><span class="n">system_monitor</span><span class="p">)</span>
<span class="k">if</span> <span class="n">system_monitor</span><span class="p">.</span><span class="nf">temperature</span> <span class="o">></span> <span class="mi">100</span>
<span class="n">system_monitor</span><span class="p">.</span><span class="nf">sound_alarms</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Let it internally handle the rules (attributes)</span>
<span class="c1"># and trigger the alarm (behaviour)</span>
<span class="k">class</span> <span class="nc">SystemMonitor</span>
<span class="k">def</span> <span class="nf">check_for_overheating</span>
<span class="k">if</span> <span class="n">temperature</span> <span class="o">></span> <span class="mi">100</span>
<span class="n">sound_alarms</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">system_monitor</span><span class="p">.</span><span class="nf">check_for_overheating</span></code></pre></figure>
<h1 id="gems">Gems</h1>
<h2 id="delayed-deprecations">Delayed deprecations</h2>
<p>Sometimes we want to ship fast, but we still want to ship well. There are some cases where we want to release code that is meant to be temporary, or to be reminded to monitor some behaviors once a feature has been live for a few weeks.</p>
<p>Temporary code is often associated with forgotten code and then technical dept, if not bugs. But <a href="https://github.com/drivy/delayed_deprecation">delayed deprecations</a> are a great way to keep a codebase clean month after month.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">DelayedDeprecation</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"Only for April fools day"</span><span class="p">,</span>
<span class="ss">reconsider_after: </span><span class="no">Date</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">2021</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="ss">owner: </span><span class="s2">"Alice"</span><span class="p">,</span>
<span class="p">)</span></code></pre></figure>
<p>Deprecations trigger notifications to their owners for both Ruby and JavaScript code. This can be useful to remind you to clean up a piece of code.</p>
<p>I enjoy using them because it helps me staying efficient while maintaining a clean codebase.</p>
<h2 id="feature-flipper">Feature flipper</h2>
<p>Another game-changer for our velocity and confidence when shipping new features is the <a href="https://github.com/jnunemaker/flipper">feature flipper</a>. It enables us to make some features available for a percentage of users or a percentage of time.</p>
<p>This is particularly useful to test changes and measure their impact without risking changing habits for all our users. If we need to urgently cancel a feature - because Murphy’s Law is always lurking - we can do so without deploying an urgent fix to hide it.</p>
<p>It doesn’t prevent us from being cautious and striving to ship high-quality tested code. Still, we are far more confident in ourselves when we know we can quickly handle unpredicted behaviors.</p>
<h2 id="timecop">Timecop</h2>
<p>Quite often, we have to test behaviors in the past or the future. Sometimes we also want to test a feature without time variation, for example, to avoid <a href="https://engineering.atspotify.com/2019/11/18/test-flakiness-methods-for-identifying-and-dealing-with-flaky-tests/">test flakiness</a>.</p>
<p><a href="https://github.com/travisjeffery/timecop">Timecop</a> is the perfect tool for this with a simple and comprehensive API.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">context</span> <span class="s2">"when rental is ended for a month"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"still respects some fundamentals rules"</span> <span class="k">do</span>
<span class="no">Timecop</span><span class="p">.</span><span class="nf">freeze</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">month</span><span class="p">.</span><span class="nf">from_now</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when booking failed 5 minutes ago"</span> <span class="k">do</span>
<span class="n">before</span> <span class="k">do</span>
<span class="no">Timecop</span><span class="p">.</span><span class="nf">travel</span><span class="p">(</span><span class="mi">5</span><span class="p">.</span><span class="nf">minutes</span><span class="p">.</span><span class="nf">ago</span><span class="p">)</span> <span class="k">do</span>
<span class="n">booking</span><span class="p">.</span><span class="nf">failure!</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s2">"created a notification"</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h1 id="good-practices">Good practices</h1>
<h2 id="zero-downtime-migrations">Zero downtime migrations</h2>
<p>Zero downtime migrations are a pretty common thing, but to be honest, I never had not the chance to work with this process before working at Getaround.</p>
<p>The rule of thumb is to ensure any migration being deployed is compatible with the code already running. For instance, you cannot deploy at once a migration renaming a table’s column and the code handling the new column name. There is a very high chance that someone will run the app while the migrations haven’t been run yet and the column name doesn’t refer to anything yet.</p>
<p>Simple caution must be taken with multiple deployments such as:</p>
<ol>
<li>Add a new column with the new name</li>
<li>Ensure both old and new columns are equally filled</li>
<li>Back-fill data from the old column to the new one</li>
<li>Stop using and referring to the old column</li>
<li><a href="https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-ignored_columns-3D">Ignore the old column</a></li>
<li>Remove the old column</li>
</ol>
<h2 id="specs-to-cover-future-changes">Specs to cover future changes</h2>
<p>Writing decoupled and reusable code is great. Ensuring this code will be properly used by others is even better. When I write code that can be shared or with variable data, I try to make sure nobody can add use cases that would break my code.</p>
<p>Let’s take an example, I am adding a <code class="language-plaintext highlighter-rouge">state</code> attribute to <code class="language-plaintext highlighter-rouge">Car</code> and I want to localize each state.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">en</span><span class="pi">:</span>
<span class="na">activerecord</span><span class="pi">:</span>
<span class="na">attributes</span><span class="pi">:</span>
<span class="na">car</span><span class="pi">:</span>
<span class="na">states</span><span class="pi">:</span>
<span class="na">active</span><span class="pi">:</span> <span class="s">Active</span>
<span class="na">deactivated</span><span class="pi">:</span> <span class="s">Not available</span></code></pre></figure>
<p>This is great, now I am able to use <code class="language-plaintext highlighter-rouge">I18n.t("activerecord.attributes.car.states.#{car.state}")</code>.</p>
<p>But what if two months later, another developer adds a <code class="language-plaintext highlighter-rouge">pending</code> state? It would break when somebody runs my code with a pending car, and I would only be warned about it when facing the bug itself.</p>
<p>To avoid this situation, when adding the new <code class="language-plaintext highlighter-rouge">state</code> attribute, I also add specs to ensure all states have an associated translation:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Car</span><span class="p">.</span><span class="nf">states</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">state</span><span class="p">,</span> <span class="n">_</span><span class="o">|</span>
<span class="n">it</span> <span class="s2">"has an associated translation for </span><span class="si">#{</span><span class="n">state</span><span class="si">}</span><span class="s2">"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="no">I18n</span><span class="p">.</span><span class="nf">t</span><span class="p">(</span><span class="s2">"activerecord.attributes.car.states.</span><span class="si">#{</span><span class="n">state</span><span class="si">}</span><span class="s2">"</span><span class="p">)).</span><span class="nf">not_to</span> <span class="n">raise_error</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h2 id="rspec-mocking">RSpec mocking</h2>
<p>Some people love it, some people don’t; in either case, we have to admit RSpec mocking is quite powerful. My perspective on the subject is to avoid mocking in feature specs as we want to stay as close as possible to a real-world example. When I have too much mocking to do to test a method, I probably need to think about the method/class dependencies.</p>
<p>Anyway, if you decide to use mocking, RSpec is a sweet candy. It helps you write difficult test cases with complex dependencies without instantiating tons of real objects and data.</p>
<p>Let’s take an example where I need an object to return a particular value. But this method has complex rules to return this value:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">pro?</span>
<span class="n">validated?</span> <span class="o">&&</span> <span class="n">bank_account</span><span class="p">.</span><span class="nf">allowed?</span> <span class="o">&&</span> <span class="n">country</span><span class="p">.</span><span class="nf">enables_pro?</span> <span class="o">&&</span> <span class="n">electric_vehicles</span><span class="p">.</span><span class="nf">any?</span> <span class="c1"># && ...</span>
<span class="k">end</span></code></pre></figure>
<p>It is expensive to write and compute all requirements for this method, and I may even want it to return different results. I don’t want to be validating this <code class="language-plaintext highlighter-rouge">pro?</code> method neither; this is not the purpose of my test. With mocking, I can <code class="language-plaintext highlighter-rouge">allow</code> this object to <code class="language-plaintext highlighter-rouge">receive</code> this method and <code class="language-plaintext highlighter-rouge">return</code> the value I need for my test, instead of the value it would have returned with its default state.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">context</span> <span class="s2">"when owner is pro"</span> <span class="k">do</span>
<span class="n">before</span> <span class="k">do</span>
<span class="n">allow</span><span class="p">(</span><span class="n">owner</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:pro?</span><span class="p">).</span><span class="nf">and_return</span><span class="p">(</span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span></code></pre></figure>
<p>Once again, let’s not forget that this powerful tool must be used cautiously; if an object is hard to unit test, maybe it is too much coupled with another object, or the abstraction is wrong.</p>
<h2 id="auto-document-a-base-class-with-notimplementederror">Auto document a base class with <code class="language-plaintext highlighter-rouge">NotImplementedError</code></h2>
<p>Finally, ensuring the next developer, who could be yourself 6 months from now, is using your base class properly. When creating a base class meant for inheritance, you may need that its children implement a method.</p>
<p>Using the <code class="language-plaintext highlighter-rouge">NotImplementedError</code> standard error is a good way to ensure the method is implemented and to document it as necessary for child instances.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Car</span>
<span class="k">def</span> <span class="nf">engine</span>
<span class="k">raise</span> <span class="no">NotImplementedError</span><span class="p">,</span> <span class="s2">"#engine must be implemented on Car's children instances"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">UrbanCar</span> <span class="o"><</span> <span class="no">Car</span>
<span class="k">end</span>
<span class="no">UrbanCar</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">engine</span>
<span class="c1"># => NotImplementedError is raised</span></code></pre></figure>
<h1 id="conclusion">Conclusion</h1>
<p>I could add many more topics, even some simpler ones. With these tips, I am more confident now than I was 2 years ago, and I am looking forward to learning more in the years to come.</p>
<p>Of course, some may seem common sense to you, or even not necessary. I may also have forgotten good practices that look like a <em>must have</em> to you. If so, please feel free to reach us and debate.</p>
<p>I joined Getaround, which was still named Drivy back then, two years ago. My previous and most extended professional experience had an internal organization that did not allow me to code full time, so many of my technical projects were actually side projects working alone.</p>
Tue, 20 Apr 2021 00:00:00 +0000
https://getaround.tech/what-i-learnt-in-two-years-at-getaround/
https://getaround.tech/what-i-learnt-in-two-years-at-getaround/How we handle incidents at GetaroundMiguel Torres<p>At Getaround, like at any other company, we sometimes experience incidents that negatively affect our product.</p>
<p>A couple of weeks ago from the time of writing, I released a feature that contained a seemingly harmless SQL query that returned the total balance of a user. This calculation was previously made on the fly with Ruby every time a user loaded a page where this was needed. This was particularly problematic with owners that had many cars and the query was slow to load, sometimes causing timeouts. So the commit that I had deployed was meant to counter this problem by using a table that was built exactly for this purpose. Instead, it brought the CPU utilisation to go over 90% and slowed down all database queries, causing timeouts all over the site</p>
<h2 id="finding-out-and-responding">Finding out and responding</h2>
<p>We use <a href="https://grafana.com/">Grafana</a> for monitoring, and we use a Slack webhook integration that let us know when certain events happen. In this case, 13 minutes after my code was live, we get a notification on Slack on a dedicated channel letting us know that there is a problem</p>
<figure>
<img alt="ALT" src="/assets/posts/2021-04-05-incident-handling-at-getaround/incident-slack-alert.png" />
<figcaption>
Automated Slack alert to let the team know there is a problem
</figcaption>
</figure>
<p>After looking at the CPU utilization graph and some further investigation, my commit is rolled back.</p>
<figure>
<img alt="ALT" src="/assets/posts/2021-04-05-incident-handling-at-getaround/incident-cpu-graph.png" />
<figcaption>
Red dotted line just before 15:30 indicates the faulty deploy
</figcaption>
</figure>
<p>Despite the rollback, MySQL was still busy running the existing queries and the CPU utilization did not diminish, but after a couple of back and forth, and communicating with the rest of the company what was going on in the perfectly named <code class="language-plaintext highlighter-rouge">#war-room</code> channel in Slack, the issue was under control in less than an hour 🎉</p>
<figure>
<img style="height: 20em;" alt="ALT" src="/assets/posts/2021-04-05-incident-handling-at-getaround/incident-cpu-graph-2.png" />
<figcaption>
Red dotted line just before 15:30 indicates the faulty deploy
</figcaption>
</figure>
<h3 id="after-the-incident">After the incident</h3>
<p>At Getaround we keep a record of all the technical incidents that have happened, and each entry on the list contains a couple of things:</p>
<ul>
<li>A <strong>description</strong> of the problem</li>
<li>The <strong>timeline</strong> of events</li>
<li>The <strong>root cause</strong></li>
</ul>
<p>This is also called a <a href="https://www.pagerduty.com/resources/learn/incident-postmortem/">Postmortem</a> and it is an important step after an incident. The goal being to be able to share knowledge with your colleagues and try to prevent it from happening in the future as much as we can, all while acknowledging that incident are a normal part of software development. It is essential that a <a href="https://codeascraft.com/2012/05/22/blameless-postmortems/">blameless culture</a> exists in the company in order to be able for everyone to write in detail freely about what went wrong so we can learn from our mistakes. The Post mortem for this incident in particular would look similar to this:</p>
<h2 id="post-mortem-example">Post Mortem Example</h2>
<h2 id="timeline">Timeline</h2>
<p>15:24 - A release was made containing the commit which included the slow query</p>
<p>15:37 - Team was alerted on Slack about about a high CPU load</p>
<p>15:44 - The team identified the issue (high CPU load) to be related with the release at 15:24</p>
<p>15:46 - Commit rolled back</p>
<p>15:52 - After noticing that the CPU usage does not decrease, even after the rollback, it is identified that the db is still busy running the queries that it had enqueued</p>
<p>16:00 - Incident opened in <a href="https://newrelic.com/">New Relic</a> (monitoring tool used at Getaround)</p>
<p>16:15 - Command launched to kill lingering db queries</p>
<p>16:23 - CPU load back to normal</p>
<p>23:48 - Incident on New Relic closed</p>
<h2 id="impact"><strong>Impact</strong></h2>
<p>User searches started timing out and there was an uptick of incidents on <a href="https://www.bugsnag.com/">Bugsnag</a></p>
<h2 id="what-caused-the-incident"><strong>What caused the incident</strong></h2>
<p>The combination of an underperforming query and the fact that it was a query used across many different placed caused the overload.</p>
<p>The offending code was:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Balance</span>
<span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">entity_type: </span><span class="s1">'User'</span><span class="p">)</span>
<span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">target_type: </span><span class="s1">'User'</span><span class="p">)</span>
<span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">entity_id: </span><span class="n">user_id</span><span class="p">)</span>
<span class="p">.</span><span class="nf">group</span><span class="p">(</span><span class="ss">:country</span><span class="p">,</span> <span class="ss">:entity_id</span><span class="p">)</span></code></pre></figure>
<p>The problem was is not obvious at first, but after trying to understand <a href="https://www.exoscale.com/syslog/explaining-mysql-queries/">what the query was doing with <code class="language-plaintext highlighter-rouge">EXPLAIN</code></a> it turns out that the query was not fully taking advantage of all of the indexes that we had in place, which means it scanned way more columns than it needed to. After the query was optimised to take advantage of the indexes, the number of examined rows returned by running <code class="language-plaintext highlighter-rouge">EXPLAIN</code> came down from 3468 to 4. So… yeah, big improvement.</p>
<p>Although we are able to objectively point towards the code that caused the incident, there are also other, more subtle factors that contributed for this code to be overlooked and committed. For example:</p>
<ol>
<li>This query is only used in the back office used only by administrators, where the volume is not as high as production and performance is not as big of a concern as it can be for other user facing code</li>
<li>The table used (<strong>Balances</strong>) was born as an attempt to speed up balance calculations, since previously balances were calculated every single time, and it was assumed that the fact that we were replacing a long ruby calculation with a SQL query would inherently be a performance boost</li>
</ol>
<h2 id="mitigation"><strong>Mitigation</strong></h2>
<p>The rollback of the offending code caused the queries to stop enqueuing themselves on an already stressed database and the killing of lingering processes managed to solve the incident completely. After finding the ids of the processes to kill, the following command was executed:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nb">cat</span> ~/Downloads/ids-to-kill | <span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> a<span class="p">;</span> <span class="k">do </span><span class="nb">echo</span> <span class="nv">$a</span> | mysql <span class="nt">--user</span> <USER> <span class="nt">--host</span> <HOST> <span class="nt">--password</span><span class="o">=</span><PASSWORD> <span class="p">;</span> <span class="k">done</span></code></pre></figure>
<h2 id="permanent-solution"><strong>Permanent solution</strong></h2>
<p>After finding out that using a <code class="language-plaintext highlighter-rouge">where</code> condition for entities was an overkill, the query was rewritten to take advantage on the existing indexes and a promising indicator that it was a good solution was that the rows to be examined dropped from 3468 to only 4</p>
<h2 id="lessons-learned-and-possible-action-items"><strong>Lessons learned and possible action items</strong></h2>
<ul>
<li>Even for “low volume” queries (i.e. queries used only in admin), it is very important to make sure that queries are performant</li>
<li>Tweak the internal Developer’s Checklist and/or the PR draft document to include soft reminders to think about performance. (For example: <em>Running explain to new queries and scan through the output</em> or <em>Making sure that the queries used can leverage on existing indexes or create new ones if they don’t exist and will be heavily read</em></li>
</ul>
<h2 id="links-and-images"><strong>Links and images</strong></h2>
<blockquote>
<p>In the original Post-Mortem I added the links to relevant places like the Slack, or the New Relic incident link, but in this public version I’ll omit some of them 😬</p>
</blockquote>
<figure>
<img style="height: 20em;" alt="ALT" src="/assets/posts/2021-04-05-incident-handling-at-getaround/incident-cpu-graph-3.png" />
<figcaption>
First dotted line just before 15:30 indicates the faulty commit deploy and the one at 15:50 is the rollback deploy
</figcaption>
</figure>
<figure>
<img style="height: 20em;" alt="ALT" src="/assets/posts/2021-04-05-incident-handling-at-getaround/incident-search-controller-response-time.jpeg" />
<figcaption>
Search Controller response time during the incident
</figcaption>
</figure>
<h2 id="takeaways">Takeaways</h2>
<p><strong>Postmortems</strong> are a great practice that help make the best out of bad situations when they happen, since incidents are not a matter of wether they will happen, but of when they will happen, and the best way to minimize the potential negative impact is for the team to be aware of potential pitfalls, this requires that everyone can feel free to go into detail about how their actions led to an undesirable outcome.</p>
<p>At Getaround, like at any other company, we sometimes experience incidents that negatively affect our product.</p>
Mon, 05 Apr 2021 00:00:00 +0000
https://getaround.tech/incident-handling-at-getaround/
https://getaround.tech/incident-handling-at-getaround/How we ran our last hack dayMichael Bensoussan<p>For the last few years, we did about one hackday a year where the whole team gathered together in Paris from different areas of France.</p>
<p>For one day, participants were given creative freedom to create a demo-able, team-based Getaround-related project. People try new technologies, explore new ideas, get coffee together and we all end up debriefing with a cold beverage and a cheese board 🧀🍻.</p>
<figure>
<img alt="hackday" src="/assets/posts/hack-day/team.jpg" />
<figcaption>
<a href="/drivy-hack-day/">Writeup</a> of our first hackday.
</figcaption>
</figure>
<p>Despite the pandemic and the fact that we’re currently all working remotely, we didn’t see any reason to miss out on the fun. Thus, the first fully-remote Hackday was born!</p>
<h3 id="the-organization">The organization</h3>
<p>A day is short and to come up with something meaningful, you need some organization.<br />
When I say meaningful, I don’t mean some <em>code</em> you could put in production at the end of the day but really anything that would make this day different from the others; work with a coworker you never work with and learn he/she likes cats, discover and play with a new technology or plant a seed for a future feature.</p>
<p>So, to make sure the day is used to its fullest the objective was mostly to have groups and ideas ready for d-day.<br />
One month before the event, we created a slack channel for people to pitch ideas and find their crew.</p>
<p>We also had a spreadsheet accessible to the whole company - not only people participating - to submit ideas.</p>
<h3 id="the-format">The format</h3>
<p>We had 32 people participating from the tech and data teams split into 12 groups of 2 to 5 people.</p>
<p>We met in the morning on a Google Meet and, while people finished their breakfast, did a small round table to explain what we’d be working on during the day.</p>
<p>People then gathered in their own Meet to work together during the day. There also was a global “breakroom” to chitchat during breaks.</p>
<p>Later in the evening, we had a virtual beer to debrief the day and chill out 🍻.</p>
<h3 id="the-results">The results</h3>
<p>Some projects warrant a blog post in their own right, but in the meantime here is a quick overview and some demos.</p>
<h4 id="-ios-app-clip">📱 iOS App Clip</h4>
<p>Jean-Élie created a proof-of-concept allowing anyone to scan QR codes off of in-street-cars and book them instantly 😍<br />
He created an <a href="https://developer.apple.com/app-clips/">iOS App Clip</a> so that users don’t need to download the full aplication.</p>
<figure>
<img alt="iOS App Clip" src="/assets/img/app_clip.gif" />
</figure>
<h4 id="-android-auto-companion-application">🚗 Android Auto Companion application</h4>
<p>Quentin created an <a href="https://www.android.com/auto/">Android Auto companion</a> displaying trip information (return date and place, kms & fuel at checkin, assistance, …), navigation and notifications (missing check-in information, fuel before return reminder, …).</p>
<figure>
<img alt="Android auto companion" src="/assets/img/android_auto.gif" />
</figure>
<h4 id="-cobalt-web-ide">🖼 Cobalt Web IDE</h4>
<p>Romain and Thibaud worked on a no-code React app connected to Android to build a full Android native page based on our <a href="https://getaround.tech/mobile-api-driven/">API Driven UI</a> and our <a href="https://cutt.ly/cobalt-eu">design system</a> ✨</p>
<figure>
<img alt="Cobalt Design System" src="/assets/img/cobalt_ide.gif" />
</figure>
<h4 id="️-green-search">♻️ “Green Search”</h4>
<p>Emily, Alice, Rémy, Benjamin, Hugo and Camille (😅) built a car ecoscore and implemented it in our search pages to direct demand towards greener supply.</p>
<p>Here’s the full presentation:</p>
<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/qgcWsCZZVgY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</center>
<h4 id="-type-signatures-with-ruby">💎 Type signatures with Ruby</h4>
<p>Miguel, Howard and Eric spent the day exploring type signatures with Ruby.<br />
They played with <a href="https://sorbet.org/">Sorbet</a> and <a href="https://github.com/ruby/rbs">rbs</a>.</p>
<p>Here’s the full presentation:</p>
<center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/TYOMHynPIwk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</center>
<p>But also, all these other projects:</p>
<p>🧬 Use of a <a href="https://dgraph.io/">graph database</a> dgraph to detect risky profiles<br />
👀 Use of <a href="https://fast.ai/">computer vision</a> to autofill car listings<br />
🤑 Create a car listing in the <a href="https://web3js.readthedocs.io/en/v1.3.4/">Blockchain</a> with Ethereum<br />
🍕 Foodaround, an app to share recipes between employees<br />
💬 A notification center for our Android & iOS apps<br />
🐦 A system to analyse, categorise and apply sentiment analysis on Twitter messages<br />
🔬 NLP on user-generated content; categorize reviews and support tickets, and recommend macros to answer our clients</p>
<figure>
<img alt="Hackday Slides" src="/assets/img/hackday-slides.jpg" />
<figcaption>
Slides from different projects presented at our Demo Day
</figcaption>
</figure>
<p>Skills were sharpened, bonds were strengthened, beers were enjoyed - it was a blast!</p>
<p>Thanks for reading 🍻</p>
<figure>
<img alt="hackday" src="/assets/img/hackday_beers.jpg" />
<figcaption>
🍻
</figcaption>
</figure>
<p>For the last few years, we did about one hackday a year where the whole team gathered together in Paris from different areas of France.</p>
Wed, 24 Mar 2021 00:00:00 +0000
https://getaround.tech/hackday-4-full-remote/
https://getaround.tech/hackday-4-full-remote/Predicting slack emoji reactions with machine learningAdrien Siami<p>Every year at getaround, we (The engineering team) take part in what we call a <a href="/drivy-hack-day/">“Hack Day”</a>.We can work on a subject of our choice for a day, in a team of developers.</p>
<p>We can work on pretty much anything we want as long as it is remotely related to Getaround. It could be exciting beta features, or tooling to make our lives better. It does not necessarily needs to be shippable.</p>
<p>This time, I wanted to work on something both fun and challenging.
I always wanted to look into machine learning but never got the chance, so it didn’t took me long to find a fun topic to work on.</p>
<p>As a slack emoji reactions power user, I thought building a bot that could react on slack messages with a relevant emoji would be very <del>useful</del> funny.</p>
<p><strong>Disclaimer</strong>: This approach is most likely far from good, this is the result of 3 full-stack engineers working for 8 hours on a topic they didn’t know anything about beforehand.</p>
<h1 id="what-are-we-building">What are we building?</h1>
<ul>
<li>In the contextual menu of a message, we want a new action to trigger an emoji reaction.</li>
<li>The emoji reaction has to be relevant to the message being reacted on.</li>
<li>To build relevance, we are going to use the existing data of emoji reactions and messages by our team, into a machine learning model.</li>
</ul>
<h1 id="what-tools-are-we-going-to-use">What tools are we going to use?</h1>
<ul>
<li>Ruby, our favorite swiss knife.</li>
<li>A neural network, which after a bit of research seemed to be an easy and “good enough” solution.</li>
<li><a href="https://github.com/tangledpath/ruby-fann">ruby-fann</a> to build the neural network in ruby</li>
<li><a href="https://github.com/brenes/stopwords-filter">stopwords-filter</a> to remove stop words from the sentences</li>
<li><a href="https://github.com/diasks2/pragmatic_tokenizer">pragmatic_tokenizer</a> to turn our sentences into a list of words, without punctuation.</li>
<li><a href="https://github.com/aurelian/ruby-stemmer">ruby-stemmer</a> to find the “<a href="https://en.wikipedia.org/wiki/Word_stem">stem</a>” of the words in our sentences</li>
</ul>
<h1 id="neural-networks-crash-course">Neural networks crash course</h1>
<p>The following video helped us a lot to grasp the concepts of neural networks :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/GvQwE2OhL8I" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<p>What I retained of this video, which may not be 100% correct but was enough to build this project, is as follows :</p>
<p>A neural network makes use of a graph data structure to predict a different set of outputs, given a different set of inputs.You have to choose the number of inputs (only rational numbers) and outputs (between 0 and 1).Those are the input layer and output layer, there are also one or more hidden layers in the middle, where the “magic happens”.</p>
<p>My rough understanding is that when you train a neural network, you basically try to find mathematical correlations between the input and the output, you kind of bruteforce coefficients which will transform your inputs into your outputs. There is also things such as the activation function that are taken into account.</p>
<p>Choosing the good number of inputs and outputs is primmordial, as well as number of hidden layers, and depends a lot on the shape of your data and what you expect to get from it.</p>
<p>For our project, as inputs we have a list of words, and as output we want one emoji.</p>
<p>Since the number of inputs and outputs has to be fixed, here is what we decided to do :</p>
<ul>
<li>We took the top 200 most used stems and used them as input, the value will be the number of times they appeared in the message.</li>
<li>We took the top 50 most used emoji reactions, the expected output will either be 1 (the emoji was used as a reaction) or 0 (the emoji was not used as a reaction).</li>
</ul>
<h1 id="importing-the-data-from-slack">Importing the data from slack</h1>
<p>First step is to get enough data to work with, we used the <a href="https://github.com/slack-ruby/slack-ruby-client"><code class="language-plaintext highlighter-rouge">slack-ruby-client</code></a> gem to fetch messages on a selected list of channels, we only kept the messages with emoji reactions.</p>
<p>We stored the message content and the emoji reactions in a JSON file.</p>
<h1 id="training-the-neural-network">Training the neural network</h1>
<p>The interesting code is <a href="https://gist.github.com/Intrepidd/9d6be0882d73e13dfb83240fd6ba0190">available here</a>.</p>
<p>One important thing to do when working with machine learning is to control how well your model is doing. An usual approach is to keep a certain amount of your data for testing purposes. That’s what we do in <code class="language-plaintext highlighter-rouge">train.rake</code>. We keep 10% of the data apart in another file, and we don’t use this data for training. Later on, we can try to apply our model on this data and see if the results make sense.</p>
<p>To be quite honest, we didn’t obtain a very good result statistically speaking. However, the emoji predictions were quite hilarious, so we decided to stick with it.</p>
<h1 id="plug-it-into-slack">Plug it into slack</h1>
<p>Then it’s just a matter of plumbing, we created a slack app and made use of <a href="https://api.slack.com/interactivity/shortcuts">message shortcuts</a>.</p>
<p>Each slack message now has a link in the contextual menu. When clicking on this link, a request is sent to our app from slack, with a payload containing informations about the clicked message. Then, we clean the message with the same process we used for training, and we run it throught the machine learning model.</p>
<p>Finally, we add a reaction to the message, from our bot API key.</p>
<p><img src="https://dzwonsemrish7.cloudfront.net/items/0M1h2m1A1s1Q3Y350o2S/Screen%20Recording%202020-03-20%20at%2002.47%20PM.gif?v=dae6de94" alt="Emoji predictor demo" /></p>
<h1 id="conclusion">Conclusion</h1>
<p>Writing this bot was very fun and informative, and made us realize that machine learning concepts, although obscure from the uninformed eye, can be grasped pretty quickly.</p>
<p>If you know about machine learning, I’d love to know what would be the best way to have done that, feel free to leave a comment in <a href="https://gist.github.com/Intrepidd/9d6be0882d73e13dfb83240fd6ba0190">the gist I shared</a>.</p>
<p>Every year at getaround, we (The engineering team) take part in what we call a <a href="/drivy-hack-day/">“Hack Day”</a>.We can work on a subject of our choice for a day, in a team of developers.</p>
Mon, 01 Jun 2020 00:00:00 +0000
https://getaround.tech/emoji-machine-learning/
https://getaround.tech/emoji-machine-learning/BookkeepingNicolas Zermati<p>All companies handle money <strong>transactions</strong>. The law enforces companies to maintain and
publish records of those transactions. Those records are called <strong>books</strong> and the act
of maintaining them is called <strong>bookkeeping</strong>.</p>
<p>At Getaround, our business-model generates a lot of transactions. We receive and route
customer’s money to various partners. For that part, we decided to build digital books
in our platform in order to do its part in the company’s bookkeeping.</p>
<p>In this article, I’ll tell you about bookkeeping techniques from the 15th century and
how close they are to programming concepts we use everyday.</p>
<h2 id="double-entry-bookkeeping">Double-entry-bookkeeping</h2>
<p>This technique was formalized in the 15th century by Luca Pacioli. It has been used way
before that though. Pacioli’s work has been translated by Jeremy Cripps in an <a href="https://jeremycripps.com/Summa2.pdf">online
available text</a>. In a nutshell, it is a simple, powerful, flexible, and
mature accounting system. It describes various documents, called <strong>books</strong>:</p>
<ul>
<li>the inventory,</li>
<li>the memorandum,</li>
<li>the journal, and</li>
<li>the ledger.</li>
</ul>
<p>Those documents provides guidelines to record and control economic activities.</p>
<p>I’ll skip the inventory and introduce only the last three books.</p>
<h3 id="memorandum">Memorandum</h3>
<p>This book keeps track of <strong>business operations</strong> in a chronological order.
Each entry includes <strong>all the details</strong> related to the operation:
« The who, what, why, how, when, and where need to be answered ».</p>
<p>An entry, page <code class="language-plaintext highlighter-rouge">53</code> of the Memorandum, could look like this:</p>
<center><img src="/assets/posts/2020-03-01-double-entry-bookkeeping/memorandum.svg" alt="split ledger view" /></center>
<p>You can see three entries:</p>
<ul>
<li>John Doe paying for an upcoming rental,</li>
<li>Nicolas Z. being paid for this rental when it is done, and</li>
<li>Insurance being paid commissions for a bunch of rentals.</li>
</ul>
<p>Each entry match a different business operation.</p>
<p>Because merchants were not always around, their employees would write those entries in the
Memorandum. The knowledge from the Memorandum will be transferred to the Journal and to the
Ledger later.</p>
<h3 id="journal">Journal</h3>
<p>Each Memorandum entry leads to at least one entry in the Journal. Each entry in the Journal
represents a transaction, it has multiple lines and each line has:</p>
<ul>
<li>an <strong>account</strong>, prefixed by <code class="language-plaintext highlighter-rouge">To</code> for debits and by <code class="language-plaintext highlighter-rouge">By</code> for credits,</li>
<li>a reference to the <strong>Memorandum page number</strong> (<code class="language-plaintext highlighter-rouge">M-53</code>),</li>
<li>a reference to the <strong><em>Ledger Account</em> page number</strong> (<code class="language-plaintext highlighter-rouge">L-000001-1</code>),</li>
<li>an <strong>amount</strong>, either in the credit or in the debit column, and</li>
<li>more details about the transactions.</li>
</ul>
<p>Here is the page <code class="language-plaintext highlighter-rouge">34</code> of the Journal:</p>
<center><img src="/assets/posts/2020-03-01-double-entry-bookkeeping/journal.svg" alt="split ledger view" /></center>
<p>We have four entries:</p>
<ul>
<li>John receives €100 from Stripe, following his credit-card payment,</li>
<li>John splits this €100 between each party involved in Rental #5043,</li>
<li>Nicolas Z. gives €60 to Stripe, to receive a bank transfer, and</li>
<li>Insurance gives €1,000 to Stripe too, to receive a bank transfer.</li>
</ul>
<p>When an account <strong>gives</strong> money, we record a <strong>credit</strong>.
When an account <strong>receives</strong> money we record a <strong>debit</strong>.</p>
<p>It can be a bit surprising to see that John starts by receiving €100.
From John’s perspective, he just gave something.
But from the merchant’s perspective, John’s <strong>internal account</strong> just received €100!
Same when we want to pay €60 to Nicolas. It is €60 that are going out from Nicolas Z.’s
<strong>internal account</strong>.</p>
<p>To ensure consistency, there is a single rule: <strong>for a transaction, the credits must
be equal to the debits</strong>. This rule makes it easy to avoid mistakes like disappearance of
resources.</p>
<h3 id="ledger">Ledger</h3>
<p>The Ledger has many sections representing <strong>Ledger Accounts</strong>. Each account has a
reference, in this example, I used a number (<code class="language-plaintext highlighter-rouge">#123456</code>) and a name.</p>
<p>The goal of a Ledger Account is to keep track of operations that happen on that specific
account. The choices of the accounts will have an impact on what we’ll be able to monitor
with the Ledger.</p>
<p>Each entry in the Ledger is coming from a line in the Journal. It has:</p>
<ul>
<li>a reference to the <strong>Journal page number</strong> (<code class="language-plaintext highlighter-rouge">J-34</code>),</li>
<li>a description mentioning the <strong>other party</strong> from the Journal transaction and</li>
<li>an <strong>amount</strong>, either in the credit or in the debit column.</li>
</ul>
<p><strong>The sum of debits, from all Ledger Accounts, must be the same as the sum of credits.</strong></p>
<center><img src="/assets/posts/2020-03-01-double-entry-bookkeeping/ledgers.svg" alt="split ledger view" /></center>
<p>We our Ledger Accounts:</p>
<ul>
<li>John Doe received €100 from Stripe and then gave it all to others accounts.</li>
<li>Nicolas Z. received €60 from John Doe and then gave it all to Stripe.</li>
<li>Stripe gave €100 to John Doe, received €60 from Nicolas Z., and received €1,000 from the Insurance.</li>
<li>The Insurance received €20 and then gave €1,000 to Stripe.</li>
<li>Assistance and Getaround received €10 from John’s Doe.</li>
</ul>
<p>With that information, we can tell that:</p>
<ul>
<li>we are good with John Doe and Nicolas Z., we don’t owe them, they don’t owe us,</li>
<li>we owe Stripe €1,050, that’s most likely an anomaly because transactions are missing,</li>
<li>Insurance owes us €980, but that’s the same kind of anomaly,</li>
<li>we owe €10 to the Assistance provider, they haven’t been paid, and</li>
<li>we owe €10 to ourselves, Getaround, they haven’t been paid either.</li>
</ul>
<h3 id="much-more">Much more</h3>
<p>There is much more to say about this system but that’s more or less all I need for the
rest of this article. If you’re interested there is still <a href="https://jeremycripps.com/Summa2.pdf">Jeremy Cripps’ interpretation
of Luca Pacioli’s writing</a>.</p>
<h2 id="what-about-tech">What about tech?</h2>
<h3 id="frameworks">Frameworks</h3>
<p>The first thing that I admire in that work is that it is <strong>generic</strong>. No matter the nature
of your business, those guidelines can help you to solve a <strong>technical</strong> problem. The
solution will adapt to the specifics of your business.</p>
<p>It really looks like a framework like Ruby on Rails. If you follow the <strong>conventions</strong>,
you will benefit from the work of a whole <strong>community</strong> and from the wisdom of those who
tried to solve the same problems before you.</p>
<h3 id="databases">Databases</h3>
<p>Those books I described are very close to a <strong>database</strong>.</p>
<p>The reference system that ties entries from different books together is comparable to
<strong>foreign keys</strong>. Information is split between books, sometimes denormalized, sometimes
referenced.</p>
<p>A <em>Ledger</em> represents the same information that we have in the <em>Journal</em> but optimized for
different use-cases. A <em>Ledger</em> is like a projection, like a <strong>materialized view</strong>. It
creates <strong>read-models</strong> optimized for certain use-cases, certain <strong>consistency checks</strong>.</p>
<h3 id="decoupling">Decoupling</h3>
<p>The Memorandum and the Journal are used by different people and for different
purposes. There is <strong>a context</strong> for business operations and another for the accounting.
That looks like some <a href="https://martinfowler.com/bliki/BoundedContext.html">Domain-Driven-Design</a> to me.</p>
<p>We don’t want the Memorandum to be impacted by the Journal <strong>unavailability</strong>.
We don’t want to need the Memorandum when searching informations in the Journal.
And finally, we don’t want to have them synchronized all the time. We’re talking about
<strong>asynchrony</strong> and <strong>eventual consistency</strong> here!</p>
<h3 id="immutability">Immutability</h3>
<p>One of the important properties of those books is that entries were <strong>immutable</strong>. Each book
is an <strong>append-only log</strong> of <strong>events</strong> and transactions. This looks very much like an
<a href="https://kickstarter.engineering/event-sourcing-made-simple-4a2625113224">event-driven architecture</a>.</p>
<p>When there is some mistake or something to update, it has to be through <strong>compensating
transactions</strong> rather than amending past transactions.</p>
<h3 id="simplicity">Simplicity</h3>
<p>Double-entry-bookkeeping was in use way before the 15th century. Being relatively
simple was one of the key to <strong>longevity</strong>. A complex solution would be dead by now, but
this is still the foundation of most financial systems today!</p>
<p>Another key is certainly its <strong>robustness</strong>. The process leaves little room for error.
It exposes a <strong>clear</strong> set of rules to follow and provides <strong>guarantees</strong> about the
information the system will deliver.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Italian merchants knew a lot about how to build information systems. I wonder if there are
other disciplines that refined systems, like accounting, over centuries, without any
computer, and to end up with such mature models.
If you have other examples, in any other field, please reach out to me!</p>
<p>Discovering such simple, effective, and flexible tools is truly inspiring.
In my opinion, it is totally relevant to guide the design of <em>modern software systems</em> as
we’re still solving the same problems!</p>
<p>All companies handle money <strong>transactions</strong>. The law enforces companies to maintain and
publish records of those transactions. Those records are called <strong>books</strong> and the act
of maintaining them is called <strong>bookkeeping</strong>.</p>
Sun, 01 Mar 2020 00:00:00 +0000
https://getaround.tech/double-entry-bookkeeping/
https://getaround.tech/double-entry-bookkeeping/Writing JavaScript like it's 2020Clement Bruno<p>JavaScript (JS) has long been criticized for being verbose and quirky. But the recent additions made to the language allow us to cope nicely with some of the debatable design decisions that were made and even benefit from a truly enjoyable development experience. In fact JS boasts a vast ecosystem, is present in a wide array of development use cases and is improved each and every year with excellent features.</p>
<p>In the following article we explore some of these great features that were added to the language with ES2019 and ES2020.</p>
<p><em>NB: The following list does not aim at being exhaustive but merely at describing some of the new stuff I am enthusiastic about.
Additionally, most of these new features are not yet supported by the major browsers but are already usable if you use the Babel transpiler or a recent version of TypeScript (>= 3.7).</em><br /></p>
<h1 id="es2020">ES2020</h1>
<h2 id="optional-chaining-operator">Optional chaining operator</h2>
<p>This one probably is among my favorites because it allows to vastly reduce the amount of code written when dealing with complex objects and when being unsure about the content structure.</p>
<p>For instance, if we take the following example:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">userInfos</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bob</span><span class="dl">'</span><span class="p">,</span>
<span class="na">email</span><span class="p">:</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">,</span>
<span class="na">familyMembers</span><span class="p">:</span> <span class="p">{</span>
<span class="na">brother</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bobby</span><span class="dl">'</span><span class="p">,</span>
<span class="na">age</span><span class="p">:</span> <span class="mi">16</span>
<span class="p">},</span>
<span class="na">mother</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Constance</span><span class="dl">'</span><span class="p">,</span>
<span class="na">age</span><span class="p">:</span> <span class="mi">55</span>
<span class="p">},</span>
<span class="na">father</span><span class="p">:</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">John</span><span class="dl">'</span><span class="p">,</span>
<span class="na">age</span><span class="p">:</span> <span class="mi">60</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span>
<span class="nx">userInfos</span><span class="p">.</span><span class="nx">familyMembers</span>
<span class="o">&&</span> <span class="nx">userInfos</span><span class="p">.</span><span class="nx">familyMembers</span><span class="p">.</span><span class="nx">mother</span>
<span class="o">&&</span> <span class="nx">userInfos</span><span class="p">.</span><span class="nx">familyMembers</span><span class="p">.</span><span class="nx">mother</span><span class="p">.</span><span class="nx">age</span>
<span class="p">);</span></code></pre></figure>
<p>As you can see above retrieving the mother’s age was a pain and the nesting level is not even that deep. Developers already tried to solve this issue in the past and some interesting solutions such as the <code class="language-plaintext highlighter-rouge">delve</code> utility function were developed. But the optional chaining operator is now a native implementation of the wanted behaviour and removes the dependency to external packages which is always appreciated.
Using this new operator allows us to reduce the code to:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">userInfos</span><span class="p">.</span><span class="nx">familyMembers</span><span class="p">?.</span><span class="nx">mother</span><span class="p">?.</span><span class="nx">age</span><span class="p">);</span> <span class="c1">// => 55</span></code></pre></figure>
<p>In case the value called after <code class="language-plaintext highlighter-rouge">?</code> is not found the program won’t crash and will return <code class="language-plaintext highlighter-rouge">undefined</code> instead.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">userInfos</span><span class="p">.</span><span class="nx">familyMembers</span><span class="p">?.</span><span class="nx">grandPa</span><span class="p">?.</span><span class="nx">age</span><span class="p">);</span> <span class="c1">// => undefined</span></code></pre></figure>
<h2 id="bigint">BigInt</h2>
<p>The addition of <code class="language-plaintext highlighter-rouge">BigInt</code> to JS primitives is a good thing since, for long, manipulating numbers in JS was considered hazardous. The <code class="language-plaintext highlighter-rouge">Number</code> type is capped to integer values of 2**53-1 which can be really limiting.<br /></p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nb">Number</span><span class="p">.</span><span class="nx">MAX_SAFE_INTEGER</span><span class="p">)</span> <span class="c1">// => 9007199254740991</span></code></pre></figure>
<p>This addition provides real built-in support for manipulating large numbers and using this new type is trivial. Just adding <code class="language-plaintext highlighter-rouge">n</code> at the end of a number makes it a <code class="language-plaintext highlighter-rouge">BigInt</code>.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// BigInt numbers can be declared like that:</span>
<span class="kd">const</span> <span class="nx">bigIntNum</span> <span class="o">=</span> <span class="mi">100</span><span class="nx">n</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">bigIntNum2</span> <span class="o">=</span> <span class="nc">BigInt</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">bigIntNum3</span> <span class="o">=</span> <span class="nc">BigInt</span><span class="p">(</span><span class="dl">"</span><span class="s2">100</span><span class="dl">"</span><span class="p">);</span></code></pre></figure>
<p>There are some limitations though:<br /></p>
<ul>
<li>Operations between numbers are only possible if they share the same type:</li>
</ul>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="mi">100</span><span class="nx">n</span> <span class="o">/</span> <span class="mi">2</span> <span class="c1">// => TypeError: Cannot mix BigInt and other types, use explicit conversions</span>
<span class="mi">100</span><span class="nx">n</span> <span class="o">/</span> <span class="mi">2</span><span class="nx">n</span> <span class="c1">// => 50n</span>
<span class="k">typeof</span><span class="p">(</span><span class="mi">100</span><span class="nx">n</span> <span class="o">/</span> <span class="mi">2</span><span class="nx">n</span><span class="p">)</span> <span class="c1">// => 'bigint'</span></code></pre></figure>
<ul>
<li>Since the output of operations involving <code class="language-plaintext highlighter-rouge">BigInt</code> numbers is itself a <code class="language-plaintext highlighter-rouge">BigInt</code>, fractional values are truncated:</li>
</ul>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="mi">25</span><span class="nx">n</span> <span class="o">/</span> <span class="mi">2</span><span class="nx">n</span> <span class="c1">// => 12n</span></code></pre></figure>
<h2 id="nullish-coalescing-operator">Nullish coalescing Operator</h2>
<p>I am really looking forward for this one to be widespread because it is also a great feature that fixes the flaws of the infamous <code class="language-plaintext highlighter-rouge">||</code>.
As a reminder previously the <code class="language-plaintext highlighter-rouge">||</code> operator could produce surprising result because it would consider all falsy values…</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someValue</span> <span class="o">=</span> <span class="kc">null</span> <span class="o">||</span> <span class="dl">"</span><span class="s2">some value</span><span class="dl">"</span> <span class="c1">// => "some value"</span></code></pre></figure>
<p>The above example is perfectly valid but I am way less comfortable with the following 2 lines:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someNumber</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">||</span> <span class="mi">300</span> <span class="c1">// => 300 </span>
<span class="kd">const</span> <span class="nx">someStr</span> <span class="o">=</span> <span class="dl">""</span> <span class="o">||</span> <span class="dl">"</span><span class="s2">some string</span><span class="dl">"</span> <span class="c1">// => "some string"</span></code></pre></figure>
<p>This is due to the fact that, contrarily to what we have in most other languages, in JavaScript:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="o">!!</span><span class="mi">0</span> <span class="c1">// => false</span>
<span class="o">!!</span><span class="dl">""</span> <span class="c1">// => false</span></code></pre></figure>
<p>Using the new <code class="language-plaintext highlighter-rouge">??</code> operator solves such issue since it only deals with <code class="language-plaintext highlighter-rouge">null</code> and <code class="language-plaintext highlighter-rouge">undefined</code> values instead of all the falsy ones:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someValue</span> <span class="o">=</span> <span class="kc">null</span> <span class="o">??</span> <span class="dl">"</span><span class="s2">some value</span><span class="dl">"</span> <span class="c1">// => "some value"</span>
<span class="kd">const</span> <span class="nx">someNumber</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">??</span> <span class="mi">300</span> <span class="c1">// => 0 </span>
<span class="kd">const</span> <span class="nx">someStr</span> <span class="o">=</span> <span class="dl">""</span> <span class="o">&&</span> <span class="dl">"</span><span class="s2">some string</span><span class="dl">"</span> <span class="c1">// => ""</span></code></pre></figure>
<h1 id="es2019">ES2019</h1>
<p>I’d also like to mention two useful features that aren’t yet very widespread despite their usefulness which were brought with ES2019.</p>
<h2 id="arrayflat">Array.flat()</h2>
<p><code class="language-plaintext highlighter-rouge">flat</code> as its name indicates allows to reduce progressively the nesting of imbricated lists:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">nestedList</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">first level</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">second level</span><span class="dl">"</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">third level</span><span class="dl">"</span><span class="p">]]];</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">nestedList</span><span class="p">.</span><span class="nf">flat</span><span class="p">());</span> <span class="c1">// => [ 'first level', 'second level', [ 'third level' ] ]</span></code></pre></figure>
<p>It takes an optional parameter that specifies how many level of nesting should be unnested.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">nestedList</span><span class="p">.</span><span class="nf">flat</span><span class="p">(</span><span class="mi">2</span><span class="p">));</span> <span class="c1">// => [ 'first level', 'second level', 'third level' ]</span></code></pre></figure>
<p>When no parameter is provided the function defaults to 1. Therefore it is equivalent to write:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">nestedList</span><span class="p">.</span><span class="nf">flat</span><span class="p">());</span> <span class="c1">// => [ 'first level', 'second level', [ 'third level' ] ]</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">nestedList</span><span class="p">.</span><span class="nf">flat</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span> <span class="c1">// => [ 'first level', 'second level', [ 'third level' ] ]</span></code></pre></figure>
<p>If you are dealing with a data structure with an unknown level of nesting and are sure you want everything unnested you can provide <code class="language-plaintext highlighter-rouge">Infinity</code> as an argument to the <code class="language-plaintext highlighter-rouge">flat</code> function.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">nestedList</span><span class="p">.</span><span class="nf">flat</span><span class="p">(</span><span class="kc">Infinity</span><span class="p">));</span> <span class="c1">// => [ 'first level', 'second level', 'third level' ]</span></code></pre></figure>
<p>NB: Please note that a <code class="language-plaintext highlighter-rouge">flatMap</code> function was also introduced and as its name indicates it combines the <code class="language-plaintext highlighter-rouge">flat</code> described above with a <code class="language-plaintext highlighter-rouge">map</code> loop method.<br />
It is also worth mentioning that, in the functional programming spirit, these new methods do no mutate the array on which they are called but create a new one.</p>
<h2 id="objectfromentries">Object.fromEntries()</h2>
<p>This new feature is interesting to switch from one data structure to another to best fit our development need.<br />
For instance, given a nested lists data structure:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someList</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">,</span> <span class="mi">55</span><span class="p">],</span> <span class="p">[</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bob</span><span class="dl">'</span><span class="p">],</span> <span class="p">[</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">]];</span></code></pre></figure>
<p>if I needed to access the “bob” value, I’d have to write something like:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">someList</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">1</span><span class="p">]);</span> <span class="c1">// => "bob"</span></code></pre></figure>
<p>This works but it is flawed because it relies on the list content ordering and it does not provide any context regarding the fact that I want to access the <code class="language-plaintext highlighter-rouge">name</code> property.
With the the new <code class="language-plaintext highlighter-rouge">Object.fromEntries()</code> function we can adopt a very much cleaner approach:</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someList</span> <span class="o">=</span> <span class="p">[[</span><span class="dl">'</span><span class="s1">age</span><span class="dl">'</span><span class="p">,</span> <span class="mi">55</span><span class="p">],</span> <span class="p">[</span><span class="dl">'</span><span class="s1">name</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bob</span><span class="dl">'</span><span class="p">],</span> <span class="p">[</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">]];</span>
<span class="kd">const</span> <span class="nx">someObject</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">fromEntries</span><span class="p">(</span><span class="nx">someList</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">someObject</span><span class="p">);</span> <span class="c1">// => { age: 55, name: 'bob', email: '[email protected]' }</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">someObject</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> <span class="c1">// => "bob"</span></code></pre></figure>
<p>NB: The corollary feature is <code class="language-plaintext highlighter-rouge">Object.entries()</code> which allows to transform an Object into a nested array structure.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">someObject</span> <span class="o">=</span> <span class="p">{</span> <span class="na">age</span><span class="p">:</span> <span class="mi">55</span><span class="p">,</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bob</span><span class="dl">'</span><span class="p">,</span> <span class="na">email</span><span class="p">:</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span> <span class="p">}</span>
<span class="kd">const</span> <span class="nx">someList</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">entries</span><span class="p">(</span><span class="nx">someObject</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">someList</span><span class="p">);</span> <span class="c1">// => [['age', 55], ['name', 'bob'], ['email', '[email protected]']];</span></code></pre></figure>
<h1 id="conclusion">Conclusion</h1>
<p>The features described above are just a subset of the recent additions made to the language but these are very much likable because they allow us to write code that is much more readable and concise. As a consequence the whole development experience feels less hacky and really enjoyable overall.</p>
<p>JavaScript (JS) has long been criticized for being verbose and quirky. But the recent additions made to the language allow us to cope nicely with some of the debatable design decisions that were made and even benefit from a truly enjoyable development experience. In fact JS boasts a vast ecosystem, is present in a wide array of development use cases and is improved each and every year with excellent features.</p>
Wed, 08 Jan 2020 00:00:00 +0000
https://getaround.tech/writing-js-like-its-2020/
https://getaround.tech/writing-js-like-its-2020/Migrating a Live IoT Telemetry BackendKhwaab Dave<p>In order to provide a magical experience for our carsharing customers, Getaround vehicles are equipped with Connect® hardware that communicates with the Getaround network. That magic is powered by an entire IoT backend which we recently migrated. Some might call that magical. As our platform grew quickly, the initial infrastructure experienced stability issues and hindered our ability to scale or improve telemetry features. Migrating an internal service which powers the business is <a href="https://youtu.be/SVGgvevWnls?t=25">a bit tricky</a>. A single mistake has the potential to shutdown business for hours if not days. With this in mind, we used an iterative and parallel approach to carefully migrate each feature from the old to the new.</p>
<h1 id="iot-at-getaround">IoT at Getaround</h1>
<p>We believe the best carsharing experience should be seamless– from booking, to finding and unlocking, and then returning the car. Getaround’s Connect® hardware powers this experience of finding and accessing the car and allows us to monitor our fleet’s health.</p>
<p>When Getaround started, IoT was still a relatively new field, and off-the-shelf services were limited at best. We created a proprietary telemetry backend and protocol. It was a single cloud server, running a service written in Erlang, receiving telemetry and routing commands and configurations to all vehicles on the platform.</p>
<p>At a high level our system looked like this.</p>
<p><img src="/assets/posts/2019-12-10-migrating-a-live-iot-telemetry-backend/iot_1-min.png" alt="" /></p>
<p>For years, with a smaller number of cars on our platform, this was functional and required minimal maintenance. However, once we started rapidly scaling, minor issues became huge problems:</p>
<ul>
<li>The service and protocol did not scale with thousands of devices</li>
<li>As we scaled, edge cases became a more frequent problem we could no longer ignore</li>
<li>Hiring a specialized Erlang team to fix and work on a system is very difficult</li>
</ul>
<h1 id="choosing-an-iot-service">Choosing an IoT Service</h1>
<p>IoT as a field had made leaps of progress in the years since we created our initial infrastructure. We decided the best way to scale our system was to switch away from our proprietary solution and use one of the newer off-the-shelf IoT products.</p>
<p>We began research and landed on four services to evaluate:</p>
<ul>
<li>AWS IoT Core</li>
<li>Google Core IoT</li>
<li>Samsung Artik</li>
<li>ThingsBoard</li>
</ul>
<p>As we looked into these offerings we had a few metrics we were comparing:</p>
<ul>
<li>Stability / Age / Development Cycle / Adoption</li>
<li>Scaling</li>
<li>Protocol - MQTT or COAP</li>
<li>Flexibility</li>
<li>Setup Ease</li>
<li>Maintenance Ease and Skillset</li>
<li>Integration Ease</li>
<li>Cost of Service</li>
<li>Bandwidth cost of service (over GSM)</li>
<li>Limits</li>
</ul>
<p>First we eliminated ThingsBoard. Their software was interesting and open source, but it was geared towards running your own instance, like we had been. They had a SaaS offering as well, but we decided it was risky to use a relatively unknown and small company for a system which our entire business relied upon.</p>
<p>Next we eliminated Samsung Artik. Artik had similar issues as AWS in moving the data from Samsung to our platform in Google Cloud, but it was a much newer and less adopted system than AWS. Samsung Artik did offer COAP and MQTT, and we looked into it specifically to evaluate COAP, but we finally chose MQTT due to its more mature libraries and better TLS support. Samsung Artik had the same feature limitations as Google Cloud and the same integration problems of AWS.</p>
<p>Finally, the decision ended up being between Google and AWS. In evaluating the basic MQTT Telemetry (offering, cost, and limits), they both seemed similar. Since we had a working relationship with Google through our other backend systems, we decided it was beneficial to try and keep our services within Google Cloud Platform. The integration seemed simpler even though AWS was more established, flexible, and feature-rich.</p>
<p>Our first design thoughts were to replicate our current features falling largely into three categories:</p>
<ul>
<li>Receive telemetry from Connect® devices</li>
<li>Send commands to Connect® devices</li>
<li>Modify configuration of Connect® devices</li>
</ul>
<p>As we began designing the commands and configuration infrastructure, we started to see the limits of Google’s Cloud IoT Core:</p>
<ul>
<li>MQTT topics were predefined, making it harder to organize the system for our purposes</li>
<li>Implementing a robust device configuration system required a large amount of work</li>
</ul>
<p>When sending a configuration request to a device, we wanted a system in which we could guarantee the configuration is sent whenever the device comes online, and monitored even while it is offline. We previously lacked this ability which hindered monitoring the state of any device. To do this with Google’s Cloud IoT Core, we would’ve had to implement another service with a database maintaining these configurations. As we started to design this system, it was almost as much work as implementing the entire IoT backend from scratch. We took a step back and started to rethink our decision regarding Google versus Amazon.</p>
<p>AWS IoT Core eventually won out because of the additional features offered along with the basic MQTT telemetry system:</p>
<ul>
<li>Flexible MQTT topics</li>
<li>Device Shadows</li>
<li>Jobs</li>
</ul>
<p>As we looked deeper into Device Shadows, we realized the configuration system we were designing with Google Core IoT was a rudimentary version of AWS’ Device Shadows. Using AWS, we didn’t need to create supplementary services to achieve the functionality we desired. The only downside was that we needed an ETL from AWS to the rest of our company infrastructure in Google, but we decided the work to implement the ETL was far less than the work required to have Google Core IoT perform the way we needed. As much as we would’ve liked to have kept our systems contained within Google, their IoT product was too new and didn’t offer as many features as AWS.</p>
<h1 id="iterative-and-parallel-migration">Iterative and Parallel Migration</h1>
<p>When we designed the migration process we took into account how we release new Connect® features. When we develop a new feature for the Connect®, it is difficult to test all the real world situations the Connect® devices are exposed to. To verify these features, we roll out firmware releases iteratively to an increasing percentage of the fleet, and with feature flags on all the new features. Feature flags allow code to be exercised with the end result mocked out, making it possible to test new features in parallel with a functioning system.</p>
<p>During the migration, our system looked like this:</p>
<p><img src="/assets/posts/2019-12-10-migrating-a-live-iot-telemetry-backend/iot_2-min.png" alt="" /></p>
<h2 id="the-sequential-steps-of-our-migration-workflows">The sequential steps of our migration workflows</h2>
<ol>
<li>Activate the connection between Connect® devices to AWS and move data from AWS to our telemetry database in Google Cloud.</li>
<li>Implement sending commands from our web platform in Google Cloud to AWS IoT Core, receiving the commands on the Connect®, and replying back.</li>
<li>Shutdown the connection between the Connect® and our old backend.</li>
</ol>
<h2 id="step-1-telemetry">Step 1: Telemetry</h2>
<p>The first migration we implemented was our main telemetry feed from each vehicle. We rolled this out iteratively based on a flag that maintained the connection to the old backend, but when enabled, sent the data to AWS instead.</p>
<p>Ideally, we would have duplicated this data and sent it through both backends to our database for comparison, but the format of our database made this a complex task. Instead, after the firmware was released, we closely observed about 10 vehicles which had been switched over to AWS. Each of these vehicles could immediately revert back to the original network if required, and could also be accessed via SMS in an emergency. Once we identified and fixed some minor issues with these vehicles, we slowly (over the course of a month) increased the number of cars until all capable devices were sending telemetry through AWS.</p>
<h2 id="step-2-commands">Step 2: Commands</h2>
<p>We focused on commands once our telemetry was stable and fully released. Fortunately, commands did not have the same database complexity as our telemetry system, so we could run both AWS and legacy commands in parallel.</p>
<p>We implemented a feature flag which allowed AWS commands to function normally up until the point a Connect® executed a command (e.g. lock, unlock, set configuration). At this point, the device replied to AWS with a message saying it would’ve performed the action if the feature flag was enabled. This allowed us to test the the communication with AWS without unintentional consequences from any bugs in our implementation.</p>
<p>Using this feature flag and the same iterative rollout process, we were able to fully update the fleet and monitor the health of AWS commands while still using our older backend to do the actual work. This allowed us to verify that AWS commands were working as expected, and to roll out any bug fixes before actually using the feature. When our analytics confirmed that AWS commands were performing as well as before (it was actually better), we started another iterative rollout to flip the feature flag which actually acted on the AWS command. We still kept the previous commands enabled, but had deduplication logic in the Connect® to ensure we didn’t execute the command twice.</p>
<p>Finally, once we had sufficiently verified all commands running in parallel, we stopped sending commands through the old system if a device was registered with AWS, completing the migration of commands and no longer relying on the old infrastructure.</p>
<h2 id="step-3-close-the-connection">Step 3: Close the connection</h2>
<p>Now that our AWS implementation had reached feature parity, we had the option to disable the connection from the Connect® to our old server and complete the migration. Because of a number of legacy devices in our fleet, we still run the old system to ensure those vehicles are still operational, but the load is so minimal it takes virtually no effort.</p>
<h1 id="conclusion">Conclusion</h1>
<p>The complete migration of our telemetry system, from choosing a new service to full migration, took just under a year. Each step had to be carefully considered to avoid any negative impact on our users. The migration would’ve been nearly impossible if it were not iterative and done in parallel with our older system. Every release was verified before roll out and we assured our operations teams that there would be no changes in day-to-day business during migration. Everything had to work the exact same way it always had.</p>
<p>There were a few hiccups along the way, but because we had iteratively rolled out all of our features in parallel with our old system, it was simple to revert back when we encountered issues. And with the iterative release, any issues we did encounter were confined to small portions of the fleet. Fixing a couple mistakes by hand is feasible, but a bug affecting the entire fleet would have been disastrous.</p>
<p>As a result, we finally have a fully scalable, distributed and flexible backend which no longer is the blocker in expanding the functionality of the Connect®. Now we just have to explore how to use all this new data!</p>
<p>In order to provide a magical experience for our carsharing customers, Getaround vehicles are equipped with Connect® hardware that communicates with the Getaround network. That magic is powered by an entire IoT backend which we recently migrated. Some might call that magical. As our platform grew quickly, the initial infrastructure experienced stability issues and hindered our ability to scale or improve telemetry features. Migrating an internal service which powers the business is <a href="https://youtu.be/SVGgvevWnls?t=25">a bit tricky</a>. A single mistake has the potential to shutdown business for hours if not days. With this in mind, we used an iterative and parallel approach to carefully migrate each feature from the old to the new.</p>
Tue, 10 Dec 2019 00:00:00 +0000
https://getaround.tech/migrating-a-live-iot-telemetry-backend/
https://getaround.tech/migrating-a-live-iot-telemetry-backend/Improving Performance with Flame GraphsHoward Wilson<p>Recently we had reason to investigate the performance of one of our most commonly used endpoints. We find <a href="https://newrelic.com/">New Relic</a> to be a great tool for identifying and exploring performance problems, but what about when performance isn’t exactly a problem per se, but we’d like to optimize it all the same?</p>
<p>Often the key to understanding can be in effective <em>visualization</em> of the problem, so in this short post we’ll explore how to do just that using <a href="http://www.brendangregg.com/flamegraphs.html">Flame Graphs</a>.</p>
<h2 id="generating-the-graph">Generating the Graph</h2>
<p>We won’t go through this in detail, because it’s very well covered in <a href="https://www.codementor.io/tylerboyd/finding-performance-bottlenecks-in-your-rails-api-du107r9zt">this good blog post</a> from 2016. We found the following development environment setup process effective:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s2">"ruby-prof-flamegraph"</span>
<span class="c1"># development.rb</span>
<span class="n">config</span><span class="p">.</span><span class="nf">cache_classes</span> <span class="o">=</span> <span class="kp">true</span>
<span class="c1"># Controller action</span>
<span class="n">profile</span> <span class="o">=</span> <span class="no">RubyProf</span><span class="p">.</span><span class="nf">profile</span> <span class="k">do</span>
<span class="c1"># Action code here</span>
<span class="k">end</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="s2">"ruby-prof-profile"</span><span class="p">,</span> <span class="s2">"w+"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
<span class="no">RubyProf</span><span class="o">::</span><span class="no">FlameGraphPrinter</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">profile</span><span class="p">).</span><span class="nf">print</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Fire the action locally and then convert the output to a graph using <a href="https://github.com/brendangregg/FlameGraph">FlameGraph</a>:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">cat </span>ruby-prof-profile | ./flamegraph.pl <span class="nt">--countname</span><span class="o">=</span>ms <span class="nt">--width</span><span class="o">=</span>1600 <span class="o">></span> flame.svg</code></pre></figure>
<p><strong>Note:</strong> <a href="https://github.com/tmm1/stackprof">Stackprof</a> is a popular alternative to <a href="https://github.com/ruby-prof/ruby-prof"><code class="language-plaintext highlighter-rouge">ruby-prof</code></a>.</p>
<h2 id="reading-the-graph">Reading the Graph</h2>
<p>Here’s part of the graph for the endpoint in question:</p>
<figure>
<img alt="Flame Graph" src="/assets/posts/2019-10-31-improving-performance-with-flame-graphs/flame_graph.png" />
<figcaption>
Flame Graph
</figcaption>
</figure>
<p><img src="146e938aa8d735588265ca14fbe6dd90_Screen20Shot202019-10-2820at2014-bf47205f-681f-4058-9de4-5a9d3fe62ff7.38.54.png" alt="" /></p>
<p>Time is represented horizontally, and the call stack is represented from bottom to top. This block of code is iterating over an array of cars and serializing a photo URL for each one, as well as some other attributes.</p>
<p>The parts of interest are highlighted in blue. Oddly enough, they were all calls to a simple <code class="language-plaintext highlighter-rouge">#config</code> method which was necessary to generate each of our photo URLs. This gets called hundreds of times by this endpoint, but it’s just a hash so that shouldn’t be a problem, right? Here’s the code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">config</span>
<span class="no">SHARED_CONFIG</span><span class="p">.</span><span class="nf">deep_merge</span><span class="p">(</span><span class="no">ENV_SPECIFIC_CONFIG</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Unfortunately, it is a problem because it’s doing a <a href="https://apidock.com/rails/Hash/deep_merge"><code class="language-plaintext highlighter-rouge">#deep_merge</code></a> of our increasingly large environment config into our system-wide config <em>every time it’s called</em>.</p>
<p>Once we’ve spotted it, the memoization fix is simple:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">config</span>
<span class="vi">@_config</span> <span class="o">||=</span> <span class="no">SHARED_CONFIG</span><span class="p">.</span><span class="nf">deep_merge</span><span class="p">(</span><span class="no">ENV_SPECIFIC_CONFIG</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p><strong>Note:</strong> Take care when memoizing class methods in this way in a threaded environment: some other thread-safe store might be more appropriate for you.</p>
<h2 id="caveats">Caveats</h2>
<ul>
<li>In development, application performance isn’t necessarily going to behave the same as it will in production. For example, we noticed local <a href="https://stackoverflow.com/questions/41353532/why-is-time-utc-slower-in-a-forked-process-in-ruby-on-os-x-and-not-in-python">performance issues with <code class="language-plaintext highlighter-rouge">Time#utc</code></a>.</li>
<li>Bear in mind the impact that adding a call-stack profiler to your production environment might have. Tools like <a href="https://rbspy.github.io/"><code class="language-plaintext highlighter-rouge">rbspy</code></a> might be a better way to go.</li>
<li>Absolute timing isn’t going to be as useful as the relative time spent in methods, since the profiler itself has an impact.</li>
</ul>
<p>Recently we had reason to investigate the performance of one of our most commonly used endpoints. We find <a href="https://newrelic.com/">New Relic</a> to be a great tool for identifying and exploring performance problems, but what about when performance isn’t exactly a problem per se, but we’d like to optimize it all the same?</p>
Tue, 29 Oct 2019 00:00:00 +0000
https://getaround.tech/improving-performance-with-flame-graphs/
https://getaround.tech/improving-performance-with-flame-graphs/What is my job like at Getaround EUNicolas Zermati<p>My job title is officially <em>backend engineer</em> but this is pretty vague. I wanted to explain a bit what I do on a daily basis. First, it is a good reflective exercise for myself. Then, if readers like what I do, maybe some of you will want to <a href="https://drivy.engineering/jobs/">join us</a>!</p>
<p>If you’re in a hurry, this is a quick recap of the main points of the article:</p>
<ul>
<li>I mostly maintain an existing system, which is both challenging and rewarding.</li>
<li>I’ll build a long-term vision in order to guide incremental refactoring rather than big rewrites.</li>
<li>I’m responsible for a key piece of the product.</li>
<li>Some of the main features I work on can take months to get done.</li>
<li>I mostly deliver internal APIs, with very little UI.</li>
<li>I have lot of freedom and very few deadlines.</li>
<li>The feedback loop on some decisions is really long.</li>
</ul>
<p>Of course, all this only reflects my own beliefs and perceptions. I’m happy to discuss anything you read here and encourage you to say <em>Hi!</em> on <a href="https://twitter.com/@nicoolas25">Twitter</a>.</p>
<p><strong>Disclaimer</strong>: Sometimes I’ll say <em>I</em>, sometimes I’ll say <em>we</em>. I’m not doing anything completely by myself. All of what I do greatly relies on and involves the work of the team.</p>
<p>I think the easiest way to share what my job is like would be to tell the story of the main areas I work on. I’m in what we call the <em>finance squad</em> so my work is mostly focused on how the company accepts money from our customers, how we keep track of it, and how we dispatch it to our various partners. The scope is a bit broader than this but it is a good starting point…</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="415px" height="136px" viewBox="-0.5 -0.5 415 136"><defs /><g><path d="M 44 54.86 L 88.97 54.86 L 127.63 54.86" fill="none" stroke="#005700" stroke-miterlimit="10" pointer-events="none" /><path d="M 132.88 54.86 L 125.88 58.36 L 127.63 54.86 L 125.88 51.36 Z" fill="#005700" stroke="#005700" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(63.5,58.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="53" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Payments</div></div></foreignObject><text x="27" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Payments</text></switch></g><ellipse cx="29" cy="32.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="none" /><path d="M 29 40 L 29 65 M 29 45 L 14 45 M 29 45 L 44 45 M 29 65 L 14 85 M 29 65 L 44 85" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(-0.5,92.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="58" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Customers</div></div></foreignObject><text x="29" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Customers</text></switch></g><ellipse cx="359" cy="32.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="none" /><path d="M 359 40 L 359 65 M 359 45 L 344 45 M 359 45 L 374 45 M 359 65 L 344 85 M 359 65 L 374 85" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(336.5,92.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="45" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Partners</div></div></foreignObject><text x="23" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Partners</text></switch></g><ellipse cx="389" cy="62.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="none" /><path d="M 389 70 L 389 95 M 389 75 L 374 75 M 389 75 L 404 75 M 389 95 L 374 115 M 389 95 L 404 115" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(363.5,122.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="51" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Providers</div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Providers</text></switch></g><path d="M 254 55 L 337.63 55" fill="none" stroke="#005700" stroke-miterlimit="10" pointer-events="none" /><path d="M 342.88 55 L 335.88 58.5 L 337.63 55 L 335.88 51.5 Z" fill="#005700" stroke="#005700" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(270.5,57.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="43" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Payouts</div></div></foreignObject><text x="22" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Payouts</text></switch></g><rect x="134" y="0" width="120" height="110" rx="16.5" ry="16.5" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(173.5,48.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="41" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">System</div></div></foreignObject><text x="21" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">System</text></switch></g></g></svg>
</center>
<p>There is no particular order here.</p>
<h4 id="payments">Payments</h4>
<p>We support credit cards via Stripe. We also accept Paypal in some countries. We used to use other providers, so our integration aims to be provider-agnostic. This integration style works quite well and I can’t thank enough the people that set it up.</p>
<p>I’ve been busy <em>keeping up with regulations</em>. For instance, I worked on being <a href="https://www.pcisecuritystandards.org/pci_security/standards_overview">PCI-DSS</a>-compliant. Thanks to Stripe, becoming compliant was a smooth process. We use Stripe <a href="https://stripe.com/payments/elements">Elements</a> and <a href="https://stripe.com/docs/sources">Sources</a> so we don’t have to handle sensitive credit card information.</p>
<p>You may have noticed, the European Commission released the <a href="https://europa.eu/rapid/press-release_MEMO-17-4961_en.htm">PSD2</a>, a directive aiming at securing online payments with two-factor authentication. All the payment providers operating in the European Commission had to work really hard to be ready for this. Account managers at Stripe did their best to help us, they made themselves very available, invited us to workshops, and so on. On my side, the main challenges were:</p>
<ul>
<li>releasing the new API incrementally, and</li>
<li>keeping our provider-agnostic code agnostic.</li>
</ul>
<p>The new (<a href="https://stripe.com/docs/api/payment_intents">Payment Intent</a> and <a href="https://stripe.com/docs/api/payment_methods">Payment Method</a>) APIs introduced some <em>pressure</em> on our existing abstractions. For instance, the system was designed in such a way that a call to the payment provider should either end up in a <code class="language-plaintext highlighter-rouge">successful</code> transaction or a <code class="language-plaintext highlighter-rouge">failed</code> one. This change introduced another state: <code class="language-plaintext highlighter-rouge">pending_action</code> when the bank requires a two-factor authentication to complete the transaction. How to handle that? What’s the impact on our apps, on our data, etc?</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="522px" height="352px" viewBox="-0.5 -0.5 522 352"><defs /><g><path d="M 200 160 L 200 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 200 228.88 L 196.5 221.88 L 200 223.63 L 203.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(190.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">2xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">2xx</text></switch></g><path d="M 240 119.67 L 360 119.67 L 360 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 360 228.88 L 356.5 221.88 L 360 223.63 L 363.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(355.5,188.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">5xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">5xx</text></switch></g><path d="M 160 119.67 L 40 119.67 L 40 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 40 228.88 L 36.5 221.88 L 40 223.63 L 43.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(30.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">4xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">4xx</text></switch></g><ellipse cx="200" cy="120" rx="40" ry="40" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(178.5,113.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="42" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 44px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">pending</div></div></foreignObject><text x="21" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">pending</text></switch></g><ellipse cx="40" cy="270" rx="40" ry="40" fill="#f8cecc" stroke="#b85450" pointer-events="none" /><g transform="translate(25.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="28" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 30px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">failed</div></div></foreignObject><text x="14" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">failed</text></switch></g><ellipse cx="200" cy="270" rx="40" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(170.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="58" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 60px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">succeeded</div></div></foreignObject><text x="29" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">succeeded</text></switch></g><ellipse cx="360" cy="270" rx="40" ry="40" fill="#ffe6cc" stroke="#d79b00" pointer-events="none" /><g transform="translate(335.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="48" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 50px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">unknown</div></div></foreignObject><text x="24" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">unknown</text></switch></g><path d="M 200 20 L 200 73.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 200 78.88 L 196.5 71.88 L 200 73.63 L 203.5 71.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><ellipse cx="200" cy="10" rx="10" ry="10" fill="#0a0a0a" stroke="#000000" pointer-events="none" /><path d="M 0 170 L 410 170" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><g transform="translate(418.5,163.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="102" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Call to the gateway</div></div></foreignObject><text x="51" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Call to the gateway</text></switch></g><g transform="translate(44.5,333.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="310" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 310px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><u>payment transaction's state machine - previous integration</u></div></div></foreignObject><text x="155" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica"><u>payment transaction's state machine - previous integration</u></text></switch></g></g></svg>
<hr />
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="641px" height="441px" viewBox="-0.5 -0.5 641 441"><defs /><g><path d="M 285 120 L 325 120 L 325 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 325 228.88 L 321.5 221.88 L 325 223.63 L 328.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(315.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">2xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">2xx</text></switch></g><path d="M 285 120 L 450 120 L 450 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 450 228.88 L 446.5 221.88 L 450 223.63 L 453.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(440.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">5xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">5xx</text></switch></g><path d="M 205 120 L 40 120 L 40 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 40 228.88 L 36.5 221.88 L 40 223.63 L 43.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(31.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">4xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">4xx</text></switch></g><path d="M 205 120 L 165 120 L 165 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 165 228.88 L 161.5 221.88 L 165 223.63 L 168.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(155.5,184.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="19" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">2xx</div></div></foreignObject><text x="10" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">2xx</text></switch></g><ellipse cx="245" cy="120" rx="40" ry="40" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(223.5,113.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="42" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 44px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">pending</div></div></foreignObject><text x="21" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">pending</text></switch></g><ellipse cx="40" cy="270" rx="40" ry="40" fill="#f8cecc" stroke="#b85450" pointer-events="none" /><g transform="translate(25.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="28" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 30px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">failed</div></div></foreignObject><text x="14" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">failed</text></switch></g><ellipse cx="325" cy="270" rx="40" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(295.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="58" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 60px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">succeeded</div></div></foreignObject><text x="29" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">succeeded</text></switch></g><ellipse cx="450" cy="270" rx="40" ry="40" fill="#ffe6cc" stroke="#d79b00" pointer-events="none" /><g transform="translate(425.5,263.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="48" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 50px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">unknown</div></div></foreignObject><text x="24" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">unknown</text></switch></g><path d="M 245 20 L 245 73.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 245 78.88 L 241.5 71.88 L 245 73.63 L 248.5 71.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><ellipse cx="245" cy="10" rx="10" ry="10" fill="#0a0a0a" stroke="#000000" pointer-events="none" /><path d="M 0 170 L 495 170" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><g transform="translate(506.5,164.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="102" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Call to the gateway</div></div></foreignObject><text x="51" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Call to the gateway</text></switch></g><g transform="translate(101.5,423.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="286" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 288px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><u>payment transaction's state machine - new integration</u></div></div></foreignObject><text x="143" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica"><u>payment transaction's state machine - new integration</u></text></switch></g><path d="M 165 310 L 165 380 L 40 380 L 40 316.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 40 311.12 L 43.5 318.12 L 40 316.37 L 36.5 318.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(81.5,384.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="27" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Error</div></div></foreignObject><text x="14" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Error</text></switch></g><path d="M 165 310 L 165 380 L 325 380 L 325 316.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 325 311.12 L 328.5 318.12 L 325 316.37 L 321.5 318.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(222.5,384.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="45" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Success</div></div></foreignObject><text x="23" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Success</text></switch></g><ellipse cx="165" cy="270" rx="40" ry="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(143.5,256.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="42" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 44px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">pending<br />action</div></div></foreignObject><text x="21" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">pending<br>action</text></switch></g><path d="M 0 350 L 495 350" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><g transform="translate(506.5,344.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="133" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Client call to the gateway</div></div></foreignObject><text x="67" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Client call to the gateway</text></switch></g></g></svg>
</center>
<p>In both situations, when moving to Sources and then to Payment Methods, my role was to <strong>adapt and extend our existing abstractions</strong> to isolate those new concepts, specific to Stripe, as much as possible from the rest of the system to finally plug the new APIs in. I attended Stripe’s workshop, worked with other squads to clarify what the impact was going to be on our product, did a lot of Q&A, monitored the releases, challenged our testing strategy for Stripe’s integration, tuned the integration during the releases, fixed bugs and so on.</p>
<p>As I mentioned, in addition to receiving money, we also dispatch it to our partners. Each payment must match certain transactions between actors.</p>
<center>
<a href="https://d2ddoduugvun08.cloudfront.net/items/2p460y3c1o2G322V270s/Screenshot%20from%202019-10-18%2010-33-33.png" target="_blank" title="Example of an accounting">
<img src="https://d2ddoduugvun08.cloudfront.net/items/2p460y3c1o2G322V270s/Screenshot%20from%202019-10-18%2010-33-33.png" title="Example of an accounting" />
</a>
</center>
<p>We need to pay or debit each of those actors. The squad maintains and scales the <em>payout system</em>. We do batches of bank transfers to the owners almost every working day. The system has to decide what we should pay to whom and what need to be reviewed by the finance department. With the company’s growth, this part of the system was often subject to technical and operational <strong>scaling</strong> issues.</p>
<p>In order to be able to expand to other countries easily we integrated Stripe <a href="https://stripe.com/connect">Connect</a>. With that came a lot of work around KYC (Know Your Customer) compliance. We needed to collect identity documents, billing information, legal information, to add rules in the payouts, etc. Introducing Connect triggered the need to introduce <strong>new concepts and internal processes</strong>. The impact of such a thing is far-reaching: we needed to make changes to our on-boarding flow to communicate with our partners, ensure that customer support were ready, etc.</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" viewBox="-0.5 -0.5 824 142"><defs /><g><rect x="461" y="0" width="210" height="140" fill="#ffffff" stroke="#000000" pointer-events="none" /><ellipse cx="806" cy="57.5" rx="7.500000000000001" ry="7.500000000000001" fill="#ffffff" stroke="#000000" pointer-events="none" /><path d="M 806 65 L 806 90 M 806 70 L 791 70 M 806 70 L 821 70 M 806 90 L 791 110 M 806 90 L 821 110" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(787.5,116.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="35" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Owner</div></div></foreignObject><text x="18" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Owner</text></switch></g><path d="M 541 68 L 501 60 L 477.22 65.29" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 472.09 66.42 L 478.17 61.49 L 477.22 65.29 L 479.68 68.32 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(488.5,44.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="25" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">KYC</div></div></foreignObject><text x="13" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">KYC</text></switch></g><path d="M 661 80 L 784.63 80" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 789.88 80 L 782.88 83.5 L 784.63 80 L 782.88 76.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(696.5,84.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="71" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Bank transfer</div></div></foreignObject><text x="36" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Bank transfer</text></switch></g><rect x="541" y="50" width="120" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(560.5,72.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="79" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 81px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Stripe Connect</div></div></foreignObject><text x="40" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Stripe Connect</text></switch></g><path d="M 471 93.33 L 501 100 L 534.76 93.25" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 539.9 92.22 L 533.73 97.02 L 534.76 93.25 L 532.35 90.16 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(485.5,107.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="32" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Billing<br />Info</div></div></foreignObject><text x="16" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><rect x="351" y="50" width="120" height="60" rx="9" ry="9" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(389.5,72.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="41" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 41px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">System</div></div></foreignObject><text x="21" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">System</text></switch></g><path d="M 31 80 L 134.63 80" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 139.88 80 L 132.88 83.5 L 134.63 80 L 132.88 76.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(50.5,87.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="61" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Credit Card<br />Payment</div></div></foreignObject><text x="31" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Credit Card<br>Payment</text></switch></g><ellipse cx="16" cy="57.5" rx="7.500000000000001" ry="7.500000000000001" fill="#ffffff" stroke="#000000" pointer-events="none" /><path d="M 16 65 L 16 90 M 16 70 L 1 70 M 16 70 L 31 70 M 16 90 L 1 110 M 16 90 L 31 110" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(-1.5,116.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Driver</div></div></foreignObject><text x="16" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Driver</text></switch></g><path d="M 261 80 L 344.63 80" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 349.88 80 L 342.88 83.5 L 344.63 80 L 342.88 76.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><g transform="translate(266.5,87.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="68" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Payment<br />Confirmation</div></div></foreignObject><text x="34" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Payment<br>Confirmation</text></switch></g><rect x="141" y="50" width="120" height="60" rx="9" ry="9" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(184.5,72.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="31" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 33px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Stripe</div></div></foreignObject><text x="16" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Stripe</text></switch></g><g transform="translate(523.5,13.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="84" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 84px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">New integration</div></div></foreignObject><text x="42" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">New integration</text></switch></g></g></svg>
</center>
<p>It is better to identify the consequences of the new constraints as soon as possible. Fortunately, this is a team effort. Most of this effort is done by the product owner; coworkers will help through kick-offs and reviews too. I need to <strong>be involved in the product</strong> in order to help to spot those consequences. When we miss something, it is no big deal, we find out and <strong>adapt</strong>.</p>
<h4 id="moving-forward">Moving forward</h4>
<p>Our application and its accounting system was centered around a single concept: the rental. The assumption that we’re dealing with a rental had firmly taken root throughout the app and coupled things together.</p>
<p>With <a href="https://www.drivy.com/open-proprietaire">Drivy Open</a>, we were actually selling something else entirely: subscriptions to a service. Whilst Open was still a startup inside the startup, we operated a separate system for everything. Because it was a great success, we incrementally merged Drivy Open to the main application. Everyone worked hard to make Drivy Open the future of Drivy. On my side, it was a challenge to untangle all the rental-coupled logic that was everywhere in the application in order to build more flexible sub-systems.</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="571px" height="162px" viewBox="-0.5 -0.5 571 162"><defs /><g><rect x="0" y="20" width="240" height="140" rx="21" ry="21" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><rect x="20" y="70" width="60" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(32.5,93.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="34" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 36px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Rental</div></div></foreignObject><text x="17" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Rental</text></switch></g><g transform="translate(69.5,33.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="101" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting system</div></div></foreignObject><text x="51" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting system</text></switch></g><rect x="90" y="60" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(100.5,83.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><rect x="100" y="70" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(110.5,93.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><rect x="110" y="80" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(120.5,103.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><rect x="280" y="20" width="180" height="140" rx="21" ry="21" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><rect x="300" y="60" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(310.5,83.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><rect x="310" y="70" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(320.5,93.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><rect x="320" y="80" width="110" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(330.5,103.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting Stuff</div></div></foreignObject><text x="44" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting Stuff</text></switch></g><g transform="translate(319.5,33.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="101" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Accounting system</div></div></foreignObject><text x="51" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Accounting system</text></switch></g><rect x="420" y="75" width="90" height="30" fill="#ffffff" stroke="#000000" transform="rotate(-90,465,90)" pointer-events="none" /><g transform="translate(438.5,83.5)rotate(-90,26,6)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="52" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 54px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Interfaces</div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Interfaces</text></switch></g><path d="M 510 72 L 485.91 81.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 481.04 83.58 L 486.24 77.74 L 485.91 81.63 L 488.84 84.23 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><rect x="510" y="30" width="60" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(522.5,53.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="34" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 36px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Rental</div></div></foreignObject><text x="17" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Rental</text></switch></g><path d="M 510 114 L 485.62 101" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 480.99 98.53 L 488.81 98.73 L 485.62 101 L 485.52 104.91 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><rect x="510" y="100" width="60" height="60" fill="#ffffff" stroke="#000000" pointer-events="none" /><g transform="translate(524.5,123.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="30" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 30px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Open</div></div></foreignObject><text x="15" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Open</text></switch></g><g transform="translate(105.5,3.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="28" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 29px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">From</div></div></foreignObject><text x="14" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">From</text></switch></g><g transform="translate(363.5,3.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="12" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 14px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">To</div></div></foreignObject><text x="6" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">To</text></switch></g><path d="M 260 160 L 260 0" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /></g></svg>
</center>
<p>To accomplish this, we needed to establish <strong>a long-term vision</strong>. Refactoring a whole system in one go would be a very big investment. Also, by doing it slowly, we can learn along the way. Each feature becomes a good opportunity to advance toward the vision. If a feature doesn’t fit that vision well, both the vision and the feature get the opportunity to get reworked.</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="541px" height="274px" viewBox="-0.5 -0.5 541 274"><defs /><g><g transform="translate(179.5,256.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="181" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 181px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><u>Adjusting the vision along the way</u></div></div></foreignObject><text x="91" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica"><u>Adjusting the vision along the way</u></text></switch></g><ellipse cx="30" cy="160" rx="30" ry="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(8.5,146.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="42" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Starting<br />point</div></div></foreignObject><text x="21" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Starting<br>point</text></switch></g><ellipse cx="510" cy="30" rx="30" ry="30" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(493.5,23.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><strike>Vision</strike></div></div></foreignObject><text x="16" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><ellipse cx="390" cy="140" rx="30" ry="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(362.5,133.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="54" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 54px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Increment</div></div></foreignObject><text x="27" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Increment</text></switch></g><path d="M 289.67 155.55 L 354.06 145.54" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 359.24 144.73 L 352.86 149.27 L 354.06 145.54 L 351.79 142.35 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><ellipse cx="260" cy="160" rx="30" ry="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(232.5,153.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="54" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 54px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Increment</div></div></foreignObject><text x="27" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Increment</text></switch></g><path d="M 174.04 137.52 L 224.81 150.81" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 229.89 152.14 L 222.23 153.76 L 224.81 150.81 L 224.01 146.98 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><ellipse cx="145" cy="130" rx="30" ry="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(117.5,123.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="54" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 54px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Increment</div></div></foreignObject><text x="27" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Increment</text></switch></g><path d="M 59.04 152.48 L 109.81 139.19" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 114.89 137.86 L 109.01 143.02 L 109.81 139.19 L 107.23 136.24 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" /><path d="M 481.07 37.92 L 173.93 122.07" fill="none" stroke="#666666" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><path d="M 481.08 222.02 L 288.89 168.09" fill="none" stroke="#666666" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><ellipse cx="510" cy="230" rx="30" ry="30" fill="#f5f5f5" stroke="#666666" pointer-events="none" /><g transform="translate(493.5,223.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><strike>Vision</strike></div></div></foreignObject><text x="16" y="12" fill="#333333" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><path d="M 480.41 124.93 L 419.59 135.07" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none" /><ellipse cx="510" cy="120" rx="30" ry="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(493.5,113.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="32" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Vision</div></div></foreignObject><text x="16" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Vision</text></switch></g></g></svg>
</center>
<p>Not rushing through the vision could get frustrating. It would solve most of our current issues, be more efficient, easier to maintain and so on. I said frustrating because moving on to that vision is definitely subject to the needs of the company. As long as the users are happy, and we can move fast enough, we don’t need to rush into building it. Thus we need to <strong>be patient</strong>. We wait for the opportunities to make progress on it. When planning and discussing with the squad, we can elaborate that vision together. We also have to <strong>make trade-offs</strong> between short-term and long-term investments.</p>
<h4 id="supporting-other-squads">Supporting other squads</h4>
<p>We’re dealing with a <a href="https://m.signalvnoise.com/the-majestic-monolith/">majestic monolith</a>. A given functional scope matches a certain area in the code. Many scopes require interaction with the payments, or with the accounting. Team organization evolved quickly. It evolved quicker than the code itself. We’re organized in cross-functional teams with a given scope (inspired by <a href="https://labs.spotify.com/2014/03/27/spotify-engineering-culture-part-1/">Spotify’s squads</a>).</p>
<p>Many squads will at some point need <em>something</em> from the finance squad. It could be some assistance for a given task, or it could be a feature that isn’t yet supported and that we’ll need to add. This brings its fair share of effort in term of planning prioritisation. It also encourages interaction and <strong>cross-squad collaboration</strong> which I enjoy. So in addition to working on the system itself, I’m often in <strong>a support role</strong> to other squads. It’s very rewarding, but sometimes this dependency becomes a bottleneck for the team’s outcomes. Fortunately, the long-term vision solves it!</p>
<p>To avoid that bottleneck, we <strong>aim for a <a href="https://engineering.shopify.com/blogs/engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity">modular monolith</a></strong>. We need to <strong>expose clear boundaries</strong> of what the subsystems are that the finance squad must provide, in term of <strong>scope and APIs</strong>. I, obviously, would like to reduce our scope. But wait, it’s not that simple… Features are built on the scope I would like to get rid of. It means than if we move a boundary between <em>A</em> and <em>B</em>, the scope of subsystem <em>A</em> will shrink and leave a hole between <em>A</em> and <em>B</em>. So my job would be to deprecate that scope; to prevent further features to rely on it. Then, thinking about how to fill the void for the existing features. Maybe by adding stuff to B? Maybe by filling the void with another subsystem? Maybe the void belongs to subsystem A after all?</p>
<center>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="321px" height="181px" viewBox="-0.5 -0.5 321 181"><defs /><g><rect x="0" y="0" width="180" height="100" rx="15" ry="15" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none" /><g transform="translate(54.5,43.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="71" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 71px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Subsystem A</div></div></foreignObject><text x="36" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Subsystem A</text></switch></g><rect x="140" y="60" width="180" height="100" rx="15" ry="15" fill="#d5e8d4" stroke="#82b366" pointer-events="none" /><g transform="translate(194.5,103.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="71" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 71px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Subsystem B</div></div></foreignObject><text x="36" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Subsystem B</text></switch></g><rect x="140" y="60" width="40" height="40" fill="#f8cecc" stroke="#b85450" pointer-events="none" /><g transform="translate(156.5,73.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="7" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 7px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">?</div></div></foreignObject><text x="4" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">?</text></switch></g><g transform="translate(80.5,163.5)"><switch><foreignObject style="overflow:visible;" pointer-events="none" width="158" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 158px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;"><u>What to do with the red area?</u></div></div></foreignObject><text x="79" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica"><u>What to do with the red area?</u></text></switch></g></g></svg>
</center>
<p>To achieve that, I must stay alert, keep the vision in mind, say no, offer alternatives, make it clear that it is a long-term investment, … Deprecating scope provides no short-term business value. But, the more the team grows, the greater the value of this investment when it pays off.</p>
<h4 id="reporting">Reporting</h4>
<p>One of the most important responsibilities of the squad is to produce reporting for the finance department to use. Seems boring, doesn’t it? Not to me it doesn’t! The needs of a finance department can create a lot of constraints that are fun to play around with.</p>
<p>Over time, this reporting pushed the system to be more robust. At some point in the past, the finance department was using a single report with all the figures for each rental the company did, <em>since forever</em>. In order to get a monthly vision, the technique was to <em>calculate the difference between the report from month N and N-1</em>. This clever way was found in order to cope with the fact that some changes could happen to past data, for instance an adjustment on an old rental. Because of the volumes, such a technique wasn’t working anymore. The extract was too big to work with, and too long to generate.</p>
<center>
<a href="https://d2ddoduugvun08.cloudfront.net/items/362M2R2q2g2l3q031K2h/Untitled%20Diagram.png" target="_blank" title="Rental-based approach">
<img src="https://d2ddoduugvun08.cloudfront.net/items/362M2R2q2g2l3q031K2h/Untitled%20Diagram.png" title="Rental-based approach" />
</a>
</center>
<p>It took <strong>months of work</strong> to take the system to a point where it could generate many <em>small monthly extracts</em> with only the data of what happened that month. We needed to find out a clear way of cutting exports, to detect modification of the past, to find out ways of avoiding those modifications, to be confident that we all we did was equivalent to the previous technique, and to do the migration correctly. We went from rental-based extracts to invoice-based ones. Maybe this doesn’t seem much to you but I’ve faced lots of technical implications while trying to figure out reporting.</p>
<center>
<a href="https://d2ddoduugvun08.cloudfront.net/items/1C2E1m1l1K3n2E0n3H0F/Untitled%20Diagram%20(1).png" target="_blank" title="Invoice-based approach">
<img src="https://d2ddoduugvun08.cloudfront.net/items/1C2E1m1l1K3n2E0n3H0F/Untitled%20Diagram%20(1).png" title="Invoice-based approach" />
</a>
</center>
<p>A good thing is that I mostly care about <strong>keeping the accounting system sound</strong>. The reports are, in the end, a dozen of - relatively simple - SQL queries that don’t move too much.</p>
<h4 id="small-features">Small features</h4>
<p>Alongside the big projects and the long-term vision, there is a lot of other features that I do. Those are smaller and can be done in less than a few days. Usually those are features that <strong>improve the team efficiency</strong>. Here are a few examples: new administration tools, financial processes that get automated, or updates on existing facilities.</p>
<p>What I like about those is that, in contrast to the other features, they provide <strong>predictable, easy, and quick satisfaction</strong>. A feature is usually done quickly, shipped, and instantly useful. Many of those features are internal tools. This context is pretty easy to work with:</p>
<ul>
<li>We only support English in the tools thus we can avoid the internationalization flow.</li>
<li>We <em>usually</em> don’t care about supporting old versions of Internet Explorer.</li>
<li>We don’t do complex CSS things: we use an internal bootstrap mixed with our design system.</li>
<li>We can iterate very fast.</li>
</ul>
<p>I also like having those small features because they have a <strong>direct impact</strong> on my coworkers workload so I can be a hero :-)</p>
<h4 id="data-analysis">Data analysis</h4>
<p>We are dealing with a database that has been holding the company’s data for almost a decade. Still more challenging, is the fact that we collect data from our providers, unstructured <code class="language-plaintext highlighter-rouge">jsons</code> fields, and even others companies’ data that we imported. Given the timescale and the diversity of sources, data isn’t always consistent with today’s happy path. We’ve got some <a href="https://drivy.engineering/checker-jobs/">safety nets</a> in place to help us gain confidence that the data stay as we expect over time.</p>
<p>Still, before adding a feature, we often need to dig in the data a little bit. It helps understand the volumes we’re dealing with, to be sure we have no holes in our thinking, … We don’t have a data-analyst like other squads could have thus we need to do that digging ourselves. This routine is very useful. It allows us to <strong>know the data</strong> well, and to <strong>keep SQL skills sharp</strong>.</p>
<p>We’re also blessed with a product owner that understands, tweaks, and writes SQL requests. When I have an unexpected result, she can proofread my requests and spot missing bits!</p>
<p>Writing <strong>SQL is a must</strong> as we sometimes have CPU-expensive logic that we need to translate from Ruby to SQL for efficiency purposes. With our growth and with the diversity of our customers, the <strong>performance truly matters</strong>. We’re constantly trying to find better ways than duplicating logic from our application code to SQL. To do that, many solutions are used such as introducing immutability, storing more information in the database, building specialized tables, caching, and more.</p>
<p>We also have a dedicated data department that makes available hundreds of tables with all the information you can imagine. We’re in the process of gaining the ability to maintain our own <a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">ETL</a> pipelines in order to suit our specific needs. I’m really excited by that prospect!</p>
<h4 id="more">More</h4>
<p>I didn’t mention the way we <a href="https://drivy.engineering/bug-management/">manage bugs</a>, how we <a href="https://drivy.engineering/continuous-integration/">deliver software</a>, how we support the rest of the team when they have a question about the system, … There are many aspects of my job that I left out here but I think I pictured the biggest part.</p>
<p>If you find all this interesting, if you want to know more or dig into specific points, I encourage you to reach out to me. It would be my pleasure to discuss all this even more!</p>
<p>My job title is officially <em>backend engineer</em> but this is pretty vague. I wanted to explain a bit what I do on a daily basis. First, it is a good reflective exercise for myself. Then, if readers like what I do, maybe some of you will want to <a href="https://drivy.engineering/jobs/">join us</a>!</p>
Tue, 29 Oct 2019 00:00:00 +0000
https://getaround.tech/what-is-my-job-like-as-backend-dev-in-the-finance-squad/
https://getaround.tech/what-is-my-job-like-as-backend-dev-in-the-finance-squad/More tips and tricks for junior developersEmily Fiennes<p>This article follows on from <a href="https://drivy.engineering/ruby-tricks-for-junior-devs/" target="_blank">Clement’s post</a> in which he details useful tips and tricks learnt while working at Drivy. Like Clement, I undertook <a href="https://www.lewagon.com/program" target="_blank">Le Wagon’s</a> intensive 9-week bootcamp. The program was great for a rapid overview of the key elements of full-stack engineering.</p>
<p>Since then, the learning curve has been a steep and stimulating one - at Drivy, I’m surrounded by real dev-warriors. As you can imagine, I relish every pull request I submit or review, for the opportunity to learn new things from my colleagues.</p>
<p>In this article, I will explore just a handful of the many useful tips and tricks that they have shared with me, in the hope that they will be useful to other junior developers.</p>
<h3 id="post-or-putor-patch-">POST or PUT?….or PATCH ?</h3>
<p>At Drivy we implement RESTful api design, where CRUD actions match to HTTP verbs. I found the difference between <code class="language-plaintext highlighter-rouge">POST</code>, <code class="language-plaintext highlighter-rouge">PUT</code> and <code class="language-plaintext highlighter-rouge">PATCH</code> difficult to grasp, before I encountered concrete examples in the Drivy codebase:</p>
<p><strong>POST</strong></p>
<p><code class="language-plaintext highlighter-rouge">POST</code> is used to create a new resource on the server, and maps to the <code class="language-plaintext highlighter-rouge">create</code> controller action. To create a car, i.e. a new row in the database, we need to gather and post to the server the data that corresponds to the columns in the <code class="language-plaintext highlighter-rouge">cars</code> table. This might be:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">is_open: </span><span class="kp">false</span><span class="p">,</span>
<span class="ss">make_id: </span><span class="mi">53</span><span class="p">,</span>
<span class="ss">model_id: </span><span class="mi">11</span><span class="p">,</span>
<span class="ss">plate_number: </span><span class="s1">'L87hYQJ'</span><span class="p">,</span>
<span class="ss">registration_year: </span><span class="mi">2019</span><span class="p">,</span>
<span class="ss">user_id: </span><span class="vi">@user</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<p>We send this information, or payload, to the server at <code class="language-plaintext highlighter-rouge">www.drivy.com/cars</code>. The server then decides the location, or URI, for the resource - which will also correspond to the resource’s unique ID - and creates the row in our databse corresponding to that location. For example, <code class="language-plaintext highlighter-rouge">www.drivy.com/cars/1</code>. So far, so good…</p>
<p><strong>PATCH and PUT</strong></p>
<p>Hang on, both verbs correspond to the <code class="language-plaintext highlighter-rouge">update</code> in CRUD? Yes, but there is a subtle difference.</p>
<p><code class="language-plaintext highlighter-rouge">PUT</code> overwrites the whole resource at an existing location. If we send the following payload to <code class="language-plaintext highlighter-rouge">www.drivy.com/cars/123</code>…</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">mileage: </span><span class="mi">9</span>
<span class="p">}</span></code></pre></figure>
<p>…the entire resource at location <code class="language-plaintext highlighter-rouge">/123</code> will be overwritten i.e. our car’s only attribute will now be its mileage. By the way, if a resource is not found on the server at the given location, a new one is created by the server.</p>
<p>On the other hand, <code class="language-plaintext highlighter-rouge">PATCH</code> overwrites only the attributes included in the payload. If the attribute is a new one, it is added to the resource. If we send this payload to our original resource, which now resides at <code class="language-plaintext highlighter-rouge">www.drivy.com/cars/1</code>…</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">mileage: </span><span class="mi">5</span><span class="p">,</span>
<span class="ss">is_open: </span><span class="kp">true</span>
<span class="p">}</span></code></pre></figure>
<p>… the <code class="language-plaintext highlighter-rouge">is_open</code> attribute will be overwritten, and the mileage attribute added. So once these changes have been applied by the server, we will end up with a <code class="language-plaintext highlighter-rouge">Car</code> resource at location <code class="language-plaintext highlighter-rouge">www.drivy.com/cars/1</code> that looks like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">is_open: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">make_id: </span><span class="mi">53</span><span class="p">,</span>
<span class="ss">mileage: </span><span class="mi">5</span><span class="p">,</span>
<span class="ss">model_id: </span><span class="mi">11</span><span class="p">,</span>
<span class="ss">plate_number: </span><span class="s1">'L87hYQJ'</span><span class="p">,</span>
<span class="ss">registration_year: </span><span class="mi">2019</span><span class="p">,</span>
<span class="ss">user_id: </span><span class="vi">@user</span><span class="p">.</span><span class="nf">id</span><span class="p">,</span>
<span class="p">}</span></code></pre></figure>
<h3 id="manipulating-data-structures-benchmarking-flat_map-vs-mapflatten">Manipulating data structures: benchmarking <code class="language-plaintext highlighter-rouge">flat_map</code> vs <code class="language-plaintext highlighter-rouge">map.flatten</code></h3>
<p>One of the first Ruby tools in the toolbox that I encountered during Le Wagon was <code class="language-plaintext highlighter-rouge">Enumerable#map</code>, and this method can be usefully combined with <code class="language-plaintext highlighter-rouge">Array#flatten</code>, to return a useable array of resources that might otherwise be nested. Take the following example:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># A `car` has many `car_photos`. Imagine we want to get all the photos of all the cars for one of our pro owners. This owner has 56 cars, and each car has at least 3 car_photos. I could:</span>
<span class="vi">@user</span><span class="p">.</span><span class="nf">cars</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:car_photos</span><span class="p">)</span></code></pre></figure>
<p>With this request, I end up with a data structure that looks something like this</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">[[</span><span class="n">car_photo_1</span><span class="p">,</span> <span class="n">car_photo_2</span><span class="p">,</span> <span class="n">car_photo_3</span><span class="p">,</span> <span class="n">car_photo_4</span><span class="p">,</span> <span class="n">car_photo_5</span><span class="p">]]</span></code></pre></figure>
<p>It’s an array of an array of ruby objects. To be able to use it, I must first <code class="language-plaintext highlighter-rouge">.flatten</code> the array because the values are nested.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@user</span><span class="p">.</span><span class="nf">cars</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:car_photos</span><span class="p">).</span><span class="nf">flatten</span></code></pre></figure>
<p>But I know I’ll be using this request a lot, for users with a lot of cars. Maybe I’ll even be rendering all the photos at once. I’m going to need a faster way I can do this, so I’ll benchmark the performance of <code class="language-plaintext highlighter-rouge">.flatten</code> compared to <code class="language-plaintext highlighter-rouge">.flat_map</code>. I’ll do this in a temporary rake task (for easy access to database connection) but you might also run it as a script. My rake task requires the <a href="https://ruby-doc.org/stdlib-2.5.3/libdoc/benchmark/rdoc/Benchmark.html" target="_blank">Benchmark</a> module and looks like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="n">task</span> <span class="ss">benchmark_flat_map_vs_map: :drivy_environment</span> <span class="k">do</span>
<span class="nb">require</span> <span class="s1">'benchmark'</span>
<span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">includes</span><span class="p">(</span><span class="ss">cars: :car_photos</span><span class="p">).</span><span class="nf">find</span><span class="p">(</span><span class="mi">977</span><span class="p">)</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">bmbm</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s1">'flatten'</span><span class="p">)</span> <span class="p">{</span> <span class="n">user</span><span class="p">.</span><span class="nf">cars</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:car_photos</span><span class="p">).</span><span class="nf">flatten</span> <span class="p">}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s1">'flat_map'</span><span class="p">)</span> <span class="p">{</span> <span class="n">user</span><span class="p">.</span><span class="nf">cars</span><span class="p">.</span><span class="nf">flat_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:car_photos</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>I’m loading all the user data into memory first, so we can just focus on the map comparison without including time needed for database roundtrips. I’ve chosen to use the <code class="language-plaintext highlighter-rouge">bmbm</code> method which does a “rehearsal” run to get a stable runtime environment and eliminate other factors.</p>
<p>(By the way, <a href="https://www.youtube.com/watch?v=XL51vf-XBTs" target="_blank">this</a> RailsConf 2019 talk is a really useful and accessible intro to how and when to profile and benchmark your code.)</p>
<p>So, the results were:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">Rehearsal <span class="nt">--------------------------------------------</span>
flatten 0.006082 0.001934 0.008016 <span class="o">(</span> 0.010056<span class="o">)</span>
flat_map 0.000158 0.000015 0.000173 <span class="o">(</span> 0.000188<span class="o">)</span>
<span class="nt">-----------------------------------</span> total: 0.008189sec
user system total real
flatten 0.000316 0.000003 0.000319 <span class="o">(</span> 0.000313<span class="o">)</span>
flat_map 0.000147 0.000002 0.000149 <span class="o">(</span> 0.000145<span class="o">)</span></code></pre></figure>
<p>You’ll notice that flat_map is more than twice as fast than <code class="language-plaintext highlighter-rouge">map</code>. This latter will create an intermediary array, and so the code has to be iterated over twice.</p>
<p>This, my friends, is where <code class="language-plaintext highlighter-rouge">flat_map</code> is useful. Like <code class="language-plaintext highlighter-rouge">map</code> it takes a block:</p>
<p><code class="language-plaintext highlighter-rouge">@user.cars.flat_map(&:car_photos)</code></p>
<p>but doesn’t create that intermediary array.</p>
<p>I used to rely on these out-of-the-box methods, without ever interrogating what was going on. Imagine running the same request involving 1000 power users, each with 50 cars, and each car with 10 photos. Using <code class="language-plaintext highlighter-rouge">flat_map</code> can significantly help improve performance.</p>
<h3 id="-safe-navigation-operatorpart-2"><code class="language-plaintext highlighter-rouge">&</code> Safe Navigation operator…part 2</h3>
<p><a href="http://127.0.0.1:4000/ruby-tricks-for-junior-devs/" target="_blank">Clement discussed</a> the use of the <code class="language-plaintext highlighter-rouge">&</code> safe navigation operator to safely navigate through layers of object relations.</p>
<p>In the Owner Success squad, we learned the hard way that method chaining using the safe navigation operator can also be a sure-fire way to introduce bugs - and precisely because navigation is safe, i.e. no errors are raised, they can be excruciatingly difficult to debug.</p>
<p>When we install an <a href="http://www.drivy.com/open" target="_blank">Open</a> box in a car, we use the <code class="language-plaintext highlighter-rouge">provider_device_id</code> given by the box provider. A device id is considered valid on the provider’s api if it is:</p>
<ul>
<li>in ALL CAPS;</li>
<li>with no whitespaces (internal, leading or trailing).</li>
</ul>
<p>However, on our side, the device ids are entered manually in the backoffice. To cover our backs against human error, we format the device number at the time of form submission:</p>
<p>The & allows us to safely chain the methods, so that in the event that <code class="language-plaintext highlighter-rouge">strip</code> returns nil, <code class="language-plaintext highlighter-rouge">upcase</code> will not raise an error. Great!</p>
<p>What we didn’t realise is that whilst <code class="language-plaintext highlighter-rouge">strip</code> returns the original receiver, even if no changes are made, <code class="language-plaintext highlighter-rouge">strip!</code> returns <code class="language-plaintext highlighter-rouge">nil</code> in the event that the receiver was not modified. Plus <code class="language-plaintext highlighter-rouge">strip</code> and <code class="language-plaintext highlighter-rouge">strip!</code> only deal with trailing and leading whitespaces, <em>not internal ones</em>.</p>
<p>So for a <code class="language-plaintext highlighter-rouge">provider_device_id</code> looking something like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="s1">'A dEVice ID 123'</span></code></pre></figure>
<p>…<code class="language-plaintext highlighter-rouge">strip!</code> returned nil and <code class="language-plaintext highlighter-rouge">upcase!</code> was never run on the original object. We didn’t know about it because <code class="language-plaintext highlighter-rouge">nil&.upcase!</code> was not raising an error. We ended up with lots of device numbers in an invalid state, leading to errors on our external provider’s api, and had to correct them manually with a rake task.</p>
<p>It’s always worth checking the documentation for the subtle differences between methods with and without the <code class="language-plaintext highlighter-rouge">!</code>. We generally try to avoid chaining them to avoid introducing bugs like this one.</p>
<h3 id="arrays-concat-prepend--and-">Arrays: concat, prepend, + and «</h3>
<p>I find it useful to remind myself with clear examples of the precise output and side effects of each of these methods. Methods that modify the original receiver can also be a source of well-hidden bugs.</p>
<p><strong><code class="language-plaintext highlighter-rouge">Array#concat</code></strong></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">array_1</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Volkswagen'</span><span class="p">,</span> <span class="s1">'Vauxhall'</span><span class="p">,</span> <span class="s1">'Renault'</span><span class="p">]</span>
<span class="n">array_2</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Tesla'</span><span class="p">,</span> <span class="s1">'BMW'</span><span class="p">]</span>
<span class="n">array_1</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="n">array_2</span><span class="p">)</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault', 'Tesla', 'BMW']</span>
<span class="n">array_1</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault', 'Tesla', 'BMW']</span>
<span class="n">array_2</span>
<span class="c1">#=> ['Tesla', 'BMW']</span></code></pre></figure>
<p>Moral of the story: <code class="language-plaintext highlighter-rouge">array_1</code> is modified, <code class="language-plaintext highlighter-rouge">array_2</code> is unchanged.</p>
<h2 id="array">Array#+</h2>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">array_1</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Volkswagen'</span><span class="p">,</span> <span class="s1">'Vauxhall'</span><span class="p">,</span> <span class="s1">'Renault'</span><span class="p">]</span>
<span class="n">array_2</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Tesla'</span><span class="p">,</span> <span class="s1">'BMW'</span><span class="p">]</span>
<span class="n">array_1</span> <span class="o">+</span> <span class="n">array_2</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault', 'Tesla', 'BMW']</span>
<span class="n">array_1</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault']</span>
<span class="n">array_2</span>
<span class="c1">#=> ['Tesla', 'BMW']</span></code></pre></figure>
<p>Moral of the story: Neither array is modified.</p>
<p><strong><code class="language-plaintext highlighter-rouge">Array#prepend</code></strong></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">array_1</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Volkswagen'</span><span class="p">,</span> <span class="s1">'Vauxhall'</span><span class="p">,</span> <span class="s1">'Renault'</span><span class="p">]</span>
<span class="n">array_2</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Tesla'</span><span class="p">,</span> <span class="s1">'BMW'</span><span class="p">]</span>
<span class="n">array_1</span><span class="p">.</span><span class="nf">prepend</span><span class="p">(</span><span class="n">array_2</span><span class="p">)</span>
<span class="c1">#=> [['Volkswagen', 'Vauxhall', 'Renault'], 'Tesla', 'BMW']</span>
<span class="n">array_1</span>
<span class="c1">#=> [['Volkswagen', 'Vauxhall', 'Renault'], 'Tesla', 'BMW']</span>
<span class="n">array_2</span>
<span class="c1">#=> ['Tesla', 'BMW']</span></code></pre></figure>
<p>Moral of the story: <code class="language-plaintext highlighter-rouge">array_1</code> is modified, <code class="language-plaintext highlighter-rouge">array_2</code> is unchanged.</p>
<p><strong><code class="language-plaintext highlighter-rouge">Array#<<</code></strong></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">array_1</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Volkswagen'</span><span class="p">,</span> <span class="s1">'Vauxhall'</span><span class="p">,</span> <span class="s1">'Renault'</span><span class="p">]</span>
<span class="n">array_2</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Tesla'</span><span class="p">,</span> <span class="s1">'BMW'</span><span class="p">]</span>
<span class="n">array_1</span> <span class="o"><<</span> <span class="n">array_2</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault', ['Tesla', 'BMW']]</span>
<span class="n">array_1</span>
<span class="c1">#=> ['Volkswagen', 'Vauxhall', 'Renault', ['Tesla', 'BMW']]</span>
<span class="n">array_2</span>
<span class="c1">#=> ['Tesla', 'BMW']</span></code></pre></figure>
<p>Moral of the story: <code class="language-plaintext highlighter-rouge">array_1</code> is modified, <code class="language-plaintext highlighter-rouge">array_2</code> is unchanged.</p>
<h3 id="delegating-methods-with-moduledelegate">Delegating methods with Module#delegate</h3>
<p>You can use <code class="language-plaintext highlighter-rouge">delegate</code> to expose the methods of objects on another class. For example, a <code class="language-plaintext highlighter-rouge">cancellation</code> belongs to a <code class="language-plaintext highlighter-rouge">rental</code> - as indeed you might expect it to in the real world.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Cancellation</span>
<span class="n">belongs_to</span> <span class="ss">:rental</span>
<span class="c1">#[...]</span>
<span class="k">end</span></code></pre></figure>
<p>By the way, this database relationship is incidental and not a strict criteria for the use of <code class="language-plaintext highlighter-rouge">delegate</code>. It is a hint though, that there might be some overlap in the implementation of these two classes, and thus that there may be scope to <code class="language-plaintext highlighter-rouge">delegate</code>.</p>
<p>The <code class="language-plaintext highlighter-rouge">cancellations</code> table might look something like this in the database:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="n">t</span><span class="p">.</span><span class="nf">string</span> <span class="s2">"state"</span>
<span class="n">t</span><span class="p">.</span><span class="nf">integer</span> <span class="s2">"rental_id"</span><span class="p">,</span>
<span class="n">t</span><span class="p">.</span><span class="nf">integer</span> <span class="s2">"some_other_id_field"</span><span class="p">,</span>
<span class="n">t</span><span class="p">.</span><span class="nf">decimal</span> <span class="s2">"some_refund_field"</span><span class="p">,</span>
<span class="n">t</span><span class="p">.</span><span class="nf">decimal</span> <span class="s2">"some_other_refund_field"</span></code></pre></figure>
<p>As you can see, it does not have its own <code class="language-plaintext highlighter-rouge">currency</code> column.</p>
<p>So, what if you need to access the currency of a cancellation? You could:</p>
<p><code class="language-plaintext highlighter-rouge">cancellation.rental.currency</code>.</p>
<p>But this means that the <code class="language-plaintext highlighter-rouge">cancellation</code> object has to know that a rental object has a currency column. That violates the <a href="https://en.wikipedia.org/wiki/Law_of_Demeter" target="_blank">Law of Demeter</a>, which is the principle that objects should know as little as possible about each other. If our <code class="language-plaintext highlighter-rouge">cancellation</code> object knows too much about the <code class="language-plaintext highlighter-rouge">rental</code> object, or is <em>coupled too closely</em>, then any future changes to the implementation of the <code class="language-plaintext highlighter-rouge">Rental</code> class become hard to maintain.</p>
<p>You can use <code class="language-plaintext highlighter-rouge">delegate</code> to avoid chainging objects in this way. On the <code class="language-plaintext highlighter-rouge">Cancellation</code> class you can do:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Cancellation</span>
<span class="n">belongs_to</span> <span class="ss">:rental</span>
<span class="n">delegate</span> <span class="ss">:currency</span><span class="p">,</span> <span class="ss">to: :rental</span>
<span class="c1">#[...]</span>
<span class="k">end</span></code></pre></figure>
<p>Then, you might call <code class="language-plaintext highlighter-rouge">cancellation.currency</code> elsewhere in the code. For example:</p>
<p><code class="language-plaintext highlighter-rouge">invoices = Invoice.where(currency: @cancellation.currency)</code></p>
<p>This helps keep your code DRYer, avoids object-chaining and respects the law of Demeter. Hoorah!</p>
<h3 id="capybarascreenshot">CapybaraScreenshot</h3>
<p>The Capybara-Screenshot gem will automatically capture a screenshot for each failure in your test suite. But did you know that you can also manually capture photos? Just pop one of the following directly in your code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Capybara</span><span class="o">::</span><span class="no">Screenshot</span><span class="p">.</span><span class="nf">screenshot_and_save_page</span>
<span class="no">Capybara</span><span class="o">::</span><span class="no">Screenshot</span><span class="p">.</span><span class="nf">screenshot_and_open_image</span></code></pre></figure>
<p>and a lovely screenshot will be taken of the current step in your integration spec at that point in time. This has helped me countless times to debug my integration specs. You get to see what the user would see at that stage in the flow, and check that all information is correct and displaying as it should. Plus you’ll get a more digestible error output and stacktrace.</p>
<h3 id="to_sql">to_sql</h3>
<p>9 weeks didn’t leave a whole lot of time to cover SQL in any detail. As I start to work on more complex projects, I need to make data-based decisions or include SQL in my requests for performance reasons. Chaining <code class="language-plaintext highlighter-rouge">to_sql</code> to an ActiveRecord relation returns the SQL statement run by the database adapter against the database to retrieve the results.</p>
<p>For example:</p>
<p><code class="language-plaintext highlighter-rouge">OpenDevice.where(“id < ?”, 500).to_sql</code></p>
<p>returns</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="nv">`open_devices`</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">`open_devices`</span> <span class="k">WHERE</span> <span class="p">(</span><span class="n">id</span> <span class="o">></span> <span class="mi">500</span><span class="p">)</span></code></pre></figure>
<p>Little by little, this is helping to improve my understanding of the underlying SQL syntax, rather than relying on the magical layer between me and the database that is provided by ActiveRecord.</p>
<p>Whether you are setting out on your full-stack adventure, or you already have a bit of experience, I hope this summary of some tips and tricks has been helpful. Don’t hestitate to reach out with comments or feedback :)</p>
<p>This article follows on from <a href="https://drivy.engineering/ruby-tricks-for-junior-devs/" target="_blank">Clement’s post</a> in which he details useful tips and tricks learnt while working at Drivy. Like Clement, I undertook <a href="https://www.lewagon.com/program" target="_blank">Le Wagon’s</a> intensive 9-week bootcamp. The program was great for a rapid overview of the key elements of full-stack engineering.</p>
Wed, 18 Sep 2019 00:00:00 +0000
https://getaround.tech/more-junior-tricks/
https://getaround.tech/more-junior-tricks/A basic decision tree in RubyJean Anquetil<p>Recently, we did a rework of the user’s profile completion flow in our Drivy web and mobile applications. We went from a basic single screen form to a multi-steps one. The idea was to simplify the flow and ask only for the information needed depending on the user’s answers. As we had to deal with multiple possible paths, we decided to work on a little decision tree algorithm.</p>
<figure>
<img alt="Multi steps flow" src="/assets/posts/2019-08-23-basic-decision-tree-in-ruby/multi_steps_flow.png" />
<figcaption>
Fig 1. The new profile flow
</figcaption>
</figure>
<h2 id="what-do-we-need-to-define-our-decision-tree">What do we need to define our decision tree?</h2>
<p>Let’s say that our decision tree is made up of multiple <code class="language-plaintext highlighter-rouge">steps</code> and for every steps there’s one or several possible answers that we will call <code class="language-plaintext highlighter-rouge">outcomes</code>.</p>
<figure>
<img alt="Decision tree drawing" src="/assets/posts/2019-08-23-basic-decision-tree-in-ruby/decision_tree_figure_1.png" />
<figcaption>
Fig 2. First step
</figcaption>
</figure>
<figure>
<img alt="Decision tree drawing" src="/assets/posts/2019-08-23-basic-decision-tree-in-ruby/decision_tree_figure_2.png" />
<figcaption>
Fig 3. Complete flow made up of outcomes
</figcaption>
</figure>
<p>If we know the <code class="language-plaintext highlighter-rouge">path</code>, which is made up of outcomes, we will be able to find the next step.</p>
<figure>
<img alt="Decision tree drawing" src="/assets/posts/2019-08-23-basic-decision-tree-in-ruby/decision_tree_figure_3.png" />
<figcaption>
Fig 4. The path made up of outcomes
</figcaption>
</figure>
<h2 id="decision-tree-declaration">Decision tree declaration</h2>
<p>The idea is to have a collection where the first element is a step class name and the second one is an object composed of the possible step’s outcomes. For each step’s outcome, we define a collection where the first element is a step class name and the second one is an object…and so on.</p>
<p>Let’s imagine that we need to ask if the user wants to rent a car or if they own a car and want to add it on the platform.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">DECISION_TREE</span> <span class="o">=</span> <span class="p">[</span>
<span class="no">RoleStep</span><span class="p">,</span> <span class="p">{</span>
<span class="no">RoleStep</span><span class="o">::</span><span class="no">DRIVER</span> <span class="o">=></span> <span class="p">[</span>
<span class="no">DriverStep</span><span class="p">,</span> <span class="p">{</span>
<span class="no">DriverStep</span><span class="o">::</span><span class="no">EligibleLicenseYears</span> <span class="o">=></span> <span class="p">[</span>
<span class="o">...</span>
<span class="p">],</span>
<span class="no">DriverStep</span><span class="o">::</span><span class="no">IneligibleLicenseYears</span> <span class="o">=></span> <span class="p">[</span>
<span class="o">...</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="no">RoleStep</span><span class="o">::</span><span class="no">OWNER</span> <span class="o">=></span> <span class="p">[</span>
<span class="no">OwnerStep</span><span class="p">,</span> <span class="p">{</span>
<span class="no">OwnerStep</span><span class="o">::</span><span class="no">CarHasManualTransmission</span> <span class="o">=></span> <span class="p">[</span>
<span class="o">...</span>
<span class="p">],</span>
<span class="no">OwnerStep</span><span class="o">::</span><span class="no">CarHasAutomaticTransmission</span> <span class="o">=></span> <span class="p">[</span>
<span class="o">...</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span></code></pre></figure>
<h2 id="step-class-declaration">Step class declaration</h2>
<p>The step class would gather its possible outcomes and everything else related to it. For instance, in almost all of our steps we had to deal with a form so this is where we defined it. We could define a <code class="language-plaintext highlighter-rouge">#can_skip?</code> method that could check if the step has already been filled, or could be skippable somehow. Doing this way, it becomes really convenient to define specific rules on steps.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">RoleStep</span>
<span class="no">DRIVER</span> <span class="o">=</span> <span class="ss">:driver</span>
<span class="no">OWNER</span> <span class="o">=</span> <span class="ss">:owner</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">form_class</span>
<span class="no">RoleStepForm</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h2 id="finding-the-next-step">Finding the next step</h2>
<p>Once we have our decision tree declared with all of its steps, we need to build a small recursive method that will find the next step according to the <code class="language-plaintext highlighter-rouge">path</code> (cf figure 4).</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">next_step</span><span class="p">(</span><span class="ss">path: </span><span class="p">[],</span> <span class="ss">steps: </span><span class="no">DECISION_TREE</span><span class="p">)</span>
<span class="k">return</span> <span class="n">steps</span> <span class="k">if</span> <span class="n">path</span><span class="p">.</span><span class="nf">empty?</span>
<span class="n">next_steps</span> <span class="o">=</span> <span class="n">steps</span><span class="p">.</span><span class="nf">last</span><span class="p">[</span><span class="n">path</span><span class="p">.</span><span class="nf">shift</span><span class="p">]</span>
<span class="n">next_step</span><span class="p">(</span><span class="ss">path: </span><span class="n">path</span><span class="p">,</span> <span class="ss">steps: </span><span class="no">Array</span><span class="p">(</span><span class="n">next_steps</span><span class="p">))</span>
<span class="k">end</span></code></pre></figure>
<p>And here you are, you can now iterate through the <code class="language-plaintext highlighter-rouge">DECISION_TREE</code>, giving a path or not, to find the according next step.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">puts next_step.first
<span class="c"># => RoleStep</span>
puts next_step<span class="o">(</span>path: <span class="o">[</span>RoleStep::OWNER]<span class="o">)</span>.first
<span class="c"># => OwnerStep</span></code></pre></figure>
<p>Recently, we did a rework of the user’s profile completion flow in our Drivy web and mobile applications. We went from a basic single screen form to a multi-steps one. The idea was to simplify the flow and ask only for the information needed depending on the user’s answers. As we had to deal with multiple possible paths, we decided to work on a little decision tree algorithm.</p>
Fri, 23 Aug 2019 00:00:00 +0000
https://getaround.tech/basic-decision-tree-in-ruby/
https://getaround.tech/basic-decision-tree-in-ruby/Sharing React components with rollup.jsThibaud Esnouf<p>At Drivy, we have defined our very own design system.
This system describes our visual guidelines and rules, and is composed of visual web components.
For each component, we have created a React implementation that could easily be used by our design team to build a web site documentation (thanks to <a href="https://mdxjs.com/">MDX</a>).</p>
<p>Having a documented design system was an achievement in itself. But to fully take advantage of it, the final step was to use our React components in other frontend projects.</p>
<p>In a Node.js world, that means importing them as a node module dependency.</p>
<p>The main steps are:</p>
<ol>
<li>bundle the components in a useable manner</li>
<li>publish the result through NPM</li>
</ol>
<h2 id="project-characteristics">Project characteristics</h2>
<p>Key points of the design system project to bundle:</p>
<ul>
<li>React components (tsx files)</li>
<li>TypeScript (ts files)</li>
<li>SVG assets</li>
<li>Sass classes and utilities</li>
<li>Design tokens (single source of truth variables stored in JSON files, used to propagate our design decisions)</li>
</ul>
<p>Notice that some of the sources have a specific syntax (tsx/ts files) and have to be transformed (transpiled) to be read by a browser.
Bundling process must produce outputs that can be seamlessly imported in a tier project without extra configuration or processing.</p>
<h2 id="building-the-project-with-rollupjs">Building the project with rollup.js</h2>
<p>We chose rollup.js as bundler tool for our library because it is well adapted: it’s efficient and easy to configure. Other module bundlers like Webpack and Parcel provide advanced features for a developer (dev-server with hot module replacement, for example) but those things are not required for our achievement.</p>
<p>We want the following outputs:</p>
<ul>
<li>React components as ES modules (preferred to CommonJs as it is more future proof and allows <a href="https://developers.google.com/web/fundamentals/performance/optimizing-javascript/tree-shaking/">tree-shaking</a>)</li>
<li>TypeScript declarations files</li>
<li>SVGs</li>
<li>Source maps</li>
<li>Sass files</li>
<li>Design tokens</li>
</ul>
<h3 id="rollup-configuration">Rollup configuration</h3>
<p>Rollup is configured thanks to a <code class="language-plaintext highlighter-rouge">rollup.config.js</code> file at the root of our project.</p>
<h4 id="plugins">Plugins</h4>
<p>Rollup has a bunch of plugins. For our needs we use:</p>
<ul>
<li>rollup-plugin-typescript2 (to transpile TypeScript files and generate declarations)</li>
<li>rollup-plugin-json (to convert our token .json files to ES6 modules)</li>
<li>rollup-plugin-svgo (to export SVGs through JavaScript)</li>
</ul>
<p>Note:
currently, we don’t use a plugin to convert our Sass files to CSS ones.
It’s a deliberate choice as we want to output our design system variables and mixins to use them in other projects using Sass.
But it would be great if we could support both Sass and CSS, in order to stick with our “ready-to-use” principle.
So we’ve scheduled this task in our roadmap.</p>
<h4 id="entry-point-and-output">Entry point and output</h4>
<p>Rollup’s config requires an entry point that will be used to resolve the dependencies</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"input"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src/index.ts"</span>
<span class="p">}</span></code></pre></figure>
<p>This file will export all our components</p>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="k">export</span> <span class="p">{</span> <span class="nx">BasicCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./components/BasicCell/</span><span class="dl">"</span>
<span class="k">export</span> <span class="p">{</span> <span class="nx">BulletList</span><span class="p">,</span> <span class="nx">BulletListItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./components/BulletList/</span><span class="dl">"</span>
<span class="k">export</span> <span class="p">{</span> <span class="nx">Button</span><span class="p">,</span> <span class="nx">ButtonGroup</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./components/Button/</span><span class="dl">"</span>
<span class="p">...</span></code></pre></figure>
<p>Then we define how the build result should be output</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dist/index.js"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"format"</span><span class="p">:</span><span class="w"> </span><span class="s2">"esm"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"sourcemap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<ul>
<li>The result will be put in a dist folder</li>
<li>We tell Rollup to generate the sourcemaps</li>
<li>We tell Rollup to generate <a href="https://rollupjs.org/guide/en#es-module-syntax">ES modules</a>.
This will allow Tree Shaking</li>
</ul>
<h3 id="exclude-external-dependencies">Exclude external dependencies</h3>
<p>Our project is based on React and so uses some external dependencies (react, react-dom, classnames …).
We don’t want such libraries to be resolved and bundled with our project.
So, all dependencies external to the project (described in package.json dependencies/peerDependencies) are configured to be excluded from the build process</p>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="p">{</span>
<span class="nl">external</span><span class="p">:</span> <span class="p">[</span>
<span class="p">...</span><span class="nb">Object</span><span class="p">.</span><span class="nf">keys</span><span class="p">(</span><span class="nx">pkg</span><span class="p">.</span><span class="nx">dependencies</span> <span class="o">||</span> <span class="p">{}),</span>
<span class="p">...</span><span class="nb">Object</span><span class="p">.</span><span class="nf">keys</span><span class="p">(</span><span class="nx">pkg</span><span class="p">.</span><span class="nx">peerDependencies</span> <span class="o">||</span> <span class="p">{})</span>
<span class="p">],</span>
<span class="p">}</span></code></pre></figure>
<h2 id="typescript-declarations-types">TypeScript declarations (types)</h2>
<p>Our project being set up with TypeScript, we want to export our declarations files (*.d.ts) so our lib API will be smoothly consumed (available) by any other TypeScript project.</p>
<p>We have 2 options:</p>
<ul>
<li>Put declaration files with the same name and at the same level as your JavaScript modules. So for the <code class="language-plaintext highlighter-rouge">hello.js</code> module, TypeScript will look for a <code class="language-plaintext highlighter-rouge">hello.d.ts</code> file in the same directory.</li>
<li>Declare the entry point for your declaration file in your <code class="language-plaintext highlighter-rouge">package.json</code></li>
</ul>
<p>Example:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"types"</span><span class="p">:</span><span class="w"> </span><span class="s2">"types/index.d.ts"</span>
<span class="p">}</span></code></pre></figure>
<p>We recommend the latter. It allows you to separate the types (TypeScript related) from your JavaScript code source</p>
<h3 id="going-further-with-the-rollup-configuration">Going further with the rollup configuration</h3>
<p>To obtain this result, you can configure the TypeScript compiler (tsconfig/json) to generate the declaration files in a dedicated directory</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"declaration"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"declarationDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dist/types"</span><span class="p">,</span></code></pre></figure>
<p>Then you can configure rollup to use this setting for its TypeScript plugin</p>
<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="p">{</span>
<span class="nl">plugins</span><span class="p">:</span> <span class="p">[</span>
<span class="nf">typescript</span><span class="p">({</span>
<span class="na">typescript</span><span class="p">:</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">typescript</span><span class="dl">"</span><span class="p">),</span>
<span class="na">useTsconfigDeclarationDir</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">}),</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="build-task">Build task</h2>
<p>We launch rollup to build our project:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">yarn rollup <span class="nt">-c</span></code></pre></figure>
<p>The result is available in the dist folder (as defined in the rollup output config)</p>
<figure>
<img alt="rollup build result" src="/assets/posts/2019-07-24-sharing-react-components-with-rollupjs/rollup_build_result.png" />
</figure>
<p>Note:
Our full build script launches rollup then copy our <code class="language-plaintext highlighter-rouge">package.json</code> to the dist folder.</p>
<h2 id="publishing">Publishing</h2>
<p>We are now ready to publish our package to NPM so it will be available as a dependency to another project (using npm install / yarn add).
NPM has different mechanisms to specify the files to package and publish:</p>
<ul>
<li>Blacklist strategy (.gitignore + .npmignore )</li>
<li>Whitelist strategy: <a href="https://docs.npmjs.com/files/package.json#files">files configuration in package.json</a></li>
</ul>
<p>These 2 approaches are somewhat different, and one is not necessarily preferable to the other. However, the blacklisting strategy tends to be riskier as it exposes files that are sensitive or not relevant.
Keep in mind that some files are always included, regardless of settings:</p>
<ul>
<li>package.json</li>
<li>README</li>
<li>CHANGES / CHANGELOG / HISTORY</li>
<li>LICENSE / LICENCE</li>
<li>NOTICE</li>
<li>The file in the “main” field</li>
</ul>
<p>In <code class="language-plaintext highlighter-rouge">package.json</code>:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"files"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="s2">"tokens/**/*"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">"**/*.{scss,d.ts,js.map,svg,png,woff,woff2}"</span><span class="p">,</span>
<span class="w"> </span><span class="s2">".stylelintrc.js"</span>
<span class="w"> </span><span class="p">]</span></code></pre></figure>
<p>Tip:
It’s not easy to visualize files that are packaged by NPM (npmjs.com doesn’t list files of a module)
To do so with your local project, execute the following command in the directory containing your <code class="language-plaintext highlighter-rouge">package.json</code> file:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">npm pack <span class="o">&&</span> <span class="nb">tar</span> <span class="nt">-xvzf</span> _.tgz <span class="o">&&</span> <span class="nb">rm</span> <span class="nt">-rf</span> package _.tgz</code></pre></figure>
<p>It will output the files that will be included in the publishing process</p>
<h2 id="testingusing-locally">Testing/using locally</h2>
<p>When importing our components in a tier-project and using them in a real context, we can often encounter conflicts (from tier css rules for example) and unintended behavior, or simply discover some bugs.
We can’t afford to wait for our components to be published to encounter those issues.
We must have a way to test our components in a tier project before publishing to NPM
We can use the npm/yarn link mechanism to symlink our component project and add it to the node_modules of another one.
However, such a mechanism doesn’t allow us to perfectly reflect how our project will be packaged by NPM and deployed in another project. Plus, some configuration files that would not be present in the node modules (filtered by the NPM publishing process) can interfere.
To be closer to the real process (how our package will be published and installed), we used an advanced tool named <code class="language-plaintext highlighter-rouge">yalc</code>.</p>
<p><code class="language-plaintext highlighter-rouge">yalc</code> allows to package a project and add it as a node module like NPM would do, but in a local store.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We can now use our design system assets in any frontend projects.
There are natural advantages that come with using a centralised library: single source of truth, reduced maintenance cost, etc. But using a centralised library also enables us to invest more in our design system, which in turn makes it easier for the design system to be adopted company-wide.</p>
<p>At Drivy, we have defined our very own design system.
This system describes our visual guidelines and rules, and is composed of visual web components.
For each component, we have created a React implementation that could easily be used by our design team to build a web site documentation (thanks to <a href="https://mdxjs.com/">MDX</a>).</p>
Wed, 24 Jul 2019 00:00:00 +0000
https://getaround.tech/sharing-react-components-with-rollupjs/
https://getaround.tech/sharing-react-components-with-rollupjs/How Kotlin's Coroutines help us to deal with BluetoothRomain Guefveneu<p>At Drivy, we want to enable users to open the car even if it’s on the bottom floor of the deepest, underground parking. Since we can’t rely on a GSM connection when so deep underground, we need to use a Bluetooth connection. <br />
But communicating with a Bluetooth device is easier said than done, due to the fact that it’s low-level and requires many asynchronous calls. Let’s see how we can improve this.</p>
<h2 id="bluetooth-101">Bluetooth 101</h2>
<p>Bluetooth communication is not exactly like HTTP communication. We don’t have URLs or ports. All we have are services and caracteristics. And UUIDs, lots of UUIDs.</p>
<p>According to the <a href="https://www.bluetooth.com/specifications/gatt/services/">official doc</a>, Bluetooth GATT services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device.
So basically a service is a set of characteristics.</p>
<p>According to the same <a href="https://www.bluetooth.com/specifications/gatt/characteristics/">official doc</a>, “Characteristics are defined attribute types that contain a single logical value.”<br />
Characteristics are where the data is, that’s what we want to read or write.</p>
<p>Last thing, services and characteristics are identified by UUIDs.</p>
<h2 id="bluetooth-callbacks">Bluetooth callbacks</h2>
<p>On Android, a Bluetooth device communicates with us via a <code class="language-plaintext highlighter-rouge">BluetoothGattCallback</code>:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">abstract</span> <span class="kd">class</span> <span class="nc">BluetoothGattCallback</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">onConnectionStateChange</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">newState</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{}</span>
<span class="k">fun</span> <span class="nf">onServicesDiscovered</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{}</span>
<span class="k">fun</span> <span class="nf">onCharacteristicRead</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">:</span> <span class="nc">BluetoothGattCharacteristic</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{}</span>
<span class="k">fun</span> <span class="nf">onCharacteristicWrite</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">:</span> <span class="nc">BluetoothGattCharacteristic</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{}</span>
<span class="na"> </span>
<span class="na"> [...]</span>
<span class="p">}</span></code></pre></figure>
<p>Here is our issue: when we write a characteristic to the Bluetooth device to send a command, we want to wait for the device’s acknowledgement to continue. In other words, we want to communicate synchronously with the device.<br />
To do so, we need to block the execution until <code class="language-plaintext highlighter-rouge">onCharacteristicWrite</code> is called back for my characteristic.</p>
<h2 id="kotlins-coroutines-and-channels">Kotlin’s coroutines and channels</h2>
<p><a href="https://kotlinlang.org/docs/reference/coroutines-overview.html">Coroutines</a> are a great tool for dealing with asynchronous calls. Combined with channels, we have here the perfect tools to communicate synchronously with a Bluetooth device.</p>
<p>Here is a simple “Hello World!” using a channel:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">suspend</span> <span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">coroutineScope</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">Channel</span><span class="p"><</span><span class="nc">String</span><span class="p">>()</span>
<span class="nf">launch</span> <span class="p">{</span>
<span class="nf">delay</span><span class="p">(</span><span class="mi">3000L</span><span class="p">)</span>
<span class="n">channel</span><span class="p">.</span><span class="nf">offer</span><span class="p">(</span><span class="s">"World!"</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Hello ${channel.receive()}"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">channel.receive()</code> will wait for the channel to have something to offer. In this way, “Hello World!” will be displayed 3 seconds later.</p>
<h2 id="bluetooth-callbacks-coroutines-and-channels">Bluetooth callbacks, Coroutines and Channels</h2>
<p>What we need is a way to wait for the device acknowledgment before sending another command. We’ll use a coroutine and a channel to achieve this.</p>
<h3 id="the-channel-setup">The channel setup</h3>
<p>We’ll use a channel of <code class="language-plaintext highlighter-rouge">BluetoothResult</code>s, a data class composed of the characteristic’s UUID and value, and the event status:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="kd">data class</span> <span class="nc">BluetoothResult</span><span class="p">(</span><span class="kd">val</span> <span class="py">uuid</span><span class="p">:</span> <span class="nc">UUID</span><span class="p">,</span> <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">ByteArray</span><span class="p">?,</span> <span class="kd">val</span> <span class="py">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span></code></pre></figure>
<p>Each call to <code class="language-plaintext highlighter-rouge">onCharacteristicRead</code> or <code class="language-plaintext highlighter-rouge">onCharacteristicWrite</code> will offer to the channel a <code class="language-plaintext highlighter-rouge">BluetoothResult</code>:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">private</span> <span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">Channel</span><span class="p"><</span><span class="nc">BluetoothResult</span><span class="p">>()</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">gattCallback</span> <span class="p">=</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">BluetoothGattCallback</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCharacteristicRead</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">:</span> <span class="nc">BluetoothGattCharacteristic</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="n">channel</span><span class="p">.</span><span class="nf">offer</span><span class="p">(</span><span class="nc">BluetoothResult</span><span class="p">(</span><span class="n">characteristic</span><span class="p">.</span><span class="n">uuid</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">status</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCharacteristicWrite</span><span class="p">(</span><span class="n">gatt</span><span class="p">:</span> <span class="nc">BluetoothGatt</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">:</span> <span class="nc">BluetoothGattCharacteristic</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="n">channel</span><span class="p">.</span><span class="nf">offer</span><span class="p">(</span><span class="nc">BluetoothResult</span><span class="p">(</span><span class="n">characteristic</span><span class="p">.</span><span class="n">uuid</span><span class="p">,</span> <span class="n">characteristic</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="n">status</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We now need a function that will wait for the channel to have a matching <code class="language-plaintext highlighter-rouge">BluetoothResult</code> to offer:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">private</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">waitForResult</span><span class="p">(</span><span class="n">uuid</span><span class="p">:</span> <span class="nc">UUID</span><span class="p">):</span> <span class="nc">BluetoothResult</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">withTimeoutOrNull</span><span class="p">(</span><span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">SECONDS</span><span class="p">.</span><span class="nf">toMillis</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">bluetoothResult</span><span class="p">:</span> <span class="nc">BluetoothResult</span> <span class="p">=</span> <span class="n">channel</span><span class="p">.</span><span class="nf">receive</span><span class="p">()</span>
<span class="k">while</span> <span class="p">(</span><span class="n">bluetoothResult</span><span class="p">.</span><span class="n">uuid</span> <span class="p">!=</span> <span class="n">uuid</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bluetoothResult</span> <span class="p">=</span> <span class="n">channel</span><span class="p">.</span><span class="nf">receive</span><span class="p">()</span>
<span class="p">}</span>
<span class="n">bluetoothResult</span>
<span class="p">}</span> <span class="o">?:</span> <span class="nf">run</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nc">BluetoothTimeoutException</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This <code class="language-plaintext highlighter-rouge">waitForResult</code> function will wait for the channel for 3 seconds, or throw a custom <code class="language-plaintext highlighter-rouge">BluetoothTimeoutException</code>.</p>
<p>Then, we’ll use a new <code class="language-plaintext highlighter-rouge">BluetoothGatt.readCharacteristic</code> function that will wait for the response, via the channel:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">private</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nc">BluetoothGatt</span><span class="p">.</span><span class="nf">readCharacteristic</span><span class="p">(</span><span class="n">serviceUUID</span><span class="p">:</span> <span class="nc">UUID</span><span class="p">,</span> <span class="n">characteristicUUID</span><span class="p">:</span> <span class="nc">UUID</span><span class="p">):</span> <span class="nc">BluetoothResult</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">characteristic</span> <span class="p">=</span> <span class="nf">getService</span><span class="p">(</span><span class="n">serviceUUID</span><span class="p">).</span><span class="nf">getCharacteristic</span><span class="p">(</span><span class="n">characteristicUUID</span><span class="p">)</span>
<span class="nf">readCharacteristic</span><span class="p">(</span><span class="n">characteristic</span><span class="p">)</span>
<span class="k">return</span> <span class="nf">waitForResult</span><span class="p">(</span><span class="n">characteristicUUID</span><span class="p">)</span>
<span class="p">}</span> </code></pre></figure>
<p>Et voilà! Now we can communicate synchronously with a Bluetooth device:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="kd">val</span> <span class="py">gatt</span> <span class="p">:</span> <span class="nc">BluetoothGatt</span> <span class="p">=</span> <span class="nf">connectToBluetoothDevice</span><span class="p">()</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">gatt</span><span class="p">.</span><span class="nf">readCharacteristic</span><span class="p">(</span><span class="nc">MY_SERVICE_UUID</span><span class="p">,</span> <span class="nc">MY_CHARACTERISTIC_UUID</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="n">e</span> <span class="p">:</span> <span class="nc">BluetoothTimeoutException</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">Log</span><span class="p">.</span><span class="nf">e</span><span class="p">(</span><span class="s">"Bluetooth"</span><span class="p">,</span> <span class="s">"Can't communicate with the device."</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>At Drivy, we want to enable users to open the car even if it’s on the bottom floor of the deepest, underground parking. Since we can’t rely on a GSM connection when so deep underground, we need to use a Bluetooth connection. <br />
But communicating with a Bluetooth device is easier said than done, due to the fact that it’s low-level and requires many asynchronous calls. Let’s see how we can improve this.</p>
Tue, 16 Jul 2019 00:00:00 +0000
https://getaround.tech/bluetooth-and-coroutines/
https://getaround.tech/bluetooth-and-coroutines/Things to consider when choosing a third-party APIChristophe Yammouni<p>External third-party-services APIs are useful: they allow you to benefit from the expertise and knowledge that others have acquired on a specific subject - a subject which is not your area of expertise and not the problem in hand. It would take too much time and effort to build and maintain such a service yourself.</p>
<p>However, choosing an API isn’t always an easy task.<br />
Indeed, your choice will have an impact on your codebase and database architecture, and even your service itself.
Imagine if your third-party payment-service went down: your customers wouldn’t be able to buy your products.</p>
<p>There are a lot of services that are alike providing APIs, with similar pricing and features. <br />
So, how can you be sure you are making the right choice?<br />
I’ll try to list all the different questions you need to ask yourself before implementing an API, covering documentation, libraries/SDKs, support, pricing, data privacy, and maintenance.</p>
<h3 id="documentation">Documentation</h3>
<p>Most of the time, when you need to choose a third-party library and there’s no documentation available, you can have a look at the source code to understand how it works and evaluate the code quality.</p>
<p>For a third-party-service API, as you won’t have access to the source code, you need to find a way to understand how it works, and to evaluate the implementation complexity.</p>
<p>Proper documentation should give you an idea of how your implementation looks. Most of your questions should be answered.</p>
<ul>
<li>What’s the data format (<a href="https://en.wikipedia.org/wiki/JSON">JSON</a>, <a href="https://en.wikipedia.org/wiki/XML">XML</a> etc.)?</li>
<li>Which authentication mechanism is required (<a href="https://en.wikipedia.org/wiki/Basic_access_authentication">basic auth</a>, API token, etc.)?</li>
<li>How are successes and errors rendered?</li>
<li>Which and how many calls do I need to complete my task?</li>
<li>For inputs and outputs, which attributes are required? Which type should they be, what do they mean and are they any examples?</li>
<li>If there are any string values with a specific format such as date, time, and country, then, which convention do they follow?</li>
</ul>
<p>Some documentation also provides live querying tools, enabling you to run tests to ensure documentation and code are aligned.
If it does not, be sure to find a way to test it before implementing it, because you can find some significant differences between documentation and production APIs.</p>
<h3 id="libraries">Libraries</h3>
<p>Having an SDK to consume an API can save you a lot of time, but be cautious about them.</p>
<ul>
<li>Is the SDK available in your language?</li>
<li>Does it follow the latest API version?</li>
<li>Does it handle all the features you want?</li>
<li>Does it provide useful feedback for errors?</li>
<li>Does it have any dependencies? Are they up-to-date? Beware that they might conflict with yours.</li>
<li>What size is it? For example, this can be a red flag for mobile apps: you don’t want to double the size of your app, just because of a third-party SDK.</li>
<li>If it has not been written by the company providing the API, be sure it’s mature enough and well maintained. Have a look at open issues, too.</li>
</ul>
<h3 id="consistency">Consistency</h3>
<p>While you’re having a look at documentation and the library, make sure everything is consistent. Lack of consistency can mean the underlying code quality is poor, and you’ll have trouble implementing it.</p>
<ul>
<li>If it’s a <a href="https://en.wikipedia.org/wiki/Representational_state_transfer">REST</a> API, make sure it follows the principles.</li>
<li>If the output is formatted in JSON, make sure all outputs are in JSON - even errors.</li>
<li>Check the cases of attributes (ex: <a href="https://en.wikipedia.org/wiki/Snake_case">Snake case</a>, <a href="https://en.wikipedia.org/wiki/CamelCase">Camel case</a>), and make sure all endpoints follow the same.</li>
<li>Does the naming of endpoints and attributes/parameters make sense to you?</li>
</ul>
<h3 id="support">Support</h3>
<p>Technical support can help a lot when you need your questions answered, whether it’s for implementation questions, or when you think there’s an outage.</p>
<ul>
<li>Does your pricing plan include any phone support?</li>
<li>If there’s phone support, is it available 24/7? If not, is it compatible with your time zone?</li>
<li>If there’s no phone support, do they answer mail quickly?</li>
<li>Are the answers relevant?</li>
<li>Are there any community forums? Are they active?</li>
<li>Does a status page exist? Are outages frequent, and if so, are they well explained?</li>
</ul>
<h3 id="reputation">Reputation</h3>
<p>Doing some research on the Internet can help you avoid surprises: find out the frequency of breaking changes and outages, or get news about the selling of the company providing the service and save yourself a migration.</p>
<ul>
<li>Can you find any resources on the internet aside from the provider’s page?</li>
<li>Do the articles about the service tend to be negative or positive?</li>
<li>Is it used by well-known companies?</li>
</ul>
<h3 id="pricing">Pricing</h3>
<p>This is a tough one because when you compare API pricing, you could think one is cheaper than the others.
However, digging deeper can make you realize the cheaper one is actually more expensive on higher volumes.</p>
<p>For a start, look beyond the pricing plan that suits your needs: you’ll scale eventually, and you’ll quickly realize that adding a new user or making a few extra requests will become so expensive that you’ll need to migrate to another service anyway.</p>
<ul>
<li>Is the price per month or per requests?</li>
<li>If it’s per request:
<ul>
<li>What’s the request limit? Does it fit your expected usage?</li>
<li>How much are you charged per additional request?</li>
<li>Can you monitor usage?</li>
</ul>
</li>
<li>How many calls are you allowed to make per hour/day/month? It can differ depending on the plan.</li>
<li>If there’s phone support, make sure it’s included in your plan.</li>
<li>If there’s a free plan, don’t hesitate to try it before you pay for it.</li>
</ul>
<h3 id="data-privacy">Data privacy</h3>
<p>Whether you are concerned by the <a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">GDPR</a> or not, if any personal data transits between your service and the provider, you should have a look at the data privacy policy.</p>
<ul>
<li>Does it have a privacy policy page?</li>
<li>For how long will they keep your data on their servers?</li>
<li>Will your data be shared with third parties?</li>
<li>If they provide an SDK, does it have dependencies on tracking libraries? If this SDK is implemented on the client side, it means that you need to ensure no data that can be used to identify a person is given.</li>
<li>Is communication between you and the provider secured enough? Take a look at authentication and communication protocol.</li>
</ul>
<h3 id="maintenance">Maintenance</h3>
<p>Even if all the lights are green and you are confident with your choice, have in mind that the third-party API you are integrating will be replaced eventually.</p>
<p>To ease up the replacement, design your integration with the migration process in mind.</p>
<ul>
<li>Encapsulate all the provider’s related code in abstraction layers (Ex: Remove the company reference from the method names).</li>
<li>Remove any provider’s related naming from your table/column names.</li>
<li>If you have some extra time, add multi-provider support, to do a progressive migration or fallback support.
<ul>
<li>For example, if you have to integrate a push notification service’s API, design your system so it can support having two providers at the same time.</li>
</ul>
</li>
</ul>
<p>To sum up, this is by no means an exhaustive list of the problems you might encounter when integrating third-party services. Spoiler: APIs are always tricky. This article feeds off our own experiences and struggles. It explores the trade-offs to be made, between investing time and energy in building and maintaining a service yourself, or choosing to benefit from the expertise and knowledge of a third-party service when you integrate their API - warts and all.</p>
<p>External third-party-services APIs are useful: they allow you to benefit from the expertise and knowledge that others have acquired on a specific subject - a subject which is not your area of expertise and not the problem in hand. It would take too much time and effort to build and maintain such a service yourself.</p>
Tue, 02 Jul 2019 00:00:00 +0000
https://getaround.tech/things-to-consider-when-choosing-a-third-party-api/
https://getaround.tech/things-to-consider-when-choosing-a-third-party-api/Design system and API-Driven UIRenaud Boulard<h1 id="why">Why?</h1>
<p>For some time now, we have been heavily relying on our API to display formatted content on our apps <a href="https://drivy.engineering/api-driven-apps/">API Driven Apps</a>. It enables us to be more agile, by shipping new features faster and easily iterating on them without updates. Recently, we pushed this paradigm even further, generating complete native views from the API with our design system.</p>
<p>There are several advantages to this, you can:</p>
<ul>
<li>Build a brand new screen with almost no mobile development if all the visual components already exist</li>
<li>Easily add or remove components on a given screen without the need of an app update</li>
<li>Run A/B testing on your screens to see what works best, by moving or adding/removing component</li>
</ul>
<p>Plus, you build your new features based on native views to keep the best experience for the end users. Let’s see how its work.</p>
<h1 id="design-system">Design system</h1>
<p>A design system is a collection of components that can be reused in different combinations to build your UI. It also includes colors, spacing and typography specifications. Design systems allow you to manage design at scale in order to build consistent websites and applications. Every component has its own purpose, and can itself show or hide a subset of information depending on the context.</p>
<p>Here’s an example of what the components in the Play Store App would be:</p>
<figure>
<img alt="PlayStore App" src="/assets/posts/2019-06-05-mobile-api-driven/playstore_app.png" />
<figcaption>
Play Store App components
</figcaption>
</figure>
<p>At Drivy we have built our own design system in order to develop consistent UI across all screens of the application. Our design team has worked on a series of components for both iOS and Android in order to respect the specific guidelines of each OS. Every component has a name and an associated custom view in our code base.</p>
<p>Here is a sneak peek of what it looks like:</p>
<figure>
<img alt="Drivy mobile design system component" src="/assets/posts/2019-06-05-mobile-api-driven/design_system_mobile.png" />
<figcaption>
Mobile design system component
</figcaption>
</figure>
<h1 id="api-driven-ui">API Driven UI</h1>
<p>The purpose of these components is to display information to our users. This information will most of the time come from a call to our API. So we decided to associate a JSON schema to every component of our design system.</p>
<p>Let’s take a simple component:</p>
<figure>
<img alt="basic_subtitled component" src="/assets/posts/2019-06-05-mobile-api-driven/drivy_open.png" />
<figcaption>
`basic_subtitled` component
</figcaption>
</figure>
<p>There are 3 pieces of information in this component:</p>
<ul>
<li>Icon</li>
<li>Title: <code class="language-plaintext highlighter-rouge">Drivy Open</code></li>
<li>Subtitle: <code class="language-plaintext highlighter-rouge">This is a self-service car.</code></li>
</ul>
<p>The associated JSON schema will look like this:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="nl">"type"</span><span class="p">:</span><span class="s2">"basic_subtitled"</span><span class="p">,</span>
<span class="nl">"title"</span><span class="p">:</span><span class="s2">"Drivy Open"</span><span class="p">,</span>
<span class="nl">"subtitle"</span><span class="p">:</span><span class="s2">"This is a self-service car."</span><span class="p">,</span>
<span class="nl">"icon_url"</span><span class="p">:</span><span class="s2">"https://drivy-assets.imgix.net/icons/open_badge.png"</span>
<span class="p">}</span></code></pre></figure>
<p>Each component has a <code class="language-plaintext highlighter-rouge">type</code> property, defining what kind of component it is. Here comes the magic: we can now have a list of components returned by the API, in order to build an entire screen. The <code class="language-plaintext highlighter-rouge">type</code> is used to deserialize the appropriate object and bind it to the appropriate custom view associated to the components.</p>
<p>Let’s take a example with our car details screen:</p>
<figure>
<img alt="basic_subtitled component" src="/assets/posts/2019-06-05-mobile-api-driven/car_detail_json.png" />
<figcaption>
Each component has a dedicated type, which gives us the ability to build an entire screen from the API. Also, `Components` are grouped by `Sections` to help organize content hierarchy.
</figcaption>
</figure>
<h1 id="concrete-example">Concrete example</h1>
<p>We have commercial vans on our platform. A few months ago, we decided to add all the dimensions of the vans to help our users know exactly what kind of furniture they can carry with the van.</p>
<p>As you can see below we were able to add a complex composant to the screen:</p>
<p><img alt="basic_subtitled component" src="/assets/posts/2019-06-05-mobile-api-driven/van.png" /></p>
<p>This change only required an update of the API, it’s win-win for everyone:</p>
<ul>
<li><strong>Customer</strong>: All of our customers can now see the van dimensions, no matter which version of the application they have</li>
<li><strong>App developer</strong>: No mobile update is required, which means no iOS or Android development</li>
<li><strong>Your Team</strong>: Only one backend developer is required, it’s time saved for the team</li>
<li><strong>The App</strong>: It’s a native feature</li>
</ul>
<h1 id="drawbacks">Drawbacks</h1>
<p>As with every software solution there are some drawbacks, this will no be the perfect solution for all of your screens:</p>
<ul>
<li>When updating a component, you should take care not to break the API for the previous version of the component</li>
<li>As usual when it comes to offline mode, you must save the data for the information, but you should also save the list of component return by the API otherwise your screen will be empty</li>
<li>You cannot have a completely different layout for the landscape mode or tablet</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>We have been using this technique for a few months, and we have already seen the benefits for some features. There is a small start up cost of setting it up in your app and API, but we definitely think that on a long terms basis, it’s a valuable and powerful tool for quickly building a new feature, in a scalable way.</p>
<p>Not all of the screens in your app will match with API/design system technique, but when it comes to a new screen you can ask yourself the following questions:</p>
<ul>
<li>Will this screen need frequent updates/iterations?</li>
<li>Do we want to run A/B tests?</li>
<li>Does this feature get its information from an API?</li>
</ul>
<p>If the answer to all of these questions is <em>YES</em>, it could be a good solution to go for.</p>
<h1 id="why">Why?</h1>
Wed, 05 Jun 2019 00:00:00 +0000
https://getaround.tech/mobile-api-driven/
https://getaround.tech/mobile-api-driven/Embracing or banishing randomnessNicolas Zermati<p>Writing tests is becoming a big part of our job. If it isn’t yet, I strongly encourage you to push your organization down that path. <em>Why</em> could be the topic of another article.</p>
<p>I think there is a tremendous value in having an efficient test-suite. By efficient, I mean that it doesn’t give much extra work when refactoring and it gives accurate information when something is broken. And by accurate, I mean having as few false positive as possible, as many defects being caught as possible, and as few tests failing as possible for a single defect.</p>
<p>As important as tests are to me, I don’t give as much attention to tests as I give to production code… In my reviews, I tend to have lower standards when looking at the tests. For instance, I won’t ask for a refactoring of the tests as long as they <em>seem</em> to be testing the behavior that just changed. It leads to heterogeneous practices. And on some topics, we simply disagree!</p>
<p>This article will be about a controversial topic and will <em>try</em> to show the benefits of using randomness in your tests. I will also cover some of the downsides too and if you have more points you would like to add, please ping <a href="https://twitter.com/nicoolas25">me on Twitter</a>.</p>
<h3 id="context">Context</h3>
<p>The examples in this article will follow a feature and its testing journey. Here is a description of the feature:</p>
<p><em>We consider the duration of the rental to be the number of 24-hour chunks between its start time and its end time. When a trip spans across more calendar days than its number of 24-hour chunks, we would like to use the pricing of the car for the most relevant days. For instance: if a trip starts at 2pm and finishes at 8am the next day, we would like to consider the pricing of the car for the first day to be from 2pm to midnight.</em></p>
<p>Here we’ll look at the development of the tests written to test the <code class="language-plaintext highlighter-rouge">#date_range</code> method. This method gives the relevant days we should consider in order to price the trip.</p>
<h3 id="use-case-based-approach">Use-case based approach</h3>
<p>In this context, in order to clarify things between the product owner and the development team, some examples were created and agreed upon before the code was created. Those examples were translated into the following test-cases by the developer:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">subject</span><span class="p">(</span><span class="ss">:date_range</span><span class="p">)</span> <span class="k">do</span>
<span class="n">described_class</span><span class="p">.</span><span class="nf">date_range</span><span class="p">(</span><span class="n">starts_at</span><span class="p">,</span> <span class="n">ends_at</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_on</span><span class="p">)</span> <span class="p">{</span> <span class="n">starts_at</span><span class="p">.</span><span class="nf">to_date</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:ends_on</span><span class="p">)</span> <span class="p">{</span> <span class="n">ends_at</span><span class="p">.</span><span class="nf">to_date</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:ends_at</span><span class="p">)</span> <span class="p">{</span> <span class="n">starts_at</span> <span class="o">+</span> <span class="n">duration</span> <span class="p">}</span>
<span class="n">context</span> <span class="s2">"when the duration is less than 24 hours"</span> <span class="k">do</span>
<span class="n">context</span> <span class="s2">"when the start and the end time are on the same day"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-01 07:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">13</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="s2">"returns a range including only the day the trip started"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="n">starts_on</span><span class="o">..</span><span class="n">starts_on</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the trip spans across 2 calendar days"</span> <span class="k">do</span>
<span class="n">context</span> <span class="s2">"when the majority of the trip happens on the first day"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-03 11:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">22</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="s2">"considers only the first day"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="n">starts_on</span><span class="o">..</span><span class="n">starts_on</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the majority of the trip happens on the second day"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-01 20:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">23</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="s2">"considers only the second day"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="n">ends_on</span><span class="o">..</span><span class="n">ends_on</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the duration is between 24 and 48 hours"</span> <span class="k">do</span>
<span class="n">context</span> <span class="s2">"when the trip spans across 2 calendar days"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-01 11:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">36</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="n">starts_on</span><span class="o">..</span><span class="n">ends_on</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the trip spans across 3 calendar days"</span> <span class="k">do</span>
<span class="n">context</span> <span class="s2">"when a majority of time is spent on the last day compared to the first day"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-01 18:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">45</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="s2">"excludes the first day"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="p">(</span><span class="n">starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="n">ends_on</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when a majority of the rental's total time is on the first day rather than on the last day"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"2018-06-01 10:00"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">48</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span>
<span class="n">it</span> <span class="s2">"excludes the last day"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="n">starts_on</span><span class="o">..</span><span class="p">(</span><span class="n">ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>I rewrote the test names as the ones we had were <code class="language-plaintext highlighter-rouge">Example 1</code>, <code class="language-plaintext highlighter-rouge">Example 2</code>, and so on. They were extracted from a spreadsheet of use-cases the product team gave us.</p>
<p>What you may see here is that those examples describe <em>some</em> use-cases that <em>we believed would be enough</em> to ensure that the implementation was correct: ie to cover all cases. And it actually covered the given specifications correctly. And the implementation was making all tests green. Unfortunately, the whole team forgot about this one:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">context</span> <span class="s2">"when the duration is less than 24 hours"</span> <span class="k">do</span>
<span class="n">context</span> <span class="s2">"when the trip spans on 3 days (because of daylight savings)"</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:starts_at</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"2018-03-24 23:30"</span><span class="p">.</span><span class="nf">in_time_zone</span><span class="p">(</span><span class="s2">"Europe/Paris"</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:duration</span><span class="p">)</span> <span class="p">{</span> <span class="mi">24</span><span class="p">.</span><span class="nf">hours</span> <span class="p">}</span> <span class="c1"># Produces this time: 2018-03-26 00:30</span>
<span class="n">it</span> <span class="s2">"excludes the first and last days"</span> <span class="k">do</span>
<span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="p">(</span><span class="n">starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="p">(</span><span class="n">ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Because of daylight savings in some time zones, we could have one trip that spans across more than <em>N + 1</em> calendar days, where <em>N</em> is the number of 24-hour chunks between <code class="language-plaintext highlighter-rouge">starts_at</code> and <code class="language-plaintext highlighter-rouge">ends_at</code>. The first lesson here is to be <em>really</em> careful about the edge cases.</p>
<p>While in this example it does look like an edge case, it was actually a bit more common. We have an extra rule that allows a trip starting from 10am and finishing at 11am the next day to be considered as a one - rather than two - day trip.</p>
<h3 id="approaching-tests-from-a-different-angle">Approaching tests from a different angle</h3>
<p>The point of the article is to show that without being more clever, we could leverage another strategy to explore the expected behavior and detect that missing use-case from earlier.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">subject</span><span class="p">(</span><span class="ss">:date_range</span><span class="p">)</span> <span class="k">do</span>
<span class="n">described_class</span><span class="p">.</span><span class="nf">date_range</span><span class="p">(</span><span class="vi">@starts_at</span><span class="p">,</span> <span class="vi">@ends_at</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the trip spans over the same number of days than its duration"</span> <span class="k">do</span>
<span class="n">add_constraint</span> <span class="p">{</span> <span class="vi">@trip_span_size</span> <span class="o">==</span> <span class="vi">@number_of_days</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="vi">@starts_on</span><span class="o">..</span><span class="vi">@ends_on</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the trip spans over one more day than its duration"</span> <span class="k">do</span>
<span class="n">add_constraint</span> <span class="p">{</span> <span class="vi">@trip_span_size</span> <span class="o">==</span> <span class="vi">@number_of_days</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span>
<span class="n">context</span> <span class="s2">"when the lowest amount of time is spent on the last day"</span> <span class="k">do</span>
<span class="n">add_constraint</span> <span class="p">{</span> <span class="n">time_spent_on</span><span class="p">(</span><span class="vi">@starts_on</span><span class="p">)</span> <span class="o">>=</span> <span class="n">time_spent_on</span><span class="p">(</span><span class="vi">@ends_on</span><span class="p">)</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="vi">@starts_on</span><span class="o">..</span><span class="p">(</span><span class="vi">@ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the lowest amount of time is spent on the first day"</span> <span class="k">do</span>
<span class="n">add_constraint</span> <span class="p">{</span> <span class="n">time_spent_on</span><span class="p">(</span><span class="vi">@starts_on</span><span class="p">)</span> <span class="o"><</span> <span class="n">time_spent_on</span><span class="p">(</span><span class="vi">@ends_on</span><span class="p">)</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="p">(</span><span class="vi">@starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="vi">@ends_on</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s2">"when the trip spans over two more days than its duration"</span> <span class="k">do</span>
<span class="n">add_constraint</span> <span class="p">{</span> <span class="vi">@trip_span_size</span> <span class="o">==</span> <span class="vi">@number_of_days</span> <span class="o">+</span> <span class="mi">2</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="p">(</span><span class="vi">@starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="p">(</span><span class="vi">@ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="c1"># This method is called for each test until the result meet all the constraints.</span>
<span class="c1"># If a context doesn't meet any branch of the constraint tree, then it raises an</span>
<span class="c1"># error telling you what context you may be missing.</span>
<span class="k">def</span> <span class="nf">generate_context</span>
<span class="vi">@starts_at</span> <span class="o">=</span> <span class="n">random_datetime</span>
<span class="vi">@duration</span> <span class="o">=</span> <span class="n">random_trip_duration</span>
<span class="vi">@ends_at</span> <span class="o">=</span> <span class="vi">@starts_at</span> <span class="o">+</span> <span class="vi">@durationlike</span>
<span class="vi">@number_of_days</span> <span class="o">=</span> <span class="no">Rational</span><span class="p">(</span><span class="vi">@duration</span><span class="p">.</span><span class="nf">to_i</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">.</span><span class="nf">to_i</span><span class="p">).</span><span class="nf">ceil</span>
<span class="vi">@starts_on</span> <span class="o">=</span> <span class="vi">@starts_at</span><span class="p">.</span><span class="nf">to_date</span>
<span class="vi">@ends_on</span> <span class="o">=</span> <span class="vi">@ends_at</span><span class="p">.</span><span class="nf">to_date</span>
<span class="vi">@trip_span_size</span> <span class="o">=</span> <span class="p">(</span><span class="vi">@ends_on</span> <span class="o">-</span> <span class="vi">@starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">time_spent_on</span><span class="p">(</span><span class="n">day</span><span class="p">)</span>
<span class="n">day</span> <span class="o">=</span> <span class="n">day</span><span class="p">.</span><span class="nf">in_time_zone</span><span class="p">(</span><span class="vi">@starts_at</span><span class="p">.</span><span class="nf">time_zone</span><span class="p">)</span>
<span class="n">from_time</span> <span class="o">=</span> <span class="p">[</span><span class="n">day</span><span class="p">.</span><span class="nf">beginning_of_day</span><span class="p">,</span> <span class="vi">@starts_at</span><span class="p">].</span><span class="nf">max</span>
<span class="n">to_time</span> <span class="o">=</span> <span class="p">[</span><span class="vi">@ends_at</span><span class="p">,</span> <span class="n">day</span><span class="p">.</span><span class="nf">end_of_day</span><span class="p">].</span><span class="nf">min</span>
<span class="n">to_time</span> <span class="o">-</span> <span class="n">from_time</span>
<span class="k">end</span>
<span class="c1"># Below are some shared helpers that could be reused everywhere.</span>
<span class="k">def</span> <span class="nf">random_datetime</span>
<span class="n">time_zone</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TimeZone</span><span class="o">::</span><span class="no">MAPPING</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">uniq</span><span class="p">.</span><span class="nf">sample</span>
<span class="n">datetime</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TimeZone</span><span class="p">[</span><span class="n">time_zone</span><span class="p">].</span><span class="nf">local</span><span class="p">(</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">2010</span><span class="o">..</span><span class="p">(</span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">year</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)),</span> <span class="c1"># year</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">12</span><span class="p">),</span> <span class="c1"># month</span>
<span class="mi">1</span><span class="p">,</span> <span class="c1"># day</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">23</span><span class="p">),</span> <span class="c1"># hour</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">30</span><span class="p">].</span><span class="nf">sample</span><span class="p">,</span> <span class="c1"># minute</span>
<span class="mi">0</span><span class="p">,</span> <span class="c1"># second</span>
<span class="p">)</span>
<span class="n">day_offset</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="o">...</span><span class="p">(</span><span class="n">datetime</span><span class="p">.</span><span class="nf">end_of_month</span><span class="p">.</span><span class="nf">day</span><span class="p">)).</span><span class="nf">to_a</span><span class="p">.</span><span class="nf">sample</span> <span class="c1"># randomize the day</span>
<span class="n">datetime</span> <span class="o">+</span> <span class="n">day_offset</span><span class="p">.</span><span class="nf">days</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">random_trip_duration</span>
<span class="nb">rand</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">second</span><span class="o">..</span><span class="mi">30</span><span class="p">.</span><span class="nf">days</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Here the <code class="language-plaintext highlighter-rouge">add_constraint</code> and <code class="language-plaintext highlighter-rouge">generate_context</code> are features that doesn’t exists yet. If you’re interested to work on implementing them, let me know!</p>
<p>Using that kind of approach leads to fewer examples, and to ones that are more meaningful. Now, <strong>the team needs to find properties that the subject under test should respect given a certain context</strong>.</p>
<p>The product and the developer must, <em>together</em>, come with both those contexts and properties. They force us to clarify our thinking. Here it means that we reformulate <em>relevant days</em> from the original specification. The context and properties forces us to extract the domain related concepts of <code class="language-plaintext highlighter-rouge">number_of_days</code>, <code class="language-plaintext highlighter-rouge">trip_span_size</code> and <code class="language-plaintext highlighter-rouge">time_spent_on</code> which could help to model the problem and maybe lead to a clearer solution.</p>
<p>Random generators can be shared across the application. Custom generators for any value of your domain must be available, very much like <a href="https://thepugautomatic.com/2012/07/randomize-your-factories/">factories would be</a>.</p>
<p>If it was that great, everyone would be doing it, right?</p>
<h3 id="caveats-and-workarounds">Caveats and workarounds</h3>
<h4 id="coding-the-logic-twice">Coding the logic twice</h4>
<p>In this appoach, we need to use elements from the context (such as <code class="language-plaintext highlighter-rouge">@starts_on</code>, <code class="language-plaintext highlighter-rouge">@ends_on</code>) to compute the expected results. What prevents me from making a mistake in both the expected value computation and the production code?</p>
<p>The use-cases approach is simpler to setup and less risky to write because it focuses on a single and fixed context. Even when the context isn’t fixed, we could use <em>constraints</em> on it in order to reduce the complexity of the expected result computation.</p>
<p>In the examples, the arithmetic on start and end dates are the same in term of complexity.</p>
<h4 id="too-much-generalization">Too much generalization</h4>
<p>The obvious difference between the two approaches is that the use-cases are really close to reality while the one using randomness forces us to come with well-structured rules and a more generalized approach. Driving the implementation from the use-cases may be more natural for TDD practitioners. The use-cases are needed in order to find relevant properties and contexts. Thus, <strong>use-cases are still mandatory</strong> in the process.</p>
<h4 id="not-bad-but-what-about-determinism">Not bad but… what about determinism</h4>
<p>Using randomness is something that many people are afraid of. They may feel that they are losing control, that their test suite is gonna start slowing them down. Here are two remarks that are deep enough to, maybe, make you reconsider:</p>
<ul>
<li>The tests are random as soon as impure functions are used such as <code class="language-plaintext highlighter-rouge">Date.current</code>.</li>
<li>The tests are random since they are randomized at programming-time by the developper <a href="https://www.youtube.com/watch?v=5pwv3cuo3Qk">*</a>.</li>
</ul>
<p>Those remarks implie that there are various classes of randomness. One is comming from impure functions either in the tests or in the production code. Those could lead to <a href="https://hackernoon.com/flaky-tests-a-war-that-never-ends-9aa32fdef359">flaky tests</a>.</p>
<p>Another one, introduced purposefully, which is here to help us to discover failures, to reveal inconsistencies in our thinking, and to detect unexpected behaviour as soon as possible.</p>
<h4 id="reproducing-failures">Reproducing failures</h4>
<p>Your tests will run on CI and will give you failures. Once spec fails, it isn’t obvious what the generated inputs were. Being able to understand and reproduce a failure is critical.</p>
<p>In the example, the context is lost upon failure. It is simple to get that context and it would give us a good hint as to what’s going on. Here is an example:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">must_equal</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">expect</span><span class="p">(</span><span class="n">subject</span><span class="p">).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">value</span><span class="p">),</span> <span class="o"><<~</span><span class="no">MSG</span>
<span class="sh"> Expected </span><span class="si">#{</span><span class="n">subject</span><span class="si">}</span><span class="sh"> to eq </span><span class="si">#{</span><span class="n">value</span><span class="si">}</span><span class="sh"> while using:</span>
<span class="sh"> - Starts at: </span><span class="si">#{</span><span class="vi">@starts_at</span><span class="si">}</span>
<span class="sh"> - Ends at: </span><span class="si">#{</span><span class="vi">@ends_at</span><span class="si">}</span>
<span class="no"> MSG</span>
<span class="k">end</span>
<span class="c1"># Replace this:</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span> <span class="p">(</span><span class="vi">@starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="p">(</span><span class="vi">@ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># With:</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">must_equal</span> <span class="p">(</span><span class="vi">@starts_on</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..</span><span class="p">(</span><span class="vi">@ends_on</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">}</span></code></pre></figure>
<p>I’m also experimenting with a custom pseudo-random generator that would use a different seed for each test and, in case of a failure, would display that specific seed to you. This experiment is a bit raw at the moment but lives in <a href="https://github.com/nicoolas25/fuzzier">Github’s nicoolas25/fuzzier repository</a>. It would look like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">random_datetime</span>
<span class="n">time_zone</span> <span class="o">=</span> <span class="no">Fuzzier</span><span class="p">.</span><span class="nf">sample</span><span class="p">(</span><span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TimeZone</span><span class="o">::</span><span class="no">MAPPING</span><span class="p">.</span><span class="nf">values</span><span class="p">.</span><span class="nf">uniq</span><span class="p">)</span>
<span class="n">datetime</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TimeZone</span><span class="p">[</span><span class="n">time_zone</span><span class="p">].</span><span class="nf">local</span><span class="p">(</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">2010</span><span class="o">..</span><span class="mi">2020</span><span class="p">),</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">12</span><span class="p">),</span>
<span class="mi">1</span><span class="p">,</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">23</span><span class="p">),</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">59</span><span class="p">),</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">59</span><span class="p">),</span>
<span class="p">)</span>
<span class="n">day_offset</span> <span class="o">=</span> <span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">...</span><span class="p">(</span><span class="n">datetime</span><span class="p">.</span><span class="nf">end_of_month</span><span class="p">.</span><span class="nf">day</span><span class="p">))</span>
<span class="n">datetime</span> <span class="o">+</span> <span class="n">day_offset</span><span class="p">.</span><span class="nf">days</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">random_trip_duration</span>
<span class="no">Fuzzier</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">second</span><span class="o">..</span><span class="mi">30</span><span class="p">.</span><span class="nf">days</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>When an error occurs, it will output an integer, lets say <code class="language-plaintext highlighter-rouge">12345</code> that can be used to reproduce the same randomness:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">it</span> <span class="s2">"has as many days as the number of days of the trip"</span><span class="p">,</span> <span class="ss">fuzzier: </span><span class="mi">12345</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span></code></pre></figure>
<p>The <a href="https://github.com/stympy/faker">faker gem</a> provides something similar with <code class="language-plaintext highlighter-rouge">Faker::Config.random.rand</code>.</p>
<h4 id="using-only-one-generation">Using only one generation</h4>
<p>This approach is very similar to <a href="https://hypothesis.works/articles/what-is-property-based-testing/">property-based-testing</a>. The difference is mostly that we don’t try many input sets on those examples; only one. But because tests run quite often, we end with way more use-cases over time. Solutions like <a href="https://github.com/rantly-rb/rantly">Rantly</a> fully embrace property-based testing and provide more tools including the ability to run a test against many input generations.</p>
<p>Because I see this approach more like an <em>exploration tool</em>, we could try to run a given test many times to be more confident that nothing could go wrong. It would look like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">1_000</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"has as many days as the number of days of the trip"</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Doing that exploration may show you some use-cases you missed and give you more confidence that the properties you specified truly match the requirements.</p>
<h3 id="when-to-use-it">When to use it</h3>
<p>I think using this kind of approach has multiple benefits:</p>
<ul>
<li>Conciseness & expressiveness of the specifications, as we don’t test samples but we specify the expected behavior using the language of the problem.</li>
<li>Adaptive and dynamic examples over the life of the test suite, as the test will run against new domain values as they are introduced in the application over time.</li>
<li>Better maintainability, as we can reason about properties rather than a long list of examples.</li>
</ul>
<p>I wouldn’t recommend this approach for integration testing where the goal is rather to secure well-known paths rather than explore all the possible cases. Also, I think about UI tests as a place I wouldn’t like randomness. You may want to compare screenshots of your application and that would be harder if the content was changing.</p>
<p>But, for components where we need its behavior to be fully described, I would consider this approach. I would consider it <strong>in addition to the usual use-cases</strong> for some edge cases. It forces me to think more about the problem and to have deeper discussions with the business. It can also point me to cases I didn’t think of.</p>
<p>As I said before, this technique can be a bit controversial and I invite you to talk about this with your team and share your opinion!</p>
<p>Writing tests is becoming a big part of our job. If it isn’t yet, I strongly encourage you to push your organization down that path. <em>Why</em> could be the topic of another article.</p>
Wed, 15 May 2019 00:00:00 +0000
https://getaround.tech/non-deterministic-testing/
https://getaround.tech/non-deterministic-testing/Your JavaScript can reveal your secretsAdrien Siami<p>Security is hard. It’s often very easy to overlook things, and one small mistake can have a very big impact.</p>
<p>When writing JavaScript, it’s easy to forget that you’re writing code that will be sent in plain text to your users.</p>
<p>Recently I have been doing a bit of offensive security, with a special interest on JavaScript files, to see what kind of information could be retrieved from them.</p>
<p>Here’s what I’ve learned.</p>
<h1 id="business-logic-and-other-business-leaks">Business logic and other business leaks</h1>
<p>It’s not uncommon to see some business logic in JavaScript files, especially for frontend-heavy websites.</p>
<p>While this is not a direct security problem, it can tell a great deal about your internals.</p>
<p>It could be a secret pricing function, a list of states that reveal an upcoming feature, or an array of translation strings that uncover some internal tools.</p>
<p>You wouldn’t want your secret algorithms exposed to the face of the world, would you?</p>
<h1 id="internal-api-paths">Internal API paths</h1>
<p>Another interesting find in JavaScript files is API paths.</p>
<p>Frontend-heavy applications need to make calls to an internal API, and often the list of API endpoints is conveniently stored in an Object in one of the JavaScript files.</p>
<p>This makes the work of security searchers very easy as they have access to all endpoints at once. Some endpoints are maybe deprecated but are still showing in the list: this is more attack surface for a security searcher.</p>
<h1 id="access-tokens">Access tokens</h1>
<p>This one is really bad, but is really not that uncommon.</p>
<p>In JavaScript files, I’ve found the following:</p>
<ul>
<li>AWS S3 id and secret key giving anyone full control over a S3 bucket</li>
<li>Cloudinary credentials giving anyone full control over the bucket</li>
<li>A CircleCI token, allowing me to launch builds, view commit history, and more</li>
<li>Various other third party API keys</li>
</ul>
<p>Those are often found in the admin / internal JS files. Developers may think these files won’t be served to regular users so it’s fine to put sensitive information inside, but more often that not, it’s easy to get access to those files.</p>
<h1 id="getting-to-the-interesting-files">Getting to the interesting files</h1>
<p>The interesting files are often the ones not intended for regular users: it can be an admin part, some internal tools, etc.</p>
<p>Every website has a different JS architecture. Some will load all the JS in every page, some more modern will have different entry points depending on the page you are visiting.</p>
<p>Let’s consider the following:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/front.js"</span><span class="nt">></script></span></code></pre></figure>
<p>It’s very trivial, but in this case, one could try to load <code class="language-plaintext highlighter-rouge">back.js</code>, or <code class="language-plaintext highlighter-rouge">admin.js</code>.</p>
<p>Let’s consider another example:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><script </span><span class="na">src=</span><span class="s">"/static/compiled/homepage.d1239afab9972f0dbeef.js"</span><span class="nt">></script></span></code></pre></figure>
<p>Now this is a bit more complicated, the file has a hash in its name so it’s impossible to do some basic enumeration.</p>
<p>What if we try to access this url: <code class="language-plaintext highlighter-rouge">https://website/static/compiled/manifest.json</code>?</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"assets"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"admin.js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin.a8240714830bbf66efb4.js"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"homepage.js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"homepage.d1239afab9972f0dbeef.js"</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nl">"publicPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/static/compiled/"</span>
<span class="p">}</span></code></pre></figure>
<p>Ooops! In this case this website is using <a href="https://webpack.js.org/">webpack</a>, a famous assets bundler. It is often used with a plugin that generates a <code class="language-plaintext highlighter-rouge">manifest.json</code> file containing the link to all assets, which is often served by the web server.</p>
<p>If you manage to find which tools a website is using, it’s easier to find this kind of vulnerabilities.</p>
<h1 id="how-to-protect-yourself">How to protect yourself</h1>
<p>Here are a few tips to avoid being vulnerable to this kind of attacks:</p>
<ul>
<li>Consider your JavaScript code public, all of it</li>
<li>If you really need access tokens in the front-end, get them via (secure & authenticated) API</li>
<li>Know your front-end toolbelt well to avoid basic attacks (manifest.json example)</li>
<li>Regularly audit your front-end code and look for specific keywords:
<ul>
<li><code class="language-plaintext highlighter-rouge">secret</code></li>
<li><code class="language-plaintext highlighter-rouge">token</code>, <code class="language-plaintext highlighter-rouge">accessToken</code>, <code class="language-plaintext highlighter-rouge">access_token</code>, etc</li>
<li>your domain name, for possible API urls</li>
<li>your company name, for possible 3rd party credentials</li>
</ul>
</li>
</ul>
<h1 id="conclusion">Conclusion</h1>
<p>Security issues can come from a lot of unexpected spots. When writing any kind of code, when pasting sensible data, it’s always good to ask yourself who will have access to this code, to avoid leaking all your secrets!</p>
<p>Security is hard. It’s often very easy to overlook things, and one small mistake can have a very big impact.</p>
Thu, 02 May 2019 00:00:00 +0000
https://getaround.tech/javascript-security/
https://getaround.tech/javascript-security/Sorbet: A Ruby type checkerAntoine Lyset<p>This article is aimed at beginner Rubyists who want to understand what the fuss around type checking is all about. It can also be relevant for more experienced developers who might be interested in using Sorbet and learning why it’s a bit special.</p>
<p>First I need to say that Sorbet has not been released yet (a preview version is <a href="https://sorbet.run/" target="_blank" rel="noopener">available</a>). Stripe is improving it internally and some other companies are testing it. We can still talk about it because it should be open-sourced in the coming future (they said summer 2019) and it’s nonetheless very interesting. This blogpost is the result of watching talks, and reading articles, Twitter feeds and the <a href="https://sorbet.org/" target="_blank" rel="noopener">official website</a>. It may contain some small mistakes and some parts may be obsolete when Sorbet will be released.</p>
<h2 id="whats-a-type-checker-and-what-do-i-need-to-know">What’s a type checker and what do I need to know?</h2>
<p>To understand Sorbet we first need to understand what a type is. A type is a definition applied to a part of our program (this part can be a variable or a function for example). This definition usually says something like “this variable is a <code class="language-plaintext highlighter-rouge">String</code>” or “this function returns an <code class="language-plaintext highlighter-rouge">Integer</code>”. A type checker will enforce these definitions by raising an expection if it finds an incoherence. An incoherence can be something like “this variable is of type <code class="language-plaintext highlighter-rouge">String</code> and you try to call the method <code class="language-plaintext highlighter-rouge">#map</code> on it but this method does not exist on type <code class="language-plaintext highlighter-rouge">String</code> so this is incoherent”, and then it will raise an exception. This exception can be raised at runtime when the program is launched (this is called dynamic typing) or just by analysing the source code without executing it (this is called static typing). The tool that will enforce these types is called a type checker.</p>
<p>There are a lot of different type checkers and it’s a large research field. We don’t need to understand Type Theory (one of the mathematical theories used by type checkers) to enjoy their use. I will just focus on Sorbet and describe what you can do with it.</p>
<h2 id="gradual-type-checking-with-runtime-checks">Gradual type checking with runtime checks</h2>
<p>Sorbet is both a static and a dynamic type checker. It will catch wrong definitions as early as possible by analysing the source code (you should run it in your editor and/or before releasing your code). This is particularly useful because Sorbet is fast, it can analyze 100kloc/sec (Rubocop is around 1kloc/s for comparison), so it will find bugs instantly before you even launch your tests.</p>
<p>The more interesting and specific side of Sorbet is that it will run side by side with your Ruby code, verifying types at runtime. Sorbet’s creator decided to implement this because Ruby is a very dynamic language and a lot of Rubyists write code that will generate code.<sup><a href="#fn-metaprogramming-and-ruby" id="metaprogramming-and-ruby">1</a></sup> Plus, Sorbet is a gradual type checker.</p>
<p>A gradual type checker is a special kind of type checker because you don’t need to add type annotations to all your code to use it. You can start small, just use it in some parts of your code then extend its usage gradually when you feel the need. Actually Stripe even added a tool to Sorbet to find which parts of your code you should type check to have the most impact. You may think that these runtime checks are costly, but it does not seem like it <sup><a href="#fn-runtime-overhead" id="runtime-overhead">2</a></sup>, and you can be sure that since Stripe is using it in production, performance problems are taken very seriously.</p>
<h2 id="how-to-use-it">How to use it</h2>
<p>First some typing.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># typed: true</span>
<span class="kp">extend</span> <span class="no">T</span><span class="o">::</span><span class="no">Sig</span>
<span class="n">sig</span> <span class="k">do</span>
<span class="n">params</span><span class="p">(</span><span class="ss">time: </span><span class="no">Time</span><span class="p">)</span>
<span class="p">.</span><span class="nf">returns</span><span class="p">(</span><span class="no">String</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">format_time</span><span class="p">(</span><span class="n">time</span><span class="p">)</span>
<span class="n">label</span> <span class="o">=</span> <span class="s2">"Time is : "</span>
<span class="n">formatted_time</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s2">"%M:%H"</span><span class="p">)</span>
<span class="n">label</span> <span class="o">+</span> <span class="n">formatted_time</span>
<span class="k">end</span></code></pre></figure>
<p><a href="https://sorbet.run/#%23%20typed%3A%20true%0Aextend%20T%3A%3ASig%0A%0Asig%20do%0A%20%20params(time%3A%20Time)%0A%20%20%20%20.returns(String)%0Aend%0Adef%20format_time(time)%0A%20%20label%20%3D%20%22Time%20is%20%3A%20%22%0A%20%20formatted_time%20%3D%20time.strftime(%22%25M%3A%25H%22)%0A%20%20label%20%2B%20formatted_time%0Aend" target="_blank" rel="noopener">Runnable Link</a></p>
<p>As you can see, Sorbet is just plain Ruby. First you add a <code class="language-plaintext highlighter-rouge"># typed: true</code> comment to instruct Sorbet that it’s a typed file (there are other values than <code class="language-plaintext highlighter-rouge">true</code> for different levels of strictness). Then you <code class="language-plaintext highlighter-rouge">extend</code> the object where you want to use it. Finally, you can call <code class="language-plaintext highlighter-rouge">sig</code> (short for signature) to define which types are your params and what the type of your returned value would be. This signature is applied to the definition of the next method.</p>
<p><code class="language-plaintext highlighter-rouge">sig</code> takes a block as a parameter and in this block you use <code class="language-plaintext highlighter-rouge">params</code> that you chain with <code class="language-plaintext highlighter-rouge">returns</code>. These <code class="language-plaintext highlighter-rouge">params</code> and <code class="language-plaintext highlighter-rouge">returns</code> methods are the core of Sorbet.</p>
<p>Here I defined the params of my method <code class="language-plaintext highlighter-rouge">format_time</code> to be a <code class="language-plaintext highlighter-rouge">Time</code> and the return type to be a <code class="language-plaintext highlighter-rouge">String</code>. As you can see I didn’t have to type <code class="language-plaintext highlighter-rouge">label</code> because Sorbet can infer types and this makes it way more practical and less verbose than some other type Systems.</p>
<h2 id="bye-nomethoderror">Bye “NoMethodError:”</h2>
<p>In the next bit of code we have an ActiveRecord-like Model with a <code class="language-plaintext highlighter-rouge">.find</code> and a <code class="language-plaintext highlighter-rouge">#plate_number</code>. This example simulates a common use-case where you query a record and ask for one of its attributes.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># typed: true</span>
<span class="k">class</span> <span class="nc">Car</span>
<span class="kp">extend</span> <span class="no">T</span><span class="o">::</span><span class="no">Sig</span>
<span class="n">sig</span> <span class="k">do</span>
<span class="n">params</span><span class="p">(</span><span class="ss">attributes: </span><span class="p">{</span><span class="ss">id: </span><span class="no">Integer</span><span class="p">,</span> <span class="ss">plate_number: </span><span class="no">String</span><span class="p">})</span>
<span class="p">.</span><span class="nf">void</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">attributes</span><span class="p">)</span>
<span class="vi">@attributes</span> <span class="o">=</span> <span class="n">attributes</span>
<span class="k">end</span>
<span class="n">sig</span> <span class="k">do</span>
<span class="n">params</span><span class="p">(</span><span class="ss">id: </span><span class="no">Integer</span><span class="p">)</span>
<span class="p">.</span><span class="nf">returns</span><span class="p">(</span><span class="no">T</span><span class="p">.</span><span class="nf">nilable</span><span class="p">(</span><span class="no">Car</span><span class="p">))</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
<span class="c1"># We are simulating some kind of Database Query</span>
<span class="k">if</span> <span class="nb">id</span> <span class="o">==</span> <span class="mi">1</span>
<span class="n">new</span><span class="p">({</span>
<span class="ss">id: </span><span class="mi">1</span><span class="p">,</span>
<span class="ss">plate_number: </span><span class="s2">"1234"</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">sig</span> <span class="p">{</span> <span class="n">returns</span><span class="p">(</span><span class="no">String</span><span class="p">)</span> <span class="p">}</span>
<span class="k">def</span> <span class="nf">plate_number</span>
<span class="vi">@attributes</span><span class="p">[</span><span class="ss">:plate_number</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">car</span> <span class="o">=</span> <span class="no">Car</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">car</span><span class="p">.</span><span class="nf">plate_number</span></code></pre></figure>
<p>Result:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">editor.rb:35: Method plate_number does not exist on NilClass component of T.nilable<span class="o">(</span>Car<span class="o">)</span>
35 |car.plate_number
^^^^^^^^^^^^^^^^
Autocorrect: Use <span class="sb">`</span><span class="nt">-a</span><span class="sb">`</span> to autocorrect
editor.rb:35: Replace with T.must<span class="o">(</span>car<span class="o">)</span>
35 |car.plate_number
^^^
Errors: 1</code></pre></figure>
<p><a href="https://sorbet.run/#%23%20typed%3A%20true%0Aclass%20Car%0A%20%20extend%20T%3A%3ASig%0A%20%20%0A%20%20sig%20do%0A%20%20%20%20params(attributes%3A%20%7Bid%3A%20Integer%2C%20plate_number%3A%20String%7D)%0A%20%20%20%20%20%20.void%0A%20%20end%0A%20%20def%20initialize(attributes)%0A%20%20%20%20%40attributes%20%3D%20attributes%0A%20%20end%0A%20%20%0A%20%20sig%20do%0A%20%20%20%20params(id%3A%20Integer)%0A%20%20%20%20%20%20.returns(T.nilable(Car))%0A%20%20end%0A%20%20def%20self.find(id)%0A%20%20%20%20%23%20We%20are%20simulating%20some%20kind%20of%20Database%20Query%0A%20%20%20%20if%20id%20%3D%3D%201%0A%20%20%20%20%20%20new(%7B%0A%20%20%20%20%20%20%20%20id%3A%201%2C%0A%20%20%20%20%20%20%20%20plate_number%3A%20%221234%22%0A%20%20%20%20%20%20%7D)%0A%20%20%20%20end%0A%20%20end%0A%20%20%0A%20%20sig%20%7B%20returns(String)%20%7D%0A%20%20def%20plate_number%0A%20%20%20%20%40attributes%5B%3Aplate_number%5D%0A%20%20end%0Aend%0A%0A%0Acar%20%3D%20Car.find(1)%0Acar.plate_number" target="_blank" rel="noopener">Runnable Link</a></p>
<p>When we type check it with Sorbet , it warns us that we didn’t handle the case where we don’t find a Car. The message is pretty clear and it even recommends that we use a special method <code class="language-plaintext highlighter-rouge">T.must</code>. This will enforce at runtime that we always have a Car. This may not be what we want and we can handle the case ourselves by adding something like:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">car</span> <span class="o">=</span> <span class="no">Car</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">car</span>
<span class="n">car</span><span class="p">.</span><span class="nf">plate_number</span>
<span class="k">else</span>
<span class="s2">"A plate number's placeholder"</span>
<span class="k">end</span></code></pre></figure>
<p><a href="https://sorbet.run/#%23%20typed%3A%20true%0Aclass%20Car%0A%20%20extend%20T%3A%3ASig%0A%20%20%0A%20%20sig%20do%0A%20%20%20%20params(attributes%3A%20%7Bid%3A%20Integer%2C%20plate_number%3A%20String%7D)%0A%20%20%20%20%20%20.void%0A%20%20end%0A%20%20def%20initialize(attributes)%0A%20%20%20%20%40attributes%20%3D%20attributes%0A%20%20end%0A%20%20%0A%20%20sig%20do%0A%20%20%20%20params(id%3A%20Integer)%0A%20%20%20%20%20%20.returns(T.nilable(Car))%0A%20%20end%0A%20%20def%20self.find(id)%0A%20%20%20%20%23%20We%20are%20simulating%20some%20kind%20of%20Database%20Query%0A%20%20%20%20if%20id%20%3D%3D%201%0A%20%20%20%20%20%20new(%7B%0A%20%20%20%20%20%20%20%20id%3A%201%2C%0A%20%20%20%20%20%20%20%20plate_number%3A%20%221234%22%0A%20%20%20%20%20%20%7D)%0A%20%20%20%20end%0A%20%20end%0A%20%20%0A%20%20sig%20%7B%20returns(String)%20%7D%0A%20%20def%20plate_number%0A%20%20%20%20%40attributes%5B%3Aplate_number%5D%0A%20%20end%0Aend%0A%0A%0Acar%20%3D%20Car.find(1)%0Aif%20car%0A%20%20car.plate_number%0Aelse%0A%20%20%22A%20plate%20number's%20placeholder%22%0Aend" target="_blank" rel="noopener">Runnable Link</a></p>
<p>And now Sorbet is happy. It understands the <code class="language-plaintext highlighter-rouge">if ... else</code> and there is no more risk of errors.</p>
<h2 id="more-than-a-type-checker">More than a type checker</h2>
<figure>
<img alt="Code autocomplete thanks to Sorbet" src="/assets/posts/2019-04-05-sorbet-a-ruby-type-checker/sorbet-autocomplete.png" />
<figcaption>
Code autocomplete thanks to Sorbet
</figcaption>
</figure>
<p>Sorbet is not only a type checker, it’s a tool suite around types. For example there is a <a href="https://microsoft.github.io/language-server-protocol/" target="_blank" rel="noopener">LSP</a> server, it enables developers to easily implement code autocomplete, go to definition and all kinds of nice things for different editors (Visual Studio Code, Atom, Sublime Text, Vim, Emacs…). So if you’re using Sorbet in your code and in your editor you will have a source of documentation already available that is always true.</p>
<h2 id="a-lot-more-to-learn-and-to-come">A lot more to learn and to come</h2>
<p>These are pretty basic examples, but it can go further with <a href="https://sorbet.run/#%23%20typed%3A%20true%0Aclass%20Box%0A%20%20extend%20T%3A%3ASig%0A%20%20extend%20T%3A%3AGeneric%0A%0A%20%20Elem%20%3D%20type_member%0A%0A%20%20sig%20%7Breturns(Elem)%7D%0A%20%20attr_reader%20%3Ax%0A%0A%20%20sig%20%7Bparams(x%3A%20Elem).returns(Elem)%7D%0A%20%20attr_writer%20%3Ax%0Aend%0A%0ABox%5BInteger%5D.new.x%20%2B%20Box%5BString%5D.new.x" target="_blank" rel="noopener">Generics</a> or <a href="https://sorbet.run/#%23%20typed%3A%20true%0Amodule%20Fooable%0A%20%20extend%20T%3A%3ASig%0A%20%20abstract!%0A%20%20sig%20%7Babstract.void%7D%0A%20%20def%20foo%0A%20%20end%0Aend%0A%0Aclass%20GoodFooable%0A%20%20include%20Fooable%0A%0A%20%20def%20foo%0A%20%20end%0Aend%0A%0Aclass%20BadFooable%0A%20%20include%20Fooable%0Aend" target="_blank" rel="noopener">Interfaces</a>. It can even warn us of <a href="https://sorbet.run/#%23%20typed%3A%20true%0Aif%20true%0A%20%20%20%20foo%20%3D%201%0Aelse%0A%20%20%20%20foo%20%3D%202%0Aend" target="_blank" rel="noopener">dead code</a>.</p>
<p>I think Sorbet will really shine in large projects: it will reduce the fear of refactoring by providing instant feedbacks, it is a self-documenting method and it helps reuse someone else’s code. Sure it won’t remove testing but it can reduce some of it and will let us focus on what’s important (and not “What will happen if I put a <code class="language-plaintext highlighter-rouge">String</code> instead of an <code class="language-plaintext highlighter-rouge">Array</code> here?”).</p>
<p>The Ruby Community is very lucky to have such a big company investing so much effort in a type checker and willing to give it to the community (we are talking about more than 9 months of work by 3 very skilled people). If you want to know more, I really encourage you to check <a href="https://sorbet.org/" target="_blank" rel="noopener">https://sorbet.org/</a> and to watch this video from Ruby Kaigi :</p>
<iframe width="800" height="450" src="https://www.youtube.com/embed/eCnnBS2LXcI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h2 id="footnotes">Footnotes</h2>
<p><a id="fn-metaprogramming-and-ruby">[1]</a>: To handle some common Ruby metaprogramming techniques (code that generate code), Sorbet is able to “unroll” Ruby code, creating the metaprogrammed methods and type checking them. <a href="#metaprogramming-and-ruby" title="Jump back">↩</a></p>
<p><a id="fn-runtime-overhead">[2]</a>: <a href="#runtime-overhead" title="Jump back">↩</a></p>
<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="en" dir="ltr">1) we run it in production;<br />2) <a href="https://twitter.com/nelhage?ref_src=twsrc%5Etfw">@nelhage</a> measured overhead and the worst case(for method that does nothing) IIRC was under 5%;<br />3) `sig` supports one more builder method: `.checked(false)` to disable runtime checking;<br />4) runtime type system erases generics.</p>— Dmitry Petrashko (@darkdimius) <a href="https://twitter.com/darkdimius/status/1003629455225511936?ref_src=twsrc%5Etfw">June 4, 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>This article is aimed at beginner Rubyists who want to understand what the fuss around type checking is all about. It can also be relevant for more experienced developers who might be interested in using Sorbet and learning why it’s a bit special.</p>
Fri, 19 Apr 2019 00:00:00 +0000
https://getaround.tech/sorbet-a-ruby-type-checker/
https://getaround.tech/sorbet-a-ruby-type-checker/From translator to developerJordan Jalabert & Emily Fiennes<p>After working as a teacher and translator for several years, Emily embarked on a new phase in her career by learning a different kind of language: programming.</p>
<p>Emily has worked at Drivy for the past year and a half as a Full-Stack Engineer, after attending the intensive Le Wagon bootcamp in Paris. Here, she shares how she began coding and what life is like for a developer at Drivy.</p>
<p>If you’re considering a career change to become a software engineer, hopefully Emily’s story will inspire you to go for it.</p>
<h3 id="what-were-you-doing-before-you-became-a-software-engineer">What were you doing before you became a software engineer?</h3>
<p>I studied modern languages and translation, and after university worked as a teacher and translator. My freelance translation work led me to work with some startups based in Bordeaux. I was intrigued by what the developers were doing and they pointed me in the direction of sites like <a href="https://www.pluralsight.com/learn" target="_blank">Pluralsight</a>, <a href="https://www.codecademy.com/" target="_blank">CodeCademy</a> and <a href="https://ruby.github.io/TryRuby/" target="_blank">TryRuby</a>. I was pretty certain that I was going to be completely useless - I hadn’t studied maths or science since the age of 15! But initially, it was like learning another language.</p>
<p>Then I heard about <a href="https://www.lewagon.com" target="_blank">Le Wagon</a>, a 9-week intensive bootcamp to learn to code. After Le Wagon I was determined to find an internship because I knew that I had still had lots to learn. I did a 6-month unofficial internship with another startup, and then, after reading Jean’s <a href="https://drivy.engineering/story-of-a-junior-developer-at-drivy/" target="_blank">Story of a Junior Developer</a>, applied to Drivy.</p>
<h3 id="describe-what-you-do-as-a-full-stack-engineer-whats-a-typical-day-like">Describe what you do as a Full-Stack Engineer. What’s a typical day like?</h3>
<p>The tech-product team is organised into squads, and I’m in the squad that builds features for owners. Our features focus on acquiring and onboarding new owners, improving fleet quality, and providing tooling for owners. I’m a Full-Stack engineer so often I’ll work on a feature from start to finish, implementing both the client- and server-side code.</p>
<p>Starting out, I was apprehensive about being stuck behind a computer all day but in fact that’s rarely the case… there’s a lot of variety in my days at Drivy. True, some days I might spend the whole day writing code. But there’s always room to ask for help, or to discuss choices and strategies with colleagues.</p>
<p>Other days are a mixture of meetings with product managers to help define feature specs; with members of the design team to discuss implementation of design specs; with my squad to define the roadmap for the coming quarter; or simply with the developers in my squad to elaborate on the technical specs for a feature.</p>
<p>Sometimes I do pair-programming with a more senior developer, which is always an opportunity to learn new tricks. We might refactor something I’ve been working on, build specific skills like writing tests or reviewing pull requests, or doing coding exercises.</p>
<p>Half of my team works remotely, so communication is either be face-to-face or via Slack and video calls. In short, there’s no ‘typical’ day - the only thing that is typical is that I’m constantly learning, and that’s really stimulating.</p>
<h3 id="what-do-you-enjoy-the-most-about-your-job-at-drivy">What do you enjoy the most about your job at Drivy?</h3>
<p>There are so many things I enjoy about my job: I genuinely go home on a Friday excited to come back on Monday. I enjoy the balance between human interaction, and losing myself in the feature I’m working on. I enjoy solving problems, big and small, and having something to show for my work at the end of the day.</p>
<p>The pleasure I take from coding is similar to the one I take from translation. I find abstractions to map on to real-world problems and create a vocabulary that represents these abstractions in the digital world, respecting coherence within the codebase and the syntax and grammar of Ruby<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. I have to think about how the code I write might be interpreted by future developers, and resolve the problem in such a way that leaves future options open. I was surprised to learn that writing code is very creative work.</p>
<p>Working at Drivy is very rewarding because we are encouraged to take responsibility for our work. When I work on a feature, I’m not just integrating technical specs that a senior developer has written for me: I’m encouraged to think about the end needs of the user and the unexpected edge cases that might arise, and challenge the product specs if necessary.</p>
<p>I’m surrounded by very talented and experienced people: it really motivates every day to think that one day I might be able to write code that is as sophisticated (which often means beautifully simple!) as they do. That said, everyone is very humble: sometimes I’ll ask a question or challenge something for being too complex, simply because my repertoire of methods - my ‘vocabulary’ - is less broad. I’m never made to feel that my opinion is less valid because I’m junior, and sometimes my suggestion even gets accepted!</p>
<h3 id="technology-is-often-described-as-a-male-dominated-field-do-you-feel-that-women-have-the-same-opportunities-as-men-in-tech">Technology is often described as a male-dominated field. Do you feel that women have the same opportunities as men in tech?</h3>
<p>I can’t speak for all women, and I’m not sure binary male-female is helpful in a bid to be more inclusive. The question of equal opportunities for men and women is huge and complex. Suffice to say: the men vastly outnumber the women in the tech team, and that speaks volumes about the inequality of opportunities.</p>
<p>But for me the problem came much earlier. I can remember being told aged 13 that I was good at HTML - but I was never told that it would be a career option for me. I was also good at languages, and instead it was suggested I would make a good teacher! I’m sure that those messages, received at such a formative age, contributed to the self-doubt I experienced (and continue to experience!) in my work.</p>
<h3 id="how-many-women-are-there-in-the-engineering-team">How many women are there in the Engineering Team?</h3>
<p>Too few - I’m the only female developer. There are two women in the data team, one of whom is the Head of Data.</p>
<h3 id="how-do-you-keep-yourself-informed-about-the-latest-trends">How do you keep yourself informed about the latest trends?</h3>
<p>I’ve attended a couple of meetups and conferences since I’ve been in Paris. In all honesty, I haven’t attended as many as I would have liked to. This is for two reasons. Firstly, I love my work but by the end of the day I’m exhausted - the learning curve is still steep. But it’s ok that I have other passions and other things I like doing beyond coding - it took me a long time to accept that.</p>
<p>Secondly, even though everyone at the events I’ve been to has been nothing less than welcoming, I still suffer from a lot of self-doubt! It’s easy to worry that you won’t be taken seriously.</p>
<p>There are also a couple of blogs and newsletters I receive regularly and other resources I’ve come across online when trying to find a solution to a problem. And I read the Drivy engineering blog of course!</p>
<h3 id="what-advice-would-you-give-to-a-woman-considering-a-career-in-the-tech-industry-what-do-you-wish-you-had-known">What advice would you give to a woman considering a career in the tech industry? What do you wish you had known?</h3>
<p>I wish I had known that the insecurity and self-doubt will never leave me, but that everyone suffers from that. In fact, I think that can even help make you a good developer, because it means you are constantly asking questions and seeking to improve your knowledge. Being aware of the things you don’t know is really important in keeping up with the changes in tech and sharing knowledge with colleagues.</p>
<p>I worried a lot about my fundamental capacities because of not having a mathematical and scientific background and because of being dyspraxic. I wish I had taken confidence from the fact that my atypical background means I have a unique skill-set that I can bring to my work.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1">
<p>Videla, A., “Programming as translation”, February 2019, <a href="https://increment.com/internationalization/programming-as-translation/" target="_blank">https://increment.com/internationalization/programming-as-translation/</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
<p>After working as a teacher and translator for several years, Emily embarked on a new phase in her career by learning a different kind of language: programming.</p>
Tue, 19 Feb 2019 00:00:00 +0000
https://getaround.tech/from-translator-to-developer/
https://getaround.tech/from-translator-to-developer/Rails 6 unnoticed featuresAlexandre Ferraille<p>Rails 6.0.0.beta1 is out and you may have already tested it. We all have heard about the main features such as multi-database connectivity, Action Mailbox & Action Text merge, parallelized testing, Action Cable testing etc. But there’s also a ton of other cool features that I found interesting.</p>
<h2 id="requirements-change">Requirements change</h2>
<p>With each major release comes new requirements, starting with Ruby which is now required with a minimal version of 2.5.0 instead of 2.2.2 for Rails 5.2. Our databases also get an upgrade with 5.5.8 for MySQL, 9.3 for PostgreSQL and 3.8 for SQLite.</p>
<h2 id="webpacker-as-default">Webpacker as default</h2>
<p>Webpacker has been merged in Rails 5.1 and provides a modern asset pipeline with the integration of Webpack for your javascript files. Before Rails 6 you had to generate your app with the <code class="language-plaintext highlighter-rouge">--webpacker</code> option to use it, now Webpacker is the default and it’s a good first step for a modern asset pipeline on Rails in replacement of Sprockets - you currently still need it to load your CSS and images.</p>
<h2 id="environment-support-for-encrypted-credentials">Environment support for encrypted credentials</h2>
<p>Rails 5.1 introduced encrypted credentials: a file containing your passwords, API keys etc., which can be safely shared. All you need to do is to store safely the <code class="language-plaintext highlighter-rouge">ENV[’RAILS_MASTER_KEY’]</code>. This created a problem: when you wanted to have different credentials for your environments you were stuck with one shared file across all your environments. This is solved now: you can have a specific encrypted file per environment.</p>
<h2 id="dns-guard-hosts-whitelist">DNS Guard: hosts whitelist</h2>
<p>Rails 6 added a new middleware called <code class="language-plaintext highlighter-rouge">ActionDispatch::HostAuthorization</code> allowing you to whitelist some hosts for your application and preventing <a href="https://dzone.com/articles/what-is-a-host-header-attack">Host header attacks</a>. You can easily configure it with a <code class="language-plaintext highlighter-rouge">String</code>, <code class="language-plaintext highlighter-rouge">IPAddr</code>, <code class="language-plaintext highlighter-rouge">Proc</code> and <code class="language-plaintext highlighter-rouge">RegExp</code> (useful when dealing with wildcard domains).</p>
<h2 id="translations-and-_html">Translations and <code class="language-plaintext highlighter-rouge">_html</code></h2>
<p>If you’re using the <a href="/security-tips-for-rails-apps"><code class="language-plaintext highlighter-rouge">_html</code> suffix</a> a lot for your translation keys, you can refactor a group of keys on the same level by adding <code class="language-plaintext highlighter-rouge">_html</code> to the parent and removing it to the children.</p>
<h2 id="filtering-sensitive-parameters">Filtering sensitive parameters</h2>
<p>If you’re dealing with sensitive data you want to hide from logs, console etc. you can configure <code class="language-plaintext highlighter-rouge">ActiveRecord::Base::filter_attributes</code> with a list of <code class="language-plaintext highlighter-rouge">String</code> and <code class="language-plaintext highlighter-rouge">RegExp</code> which match sensitive attributes.</p>
<h2 id="time-comparisons">Time comparisons</h2>
<p><code class="language-plaintext highlighter-rouge">Date</code>, <code class="language-plaintext highlighter-rouge">DateTime</code> and <code class="language-plaintext highlighter-rouge">Time</code> received a bunch of methods allowing us to do comparisons without traditional operators - easier to read:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">rental_1</span><span class="p">.</span><span class="nf">starts_at</span><span class="p">.</span><span class="nf">after?</span> <span class="n">rental_2</span><span class="p">.</span><span class="nf">ends_at</span> <span class="c1"># => true</span>
<span class="n">rental_1</span><span class="p">.</span><span class="nf">starts_at</span><span class="p">.</span><span class="nf">before?</span> <span class="n">rental_2</span><span class="p">.</span><span class="nf">ends_at</span> <span class="c1"># => false</span></code></pre></figure>
<h2 id="private-delegate">Private <code class="language-plaintext highlighter-rouge">delegate</code></h2>
<p>You can now <code class="language-plaintext highlighter-rouge">delegate</code> a method without exposing it publicly with the new option <code class="language-plaintext highlighter-rouge">private</code>.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">delegate</span> <span class="ss">:full_name</span><span class="p">,</span> <span class="ss">to: :current_user</span><span class="p">,</span> <span class="ss">private: </span><span class="kp">true</span></code></pre></figure>
<h2 id="relationpick"><code class="language-plaintext highlighter-rouge">Relation#pick</code></h2>
<p>If you need to select a column from an <code class="language-plaintext highlighter-rouge">ActiveRecord::Relation</code> you can use <code class="language-plaintext highlighter-rouge">pluck</code> which will trigger the following query:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="nv">`articles`</span><span class="p">.</span><span class="nv">`title`</span> <span class="k">FROM</span> <span class="nv">`articles`</span></code></pre></figure>
<p>But if you want the first value you’ll need to add a <code class="language-plaintext highlighter-rouge">.limit(1)</code>, that’s what <code class="language-plaintext highlighter-rouge">pick</code> is doing for you:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="nv">`articles`</span><span class="p">.</span><span class="nv">`title`</span> <span class="k">FROM</span> <span class="nv">`articles`</span> <span class="k">LIMIT</span> <span class="mi">1</span></code></pre></figure>
<h2 id="deprecate-update_attributes--update_attributes">Deprecate <code class="language-plaintext highlighter-rouge">update_attributes!</code> & <code class="language-plaintext highlighter-rouge">update_attributes</code></h2>
<p>Should we prefer <code class="language-plaintext highlighter-rouge">update_attributes</code> or <code class="language-plaintext highlighter-rouge">update</code>? <code class="language-plaintext highlighter-rouge">update_attributes!</code> or <code class="language-plaintext highlighter-rouge">update!</code>? These methods have always been confusing but were nothing more than aliases. No more confusions and consistency issues!</p>
<h2 id="utf8mb4-as-default-for-mysql">Utf8mb4 as default for MySQL</h2>
<p>Users are putting emojis 😀 everywhere, I’m 💯% sure you already got the issue when trying to insert them in your database. Setting Utf8mb4 as default instead of Utf8 solves the problem. It also helps if you need to handle Asian characters, mathematical characters etc.
Note that you still need to migrate your old tables manually.</p>
<h2 id="change-system-database">Change system database</h2>
<p>Changing database from default SQLite to PostgreSQL (for example) is something that you might need to do at an early stage of your project, and could be painful if you don’t have the proper config files and templates in your Rails app. Now, you no longer need to generate a new Rails app with the proper database to grab the files you need, <code class="language-plaintext highlighter-rouge">rails db:system:change</code> is here.</p>
<h2 id="create_table-if_not_exist-option-for-migrations"><code class="language-plaintext highlighter-rouge">create_table</code>: <code class="language-plaintext highlighter-rouge">:if_not_exist</code> option for migrations</h2>
<p>When running, rollbacking and updating migrations it can be a mess and sometimes you need to manually clean your database in order to run a <code class="language-plaintext highlighter-rouge">create_table</code> which already physically exists. Now, you can bypass a <code class="language-plaintext highlighter-rouge">create_table</code> block in this case using <code class="language-plaintext highlighter-rouge">:if_not_exist</code> option.</p>
<h2 id="navigating-in-the-session-object">Navigating in the session object</h2>
<p>Using and navigating in the session object can be painful if the keys/sub-keys you’re looking for are not defined. Fortunately, Rails 6 is adding a Hash-like method called <code class="language-plaintext highlighter-rouge">dig</code>, allowing you to safely navigate in your session object.</p>
<h2 id="enumerableindex_with"><code class="language-plaintext highlighter-rouge">Enumerable#index_with</code></h2>
<p>New method allowing you to create a Hash from an Enumerable:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sx">%w(driver owner drivy)</span><span class="p">.</span><span class="nf">index_with</span><span class="p">(</span><span class="kp">nil</span><span class="p">)</span>
<span class="c1"># => { 'driver' => nil, 'owner' => nil, 'drivy' => nil }</span>
<span class="sx">%w(driver owner drivy)</span><span class="p">.</span><span class="nf">index_with</span> <span class="p">{</span> <span class="o">|</span><span class="n">role</span><span class="o">|</span> <span class="n">delta_amount_for</span><span class="p">(</span><span class="n">role</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># => { 'driver' => '...', 'owner' => '...', 'drivy' => '...' }</span></code></pre></figure>
<h2 id="arrayextract"><code class="language-plaintext highlighter-rouge">Array#extract!</code></h2>
<p>This is a new method added to <code class="language-plaintext highlighter-rouge">Array</code> which works like <code class="language-plaintext highlighter-rouge">reject!</code> but instead of returning an array of the non-rejected values, you get the values which returns true for the block.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">my_array</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">]</span>
<span class="n">my_array</span><span class="p">.</span><span class="nf">extract!</span> <span class="p">{</span> <span class="o">|</span><span class="n">v</span><span class="o">|</span> <span class="n">v</span> <span class="o">></span> <span class="mi">2</span> <span class="p">}</span> <span class="c1"># => [3,4]</span>
<span class="n">my_array</span> <span class="o">==</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># => true</span></code></pre></figure>
<h2 id="starting-your-app">Starting your app</h2>
<p>You get an error when starting your rails server? The <code class="language-plaintext highlighter-rouge">rails server</code> command no longer passes the server name as a Rack argument but is using <code class="language-plaintext highlighter-rouge">-u</code> option. You will start you server this way: <code class="language-plaintext highlighter-rouge">rails server -u puma</code>.</p>
<h2 id="of-course-theres-more">Of course, there’s more</h2>
<p>This is a non-exhaustive list of things I found fun and/or useful and I encourage you to <a href="https://github.com/rails/rails/releases/tag/v6.0.0.beta1">read the full changelog</a>. A lot of deprecation has been added, previously deprecated methods have been dropped and there’s much more that you might find useful.</p>
<p>Rails 6.0.0.beta1 is out and you may have already tested it. We all have heard about the main features such as multi-database connectivity, Action Mailbox & Action Text merge, parallelized testing, Action Cable testing etc. But there’s also a ton of other cool features that I found interesting.</p>
Fri, 15 Feb 2019 00:00:00 +0000
https://getaround.tech/rails-6-unnoticed-features/
https://getaround.tech/rails-6-unnoticed-features/Handle disabled mobile data setting on iOSThomas Senkman<p>For an unknown reason, a significant number of users <strong>disable the mobile data for our iOS app</strong>. This is not a problem when they are booking a car from their couch at home with Wi-Fi, but can quickly become a major issue when they try to unlock their Drivy Open car in the street. At this specific moment, there is very little chance that they remember they disabled this setting, so it very often leads to a call to Customer Services that could have been avoided.</p>
<figure>
<img alt="Drivy's settings with mobile data switched off" src="/assets/posts/2019-02-13-handle-disabled-mobile-data-setting-on-ios/settings.jpeg" />
<figcaption>
Drivy's settings with mobile data switched off
</figcaption>
</figure>
<p>If we do nothing about this, the users’s resquests would always fail when not on Wi-Fi, and they would see a default error. In our case, they would see the message “An error has occurred”, which doesn’t help them to understand what’s wrong. However, <strong>it’s our job to let the users know what they can do to fix the issue.</strong></p>
<h2 id="ios-native-implementation">iOS native implementation</h2>
<p>To try to solve this, according to our tests, iOS shows an alert when <strong>all these conditions are met</strong>:</p>
<ul>
<li>network is <strong>not reachable</strong></li>
<li>mobile data setting <strong>is disabled</strong> for the current app</li>
<li>mobile data setting <strong>value has changed since last app opening</strong></li>
</ul>
<figure>
<img alt="iOS native alert" src="/assets/posts/2019-02-13-handle-disabled-mobile-data-setting-on-ios/popup.png" />
<figcaption>
iOS native alert
</figcaption>
</figure>
<p>Its UX is nice, because it <strong>redirect the user to the correct screen to update the setting with a single tap</strong>. But this doesn’t cover all the cases: if a first Wi-Fi call is successful, even with the mobile data setting disabled, the user will never see the alert.</p>
<p>Because we think it’s not sufficient, <strong>we decided to reimplement this alert for each network call that fails because of this specific setting</strong>.</p>
<h2 id="lets-code-this">Let’s code this</h2>
<h3 id="requirements">Requirements</h3>
<p>The only requirement is to have an <strong>iOS 9</strong> app target, as we will use <strong><code class="language-plaintext highlighter-rouge">CTCellularData</code></strong> which is only available from this version. To our knownledge there is unfortunately no way to check the network-data setting value before.</p>
<h3 id="used-tools">Used tools</h3>
<p>So since iOS 9, Apple provides in <a href="https://developer.apple.com/documentation/coretelephony/ctcellulardata"><code class="language-plaintext highlighter-rouge">CTCellularData</code></a> a listener to check the value of the mobile data switch for the current app.</p>
<p>We’ll also need to be able to check for <strong>reachability</strong> to know if we are in our specific error case. As we use Alamofire in our app, we used their <a href="https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability"><code class="language-plaintext highlighter-rouge">NetworkReachabilityManager</code></a>, but you can also use another solution like <a href="https://github.com/ashleymills/Reachability.swift">Reachability.swift</a> or even add Apple’s <a href="https://developer.apple.com/documentation/systemconfiguration/scnetworkreachability-g7d"><code class="language-plaintext highlighter-rouge">SCNetworkReachability</code></a> class to your app for this.</p>
<h3 id="getting-the-current-mobile-data-setting-value">Getting the current mobile data setting value</h3>
<p>Implementing the listener to get the current value is pretty straightforward:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">CoreTelephony</span>
<span class="c1">//...</span>
<span class="k">let</span> <span class="nv">cellularData</span> <span class="o">=</span> <span class="kt">CTCellularData</span><span class="p">()</span>
<span class="n">cellularData</span><span class="o">.</span><span class="n">cellularDataRestrictionDidUpdateNotifier</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="k">in</span>
<span class="nf">print</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<h3 id="getting-the-reachability-status">Getting the reachability status</h3>
<p>Same logic here, we just have to explicitly start the listener after setting it:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">Alamofire</span>
<span class="c1">// ...</span>
<span class="k">let</span> <span class="nv">networkReachabilityManager</span> <span class="o">=</span> <span class="kt">NetworkReachabilityManager</span><span class="p">()</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="n">listener</span> <span class="o">=</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">state</span> <span class="k">in</span>
<span class="nf">print</span><span class="p">(</span><span class="n">state</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="nf">startListening</span><span class="p">()</span></code></pre></figure>
<h3 id="data-availability">Data availability</h3>
<p>Checking reachability can be done at any moment, but is not instant. So if you init the <strong><code class="language-plaintext highlighter-rouge">NetworkReachabilityManager</code></strong> and directly try to get the current status, this will probably fail. To avoid this, and because this does not consume a great deal of memory, we can have our own manager that stores the current value whenever it changes:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">Alamofire</span>
<span class="kd">class</span> <span class="kt">ApiReachabilityManager</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentNetworkReachabilityState</span><span class="p">:</span> <span class="kt">NetworkReachabilityManager</span><span class="o">.</span><span class="kt">NetworkReachabilityStatus</span> <span class="o">=</span> <span class="o">.</span><span class="n">unknown</span>
<span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">networkReachabilityManager</span> <span class="o">=</span> <span class="kt">NetworkReachabilityManager</span><span class="p">()</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="n">listener</span> <span class="o">=</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">state</span> <span class="k">in</span>
<span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">currentNetworkReachabilityState</span> <span class="o">=</span> <span class="n">state</span>
<span class="p">}</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="nf">startListening</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="when-to-make-the-checks">When to make the checks</h2>
<p>We strongly advise to <strong>check both statuses</strong>, especially reachability, <strong>only when network errors happen</strong>. You should never check for reachability before making a network call to potentially avoid making it.
If there is an issue with the reachability, this would result in blocking all the network calls of your app. This is even the first “important thing” Alamofire says in their documentation:</p>
<blockquote>
<p>Do NOT use Reachability to determine if a network request should be sent.
You should ALWAYS send it.</p>
</blockquote>
<h2 id="final-implementation">Final implementation</h2>
<p>Here is our singleton manager, which contains:</p>
<ul>
<li>2 publics functions to:
<ul>
<li>start listeners</li>
<li>check current statuses</li>
</ul>
</li>
<li>1 private function to present the same alert as the native one</li>
</ul>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="kd">import</span> <span class="kt">CoreTelephony</span>
<span class="kd">import</span> <span class="kt">Alamofire</span>
<span class="kd">class</span> <span class="kt">ApiReachabilityManager</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">ApiReachabilityManager</span><span class="p">()</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">networkReachabilityManager</span> <span class="o">=</span> <span class="kt">NetworkReachabilityManager</span><span class="p">()</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">cellularData</span> <span class="o">=</span> <span class="kt">CTCellularData</span><span class="p">()</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentNetworkReachabilityState</span><span class="p">:</span> <span class="kt">NetworkReachabilityManager</span><span class="o">.</span><span class="kt">NetworkReachabilityStatus</span> <span class="o">=</span> <span class="o">.</span><span class="n">unknown</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentCellularDataState</span><span class="p">:</span> <span class="kt">CTCellularDataRestrictedState</span> <span class="o">=</span> <span class="o">.</span><span class="n">restrictedStateUnknown</span>
<span class="kd">func</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
<span class="n">cellularData</span><span class="o">.</span><span class="n">cellularDataRestrictionDidUpdateNotifier</span> <span class="o">=</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="p">(</span><span class="n">state</span><span class="p">)</span> <span class="k">in</span>
<span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">currentCellularDataState</span> <span class="o">=</span> <span class="n">state</span>
<span class="p">}</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="n">listener</span> <span class="o">=</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">state</span> <span class="k">in</span>
<span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">currentNetworkReachabilityState</span> <span class="o">=</span> <span class="n">state</span>
<span class="p">}</span>
<span class="n">networkReachabilityManager</span><span class="p">?</span><span class="o">.</span><span class="nf">startListening</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">checkApiReachability</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">?,</span> <span class="nv">completion</span><span class="p">:</span> <span class="p">(</span><span class="n">_</span> <span class="nv">restricted</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">isRestricted</span> <span class="o">=</span> <span class="n">currentNetworkReachabilityState</span> <span class="o">==</span> <span class="o">.</span><span class="n">notReachable</span> <span class="o">&&</span> <span class="n">currentCellularDataState</span> <span class="o">==</span> <span class="o">.</span><span class="n">restricted</span>
<span class="k">guard</span> <span class="o">!</span><span class="n">isRestricted</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">viewController</span> <span class="o">=</span> <span class="n">viewController</span> <span class="p">{</span>
<span class="nf">presentReachabilityAlert</span><span class="p">(</span><span class="nv">on</span><span class="p">:</span> <span class="n">viewController</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">presentReachabilityAlert</span><span class="p">(</span><span class="n">on</span> <span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">UIAlertController</span><span class="p">(</span>
<span class="c1">// TODO: replace YOUR-APP by your app's name</span>
<span class="nv">title</span><span class="p">:</span> <span class="s">"Mobile Data is Turned Off for </span><span class="se">\"</span><span class="s">YOUR-APP</span><span class="se">\"</span><span class="s">"</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="s">"You can turn on mobile data for this app in Settings."</span><span class="p">,</span>
<span class="nv">preferredStyle</span><span class="p">:</span> <span class="o">.</span><span class="n">alert</span>
<span class="p">)</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">settingsUrl</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">openSettingsURLString</span><span class="p">),</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">canOpenURL</span><span class="p">(</span><span class="n">settingsUrl</span><span class="p">)</span> <span class="p">{</span>
<span class="n">alertController</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span>
<span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Settings"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="p">{</span> <span class="n">action</span> <span class="k">in</span>
<span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">open</span><span class="p">(</span><span class="n">settingsUrl</span><span class="p">)</span>
<span class="p">})</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">okAction</span> <span class="o">=</span> <span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"OK"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="n">alertController</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span><span class="n">okAction</span><span class="p">)</span>
<span class="n">alertController</span><span class="o">.</span><span class="n">preferredAction</span> <span class="o">=</span> <span class="n">okAction</span>
<span class="n">viewController</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">alertController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We need to start the listeners at some point, we’ve chosen to do it directly at app launch, in <strong>AppDelegate</strong> since our app needs network calls directly:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">ApiReachabilityManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span></code></pre></figure>
<p>Then, in your view controller, you can simply call the <strong><code class="language-plaintext highlighter-rouge">checkApiReachability</code></strong> method in case of error:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">handleError</span><span class="p">(</span><span class="n">_</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">Error</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">ApiReachabilityManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">checkApiReachability</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">restricted</span><span class="p">)</span> <span class="k">in</span>
<span class="k">if</span> <span class="o">!</span><span class="n">restricted</span> <span class="p">{</span>
<span class="c1">// TODO: continue to handle error, there is no network-data issue</span>
<span class="p">}</span>
<span class="c1">// No need to handle else case as alert has been presented if needed</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>It’s kind of strange to reimplement a native alert, but we were really surprised by iOS’s <del>incomplete</del> basic version, which isn’t that much of a help in our case. We only did this recently so we don’t have enough data to draw conclusions, but we hope this will avoid some calls to Customer Services.</p>
<p>And don’t forget to <strong>never rely on reachability before making the actual network call</strong>: it should always be an error handling helper.</p>
<p>For an unknown reason, a significant number of users <strong>disable the mobile data for our iOS app</strong>. This is not a problem when they are booking a car from their couch at home with Wi-Fi, but can quickly become a major issue when they try to unlock their Drivy Open car in the street. At this specific moment, there is very little chance that they remember they disabled this setting, so it very often leads to a call to Customer Services that could have been avoided.</p>
Wed, 13 Feb 2019 00:00:00 +0000
https://getaround.tech/handle-disabled-mobile-data-setting-on-ios/
https://getaround.tech/handle-disabled-mobile-data-setting-on-ios/Ruby tricks for junior developersClement Bruno<p>As a junior developer who started his professional coding journey fairly recently I realized that I only used a limited number of methods and ruby capabilities. Since I got started at Drivy, I have discovered several ruby tricks that helped me make my code more readable and efficient.</p>
<h2 id="the-dig-method">The dig method</h2>
<p>The <strong><code class="language-plaintext highlighter-rouge">dig</code></strong> method can be used on hashes (and Arrays) to, as its name suggests, dig through the potential multiple layers in the object and retrieve the value corresponding to the argument provided to the method.<br />
By using <strong><code class="language-plaintext highlighter-rouge">dig</code></strong> you find yourself able to nicely shorten your code and improve overall readability:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Let's consider the following hash structure</span>
<span class="n">user_info</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">name: </span><span class="s1">'Bob'</span><span class="p">,</span>
<span class="ss">email: </span><span class="s1">'[email protected]'</span><span class="p">,</span>
<span class="ss">family_members: </span><span class="p">{</span>
<span class="ss">brother: </span><span class="p">{</span>
<span class="ss">name: </span><span class="s1">'Bobby'</span><span class="p">,</span>
<span class="ss">age: </span><span class="mi">16</span>
<span class="p">},</span>
<span class="ss">mother: </span><span class="p">{</span>
<span class="ss">name: </span><span class="s1">'Constance'</span><span class="p">,</span>
<span class="ss">age: </span><span class="mi">55</span>
<span class="p">},</span>
<span class="ss">father: </span><span class="p">{</span>
<span class="ss">name: </span><span class="s1">'John'</span><span class="p">,</span>
<span class="ss">age: </span><span class="mi">60</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Formerly, if I wanted to access our user’s brother name, I would have written something like that:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">user_info</span><span class="p">[</span><span class="ss">:family_members</span><span class="p">][</span><span class="ss">:brother</span><span class="p">][</span><span class="ss">:name</span><span class="p">]</span></code></pre></figure>
<p>But that is considering the fact that I was <strong>SURE</strong> that each key in fact existed in the hash structure.<br />
For instance, if I executed the following statement, my program would crash because of a non existing key:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">user_info</span><span class="p">[</span><span class="ss">:family_members</span><span class="p">][</span><span class="ss">:grand_father</span><span class="p">][</span><span class="ss">:name</span><span class="p">]</span>
<span class="c1"># => `NoMethodError: undefined method `[]' for nil:NilClass`</span></code></pre></figure>
<p>Therefore, if I wanted to be safe while navigating in my hash structure, I should have written something like that:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">user_info</span><span class="p">[</span><span class="ss">:family_members</span><span class="p">]</span> <span class="o">&&</span> <span class="n">user_info</span><span class="p">[</span><span class="ss">:family_members</span><span class="p">][</span><span class="ss">:grand_father</span><span class="p">]</span> <span class="o">&&</span> <span class="n">user_info</span><span class="p">[</span><span class="ss">:family_members</span><span class="p">][</span><span class="ss">:grand_father</span><span class="p">][</span><span class="ss">:name</span><span class="p">]</span>
<span class="c1"># => nil</span></code></pre></figure>
<p>This is way too long and annoying to write… With the <strong><code class="language-plaintext highlighter-rouge">dig</code></strong> method I can simplify this statement a lot:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">users_info</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:first</span><span class="p">,</span> <span class="ss">:family_members</span><span class="p">,</span> <span class="ss">:brother</span><span class="p">,</span> <span class="ss">:name</span><span class="p">)</span>
<span class="c1">#=> "Bobby"</span>
<span class="n">users_info</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="ss">:first</span><span class="p">,</span> <span class="ss">:family_members</span><span class="p">,</span> <span class="ss">:grand_father</span><span class="p">,</span> <span class="ss">:name</span><span class="p">)</span>
<span class="c1">#=> nil</span></code></pre></figure>
<h2 id="protected-methods">Protected methods</h2>
<p>Everyone knows about <code class="language-plaintext highlighter-rouge">public</code> and <code class="language-plaintext highlighter-rouge">private</code> methods but I’ve found that not many people use <code class="language-plaintext highlighter-rouge">protected</code> ones.</p>
<p>Neither <code class="language-plaintext highlighter-rouge">private</code> methods nor <code class="language-plaintext highlighter-rouge">protected</code> ones can be called <strong>directly</strong> by instances of the class in which the method is defined.<br />
For an instance to access these methods they must be called within a public method defined in the class.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Let's consider the following classes</span>
<span class="k">class</span> <span class="nc">Animal</span>
<span class="k">def</span> <span class="nf">current_activity</span>
<span class="nb">puts</span> <span class="s2">"I am </span><span class="si">#{</span><span class="n">define_activity</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">define_activity</span>
<span class="vi">@activity</span> <span class="o">||=</span> <span class="p">[</span><span class="s2">"eating"</span><span class="p">,</span> <span class="s2">"hunting"</span><span class="p">,</span> <span class="s2">"sleeping"</span><span class="p">].</span><span class="nf">sample</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Hereabove, the <code class="language-plaintext highlighter-rouge">current_activity</code> method could be called by any instance of the Animal class (<em>or any other class inheriting from the Animal class</em>).</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># For instance</span>
<span class="k">class</span> <span class="nc">Cat</span> <span class="o"><</span> <span class="no">Animal</span>
<span class="k">def</span> <span class="nf">preferred_activity</span>
<span class="s2">"My favorite activity is </span><span class="si">#{</span><span class="n">define_activity</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">felix</span> <span class="o">=</span> <span class="no">Cat</span><span class="p">.</span><span class="nf">new</span>
<span class="n">felix</span><span class="p">.</span><span class="nf">preferred_activity</span>
<span class="c1"># => "My favorite activity is sleeping"</span></code></pre></figure>
<p>However, when a method is <code class="language-plaintext highlighter-rouge">private</code> it cannot be called on <code class="language-plaintext highlighter-rouge">self</code> within a class…
For instance, this would raise an error:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Animal</span>
<span class="k">def</span> <span class="nf">current_activity</span>
<span class="nb">puts</span> <span class="s2">"I am </span><span class="si">#{</span><span class="nb">self</span><span class="p">.</span><span class="nf">define_activity</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">define_activity</span>
<span class="vi">@activity</span> <span class="o">||=</span> <span class="p">[</span><span class="s2">"eating"</span><span class="p">,</span> <span class="s2">"hunting"</span><span class="p">,</span> <span class="s2">"sleeping"</span><span class="p">].</span><span class="nf">sample</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">kong</span> <span class="o">=</span> <span class="no">Animal</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">current_activity</span>
<span class="c1"># => NoMethodError: private method `define_activity' called for #<Animal:0x007ff6881d08b8></span></code></pre></figure>
<p>Being able to do so could be especially handy if you wanted to call it on other instances of your class passed as method arguments. For instance it would be useful to be able to do that:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Animal</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">same_activity_as?</span><span class="p">(</span><span class="n">other_animal</span><span class="p">)</span>
<span class="n">define_activity</span> <span class="o">==</span> <span class="n">other_animal</span><span class="p">.</span><span class="nf">define_activity</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">define_activity</span>
<span class="vi">@activity</span> <span class="o">||=</span> <span class="p">[</span><span class="s2">"eating"</span><span class="p">,</span> <span class="s2">"hunting"</span><span class="p">,</span> <span class="s2">"sleeping"</span><span class="p">].</span><span class="nf">sample</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">fido</span> <span class="o">=</span> <span class="no">Animal</span><span class="p">.</span><span class="nf">new</span>
<span class="n">bobby</span> <span class="o">=</span> <span class="no">Animal</span><span class="p">.</span><span class="nf">new</span>
<span class="n">fido</span><span class="p">.</span><span class="nf">same_activity_as?</span><span class="p">(</span><span class="n">bobby</span><span class="p">)</span>
<span class="c1"># => NoMethodError: private method `define_activity' called for #<Animal:0x007ff688142f90></span></code></pre></figure>
<p>In order for the above code not to break we can make the <code class="language-plaintext highlighter-rouge">define_activity</code> method <code class="language-plaintext highlighter-rouge">protected</code> instead of <code class="language-plaintext highlighter-rouge">private</code> and everything will work just fine:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Animal</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">same_activity_as?</span><span class="p">(</span><span class="n">other_animal</span><span class="p">)</span>
<span class="n">define_activity</span> <span class="o">==</span> <span class="n">other_animal</span><span class="p">.</span><span class="nf">define_activity</span>
<span class="k">end</span>
<span class="kp">protected</span>
<span class="k">def</span> <span class="nf">define_activity</span>
<span class="vi">@activity</span> <span class="o">||=</span> <span class="p">[</span><span class="s2">"eating"</span><span class="p">,</span> <span class="s2">"hunting"</span><span class="p">,</span> <span class="s2">"sleeping"</span><span class="p">].</span><span class="nf">sample</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">fido</span> <span class="o">=</span> <span class="no">Animal</span><span class="p">.</span><span class="nf">new</span>
<span class="n">bobby</span> <span class="o">=</span> <span class="no">Animal</span><span class="p">.</span><span class="nf">new</span>
<span class="n">fido</span><span class="p">.</span><span class="nf">same_activity_as?</span><span class="p">(</span><span class="n">bobby</span><span class="p">)</span>
<span class="c1"># => true</span></code></pre></figure>
<p><em>NB: Please note that the protected method can here be called on instances of the class but only within the class definition body. These methods cannot be directly called on an instance of the class such as: <code class="language-plaintext highlighter-rouge">fido.define_activity</code> which would return an error <code class="language-plaintext highlighter-rouge">NoMethodError: protected method 'define_activity' called for #<Animal:0x007ff688846120></code></em></p>
<h2 id="-assignment">||= assignment</h2>
<p>When I got started I didn’t know about ruby’s <code class="language-plaintext highlighter-rouge">double pipe equals:</code> <strong>||=</strong>. The concept is fairly simple and can be very useful in a variety of situations.
Basically using <code class="language-plaintext highlighter-rouge">||=</code> allows you to perform a variable assignment <strong>if and only if</strong> the variable is not yet defined or if its value is currently falsey (<code class="language-plaintext highlighter-rouge">nil</code> or <code class="language-plaintext highlighter-rouge">false</code>).</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">number</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">nil_variable</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">nil_variable</span> <span class="o">||=</span> <span class="n">number</span>
<span class="c1"># => 10 --> nil_variable is now set to 10</span>
<span class="n">false_variable</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">false_variable</span> <span class="o">||=</span> <span class="n">number</span>
<span class="c1"># => 10 --> false_variable is now set to 10</span>
<span class="n">not_defined_variable</span> <span class="o">||=</span> <span class="n">number</span>
<span class="c1"># => 10 --> not_defined_variable is now set to 10</span>
<span class="n">content</span> <span class="o">=</span> <span class="s2">"I already have some content"</span>
<span class="n">content</span> <span class="o">||=</span> <span class="n">number</span>
<span class="c1"># => "I already have some content" --> The content variable is not reassigned and keeps its initial value</span></code></pre></figure>
<h2 id="safe-navigation-operator-">Safe navigation operator: <em>&</em></h2>
<p>This one allows to navigate safely through the layers of objects relations.
Basically, let’s say that we have a company with only one employee and that this employee has a name and an email address:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Company</span>
<span class="nb">attr_reader</span> <span class="ss">:employee</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">employee</span><span class="p">)</span>
<span class="vi">@employee</span> <span class="o">=</span> <span class="n">employee</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Person</span>
<span class="nb">attr_reader</span> <span class="ss">:name</span><span class="p">,</span> <span class="ss">:email</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">email</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
<span class="vi">@email</span> <span class="o">=</span> <span class="n">email</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">bobby</span> <span class="o">=</span> <span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'Bobby'</span><span class="p">,</span> <span class="s1">'[email protected]'</span><span class="p">)</span>
<span class="n">drivy</span> <span class="o">=</span> <span class="no">Company</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">bobby</span><span class="p">)</span></code></pre></figure>
<p>In this context if I wanted to access Drivy’s employee name I woud probably do the following:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">puts</span> <span class="n">drivy</span><span class="p">.</span><span class="nf">employee</span><span class="p">.</span><span class="nf">name</span>
<span class="c1"># => Bobby</span></code></pre></figure>
<p>However, this only works in an environment where none of the elements in the chain (except possibly for the last one) can be <code class="language-plaintext highlighter-rouge">nil</code>.
Now, let’s imagine a case where the company does not really have any employee. The <code class="language-plaintext highlighter-rouge">drivy</code> object would be instantiated as follows and the above code would raise an error:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">drivy</span> <span class="o">=</span> <span class="no">Company</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="kp">nil</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">drivy</span><span class="p">.</span><span class="nf">employee</span><span class="p">.</span><span class="nf">name</span>
<span class="c1"># => NoMethodError: undefined method `name' for nil:NilClass</span></code></pre></figure>
<p>In order to prevent this behaviour, ruby has the <code class="language-plaintext highlighter-rouge">&</code> operator (since version 2.3) which behaves a bit like the <code class="language-plaintext highlighter-rouge">try</code> method in rails.
It tries to fetch the object attribute and returns <code class="language-plaintext highlighter-rouge">nil</code> if any element in the chain is <code class="language-plaintext highlighter-rouge">nil</code>.
For instance:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">drivy</span> <span class="o">=</span> <span class="no">Company</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="kp">nil</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">drivy</span><span class="o">&</span><span class="p">.</span><span class="nf">employee</span><span class="o">&</span><span class="p">.</span><span class="nf">name</span>
<span class="c1"># => nil</span>
<span class="n">google</span> <span class="o">=</span> <span class="no">Company</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">bobby</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">google</span><span class="o">&</span><span class="p">.</span><span class="nf">employee</span><span class="o">&</span><span class="p">.</span><span class="nf">name</span>
<span class="c1"># => Bobby</span></code></pre></figure>
<h2 id="struct">Struct</h2>
<p><code class="language-plaintext highlighter-rouge">Struct</code> is kind of a shortcut class which acts in a way more “liberal” manner. It allows for faster development when you are not willing to create a new class with an <code class="language-plaintext highlighter-rouge">initialize</code> method and other behavioural methods.</p>
<p><code class="language-plaintext highlighter-rouge">Struct</code> is created as follows:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">animal</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:type</span><span class="p">,</span> <span class="ss">:number_of_legs</span><span class="p">)</span>
<span class="n">fido</span> <span class="o">=</span> <span class="n">animal</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"fido"</span><span class="p">,</span> <span class="s2">"dog"</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span></code></pre></figure>
<p>And the parameters passed to the <code class="language-plaintext highlighter-rouge">new</code> method are instance variables directly accessible from the instance:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">puts</span> <span class="n">fido</span><span class="p">.</span><span class="nf">name</span>
<span class="c1"># => "fido"</span>
<span class="n">fido</span><span class="p">.</span><span class="nf">type</span> <span class="o">=</span> <span class="s1">'cat'</span>
<span class="c1"># => "cat"</span>
<span class="c1"># I said that Struct are more liberal because they do not enforce the presence of the arguments that you have to provide to create new instances:</span>
<span class="n">bob</span> <span class="o">=</span> <span class="n">animal</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"bob"</span><span class="p">)</span></code></pre></figure>
<p>In addition to <code class="language-plaintext highlighter-rouge">Struct</code>, ruby provides the ability to use <code class="language-plaintext highlighter-rouge">OpenStruct</code>. The main difference is that <code class="language-plaintext highlighter-rouge">Struct</code> creates a new class whereas <code class="language-plaintext highlighter-rouge">OpenStruct</code> directly creates a new object.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">dinosaur</span> <span class="o">=</span> <span class="no">OpenStruct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"t-rex"</span><span class="p">,</span> <span class="ss">number_of_legs: </span><span class="mi">2</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">dinosaur</span><span class="p">.</span><span class="nf">number_of_legs</span>
<span class="c1"># => 2</span></code></pre></figure>
<p>These tools can be very useful to stub simple object or classes while testing since they allow to replicate basic behaviours very quickly without much hassle.</p>
<h2 id="symbolto_proc">Symbol#to_proc</h2>
<p>Finally I’d like to share a ruby idiom which allows to nicely shorten some statements and improve readability :)</p>
<p>You may have already seen things such as:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">].</span><span class="nf">reduce</span><span class="p">(</span><span class="o">&</span><span class="p">:</span><span class="o">+</span><span class="p">)</span>
<span class="c1"># => 6</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">].</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_s</span><span class="p">)</span>
<span class="c1"># => ["1", "2", "3"]</span></code></pre></figure>
<p>When ruby sees the <code class="language-plaintext highlighter-rouge">&</code> followed by a symbol, it calls the <code class="language-plaintext highlighter-rouge">to_proc</code> method on the symbol and passes the proc as a block to the method.</p>
<p>The above examples are equivalent to writing:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">].</span><span class="nf">reduce</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">sum</span><span class="p">,</span> <span class="n">num</span><span class="o">|</span>
<span class="n">sum</span> <span class="o">+</span> <span class="n">num</span>
<span class="k">end</span>
<span class="c1"># => 6</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">].</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">n</span><span class="o">|</span>
<span class="n">n</span><span class="p">.</span><span class="nf">to_s</span>
<span class="k">end</span>
<span class="c1"># => ["1", "2", "3"]</span></code></pre></figure>
<p>As a junior developer who started his professional coding journey fairly recently I realized that I only used a limited number of methods and ruby capabilities. Since I got started at Drivy, I have discovered several ruby tricks that helped me make my code more readable and efficient.</p>
Tue, 22 Jan 2019 00:00:00 +0000
https://getaround.tech/ruby-tricks-for-junior-devs/
https://getaround.tech/ruby-tricks-for-junior-devs/Lambda composition in ruby 2.6David Bourguignon<h2 id="what-are-we-talking-about">What are we talking about?</h2>
<p>We recently updated a sizeable application to ruby 2.5, which opened up some nice features for us,
such as
the <a href="https://mlomnicki.com/yield-self-in-ruby-25/">yield_self</a> feature.</p>
<p>But I also wanted to have a quick look at 2.6 for comparison purposes,
and I found a small feature that can easily be overlooked:
the new proc composition operators: <code class="language-plaintext highlighter-rouge"><<</code> and <code class="language-plaintext highlighter-rouge">>></code>.</p>
<p>You can find the original request (from 2012!) <a href="https://bugs.ruby-lang.org/issues/6284">here</a>.</p>
<p>This is a way to compose a new proc by “appending” several other procs together.</p>
<p><em>Note: all the code that is present in this article can also be found in
this <a href="https://gist.github.com/dbourguignon/1d7e94daaddd3b2e133ae1fe17aad92f">gist</a></em></p>
<h2 id="a-simple-example">A simple example</h2>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># This lambda takes one argument and returns the same prefixed by "hello"</span>
<span class="n">greet</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="s2">"hello </span><span class="si">#{</span><span class="n">val</span><span class="si">}</span><span class="s2">"</span> <span class="p">}</span>
<span class="c1"># This lambda takes one argument and returns the upcased version</span>
<span class="n">upper</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span><span class="p">.</span><span class="nf">upcase</span> <span class="p">}</span>
<span class="c1"># So you can do</span>
<span class="nb">puts</span> <span class="n">greet</span><span class="p">[</span><span class="s2">"world"</span><span class="p">]</span> <span class="c1"># => hello world</span>
<span class="nb">puts</span> <span class="n">upper</span><span class="p">[</span><span class="s2">"world"</span><span class="p">]</span> <span class="c1"># => WORLD</span>
<span class="n">present</span> <span class="o">=</span> <span class="n">greet</span> <span class="o">>></span> <span class="n">upper</span>
<span class="nb">puts</span> <span class="n">present</span><span class="p">[</span><span class="s2">"world"</span><span class="p">]</span> <span class="c1"># => HELLO WORLD</span>
<span class="n">present</span> <span class="o">=</span> <span class="n">greet</span> <span class="o"><<</span> <span class="n">upper</span>
<span class="nb">puts</span> <span class="n">present</span><span class="p">[</span><span class="s2">"world"</span><span class="p">]</span> <span class="c1"># => hello WORLD</span></code></pre></figure>
<p>Lines 2 and 4 declare 2 simple lambdas, taking 1 argument each.</p>
<p>Lines 10 and 14 are where the magic happens.</p>
<p>This works like a shell-redirect operator, it takes the “output” from one
lambda and sets it as input of the other.</p>
<p>In line 10, we take the “world” input, pass it to <code class="language-plaintext highlighter-rouge">greet</code>, then take the output to pass it to
<code class="language-plaintext highlighter-rouge">upper</code>.</p>
<p>The equivalent would be doing: <code class="language-plaintext highlighter-rouge">upper[greet["world"]]</code></p>
<p>Line 14 is the same in reverse order. The equivalent is <code class="language-plaintext highlighter-rouge">greet[upper["world"]]</code></p>
<h2 id="lets-dive-deeper">Let’s dive deeper</h2>
<p>That was a simplistic example. Let’s try something more useful.</p>
<p>Let’s say we have a transformation-rules directory,
and some pipeline definition that would define which rules we should use in a particular case.</p>
<p>Let’s define some pricing rules:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># List of our individual pricing rules</span>
<span class="no">TAX</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span> <span class="o">+</span> <span class="n">val</span><span class="o">*</span><span class="mf">0.05</span> <span class="p">}</span>
<span class="no">FEE</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span>
<span class="no">PREMIUM</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span> <span class="o">+</span> <span class="mi">10</span> <span class="p">}</span>
<span class="no">DISCOUNT</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span> <span class="o">*</span> <span class="mf">0.90</span> <span class="p">}</span>
<span class="no">ROUND_TO_CENT</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span><span class="p">.</span><span class="nf">round</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># One presenter</span>
<span class="no">PRESENT</span> <span class="o">=</span> <span class="o">-></span><span class="p">(</span><span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="n">val</span><span class="p">.</span><span class="nf">to_f</span> <span class="p">}</span>
<span class="c1"># Pre-define some rule sets for some pricing scenarios</span>
<span class="no">REGULAR_SET</span> <span class="o">=</span> <span class="p">[</span><span class="no">FEE</span><span class="p">,</span> <span class="no">TAX</span><span class="p">,</span> <span class="no">ROUND_TO_CENT</span><span class="p">,</span> <span class="no">PRESENT</span><span class="p">]</span>
<span class="no">PREMIUM_SET</span> <span class="o">=</span> <span class="p">[</span><span class="no">FEE</span><span class="p">,</span> <span class="no">PREMIUM</span><span class="p">,</span> <span class="no">TAX</span><span class="p">,</span> <span class="no">ROUND_TO_CENT</span><span class="p">,</span> <span class="no">PRESENT</span><span class="p">]</span>
<span class="no">DISCOUNTED_SET</span> <span class="o">=</span> <span class="p">[</span><span class="no">FEE</span><span class="p">,</span> <span class="no">DISCOUNT</span><span class="p">,</span> <span class="no">TAX</span><span class="p">,</span> <span class="no">ROUND_TO_CENT</span><span class="p">,</span> <span class="no">PRESENT</span><span class="p">]</span></code></pre></figure>
<p>Now we can define a price calculator:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">apply_rules</span><span class="p">(</span><span class="n">rules</span><span class="p">:,</span> <span class="n">base_price</span><span class="p">:)</span>
<span class="n">rules</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="ss">:>></span><span class="p">).</span><span class="nf">call</span><span class="p">(</span><span class="n">base_price</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>At this point we can easily calculate the pricing for a given scenario:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">amount</span> <span class="o">=</span> <span class="no">BigDecimal</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
<span class="nb">puts</span> <span class="s2">"regular: </span><span class="si">#{</span><span class="n">apply_rules</span><span class="p">(</span><span class="ss">rules: </span><span class="no">REGULAR_SET</span><span class="p">,</span> <span class="ss">base_price: </span><span class="n">amount</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># => 106.05</span>
<span class="nb">puts</span> <span class="s2">"premium: </span><span class="si">#{</span><span class="n">apply_rules</span><span class="p">(</span><span class="ss">rules: </span><span class="no">PREMIUM_SET</span><span class="p">,</span> <span class="ss">base_price: </span><span class="n">amount</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># => 116.55</span>
<span class="nb">puts</span> <span class="s2">"discounted: </span><span class="si">#{</span><span class="n">apply_rules</span><span class="p">(</span><span class="ss">rules: </span><span class="no">DISCOUNTED_SET</span><span class="p">,</span> <span class="ss">base_price: </span><span class="n">amount</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span> <span class="c1"># => 95.45</span></code></pre></figure>
<p>Again, this is quite a naive implementation.</p>
<p>Here we can find that the order of the rules and the type of operator we use will change, for instance
in our case we want to apply taxes on the final amount (including discount, premium or other).
Rounding as a last step is important too.</p>
<h2 id="is-using-these-composition-operators-a-good-idea">Is using these composition operators a good idea?</h2>
<p>I’ll let you make your own mind up.</p>
<p>This is one more tool in the ruby toolbox. It’s one important step toward
a more functional style, like <code class="language-plaintext highlighter-rouge">yield_self</code> in 2.5 (now aliased as <code class="language-plaintext highlighter-rouge">then</code>).</p>
<p>Both allow for some kind of pipeline, <code class="language-plaintext highlighter-rouge">yield_self</code> for transforming values, and <code class="language-plaintext highlighter-rouge"><<</code>/<code class="language-plaintext highlighter-rouge">>></code> for
transforming lambdas.</p>
<h2 id="what-are-we-talking-about">What are we talking about?</h2>
Tue, 15 Jan 2019 00:00:00 +0000
https://getaround.tech/ruby-lambda-composition/
https://getaround.tech/ruby-lambda-composition/Why we've chosen Snowflake ❄️ as our Data WarehouseFaouz EL FASSI<p>In the first of this series of blog posts about Data-Warehousing, I’ve been talking about <a href="https://drivy.engineering/redshift_tips_ticks_part_1/">how we use and manage our Amazon Redshift cluster</a> at Drivy.</p>
<p>One of the most significant issues we had at this time was: how to isolate the compute from the storage to ensure maximum concurrency on read in order to <strong>do more and more data analysis and on-board more people in the team</strong>.</p>
<p>I briefly introduced Amazon Spectrum and promised to talk about how we were going to use it in a second blog post… But, that turned out not to be the case, because we ultimately decided to choose another data-warehousing technology (<a href="https://www.snowflake.com/">Snowflake Computing</a>) which addresses the issue mentioned above, among other things, that I’ll expose here.</p>
<h1 id="why-are-we-changing-our-data-warehouse">Why are we changing our Data Warehouse?</h1>
<p>In Redshift and most of the Massive Parallel Processing SQL DBMS, the underlying data architecture is a mix of two paradigms:</p>
<ul>
<li>Shared nothing: chunks of a table are spread across the worker nodes with no overlaps;</li>
<li>Shared everything: a full copy of a table is available on every worker node.</li>
</ul>
<p>This approach is convenient for homogeneous workloads: a system configuration that is ideal of bulk loading (high I/O, light compute) is a poor fit for complex analytical queries (low I/O, heavy compute) and vice versa.</p>
<p>When you deal with many consumers with different volumes and treatments you usually tend towards a multi-cluster organization of your data warehouse, where each cluster is dedicated to a workload category: I/O intensive, storage-intensive or compute-intensive.</p>
<p>This design gives more velocity to the teams. You can decide to have one cluster for each team, for example, one for the finance, one for the marketing, one for the product, etc. They generally no longer have resource related issues, but new kinds of problems could emerge: <strong>data freshness and consistency across clusters</strong>.</p>
<p>Indeed, multi-clustering involves synchronization between clusters to ensure that the same complete data is available on every cluster on time. It complexifies the overall system, and thus results in a loss of agility.</p>
<p>In our case we have thousands of queries running on a single Redshift cluster, so very different workloads can occur concurrently:</p>
<ul>
<li>a Drivy fraud application frequently requires the voluminous web and mobile app tracking data to detect fraudulent devices,</li>
<li>the main business-reporting runs a large computation on multiple tables,</li>
<li>the ETL pipeline of production DB dump and enrichment is running,</li>
<li>the ETL pipeline responsible for the tracking is running,</li>
<li>an exploration software extracts millions of records.</li>
</ul>
<p>In order to improve the overall performance, to reduce our SLAs and make room for every analyst who wants to sandbox a complex analysis, we were looking for a solution that would increase the current capabilities of the system without adding new struggles.</p>
<p>It has to ensure the following:</p>
<ul>
<li>ANSI SQL support and ACID transactions.</li>
<li>Peta-byte scale.</li>
<li>A fully managed solution.</li>
<li>Seamless scaling capability, ideally ability to scale independently compute and storage.</li>
<li>Cost effective.</li>
</ul>
<p>Snowflake Computing meets all those requirements, it has a cloud-agnostic (could be Azure or AWS) shared-data architecture and elastic on-demand virtual warehouses that access the same data layer.</p>
<h1 id="the-snowflake-elastic-data-warehouse">The Snowflake Elastic data warehouse</h1>
<p>Snowflake is a pure software as a service, which supports ANSI SQL and ACID transactions. It also supports semi-structured data such as JSON and AVRO.</p>
<blockquote>
<p>The most important aspect is its elasticity.</p>
</blockquote>
<p>Storage and computing resources can be scaled independently in seconds. To achieve that, virtual warehouses can be created and decommissioned on the fly. Each virtual warehouse has access to the shared tables directly on S3, <strong>without the need to physically copy the data</strong>.</p>
<figure>
<img alt="Snowflake architecture" src="/assets/posts/2019-01-07-snowflake-migration/architecture.png" />
<figcaption>
Multi-Cluster, Shared Data Architecture. Source: https://www.snowflake.com
</figcaption>
</figure>
<p>They also have two really interesting features: <strong>auto-suspend</strong> and <strong>auto-scale</strong>.
Every time a cluster is not used for more than 10 minutes, it is automatically put in sleep mode with no additional fees.
The “Enterprise” plan also gives the auto-scale feature that adapts the size of the virtual warehouse according to the workload (horizontal scaling). I haven’t tested this feature yet since we have the lower “Premier” plan.</p>
<h1 id="from-redshift-to-snowflake">From Redshift to Snowflake</h1>
<p>The data engineering team at Drivy is composed of two engineers. We dedicated a full quarter to the migration on top of the day-to-day operations, and it’s not finished yet.
During this migration, we took the opportunity to pay some of our technical debt and modernize some of our ETL processes.</p>
<p>One of the greatest improvements we addressed was the versioning on S3 of every data involved prior and post a transformation.
At every run of every ETL pipeline, for instance, if we consider the bulk loading of the production DB, a copy of the raw data and the transformed data is stored on S3.</p>
<p>That gives us many new capabilities: reproducibility, auditing and easier operations (when backfilling or when updating a table schema).</p>
<p>The biggest blocks of the migration were:</p>
<ul>
<li>MySQL to Snowflake: Production DB bulk loading and transformations, with three kinds of ingestions, incremental append-only, incremental upsert, and full dump - we made a questionable choice here: our intermediate format is csv, we had many formatting issues.</li>
<li>Captur: Our internal tracking framework, it’s a pipeline that loads raw events from S3 (sent by the web and the mobile apps through a Kinesis stream) and split them into a backend and a frontend schema holding different tables (one for each event). It also automatically detects changes and adapts the schema (new columns, new tables) when needed.</li>
<li>API integrations: spreadsheets, 3rd parties APIs… straightforward but numerous.</li>
<li>Security and Grants management.</li>
</ul>
<h2 id="virtual-warehouses-mix">Virtual Warehouses mix</h2>
<p>We want to group similar workloads in the same warehouses, to tailor the resources needed to the complexity of the computations, we made the following choice in our first iteration:</p>
<table>
<thead>
<tr>
<th>quantity</th>
<th>size</th>
<th>users</th>
<th>description</th>
<th>usage per day</th>
<th>usage per week</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>S</td>
<td>ETL + Viz</td>
<td>Main warehouse for bulk loading, ETL and visualizations software.</td>
<td>∞</td>
<td>7d/7</td>
</tr>
<tr>
<td>1</td>
<td>L</td>
<td>Exploration</td>
<td>Used early in the morning for ~100 high I/O extractions for an exploration software.</td>
<td>0 - 4h</td>
<td>7d/7</td>
</tr>
<tr>
<td>1</td>
<td>XS</td>
<td>Analysts + Business users</td>
<td>Main warehouse for analysts, ~200 daily complex analytical queries.</td>
<td>0 - 10h</td>
<td>5d/7</td>
</tr>
<tr>
<td>1</td>
<td>L</td>
<td>Machine Learning + Ops</td>
<td>Compute intensive warehouse for punctual heavy computations.</td>
<td>0 - 2h</td>
<td>N.A.</td>
</tr>
</tbody>
</table>
<p>Every warehouse has the default auto-suspend set to 10min of inactivity.</p>
<h1 id="whats-next">What’s next</h1>
<p>Once we finish our migration, I’ll share my thoughts with you about the overall performance of the new system.
I’ll also iterate on the mix of strategies presented above to ensure maximum velocity and convenience while minimizing the costs.
Also, I’ll tell you more about how we do grant management.</p>
<p>Meanwhile, don’t hesitate of course to reach out to me if you have any feedback!</p>
<p>In the first of this series of blog posts about Data-Warehousing, I’ve been talking about <a href="https://drivy.engineering/redshift_tips_ticks_part_1/">how we use and manage our Amazon Redshift cluster</a> at Drivy.</p>
Mon, 07 Jan 2019 00:00:00 +0000
https://getaround.tech/snowflake-migration/
https://getaround.tech/snowflake-migration/Airflow Architecture at DrivyEloïse Gomez<p>Drivy has been using Airflow to orchestrate tasks for 2 years now. We thought it was the best tool on the market when we wanted to start digging into data. The purpose was to understand how well our features were performing. We didn’t really know how the data was going to be used, and by whom. We wanted something easy to use and set up. We set up everything on an ec2 instance. 75 workflows later, we wanted to upgrade our Airflow version and move from a local to a celeryExecutor mode. In a local mode there is only one worker (which is also the webserver and the scheduler). In the celeryExecutor, on the contrary, there are several workers which can execute tasks in parallel. Our number of DAGs is constantly growing and Celery mode is the best choice to handle this growth.</p>
<h1 id="what-is-airflow-">What is Airflow ?</h1>
<p>Airflow is Airbnb’s baby. It is an open-source project which schedules DAGs. Dag stands for Directed Acyclic Graph. Basically, they are an organized collection of tasks. Thanks to Airflow’s nice UI, it is possible to look at how DAGs are currently doing and how they perform. If a DAG fails an email is sent with its logs. It can be manually re-triggered through the UI. Dags can combine lot of different types of tasks (bash, python, sql…) and interact with different datasources. Airflow is a really handy tool to transform and load data from a point A to a point B.
You can check their documentation over <a href="https://airflow.apache.org/">here</a>.</p>
<p>A simple Airflow DAG with several tasks:</p>
<p><img src="../assets/posts/2018-08-24-airflow-architecture/airflow-dag.png" alt="" /></p>
<h1 id="airflow-components">Airflow components</h1>
<p>An Airflow cluster has a number of daemons that work together : a webserver, a scheduler and one or several workers.</p>
<h2 id="webserver">Webserver</h2>
<p>The airflow webserver accepts HTTP requests and allows the user to interact with it. It provides the ability to act on the DAG status (pause, unpause, trigger). When the webserver is started, it starts gunicorn workers to handle different requests in parallel.</p>
<h2 id="scheduler">Scheduler</h2>
<p>The Airflow scheduler monitors DAGs. It triggers the task instances whose dependencies have been met. It monitors and stays in synchronisation with a folder for all DAG objects, and periodically inspects tasks to see if they can be triggered.</p>
<h2 id="worker">Worker</h2>
<p>Airflow workers are daemons that actually execute the logic of tasks. They manage one to many CeleryD processes to execute the desired tasks of a particular DAG.</p>
<h2 id="how-do-they-interact-">How do they interact ?</h2>
<p>Airflow daemons don’t need to register with each other and don’t need to know about each other. They all take care of a specific task and when they are all running, everything works as expected.
The scheduler periodically polls to see if any DAGs which are registered need to be executed. If a specific DAG needs to be triggered, then the scheduler creates a new DagRun instance in the Metastore and starts to trigger the individual tasks in the DAG. The scheduler will do that by pushing messages into the queuing service. A message contains information about the task to execute (DAG_id, task_id..) and what function needs to be performed.
In some cases, the user will interact with the web server. He can manually trigger a DAG to be ran. A DAGRun is created and the scheduler will start trigger individual tasks the same way as described before.
Celeryd processes, controlled by workers, periodically pull from the queuing service. When a celeryd process pulls a task message, it updates the task instance in the metastore to a running state and begins executing the code provided. When the task ends (in a success or fail state) it updates the state of the task.</p>
<h1 id="airflow-architecture">Airflow architecture</h1>
<h2 id="single-node-architecture">Single-node architecture</h2>
<p>In a single-node architecture all components are on the same node. To use a single node architecture, Airflow has to be configured with the LocalExecutor mode.</p>
<p><img src="../assets/posts/2018-08-24-airflow-architecture/single-node-architecture.png" alt="" /></p>
<p>The single-node architecture is widely used by the users in case they have a moderate amount of DAGs. In this mode, the worker pulls tasks to run from an IPC (Inter Process Communication) queue. This mode doesn’t any need external dependencies. It scales up well until all resources on the server are used.
This solution works pretty well. However, to scale out to multiple servers, the Celery executor mode has to be used. Celery executor uses Celery (and a message-queuing server) to distribute the load on a pool of workers.</p>
<h2 id="multi-node-architecture">Multi-node Architecture</h2>
<p>In a multi node architecture daemons are spread in different machines. We decided to colocate the webserver and the scheduler. To use this architecture, Airflow has to be configure with the Celery Executor mode.</p>
<p><img src="../assets/posts/2018-08-24-airflow-architecture/multi-node-architecture.png" alt="" />.</p>
<p>In this mode, a Celery backend has to be set (Redis in our case). Celery is an asynchronous queue based on distributed message passing. Airflow uses it to execute several tasks concurrently on several workers server using multiprocessing. This mode allows to scale up the Airflow cluster really easily by adding new workers.</p>
<h3 id="why-did-we-choose-to-use-the-multi-node-architecture-">Why did we choose to use the multi-node architecture ?</h3>
<p>Multi-node architecture provides several benefits :</p>
<ul>
<li>Higher availability: if one of the worker nodes goes down, the cluster will still be up and DAGs will still be running.</li>
<li>Dedicated workers for specific tasks : we have a workflow where some of our DAGs are CPU intensive. As we have several workers we can dedicate some of them to these kinds of DAGs.</li>
<li>Scaling horizontally: Indeed since workers don’t need to register with any central authority to start processing tasks, we can scale our cluster by easily adding new workers. Nodes can be turned on and off without any downtime on the cluster.</li>
</ul>
<p>In the next episode, we’ll look at how we automate Airflow cluster deployment :).</p>
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://airflow.apache.org/airflow">Airflow Documentation</a></li>
<li><a href="https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8">https://medium.com/airbnb-engineering/airflow-a-workflow-management-platform-46318b977fd8</a></li>
<li><a href="https://www.slideshare.net/RobertSanders49/running-apache-airflow-workflows-as-etl-processes-on-hadoop">https://www.slideshare.net/RobertSanders49/running-apache-airflow-workflows-as-etl-processes-on-hadoop</a></li>
<li><a href="http://demorenoc.github.io/slides/pycon-co-2017/airflow/">http://demorenoc.github.io/slides/pycon-co-2017/airflow/</a></li>
</ol>
<p>Drivy has been using Airflow to orchestrate tasks for 2 years now. We thought it was the best tool on the market when we wanted to start digging into data. The purpose was to understand how well our features were performing. We didn’t really know how the data was going to be used, and by whom. We wanted something easy to use and set up. We set up everything on an ec2 instance. 75 workflows later, we wanted to upgrade our Airflow version and move from a local to a celeryExecutor mode. In a local mode there is only one worker (which is also the webserver and the scheduler). In the celeryExecutor, on the contrary, there are several workers which can execute tasks in parallel. Our number of DAGs is constantly growing and Celery mode is the best choice to handle this growth.</p>
Wed, 21 Nov 2018 00:00:00 +0000
https://getaround.tech/airflow-architecture/
https://getaround.tech/airflow-architecture/Open-sourcing checker jobsNicolas Zermati<p>We’ve recently extracted the <a href="https://github.com/drivy/checker_jobs">checker_jobs</a> gem from our codebase.
It’s a simple alerting tool with a very specific purpose which this article will explain.</p>
<p>Over time, we update the rules that our data has to comply with. Making sure our data
is always what we expect it to be is hard, especially when old constraints change, new
constraints come along, new fields are added, backfill isn’t always possible…</p>
<p>Even with a careful team behind it, the system can produce corrupted data for weeks,
months, or years before anyone notices. By that time, it could be too late or just
impossible to fix. In comparison, crashes are noticed faster and could be corrected
quickly, when a data issue could spread and impact many parts of the system making the
issue way more expensive to fix.</p>
<p>The <code class="language-plaintext highlighter-rouge">checker_jobs</code> are here to be sure that when this sneaky data corruption happens,
you notice it right away.</p>
<h2 id="what-was-the-problem">What was the problem?</h2>
<p>Imagine we’ve got, a <code class="language-plaintext highlighter-rouge">users</code> table with a <code class="language-plaintext highlighter-rouge">terms_of_services_accepted_at</code> column. This
column could be set for new users but not for old ones. We need the user to accept the
ToS before they can book a trip on our platform. Unfortunately, old trips aren’t subject
to that rule since the column didn’t exist back then.</p>
<p>We’ll do the best we can to be sure that we update all our user’s paths to take that
new requirement into account. Even with our nice test suite, we don’t cover all the code
paths, especially with all the production data. That data isn’t fresh from a testing
factory, but testing on legacy, old, and sparse data is a different topic!</p>
<p>So to get some peace of mind, we would like to be sure that there are no recent trips
booked where the driver didn’t accept the ToS. What we could do is write a piece of
code verifying that <em>we have no trips with users having the <code class="language-plaintext highlighter-rouge">users.terms_of_services_accepted_at</code>
unset</em>.</p>
<h2 id="how-this-gem-is-useful">How this gem is useful</h2>
<p>The gem is offering you a quick way to get alerted when this piece of code finds such a
trip. You can basically get notifications (emails, bugtrackers, …) when a trip doesn’t
honor the ToS rule.</p>
<p>It would look like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">TripChecker</span>
<span class="kp">include</span> <span class="no">CheckerJobs</span><span class="o">::</span><span class="no">Base</span>
<span class="n">notify</span> <span class="ss">:email</span><span class="p">,</span> <span class="ss">to: </span><span class="s2">"[email protected]"</span>
<span class="n">ensure_no</span> <span class="ss">:trip_without_users_terms_of_service_being_accepted</span> <span class="k">do</span>
<span class="no">Trip</span><span class="p">.</span><span class="nf">joins</span><span class="p">(</span><span class="ss">:users</span><span class="p">).</span><span class="nf">merge</span><span class="p">(</span><span class="no">User</span><span class="p">.</span><span class="nf">terms_of_service_not_yet_accepted</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">ensure_no</span> <span class="ss">:trip_with_deactivated_car</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Then you would have to enqueue that <code class="language-plaintext highlighter-rouge">TripChecker</code> as often as you want to do that verification.
In our case, because we use a Ruby tasks scheduler and Sidekiq, it looks like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">every</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="nf">day</span><span class="p">,</span> <span class="s1">'trip_checker'</span><span class="p">,</span> <span class="ss">at: </span><span class="p">[</span><span class="s1">'00:10'</span><span class="p">,</span> <span class="s1">'12:05'</span><span class="p">],</span> <span class="ss">tz: </span><span class="s2">"Paris"</span><span class="p">)</span> <span class="k">do</span>
<span class="no">Sidekiq</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">enqueue</span><span class="p">(</span><span class="no">TripChecker</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Here is an example of what we see in Bugsnag when one of our checkers is triggered:</p>
<p><a href="https://dha4w82d62smt.cloudfront.net/items/253T2g0Y2f1s3q3T0p1x/Screenshot%20from%202018-09-25%2011-30-05.png" target="_blank">
<img src="https://dha4w82d62smt.cloudfront.net/items/253T2g0Y2f1s3q3T0p1x/Screenshot%20from%202018-09-25%2011-30-05.png" alt="CheckerJob error in BugSnag" />
</a></p>
<h2 id="what-are-the-other-ways-of-solving-this">What are the other ways of solving this?</h2>
<p>There are others solutions to this issue like:</p>
<ul>
<li>code that is more defensive and crashes if the preconditions aren’t met,</li>
<li>some database features such as triggers, foreign keys, or checks, or</li>
<li>a better test suite that can work with production data.</li>
</ul>
<p>We try to use those when it makes sense, and we advise you to do the same.
Still, the <code class="language-plaintext highlighter-rouge">checker_jobs</code> are different from all of those solutions:</p>
<ul>
<li>they are safer than code defensiveness, they don’t impact your production system,</li>
<li>they are cheaper to create, maintain, and, most of all, delete than database constraints, and</li>
<li>they are easier to setup than regression testing on production data.</li>
</ul>
<p>Of course, they don’t provide the same guarantees compared to the other solutions
thus the comparison isn’t that fair.</p>
<h2 id="whats-next">What’s next?</h2>
<p>You could give <a href="https://github.com/drivy/checker_jobs">checker_jobs</a> a go, follow the instructions on Github and tell us
how it went!</p>
<p>In the future, there are many things that we would like to see, things such as:</p>
<ul>
<li>More job processors, ActiveJob is a good candidate,</li>
<li>More notifiers, I’m talking about PagerDuty, Bugtrackers, SMS, etc.</li>
<li><code class="language-plaintext highlighter-rouge">checker_jobs-web</code> a extra gem that allows you to publish the results of the checks on a dedicated web UI, and of course</li>
<li>Contributions from the community!</li>
</ul>
<p>We intend to extract and release more of this kind of libraries and we hope others will find them useful.</p>
<p>We’ve recently extracted the <a href="https://github.com/drivy/checker_jobs">checker_jobs</a> gem from our codebase.
It’s a simple alerting tool with a very specific purpose which this article will explain.</p>
Mon, 24 Sep 2018 00:00:00 +0000
https://getaround.tech/checker-jobs/
https://getaround.tech/checker-jobs/Exporting significant SQL reports with ActiveRecordNicolas Zermati<p>A few months ago we faced a memory issue on some of our background jobs.
Heroku was killing our dyno because it was exceeding its allowed memory. Thanks
to our <a href="/sidekiq-instrumentation">instrumentation of Sidekiq</a>, it was easy to
spot the culprit. The job was doing a fairly complex SQL request, and
outputing the query’s result into a CSV file before archiving this file.</p>
<p>In this article, I’ll explain what happened and detail the method we used
to solve the problem. I had never seen or used this technique before thus I
thought it would be nice to share.</p>
<h2 id="more-context">More context</h2>
<p>We run a tiny framework, something more like a convention, to run SQL queries
and archive the results. If I remove the noise of the framework, we had a code
like:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">rows</span> <span class="o">=</span> <span class="n">with_replica_database</span> <span class="k">do</span>
<span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">select_rows</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">CSV</span><span class="p">.</span><span class="nf">generate</span> <span class="k">do</span> <span class="o">|</span><span class="n">csv</span><span class="o">|</span>
<span class="n">csv</span> <span class="o"><<</span> <span class="n">header</span>
<span class="n">rows</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span> <span class="n">csv</span> <span class="o"><<</span> <span class="n">row</span> <span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>In this simplified example, there are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">with_replica_database</code>: a helper that helps us run a piece of code using a replica database,</li>
<li><code class="language-plaintext highlighter-rouge">query</code>: our SQL query, as a <code class="language-plaintext highlighter-rouge">String</code>, and</li>
<li><code class="language-plaintext highlighter-rouge">header</code>: a placeholder for the <code class="language-plaintext highlighter-rouge">Array</code> of our columns names.</li>
</ul>
<p>We used <code class="language-plaintext highlighter-rouge">select_rows</code> as the results of the query didn’t really match any of our
models. It is a reporting query that does too many <code class="language-plaintext highlighter-rouge">join</code>, <code class="language-plaintext highlighter-rouge">group by</code>, and
<code class="language-plaintext highlighter-rouge">subqueries</code>. The query takes dozens of minutes to run. We could, and probably
should, integrate that into our ETL but that’s not the point…</p>
<p>The resulting CSV file wasn’t that big, maybe a hundred megabytes.</p>
<h2 id="the-issue">The issue</h2>
<p>The memory comsumption of this came from the many rows returned by the
<code class="language-plaintext highlighter-rouge">select_rows</code> method. Each row is an array containing many entries as our CSV
have many columns. Each entry could be a complex datatype converted by
<code class="language-plaintext highlighter-rouge">ActiveRecord</code> into even more complex Ruby objects. We had many instances of
<code class="language-plaintext highlighter-rouge">Time</code> with their <code class="language-plaintext highlighter-rouge">TimeZone</code>, <code class="language-plaintext highlighter-rouge">BigDecimal</code>, …</p>
<p>Since the query returns millions of rows, even while having a linear complexity,
the memory consumption is too high.</p>
<h2 id="an-impossible-approach">An impossible approach</h2>
<p>At first I thought about paginating the results much in the same way that <code class="language-plaintext highlighter-rouge">find_each</code>
works. The problem with that was that for 10000 rows, if I paginatd by 1000, it
would take 10 times the time of the same request without pagination.</p>
<p>Our query looked like this:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">t</span><span class="p">.</span><span class="n">a</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">b</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="k">c</span><span class="p">)</span> <span class="k">as</span> <span class="k">c</span>
<span class="k">FROM</span> <span class="n">t</span>
<span class="k">JOIN</span> <span class="n">u</span> <span class="k">ON</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">t</span><span class="p">.</span><span class="n">u_id</span>
<span class="k">JOIN</span> <span class="n">v</span> <span class="k">ON</span> <span class="n">v</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">u</span><span class="p">.</span><span class="n">v_id</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">t</span><span class="p">.</span><span class="n">a</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">b</span></code></pre></figure>
<p>Just imagine t, u, v being <em>subqueries</em> with <em>unions</em>, <code class="language-plaintext highlighter-rouge">OR</code> conditions, other
<code class="language-plaintext highlighter-rouge">GROUP BY</code>and more of poorly performing stuff. The sad part is the <code class="language-plaintext highlighter-rouge">GROUP BY</code>
which required the engine to go through all results in order to group rows
correctly. Using pagination on this would be something like:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">t</span><span class="p">.</span><span class="n">a</span><span class="p">,</span> <span class="n">u</span><span class="p">.</span><span class="n">b</span><span class="p">,</span> <span class="k">SUM</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="k">c</span><span class="p">)</span> <span class="k">as</span> <span class="k">c</span>
<span class="k">FROM</span> <span class="n">t</span>
<span class="k">JOIN</span> <span class="n">u</span> <span class="k">ON</span> <span class="n">u</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">t</span><span class="p">.</span><span class="n">u_id</span>
<span class="k">JOIN</span> <span class="n">v</span> <span class="k">ON</span> <span class="n">v</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="n">u</span><span class="p">.</span><span class="n">v_id</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span>
<span class="k">LIMIT</span> <span class="mi">10000</span>
<span class="k">OFFSET</span> <span class="mi">1000000</span></code></pre></figure>
<p>So the fewer entries on a page, the less memory used on the client-side but the
more time spent in the database because more requests will be done.
The more entries on a page, the more memory used on the client-side but the
less time spent in the database because less requests will be done.</p>
<p>In the end, this approach wouldn’t have been future-proof.</p>
<h2 id="focusing-more-on-the-problem">Focusing more on the problem</h2>
<p>It was easy to try to find solutions to the <em>results does not fit in memory</em>
problem because it is a known one. It is common with Rails that long lists and
association-preloading will cause you memory issues. The quick-fix is to use
the <code class="language-plaintext highlighter-rouge">find_each</code> or <code class="language-plaintext highlighter-rouge">in_batches</code> methods.</p>
<p>I realized that I didn’t actually need to load everything in memory, I’m only
interested in getting one line at a time in order to write it into the CSV and
then forgotting about it, thanks to the garbage collector.</p>
<h2 id="solving-the-right-problem">Solving the right problem</h2>
<p>After acknowledging what the true issue was, it was possible to find something
more efficient: streaming APIs.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">CSV</span><span class="p">.</span><span class="nf">generate</span> <span class="k">do</span> <span class="o">|</span><span class="n">csv</span><span class="o">|</span>
<span class="n">csv</span> <span class="o"><<</span> <span class="n">header</span>
<span class="n">with_replica_database</span> <span class="k">do</span>
<span class="n">mysql2</span> <span class="o">=</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="p">.</span><span class="nf">connection</span><span class="p">.</span><span class="nf">instance_variable_get</span><span class="p">(</span><span class="ss">:@connection</span><span class="p">)</span>
<span class="n">rows</span> <span class="o">=</span> <span class="n">mysql2</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="ss">stream: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">cache_rows: </span><span class="kp">false</span><span class="p">)</span>
<span class="n">rows</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span> <span class="n">csv</span> <span class="o"><<</span> <span class="n">row</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>The idea was to bypass <code class="language-plaintext highlighter-rouge">ActiveRecord</code> and use the underlying MySQL client which
was providing the <a href="https://github.com/brianmario/mysql2#streaming">stream</a> option.
I’m sure there are similar options for <a href="https://deveiate.org/code/pg/PG/Result.html#method-i-stream_each_row">other databases</a>.</p>
<p>With that implementation, we only do one request, so no pagination, but we won’t
have all the results in memory. We never needed to have all those results in memory
in the first place anyway.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I would be very interested to use this feature with <code class="language-plaintext highlighter-rouge">ActiveRecord</code>’s ability to
return models rather than rows. Maybe it is already possible but I didn’t find
it. If you have any further information on the subject, please let me know!</p>
<p>I hope you won’t have to use these lower level APIs. But, if you do encounter the
same kind of memory issues, don’t throw money at it right away. Try this first ^^</p>
<p>And obviously, most of this could be avoided by tweaking the layout of data and
their relations. In our case, denormalization could make this easier but we’re not
ready to pay that cost - yet.</p>
<p><strong>Edit</strong> As <a href="https://www.reddit.com/r/ruby/comments/9bkqoq/exporting_significant_sql_reports_with/e54baw5/">nyekks mentionned it on Reddit</a>, <a href="https://sequel.jeremyevans.net/">sequel</a> seems
to be better at this out of the box.</p>
<p>A few months ago we faced a memory issue on some of our background jobs.
Heroku was killing our dyno because it was exceeding its allowed memory. Thanks
to our <a href="/sidekiq-instrumentation">instrumentation of Sidekiq</a>, it was easy to
spot the culprit. The job was doing a fairly complex SQL request, and
outputing the query’s result into a CSV file before archiving this file.</p>
Wed, 29 Aug 2018 00:00:00 +0000
https://getaround.tech/streaming-raw-sql-results-with-active-record/
https://getaround.tech/streaming-raw-sql-results-with-active-record/Security tips for rails appsAdrien Siami<p>As your application gets larger and larger, the surface area for security issues expands accordingly, and security bugs become more and more problematic.</p>
<p>Here are a few tips to avoid some common pitfalls regarding security for Rails apps.</p>
<h1 id="use-i18n-with-html-tags-properly">Use I18n with html tags properly</h1>
<p>It is quite common to want to mix I18n translation keys with HTML tags. I’d recommend against doing that as much as possible, but sometimes you can’t
really avoid it.</p>
<p>Let’s take the following example:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="c1"># en.yml</span>
<span class="na">en</span><span class="pi">:</span>
<span class="na">hello</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Welcome</span><span class="nv"> </span><span class="s"><strong>%{user_name}</strong>!"</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-erb" data-lang="erb"><span class="cp"><%=</span> <span class="n">t</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="ss">user_name: </span><span class="n">current_user</span><span class="p">.</span><span class="nf">first_name</span><span class="p">)</span> <span class="cp">%></span></code></pre></figure>
<p>We have a problem here, because this will produce the following output:</p>
<p>Welcome <strong>John</strong>!</p>
<p>Oops! Indeed, our string was never marked as html safe, therefore rails will escape html entities.</p>
<p>One (bad) way to fix it would be to do the following:</p>
<figure class="highlight"><pre><code class="language-erb" data-lang="erb"><span class="cp"><%</span> <span class="c1"># Don't do this! </span><span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">t</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="ss">user_name: </span><span class="n">current_user</span><span class="p">.</span><span class="nf">first_name</span><span class="p">).</span><span class="nf">html_safe</span> <span class="cp">%></span></code></pre></figure>
<p>While it works, we just exposed ourselves to a nasty <a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)">XSS</a>. Indeed, our user can now change their name with some pesky JavaScript in it and the JavaScript will be executed.</p>
<p>XSSes are often underrated as benign security issues, but they can be fatal if exploited properly.</p>
<h2 id="recommended-solution">Recommended solution</h2>
<p>Fortunately, Rails has a <a href="https://guides.rubyonrails.org/i18n.html#using-safe-html-translations">nice solution</a> for us: if an I18n key ends up with <code class="language-plaintext highlighter-rouge">_html</code>, it will automatically be marked as html safe <strong>while the key interpolations will be escaped</strong>!</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="c1"># en.yml</span>
<span class="na">en</span><span class="pi">:</span>
<span class="na">hello_html</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Welcome</span><span class="nv"> </span><span class="s"><strong>%{user_name}</strong>!"</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-erb" data-lang="erb"><span class="cp"><%</span> <span class="c1"># Do this! </span><span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">t</span><span class="p">(</span><span class="s1">'hello_html'</span><span class="p">,</span> <span class="ss">user_name: </span><span class="n">current_user</span><span class="p">.</span><span class="nf">first_name</span><span class="p">)</span> <span class="cp">%></span></code></pre></figure>
<h2>🎉</h2>
<p>Note that this is pretty much the same as doing this:</p>
<figure class="highlight"><pre><code class="language-erb" data-lang="erb"><span class="cp"><%</span> <span class="c1"># Don't do this either! </span><span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">t</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">,</span> <span class="ss">user_name: </span><span class="n">h</span><span class="p">(</span><span class="n">current_user</span><span class="p">.</span><span class="nf">first_name</span><span class="p">)).</span><span class="nf">html_safe</span> <span class="cp">%></span></code></pre></figure>
<p>One good way to avoid XSSes is to really try to avoid using <code class="language-plaintext highlighter-rouge">html_safe</code> (or <code class="language-plaintext highlighter-rouge">raw</code>) as much as possible, and when forced, double check that you have full control of the content displayed.</p>
<h1 id="be-defensive-by-default">Be defensive by default</h1>
<p>You can’t trust user params; you most likely already know that. But there are different ways to implement sanitization of user params.</p>
<p>Let’s pretend we have a form, and we want to use one of two different <a href="https://robots.thoughtbot.com/activemodel-form-objects">Form Objects</a> depending on a param:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">FooForm</span><span class="p">;</span> <span class="k">end</span>
<span class="k">class</span> <span class="nc">BarForm</span><span class="p">;</span> <span class="k">end</span>
<span class="n">form_klass</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:kind</span><span class="p">].</span><span class="nf">camelize</span><span class="si">}</span><span class="s2">Form"</span><span class="p">.</span><span class="nf">constantize</span> <span class="c1"># Don't do that</span>
<span class="n">form_klass</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">params</span><span class="p">)</span></code></pre></figure>
<p>Here, we get the good form class by constantizing a string that is controllable by the user. This is very bad practice and can lead to terrible side effects (imagine sending <code class="language-plaintext highlighter-rouge">make_user_admin</code> instead of <code class="language-plaintext highlighter-rouge">foo</code> or <code class="language-plaintext highlighter-rouge">bar</code>)</p>
<p>One solution could be to do this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="ss">:kind</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'foo'</span> <span class="o">||</span> <span class="n">params</span><span class="p">[</span><span class="ss">:kind</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'bar'</span>
<span class="n">form_klass</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:kind</span><span class="p">].</span><span class="nf">camelize</span><span class="si">}</span><span class="s2">Form"</span><span class="p">.</span><span class="nf">constantize</span> <span class="c1"># Still, don't do that</span>
<span class="n">form_klass</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Here we are ‘safe’. We check that the params are one of the two expected values and only constantize if needed. While this works fine, we haven’t corrected the root security issue (which is the use of <code class="language-plaintext highlighter-rouge">constantize</code> over user input).</p>
<p>Code grows old and evolves, developers copy and paste parts constantly, and at some point your offending line could end up outside of its guard.</p>
<p>Now let’s have a look at this alternative:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">klasses</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'foo'</span> <span class="o">=></span> <span class="no">FooForm</span><span class="p">,</span>
<span class="s1">'bar'</span> <span class="o">=></span> <span class="no">BarForm</span>
<span class="p">}</span>
<span class="n">klass</span> <span class="o">=</span> <span class="n">klasses</span><span class="p">[</span><span class="n">params</span><span class="p">[</span><span class="ss">:kind</span><span class="p">]]</span>
<span class="k">if</span> <span class="n">klass</span>
<span class="n">klass</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">submit</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>We have the same behaviour as above, except this time we don’t use <code class="language-plaintext highlighter-rouge">constantize</code>.</p>
<p>By being defensive and keeping a close eye on user input, we can avoid many basic security issues.</p>
<h1 id="beware-of-arrays-or-hashes">Beware of arrays or hashes</h1>
<p>Consider the following code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="c1"># POST /delete_user?id=xxx</span>
<span class="k">def</span> <span class="nf">can_delete?</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
<span class="n">other_user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">id: </span><span class="n">user_id</span><span class="p">)</span>
<span class="n">current_user</span><span class="p">.</span><span class="nf">can_delete?</span><span class="p">(</span><span class="n">other_user</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">user_id</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">]</span>
<span class="k">if</span> <span class="n">can_delete?</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
<span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="n">user_id</span><span class="p">).</span><span class="nf">update</span><span class="p">(</span><span class="ss">deleted: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>This code is voluntarily weird-looking in its structure to be vulnerable to the security issue, but trust me, I’ve seen it in the wild ;)</p>
<p>Everything works ok here until we start messing a bit with the params.</p>
<p>Let’s imagine we send the following request:</p>
<p><code class="language-plaintext highlighter-rouge">POST /delete_user?id[]=42&id[]=43&id[]=44&id[]=45..</code></p>
<p>Rails will parse <code class="language-plaintext highlighter-rouge">params[:id]</code> as an array: <code class="language-plaintext highlighter-rouge">[42, 43, 44, 45]</code></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"> <span class="no">User</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">id: </span><span class="p">[</span><span class="mi">42</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">45</span><span class="p">])</span> <span class="c1"># This will return one user with id 42 (lower id)</span>
<span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="p">[</span><span class="mi">42</span><span class="p">,</span> <span class="mi">43</span><span class="p">,</span> <span class="mi">44</span><span class="p">,</span> <span class="mi">45</span><span class="p">]).</span><span class="nf">update</span><span class="p">(</span><span class="ss">deleted: </span><span class="kp">true</span><span class="p">)</span> <span class="c1"># This will update all of those records!</span></code></pre></figure>
<p>Thanks to some <em>weirdness</em> in <code class="language-plaintext highlighter-rouge">find_by</code> and messing with the params, here we managed to act on records we may not have access to.</p>
<p>It’s always good to remember that params can also be arrays (or hashes!) as that can pose some security risks.</p>
<h1 id="remember-that-evil-input-is-not-always-where-we-think-it-is">Remember that evil input is not always where we think it is</h1>
<p>Most of us are very wary when dealing with user params, or values coming from the database.</p>
<p>However, there are some attack vectors that can be forgotten, including (but not limited to) the following:</p>
<ul>
<li>Cookies: they are 100% editable by the user</li>
<li>Other headers in general: <code class="language-plaintext highlighter-rouge">Referer</code>, <code class="language-plaintext highlighter-rouge">User-Agent</code>, etc.</li>
<li>User IP: easily spoofable on misconfigured apps (using <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code>)</li>
<li>Local files: here’s an <a href="http://www.hackingarticles.in/rce-with-lfi-and-ssh-log-poisoning/">interesting example</a> of poisoning the ssh auth.log file in order to perform a remote code execution.</li>
</ul>
<p>It’s always good to think about where any given input comes from and wonder if it can be tampered.</p>
<p>As your application gets larger and larger, the surface area for security issues expands accordingly, and security bugs become more and more problematic.</p>
Mon, 27 Aug 2018 00:00:00 +0000
https://getaround.tech/security-tips-for-rails-apps/
https://getaround.tech/security-tips-for-rails-apps/Implementing Up Navigation on AndroidRomain Guefveneu<p>Parent Navigation has always been a tough topic on Android. There are not a lot of apps that implement the <a href="https://developer.android.com/training/design-navigation/ancestral-temporal">guidelines</a> correctly, maybe because they are hard to understand or complicated to implement. Even the Google apps don’t implement them: it’s always frustrating to take a screenshot, press <em>Up</em> on the preview and not be redirected to the <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.photos">Google Photo</a> app 😞.</p>
<blockquote>
<p>Unlike the <em>Back</em> button, which should come back to the previous screen – even if that screen was not from the same app –, the <em>Up</em> button should stay <a href="https://developer.android.com/training/design-navigation/ancestral-temporal#into-your-app">in the same app</a>.</p>
</blockquote>
<p>Let’s see how to implement this navigation.</p>
<h1 id="a-simple-app">A Simple App</h1>
<p>Here is a simple music app, with 3 activities:</p>
<ul>
<li>a <em>Main</em> activity, with a list of albums</li>
<li>an <em>Album</em> activity, with a list of tracks</li>
<li>a <em>Track</em> activity, with the track’s name and that of the album.</li>
</ul>
<p>Forward navigation is pretty obvious:</p>
<h2 id="forward-navigation">Forward Navigation</h2>
<p><img src="../assets/posts/2018-08-04-android-parent-navigation/forward-navigation.png" alt="" /></p>
<p>On <code class="language-plaintext highlighter-rouge">MainActivity</code>, users can go to <code class="language-plaintext highlighter-rouge">AlbumActivity</code> or <code class="language-plaintext highlighter-rouge">TrackActivity</code>.<br />
Nothing special here. So what about back navigation?</p>
<h2 id="back-navigation">Back Navigation</h2>
<p><img src="../assets/posts/2018-08-04-android-parent-navigation/back-navigation.png" alt="" /></p>
<p>Since we’re in the same <a href="https://developer.android.com/guide/components/activities/tasks-and-back-stack">task</a>, <em>Up</em> navigation and <em>Back</em> navigation do the same thing: they come back to the previous activity.
But if we don’t start from the main activity (for instance from a notification or a widget), <em>Up</em> and <em>Back</em> won’t have the same behavior:</p>
<ul>
<li><em>Back</em> will still dismiss the current activity (or fragment), so users will come back to the previous app</li>
<li><em>Up</em> should redirect to the app’s parent activity.</li>
</ul>
<h2 id="notification-navigation">Notification Navigation</h2>
<p><img src="../assets/posts/2018-08-04-android-parent-navigation/notification-navigation.png" alt="" /></p>
<h1 id="how-to-implement">How to implement</h1>
<p>No need to write anything in the <a href="https://developer.android.com/reference/android/app/Activity.html#onOptionsItemSelected(android.view.MenuItem)"><code class="language-plaintext highlighter-rouge">Activity#onOptionsItemSelected</code></a> method! Everything is already done in the Android SDK.
We mainly just need to edit the <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> file to add some attributes to our activities.</p>
<h2 id="declare-a-parentactivityname">Declare a <code class="language-plaintext highlighter-rouge">parentActivityName</code></h2>
<p>First, we need to declare a parent activity for each child activity:
<code class="language-plaintext highlighter-rouge">TrackActivity</code> will come back to <code class="language-plaintext highlighter-rouge">AlbumActivity</code>, which itself comes back to <code class="language-plaintext highlighter-rouge">MainActivity</code>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".AlbumActivity"</span>
<span class="na">android:parentActivityName=</span><span class="s">".MainActivity"</span>
<span class="err">...</span> <span class="nt">/></span>
<span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".TrackActivity"</span>
<span class="na">android:parentActivityName=</span><span class="s">".AlbumActivity"</span>
<span class="err">...</span> <span class="nt">/></span></code></pre></figure>
<p>See also : <a href="https://developer.android.com/reference/android/app/Activity.html#getParentActivityIntent()">getParentActivityIntent</a></p>
<p>But that’s not enough: now when pressing the <em>Up</em> button a new activity is created – even if we’re in the same task –, instead of dimissing the current activity.</p>
<figure>
<video width="100%" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/activity-recreated.mp4" type="video/mp4" controls="controls"></video>
<figcaption>
The parent activity is recreated when pressing up. Subtle, isn’t it?
</figcaption>
</figure>
<h2 id="declare-a-launchmode">Declare a <code class="language-plaintext highlighter-rouge">launchMode</code></h2>
<p>By declaring parent activities’ <a href="https://developer.android.com/guide/topics/manifest/activity-element#lmode"><code class="language-plaintext highlighter-rouge">launchMode</code></a> as <code class="language-plaintext highlighter-rouge">singleTop</code>, we prevent the system from creating a new activity each time we press <em>Up</em>.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".MainActivity"</span>
<span class="na">android:launchMode=</span><span class="s">"singleTop"</span>
<span class="err">...</span><span class="nt">></span>
...
<span class="nt"></activity></span>
<span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".AlbumActivity"</span>
<span class="na">android:launchMode=</span><span class="s">"singleTop"</span>
<span class="err">...</span> <span class="nt">/></span></code></pre></figure>
<figure>
<video width="100%" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/activity-not-recreated.mp4" type="video/mp4" controls="controls"></video>
<figcaption>
The parent activity is no longer recreated. 🎉
</figcaption>
</figure>
<p>Now we have the desired behavior, but we don’t cover all the cases. What if I start <code class="language-plaintext highlighter-rouge">TrackActivity</code> from outside the app, and press <em>Up</em>? I want to be redirected to <code class="language-plaintext highlighter-rouge">AlbumActivity</code>.</p>
<figure>
<video width="100%" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/notification-up-not-redirected.mp4" type="video/mp4" controls="controls"></video>
<figcaption>
<code>TrackActivity</code> is not redirect to <code>AlbumActivity</code> when pressing Up. Mildly frustrating, to say the least.
</figcaption>
</figure>
<p>To do that, we have to declare a <a href="https://developer.android.com/guide/topics/manifest/activity-element#aff"><code class="language-plaintext highlighter-rouge">taskAffinity</code></a>.</p>
<h2 id="declare-a-taskaffinity">Declare a <code class="language-plaintext highlighter-rouge">taskAffinity</code></h2>
<p>Declaring a <a href="https://developer.android.com/guide/topics/manifest/activity-element#aff"><code class="language-plaintext highlighter-rouge">taskAffinity</code></a> for <code class="language-plaintext highlighter-rouge">TrackActivity</code> allows a new task to be created when starting this activity from outside the app. Thanks to that, <em>Up</em> navigation will switch to the main task and create an <code class="language-plaintext highlighter-rouge">AlbumActivity</code>.<br />
<em>Curious about how it works? See <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/app/Activity.java#L3643">Activity#onNavigateUp</a></em></p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".TrackActivity"</span>
<span class="na">android:taskAffinity=</span><span class="s">".Track"</span>
<span class="err">...</span><span class="nt">/></span></code></pre></figure>
<p>One issue here: <code class="language-plaintext highlighter-rouge">AlbumActivity</code> needs to know which album we want to display.</p>
<h2 id="override-onpreparesupportnavigateuptaskstack">Override <code class="language-plaintext highlighter-rouge">onPrepareSupportNavigateUpTaskStack</code></h2>
<p>On <code class="language-plaintext highlighter-rouge">TrackActivity</code>, we need to override <a href="https://developer.android.com/reference/android/app/Activity#onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)"><code class="language-plaintext highlighter-rouge">onPrepareSupportNavigateUpTaskStack</code></a> to edit the intent that will start the parent activity when pressing <em>Up</em>:</p>
<figure class="highlight"><pre><code class="language-kotlin" data-lang="kotlin"><span class="k">override</span> <span class="k">fun</span> <span class="nf">onPrepareSupportNavigateUpTaskStack</span><span class="p">(</span><span class="n">builder</span><span class="p">:</span> <span class="nc">TaskStackBuilder</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onPrepareSupportNavigateUpTaskStack</span><span class="p">(</span><span class="n">builder</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">albumId</span> <span class="p">=</span> <span class="n">intent</span><span class="p">.</span><span class="nf">getLongExtra</span><span class="p">(</span><span class="nc">TrackActivity</span><span class="p">.</span><span class="nc">EXTRA_ALBUM_ID</span><span class="p">,</span> <span class="p">-</span><span class="mi">1L</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">albumIntent</span> <span class="p">=</span> <span class="nc">AlbumActivity</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">albumId</span><span class="p">)</span>
<span class="n">builder</span><span class="p">.</span><span class="nf">editIntentAt</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">intentCount</span> <span class="p">-</span> <span class="mi">1</span><span class="p">)</span><span class="o">?.</span><span class="nf">putExtras</span><span class="p">(</span><span class="n">albumIntent</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p><em>See also <a href="https://developer.android.com/reference/android/app/Activity.html#onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)">onCreateNavigateUpTaskStack</a>.</em></p>
<figure>
<video width="100%" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/notification-up-redirected.mp4" type="video/mp4" controls="controls"></video>
<figcaption>
<code>TrackActivity</code> is now redirected to <code>AlbumActivity</code> when pressing Up. 👌
</figcaption>
</figure>
<p>One last thing: because we create a new task when starting <code class="language-plaintext highlighter-rouge">TrackActivity</code> from outside the app, <code class="language-plaintext highlighter-rouge">TrackActivity</code> will remain on the <a href="https://developer.android.com/guide/components/activities/recents">Recents screen</a> when pressing <em>Up</em>. It would be great to remove it automatically.</p>
<figure>
<img width="311" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/2-recents-tasks.png" />
<figcaption>
<code>TrackActivity</code> is still visible in the Recents screen. Not very useful.
</figcaption>
</figure>
<h2 id="declare-autoremovefromrecents">Declare <code class="language-plaintext highlighter-rouge">autoRemoveFromRecents</code></h2>
<p>With <a href="https://developer.android.com/guide/topics/manifest/activity-element#autoremrecents"><code class="language-plaintext highlighter-rouge">autoRemoveFromRecents</code></a>, the activity will be removed from the Recents screen when its task is completed, for instance when coming back to the parent activity.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".TrackActivity"</span>
<span class="na">android:autoRemoveFromRecents=</span><span class="s">"true"</span>
<span class="err">...</span><span class="nt">/></span></code></pre></figure>
<figure>
<img width="311" height="540" src="../assets/posts/2018-08-04-android-parent-navigation/1-recent-task.png" />
<figcaption>
<code>TrackActivity</code> is no more visible in the Recents screen. 🙌
</figcaption>
</figure>
<h1 id="summary">Summary</h1>
<p>To sum up, this is what the <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> looks like:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".MainActivity"</span>
<span class="na">android:label=</span><span class="s">"@string/app_name"</span>
<span class="na">android:launchMode=</span><span class="s">"singleTop"</span><span class="nt">></span>
<span class="nt"><intent-filter></span>
<span class="nt"><action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.MAIN"</span> <span class="nt">/></span>
<span class="nt"><category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.LAUNCHER"</span> <span class="nt">/></span>
<span class="nt"></intent-filter></span>
<span class="nt"></activity></span>
<span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".AlbumActivity"</span>
<span class="na">android:label=</span><span class="s">"@string/album_title"</span>
<span class="na">android:launchMode=</span><span class="s">"singleTop"</span>
<span class="na">android:parentActivityName=</span><span class="s">".MainActivity"</span> <span class="nt">/></span>
<span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".TrackActivity"</span>
<span class="na">android:autoRemoveFromRecents=</span><span class="s">"true"</span>
<span class="na">android:label=</span><span class="s">"@string/track_title"</span>
<span class="na">android:parentActivityName=</span><span class="s">".AlbumActivity"</span>
<span class="na">android:taskAffinity=</span><span class="s">".Track"</span> <span class="nt">/></span></code></pre></figure>
<h1 id="conclusion">Conclusion</h1>
<p>Now that you know how to tune the <code class="language-plaintext highlighter-rouge">AndroidManifest</code> to get a satisfying parent navigation, please don’t just <code class="language-plaintext highlighter-rouge">finish()</code> the activity when pressing <em>Up</em> 😀</p>
<h2 id="sources">Sources</h2>
<ol>
<li><a href="https://github.com/drivy/blog-android-parent-navigation">Github Project</a></li>
<li><a href="https://developer.android.com/training/implementing-navigation/temporal">https://developer.android.com/training/implementing-navigation/temporal</a></li>
<li><a href="https://developer.android.com/training/design-navigation/ancestral-temporal">https://developer.android.com/training/design-navigation/ancestral-temporal</a></li>
<li><a href="https://developer.android.com/guide/components/tasks-and-back-stack.html">https://developer.android.com/guide/components/tasks-and-back-stack.html</a></li>
<li><a href="https://developer.android.com/design/patterns/navigation.html">https://developer.android.com/design/patterns/navigation.html</a></li>
</ol>
<p>Parent Navigation has always been a tough topic on Android. There are not a lot of apps that implement the <a href="https://developer.android.com/training/design-navigation/ancestral-temporal">guidelines</a> correctly, maybe because they are hard to understand or complicated to implement. Even the Google apps don’t implement them: it’s always frustrating to take a screenshot, press <em>Up</em> on the preview and not be redirected to the <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.photos">Google Photo</a> app 😞.</p>
Fri, 03 Aug 2018 00:00:00 +0000
https://getaround.tech/android-parent-navigation/
https://getaround.tech/android-parent-navigation/Quick wins to deal with users' broken email addressesJean Anquetil<p>If a user signs up to Drivy, we want to welcome them. If a driver has an upcoming booked trip, we would like to send them the needed information. If they want to reset their password, they need to receive a confirmation email and so on.</p>
<p>In another words, transactional emails are very important for a successful experience. So, how do we deal with broken email addresses?</p>
<h2 id="regex-ing-the-format">Regex-ing the format</h2>
<p>First of all we decided to check the email-address format of a new user during her sign-up flow. To do so, we compare it with a very simple regex.</p>
<p><code class="language-plaintext highlighter-rouge">\A\S+@\S+\.\S+\z</code></p>
<p>Here is our assumption. An email address can:</p>
<ul>
<li>have at least one or more instance of any non-whitespace character,</li>
<li>be imperatively followed by an <code class="language-plaintext highlighter-rouge">at</code> symbol,</li>
<li>then have again one or more instance of any non-whitespace character,</li>
<li>be imperatively followed by a dot symbol,</li>
<li>then have again one or more instance of any non-whitespace character.</li>
</ul>
<p>And that’s it. We don’t want to define a complex pattern such as the <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> one whereas an email provider has its own syntax rules: we don’t want to block some potentially valid addresses.</p>
<h2 id="transliterating">Transliterating</h2>
<p>Later down the line, we faced some delivery issues with email addresses containing special characters (i.e. <code class="language-plaintext highlighter-rouge">àéèù…</code>) so we decided to transliterate the email addresses of some specific domains.</p>
<p>For instance, we know that Gmail supports addresses with accents. But they don’t differentiate between an address with or without accents: they are the same. We therefore decided to transliterate the email addresses from the following domain names: Gmail, Outlook, Hotmail and Live.</p>
<p>Using a <a href="/sanitize-your-attributes/" target="_blank">custom coercion</a> with Virtus in our form object, this is done really smoothly. (However, Virtus is now deprecated so if we were to start from scratch today, we would use something else.)</p>
<p>However, transliteration is not without its limits. The day we open a country without a Latin alphabet we will not be able to transliterate the email addresses anymore:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">I18n</span><span class="p">.</span><span class="nf">transliterate</span><span class="p">(</span><span class="s2">"日本語"</span><span class="p">)</span>
<span class="c1"># => "???"</span></code></pre></figure>
<h2 id="using-an-external-service">Using an external service</h2>
<p>To go further, we could carry out many more checks using an external service. For instance, <a href="https://www.mailgun.com/">MailGun</a> released a library called <a href="https://github.com/mailgun/flanker">Flanker</a>.</p>
<p>It carries out the following checks:</p>
<ul>
<li>DNS lookup: that the @domain.com exists.</li>
<li>MX check: if that @domain.com has a Mail Exchange record. In other words, that the domain is configured to receive emails.</li>
<li>That the email address complies with general validation rules but also specific ones. For instance, regarding @gmail.com they check if <a href="https://github.com/mailgun/flanker/blob/master/flanker/addresslib/plugins/gmail.py#L8">the address length is between 6 and 30 characters</a>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>There will always be a lot of different ways to prevent or sanitize broken email addresses but it will remain difficult to handle all the use cases. Maybe another way to fight this would be by not relying too much on emails: using the phone number to verify a profile and using push or browser notifications to talk to a user.</p>
<p>If a user signs up to Drivy, we want to welcome them. If a driver has an upcoming booked trip, we would like to send them the needed information. If they want to reset their password, they need to receive a confirmation email and so on.</p>
Thu, 05 Jul 2018 00:00:00 +0000
https://getaround.tech/dealing-with-users-broken-email-addresses/
https://getaround.tech/dealing-with-users-broken-email-addresses/Usage of Sidekiq middlewareDavid Bourguignon<p>At Drivy, we use a lot of background jobs, called from service objects, API calls, cron, etc.<br />
A time came when we needed to add some context data across several of these code layers.</p>
<p>For instance, we have some context data we need to keep for auditing reasons.
This data can originate from several points in the application:
maybe from some part of the web application, from the mobile app,
or from a service object.</p>
<p>We tried to find a way to keep this new context data through all code layers and jobs
without having to resort to adding context data arguments everywhere.</p>
<p>We decided to use <code class="language-plaintext highlighter-rouge">Thread.current</code> objects to host this data for the current process.</p>
<blockquote>
<p>CAVEAT: Using this kind of <em>global</em> data in this way is usually considered to be bad practice.
I will not discuss it here, but you can look at this <a href="https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil">discussion</a> for more detail.<br />
We use global data with caution, in a limited scope and only after having really thought about it.
All interactions with the global data is tightly contained in service objects to limit the risk of
using the data outside of its intended scope.</p>
</blockquote>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">ProcessContext</span>
<span class="kp">module_function</span>
<span class="k">def</span> <span class="nf">reset</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">attributes</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">attributes</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">attributes</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="s2">"process_context"</span><span class="p">]</span> <span class="o">||</span> <span class="n">reset</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">attributes</span><span class="o">=</span><span class="p">(</span><span class="n">new_attributes</span><span class="p">)</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="s2">"process_context"</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_attributes</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>It works well, up to the point where we delegate some of this processing to background jobs.
The jobs run on a different thread (even on a different machine).</p>
<p>We use Sidekiq to manage our jobs.
Sidekiq works in the following way (a simplified version):</p>
<ul>
<li>The client side enqueues a job into a Redis database;</li>
<li>On the server side the workers:
<ul>
<li>read the database to pick a job in the queue;</li>
<li>run them.</li>
</ul>
</li>
</ul>
<p>Conveniently, Sidekiq provides a way to add some code around job processing,
on the client side, the server side or both.
So we used these middlewares to propagate the context information from the client
(our Rails application) to the Sidekiq server.</p>
<h2 id="client-side">Client side</h2>
<p>The Sidekiq middleware client API is:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Drivy::MyClientMiddleware</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">worker_class</span><span class="p">,</span> <span class="n">job</span><span class="p">,</span> <span class="n">queue</span><span class="p">,</span> <span class="n">redis_pool</span><span class="p">)</span>
<span class="c1"># custom code</span>
<span class="k">yield</span>
<span class="c1"># custom code</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>And you add it to Sidekiq configuration in this way:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/initializers/sidekiq.rb</span>
<span class="no">Sidekiq</span><span class="p">.</span><span class="nf">configure_client</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">client_middleware</span> <span class="k">do</span> <span class="o">|</span><span class="n">chain</span><span class="o">|</span>
<span class="n">chain</span><span class="p">.</span><span class="nf">add</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">MyClientMiddleware</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p><em>Note: You may want to add this client middleware to the server middleware pipe, see below</em></p>
<p>In our case, we want to enrich the job with some metadata.
Sidekiq allows the adding of information to the job that will be available on the server side:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">Drivy::Sidekiq::Middleware::Client</span>
<span class="k">class</span> <span class="nc">AddProcessContext</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">job</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span>
<span class="n">process_context</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">process_context</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="no">ProcessContext</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">present?</span>
<span class="n">job</span><span class="p">[</span><span class="s1">'process_context'</span><span class="p">]</span> <span class="o">=</span> <span class="no">TrackedEventContext</span><span class="p">.</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">to_json</span>
<span class="k">end</span>
<span class="k">rescue</span> <span class="o">=></span> <span class="n">e</span>
<span class="c1"># Log/notify error as we do not want to fail the job in this case</span>
<span class="nb">puts</span> <span class="n">e</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>We only need the job argument here. It’s basically a <a href="https://github.com/mperham/sidekiq/wiki/Job-Format">regular Hash</a>.
We just add here our own information
(be careful to store only data that will be serialised in JSON).</p>
<h2 id="server-side">Server Side</h2>
<p>The Sidekiq middleware server API is:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Drivy::MyServerMiddleware</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="n">job</span><span class="p">,</span> <span class="n">queue</span><span class="p">)</span>
<span class="c1"># custom code</span>
<span class="k">yield</span>
<span class="c1"># custom code</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>And you add it to Sidekiq configuration in this way:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/initializers/sidekiq.rb</span>
<span class="no">Sidekiq</span><span class="p">.</span><span class="nf">configure_server</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">server_middleware</span> <span class="k">do</span> <span class="o">|</span><span class="n">chain</span><span class="o">|</span>
<span class="n">chain</span><span class="p">.</span><span class="nf">add</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">MyServerMiddleware</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>In our usage, we need to retrieve the metadata from the job and set it in the current process:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">Drivy::Sidekiq::Middleware::Server</span>
<span class="k">class</span> <span class="nc">AddProcessContext</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">job</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span>
<span class="n">process_metadata</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">yield</span>
<span class="n">reset_metadata</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">process_metadata</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="n">job</span><span class="p">[</span><span class="s1">'process_context'</span><span class="p">]</span>
<span class="no">ProcessContext</span><span class="p">.</span><span class="nf">attributes</span> <span class="o">=</span> <span class="n">job</span><span class="p">[</span><span class="s1">'process_context'</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">rescue</span> <span class="o">=></span> <span class="n">e</span>
<span class="c1"># Log/notify error as we do not want to fail the job in this case</span>
<span class="nb">puts</span> <span class="n">e</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">reset_metadata</span>
<span class="no">ProcessContext</span><span class="p">.</span><span class="nf">reset</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>We simply restore the data from the serialised version.</p>
<p>Each middleware is executed in the same thread as the main job process,
so we know the context data will be available to the Ruby job.</p>
<h2 id="a-word-of-caution">A word of caution</h2>
<h3 id="thread-reuse">Thread reuse</h3>
<p>Sidekiq will reuse threads for different jobs in some cases,
so we must be very careful to cleanup our ProcessContext
to ensure we do not pollute the context of other jobs.</p>
<h3 id="middleware-client-on-the-server-side">Middleware client on the server side</h3>
<p>Sometimes, jobs running on the server can enqueue jobs, and act as a client.
In this case, you’ll want to add the client middleware to the server configuration as well:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># config/initializers/sidekiq.rb</span>
<span class="no">Sidekiq</span><span class="p">.</span><span class="nf">configure_server</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">client_middleware</span> <span class="k">do</span> <span class="o">|</span><span class="n">chain</span><span class="o">|</span>
<span class="n">chain</span><span class="p">.</span><span class="nf">add</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">MyClientMiddleware</span>
<span class="k">end</span>
<span class="n">config</span><span class="p">.</span><span class="nf">server_middleware</span> <span class="k">do</span> <span class="o">|</span><span class="n">chain</span><span class="o">|</span>
<span class="n">chain</span><span class="p">.</span><span class="nf">add</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">MyServerMiddleware</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>Middlewares are a useful tool, we use them for logging, and monitoring mainly.
You can find some interesting plugins using middleware on the <a href="https://github.com/mperham/sidekiq/wiki/Related-Projects">Sidekiq Wiki</a>.</p>
<p>And again, do not use global states if you can avoid it.</p>
<p>At Drivy, we use a lot of background jobs, called from service objects, API calls, cron, etc.<br />
A time came when we needed to add some context data across several of these code layers.</p>
Thu, 31 May 2018 00:00:00 +0000
https://getaround.tech/use-sidekiq-middleware/
https://getaround.tech/use-sidekiq-middleware/Rails 5.2: ActiveStorage highlightAlexandre Ferraille<p>Rails 5.2 was released a few weeks ago and comes with awesome features like the new credentials vault, HTTP/2 early hints, Redis cache store and ActiveStorage, which I’m going to focus on in this blog post. The project was initiated by DHH in mid-2017 and has been merged into Rails core. It’s a built-in way to deal with uploads without extra dependencies like Paperclip, Carrierwave or Shrine.</p>
<h1 id="how-does-it-work">How does it work?</h1>
<p>ActiveStorage comes with a complete DSL which allows you to attach and detach one or multiple files to a model. By default, ActiveStorage isn’t installed in a new rails project, you have to run:</p>
<p><code class="language-plaintext highlighter-rouge">rails active_storage:install</code></p>
<p>This command simply copies a migration in your projet. ActiveStorage needs two models/tables : <code class="language-plaintext highlighter-rouge">active_storage_blobs</code> where each record represents a file (which is not stored in the database of course) and <code class="language-plaintext highlighter-rouge">active_storage_attachments</code> is a polymorphic bridge between your models and your uploaded files.</p>
<p>In your model, you need to declare that you’re attaching files:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Car</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_one_attached</span> <span class="ss">:photo</span>
<span class="k">end</span></code></pre></figure>
<p>Then, you can add a file input into your form:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o"><</span><span class="sx">%= image_tag url_for(car.photo) if car.photo.attachment.present? %></span>
<span class="sx"><%=</span> <span class="n">form</span><span class="p">.</span><span class="nf">label</span> <span class="ss">:photo</span> <span class="o">%></span>
<span class="o"><</span><span class="sx">%= form.file_field :photo %></span></code></pre></figure>
<p>In your controller, you must specify that you want to attach a file:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@car</span><span class="p">.</span><span class="nf">photo</span><span class="p">.</span><span class="nf">attach</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:car</span><span class="p">][</span><span class="ss">:photo</span><span class="p">])</span> <span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="ss">:car</span><span class="p">][</span><span class="ss">:photo</span><span class="p">]</span></code></pre></figure>
<p>And that’s all you need to do for a basic file uploader. If you attach a new file or you delete your record, ActiveStorage will remove the old one from your storage and clean up your database.</p>
<h1 id="activestorage-goes-further-still">ActiveStorage goes further still</h1>
<p>With a lot of nice little “cherries on the cake”, ActiveStorage covers most of the cases:</p>
<ul>
<li><strong>Storage:</strong> A few lines of configuration are enough to store/mirror your files into AWS S3, Microsoft Azure or Google Cloud. And if you’re using a more funky storage provider, you can extend the <code class="language-plaintext highlighter-rouge">ActiveStorage::Service</code> class.</li>
<li><strong>Direct upload:</strong> A complete JavaScript library has been written for ActiveStorage which allows you to directly upload to your storage bypassing the rails backend. It comes with a lot a JavaScript events to easily plug this feature with common libraries such as TinyMCE, DropZoneJS…</li>
<li><strong>Image post-processing:</strong> A common need when uploading images is to create resized variants from the original. ActiveStorage works by default with MiniMagick, which is a Ruby implementation for ImageMagick.</li>
<li><strong>Video and PDF previews:</strong> With external libraries (ffmpeg and mutool), you can get a preview from a file without downloading it entirely.</li>
</ul>
<h1 id="whats-the-difference-with-other-upload-managers">What’s the difference with other upload managers?</h1>
<p><strong>Maintenability:</strong>
Since ActiveStorage has been merged into Rails, all the features described above are built-in and don’t require any extra dependencies and so less maintenance needs to be scheduled.</p>
<p><strong>Structure:</strong>
Most popular gems like Shrine, Paperclip, etc. don’t provide ready-to-use tables and require a migration to add a few fields where you want to store your file information. Even if you feel free to do what you want and you’re not stuck to the ActiveStorage way, from my experience you’ll certainly recreate a polymorphic <code class="language-plaintext highlighter-rouge">Asset</code> model.</p>
<p><strong>Form:</strong>
As we seen above, ActiveStorage attaches and detaches files outside ActiveRecord transactions. You need to do it by yourself when you need to, independently from a <code class="language-plaintext highlighter-rouge">save</code>, whereas common gems store your files using ActiveRecord callbacks directly from your params. In my opinion, ActiveStorage provides a better way to handle file attachments by separating two concepts: attributes which go in the database and files which depend on your storage.</p>
<p><strong>Missing features:</strong>
There’s a few advanced features which are not handled by ActiveStorage (yet?) and you’ll need to develop them if you choose to go with ActiveStorage. For instance, Shrine (currently the most advanced competitor), provides a way to cache uploaded files and avoid re-upload when your form has errors. Shrine also provides a simple way to manipulate and post-process files in the background. And last but not least, the implementation of TUS protocol allow you to do multi-part uploads.</p>
<h1 id="what-does-the-future-have-in-store-for-them">What does the future have in store for them?</h1>
<p>It’s really interresting to see that <em>Thoughbot</em> (the Paperclip maintainers) just announced the deprecation of Paperclip in favor of ActiveStorage and it’s a good example of what makes the Ruby On Rails community so strong.</p>
<blockquote class="twitter-tweet" data-lang="fr"><p lang="en" dir="ltr">Major kudos to <a href="https://twitter.com/thoughtbot?ref_src=twsrc%5Etfw">@thoughtbot</a> for all the work on Paperclip over the years! It was one of the premiere file attachment solutions for Rails for a very long time. The work helped inform and inspire Active Storage 🙏❤️ <a href="https://t.co/DGoCDAZS0N">https://t.co/DGoCDAZS0N</a></p>— DHH (@dhh) <a href="https://twitter.com/dhh/status/996456644287414272?ref_src=twsrc%5Etfw">15 mai 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-lang="fr"><p lang="en" dir="ltr">We are deprecating Paperclip in favor of ActiveStorage. Learn what this means for you. <a href="https://t.co/b4MpPhKXaN">https://t.co/b4MpPhKXaN</a></p>— thoughtbot (@thoughtbot) <a href="https://twitter.com/thoughtbot/status/996001704377311232?ref_src=twsrc%5Etfw">14 mai 2018</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>We can be sure they’ll use their experience by contributing to ActiveStorage, as did <em>janko-m</em> (Shrine maintainer) who already improved S3 storage and <a href="https://github.com/rails/rails/pull/32471">implemented his own ImageProcessing gem</a> to ActiveStorage.</p>
<h1 id="how-we-handle-file-uploads-at-drivy">How we handle file uploads at Drivy</h1>
<p>At Drivy we handle file uploads in a similar way to ActiveStorage. We have our own DSL for models and a lot of methods for controllers and views. We also have some JavaScript for direct upload to our cloud storage. We don’t use post processing libraries for our images and we delegate all these tasks to a third party in order to reduce the impact on our CPUs.</p>
<p>So, should we move to ActiveStorage? We already have all the features we need and there’s currently no need to move to ActiveStorage. Sure it might reduce the maintenance cost and we could take advantage of security fixes and evolution with Rails upgrades, but with a 6-year-old codebase and thousands of attachments the migration would be huge!</p>
<p>In my opinion, ActiveStorage is a really good choice for a new project.</p>
<p>EDIT: <em>janko-m</em> (Shrine maintainer) commented this post <a href="https://www.reddit.com/r/ruby/comments/8k3qlm/rails_52_activestorage_highlight_drivy_engineering/dz5ocn1/">via reddit</a> and raised interesting points related to ActiveStorage and future changes for Shrine.</p>
<p>Rails 5.2 was released a few weeks ago and comes with awesome features like the new credentials vault, HTTP/2 early hints, Redis cache store and ActiveStorage, which I’m going to focus on in this blog post. The project was initiated by DHH in mid-2017 and has been merged into Rails core. It’s a built-in way to deal with uploads without extra dependencies like Paperclip, Carrierwave or Shrine.</p>
Thu, 17 May 2018 00:00:00 +0000
https://getaround.tech/rails-5.2-active-storage-highlight/
https://getaround.tech/rails-5.2-active-storage-highlight/Android Makers 2018 Key takeawaysRenaud Boulard<p>Android Makers is the largest Android event in France, organized by the <a href="http://www.paug.fr/">PAUYG</a> and <a href="http://www.bemyapp.com/">BeMyApp</a>. This year the event was held in Le Beffroi de Montrouge - what a great place to enjoy a conference. There was much more space than the previous Android Makers event: we were really comfortable, with all we needed to be fully focussed on the conferences.</p>
<p>Below are some key takeaways of these 2 days of great conferences.</p>
<h2 id="modern-android-development">Modern Android development</h2>
<p><a href="https://twitter.com/romainguy">Romain Guy</a> & <a href="https://twitter.com/chethaase">Chet Haase</a>, Google, <a href="https://www.youtube.com/watch?v=8GFLCL0u1lw&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=1&t=0s">Video</a></p>
<p>They gave us a big overview of the improvements of the last few years to Core Android development, from the view (ConstraintLayout) to the programming language (Kotlin) through the architecture, the tools and more.</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>Use <a href="https://developer.android.com/studio/debug/layout-inspector">Layout Inspector</a> instead of the deprecated <del>Hierarchy Viewer</del></li>
<li>Don’t use <del>AbsoluteLayout</del>, <del>GridLayout</del> and <del>RelativeLayout</del> any more. The best performances are provided by LinearLayout, FrameLayout and ConstraintLayout</li>
<li>Use SurfaceView not <del>TextureView</del></li>
<li>Handling life cycle should be much more simple than previously, with the help of Architecture Component</li>
<li>Architecture components are not necessarily the way you must build your app, it’s a recommended way, but you must build what matches your need</li>
<li>Google recommends the libraries such as <a href="https://bumptech.github.io/glide/">Glide</a>, <a href="http://square.github.io/picasso/">Picasso</a> and <a href="https://airbnb.design/lottie/">Lottie</a>, and they will never build equivalent tools in android SDK as they are already really good</li>
<li>Use systrace instead of <del>traceview</del> to profile your code, and the Android Profiler which containts a nice view including the touch events at the top (purple dot, the long dot are swipe event)
<img src="../assets/posts/2018-04-25-android-makers-2018/android-profiler.png" alt="Android Profiler" /></li>
<li>Remember that devices are resource-constrained, you must pay attention to, take care with the resources you use because there is a direct link to your user’s battery life</li>
</ul>
<h2 id="travelling-across-asia---our-journey-from-java-to-kotlin">Travelling across Asia - Our journey from Java to Kotlin</h2>
<p><a href="https://twitter.com/K4KYA">Amal Kakaiya</a>, Deliveroo, <a href="https://www.youtube.com/watch?v=ZaPIcI42Qtw&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=13&t=0s">Video</a></p>
<p>A talk about their switch from Java to Kotlin at Deliveroo</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>Start small, like the test or the data class, and then increase slightly the amount of Kotlin code in your project. Everyone in your team must be confident about using it, and not feel the need to rush.</li>
<li>Use comments on PRs, to learn the language from concrete examples in your own code base - it’s always better than a HelloWorld example</li>
<li>Android Studio is your friend: it offers you alternatives to improve your kotlin code</li>
<li>You can use Android studio converter, but it doesn’t always convert with the optimum results.</li>
<li>At Deliveroo they schedule a weekly meeting called <code class="language-plaintext highlighter-rouge">Kotlin Hour</code> where all the developers share what they have learned on Kotlin.</li>
<li>I learned the existence of the <a href="https://www.google.fr/maps/place/Kotlin+Island/@60.001918,29.6841889,13z/data=!3m1!4b1!4m5!3m4!1s0x4696454b3c730d79:0x84277e6a3fbe0093!8m2!3d60.0125003!4d29.7336138">Koltin Island</a> (An island in Russia) far far away from the <a href="https://www.google.fr/maps/place/Java,+Indon%C3%A9sie/data=!4m2!3m1!1s0x2e7aa07ed2323237:0x86fe1c59d6abed60?sa=X&ved=0ahUKEwjSlJfDxdjaAhWGcRQKHUXbB4sQ8gEI4AEwEA">Java Island</a></li>
</ul>
<h2 id="better-asynchronous-programming-with-kotlin-coroutines">Better asynchronous programming with Kotlin Coroutines</h2>
<p><a href="https://twitter.com/ErikHellman">Erik Hellman</a>, Hellsoft, <a href="https://speakerdeck.com/erikhellman/better-async-with-kotlin-coroutines">Slides</a>, <a href="https://www.youtube.com/watch?v=Bb4CVrf5al4&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=11&t=0s">Video</a></p>
<p>Good starting points if you have never used <a href="https://kotlinlang.org/docs/reference/coroutines.html">Coroutine</a>, Erik told us about coroutines, and how we can use them to end up with simple code looking like the excerpt below for async calls:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">load</span> <span class="o">{</span>
<span class="n">loadTweets</span><span class="o">(</span><span class="s">"#AndroidMakers"</span><span class="o">)</span>
<span class="o">}</span> <span class="n">then</span> <span class="o">{</span>
<span class="n">showTweets</span><span class="o">(</span><span class="n">it</span><span class="o">)</span>
<span class="o">}</span></code></pre></figure>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>Channel: basically a queue, it’s a way of passing multiple values to a coroutine</li>
<li>The coroutine doesn’t replace RxJava they can work together</li>
<li><a href="https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter">Retrofit</a> has a coroutine adapter</li>
<li>Coroutine is still in an experimental phase</li>
</ul>
<h2 id="design-tools-and-handoffs">Design Tools and Handoffs</h2>
<p><img src="../assets/posts/2018-04-25-android-makers-2018/taylor.jpg" alt="Design Tools and Handoffs" /></p>
<p><a href="https://twitter.com/taylorling">Taylor Ling</a>, Fabulous, <a href="https://speakerdeck.com/taylorling/design-tools-closing-the-gap-between-designer-and-developer">Slides</a>, <a href="https://www.youtube.com/watch?v=_yIsiEiBk5s&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=6&t=0s">Video</a></p>
<p>During his talk Taylor answered a really good question <code class="language-plaintext highlighter-rouge">What should a developer expect from a designer?</code></p>
<p>He divides his answer into 5 different parts:</p>
<ul>
<li><strong>Visual Design</strong>
<ul>
<li>Always design on Small Screen (recommended size 640x360, Nexus 5) because this is where you have all the constraints</li>
<li>Use consistent design across all of your app</li>
</ul>
</li>
<li><strong>Design Assets</strong>
<ul>
<li>Proper size, proper format assets (SVG, PNG, JPG), and optimized assets to avoid increasing your apk size. New <a href="https://developer.android.com/guide/app-bundle/">App Bundle</a> will also help a lot for this point)</li>
</ul>
</li>
<li><strong>Design Specification</strong>
<ul>
<li>To avoid wasting both your designer’s and developer’s time, use tools like <a href="https://zeplin.io/">Zeplin</a> to easily communicate the complete design specification (The new <a href="https://material.io/tools/gallery/">Gallery</a> tools would be a good alternative)</li>
</ul>
</li>
<li><strong>Interaction Design</strong>
<ul>
<li>Provided not only screen but also screen flow diagram</li>
<li>Animation should be defined with high quality video and even animation specification (delay, scale, duration etc…) and a timeline</li>
<li>Define the right interpolator depending on the animation purpose (<code class="language-plaintext highlighter-rouge">FastOutSlowIn</code>, LinearOutSlow`, etc… )</li>
</ul>
</li>
<li><strong>Animation Design</strong>
<ul>
<li>Today <a href="https://airbnb.design/lottie/">Lottie</a> and <a href="https://www.adobe.com/products/aftereffects.html">After effect</a> are the best ways to easily add complex animation to add to a mobile application</li>
<li>For simple icon transition you could also use <a href="https://shapeshifter.design/">Shape Shifter</a></li>
</ul>
</li>
</ul>
<p>He turned the question the other way around <code class="language-plaintext highlighter-rouge">What should a designer expect from a developper</code></p>
<ul>
<li>Communication, ensure we all speak the same language (dp, sp, FAB, Snackar, etc…)</li>
<li>Explain to them what the constraints are, what could be implemented, and what could be not, and moreover suggest alternative solutions</li>
<li>Understand the business goal of design</li>
</ul>
<h2 id="themes-style--more-from-zero-to-hero">Themes, style & more: from zero to hero</h2>
<p><a href="https://twitter.com/cyrilmottier">Cyril Mottier</a>, Zenly, <a href="https://speakerdeck.com/cyrilmottier/theme-styles-and-more-from-zero-to-here">Slides</a>, <a href="https://www.youtube.com/watch?v=q_LJG4VuU3Q&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=7&t=0s">Video</a></p>
<p>Overall a really great overview of theme, style, text appereance and more. He explained how you should write them and organize them in your project. If you don’t feel confident with this part of the Android sdk, I definitely recommend that you watch this presentation.</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>How to properly handle themes regarding API version, to avoid re-writing them for every version in addition to the new attributes for each new version</li>
</ul>
<p><img src="../assets/posts/2018-04-25-android-makers-2018/theme.png" alt="Theme API version" /></p>
<h2 id="remote-control-your-application">Remote-control your application</h2>
<p><a href="https://twitter.com/stan_kocken">Stan Kocken</a>, Dashlane, <a href="https://speakerdeck.com/stankocken/remote-control-your-app">Slides</a></p>
<p>Stan gave us his feedback on how they manage remote configuration of their Dashlane app, without need of a new release on the store. He told us about five main concepts:</p>
<ul>
<li><strong>Web View</strong>: Load web content in your app
<ul>
<li>Pros: It allows you to update the entire screen</li>
<li>Cons: Bad user experience. This is ok for small parts of your app like the help center</li>
</ul>
</li>
<li><strong>Remote Screen config</strong>: Send screen content with json (text, icon)
<ul>
<li>Pros: Good user experience</li>
<li>Cons: Limited possibility for updating the layout</li>
</ul>
</li>
<li><strong>Remote Copy</strong>: Change the strings.xml by a remotely downloaded json file to easily update the wording with a Custom Layout inflator to manage string in .xml file
<ul>
<li>Pros: Fast wording updates in your app</li>
<li>Cons: Hard to manage multi-language, plurals and text formating</li>
</ul>
</li>
<li><strong>A/B test</strong>: Compare two experiences to choose the best
<ul>
<li>Comparing local and server-side A/B test</li>
<li>Server-side is more appropriate for remotely updating the A/B tests</li>
</ul>
</li>
<li><strong>Feature Flip</strong>: Remotely enable or disable features in your app
<ul>
<li>At Dashlane they distinguish between <em>App Release</em> and <em>Feature Release</em>. They release their app every 2 weeks, no matter the state of the feature, and then enable the feature when it’s ready</li>
<li>Good question from the audience, you can no longer use the new release note feature from the Playstore with this workflow</li>
</ul>
</li>
</ul>
<h2 id="adb-break-on-though-to-the-other-side">ADB, Break On though To the Other Side</h2>
<p><a href="https://twitter.com/Eyal_Lezmy">Eyal Lezmy</a>, Quonto, <a href="https://bit.ly/adb-chill">Slides</a></p>
<p>As I’m not a fan of “Magic programming”, it’s always interesting to understand how the tools you use work. This presentation about ADB was an in-depth understanding of how ADB works under the hood.</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>ADB is split in 3 parts:
<ul>
<li>adb client (<code class="language-plaintext highlighter-rouge">adb</code> command)</li>
<li>adb server (Multiplexer)</li>
<li>adb deamon (Phone)</li>
</ul>
</li>
<li>Android Studio does not use <em>abd</em> but <em>ddmlib</em>(a jar that provides APIs for talking with Dalvik VM, both on Android phones and emulators)</li>
<li>I learned about the existence of the <a href="https://www.androidauthority.com/google-sooner-first-android-reference-device-83468/">Google Sooner</a> first ever Android phone, which has never been shown to the public.</li>
</ul>
<h2 id="typesetting-desiging-and-building-beautiful-text-layout">Typesetting: desiging and building beautiful text layout</h2>
<p><a href="https://twitter.com/FMuntenescu?lang=en">Florina Muntenescu</a> & <a href="https://twitter.com/crafty?lang=en">Nick Butcher</a>, Google
<img src="../assets/posts/2018-04-25-android-makers-2018/typesetting.jpg" alt="Typesetting" />, <a href="https://www.youtube.com/watch?v=ccEMHdTc0ZM&list=PLn7H9CUCuXAus0YgFW7QiIpnwHJd3Yinw&index=3&t=0s">Video</a></p>
<p>Big overview of all the Typesetting you can include in your app</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>Android P will add baseline param in TextView</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="o"><</span><span class="nc">TextView</span>
<span class="nl">android:</span><span class="n">firstBaselineToTopHeight</span><span class="o">=</span><span class="s">"24dp"</span>
<span class="nl">android:</span><span class="n">lastBaselineToBottomHeight</span><span class="o">=</span><span class="s">"24dp"</span>
<span class="o">.../></span></code></pre></figure>
<p><a href="https://developer.android.com/reference/android/widget/TextView.html#androidfirstbaselinetotopheight">android:firstBaselineToTopHeight</a>: Distance from the top of the TextView to the first text baseline. If set, this overrides the value set for <code class="language-plaintext highlighter-rouge">paddingTop</code>.
<a href="https://developer.android.com/reference/android/widget/TextView.html#androidlastbaselinetobottomheight">android:lastBaselineToBottomHeight</a>: Distance from the bottom of the TextView to the last text baseline. If set, this overrides the value set for <code class="language-plaintext highlighter-rouge">paddingBottom</code>.</p>
<ul>
<li>I learnt about the existence of the <a href="https://developer.android.com/reference/android/widget/TextView.html#attr_android:breakStrategy">android:breakStrategy</a> param: Break strategy (control over paragraph layout).</li>
<li>If you have a long piece of text don’t use a single TextView but use a subset of paragraph that you must put in a recyclerView, doing it this way will give you all the benefits of the RecyclerView when you scroll.</li>
</ul>
<h2 id="gradle-in-android-studio-32-33-and-beyond">Gradle in Android Studio <del>3.2</del> 3.3 and beyond</h2>
<p>Bradley Smith & Lucas Smaira, Google</p>
<p>This was a good overview of how Gradle and Android Studio work together. They highlighted the pain points and explained to us why it’s slow sometimes.</p>
<p><strong>Key takeaways</strong> (new upcoming features):</p>
<ul>
<li>Single Variant Sync (Sync only the selected variant, should be available in AS 3.3-canary)</li>
<li>PSD (Project structure Dialog) new graphical interface with nice suggestions, dependency graphs and more to help you manage your gradle file</li>
</ul>
<p><img src="../assets/posts/2018-04-25-android-makers-2018/gradle.jpg" alt="Project structure Dialog" /></p>
<p>What’s next (Utlimate goal):</p>
<ul>
<li>Running Android test through Gradle</li>
<li>Remove need for manual Sync of Gradle</li>
</ul>
<h2 id="tools-of-the-trade">Tools of the Trade</h2>
<p><a href="https://twitter.com/tsmith">Ty Smith</a>, Uber</p>
<p>He gave us a big overview of how the Android team work at Uber, and the tools and architectures they use.</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li>The Uber app is based on a MonoRepository mainly to avoid dependency issues <a href="https://eng.uber.com/android-monorepo/">Full article</a></li>
<li>They introduced a system called Submit Queue which rebases changes on master and runs a customizable set of tests before merging them to avoid broken master</li>
<li>They use <a href="https://phacility.com/phabricator/">Phacility./Phabricator</a> as a main tools</li>
<li>They use <a href="https://buckbuild.com/">Buck</a> and the <a href="https://buckbuild.com/article/exopackage.html">Exopackage</a>: from Ty word <code class="language-plaintext highlighter-rouge">It's like instantApp but it's works :)</code></li>
<li>They have built a demo application with all the design components available, to keep a consistent design across the app
<img src="../assets/posts/2018-04-25-android-makers-2018/uber.jpg" alt="Project structure Dialog" /></li>
</ul>
<p>It’s quite impressive to see how many open-source libraries Uber have shared with the Android community:</p>
<ul>
<li><a href="https://github.com/uber/okbuck">OkBuck</a>: OkBuck is a gradle plugin that lets developers utilize the Buck build system on a gradle project.</li>
<li><a href="https://github.com/uber/ribs">RIBs</a>: Uber’s cross-platform mobile architecture framework.</li>
<li><a href="https://github.com/uber/crumb">Crumb</a>: An annotation processor for breadcrumbing metadata across compilation boundaries.</li>
<li><a href="https://github.com/uber/autodispose">AutoDispose</a>: Automatic binding+disposal of RxJava 2 streams.</li>
<li><a href="https://github.com/uber/artist">Artist</a>: An artist creates views. Artist is a Gradle plugin that codegens a base set of Android Views.</li>
<li><a href="https://github.com/uber/NullAway">NullAway</a>: A tool to help eliminate NullPointerExceptions (NPEs) in your Java code with low build-time overhead</li>
<li><a href="https://github.com/uber-common/rave">RAVE</a>: A data model validation framework that uses java annotation processing.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>This year Android Makers regroups 816 people, including 82 speakers and it was organized by 34 organizers. Thanks to them and all the sponsors who make this event possible! We will definitely come back next year. You can already buy your ticket for <a href="http://bit.ly/am-19">Android Makers 2019</a>!</p>
<p>As always, it’s good to meet the android community and learn from other developers. This year the event became more international - I only saw one talk in French, all the other talks were in English. It was great to see that we have a conference in France that is attractive for the Android community!</p>
<p>See you next year!</p>
<p>Android Makers is the largest Android event in France, organized by the <a href="http://www.paug.fr/">PAUYG</a> and <a href="http://www.bemyapp.com/">BeMyApp</a>. This year the event was held in Le Beffroi de Montrouge - what a great place to enjoy a conference. There was much more space than the previous Android Makers event: we were really comfortable, with all we needed to be fully focussed on the conferences.</p>
Thu, 17 May 2018 00:00:00 +0000
https://getaround.tech/android-makers-2018/
https://getaround.tech/android-makers-2018/Ensuring consistent spacing in your UITim Petricola<p>Drivy is growing, and the impact of this is particularly reflected in the evolution of our visual identity, conveyed by Drivy’s UI.</p>
<p>Having more and more people involved in new features (product managers, copywriters, designers, developers, …) means having more UI updates on the website. To make things easier, we’ve recently started working on Cobalt, Drivy’s internal design system.</p>
<p>Design systems are a broad topic. This post will only focus on dealing with whitespace across the website.</p>
<h1 id="the-problem">The problem</h1>
<p>In the past, we didn’t have a process for deciding on what value we should use for a given whitespace. Sometimes, a designer would choose a specific value or the developer might decide to take the time to make it pixel perfect. Other times it would just be a question of getting a feeling for “what looked right” during implementation. And then we’d end up with feedback such as “try adding 1 or 2 pixels there”.</p>
<p>This would lead to two main frustrations:</p>
<ul>
<li>developers and designers would spend time searching for <em>the perfect value™</em></li>
<li>visual approximations and inconsistency would appear across pages</li>
</ul>
<h1 id="choosing-values">Choosing values</h1>
<p>We knew we wanted to establish a fixed set of values that we could use for all whitespaces. But how to choose them?</p>
<p>We experimented with some of our components (a car card, new landing pages, our new booking page etc.) to see what could work. This also meant playing with typography to find the proper line-heights for every font style. In doing so, we ended up with a 4px baseline.</p>
<p>We then extracted the main values from this set of experimental components. When it made sense to do so, we homogenized and merged some of the values to end up with a reduced set. Several components and a bit of tweaking later, we now use this set of values:</p>
<p><img src="../assets/posts/2018-04-19-design-system-spacings/spacings.png" alt="spacing values" /></p>
<h1 id="enforcing-it">Enforcing it</h1>
<p>Having a theoretical set of values is great. But how do you make sure that they will be used?</p>
<p>This is not an issue for our UI team as they are at the core of the design-system project and know what values to use. But what about developers who had been less involved? And, is it now necessary for them to know the specific value of a given spacing as they implement it?</p>
<p>We don’t think so.</p>
<p>We got inspired by what Shopify are doing with <a href="https://polaris.shopify.com/">Polaris</a> and ended up writing a custom Scss function. Whenever someone needs to add some spacing, they can write the following:</p>
<figure class="highlight"><pre><code class="language-scss" data-lang="scss"><span class="nc">.component</span> <span class="p">{</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="nf">spacing</span><span class="p">(</span><span class="n">tight</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>
<p>And the implementation is quite simple:</p>
<figure class="highlight"><pre><code class="language-scss" data-lang="scss"><span class="nv">$unit</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
<span class="nv">$spacing-data</span><span class="p">:</span> <span class="p">(</span>
<span class="nb">none</span><span class="o">:</span> <span class="m">0</span><span class="o">,</span>
<span class="n">unit</span><span class="o">:</span> <span class="nv">$unit</span><span class="o">,</span>
<span class="n">extra-tight</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">2</span><span class="o">,</span>
<span class="n">tight</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">4</span><span class="o">,</span>
<span class="n">base</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">6</span><span class="o">,</span>
<span class="nb">medium</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">8</span><span class="o">,</span>
<span class="n">loose</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">12</span><span class="o">,</span>
<span class="n">extra-loose</span><span class="o">:</span> <span class="nv">$unit</span> <span class="o">*</span> <span class="m">16</span>
<span class="p">);</span>
<span class="k">@function</span> <span class="nf">spacing</span><span class="p">(</span><span class="nv">$variant</span><span class="o">:</span> <span class="n">base</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$value</span><span class="p">:</span> <span class="nf">map-get</span><span class="p">(</span><span class="nv">$spacing-data</span><span class="o">,</span> <span class="nv">$variant</span><span class="p">);</span>
<span class="k">@if</span> <span class="nv">$value</span> <span class="o">==</span> <span class="n">null</span> <span class="p">{</span>
<span class="k">@error</span> <span class="s2">"Spacing variant `</span><span class="si">#{</span><span class="nv">$variant</span><span class="si">}</span><span class="s2">` not found."</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@return</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h1 id="dealing-with-edge-cases">Dealing with edge cases</h1>
<h2 id="components-with-a-border">Components with a border</h2>
<p>But what if a component has a bottom border of <code class="language-plaintext highlighter-rouge">1px</code>, setting the whole baseline off? There are various solutions to this issue, such as dropping borders in favor of box shadows. But our approach is quite simple: compensate by removing some spacing.</p>
<figure class="highlight"><pre><code class="language-scss" data-lang="scss"><span class="nc">.component</span> <span class="p">{</span>
<span class="nv">$border-width</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
<span class="nl">padding-bottom</span><span class="p">:</span> <span class="nf">spacing</span><span class="p">(</span><span class="n">tight</span><span class="p">)</span> <span class="o">-</span> <span class="nv">$border-width</span><span class="p">;</span>
<span class="nl">border-bottom</span><span class="p">:</span> <span class="nv">$border-width</span> <span class="nb">solid</span> <span class="mh">#fff</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<h2 id="component-with-user-uploaded-media">Component with user uploaded media</h2>
<p>We have to accept that the baseline will not be respected. In our case here at Drivy, this occurs when we display pictures uploaded by users, where we want to respect the ratio.</p>
<figure>
<img alt="Car Card" src="/assets/posts/2018-04-19-design-system-spacings/car-card.png" />
<figcaption>
The image has a height of 222px, which does not conform to the base 4 rule.
</figcaption>
</figure>
<h1 id="conclusion">Conclusion</h1>
<p>Our design system is still young and we have a lot of things to improve on and decide about. In the same way that we chose spacing values, we also have conventions for typography and colors.</p>
<p>Seeing as we have a lot of pages in our application, it’s not feasible to migrate the whole site to Cobalt at once. But we’re very excited to slowly roll out our new system throughout the application!</p>
<p>Drivy is growing, and the impact of this is particularly reflected in the evolution of our visual identity, conveyed by Drivy’s UI.</p>
Thu, 19 Apr 2018 00:00:00 +0000
https://getaround.tech/design-system-spacings/
https://getaround.tech/design-system-spacings/Running Our First Internal Hack DayEmily Fiennes & Marc G Gauthier<p>Trying out new technologies, exploring new ideas, investigating potential solutions: these are all fundamental parts of the problem-solving element of a developer’s work. As with any form of creative work, it’s important to keep motivated and one step ahead of ever-changing technological advancements. This encourages a fresh approach, and above all, boosts the enjoyment we take from our work.</p>
<p>That’s why we’ve started holding regular hack days here at Drivy. Every few months, the tech, product and data teams can get together and pool their skills, experience and ideas. The brief? To plan and implement an Drivy-related MVP that could improve the product, but which you wouldn’t usually get the chance to explore or implement in your day-to-day work.</p>
<h1 id="format">Format</h1>
<p>We felt strongly that, if our hack days were to be a sustainable project, it was important to get the format right. This meant setting aside a specific day within our normal working week. Many team members with commitments outside of work wouldn’t have been able to show up, had we held it at the weekend. Furthermore, the direct benefits of the exercise - team cohesion, innovation, boosting motivation - could ultimately be invested back into our productivity and creativity in the long run. </p>
<h2 id="keeping-focus">Keeping focus</h2>
<p>We decided to venture out of Drivy HQ, so as to focus purely on coding and avoid any unwanted interruptions. This made a nice change of pace from what can be a fast-moving and sometimes intense working environment. Plus, the change of scene made it feel different to a usual working day, and got those creative juices flowing.</p>
<p>Because that’s exactly what it was - another working day, just not at the office.</p>
<p><img src="../assets/posts/hack-day/offsite.jpg" alt="Working in a different setup" /></p>
<p>But there was one problem: we didn’t want to hold the hack day at the weekend, but we also couldn’t desert the office en masse and have no one available to respond to potential production issues either. We thus split the team, with each half hacking on different days, one week apart. Sure, it might have been better if the entire team had been present but this was a good middle ground that actually afforded a certain intimacy and didn’t disturb the usual operations of the website.</p>
<p>One ground rule was that all hackers would quit Slack. If something really critical happened in production, we would revert to a good old-fashioned phone call and a developer would stop hacking in order to address the issue. Thankfully it didn’t come to that, but it’s always reassuring to have a back-up plan.</p>
<h2 id="teams-and-projects">Teams and projects</h2>
<p>We decided on teams and projects beforehand, so were able to get straight down to business and fully capitalise on the 1-day timeframe.</p>
<p><img src="../assets/posts/hack-day/ideas.jpeg" alt="Hack day ideas" /></p>
<p>Starting the discussion early also gave us plenty of time to improve our ideas and debate the most interesting, and most feasible, projects to pursue.</p>
<h2 id="quality-time">Quality time</h2>
<p>Some members of our tech team work remotely, but they all came to Paris for the hack day. This was a great chance for everyone to collaborate with people they might not otherwise get to work with. There’s also something to be said for the team-building potential of a hack day in a wider sense. For one day, we collectively ventured outside of our zones of comfort, in terms of the projects we chose, the speed of delivery and the technologies used.</p>
<p>In that context, trust and communication between teammates becomes key to decision-making, and ultimately the experience can make for stronger, more cohesive teams. As the Drivy team continues to grow, and collaboration becomes more squad-oriented, team cohesion and communication will be increasingly important.</p>
<h2 id="project-time">Project time!</h2>
<p>With 17 developers, there were many projects. Some warrant a blog post in their own right, but in the meantime here is a quick overview:</p>
<ul>
<li>Rebuild the search using ReactJS, Flux and styled components</li>
<li>Integrate Google Assistant to book cars using Google Home</li>
<li>Pay on Android using credit cards NFC <a href="https://en.wikipedia.org/wiki/EMV">capabilities</a></li>
<li>Build a Slack chatbot to book meeting rooms</li>
<li>Search and book cars with Alexa</li>
<li>Detect licence plates and more based on user-uploaded pictures</li>
<li>Try out a new way of doing caching</li>
<li>Use <a href="../assets/posts/hack-day/machine-learning.jpg">machine learning</a> to better understand user behaviour with our new Instant Booking feature</li>
<li>Use <a href="https://developer.apple.com/arkit/">iOS 11’s ARKit</a> to locate Drivy cars in the street</li>
<li>Add a chat system to help users encountering problems</li>
<li>Reliably send SMS using <a href="https://wammu.eu/gammu/">Gammu</a>, a sim card and a Raspberry Pi</li>
</ul>
<p>Here’s a bonus extract of the demo day presentation for the Alexa project by <a href="https://github.com/watsonbox">Howard</a>.</p>
<center><iframe width="560" height="315" src="https://www.youtube.com/embed/V5i7RreIxqc?rel=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe></center>
<h2 id="crunch-time">Crunch time</h2>
<p>Once the two hack days were completed, we held a Demo Day together back at Drivy HQ. It lasted for an hour, where each group had the opportunity to present their objectives, work process and results.</p>
<p>We recorded the demo and shared it with the rest of the company, as many people were curious to learn about what we built. Interest had been sparked by the endless potential for future developments to the product that could be imagined, and it was cool to transcend any tech/non-tech frontiers in this way. Plus the positive feedback further proved the real sense of ownership and investment that all team members have here at Drivy.</p>
<p><img src="../assets/posts/hack-day/demo.jpg" alt="Hack day demo at Drivy" /></p>
<p>With our first hack day successfully under our belt, we’re already eagerly anticipating the next. The day was a valuable opportunity to collaborate with different teammates, to explore novel ideas in a new context and to disrupt our usual working practices. After all, it’s through moving fast and breaking a few things along the way that you can come up with novel solutions to problems you maybe didn’t even know existed.</p>
<p>Trying out new technologies, exploring new ideas, investigating potential solutions: these are all fundamental parts of the problem-solving element of a developer’s work. As with any form of creative work, it’s important to keep motivated and one step ahead of ever-changing technological advancements. This encourages a fresh approach, and above all, boosts the enjoyment we take from our work.</p>
Tue, 03 Apr 2018 00:00:00 +0000
https://getaround.tech/drivy-hack-day/
https://getaround.tech/drivy-hack-day/Redshift tips and tricks - part 1Faouz EL FASSI<p>At Drivy we have massively been using <a href="https://aws.amazon.com/fr/redshift/">Redshift</a> as our data warehouse since mid-2015, we store in it all our transformations and aggregations of our production database and 3rd-party data (spreadsheets, csv, APIs and so on).
In this first blog post, we will discuss how we adapted our Redshift configuration and architecture as our usages changed over time.</p>
<p>This article targets a technical audience designing or maintaining Redshift data warehouses: architects, database developers or data engineers.
It will aim to provide a simple overview, with a mix of tips to help you scale your Redshift cluster.</p>
<p>To recap, Amazon Redshift is a fully managed, petabyte-scale data warehouse deployed on AWS. It is based on PostgreSQL 8.0.2, uses <a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS">columnar storage</a> and massively parallel processing.
It also has a very good query plan optimizer and strong compression capabilities.</p>
<figure>
<img alt="Redshift architecture" src="/assets/posts/redshift/architecture.png" />
<figcaption>
Overview of Redshift's architecture. Source: https://docs.aws.amazon.com
</figcaption>
</figure>
<p>In this first blog post, we will cover the following topics:</p>
<ul>
<li>how engineers must adapt the default-queue management strategy, called workload management (WLM) to fit their needs;</li>
<li>how to tweak Redshift’ distribution and sorting styles in order to tune table design for improving queries performance, which is crucial for large tables (> ~100M rows).</li>
</ul>
<h2 id="usage-at-drivy">Usage at Drivy</h2>
<p>The big picture is that we have different usages with different SLA levels: from fast-running queries that must be highly available (near real-time reporting for fraud) to long-running batch jobs (e.g: propagating an ID on all the tracking records for all the sessions of all the users across all their devices 😅).</p>
<p>Prior to recent changes, Redshift was subject to roughly 50K requests per day:</p>
<ul>
<li>~70% were ETL jobs and visualizations jobs, having a high reliability and availability requirement and various execution times [1min, 60min];</li>
<li>~10% were short running queries (< 15min) written by analysts, having no specific SLA;</li>
<li>~20% were very short queries (< 1min), metrics, health and stats (internals of Redshift).</li>
</ul>
<p>Since a few months ago our usages have slightly changed as more analysts came and a new set of exploratory tools is being used.</p>
<p>We’ve decided to deploy <a href="https://www.tableau.com/">Tableau</a> to all project managers and analysts to improve agility in data-driven decision making. They have started using it with their own credentials to ingest data from Redshift to Tableau.</p>
<p>It resulted in multiplying the concurrent connections to Redshift by two, and a high load on the queue dedicated to analysts, neither fitting the current WLM strategy, therefore breaking our SLAs.</p>
<p>We identified a few levers.</p>
<ol>
<li>Design a better WLM strategy and monitor it thoroughly.</li>
<li>Improve our schema design:
<ul>
<li>create pre-processing ETL pipelines for the frequent extractions that do a lot of aggregations and computations which are responsible for memory issues;</li>
<li>reduce redistribution among worker nodes of the Redshift cluster for frequent computations with high cardinality;</li>
<li>leverage AWS S3 if it is a simple extraction of large tables (relocate the data source).</li>
</ul>
</li>
</ol>
<h2 id="concurrency-issues">Concurrency issues</h2>
<p>Initially we had the following workload management strategy, in addition to the Short Query Acceleration queue set at a maximal timeout of 6 seconds:</p>
<table>
<thead>
<tr>
<th>queue</th>
<th>concurrency</th>
<th>timeout</th>
<th>RAM</th>
</tr>
</thead>
<tbody>
<tr>
<td>etl</td>
<td>5</td>
<td>∞</td>
<td>50%</td>
</tr>
<tr>
<td>visualisations</td>
<td>5</td>
<td>900s</td>
<td>20%</td>
</tr>
<tr>
<td>analysts</td>
<td>3</td>
<td>900s</td>
<td>20%</td>
</tr>
<tr>
<td>default</td>
<td>2</td>
<td>1800s</td>
<td>10%</td>
</tr>
</tbody>
</table>
<p>When enabled, Redshift uses machine learning to predict short running queries and affect them to this queue, so there is no need to define and manage a queue dedicated to short running queries, <a href="https://docs.aws.amazon.com/redshift/latest/dg/wlm-short-query-acceleration.html">for more info</a>.</p>
<p>To face the limitations introduced by the use of Tableau through the credentials of the analysts, we’ve created a dedicated Redshift user group called <code class="language-plaintext highlighter-rouge">exploration</code> where we’ve added the Tableau user, using the same Redshift queue as the <code class="language-plaintext highlighter-rouge">etl</code> and slightly changed the timeout of the other ones to the following configuration:</p>
<table>
<thead>
<tr>
<th>queue</th>
<th>concurrency</th>
<th>timeout</th>
<th>RAM</th>
</tr>
</thead>
<tbody>
<tr>
<td>etl, exploration</td>
<td>5</td>
<td>∞</td>
<td>50%</td>
</tr>
<tr>
<td>visualisations</td>
<td>4</td>
<td>1500s</td>
<td>20%</td>
</tr>
<tr>
<td>analysts</td>
<td>4</td>
<td>1800s</td>
<td>20%</td>
</tr>
<tr>
<td>default</td>
<td>2</td>
<td>3600s</td>
<td>10%</td>
</tr>
</tbody>
</table>
<p>We kept the SQA queue and increased its timeout to 20s. This avoids short queries getting stuck behind the long-running ones in the <code class="language-plaintext highlighter-rouge">visualisations</code>, <code class="language-plaintext highlighter-rouge">analysts</code> and <code class="language-plaintext highlighter-rouge">default</code> queues.</p>
<p>This new configuration limited the high load on the <code class="language-plaintext highlighter-rouge">analysts</code> queue resulting in queries being queued and frequent out of memory issues, but added some lag on the ETL pipelines.</p>
<p>We wanted to monitor badly designed queries, and queries that are subject to a bad distribution of the underlying data, significantly impacting the queries execution time.
WLM gives us the possibility to define rules for logging, re-routing or aborting queries when specific conditions were met.</p>
<p>We decided to log all the queries that may contain errors, such as badly designed joins requiring a nested loop (cartesian product between two tables).</p>
<p>Here is an example of our current logging strategy:</p>
<figure>
<img alt="WLM rules" src="/assets/posts/redshift/wlm.png" />
<figcaption>
Logging rules for the etl & exploration queue
</figcaption>
</figure>
<p>When the rules are met, the query ID is logged in the <a href="https://docs.aws.amazon.com/redshift/latest/dg/r_STL_WLM_RULE_ACTION.html"><code class="language-plaintext highlighter-rouge">STL_WLM_RULE_ACTION</code></a> internal table.
Here is a view to locating the culprit: the query text, the user or system who ran it and the rule name that it is violating (defined in the WLM json configuration file).</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">CREATE</span> <span class="k">OR</span> <span class="k">REPLACE</span> <span class="k">VIEW</span> <span class="k">admin</span><span class="p">.</span><span class="n">v_wlm_rules_violations</span> <span class="k">AS</span>
<span class="k">SELECT</span>
<span class="k">distinct</span> <span class="n">usename</span><span class="p">,</span>
<span class="nv">"rule"</span><span class="p">,</span>
<span class="nv">"database"</span><span class="p">,</span>
<span class="n">querytxt</span><span class="p">,</span>
<span class="k">max</span><span class="p">(</span><span class="n">recordtime</span><span class="p">)</span> <span class="k">as</span> <span class="n">last_record_time</span>
<span class="k">FROM</span> <span class="n">STL_WLM_RULE_ACTION</span> <span class="n">w</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">STL_QUERY</span> <span class="n">q</span>
<span class="k">ON</span> <span class="n">q</span><span class="p">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="n">query</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">pg_user</span> <span class="n">u</span>
<span class="k">on</span> <span class="n">u</span><span class="p">.</span><span class="n">usesysid</span> <span class="o">=</span> <span class="n">q</span><span class="p">.</span><span class="n">userid</span>
<span class="k">group</span> <span class="k">by</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">;</span></code></pre></figure>
<p>Note that the query rules are executed in a bottom-up approach, if 3 rules are defined (log, hop and abort).
The query will be logged and then re-routed to the next available queue (⚠️ only for <code class="language-plaintext highlighter-rouge">SELECT</code> and <code class="language-plaintext highlighter-rouge">CREATE</code> statements) before being aborted.</p>
<p>Now that we have a suitable workload configuration and a few monitoring tools to log badly designed queries, let’s see how to improve query performances to shorten the ETL pipelines!</p>
<h2 id="schema-optimizations">Schema optimizations</h2>
<h3 id="one-of-the-most-important-aspect-of-a-columnar-storage-database-such-as-redshift-is-to-decrease-the-amount-of-redistribution-needed-to-perform-a-specific-task">One of the most important aspect of a columnar storage database such as Redshift is to decrease the amount of redistribution needed to perform a specific task.</h3>
<p>The only way of approximating it is to define the correct combination of distribution and sort keys.</p>
<p>Here is a recipe for choosing the best sort keys, adapted from AWS documentation:</p>
<ul>
<li>if recent data is queried most frequently, specify the timestamp column as the leading column for the sort key;</li>
<li>if you do frequent range filtering or equality filtering on one column, specify that column as the sort key;</li>
<li>if you frequently join a (dimension) table, specify the join column as the sort key;</li>
<li>if one of your fact tables has more than ~100M rows and has many dimensions, use an <code class="language-plaintext highlighter-rouge">interleaved</code> distribution style.</li>
</ul>
<p>And, for distribution keys:</p>
<ul>
<li>distribute the fact table and one dimension table on their common columns;</li>
<li>choose the largest dimension based on the size of the filtered data set;</li>
<li>choose a column with high cardinality in the filtered result set;</li>
<li>change some dimension tables to use ALL distribution (copy the whole table to all compute nodes).</li>
</ul>
<p>The <code class="language-plaintext highlighter-rouge">explain</code> command gives us the opportunity to test different distribution styles by measuring the query cost.</p>
<p>To summarize, using <code class="language-plaintext highlighter-rouge">explain</code> it’s really important to follow certain points.</p>
<ol>
<li>Avoid <strong>NESTED LOOP</strong> in all your queries.</li>
<li>Limit <strong>HASH JOINS</strong>: by defining the join condition as distribution <strong>and</strong> sorting key it will be transformed to a <strong>MERGE JOIN</strong> <strong>-> fastest join style</strong>.</li>
<li>Maximize <strong>DB_DIST_NONE</strong> in your long-running queries: this means that the records are collocated on the same node, thus no redistribution is needed.</li>
</ol>
<p>You should also be careful regarding the skew ratio across slices of your worker nodes if you have an <code class="language-plaintext highlighter-rouge">interleaved</code> sort distribution style, if the data is evenly distributed the load is split evenly across slices of each worker.</p>
<figure>
<img alt="Skew ratios source: aws.amazon.com" src="/assets/posts/redshift/skew.gif" />
<figcaption>
Skewed distribution resulting in starvation, computations are as long as the slowest slice (containing most of the data). Source: https://aws.amazon.com/fr/blogs/big-data/top-10-performance-tuning-techniques-for-amazon-redshift/
</figcaption>
</figure>
<p><strong>Bonus tip:</strong> this view gives you a full overview of all the tables in your database and it gives, the following information on each table:</p>
<ul>
<li>its size in MB;</li>
<li>if it has a distribution key;</li>
<li>if it has a sortkey;</li>
<li>its skew ratio.</li>
</ul>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">CREATE</span> <span class="k">OR</span> <span class="k">REPLACE</span> <span class="k">VIEW</span> <span class="k">admin</span><span class="p">.</span><span class="n">v_tables_infos</span> <span class="k">as</span>
<span class="k">SELECT</span>
<span class="k">SCHEMA</span> <span class="n">schemaname</span><span class="p">,</span>
<span class="nv">"table"</span> <span class="n">tablename</span><span class="p">,</span>
<span class="n">table_id</span> <span class="n">tableid</span><span class="p">,</span>
<span class="k">size</span> <span class="n">size_in_mb</span><span class="p">,</span>
<span class="k">CASE</span>
<span class="k">WHEN</span> <span class="n">diststyle</span> <span class="k">NOT</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'EVEN'</span><span class="p">,</span><span class="s1">'ALL'</span><span class="p">)</span> <span class="k">THEN</span> <span class="mi">1</span>
<span class="k">ELSE</span> <span class="mi">0</span>
<span class="k">END</span> <span class="n">has_dist_key</span><span class="p">,</span>
<span class="k">CASE</span>
<span class="k">WHEN</span> <span class="n">sortkey1</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">THEN</span> <span class="mi">1</span>
<span class="k">ELSE</span> <span class="mi">0</span>
<span class="k">END</span> <span class="n">has_sort_key</span><span class="p">,</span>
<span class="k">CASE</span>
<span class="k">WHEN</span> <span class="n">encoded</span> <span class="o">=</span> <span class="s1">'Y'</span> <span class="k">THEN</span> <span class="mi">1</span>
<span class="k">ELSE</span> <span class="mi">0</span>
<span class="k">END</span> <span class="n">has_col_encoding</span><span class="p">,</span>
<span class="k">CAST</span><span class="p">(</span><span class="n">max_blocks_per_slice</span> <span class="o">-</span> <span class="n">min_blocks_per_slice</span> <span class="k">AS</span> <span class="nb">FLOAT</span><span class="p">)</span> <span class="o">/</span> <span class="n">GREATEST</span><span class="p">(</span><span class="n">NVL</span> <span class="p">(</span><span class="n">min_blocks_per_slice</span><span class="p">,</span><span class="mi">0</span><span class="p">)::</span><span class="nb">int</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span> <span class="n">ratio_skew_across_slices</span><span class="p">,</span>
<span class="k">CAST</span><span class="p">(</span><span class="mi">100</span><span class="o">*</span><span class="n">dist_slice</span> <span class="k">AS</span> <span class="nb">FLOAT</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="k">SELECT</span> <span class="k">COUNT</span><span class="p">(</span><span class="k">DISTINCT</span> <span class="n">slice</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">stv_slices</span><span class="p">)</span> <span class="n">pct_slices_populated</span>
<span class="k">FROM</span> <span class="n">svv_table_info</span> <span class="n">ti</span>
<span class="k">JOIN</span> <span class="p">(</span>
<span class="k">SELECT</span>
<span class="n">tbl</span><span class="p">,</span>
<span class="k">MIN</span><span class="p">(</span><span class="k">c</span><span class="p">)</span> <span class="n">min_blocks_per_slice</span><span class="p">,</span>
<span class="k">MAX</span><span class="p">(</span><span class="k">c</span><span class="p">)</span> <span class="n">max_blocks_per_slice</span><span class="p">,</span>
<span class="k">COUNT</span><span class="p">(</span><span class="k">DISTINCT</span> <span class="n">slice</span><span class="p">)</span> <span class="n">dist_slice</span>
<span class="k">FROM</span> <span class="p">(</span>
<span class="k">SELECT</span>
<span class="n">b</span><span class="p">.</span><span class="n">tbl</span><span class="p">,</span>
<span class="n">b</span><span class="p">.</span><span class="n">slice</span><span class="p">,</span>
<span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="k">c</span>
<span class="k">FROM</span> <span class="n">STV_BLOCKLIST</span> <span class="n">b</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">b</span><span class="p">.</span><span class="n">tbl</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="n">slice</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="n">tbl</span> <span class="k">IN</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">table_id</span> <span class="k">FROM</span> <span class="n">svv_table_info</span><span class="p">)</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="n">tbl</span><span class="p">)</span> <span class="n">iq</span>
<span class="k">ON</span> <span class="n">iq</span><span class="p">.</span><span class="n">tbl</span> <span class="o">=</span> <span class="n">ti</span><span class="p">.</span><span class="n">table_id</span><span class="p">;</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>This not-too-long blog post highlighted some of the straight forward ways to scale a Redshift cluster, by configuring the best WLM setup, leveraging query rules monitoring and improving query performances by limiting redistribution.</p>
<p>You should also bear the following list of various points in mind when designing your data warehouse:</p>
<ul>
<li>You’ll need at least 3 times the size of your largest table as available disk space to be able to perform basic maintenance operations;</li>
<li>Use distribution keys to avoid redistribution, and use ALL distribution on small dimensions;</li>
<li>Reduce the use of the leader node as much as possible by leveraging COPY/UNLOAD;</li>
<li>Compress your columns. <strong>Pro-tip:</strong> don’t compress sort keys columns because there will be more data in each zone map and the <code class="language-plaintext highlighter-rouge">SCAN</code> operation will take more time;</li>
<li>Increase batch size as much as possible;</li>
<li>Gain half the IO time in your ETL pipelines by creating temporay tables for pre-processing instead of disposable regular tables: temporary tables are not replicated!</li>
</ul>
<p>On the last major update of Redshift, Amazon came up with Redshift Spectrum. It is a dedicated Amazon Redshift server independent from the main cluster. Such as many compute intensive tasks can be pushed down to the Amazon Spectrum layer using Amazon S3 as its storage.
It uses much less of the cluster’s processing and storage resources and provides <strong>unlimited</strong><em>ish</em> read concurrency!</p>
<p>We will deep dive in Redshift Spectrum in the second part of this blog post series.</p>
<p>Meanwhile, don’t hesitate of course to reach me out for any feedback!</p>
<p>At Drivy we have massively been using <a href="https://aws.amazon.com/fr/redshift/">Redshift</a> as our data warehouse since mid-2015, we store in it all our transformations and aggregations of our production database and 3rd-party data (spreadsheets, csv, APIs and so on).
In this first blog post, we will discuss how we adapted our Redshift configuration and architecture as our usages changed over time.</p>
Wed, 21 Mar 2018 00:00:00 +0000
https://getaround.tech/redshift_tips_ticks_part_1/
https://getaround.tech/redshift_tips_ticks_part_1/Rails 5.1 Change Tracking in CallbacksHoward Wilson<p>After recently upgrading to Rails 5.1, we noticed that certain model changes were no longer getting logged properly by <a href="https://github.com/airblade/paper_trail"><code class="language-plaintext highlighter-rouge">PaperTrail</code></a>. After a bit of digging, this turned out to be due to a subtle difference in the way that Rails <a href="https://github.com/rails/rails/pull/25337">now tracks changes</a>.</p>
<p>It’s a little contrived, but let’s say we have a model that becomes active once info is present, like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Car</span>
<span class="n">after_save</span> <span class="k">do</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">state</span> <span class="o">=</span> <span class="s2">"active"</span> <span class="k">if</span> <span class="n">info_was</span><span class="p">.</span><span class="nf">blank?</span> <span class="o">&&</span> <span class="n">info</span><span class="p">.</span><span class="nf">present?</span> <span class="o">&&</span> <span class="n">state</span> <span class="o">!=</span> <span class="s2">"active"</span>
<span class="nb">puts</span> <span class="n">changes</span> <span class="c1"># Let's see how changes are tracked</span>
<span class="n">save!</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>In Rails 5, we get something like:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">></span> <span class="n">car</span><span class="p">.</span><span class="nf">update!</span><span class="p">(</span><span class="ss">info: </span><span class="s2">"foo"</span><span class="p">)</span>
<span class="p">{</span>
<span class="s2">"info"</span> <span class="o">=></span> <span class="p">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s2">"foo"</span><span class="p">],</span>
<span class="s2">"active"</span> <span class="o">=></span> <span class="p">[</span><span class="kp">false</span><span class="p">,</span> <span class="kp">true</span><span class="p">],</span>
<span class="s2">"updated_at"</span> <span class="o">=></span> <span class="p">[</span><span class="no">Fri</span><span class="p">,</span> <span class="mi">19</span> <span class="no">Jan</span> <span class="mi">2018</span> <span class="mi">15</span><span class="p">:</span><span class="mi">48</span><span class="p">:</span><span class="mi">12</span> <span class="no">CET</span> <span class="o">+</span><span class="mo">01</span><span class="p">:</span><span class="mo">00</span><span class="p">,</span> <span class="no">Fri</span><span class="p">,</span> <span class="mi">19</span> <span class="no">Jan</span> <span class="mi">2018</span> <span class="mi">15</span><span class="p">:</span><span class="mi">48</span><span class="p">:</span><span class="mi">15</span> <span class="no">CET</span> <span class="o">+</span><span class="mo">01</span><span class="p">:</span><span class="mo">00</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>
<p>However, after upgrading to Rails 5.1, we’ll get:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">></span> <span class="n">car</span><span class="p">.</span><span class="nf">update!</span><span class="p">(</span><span class="ss">info: </span><span class="s2">"foo"</span><span class="p">)</span>
<span class="p">{</span>
<span class="s2">"info"</span> <span class="o">=></span> <span class="p">[</span><span class="kp">nil</span><span class="p">,</span> <span class="s2">"foo"</span><span class="p">],</span>
<span class="s2">"updated_at"</span> <span class="o">=></span> <span class="p">[</span><span class="no">Fri</span><span class="p">,</span> <span class="mi">19</span> <span class="no">Jan</span> <span class="mi">2018</span> <span class="mi">15</span><span class="p">:</span><span class="mi">48</span><span class="p">:</span><span class="mi">12</span> <span class="no">CET</span> <span class="o">+</span><span class="mo">01</span><span class="p">:</span><span class="mo">00</span><span class="p">,</span> <span class="no">Fri</span><span class="p">,</span> <span class="mi">19</span> <span class="no">Jan</span> <span class="mi">2018</span> <span class="mi">15</span><span class="p">:</span><span class="mi">48</span><span class="p">:</span><span class="mi">15</span> <span class="no">CET</span> <span class="o">+</span><span class="mo">01</span><span class="p">:</span><span class="mo">00</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>
<p>The state transition is now missing from the tracked changes.</p>
<p><em>NOTE: As of Rails 5.1, we’ll now see also see the following warning, but it doesn’t give us any indication of this small change in behavior.</em></p>
<blockquote>
<p>DEPRECATION WARNING: The behavior of <code class="language-plaintext highlighter-rouge">changes</code> inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after <code class="language-plaintext highlighter-rouge">save</code> returned (e.g. the opposite of what it returns now). To maintain the current behavior, use <code class="language-plaintext highlighter-rouge">saved_changes</code> instead.</p>
</blockquote>
<p>Admittedly this is a small and isolated change, and this illustration is something of an anti-pattern, but this might just help clarify the new behavior for others seeing similar issues!</p>
<p>After recently upgrading to Rails 5.1, we noticed that certain model changes were no longer getting logged properly by <a href="https://github.com/airblade/paper_trail"><code class="language-plaintext highlighter-rouge">PaperTrail</code></a>. After a bit of digging, this turned out to be due to a subtle difference in the way that Rails <a href="https://github.com/rails/rails/pull/25337">now tracks changes</a>.</p>
Mon, 12 Feb 2018 00:00:00 +0000
https://getaround.tech/rails-5.1-change-tracking/
https://getaround.tech/rails-5.1-change-tracking/How we documented our API using unit testingChristophe Yammouni<p>At Drivy, we have an internal API to communicate with our native apps available on both iOS and Android.
One of the main pain point we experienced is documentation.</p>
<p>As our product is constantly evolving, we need to have up-to-date documentation in order to help both mobile and backend developers stay aware of what each endpoint is expecting and returning.</p>
<p>Static documentation is known to be hard to maintain, it’s indeed easy to forget to update it from time to time. What is more, you quickly end up with differences between documentation and actual behaviour.</p>
<p>The solution we are going to see is something between a static and a live documentation.</p>
<h2 id="backend-stack">Backend stack</h2>
<p>Our backend is written in Rails and we use Rspec as part of our test suite.
For our API, we use <a href="https://github.com/rails-api/active_model_serializers">active_model_serializers</a> to handle the view component of the MVC pattern. Here, views are called serializers.
We tried other options like <a href="https://github.com/nesquena/rabl">RABL</a>, but felt that active_model_serializers was the best choice. For example, its DSL is inspired from Rails, so a Ruby developer does not need special training to learn how to use it, it’s also more simple to do unit testing.</p>
<p>We tend to have a serializer per action and some nested nodes are generated by shared ones.</p>
<h2 id="our-solution">Our solution</h2>
<p>Here’s an example of one of our serializer spec.</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="err">let(:expected_json)</span><span class="w"> </span><span class="err">do</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="err">user.id</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Ben"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"last_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Driver"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[email protected]"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"phone_number_national"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0612345678"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"phone_country"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FR"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"address_line1"</span><span class="p">:</span><span class="w"> </span><span class="s2">"28 rue des paquerettes"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"address_line2"</span><span class="p">:</span><span class="w"> </span><span class="s2">"appt B"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Suresnes"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"postal_code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"92150"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"country"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FR"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"about_me"</span><span class="p">:</span><span class="w"> </span><span class="s2">"I love cars"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"license_number"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1234567890"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"license_first_issue_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2001-01-01"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"license_country"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FR"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1981-01-01"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"birth_place"</span><span class="p">:</span><span class="w"> </span><span class="s2">"macon"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2016-01-01T11:00:00Z"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"avatar"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"thumb_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://drivy-test.imgix.net/uploads/originals/ed3585a06f3ad9a1c945456953cb9ed7.jpeg?auto=format%2Ccompress&crop=faces&dpr=2.0&fit=crop&fm=png&h=100&mask=https%3A%2F%2Fdrivy-prod-static.s3.amazonaws.com%2Fimgix%2Favatar_mask_circle.png&w=100"</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="nl">"license_release_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2001-01-01"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"stats"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nl">"ratings_average"</span><span class="p">:</span><span class="w"> </span><span class="err">ratings_average</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"ratings_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"owner_ended_rentals_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"driver_ended_rentals_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="err">end</span>
<span class="err">it_behaves_like</span><span class="w"> </span><span class="s2">"a serializer"</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">shared_examples_for</span> <span class="s2">"a serializer"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"matches the expected output"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="no">JSON</span><span class="p">.</span><span class="nf">pretty_generate</span><span class="p">(</span><span class="n">generated_json</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">pretty_generate</span><span class="p">(</span><span class="n">expected_json</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Under the hood, we generate the JSON output from the serializer, and compare it to our <code class="language-plaintext highlighter-rouge">expected_json</code> variable. If any difference is spotted, the spec will fail.</p>
<p>What’s really interesting here is that this is an actual test. This means that any change to the serializer will break the spec, preventing a release to production and therefore forcing developers to actually update the test.</p>
<p>So what we see in this file will always be synchronised with the production generated content.</p>
<p>From a quick look, you can learn attribute names, possible values/types, and if some changes are tied to a specific app version, you can have different context, exposing those behaviors.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We’ve been using this solution for more than a year, with new developers joining the team, and it still fits our needs. Contexts and expected outputs are easy to read by a non-Ruby developer.</p>
<p>Of course, we have room for improvements, for example, it’s missing description of attributes, endpoints path, etc.
But it’s a win-win solution, because we have all benefits of unit testing, and it acts as a documentation.</p>
<p>With some additional work, we could easily generate a standalone documentation on a regular basis regrouping all serializers output in a single page.</p>
<p>We’ve only been seeing a standalone piece of documentation for what a client can expect to receive from requesting our API.
But what about inputs? What does a client need to send as parameters to properly receive those answers.
In a future article, we’ll see how we managed to document request inputs.</p>
<p>At Drivy, we have an internal API to communicate with our native apps available on both iOS and Android.
One of the main pain point we experienced is documentation.</p>
Tue, 23 Jan 2018 00:00:00 +0000
https://getaround.tech/api-documentation/
https://getaround.tech/api-documentation/Pro tips for productivityEmily Fiennes<p>Every 2 weeks we hold Tech Talks here at Drivy. These are an opportunity for our now 17-strong team of developers to gather for 1 hour in a non-squad setting. The Tech Talks are emphatically not about tackling issues or solving problems, but are instead an opportunity to explore new ideas, ask questions, or to return to discussions started in a different context.</p>
<p>As a recent new hire to Drivy in a junior capacity, I quickly recognised that the Tech Talks were going to be a valuable addition to my onboarding experience. Any junior, joining an established tech team with rigorous working practices in place, will likely feel daunted as I did. One way I combatted this was by trying to learn as much from my colleagues as possible.</p>
<p>Imagine my glee then, when during a recent edition of the Tech Talks, we were invited to share our productivity tips 🎁. I’ve rounded up a selection of the tips and tricks that were shared, which covered a broad range of topics including Git, shell config and debugging. These tips may be highly valuable to other juniors out there - or indeed anyone looking to streamline their personal working practices. Plus it’s really interesting — and reassuring — to take a step back and see that everyone has their own way of working.</p>
<p><strong>Disclaimer:</strong> these are tips that were shared by members of the dev team at Drivy. Many of the points have themselves been sifted from the Internet - no point reinventing the wheel as they say, especially when you’re aiming to move fast and break things 😉.</p>
<h2 id="git">Git</h2>
<h3 id="force-with-lease">force-with-lease</h3>
<p><a href="https://twitter.com/_renaudb">Renaud</a> reminded us that the standard</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">git</span> <span class="n">push</span> <span class="o">-</span><span class="n">f</span></code></pre></figure>
<p>overwrites the remote branch with your local branch, thereby potentially overwriting any work that may have been built on top of your original remote branch. The alternative is to</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">git</span> <span class="n">push</span> <span class="o">--</span><span class="n">force</span><span class="o">-</span><span class="n">with</span><span class="o">-</span><span class="n">lease</span></code></pre></figure>
<p>which affords more flexibility: if any new commits have been added to the remote branch in the meantime, the force-with-lease option will not update the remote branch.</p>
<p>To quote the <a href="https://git-scm.com/docs/git-push">documentation</a>, ‘it is like taking a “lease” on the ref without specifically locking it, and the remote ref is updated only if the “lease” is still valid’. This is important if it’s possible that another team member has checked out your branch locally and continued working on it.</p>
<h3 id="git_dig">git_dig</h3>
<p><a href="https://twitter.com/nicoolas25">Nico</a> has the following in his .bashrc file</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">function</span> <span class="n">git_dig</span> <span class="p">{</span>
<span class="n">git</span> <span class="n">log</span> <span class="o">--</span><span class="n">pretty</span><span class="o">=</span><span class="nb">format</span><span class="ss">:'%Cred%h%Creset - %Cgreen(%ad)%Creset - %s %C(bold blue)<%an>%Creset'</span> <span class="o">--</span><span class="n">abbrev</span><span class="o">-</span><span class="n">commit</span> <span class="o">--</span><span class="n">date</span><span class="o">=</span><span class="n">short</span> <span class="o">-</span><span class="no">G</span><span class="s2">"$1"</span> <span class="o">--</span> <span class="vg">$2</span>
<span class="p">}</span></code></pre></figure>
<p>With the following command:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">git_dig</span> <span class="n">rental_agreement</span></code></pre></figure>
<p>he can subsequently see all commits that contain the string <code class="language-plaintext highlighter-rouge">rental_agreement</code> in their diff, whether those commits have been deleted or not. Sometimes you might want to find dead code, or to see the evolution over time of a given method.</p>
<p><img src="../assets/posts/2018-1-16-productivity-pro-tips-highlights/git_dig_nicolas.gif" alt="git_dig gif" /></p>
<h3 id="octobox-for-github-notifications">Octobox for GitHub notifications</h3>
<p><em>Nico</em> also shared this tool with us, to help keep on top of GitHub notifications in your inbox. <a href="https://octobox.io/">Octobox</a> ‘adds an extra “archived” state to each notification so that you can mark it as done.’ Plus, if any new activity occurs on an archived item, it is moved back to your inbox so it won’t get forgotten.</p>
<h2 id="aliases">Aliases</h2>
<p>Aliases were a new discovery for me, right on Day One of my Drivy adventure. There just wasn’t time to cover this kind of setup in depth at <a href="https://www.lewagon.com/">Le Wagon</a>, the 9-week intensive bootcamp where I started to code.</p>
<p>So, for anyone who is in the dark as I was, an alias basically lets you abbreviate a system command or add default arguments to a command you use regularly. For example, to start up the server or to drop and recreate the database, you can create a shorter alias. You write them in the config file of whatever framework you are using for managing your z-shell config (usually <code class="language-plaintext highlighter-rouge">~/.zshrc</code> or <code class="language-plaintext highlighter-rouge">~/.zshenv</code>).</p>
<p>Here’s a quick tour of some of the useful ones that came up. This can give you an idea of how you might use aliases, although of course you’ll need to customise for your own setup and tools:</p>
<ul>
<li>To start or restart the server, or open your CI:</li>
</ul>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">alias</span> <span class="n">dstart</span><span class="o">=</span><span class="s1">'foreman start -f Procfile.dev'</span>
<span class="k">alias</span> <span class="n">drestart</span><span class="o">=</span><span class="s1">'pgrep unicorn | xargs kill - USR2'</span>
<span class="k">alias</span> <span class="n">circle</span><span class="o">=</span><span class="s1">'open https://circleci.com/gh/drivy/drivy-rails'</span></code></pre></figure>
<ul>
<li><a href="https://twitter.com/TimPetricola">Tim</a> suggested the following to quickly migrate and rollback:</li>
</ul>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">alias</span> <span class="n">rdm</span><span class="o">=</span><span class="s1">'rake db:migrate && rake db:rollback && rake db:migrate'</span></code></pre></figure>
<ul>
<li>Drivy operates in 6 countries, so there’s a lot of country-specific config. <a href="https://twitter.com/jeanquetil">Jean</a> suggested the following alias to quickly open all country files for editing in Sublime:</li>
</ul>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">alias</span> <span class="n">countries</span><span class="o">=</span><span class="s1">'subl lib/drivy/countries/*.rb'</span></code></pre></figure>
<ul>
<li><a href="https://twitter.com/RomainGuefveneu">Romain</a> showed how to set aliases for various workspaces:</li>
</ul>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">alias</span> <span class="n">dr</span><span class="o">=</span><span class="s1">'cd ~Workspaces/drivy-rails'</span>
<span class="k">alias</span> <span class="n">da</span><span class="o">=</span><span class="s1">'cd ~Workspaces/drivy-android'</span>
<span class="k">alias</span> <span class="n">di</span><span class="o">=</span><span class="s1">'cd ~/Workspace/drivy-ios'</span>
<span class="k">alias</span> <span class="n">weh</span><span class="o">=</span><span class="s1">'~/Workspaces/drivy-android/scripts/write_device_hosts'</span></code></pre></figure>
<ul>
<li><a href="https://twitter.com/mickeyben">Mike</a> uses the following aliases. The first asks for confirmation before file deletion. The second searches your code for any ‘TO DO’ or a ‘FIX ME’ left as a note-to-self awaiting action.</li>
</ul>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">alias</span> <span class="n">rm</span><span class="o">=</span><span class="s1">'rm -i'</span>
<span class="k">alias</span> <span class="n">todos</span><span class="o">=</span><span class="s2">"ack -n --nogroup '(TODO|XXX|FIX(ME)?):'"</span></code></pre></figure>
<p>Whatever aliases you choose to set, remember to commit the file somewhere!</p>
<h2 id="git-aliases">Git aliases</h2>
<p>Git aliases go in the global .gitconfig file, and can save you time and effort on regularly used commands. The following were suggested by <em>Christophe</em> and <em>Romain</em></p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">[</span><span class="k">alias</span><span class="p">]</span>
<span class="n">co</span> <span class="o">=</span> <span class="n">checkout</span>
<span class="n">ci</span> <span class="o">=</span> <span class="n">commit</span>
<span class="n">st</span> <span class="o">=</span> <span class="n">status</span>
<span class="n">br</span> <span class="o">=</span> <span class="n">branch</span>
<span class="n">di</span> <span class="o">=</span> <span class="n">diff</span>
<span class="n">lg</span> <span class="o">=</span> <span class="n">log</span> <span class="o">--</span><span class="n">color</span> <span class="o">--</span><span class="n">graph</span> <span class="o">--</span><span class="n">pretty</span><span class="o">=</span><span class="nb">format</span><span class="ss">:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'</span> <span class="o">--</span><span class="n">abbrev</span><span class="o">-</span><span class="n">commit</span></code></pre></figure>
<p>Plus, less typing means less chance of making a mistake 😇.</p>
<h2 id="plugins">Plugins</h2>
<h3 id="the-fuck">The Fuck</h3>
<p>If you do make mistakes often though, <a href="https://twitter.com/Intrepidd">Adrien</a> has a remedy.<a href="https://github.com/nvbn/thefuck">The Fuck</a> can be installed via Homebrew and is, in their own words, ‘a magnificent app which corrects your previous console command’.</p>
<p>It tries to match mis-spelled commands, and then create a new command using the matched rule like so:</p>
<p><img src="../assets/posts/2018-1-16-productivity-pro-tips-highlights/the_fuck_adrien.png" alt="The Fuck" /></p>
<h3 id="bitbar-mike">BitBar (Mike)</h3>
<p><em>Mike</em> recommended <a href="https://github.com/matryer/bitbar">BitBar</a> to us, for putting the output from any script or program directly in your menu bar. You can either browse their plugins, or write your own if you’ve got what it takes 💪.</p>
<p>He uses it to be able to easily check the status of external tools, or to stop and and start Homebrew services, like so:</p>
<p><img src="../assets/posts/2018-1-16-productivity-pro-tips-highlights/bitbar_mike.png" alt="Bitbar" /></p>
<h2 id="sublime-plugins">Sublime plugins</h2>
<p>A real medley of text-editor plugins were mentioned. Some useful ones for Sublime were:</p>
<h3 id="click-to-partial">Click to Partial</h3>
<p>With <code class="language-plaintext highlighter-rouge">⌘ + shift + click</code> on a Rails partial path, you can open that file in a new tab, as explained by <em>Adrien</em>.</p>
<p><img src="../assets/posts/2018-1-16-productivity-pro-tips-highlights/click_to_partial_adrien.gif" alt="Click to Partial" /></p>
<h3 id="trailing-spaces">Trailing Spaces</h3>
<p><em>Jean</em> recommended <a href="https://github.com/SublimeText/TrailingSpaces">Trailing Spaces</a>. This plugin lets you highlight and delete any redundant trailing spaces in your code, either automatically upon saving or by hand at any time.</p>
<h3 id="brackethighlighter">BracketHighlighter</h3>
<p>Another handy Sublime plugin is <a href="https://github.com/facelessuser/BracketHighlighter">BracketHighlighter</a>. It will match a variety of brackets, helping you to avoid unclosed or unmatched brackets. It too is fully customizable.</p>
<h2 id="debugging">Debugging</h2>
<p>Lastly, a quick word on debugging. We use <a href="https://github.com/Mon-Ouie/pry-remote">pry_remote</a> for debugging at Drivy, because running our processes on <a href="https://www.theforeman.org/">Foreman</a> means we can’t interact with a regular Pry session.</p>
<p><em>Jean</em> reminded us that when pry hits its breakpoint,</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="err">@</span></code></pre></figure>
<p>can be used in the pry console as a shortcut for</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">whereami</span></code></pre></figure>
<p><em>Adrien</em> shared a tip for pry_remote too. You can call:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">ls</span></code></pre></figure>
<p>on an object within the pry console, and get a list of associated methods:</p>
<p><img src="../assets/posts/2018-1-16-productivity-pro-tips-highlights/pry_ls_adrien.png" alt="" /></p>
<p>This has just been a sample of the points touched upon, and of course there were other, more sophisticated ideas too - beyond the scope of a junior. But if there’s one thing that emerged from the veritable smörgåsbord of tips and tricks shared, it’s that there is no one standardized way of working. Joining an established tech team in a junior capacity can be very overwhelming, so it can be reassuring to remember that sometimes.</p>
<p>Every 2 weeks we hold Tech Talks here at Drivy. These are an opportunity for our now 17-strong team of developers to gather for 1 hour in a non-squad setting. The Tech Talks are emphatically not about tackling issues or solving problems, but are instead an opportunity to explore new ideas, ask questions, or to return to discussions started in a different context.</p>
Tue, 16 Jan 2018 00:00:00 +0000
https://getaround.tech/productivity-pro-tips-highlights/
https://getaround.tech/productivity-pro-tips-highlights/Highlights from the 2017 dotJSVictor Mours<p><img src="../assets/posts/2018-01-11-highlights-from-dotjs/dotjs-marcy-sutton.jpg" alt="Marcy Sutton speaking at the 2017 dotJS" /></p>
<p>Last month was the dotJS conference in Paris, which we attended, along with some 1400 developers from all over the world. As you could expect, the speakers were stellar. Some talks made us want to ponder our assumptions about how we write JavaScript and come to a more elevated understanding of our front-end ways, and some of them made us want to grab the nearest keyboard and tinker frantically with WebAssembly, TypeScript, or WebGL.</p>
<p>If you don’t have a full day of free time ahead of you to watch all the talks, here are some highlights of the ones we recommend checking out first.</p>
<h1 id="wes-bos---async--await"><a href="https://www.dotconferences.com/2017/12/wes-bos-async-await">Wes Bos - Async & Await</a></h1>
<p>If you haven’t had time to play with <code class="language-plaintext highlighter-rouge">async</code> and <code class="language-plaintext highlighter-rouge">await</code>, you could take a look at the documentation by yourself, or you could just kick back, relax, and let Wes Bos do the explaining. He lays out really nicely how you can simplify your code when you’re chaining synchronous promises, and how to handle errors in this case.</p>
<p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/9YkUCxvaLEk?showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>
</p>
<h1 id="trent-willis---working-well-the-future-of-web-testing">Trent Willis - Working Well: The Future of Web Testing</h1>
<p>Trent’s talk explores the consequences of the arrival of Headless Chrome in our testing toolkit.
Chrome headless now comes with Puppeteer, an API for controlling the browser and its DevTools, which makes for a much better developer experience than the old school Selenium-driven scripts.</p>
<p>Along with the DevTool profiler and the accessibility testing library <code class="language-plaintext highlighter-rouge">axe-core</code>, these open up the possibility of a shift of what we expect from our tests. We can now go from “does this code work?” to “how well does this code work?”. This allows a more nuanced, yet measurable way of seeing our code.</p>
<p>I think what really lies beyond this is the topic of code metrics. While we have a fairly established way of taking code validity into account in the standard development workflow - either the test suite runs a green build and the code can be merged, or it is red and must be fixed before merging - there doesn’t seem to be a standard way of taking code quality metrics into account.</p>
<p><img src="../assets/posts/2018-01-11-highlights-from-dotjs/dotjs-trent-willis.jpg" alt="Trent Willis speaking at the 2017 dotJS" /></p>
<p>One way some teams deal with this is to consider the build to be green if it improves code quality metrics overall, and red if it doesn’t. But that process can break down easily. What if a change to the codebase were to considerably improve accessibility, while degrading performance a bit?</p>
<p>Would it be acceptable to merge it? Obviously the answer is “It depends”. If we want to make informed decisions based on code quality, we should also be considering other factors that are harder to measure, such as code readability and maintainability.</p>
<p>I feel like these are still open problems, and I’m excited to see what comes out of the more widespread use of these tools in the years to come.</p>
<p>The video of the talk is not online yet, but <a href="https://pretty-okay.com/2017/12/04/qunit-in-browser">this article</a> from Trent’s blog is worth a read.</p>
<h1 id="adrian-holovaty---a-framework-authors-case-against-frameworks"><a href="https://www.dotconferences.com/2017/12/adrian-holovaty-a-framework-author-case-against-frameworks">Adrian Holovaty - A framework author’s case against frameworks</a></h1>
<p>As we’re all three node packages away from irrecoverable JavaScript fatigue, this was one of the most refreshing talks of the conference.
Adrian gave us a solid reminder that we may want to chill out about keeping up with every framework out there,
maybe focus a bit more on the patterns than on the frameworks, and take it easy. He’s building <a href="https://www.soundslice.com/">Soundslice</a> in plain vanilla JavaScript, and it’s an impressive app, so he’s probably got a fair point.</p>
<p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/VvOsegaN9Wk?showinfo=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen=""></iframe>
</p>
<h1 id="and-if-youve-still-got-time-check-these-out">…And if you’ve still got time, check these out:</h1>
<ul>
<li><a href="https://www.dotconferences.com/2017/12/feross-aboukhadijeh-the-most-annoying-website">Feross Aboukhadijeh</a> built the best annoying website to send to spammers. And it’s hilarious.</li>
<li>Suz Hinton told an inspiring story about the design of the accessibility icon. The video is not yet available, but in the meantime, I encourage you to find out more <a href="http://accessibleicon.org">here</a>.</li>
</ul>
<p><img src="../assets/posts/2018-01-11-highlights-from-dotjs/dotjs-marcy-sutton.jpg" alt="Marcy Sutton speaking at the 2017 dotJS" /></p>
Thu, 11 Jan 2018 00:00:00 +0000
https://getaround.tech/highlights-from-dotjs/
https://getaround.tech/highlights-from-dotjs/dotCSS 2017 hightlightsJean Anquetil<p>dotCSS is the largest CSS conference in Europe. The 2017 edition occurred late November in Paris and it was a great opportunity to exchange and learn from the community. As you can play back the talks on the <a href="https://www.dotconferences.com/talks">dotConferences</a> website, this post won’t dive into the details of all the conferences, this is a digest of what what was most interesting for us.</p>
<h2 id="a-search-engine-built-with-css-only">A search engine built with CSS only</h2>
<p><a href="https://twitter.com/pixelastic">Tim Carry</a> from <a href="https://blog.algolia.com/real-demo-fake-css-api-client/">Algolia</a> told us how he built a search engine with CSS only. It uses the power of selectors, as the language is super powerful at targeting elements in the HTML and applying style to them.</p>
<p>The search bar is a simple <code class="language-plaintext highlighter-rouge">input</code> tag which has a <code class="language-plaintext highlighter-rouge">value</code> attribute. Thus, a CSS selector will be able to match this input when it has a specific value.</p>
<p>Actually the search engine cheats somewhat, as the <code class="language-plaintext highlighter-rouge">value</code> attribute has to be updated each time something is typed with a JavaScript statement. But this is the only line of JS to make it work 🙂</p>
<p><a href="https://www.dotconferences.com/2017/11/tim-carry-building-a-search-engine-in-css">Link to the talk</a></p>
<h2 id="the-grid-css-property">The <code class="language-plaintext highlighter-rouge">grid</code> CSS property</h2>
<p><a href="https://twitter.com/bdc">Benjamin De Cock</a> from Stripe showed us how cool the <code class="language-plaintext highlighter-rouge">grid</code> CSS property is. This property is mainly used by Stripe on their website’s backgrounds and often combined with a <code class="language-plaintext highlighter-rouge">skewY</code> property.</p>
<p>Quickly, here’s how it works: the grid area is made of multiple span tags where each one can receive a specific style. In comparison, Flexbox is great when working on a single axis but grids are easier to deal with on two axes.</p>
<p><img src="../assets/posts/2017-12-21-dot-css-conference-highlights/drivy_grid.png" alt="Drivy Homepage" />
<em>We are also using a grid on the Drivy homepage</em></p>
<p>Unfortunately the browser support is not that good yet: depending on your target audience, decide if you need a fallback style or if it is reasonable to drop support for older browsers. Anyway using the <code class="language-plaintext highlighter-rouge">grid</code> CSS property with simple rules gives a bunch of possibilities.</p>
<p><a href="https://www.dotconferences.com/2017/11/benjamin-de-cock-css-grid-in-production">Link to the talk</a></p>
<h2 id="write-less-css">Write less CSS</h2>
<p><a href="https://twitter.com/akdetrick">Adam Detrick</a>, Web Engineer & Design Systems Lead at Meetup talked about the difficulty of maintaining a sustainable style codebase.</p>
<p>He explained that as reading CSS is too complicated we are used to writing new components and to adding new styles. We should write less of it and break the vicious circle by framing the problem correctly and thinking more about the developer experience.</p>
<p>For instance, he mentioned that using these kinds of utility classes could be a step in the right direction:</p>
<p><code class="language-plaintext highlighter-rouge">at[breakpoint]_[property]--[variant]</code></p>
<ul>
<li>These are small sharp tools</li>
<li>You style by memory, these classes are easy to understand and to remember</li>
<li>This leads to a quick implementation</li>
</ul>
<p>An example would be <code class="language-plaintext highlighter-rouge">atLarge_align--center</code> where the text inherits the default left alignment but at large viewports the text is centered.</p>
<p>Finally he also mentionned the fact that the documentation should be kept as close to the code as possible. In this way, using a documentation generator could be useful.</p>
<h1 id="media-queries-level-4">Media queries level 4</h1>
<p>Member of the CSS Working Group, <a href="https://twitter.com/frivoal">Florian Rivoal</a> presented the Media Queries Level 4 which offers some syntax improvements regarding the range, the boolean logic and shortcuts.</p>
<p>He also explained that using Media Types is not a good idea anymore. For instance, using <code class="language-plaintext highlighter-rouge">@media screen {...}</code> goes for a smartphone, a TV screen, a laptop… Perhaps we want different behaviors on each ones: using Media Features we could match with a precise device such as a Wii, a phone, a e-ink media and so on.</p>
<p>Finally, he gave us some best practices.</p>
<p>Don’t:</p>
<ul>
<li>use wrong media features as proxy,</li>
<li>set breakpoints based on popular devices,</li>
<li>try not to be too specific,</li>
<li>use <code class="language-plaintext highlighter-rouge">px</code> to define size.</li>
</ul>
<p>Do:</p>
<ul>
<li>try not to use Media Queries that much, there is Flexbox, grid, etc,</li>
<li>use all tools in the box,</li>
<li>set breakpoints for where your design would break,</li>
<li>use <code class="language-plaintext highlighter-rouge">em</code> to define size.</li>
</ul>
<p><a href="https://www.dotconferences.com/2017/11/florian-rivoal-media-queries-4">Link to the talk</a></p>
<h1 id="typography---axis-praxis">Typography - Axis-Praxis</h1>
<p><a href="https://twitter.com/Lorp">Laurence Penney</a> created Axis-Praxis which is an environment for playing with Variable Fonts.</p>
<p>You choose fonts then you can adjust sliders and play with precise settings on the variations axes built into them. It relies on the font-variation-settings CSS property and this lets us experiment what the future of Variable Fonts may be.</p>
<p>Check it out on <a href="http://www.axis-praxis.org/">Axis-Praxis.org</a> 😉</p>
<p><a href="https://www.dotconferences.com/2017/11/laurence-penney-variable-fonts">Link to the talk</a></p>
<p>dotCSS is the largest CSS conference in Europe. The 2017 edition occurred late November in Paris and it was a great opportunity to exchange and learn from the community. As you can play back the talks on the <a href="https://www.dotconferences.com/talks">dotConferences</a> website, this post won’t dive into the details of all the conferences, this is a digest of what what was most interesting for us.</p>
Thu, 21 Dec 2017 00:00:00 +0000
https://getaround.tech/dot-css-conference-highlights/
https://getaround.tech/dot-css-conference-highlights/Embulk: move easily data across datasourcesAntoine Augusti<p>At Drivy, we heavily use <a href="http://www.embulk.org/docs/">Embulk</a> for our data needs. Embulk is an <a href="https://github.com/embulk/embulk">open-source</a> data loader that helps data transfer between various databases, storages, file formats, and cloud services. It can automatically guess file formats, distribute execution to deal with big datasets, offers transactions, can resume stuck tasks and is modular thanks to plugins.</p>
<p>Embulk is written in JRuby and the configuration is specified in YAML. You then execute Embulk configuration files through the command line. It’s possible to inject environment variables and other configuration files can be embedded thanks to the <a href="https://shopify.github.io/liquid/">Liquid template engine</a>.</p>
<figure>
<img alt="Embulk architecture" src="/assets/posts/embulk/architecture.png" />
<figcaption>
Overview of Embulk's architecture and its various components
</figcaption>
</figure>
<p>The available components are the following:</p>
<ul>
<li>Input: specify where the data is coming from (MySQL, AWS S3, Jira, Mixpanel etc.)</li>
<li>Output: specify the destination of the data (BigQuery, Vertica, Redshift, CSV etc.)</li>
<li>File parser: to parse specific input files (JSON, Excel, Avro, XML etc.)</li>
<li>File decoder: to deal with compressed files</li>
<li>File formatter: to format specific output files (similar to parsers)</li>
<li>Filter: to keep only some rows from the input</li>
<li>File encoder: to compress output file (similar to decoders)</li>
<li>Executor: where do Embulk task are executed (locally or Hadoop)</li>
</ul>
<p>The plugins <a href="http://www.embulk.org/plugins/">are listed on the Embulk website</a> and are usually available on GitHub. If needed, you can <a href="http://www.embulk.org/docs/customization.html#creating-plugins">write your own plugin</a>.</p>
<h2 id="usage-at-drivy">Usage at Drivy</h2>
<p>At Drivy, we currently have a bit less than 150 Embulk configuration files and we perform nearly 1,200 Embulk tasks everyday for our ETL needs running on <a href="https://airflow.apache.org/">Apache Airflow</a>. Our main usage is to replicate tables coming from MySQL to Amazon Redshift, our data warehouse.</p>
<h3 id="from-mysql-to-redshift">From MySQL to Redshift</h3>
<p>For example, here is the Embulk configuration file we use to pull data about push notifications from MySQL to Redshift, incrementally.</p>
<p>This is stored in <code class="language-plaintext highlighter-rouge">push_notifications.yml.liquid</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="pi">{</span><span class="err">%</span> <span class="nv">include 'datasources/in_mysql_read_only' %</span><span class="pi">}</span>
<span class="na">table</span><span class="pi">:</span> <span class="s">push_notifications</span>
<span class="na">incremental</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">incremental_columns</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">updated_at</span><span class="pi">,</span> <span class="nv">id</span><span class="pi">]</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">include 'datasources/out_redshift' %</span><span class="pi">}</span>
<span class="na">table</span><span class="pi">:</span> <span class="s">push_notifications</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">merge</span>
<span class="na">merge_keys</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">id</span><span class="pi">]</span></code></pre></figure>
<p>This short configuration file uses powerful concepts. First, it leverages <a href="https://github.com/embulk/embulk-input-jdbc/tree/master/embulk-input-mysql#incremental-loading">incremental loading</a> to load records inserted (or updated) after the latest execution. In our case, we will load or update records according to the value of the latest <code class="language-plaintext highlighter-rouge">updated_at</code> and <code class="language-plaintext highlighter-rouge">id</code> columns. Records will be merged according to the <code class="language-plaintext highlighter-rouge">id</code> column, which is a primary key. Secondly, we use the Liquid template engine to pull two partials. <code class="language-plaintext highlighter-rouge">datasources/in_mysql_read_only</code> is used to specify the common MySQL configuration for the input mode and <code class="language-plaintext highlighter-rouge">datasources/out_redshift</code> is used to specify the Redshift configuration for the output mode.</p>
<p>Here is what the file <code class="language-plaintext highlighter-rouge">datasources/out_redshift.yml.liquid</code> looks like:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">out</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">redshift</span>
<span class="na">host</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.REDSHIFT_HOST</span> <span class="pi">}}</span>
<span class="na">user</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.REDSHIFT_USER</span> <span class="pi">}}</span>
<span class="na">password</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.REDSHIFT_PASSWORD</span> <span class="pi">}}</span>
<span class="na">ssl</span><span class="pi">:</span> <span class="s">enable</span>
<span class="na">database</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.REDSHIFT_DB</span> <span class="pi">}}</span>
<span class="na">aws_access_key_id</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.S3_ACCESS_KEY_ID</span> <span class="pi">}}</span>
<span class="na">aws_secret_access_key</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.S3_SECRET_ACCESS_KEY</span> <span class="pi">}}</span>
<span class="na">iam_user_name</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.S3_IAM_USER_NAME</span> <span class="pi">}}</span>
<span class="na">s3_bucket</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.S3_BUCKET</span> <span class="pi">}}</span>
<span class="na">s3_key_prefix</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.S3_KEY_PREFIX</span> <span class="pi">}}</span>
<span class="na">default_timezone</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.REDSHIFT_DEFAULT_TIMEZONE</span> <span class="pi">}}</span></code></pre></figure>
<p>Basically, it describes how to connect to our Redshift cluster and it respects the format defined by the <a href="https://github.com/embulk/embulk-output-jdbc/tree/master/embulk-output-redshift">Redshift output plugin for Embulk</a>. Note that we reference almost only environment variables that will be injected at runtime. This is used to keep secrets out of the codebase and gives us the ability to switch easily between several environments (production and staging for instance).</p>
<p>Running the script is then as straightforward as executing the Bash command</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">embulk run push_notifications.yml.liquid <span class="nt">-c</span> diffs/push_notifications.yml</code></pre></figure>
<p>after setting the required environment variables. Embulk will keep the last values for the <code class="language-plaintext highlighter-rouge">updated_at</code> and <code class="language-plaintext highlighter-rouge">id</code> columns in <code class="language-plaintext highlighter-rouge">diffs/push_notifications.yml</code> for future executions.</p>
<p>The <code class="language-plaintext highlighter-rouge">diffs/push_notifications.yml</code> file looks like this:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">in</span><span class="pi">:</span>
<span class="na">last_record</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">2017-12-11T13:51:41.000000'</span><span class="pi">,</span> <span class="nv">11230196</span><span class="pi">]</span>
<span class="na">out</span><span class="pi">:</span> <span class="pi">{}</span></code></pre></figure>
<h3 id="from-a-csv-file-to-redshift">From a CSV file to Redshift</h3>
<p>Here is how we import CSV files into Redshift.</p>
<p>Embulk ships with a CSV guesser, that can automatically build a configuration file from a CSV file.</p>
<p>If we start from a sample configuration file like this one that we will write in <code class="language-plaintext highlighter-rouge">cr_agents.yml.liquid</code>:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">in</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">file</span>
<span class="na">path_prefix</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.EMBULK_PATH_PREFIX</span> <span class="pi">}}</span>
<span class="na">parser</span><span class="pi">:</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">include 'datasources/out_redshift' %</span><span class="pi">}</span>
<span class="na">table</span><span class="pi">:</span> <span class="s">cr_agents</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">truncate_insert</span></code></pre></figure>
<p>and by running the Bash command</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">EMBULK_PATH_PREFIX</span><span class="o">=</span>/tmp/agents.csv embulk guess cr_agents.yml.liquid</code></pre></figure>
<p>Embulk will then generate the appropriate CSV boilerplate like this, after parsing our CSV file.</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">in</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">file</span>
<span class="na">path_prefix</span><span class="pi">:</span> <span class="pi">{{</span> <span class="nv">env.EMBULK_PATH_PREFIX</span> <span class="pi">}}</span>
<span class="na">parser</span><span class="pi">:</span>
<span class="na">charset</span><span class="pi">:</span> <span class="s">UTF-8</span>
<span class="na">newline</span><span class="pi">:</span> <span class="s">CRLF</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">csv</span>
<span class="na">delimiter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">,'</span>
<span class="na">quote</span><span class="pi">:</span> <span class="s1">'</span><span class="s">"'</span>
<span class="na">escape</span><span class="pi">:</span> <span class="s1">'</span><span class="s">"'</span>
<span class="na">null_string</span><span class="pi">:</span> <span class="s1">'</span><span class="s">NULL'</span>
<span class="na">trim_if_not_quoted</span><span class="pi">:</span> <span class="kc">false</span>
<span class="na">skip_header_lines</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">allow_extra_columns</span><span class="pi">:</span> <span class="kc">false</span>
<span class="na">allow_optional_columns</span><span class="pi">:</span> <span class="kc">false</span>
<span class="na">stop_on_invalid_record</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">columns</span><span class="pi">:</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">id</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">long</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">drivy_id</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">long</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">zendesk_user_id</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">long</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">talkdesk_user_name</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">first_name</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">last_name</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">country</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">second_country</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">third_country</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">fourth_country</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">string</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">is_drivy</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">boolean</span><span class="pi">}</span>
<span class="pi">-</span> <span class="pi">{</span><span class="nv">name</span><span class="pi">:</span> <span class="nv">is_active</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="nv">boolean</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">include 'datasources/out_redshift' %</span><span class="pi">}</span>
<span class="na">table</span><span class="pi">:</span> <span class="s">cr_agents</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">truncate_insert</span></code></pre></figure>
<p>You can then adjust manually the configuration for <a href="http://www.embulk.org/docs/built-in.html#csv-parser-plugin">the CSV parser</a> if needed.</p>
<p>Finally, we’re now ready to import our CSV file into Redshift. This can be done thanks to the Bash command</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">EMBULK_PATH_PREFIX</span><span class="o">=</span>/tmp/agents.csv embulk run cr_agents.yml.liquid</code></pre></figure>
<p>Because we specified that we want to use the <code class="language-plaintext highlighter-rouge">truncate_insert</code> mode for the output plugin, Embulk will delete first every record in the destination table <code class="language-plaintext highlighter-rouge">cr_agents</code> before inserting rows from the CSV file.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you now have a quick grasp of what Embulk is and how it can speed up your data import and export tasks. With simple configuration files and a good plugin ecosystem, it is our go-to solution almost every time we need to perform data transfers in our ETL.</p>
<p>At Drivy, we heavily use <a href="http://www.embulk.org/docs/">Embulk</a> for our data needs. Embulk is an <a href="https://github.com/embulk/embulk">open-source</a> data loader that helps data transfer between various databases, storages, file formats, and cloud services. It can automatically guess file formats, distribute execution to deal with big datasets, offers transactions, can resume stuck tasks and is modular thanks to plugins.</p>
Mon, 11 Dec 2017 00:00:00 +0000
https://getaround.tech/embulk-data-transfer/
https://getaround.tech/embulk-data-transfer/Sending an e-mail to millions of usersAdrien Siami<p>Recently, we had to send an e-mail to all our active users. For cost reasons, we decided
to invest a bit of tech time and to go with transactional e-mails instead of using an
e-mail marketing platform.</p>
<p>While it would certainly be quite straightforward for, say, hundreds or
even thousands of users, it starts to get a bit more complicated for larger user bases.</p>
<p>In our case, we had to send the e-mail to ~1.5 million e-mail addresses.</p>
<p>In this blog post, I’ll quickly explain why a standard approach is not acceptable
and go through the solution we chose.</p>
<h1 id="a-naive-solution">A naive solution</h1>
<p>Let’s implement a very naive way to send an e-mail to all our users. We’re going to create
a job that loops through all the users and enqueues an e-mail.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">MassEmailJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:default</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="no">User</span><span class="p">.</span><span class="nf">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">user</span><span class="o">|</span>
<span class="no">Notifier</span><span class="p">.</span><span class="nf">some_email</span><span class="p">(</span><span class="n">user</span><span class="p">).</span><span class="nf">deliver_later</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Now let’s see what could go wrong.</p>
<h2 id="your-job-might-get-killed">Your job might get killed</h2>
<p>Looping through millions of users is not free and it will most likely take a fair amount
of time.</p>
<p>During this time, you’re maybe going to deploy, restarting your job manager, and killing
your job. Now you don’t know which users have received the e-mail, and which have not yet.</p>
<p>One easy fix would be to run the code outside of your job workers, maybe in a rake task,
but you have to make sure it won’t get killed, or that if it’s killed, you can resume
it without any issue.</p>
<h2 id="you-are-going-to-get-blacklisted-from-e-mail-providers">You are going to get blacklisted from e-mail providers</h2>
<p>E-mail providers don’t like spam. If you send thousands of e-mails from the same IP in a
short time, you’re guaranteed to get throttled or even blacklisted.</p>
<p>Therefore, it is necessary to space out the e-mails a bit, for example, adding a 30s delay
every 100 e-mails.</p>
<h2 id="you-are-going-to-congest-your-job-queue">You are going to congest your job queue</h2>
<p>Every e-mail to be sent equals a job run in your job queue: if you enqueue millions of
jobs in the same queue you use for other operations, you’re going to create a lot of
congestion.</p>
<p>Therefore, you’d probably want to have a special queue only for your sending with a
dedicated worker.</p>
<h1 id="our-solution">Our solution</h1>
<p>First, let’s list the requirements we had in mind:</p>
<ul>
<li>We wanted to be able to enqueue as many or as few e-mails to be able to test
the water first (check deliverability, congestion) and then scale up</li>
<li>We wanted to easily be able to establish those users for whom we had scheduled an
email, and those users who were still waiting.</li>
<li>We had to be able to stop sending emails quickly in case something went wrong, and we
had to be able to resume it without losing data.</li>
</ul>
<h1 id="redis-to-the-rescue">Redis to the rescue</h1>
<p><a href="https://redis.io">Redis</a> is an amazing multi-purpose tool, it can be used for storing
short lived data such as cache, used as a session store, etc. It has a multitude of useful
data structures, the one we’re going to use today is the
<a href="https://redis.io/commands#sorted_set">Sorted Set</a>.</p>
<p>A sorted set it a bit like a hash / dictionary / associative array. It contains a list
of values, and each of these values has a <strong>score</strong>.</p>
<p>Redis offers very useful functions to deal with sorted sets, let’s have a look at one in
particular.</p>
<h2 id="zrangebyscore"><code class="language-plaintext highlighter-rouge">ZRANGEBYSCORE</code></h2>
<p><a href="https://redis.io/commands/zrangebyscore">This function</a> returns a range of <code class="language-plaintext highlighter-rouge">n</code> elements
from the sorted set, with a score included between <code class="language-plaintext highlighter-rouge">min</code> and <code class="language-plaintext highlighter-rouge">max</code>, can you see where
this is going? :)</p>
<p>We’re going to store all our user ids in a sorted set, with a score of 0, and change that
score to 1 when we enqueue an e-mail for them.</p>
<p>Then, it’s really easy to ask for any number of users for whom we haven’t enqueued the
e-mail, using <code class="language-plaintext highlighter-rouge">ZRANGEBYSCORE</code>.</p>
<h2 id="building-the-sorted-set">Building the sorted set</h2>
<p>Let’s create a rake task to populate a sorted set with our user ids.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">task</span> <span class="ss">:populate_users_zset</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span>
<span class="n">redis</span> <span class="o">=</span> <span class="no">Redis</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">YOUR_CONFIG</span><span class="p">)</span>
<span class="no">User</span><span class="p">.</span><span class="nf">select</span><span class="p">(</span><span class="s1">'id'</span><span class="p">).</span><span class="nf">find_each</span><span class="p">.</span><span class="nf">each_slice</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">users</span><span class="o">|</span>
<span class="n">redis</span><span class="p">.</span><span class="nf">multi</span> <span class="k">do</span>
<span class="n">users</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">user</span><span class="o">|</span>
<span class="n">redis</span><span class="p">.</span><span class="nf">zadd</span><span class="p">(</span><span class="s1">'mass_email_user_ids'</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Here I’m using <a href="https://redis.io/topics/transactions"><code class="language-plaintext highlighter-rouge">MULTI</code></a> to add the user ids 100 by
100 to the set in transactions, to go easy on redis CPU.</p>
<p>While this task may take quite some time, it is safe to re-launch if killed.</p>
<h2 id="enqueuing-a-number-of-e-mails-for-send">Enqueuing a number of e-mails for send</h2>
<p>Now that we have our sorted set, let’s write another task. This one will
pick a given number of user ids from the set and enqueue an e-mail for them, while spacing
out the sends in time a bit.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">task</span> <span class="ss">:send_email_batch</span><span class="p">,</span> <span class="p">[</span><span class="ss">:batch_size</span><span class="p">]</span> <span class="o">=></span> <span class="ss">:environment</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="p">,</span> <span class="n">args</span><span class="o">|</span>
<span class="n">redis</span> <span class="o">=</span> <span class="no">Redis</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">YOUR_CONFIG</span><span class="p">)</span>
<span class="n">ids</span> <span class="o">=</span> <span class="n">redis</span><span class="p">.</span><span class="nf">zrangebyscore</span><span class="p">(</span><span class="s1">'mass_email_user_ids'</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">limit: </span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="nf">batch_size</span><span class="p">])</span>
<span class="n">delay</span> <span class="o">=</span> <span class="mi">30</span><span class="p">.</span><span class="nf">seconds</span>
<span class="n">ids</span><span class="p">.</span><span class="nf">each_slice</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">ids_slice</span><span class="o">|</span>
<span class="n">ids_slice</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="nb">id</span><span class="o">|</span>
<span class="no">Notifier</span><span class="p">.</span><span class="nf">some_email</span><span class="p">(</span><span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nb">id</span><span class="p">)).</span><span class="nf">deliver_later</span><span class="p">(</span><span class="ss">wait: </span><span class="n">delay</span><span class="p">)</span>
<span class="n">redis</span><span class="p">.</span><span class="nf">zadd</span><span class="p">(</span><span class="s1">'mass_email_user_ids'</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="ss">xx: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">delay</span> <span class="o">+=</span> <span class="mi">30</span><span class="p">.</span><span class="nf">seconds</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Here I get as many user ids as requested thanks to <code class="language-plaintext highlighter-rouge">ZRANGEBYSCORE</code> and its <code class="language-plaintext highlighter-rouge">limit</code> option.
I then iterate over the ids and enqueue the jobs 100 by 100, while delaying the sending by
30 seconds each time.</p>
<p>And that’s it! Thanks to this system you can gradually increase your e-mail batches while
keeping an eye on deliverability.</p>
<p>Send 100 mails to test it out:</p>
<p><code class="language-plaintext highlighter-rouge">rake 'your_namespace:send_email_batch[100]'</code></p>
<p>Everything looks good ? Send 1000, then 10000, etc.</p>
<p>Then it’s easy to know how many e-mails are left to be scheduled: just pop a redis console
and ask away using <a href="https://redis.io/commands/zcount">zcount</a>!</p>
<p>Remaining e-mails to schedule:</p>
<p><code class="language-plaintext highlighter-rouge">ZCOUNT mass_email_user_ids 0 0</code></p>
<p>E-mails already scheduled or sent:</p>
<p><code class="language-plaintext highlighter-rouge">ZCOUNT mass_email_user_ids 1 1</code></p>
<h1 id="cons">Cons</h1>
<p>Obviously there is no perfect solution, here are a few downsides:</p>
<h2 id="quite-a-few-manual-actions">Quite a few manual actions</h2>
<p>This is clearly not a fire and forget solution, it needs
the attention of a dev for a little bit of time: enqueuing the sends, monitoring, waiting
for a batch to finish and then send another one, etc.</p>
<p>However, this kind of sending is usually rare but important, so having it done right
is worth the effort.</p>
<h2 id="stopping-the-machine-is-possible-but-at-a-cost">Stopping the machine, is possible, but at a cost</h2>
<p>If you enqueue a lot of small batches,
you’re going to be fine, but at some point you are going to enqueue batches of 100k
e-mails or even more.</p>
<p>What if something goes wrong (deliverability dropping, etc) and you want to stop
everything to have a look? You would need to stop the dedicated worker but the jobs are
already enqueued, meaning that if you don’t resume for a long time, when starting over
the jobs are going to run without delay and you may experience congestion or throttling
from your e-mail provider.</p>
<p>This is a risk we were willing to take and that we mitigated
with strong monitoring and cautious batching.</p>
<h1 id="conclusion">Conclusion</h1>
<p>This solution worked well for our needs, but as always, your mileage may vary!</p>
<p>Sending millions of e-mails is tricky, but is an interesting problem to solve.
Thanks to a bit of custom dev and redis, we were able to send our e-mail in a reasonable
amount of time with excellent deliverability.</p>
<p>Recently, we had to send an e-mail to all our active users. For cost reasons, we decided
to invest a bit of tech time and to go with transactional e-mails instead of using an
e-mail marketing platform.</p>
Mon, 20 Nov 2017 00:00:00 +0000
https://getaround.tech/sending-mass-emails/
https://getaround.tech/sending-mass-emails/Multi-currency support in JavaRomain Guefveneu<p>For a few weeks, <a href="https://www.drivy.co.uk">Drivy</a> has been available in the United-Kingdom. Unlike the others European countries where Drivy operates, the United-Kingdom uses a different currency: the pound (£). We had to make some changes in our Android apps to support this.</p>
<h1 id="server-side-or-client-side-formatting">Server-side or Client-side Formatting?</h1>
<p>At Drivy, formatting is generally done <a href="https://drivy.engineering/api-driven-apps/">server-side</a>, we just display the values as they are:</p>
<p><img src="../assets/posts/2017-11-20-multi-currency-java/picks.png" alt="" />
<em>Here, prices are formatted server-side, depending on the search place (London, so £), and the app’s locale (french).</em></p>
<p>But for some specific features we need client-side formatting, for instance an input field. Let’s dive into some Java APIs to see how they can help.</p>
<h1 id="formatting">Formatting</h1>
<p>First thing first, how to format a currency? The position of the currency symbol doesn’t depend on the currency itself, but on the country of the locale. That means we’ll display “1 234,50 GBP” in French, and “€1,234.50” in English:</p>
<table>
<thead>
<tr>
<th> </th>
<th>€</th>
<th>£</th>
<th>$</th>
</tr>
</thead>
<tbody>
<tr>
<td>France</td>
<td>1 234,50 €</td>
<td>1 234,50 GBP</td>
<td>1 234,50 USD</td>
</tr>
<tr>
<td>Switzerland</td>
<td>EUR 1’234.50</td>
<td>GBP 1’234.50</td>
<td>USD 1’234.50</td>
</tr>
<tr>
<td>Italy</td>
<td>€ 1.234,50</td>
<td>GBP 1.234,50</td>
<td>USD 1.234,50</td>
</tr>
<tr>
<td>United-Kingdom</td>
<td>€1,234.50</td>
<td>£1,234.50</td>
<td>USD1,234.50</td>
</tr>
<tr>
<td>USA</td>
<td>EUR1,234.50</td>
<td>$1,234.50</td>
<td>$1,234.50</td>
</tr>
</tbody>
</table>
<p>As you can see, the currency symbol is not always displayed. Since there are multiple currencies using the same symbol (e.g. United States dollar and Canadian dollar), we will instead display the currency code if there is any ambiguity (well, except for € in en_US 🤷).</p>
<p>Alright, how do we do that in Java? Pretty simple:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">Locale</span> <span class="n">countryLocale</span> <span class="o">=</span> <span class="nc">Locale</span><span class="o">.</span><span class="na">FRANCE</span><span class="o">;</span>
<span class="nc">Locale</span> <span class="n">currencyLocale</span> <span class="o">=</span> <span class="nc">Locale</span><span class="o">.</span><span class="na">UK</span><span class="o">;</span>
<span class="nc">NumberFormat</span> <span class="n">currencyFormat</span> <span class="o">=</span> <span class="nc">NumberFormat</span><span class="o">.</span><span class="na">getCurrencyInstance</span><span class="o">(</span><span class="n">countryLocale</span><span class="o">);</span>
<span class="nc">Currency</span> <span class="n">currency</span> <span class="o">=</span> <span class="nc">Currency</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">currencyLocale</span><span class="o">);</span>
<span class="n">currencyFormat</span><span class="o">.</span><span class="na">setCurrency</span><span class="o">(</span><span class="n">currency</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">currencyFormat</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="mf">1234.5f</span><span class="o">));</span>
<span class="c1">//1 234,50 GBP</span></code></pre></figure>
<p>If we dig a bit deeper, we can extract the format pattern:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">DecimalFormat</span> <span class="n">currencyFormat</span> <span class="o">=</span> <span class="o">(</span><span class="nc">DecimalFormat</span><span class="o">)</span> <span class="nc">NumberFormat</span><span class="o">.</span><span class="na">getCurrencyInstance</span><span class="o">(</span><span class="nc">Locale</span><span class="o">.</span><span class="na">FRANCE</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">currencyFormat</span><span class="o">.</span><span class="na">toPattern</span><span class="o">());</span>
<span class="c1">//#,##0.00 ¤</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">#,##0.00 ¤</code> is the French currency format, and it doesn’t depend on any currency. <code class="language-plaintext highlighter-rouge">¤</code> is the <a href="https://en.wikipedia.org/wiki/Currency_sign_(typography)">currency symbol</a> and behaves like a placeholder for the currency symbol or code.</p>
<h1 id="input-field">Input field</h1>
<p>Building our own currency input field is not very complicated. The main issue to solve is to know where to draw the currency symbol. Indeed, depending on the currency format, we have seen that the symbol can be either before the value or after the value. As we certainly don’t want to parse the format pattern, these four <a href="https://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html"><code class="language-plaintext highlighter-rouge">DecimalFormat</code></a> methods will be useful:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">getNegativePrefix()</code></li>
<li><code class="language-plaintext highlighter-rouge">getPositivePrefix()</code></li>
<li><code class="language-plaintext highlighter-rouge">getNegativeSuffix()</code></li>
<li><code class="language-plaintext highlighter-rouge">getPositiveSuffix()</code></li>
</ul>
<p>Here are some interesting values:</p>
<table>
<thead>
<tr>
<th> </th>
<th>NegativePrefix</th>
<th>PositivePrefix</th>
<th>NegativeSuffix</th>
<th>PositiveSuffix</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>France</td>
<td>-</td>
<td> </td>
<td>€</td>
<td>€</td>
<td>-1 234,50 €</td>
</tr>
<tr>
<td>Denmark</td>
<td>kr -</td>
<td>kr</td>
<td> </td>
<td> </td>
<td>kr -1.234,50</td>
</tr>
<tr>
<td>Netherlands</td>
<td>€</td>
<td>€</td>
<td>-</td>
<td> </td>
<td>€ 1.234,50-</td>
</tr>
<tr>
<td>United-Kingdom</td>
<td>-£</td>
<td>£</td>
<td> </td>
<td> </td>
<td>-£1,234.50</td>
</tr>
<tr>
<td>USA</td>
<td>($</td>
<td>$</td>
<td>)</td>
<td> </td>
<td>($1,234.50)</td>
</tr>
</tbody>
</table>
<p>Now all we have to do is to draw the prefix and postfix!</p>
<h1 id="conclusion">Conclusion</h1>
<p>With the help of just a few provided APIs, we have seen that formatting a currency can be easy. So don’t try and format currencies by hand!</p>
<p>For a few weeks, <a href="https://www.drivy.co.uk">Drivy</a> has been available in the United-Kingdom. Unlike the others European countries where Drivy operates, the United-Kingdom uses a different currency: the pound (£). We had to make some changes in our Android apps to support this.</p>
Mon, 20 Nov 2017 00:00:00 +0000
https://getaround.tech/multi-currency-java/
https://getaround.tech/multi-currency-java/Data quality checkersAntoine Augusti<p>At Drivy, we store, process and analyse hundreds of gigabytes of data in our production systems and our data warehouse. Data is of utmost importance to us because it makes our marketplace run and we use it <a href="https://drivy.engineering/from-member-voice-to-ux/">to continuously improve our service</a>.</p>
<p>Making sure that the data we store and use is what we expect is a challenge. We use multiple techniques to achieve this goal such as <a href="https://drivy.engineering/code_simplicity_introduction/">high standard coding practices</a> or <a href="https://drivy.engineering/best-practices-for-large-features/">checker jobs</a> we run on production data to make sure that our assumptions are respected.</p>
<h2 id="defining-data-quality">Defining data quality</h2>
<p>There are several research papers discussing the data quality dimensions as professionals have a hard time agreeing on the terminology. I found that <a href="https://www.whitepapers.em360tech.com/wp-content/files_mf/1407250286DAMAUKDQDimensionsWhitePaperR37.pdf">the article written by the DAMA UK Working Group</a> successfully defines 6 key dimensions that I summarize as follows:</p>
<ul>
<li><strong>Completeness</strong>: are all data items recorded? If something is mandatory, 100% completeness will be achieved. Sampling data does not achieve completeness for example.</li>
<li><strong>Consistency</strong>: can we match the same data across data stores?</li>
<li><strong>Timeliness</strong>: do we store data when the event occurred? For example, if we know that an event occurred 6 hours ago and we stored it only 1 hour ago, it could break a timeliness constraint.</li>
<li><strong>Uniqueness</strong>: do we have duplicate records? Nothing will be recorded more than once based upon how a record is identified.</li>
<li><strong>Validity</strong>: do we store data conforming to the syntax (format, type, range of values) of its definition? Storing a negative integer for a user’s age breaks the validity of the record for example.</li>
<li><strong>Accuracy</strong>: does the data describe the real-world? For example if a temperature sensor is malconfigured and reports wrong data points that are still within the accepted validity range, the data generated is not accurate.</li>
</ul>
<p>For example in a log database, uniqueness does not always need to be enforced. However, in another table aggregating these logs we might want to enforce the uniqueness dimension.</p>
<h2 id="data-quality-in-the-context-of-data-warehousing">Data quality in the context of data warehousing</h2>
<p>My main goal was to enforce a high quality of data in our data warehouse, which we fill with standard <a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">ETL processes</a>.</p>
<p>For our web application, we already have checker jobs (we talked about this <a href="https://drivy.engineering/best-practices-for-large-features/">in this blog post</a>) in the context of a monolith Rails application with MySQL databases. They are somewhat simpler: they run on a single database and check the quality of data we have control over because we wrote code to produce it. We can also afford to perform migrations or backfill in case we detect a corruption and want to fix the data.</p>
<p>When working with ETL processes and in the end a data warehouse, we have different needs. The main issue we face is that we pull data from various databases, third parties, APIs, spreadsheets, unreliable FTPs connections etc. Unfortunately, we have little or no control over what we fetch or collect from these external systems. Working with external datasources is <a href="https://marcgg.com/blog/2012/09/24/working-with-apis-facebook/">a hard challenge</a>.</p>
<p>We ingest raw data, we build aggregates and summaries, and we cross join data. Freshness depends on the source of the data and how we extract it.
We don’t want alerts on data that is already corrupted upstream (this point is debatable), but we want to know if an upstream datasource <em>gets</em> corrupted. We usually want to compare datasets side by side (especially when pulling from another database) to make sure that the consistency dimension is respected.</p>
<p>Overall, I find it hard to enforce a strict respect of all data quality dimensions with 100% confidence, as data we pull upstream will never fully respect what was advertised. Data quality checkers can help us in improving our data quality, make sure preconditions hold true and aim for better data quality in the long run.</p>
<h2 id="abstractions">Abstractions</h2>
<p>Now that we have a clearer idea about what data quality dimensions are and what we want to achieve, we can starting building something. My goal was to be able to perform checks to prove that data quality dimensions are respected. I had to come up with high-level abstractions to have a flexible library to work with and <a href="https://www.researchgate.net/profile/Tarek_Mahmoud5/publication/287571065_Automated_ETL_Testing_on_the_Data_Quality_of_a_Data_Warehouse/links/5677a2b008ae0ad265c71a3e/Automated-ETL-Testing-on-the-Data-Quality-of-a-Data-Warehouse.pdf">this research article</a> helped me.</p>
<p>My key components can be defined as follows:</p>
<blockquote>
<p>Data quality checks are performed at a specified interval on one or multiple datasets that are coming from various datasources, using predicates we define. Checks have a tolerance and trigger alerts on alert destinations with an alert level defined by the severity of the found errors.</p>
</blockquote>
<p>Let’s define each word used here:</p>
<ul>
<li><strong>Alert levels</strong>: define how important the error is</li>
<li><strong>Alerters</strong>: alert people or systems when errors are detected</li>
<li><strong>Checkers</strong>: perform predicate checks on datasets</li>
<li><strong>Parsers</strong>: create datasets from a source (parse a CSV file, read database records, call an API etc.)</li>
<li><strong>Tolerance levels</strong>: tolerate some errors on a check (number, percentage, known broken points)</li>
<li><strong>Escalation policies</strong>: switch alert destination depending on alert level</li>
<li><strong>Logger</strong>: logs failing datasets somewhere</li>
<li><strong>Clock</strong>: defines when a checker should be executed</li>
<li><strong>Scheduler</strong>: run checks when they are up for execution</li>
</ul>
<h3 id="checkers">Checkers</h3>
<p>Checkers are the most important components of the system. They actually perform the defined data quality checks on datasets. When implementing a new checker, you write a subclass from one of the abstract checkers supporting the core functionalities (extraction types, alert destinations, alert levels, logging etc.)</p>
<p>Available checkers:</p>
<ul>
<li><em>PredicateSingleDatasetChecker</em>: check that each element of the dataset respects a predicate</li>
<li><em>OffsetPredicateSingleDatasetChecker</em>: given a predicate, an offset, check that two elements separated by the given offset respect the predicate. This is very useful to compare time records for example</li>
<li><em>PredicateDoubleDatasetsChecker</em>: iterate on 2 datasets at the same time and check that the 2 records respect a predicate</li>
</ul>
<h3 id="scheduler">Scheduler</h3>
<p>We rely on <a href="https://airflow.incubator.apache.org/">Apache Airflow</a> to specify, schedule and run our tasks for our data platform. We therefore created a pipeline putting together the data quality checks library with Airflow tasks and scheduling capabilities to easily run checks.</p>
<p>The main pipeline is executed every 15 minutes. Each data-quality check is composed of 2 main tasks:</p>
<ul>
<li>a task with a <a href="https://pythonhosted.org/airflow/code.html#airflow.operators.ShortCircuitOperator"><em>ShortCircuitOperator</em></a> which determines if the quality check needs to be executed now or not. If the quality check is not up for running, the second task is skipped</li>
<li>a task with a <a href="https://pythonhosted.org/airflow/concepts.html#subdags"><em>SubDagOperator</em></a> to actually run the check: extract the dataset, run the checker and perform any alerting if needed.</li>
</ul>
<figure>
<img alt="Airflow DAG for data quality checks" src="/assets/posts/data-quality/dags.png" />
<figcaption>
Airflow directed acyclic graph in charge of running the various data quality checks
</figcaption>
</figure>
<figure>
<img alt="Airflow subdag" src="/assets/posts/data-quality/subdag.png" />
<figcaption>
Airflow sub graph of a quality check running on two datasets side by side
</figcaption>
</figure>
<h2 id="alerts">Alerts</h2>
<p>When a check is executed and detects a malfunction, we get alerted. For now on we only use Slack, but there is room for other alerters such as text messages, PagerDuty or emails.</p>
<p>When an alert triggers, we get to know what’s the alert, what’s the purpose of the associated check, how important the alert is with the number of falsy elements etc. Remember that alerts can have a certain level of tolerance - some errors can be tolerated - and different alert levels to help triage alerts. We get a quick view of data points which failed the check to have a rough idea about what’s going on, without jumping to the logs or looking immediately at the dataset.</p>
<figure>
<img alt="Sample alert message on Slack" src="/assets/posts/data-quality/alert.png" />
<figcaption>
Sample alert message on Slack showing a breach of SLA for data freshness
</figcaption>
</figure>
<p>If we need to investigate further, we can look at the logs in Airflow or inspect the raw dataset. We find it convenient to have alerts in Slack so that we can start threads explaining why an alert triggered and if we need to take actions.</p>
<h2 id="the-future">The future</h2>
<p>We’ve been using these data quality checks over the last 3 months and we’ve been really happy to have them. It makes us trust more our data, helps us detect issues or prove that assumptions are indeed always respected. It’s also a good opportunity to step up our data quality level: we can lower thresholds over time, review SLAs and put more pressure on the level of confidence we have in critical parts of our storage.</p>
<p>For now, we plan to add more checkers (we have currently 20-30 checkers) to see if we’re happy with what we have, improve it and keep building on it.</p>
<h3 id="open-source">Open source</h3>
<p>We thought about open sourcing what we built, but we think that it’s a bit too soon and we want to gain more confidence before publishing it on GitHub.</p>
<h3 id="ideas-and-thoughts">Ideas and thoughts</h3>
<p>If data quality is of interest to you and you want to react to this blog post, I would be thrilled to hear from you! Reach out <a href="https://twitter.com/AntoineAugusti">on my Twitter</a>.</p>
<h2 id="code-sample">Code sample</h2>
<p>To get an idea of what a data quality checker looks like, here is a sample quality check which checks if data is fresh enough for various tables in our data warehouse (Redshift). This class can easily be tested, to have automated tests proving that alerts trigger with specific datasets.</p>
<p>This class is complete enough so that Airflow can know how to extract data from Redshift, transform and run the check automatically.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="kn">import</span> <span class="n">datetime</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">timedelta</span>
<span class="kn">from</span> <span class="n">data_quality.alert_levels</span> <span class="kn">import</span> <span class="n">FailingElementsThresholds</span>
<span class="kn">from</span> <span class="n">data_quality.checkers</span> <span class="kn">import</span> <span class="n">PredicateSingleDatasetChecker</span>
<span class="kn">from</span> <span class="n">data_quality.tolerances</span> <span class="kn">import</span> <span class="n">LessThan</span>
<span class="kn">from</span> <span class="n">data_quality_checks.base_checkers.base</span> <span class="kn">import</span> <span class="n">BaseQualityCheck</span>
<span class="kn">from</span> <span class="n">data_quality_checks.base_checkers.base</span> <span class="kn">import</span> <span class="n">DatasetTypes</span>
<span class="kn">from</span> <span class="n">data_quality_checks.base_checkers.base</span> <span class="kn">import</span> <span class="n">ExtractionTypes</span>
<span class="kn">from</span> <span class="n">data_quality_checks.base_checkers.base</span> <span class="kn">import</span> <span class="n">ScheduleTypes</span>
<span class="k">class</span> <span class="nc">DataFreshness</span><span class="p">(</span><span class="n">BaseQualityCheck</span><span class="p">):</span>
<span class="c1"># Run a query on Redshift</span>
<span class="n">EXTRACTION_TYPE</span> <span class="o">=</span> <span class="n">ExtractionTypes</span><span class="p">.</span><span class="n">REDSHIFT_QUERY</span>
<span class="c1"># Dataset can be parsed from a CSV</span>
<span class="n">DATASET_TYPE</span> <span class="o">=</span> <span class="n">DatasetTypes</span><span class="p">.</span><span class="n">CSV</span>
<span class="n">SCHEDULE_TYPE</span> <span class="o">=</span> <span class="n">ScheduleTypes</span><span class="p">.</span><span class="n">CRON</span>
<span class="n">CRON_SCHEDULE</span> <span class="o">=</span> <span class="sh">'</span><span class="s">20,50 7-22 * * *</span><span class="sh">'</span>
<span class="k">def</span> <span class="nf">alert_level</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="c1"># 0-2: warning</span>
<span class="c1"># 2-3: error</span>
<span class="c1"># > 3: critical</span>
<span class="k">return</span> <span class="nc">FailingElementsThresholds</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">tolerance</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="c1"># Get notified as soon as we have a single issue</span>
<span class="k">return</span> <span class="nc">LessThan</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sh">'</span><span class="s">Check that data is fresh enough in various tables</span><span class="sh">'</span>
<span class="k">def</span> <span class="nf">checker</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">dataset</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Dummy</span><span class="p">(</span><span class="n">PredicateSingleDatasetChecker</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">dataset</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">options</span><span class="p">,</span> <span class="n">parent</span><span class="p">):</span>
<span class="nf">super</span><span class="p">(</span><span class="n">Dummy</span><span class="p">,</span> <span class="n">self</span><span class="p">).</span><span class="nf">__init__</span><span class="p">(</span>
<span class="n">dataset</span><span class="p">,</span> <span class="n">predicate</span><span class="p">,</span> <span class="n">options</span>
<span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">parent</span> <span class="o">=</span> <span class="n">parent</span>
<span class="k">def</span> <span class="nf">checker_name</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">parent</span><span class="p">.</span><span class="n">__class__</span><span class="p">.</span><span class="n">__name__</span>
<span class="k">def</span> <span class="nf">description</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">parent</span><span class="p">.</span><span class="nf">description</span><span class="p">()</span>
<span class="n">fn</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="n">e</span><span class="p">[</span><span class="sh">'</span><span class="s">last_update</span><span class="sh">'</span><span class="p">]</span> <span class="o">>=</span> <span class="n">self</span><span class="p">.</span><span class="nf">target_time</span><span class="p">(</span><span class="n">e</span><span class="p">[</span><span class="sh">'</span><span class="s">table_name</span><span class="sh">'</span><span class="p">])</span>
<span class="k">return</span> <span class="nc">Dummy</span><span class="p">(</span>
<span class="n">dataset</span><span class="p">,</span>
<span class="n">fn</span><span class="p">,</span>
<span class="n">self</span><span class="p">.</span><span class="nf">checker_options</span><span class="p">(),</span>
<span class="n">self</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">freshness_targets</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="n">conf</span> <span class="o">=</span> <span class="p">{</span>
<span class="mi">5</span><span class="p">:</span> <span class="n">config</span><span class="p">.</span><span class="n">FINANCE_TABLES</span><span class="p">,</span>
<span class="mi">8</span><span class="p">:</span> <span class="n">config</span><span class="p">.</span><span class="n">CORE_TABLES</span><span class="p">,</span>
<span class="mi">24</span><span class="p">:</span> <span class="n">config</span><span class="p">.</span><span class="n">NON_URGENT_TABLES</span>
<span class="p">}</span>
<span class="n">res</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">lag</span><span class="p">,</span> <span class="n">tables</span> <span class="ow">in</span> <span class="n">conf</span><span class="p">.</span><span class="nf">iteritems</span><span class="p">():</span>
<span class="k">for</span> <span class="n">table</span> <span class="ow">in</span> <span class="n">tables</span><span class="p">:</span>
<span class="n">res</span><span class="p">.</span><span class="nf">append</span><span class="p">({</span><span class="sh">'</span><span class="s">table</span><span class="sh">'</span><span class="p">:</span> <span class="n">table</span><span class="p">,</span> <span class="sh">'</span><span class="s">target</span><span class="sh">'</span><span class="p">:</span> <span class="nf">timedelta</span><span class="p">(</span><span class="n">hours</span><span class="o">=</span><span class="n">lag</span><span class="p">)})</span>
<span class="k">return</span> <span class="n">res</span>
<span class="k">def</span> <span class="nf">freshness_configuration</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">table</span><span class="p">):</span>
<span class="n">targets</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">freshness_targets</span><span class="p">()</span>
<span class="n">table_conf</span> <span class="o">=</span> <span class="p">[</span><span class="n">e</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">targets</span> <span class="k">if</span> <span class="n">e</span><span class="p">[</span><span class="sh">'</span><span class="s">table</span><span class="sh">'</span><span class="p">]</span> <span class="o">==</span> <span class="n">table</span><span class="p">]</span>
<span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">table_conf</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">raise</span> <span class="nb">KeyError</span>
<span class="k">return</span> <span class="n">table_conf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">target_time</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">table</span><span class="p">):</span>
<span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span>
<span class="n">lag</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">freshness_configuration</span><span class="p">(</span><span class="n">table</span><span class="p">)[</span><span class="sh">'</span><span class="s">target</span><span class="sh">'</span><span class="p">]</span>
<span class="k">return</span> <span class="n">now</span> <span class="o">-</span> <span class="n">lag</span>
<span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
<span class="n">parts</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">table_conf</span> <span class="ow">in</span> <span class="n">self</span><span class="p">.</span><span class="nf">freshness_targets</span><span class="p">():</span>
<span class="n">query</span> <span class="o">=</span> <span class="sh">'''</span>
<span class="s"> SELECT</span>
<span class="s"> MAX(</span><span class="sh">"</span><span class="s">{col}</span><span class="sh">"</span><span class="s">) last_update,</span>
<span class="s"> </span><span class="sh">'</span><span class="s">{table}</span><span class="sh">'</span><span class="s"> table_name</span>
<span class="s"> FROM </span><span class="sh">"</span><span class="s">{table}</span><span class="sh">"</span>
<span class="s"> </span><span class="sh">'''</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span>
<span class="n">col</span><span class="o">=</span><span class="n">table_conf</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">col</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">created_at</span><span class="sh">'</span><span class="p">),</span>
<span class="n">table</span><span class="o">=</span><span class="n">table_conf</span><span class="p">[</span><span class="sh">'</span><span class="s">table</span><span class="sh">'</span><span class="p">],</span>
<span class="p">)</span>
<span class="n">parts</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
<span class="n">the_query</span> <span class="o">=</span> <span class="sh">'</span><span class="s"> UNION </span><span class="sh">'</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span>
<span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="nf">remove_whitespace</span><span class="p">(</span><span class="n">the_query</span><span class="p">)</span></code></pre></figure>
<p>At Drivy, we store, process and analyse hundreds of gigabytes of data in our production systems and our data warehouse. Data is of utmost importance to us because it makes our marketplace run and we use it <a href="https://drivy.engineering/from-member-voice-to-ux/">to continuously improve our service</a>.</p>
Thu, 09 Nov 2017 00:00:00 +0000
https://getaround.tech/data-quality/
https://getaround.tech/data-quality/Sanitize your attributes through your form objectJean Anquetil<p>At Drivy, we use the <a href="https://github.com/solnic/virtus" target="_blank">Virtus gem</a> to build <a href="https://robots.thoughtbot.com/activemodel-form-objects" target="_blank">form objects</a> in our codebase. This lets us:</p>
<ul>
<li>Keep our business logic out of the Controller and Views</li>
<li>Deal with unpersisted attributes</li>
<li>Add specific validations instead of adding them directly in the model</li>
<li>Display custom data validations errors directly in the form</li>
<li>Use features from <code class="language-plaintext highlighter-rouge">ActiveModel::Model</code> by including it</li>
</ul>
<p>Sometimes, we have to sanitize user input: format the data, remove whitespaces and so on. Here is a convenient way to handle it with Virtus.</p>
<h1 id="using-coerce">Using #coerce</h1>
<p>Let’s imagine that we want to remove all the whitespaces from a VAT number recorded as a string. This is a pretty simple use case, but concepts will apply to more complex situation as well.</p>
<p><img src="/assets/posts/2017-10-17-sanitize-your-attributes/form_field.png" alt="User input" /></p>
<p>First we have to define a custom attribute object for the attribute we want to sanitize. It has to inherit from <code class="language-plaintext highlighter-rouge">Virtus::Attribute</code> in order to use the <code class="language-plaintext highlighter-rouge">coerce</code> method. Then, in this method, we just have to define the reformatting we want to perform.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">SanitizedVatNumber</span> <span class="o"><</span> <span class="no">Virtus</span><span class="o">::</span><span class="no">Attribute</span>
<span class="k">def</span> <span class="nf">coerce</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">value</span><span class="p">.</span><span class="nf">respond_to?</span><span class="p">(</span><span class="ss">:to_s</span><span class="p">)</span> <span class="p">?</span> <span class="n">value</span><span class="p">.</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="sr">/\s+/</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span> <span class="p">:</span> <span class="n">value</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Next, in your Virtus form object, we specify the <code class="language-plaintext highlighter-rouge">vat_number</code> attribute - the one we want to update - as a <code class="language-plaintext highlighter-rouge">SanitizedVatNumber</code>:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">CompanyForm</span>
<span class="n">attribute</span> <span class="ss">:vat_number</span><span class="p">,</span> <span class="no">SanitizedVatNumber</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>And there we have it! The <code class="language-plaintext highlighter-rouge">vat_number</code> will be sanitized once the form is submitted.</p>
<h1 id="testing-it-with-rspec">Testing it with Rspec</h1>
<p>It is also easy to add basic tests on this custom Virtus attribute, for instance by using Rspec:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">describe</span> <span class="no">SanitizedVatNumber</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:object</span><span class="p">)</span> <span class="p">{</span> <span class="n">described_class</span><span class="p">.</span><span class="nf">build</span><span class="p">(</span><span class="n">described_class</span><span class="p">)</span> <span class="p">}</span>
<span class="n">subject</span> <span class="p">{</span> <span class="n">object</span><span class="p">.</span><span class="nf">coerce</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">}</span>
<span class="n">context</span> <span class="s1">'when vat_number is nil'</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:value</span><span class="p">)</span> <span class="p">{</span> <span class="kp">nil</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">context</span> <span class="s1">'when vat_number has white spaces'</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:value</span><span class="p">)</span> <span class="p">{</span> <span class="s1">'EN XX 999 999 999'</span> <span class="p">}</span>
<span class="n">it</span> <span class="p">{</span> <span class="n">is_expected</span><span class="p">.</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="s1">'ENXX999999999'</span><span class="p">)}</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h1 id="conclusion">Conclusion</h1>
<p>You avoid giving too much responsibility to your form object, which would be the risk of sanitizing attributes directly inside the form. Plus, Virtus custom coercion can be reused across multiple forms, and lends itself well to be easily unit tested.</p>
<p>At Drivy, we use the <a href="https://github.com/solnic/virtus" target="_blank">Virtus gem</a> to build <a href="https://robots.thoughtbot.com/activemodel-form-objects" target="_blank">form objects</a> in our codebase. This lets us:</p>
Tue, 17 Oct 2017 00:00:00 +0000
https://getaround.tech/sanitize-your-attributes/
https://getaround.tech/sanitize-your-attributes/Evolution Of Our Continuous Delivery ProcessMarc G Gauthier<p>We’ve always valued releasing quickly, as unreleased code is <a href="https://en.wikipedia.org/wiki/Theory_of_constraints">basically inventory</a>. It slowly gathers dust and becomes outdated or costs time to be kept updated. Almost 3 years ago we published an article “<a href="https://blog.drivy.com/2015/01/27/drivy-500/">Drivy, version 500!</a>” on our main blog, so I feel now is the time to get more into the details of how we accomplish pushing a lot of new versions of the app to production.</p>
<h2 id="our-different-iterations">Our Different Iterations</h2>
<p>As in most cases, we keep <a href="https://en.wikipedia.org/wiki/Kaizen">改善</a> in mind and go for continuous improvement over one huge definitive solution straight away. We don’t try to build the perfectly automated system that handles all cases, when there are only 2 developers and no users. Probably obvious, but it’s something always worth repeating.</p>
<p>In this article I’ll try to explain chronologically all the steps we went through over the course of 5 years, so as to showcase the evolution of our tools and processes. Of course if you want to see how we do it now, go straight to the last step… but it might already be outdated by the time you read this!</p>
<h3 id="adding-tests--releasing-manually">Adding Tests & Releasing Manually</h3>
<p>Drivy used to be in PHP, but for <a href="https://blog.drivy.com/2013/03/22/au-revoir-php/">various reasons</a> we decided to move away from it and use Ruby on Rails. At this point we started creating automated tests for everything we were doing, and have kept adding to our test suite since then. This is very important because without automated tests, you will never be able to release often whilst avoiding major bugs.</p>
<figure>
<img alt="First specs added in 2012" src="/assets/posts/releasing/specs.jpg" />
<center><small>First specs added in 2012</small></center>
</figure>
<p>As far as pushing to production was concerned, we tended to basically do nothing about automation, and would just release manually after running to the specs on our machines. This was fine because we were not a lot of developers and therefore didn’t move too quickly, and there were not too many specs yet so the build was fast.</p>
<p>Note that I don’t mention <a href="http://capistranorb.com/">Capistrano</a> or similar tools. This is because we are hosted on Heroku, and they provide their own simple toolbelt for deployment. However, if we were not using this provider, I feel like the minimal first step would be setting up something like Capistrano.</p>
<h3 id="improved-process">Improved Process</h3>
<p>Quickly we developed a simple process loosely inspired by <a href="https://en.wikipedia.org/wiki/Kanban">Kanban</a> and <a href="http://marcgg.com/blog/2014/09/17/huboard-kanban-board-github/">based on Github tags and Huboard</a> to be able to visualize progress. This would allow us to quickly see if a given commit could be deployed or not, and therefore to release faster without the need for additional back and forth.</p>
<p>To do so we started linking every commit to issues and used tags such as:</p>
<ul>
<li>Backlog: This needs to be done eventually</li>
<li>Todo: This needs to be done soon</li>
<li>Going: Someone is currently working on it</li>
<li>On Staging: The ticket is mostly completed and is being tested on the staging environment</li>
<li>Ready For Prod: The ticket has been tested on staging and works as expected</li>
<li>On Prod: The ticket has been deployed to production</li>
</ul>
<p>Closing an issue would mean “the ticket has been proved to be successful in production, with no bugs or regressions of any kind”.</p>
<figure>
<img alt="Huboard a few years ago" src="/assets/posts/releasing/kanban.jpg" />
<center><small>Huboard a few years ago</small></center>
</figure>
<p>Since then we moved from Huboard to <a href="https://waffle.io/">waffle</a> which was more stable and quicker, but there are tons of options out there nowadays including <a href="https://www.zenhub.com/">Zenhub</a> or <a href="https://help.github.com/articles/about-project-boards/">Github project boards</a>.</p>
<h4 id="note-on-documentation">Note On Documentation</h4>
<p>At this point we were already half a dozen developers and we needed simple ways to share information with newcomers. We started adding and maintaining more documentation on the important parts of the release process.</p>
<figure>
<img alt="Documentation" src="/assets/posts/releasing/doc.png" />
<center><small>Release documentation from the Drivy wiki</small></center>
</figure>
<h4 id="getting-migrations-right">Getting Migrations Right</h4>
<p>Releasing code might also imply changing the database schema, which can get tricky. Rails makes it easier since it uses migrations, but we’ve added a couple of ground rules in order to be effective:</p>
<ul>
<li>
<p>Never edit/remove a migration file that has been merged into master. Add new migrations instead.</p>
</li>
<li>
<p>Commit the migration file (and schema.rb) in its own commit if the migration needs to be done in two steps. There shouldn’t be other files in the commit. This makes it easier to do a zero-downtime deployment.</p>
</li>
</ul>
<p>There are a lot of articles and <a href="https://github.com/soundcloud/lhm">resources</a> on how to do <a href="http://jakeyesbeck.com/2016/02/07/how-to-remove-a-column-with-zero-downtime-in-ruby-on-rails/">zero downtime deployments</a>, so I won’t get into details here, but know that’s something required in order to ship fast.</p>
<h3 id="jenkins">Jenkins</h3>
<p>After a bit we added <a href="https://jenkins.io/">Jenkins</a>, a continuous integration server. It would run on a spare mac mini in the open space. This was a good improvement because it would help make sure we always ran tests and that any red build would be noticed.</p>
<p>Jenkins also had the great advantage of deploying to our staging environment right after a succesfull build.</p>
<h3 id="shell-deploy-script">Shell Deploy Script</h3>
<p>Since we already had a documented flow to release and a Jenkins server, it was only a matter of time until we could automate it. The thing that made us decide to automate was the fact that, with people joining the team, we were afraid that it would slow down releases and create larger and therefore riskier releases.</p>
<p>To achieve this we created a simple shell script that could be run by Jenkins at the press of a button. It would go through all the documented steps to release, except automatically!</p>
<figure>
<img alt="Release script" src="/assets/posts/releasing/script.png" />
<center><small>Excerpt from the release script</small></center>
</figure>
<p>Note that all this time we would often look back at the data and see how we were doing in term of number of releases.</p>
<figure>
<img alt="Releases" src="/assets/posts/releasing/release_rythm.png" />
<center><small>Releases per day in 2014 using git logs and Excel</small></center>
</figure>
<h3 id="feature-flippers--soft-releases">Feature Flippers & Soft Releases</h3>
<p>Releasing quickly proved to be steadily improving the way we worked, and reduced the risk of bugs. However there were cases where we could not deploy to all users right away. To deal with this issue, we added a feature flipper feature using the appropriately named <a href="https://github.com/jnunemaker/flipper">flipper gem</a>.</p>
<figure>
<img alt="Releases" src="/assets/posts/releasing/flipper.png" />
<center><small>UI to decide how to make a feature available to our users</small></center>
</figure>
<p>This allowed us to release code that we didn’t intend to use right away, or that we wanted to offer only to a subset of users. It was a great way to decorrelate the “technical” release from the “product” release.</p>
<h3 id="circleci--the-drivy-cli">CircleCI & the Drivy CLI</h3>
<h4 id="the-limits-of-jenkins">The Limits of Jenkins</h4>
<p>After a while with this setup, we started to see some limitations. We didn’t want to invest a lot of time managing Jenkins, but the machine would sometime go down, there would be random hardware issues and making sure to apply software updates were a pain.</p>
<p>We decided not to invest more energy into Jenkins and instead move to a cloud solution. At this time <a href="https://circleci.com/">CircleCI</a> looked like a great option. So after properly benchmarking all other competitors, we dropped Jenkins and started using CircleCI.</p>
<p>CircleCI also provides additional functionalities out of the box. One particularly interesting feature is to add multiple containers in order to increase parallelization when running specs, thereby speeding up the test suite.</p>
<h4 id="adding-a-command-line-tool">Adding a Command Line Tool</h4>
<p>Since CircleCI didn’t provide a way to manually trigger a release, we had to build a bit of instrumentation and it took the form of a command line interface tool.</p>
<figure>
<img alt="Releases" src="/assets/posts/releasing/cli.png" />
</figure>
<p>This was fairly easy to develop using <a href="https://github.com/mdub/clamp">clamp</a> and we integrated it as a gem in our project so that every developer could release easily.</p>
<h4 id="adding-more-developer-tools">Adding More Developer Tools</h4>
<p>When needed, we would build small tools like <a href="https://github.com/watsonbox/chrome-github-shipit">a chrome extension</a> to be able to better visualize in github was was about to get released using a URL looking like this:</p>
<p><code class="language-plaintext highlighter-rouge">https://github.com/ORG/REPO/compare/last_release...master</code></p>
<h3 id="improving-bug-management">Improving Bug Management</h3>
<p>As we grew and shipped faster, we needed to make sure we weren’t introducing regressions. We were adding automated tests of course, but this doesn’t prevent every possible issue, so we improved the way we were monitoring and fixing bugs.</p>
<p>If you’d like to know more about this, we actually have an entire article dedicated to it: <a href="https://drivy.engineering/bug-management/">Managing Bugs at Drivy</a>.</p>
<h3 id="improving-the-cli">Improving the CLI</h3>
<p>As time passed we improved with a Slack integration for notifications, a GitHub integration for automatic updates on issues and much more.</p>
<figure>
<img alt="Slack Integration" src="/assets/posts/releasing/cli_slack.png" />
</figure>
<figure>
<img alt="Slack Integration" src="/assets/posts/releasing/cli_update.png" />
</figure>
<p>This proved to be very useful and made the act of releasing more or less painless.</p>
<h3 id="simplifying-the-release-process">Simplifying The Release Process</h3>
<p>We were using <a href="https://datasift.github.io/gitflow/IntroducingGitFlow.html">git-flow</a>, but it felt way too complicated compared to what we actually needed. We decided to streamline the process, making releases even simpler to understand. This was detailed in this article: “<a href="http://marcgg.com/blog/2016/02/22/git-flow-heroku-pipelines/">Simple Git Flow With Heroku Pipelines</a>”.</p>
<figure>
<img alt="Git flow at Drivy" src="/assets/posts/releasing/git-flow-drivy.jpg" />
</figure>
<p>We also worked on making even smaller incremental releases than before, splitting work into individual and releasable commits. This made releases easier, and if you’d like to know more you can check out this article: “<a href="https://drivy.engineering/best-practices-for-large-features/">Best Practices for Large Features</a>”</p>
<h3 id="better-data">Better Data</h3>
<p>Since we prefer looking at data rather than staying in the dark, we added more information about the different steps of our release process. This way we now have a nice graph in <a href="https://grafana.com/">Grafana</a> with the number of releases we are making per week:</p>
<figure>
<img alt="Releases count in Grafana" src="/assets/posts/releasing/grafana.png" />
</figure>
<h3 id="improving-organization">Improving Organization</h3>
<p>As we grew, the engineering team got larger and it lead to reduced <a href="https://pragdave.me/blog/2014/03/04/time-to-kill-agile.html">agility</a>. We worked on a new organisation based on <a href="https://labs.spotify.com/2014/03/27/spotify-engineering-culture-part-1/">Spotify’s squads</a> and it helped us move even faster than before, which was visible in the number of releases.</p>
<figure>
<img alt="Spotify" src="/assets/posts/releasing/spotify.jpg" />
<center><small>Excerpt from Spotify's <a href="https://vimeo.com/85490944">presentation video</a> about Squads</small></center>
</figure>
<h3 id="circleci-2--docker">CircleCI 2 & Docker</h3>
<p>After years spent adding on to the test suite, we started to feel that the build time was slowing us down, clocking in at approximatively 15 minutes. At this point CircleCI released a <a href="https://circleci.com/docs/2.0/">new version</a> that allowed us to tweak our build better thanks to <a href="https://www.docker.com/">Docker</a>, so we integrated it and saw great improvements on build speed.</p>
<figure>
<img alt="Drivy + Docker" src="/assets/posts/releasing/docker.png" />
</figure>
<h3 id="release-tool">Release Tool</h3>
<p>Once again growth caused us to change our way of working. Now with more developers than ever in the team, there are more questions to be answered: about access rights, enforcing certain constraints, making internal contributions to the tooling easier…</p>
<p>This is why we introduced our new Drivy Tool app, strongly inspired by Shopify’s <a href="https://shopifyengineering.myshopify.com/blogs/engineering/introducing-shipit">shipit</a>. This allowed us to better control credentials and improve onboarding as there is nothing to install: just login using Github and use the app!</p>
<figure>
<img alt="Drivy tools" src="/assets/posts/releasing/tools.png" />
</figure>
<h2 id="what-about-micro-services">What About Micro Services?</h2>
<p>Splitting the app into a lot of micro services could improve release rate as well. However this is quite costly to do, so we plan on extracting services on an as-we-go and if-appropriate basis. We don’t feel any pain (yet), so there is no need to make a big technical move.</p>
<h2 id="current-state-of-affairs">Current State Of Affairs</h2>
<p>As you can see we went through a lot of iterations, involving new tools, changes in processes and more. Nowadays we release to production close to 10 times a day, with very few regressions.</p>
<p>I can’t say enough how central a good test suite is to any continuous integration process. If you can’t catch regressions or new bugs quickly, there is no chance you can implement such a process.</p>
<p>Same goes for good and simple processes. There’s no need to go overboard with <a href="http://marcgg.com/blog/2014/07/28/company-process-red-tape/">red tape</a>… but effective, structured and documented ways of doing things will help with productivity. It’s also great for onboarding new and more junior developers.</p>
<p>We’ve always valued releasing quickly, as unreleased code is <a href="https://en.wikipedia.org/wiki/Theory_of_constraints">basically inventory</a>. It slowly gathers dust and becomes outdated or costs time to be kept updated. Almost 3 years ago we published an article “<a href="https://blog.drivy.com/2015/01/27/drivy-500/">Drivy, version 500!</a>” on our main blog, so I feel now is the time to get more into the details of how we accomplish pushing a lot of new versions of the app to production.</p>
Wed, 27 Sep 2017 00:00:00 +0000
https://getaround.tech/continuous-integration/
https://getaround.tech/continuous-integration/Setting up Vim for React developmentVictor Mours<p>We’ve recently introduced <a href="https://preactjs.com/">Preact</a> to our Rails stack at Drivy, and the results have been rather satisfying so far.
As a Vim lover, I was curious to see how to go about setting up Vim React or React-like project. As usual, the wealth of plugins out there didn’t disappoint.</p>
<p>Here’s the setup that I came up with:</p>
<h1 id="syntax-highlighting">Syntax Highlighting</h1>
<p>Let’s start with the basics, and get some syntax highlighting for JavaScript and JSX by adding these to the plugins section of your <code class="language-plaintext highlighter-rouge">.vimrc</code>:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Plug</span> <span class="s1">'pangloss/vim-javascript'</span>
<span class="no">Plug</span> <span class="s1">'mxw/vim-jsx'</span></code></pre></figure>
<p>I’m assuming here that you’re using <a href="https://github.com/junegunn/vim-plug">vim-plug</a> as your plugin manager. If you’re unsure about how to install these with another plugin manager, check out the README from the respective repos.</p>
<h1 id="emmet-for-easier-jsx">Emmet for easier JSX</h1>
<p>In a Rails environment, I’m mostly used to languages such as Slim or Haml which simplify writing HTML, so going back to writing closing tags in JSX felt a little tedious.
Fortunately, you can get rid of some of the grunt work with <a href="https://github.com/mattn/emmet-vim">Emmet-vim</a>, which enables you to expand your CSS selectors into HTML (or JSX) on the fly.</p>
<p>For example, you could type</p>
<figure class="highlight"><pre><code class="language-haml" data-lang="haml"><span class="nt">%h2</span><span class="nf">#tagline</span><span class="nc">.hero-text</span></code></pre></figure>
<p>and then expand it to</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><h2</span> <span class="na">id=</span><span class="s">"tagline"</span> <span class="na">className=</span><span class="s">"hero-text"</span><span class="nt">></h2></span></code></pre></figure>
<p>in just two keystrokes.</p>
<p>Let’s install it:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Plug</span> <span class="s1">'mattn/emmet-vim'</span></code></pre></figure>
<p>and then add this to your <code class="language-plaintext highlighter-rouge">.vimrc</code>:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span> <span class="n">g</span><span class="ss">:user_emmet_leader_key</span><span class="o">=</span><span class="s1">'<Tab>'</span>
<span class="n">let</span> <span class="n">g</span><span class="ss">:user_emmet_settings</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">\</span> <span class="s1">'javascript.jsx'</span> <span class="p">:</span> <span class="p">{</span>
<span class="p">\</span> <span class="s1">'extends'</span> <span class="p">:</span> <span class="s1">'jsx'</span><span class="p">,</span>
<span class="p">\</span> <span class="p">},</span>
<span class="p">\}</span></code></pre></figure>
<p>Give it a try: in insert mode, type <code class="language-plaintext highlighter-rouge">p.description</code>, and then hit <code class="language-plaintext highlighter-rouge">Tab-,</code> (without leaving insert mode). It will expand as <code class="language-plaintext highlighter-rouge"><p className="description"></p></code>. Note that this is using the JSX <code class="language-plaintext highlighter-rouge">className</code> syntax, thanks to the tweak on <code class="language-plaintext highlighter-rouge">user_emmet_settings</code>.</p>
<h1 id="syntax-checking">Syntax checking</h1>
<p>Syntastic has been the go-to solution for syntax checking in Vim for a while, but it has the major flaw of being synchronous.
That means that you can’t do anything - not even move your cursor - while it is running.
For large files, this gets annoying rather quickly. The good news is that Vim now has support for async tasks, and you can switch to <a href="https://github.com/w0rp/ale">Ale</a>, which is short for Asynchronous Lint Engine.
You will never be interrupted by your linter again, hurray!</p>
<p>Arguably, this isn’t specific to React, but since you’ll need syntax checking for JSX, it’s a good opportunity to improve your overall setup.</p>
<p>Installing Ale is nothing unexpected:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Plug</span> <span class="s1">'w0rp/ale'</span></code></pre></figure>
<p>Of course, Ale is only the glue between Vim and the actual syntax checker that runs under the hood, which in this case would be ESLint.</p>
<p>Here’s how to install ESLint:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">yarn</span> <span class="n">add</span> <span class="o">--</span><span class="n">dev</span> <span class="n">eslint</span> <span class="n">babel</span><span class="o">-</span><span class="n">eslint</span> <span class="n">eslint</span><span class="o">-</span><span class="n">plugin</span><span class="o">-</span><span class="n">react</span></code></pre></figure>
<p>and then configure it by runnning:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">eslint</span> <span class="o">--</span><span class="n">init</span></code></pre></figure>
<p>This will create an <code class="language-plaintext highlighter-rouge">.eslintrc</code> file, which you should check in to version control so that everybody is using the same style guide. You may want to have a chat with the other people working on your project, to make sure everybody agrees on which rules you’ll enforce.</p>
<p>Ale works out of the box with ESLint, so there’s no further setup needed. However, I found Ale more pleasant to use with a couple tweaks in my vimrc:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">let</span> <span class="n">g</span><span class="ss">:ale_sign_error</span> <span class="o">=</span> <span class="s1">'●'</span> <span class="s2">" Less aggressive than the default '>>'</span>
<span class="s2">let g:ale_sign_warning = '.'</span>
<span class="s2">let g:ale_lint_on_enter = 0 "</span> <span class="no">Less</span> <span class="n">distracting</span> <span class="k">when</span> <span class="n">opening</span> <span class="n">a</span> <span class="n">new</span> <span class="n">file</span></code></pre></figure>
<h1 id="autoformatting">Autoformatting</h1>
<p>Ok, this is the best part. You may know about <a href="https://github.com/prettier/prettier">Prettier</a>, an “opinionated code formatter”, which will reformat your Javascript code from scratch, much like <code class="language-plaintext highlighter-rouge">gofmt</code> does for Go.</p>
<p>Having Prettier run each time that you save a file is surprisingly satisfying: you’ll basically never have to think about formatting again.
After using it for a couple hours, I even realized my way of writing was a bit different: I was just typing unformatted code, and trusting Prettier to make it look good.
That’s its killer feature: you get to focus on what your code does, not how it’s written.</p>
<p>Once again, this will be useful for all your JS projects, not just React ones, so let’s get the setup going:</p>
<p>First, let’s install prettier:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="err">$</span> <span class="n">yarn</span> <span class="n">add</span> <span class="o">--</span><span class="n">dev</span> <span class="n">prettier</span> <span class="n">eslint</span><span class="o">-</span><span class="n">config</span><span class="o">-</span><span class="n">prettier</span> <span class="n">eslint</span><span class="o">-</span><span class="n">plugin</span><span class="o">-</span><span class="n">prettier</span></code></pre></figure>
<p>Now, you should be able to run <code class="language-plaintext highlighter-rouge">eslint --fix src/App.js</code>, and <code class="language-plaintext highlighter-rouge">src/App.js</code> will be reformatted automatically.</p>
<p>Good, now let’s make that happen in vim each time you save a file. A naive way of doing this would just be to set an autocommand to run ESLint, but that would have the downside of being synchronous.
Rather than digging into Vim’s async job api, the easiest way of doing this is to use <code class="language-plaintext highlighter-rouge">asyncrun</code>, a plugin to easily run shell commands in the background.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Plug</span> <span class="s1">'skywind3000/asyncrun.vim'</span></code></pre></figure>
<p>And then you can add that sweet sweet autocommand.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">autocmd</span> <span class="no">BufWritePost</span> <span class="o">*</span><span class="p">.</span><span class="nf">js</span> <span class="no">AsyncRun</span> <span class="o">-</span><span class="n">post</span><span class="o">=</span><span class="n">checktime</span> <span class="p">.</span><span class="nf">/</span><span class="n">node_modules</span><span class="o">/</span><span class="p">.</span><span class="nf">bin</span><span class="o">/</span><span class="n">eslint</span> <span class="o">--</span><span class="n">fix</span> <span class="o">%</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">-post=checktime</code> option reloads the buffer from the file after the command is done running.</p>
<p>However, this does bring an issue: each time you tweak a file, the whole thing will be reformatted, which might make your git diff a bit unreadable.
Here at Drivy, we’ve decided to bite the bullet and run prettier on our whole JS codebase, so that the styling would be up to date on the whole app.
It was a big commit, but everything went smoothly, and we now have a consistent and pleasant style across the codebase.</p>
<h1 id="closing-thoughts">Closing thoughts</h1>
<p>This config is working pretty well for me, but as ever with Vim, it’s always possible to go deeper and find other improvements.
If you do or if you have any questions, feel free to <a href="https://twitter.com/victormours">ping me on twitter</a>.</p>
<p>Happy coding with Vim and (p)React!</p>
<p>We’ve recently introduced <a href="https://preactjs.com/">Preact</a> to our Rails stack at Drivy, and the results have been rather satisfying so far.
As a Vim lover, I was curious to see how to go about setting up Vim React or React-like project. As usual, the wealth of plugins out there didn’t disappoint.</p>
Tue, 12 Sep 2017 00:00:00 +0000
https://getaround.tech/setting-up-vim-for-react/
https://getaround.tech/setting-up-vim-for-react/Code simplicity - Reading levelsNicolas Zermati<p>When we write code, who is it for? Is it for the machine? It is the machine that
will parse and run your code. Is it for the next developer? It is that person that
will spend time reading and updating the code. Is it for the business? After all,
the code wouldn’t exist if it had no purpose. Obviously I think code targets all
of them.</p>
<p>The goal is to not only make your code understandable by the machine, but also to
your future self and to the business itself. This isn’t an easy thing. In this
article I only aim to go past the machine-readable to reach the next developer
level. To do that, let’s refactor a small fictive class…</p>
<h3 id="initial-code">Initial code</h3>
<p>In the <a href="/code_simplicity_command_pattern">previous article</a> of <a href="/code_simplicity_introduction">the serie</a>, I started extracting a
chunk of code from a Rails’ controller to an external object. I did it in a very
simple way and here is the result:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ConfirmOrder</span> <span class="o"><</span> <span class="no">Command</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">payment_token</span><span class="p">,</span> <span class="ss">notify: </span><span class="kp">true</span><span class="p">)</span>
<span class="vi">@order</span> <span class="o">=</span> <span class="n">order</span>
<span class="vi">@payment_token</span> <span class="o">=</span> <span class="n">payment_token</span>
<span class="vi">@notify</span> <span class="o">=</span> <span class="n">notify</span>
<span class="k">end</span>
<span class="n">validate</span> <span class="k">do</span>
<span class="n">payment</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">)</span>
<span class="k">if</span> <span class="o">!</span><span class="n">payment</span><span class="p">.</span><span class="nf">valid?</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_token_invalid</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">||</span> <span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_amount_mismatch</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">perform</span> <span class="k">do</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">).</span><span class="nf">capture!</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
<span class="n">invoice</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">invoices</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">amount: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span>
<span class="ss">currency: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span><span class="p">,</span>
<span class="p">})</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
<span class="n">invoice</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">product_id: </span><span class="n">item</span><span class="p">.</span><span class="nf">product_id</span><span class="p">,</span>
<span class="ss">quantity: </span><span class="n">item</span><span class="p">.</span><span class="nf">quantity</span><span class="p">,</span>
<span class="ss">unit_price: </span><span class="n">item</span><span class="p">.</span><span class="nf">unit_price</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :confirmed</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="vi">@notify</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">preparation_details</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">confirmation</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">available_invoice</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="k">end</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">CaptureError</span> <span class="o">=></span> <span class="n">error</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_capture_error</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>It is machine-readable. Does that mean that you can read that easily? Nope…
I explain a bit what the code is doing in the <a href="/code_simplicity_command_pattern">other article</a> but it
isn’t enough and more importantly, do we want to maintain an up to date
documentation for everything?</p>
<h3 id="use-the-method-luke">Use the method Luke</h3>
<p>One of the technique I use is to abstract things in methods. As we do with
variable, picking relevant name could create a narrative that’s much easier
to follow.</p>
<p>For instance, let’s look at that <code class="language-plaintext highlighter-rouge">validate</code> block, starting with this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">||</span> <span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_amount_mismatch</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>We don’t want to clutter the reader’s mind with the detail of the conditional.
It doesn’t even fit on my screen! We could instead wrap this in a method:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_amount_mismatch</span><span class="p">)</span> <span class="k">unless</span> <span class="n">payment_matches_sales_quote?</span></code></pre></figure>
<p>This doesn’t put all the details in the <code class="language-plaintext highlighter-rouge">validate</code> block. And I think it is for
the best because when I read that block, I would like to have an overview of
what’s validated, not every single implementation details.</p>
<p>We could apply the same for the other conditional and get this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">validate</span> <span class="k">do</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_token_invalid</span><span class="p">)</span> <span class="k">unless</span> <span class="n">valid_payment_token?</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_amount_mismatch</span><span class="p">)</span> <span class="k">unless</span> <span class="n">payment_amount_matches_sales_quote?</span>
<span class="k">end</span></code></pre></figure>
<p>To make this happen, we need to define 3 private methods:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="kp">private</span>
<span class="k">def</span> <span class="nf">payment</span>
<span class="vi">@payment</span> <span class="o">||=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">valid_payment_token?</span>
<span class="n">payment</span><span class="p">.</span><span class="nf">valid?</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">payment_matches_sales_quote?</span>
<span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">==</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">&&</span>
<span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">==</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="k">end</span></code></pre></figure>
<p>After that we end up with more lines in the file but the <code class="language-plaintext highlighter-rouge">validate</code> block can
now provide a faster understanding to anyone reading the class.</p>
<p>It is possible to go to an higher level of narrative with something like:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">validate</span> <span class="k">do</span>
<span class="n">payment_token_must_be_valid</span>
<span class="n">payment_amount_must_match_sales_quote</span>
<span class="k">end</span></code></pre></figure>
<p>You’re the judge of the abstraction level you want to give. The first refactoring
uses <code class="language-plaintext highlighter-rouge">unless</code>. It is understandable by any Ruby developer. The second version could be
understood even by the business person asking for that feature.</p>
<h3 id="taking-a-few-steps-further">Taking a few steps further</h3>
<p>If we continue to apply this to the <code class="language-plaintext highlighter-rouge">perform</code> block, we could end up with something
like:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">perform</span> <span class="k">do</span>
<span class="n">capture_payment!</span>
<span class="n">create_invoice_and_update_status</span>
<span class="n">send_notifications</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">CaptureError</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_capture_error</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>If you’re interested in the <code class="language-plaintext highlighter-rouge">create_invoice_and_update_status</code>, you’re free to
dig deeper, but if you don’t you have the choice not to bother with the details.</p>
<h3 id="conclusion">Conclusion</h3>
<p>By creating different narratives you can optimize for different targets.
Targeting the business forces you to think in the same mindset which has great
communication benefits.</p>
<p><em>You can find the gist of the code <a href="https://gist.github.com/nicoolas25/d3297e9e10e870787c6a40431c2ffa88">here</a></em></p>
<p>When we write code, who is it for? Is it for the machine? It is the machine that
will parse and run your code. Is it for the next developer? It is that person that
will spend time reading and updating the code. Is it for the business? After all,
the code wouldn’t exist if it had no purpose. Obviously I think code targets all
of them.</p>
Fri, 01 Sep 2017 00:00:00 +0000
https://getaround.tech/code_simplicity_reading_levels/
https://getaround.tech/code_simplicity_reading_levels/Code simplicity - Command patternNicolas Zermati<p>The command pattern is sometimes called a service object, an operation, an action,
and probably more names that I’m not aware of. Whatever the name we gave it,
the purpose of such a pattern is rather simple: take a business action and put it
behind an object with a simple interface.</p>
<h3 id="a-controllers-action-doing-it-all">A controller’s action doing it all</h3>
<p>One of the most common use case I encounter for this pattern is to get business
logic out of MVC’s controllers. For instance, in a Rails application, an action
responds to a single HTTP call using a <code class="language-plaintext highlighter-rouge">POST</code>, a <code class="language-plaintext highlighter-rouge">PATCH</code>, or a <code class="language-plaintext highlighter-rouge">PUT</code> verb and
semantic. It means that those actions are intended to update the application’s
state.</p>
<p>The following example takes an action to illustrate the situation. The goal of
the confirm action is to complete an order. Completing an order follow those
steps:</p>
<ol>
<li>validate that the payment amount is correct,</li>
<li>pay the order,</li>
<li>create an invoice using the existing sales quote,</li>
<li>update the state of the order, and</li>
<li>send notifications.</li>
</ol>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">OrdersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">confirm</span>
<span class="n">order</span> <span class="o">=</span> <span class="n">current_user</span><span class="p">.</span><span class="nf">orders</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:id</span><span class="p">))</span>
<span class="n">payment</span> <span class="o">=</span> <span class="n">order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">token: </span><span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:payment_token</span><span class="p">))</span>
<span class="k">if</span> <span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">!=</span> <span class="n">order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">||</span> <span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">!=</span> <span class="n">order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="k">raise</span> <span class="no">Payment</span><span class="o">::</span><span class="no">MismatchError</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">payment</span><span class="p">,</span> <span class="n">order</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">payment</span><span class="p">.</span><span class="nf">capture!</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
<span class="n">invoice</span> <span class="o">=</span> <span class="n">order</span><span class="p">.</span><span class="nf">invoices</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">amount: </span><span class="n">order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span>
<span class="ss">currency: </span><span class="n">order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span><span class="p">,</span>
<span class="p">})</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
<span class="n">invoice</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">product_id: </span><span class="n">item</span><span class="p">.</span><span class="nf">product_id</span><span class="p">,</span>
<span class="ss">quantity: </span><span class="n">item</span><span class="p">.</span><span class="nf">quantity</span><span class="p">,</span>
<span class="ss">unit_price: </span><span class="n">item</span><span class="p">.</span><span class="nf">unit_price</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="n">order</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :confirmed</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">preparation_details</span><span class="p">(</span><span class="n">order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">confirmation</span><span class="p">(</span><span class="n">order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">available_invoice</span><span class="p">(</span><span class="n">order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:success</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.success"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="n">invoice_path</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">MismatchError</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_amount_mismatch"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">CaptureError</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_capture_error"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">rescue</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">RecordInvalid</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_token_invalid"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>There are more or less obvious issues in that implementation. Let’s see how much
extracting that logic could help.</p>
<h3 id="moving-out">Moving out</h3>
<p>The first step of the extracting process is simple: take the content of the action, put
it in an object and call this object from the controller.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">OrdersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">confirm</span>
<span class="no">ConfirmOrder</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">find</span> <span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:id</span><span class="p">),</span>
<span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:payment_token</span><span class="p">)</span>
<span class="p">).</span><span class="nf">perform</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:success</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.success"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="n">invoice_path</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">MismatchError</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_amount_mismatch"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">CaptureError</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_capture_error"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">rescue</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">RecordInvalid</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.payment_token_invalid"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">ConfirmOrder</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">payment_token</span><span class="p">)</span>
<span class="vi">@order</span> <span class="o">=</span> <span class="n">order</span>
<span class="vi">@payment_token</span> <span class="o">=</span> <span class="n">payment_token</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="n">payment</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">)</span>
<span class="k">if</span> <span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">||</span> <span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="k">raise</span> <span class="no">Payment</span><span class="o">::</span><span class="no">MismatchError</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">payment</span><span class="p">,</span> <span class="vi">@order</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">payment</span><span class="p">.</span><span class="nf">capture!</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
<span class="n">invoice</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">invoices</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">amount: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span>
<span class="ss">currency: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span><span class="p">,</span>
<span class="p">})</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
<span class="n">invoice</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">product_id: </span><span class="n">item</span><span class="p">.</span><span class="nf">product_id</span><span class="p">,</span>
<span class="ss">quantity: </span><span class="n">item</span><span class="p">.</span><span class="nf">quantity</span><span class="p">,</span>
<span class="ss">unit_price: </span><span class="n">item</span><span class="p">.</span><span class="nf">unit_price</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :confirmed</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">preparation_details</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">confirmation</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">available_invoice</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>It seems to be more complicated than before. In some way it is since there is one
extra level of indirection to the <code class="language-plaintext highlighter-rouge">ConfirmOrder</code> object now. Despite that, this
basic extraction provides interesting benefits such as:</p>
<ul>
<li>focusing the controller on fetching the parameters and handling the response,</li>
<li>reusing the <code class="language-plaintext highlighter-rouge">ConfirmOrder</code> in another context,</li>
<li>testing <code class="language-plaintext highlighter-rouge">ConfirmOrder</code> itself, this is an important-enough context to mention,</li>
<li>sharing behavior between commands, and</li>
<li>getting some <em>privacy</em>.</li>
</ul>
<h3 id="supporting-multiple-contexts">Supporting multiple contexts</h3>
<p>Reusing this <code class="language-plaintext highlighter-rouge">ConfirmOrder</code> in a different context is easy. It is a small amount
of work to get variations. Imagine that, <em>because of the context</em>, you want to
confirm an order without sending the notifications…</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ConfirmOrder</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">payment_token</span><span class="p">,</span> <span class="ss">notify: </span><span class="kp">true</span><span class="p">)</span>
<span class="vi">@order</span> <span class="o">=</span> <span class="n">order</span>
<span class="vi">@payment_token</span> <span class="o">=</span> <span class="n">payment_token</span>
<span class="vi">@notify</span> <span class="o">=</span> <span class="n">notify</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="c1"># Same code as before...</span>
<span class="k">if</span> <span class="vi">@notify</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">preparation_details</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">confirmation</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">available_invoice</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>We could use some state machine’s hook in order to deliver notifications and even
to create the invoice. I tend to avoid callback as much as possible. Encapsulating
the behavior in an object allows us to see what’s going on during that action in
the same file. Also, it is easy to tweak the behavior if needed without impacting
the rest of the system, as we just did.</p>
<h3 id="sharing-behavior">Sharing behavior</h3>
<p>Many business actions, such as completing an order, can be extracted using this
pattern. Giving a clean API to all those commands gives some structure and
consistency to the codebase.</p>
<p>In the example, the errors mechanism is using exceptions, such as <code class="language-plaintext highlighter-rouge">Payment::CaptureError</code>,
forcing the controller to know about each one of them. At Drivy we’ve built a
validation layer allowing us to write:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">OrdersController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">confirm</span>
<span class="no">ConfirmOrder</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">find</span> <span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:id</span><span class="p">),</span>
<span class="n">params</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="ss">:payment_token</span><span class="p">)</span>
<span class="p">).</span><span class="nf">perform!</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:success</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.success"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="n">invoice_path</span><span class="p">(</span><span class="n">invoice</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Command</span><span class="o">::</span><span class="no">Error</span> <span class="o">=></span> <span class="n">error</span>
<span class="n">flash</span><span class="p">[</span><span class="ss">:error</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">(</span><span class="s2">"orders.create.</span><span class="si">#{</span><span class="n">error</span><span class="p">.</span><span class="nf">code</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">redirect_to</span> <span class="ss">:back</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">ConfirmOrder</span> <span class="o"><</span> <span class="no">Command</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">order</span><span class="p">,</span> <span class="n">payment_token</span><span class="p">,</span> <span class="ss">notify: </span><span class="kp">true</span><span class="p">)</span>
<span class="vi">@order</span> <span class="o">=</span> <span class="n">order</span>
<span class="vi">@payment_token</span> <span class="o">=</span> <span class="n">payment_token</span>
<span class="vi">@notify</span> <span class="o">=</span> <span class="n">notify</span>
<span class="k">end</span>
<span class="n">validate</span> <span class="k">do</span>
<span class="n">payment</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">)</span>
<span class="k">if</span> <span class="o">!</span><span class="n">payment</span><span class="p">.</span><span class="nf">valid?</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_token_invalid</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="n">payment</span><span class="p">.</span><span class="nf">amount</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span> <span class="o">||</span> <span class="n">payment</span><span class="p">.</span><span class="nf">currency</span> <span class="o">!=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_amount_mismatch</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">perform</span> <span class="k">do</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">payments</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">token: </span><span class="vi">@payment_token</span><span class="p">).</span><span class="nf">capture!</span>
<span class="no">Order</span><span class="p">.</span><span class="nf">transaction</span> <span class="k">do</span>
<span class="n">invoice</span> <span class="o">=</span> <span class="vi">@order</span><span class="p">.</span><span class="nf">invoices</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">amount: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span>
<span class="ss">currency: </span><span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">currency</span><span class="p">,</span>
<span class="p">})</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">sales_quote</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
<span class="n">invoice</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">create!</span><span class="p">({</span>
<span class="ss">product_id: </span><span class="n">item</span><span class="p">.</span><span class="nf">product_id</span><span class="p">,</span>
<span class="ss">quantity: </span><span class="n">item</span><span class="p">.</span><span class="nf">quantity</span><span class="p">,</span>
<span class="ss">unit_price: </span><span class="n">item</span><span class="p">.</span><span class="nf">unit_price</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="vi">@order</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">status: :confirmed</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="vi">@notify</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">preparation_details</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">confirmation</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="no">OrderMailer</span><span class="p">.</span><span class="nf">available_invoice</span><span class="p">(</span><span class="vi">@order</span><span class="p">).</span><span class="nf">deliver_async</span>
<span class="k">end</span>
<span class="k">rescue</span> <span class="no">Payment</span><span class="o">::</span><span class="no">CaptureError</span>
<span class="n">add_error</span><span class="p">(</span><span class="ss">:payment_capture_error</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>You may be able to guess what’s in the <code class="language-plaintext highlighter-rouge">Command</code> class but there isn’t much.
Also here I’m using the Ruby 2.5 <code class="language-plaintext highlighter-rouge">rescue</code> which will work inside blocks!</p>
<h3 id="wait-what-did-you-meant-by-privacy">Wait, what did you meant by privacy?</h3>
<p>When all that code was in the controller, it was surrounded by other actions. It
means that each private method that you would like to define would also be visible
from within those other actions. Most of the time it doesn’t make sense. I’ve seen,
and unfortunately wrote myself, controllers with too many private methods. I dodged
the name clashes with prefixes, I grouped methods by the action they referred to, I
added comments, and I even tried concerns. Nothing really was really satisfying.</p>
<p>In that sense, a dedicated object make things a lot simpler to organize. In the next
article of the serie, I’ll go deeper on how to <a href="/code_simplicity_reading_levels">use and abuse methods</a> in order
to offer the best documentation to the next developer. It’ll continue this example
so be sure to check it out.</p>
<h3 id="conclusion">Conclusion</h3>
<p>In this article nothing is especially new but this way of bundling business actions
is getting more and more common. <a href="http://hanamirb.org/">Hanami</a> has <a href="http://hanamirb.org/guides/actions/overview/">Action</a> and
<a href="http://trailblazer.to/">Trailblazer</a> has <a href="http://trailblazer.to/gems/operation/2.0/index.html">Operation</a> for instance. If you never thought of it,
it is time to practice!</p>
<p>The command pattern is sometimes called a service object, an operation, an action,
and probably more names that I’m not aware of. Whatever the name we gave it,
the purpose of such a pattern is rather simple: take a business action and put it
behind an object with a simple interface.</p>
Fri, 01 Sep 2017 00:00:00 +0000
https://getaround.tech/code_simplicity_command_pattern/
https://getaround.tech/code_simplicity_command_pattern/Best Practices for Large FeaturesHoward Wilson<p>As developers, we sometimes find ourselves faced with feature requests that will take weeks of work and touch many areas of the codebase. This comes with increased risk in terms of how we spend our time and whether things break when we come to release.</p>
<p>Examples might be moving from a single to multi-tennant application (scoping everything by accounts), or supporting multiple currencies or time zones. This post brings together some tips that we find useful at Drivy for approaching these types of problems.</p>
<p>In general, our goals are to <strong>build the right thing</strong>, <strong>take the right implementation approach</strong>, and <strong>not to break anything</strong>. We’d like to try to do those things pretty quickly, too!</p>
<h2 id="building-the-right-thing">Building the Right Thing</h2>
<p>When working on large features, the cost of building the wrong thing is higher than usual, since there is more time between receiving a feature request and presenting the completed feature back to the product team for validation. This makes up-front communication a very important part of the process, especially since more agile startups often don’t use formal specification documents.</p>
<h3 id="assumptions">Assumptions</h3>
<p>One way to avoid misunderstandings is to make a list of <strong>assumptions</strong>. Check these with the product team and include them in pull requests so any reviewers are aware of them (and can challenge them). Assumptions might take the following form:</p>
<ul>
<li>It’s not possible for a user to be in state X and Y at the same time</li>
<li>This feature will only be implemented for countries X and Y</li>
<li>There’s no need to update old data to be consistent with the new rules</li>
</ul>
<h3 id="smaller-features">Smaller Features</h3>
<p>It’s always worth questioning whether a large feature really does need to be released all in one go. Are there smaller pieces which all add value incrementally? For example, Drivy now supports precise times for rental start and end, instead of just AM/PM time slots. But we didn’t need to make this change all in one go. We started with bookings, then moved on to booking changes, and eventually the state changes of the rentals themselves.</p>
<h2 id="taking-the-right-implementation-approach">Taking the Right Implementation Approach</h2>
<p>There are normally several ways to solve a problem. Taking the right implementation approach is the “tech side” of building the right thing. In other words, “will we solve this problem in a way that the tech team generally agrees is appropriate?”</p>
<h3 id="naming">Naming</h3>
<p>Often, naming is a useful place to start. Getting a few developers together to talk about what any new entities or concepts will be called can help to identify the right abstractions in our code. Even if sometimes they feel like isolated implementation details, the abstractions developers select can strongly influence terminology and understanding across other teams in the company. For example, modeling an <code class="language-plaintext highlighter-rouge">Order</code> rather than a <code class="language-plaintext highlighter-rouge">Request</code> can have a profound impact on the perceived urgency of that user action.</p>
<p>Are data flows or processes changing? Even if not explicitly <a href="https://drivy.engineering/designing-state-machines/">designing a state machine</a>, it’s exactly these kinds of problems that typically take a lot longer to discuss and get right, than to implement. There’s nothing wrong with taking time up front to properly explore the different possibilities. Draw on the whiteboard and get the opinions of the rest of your team!</p>
<h3 id="spike--prototype">Spike / Prototype</h3>
<p>The purpose of a <a href="https://en.wikipedia.org/wiki/Spike_(software_development)">spike</a> is to gain the knowledge necessary to reduce the risk of a technical approach. It also often gives developers enough confidence to more accurately estimate how long the feature will take to develop.</p>
<p>Some general guidelines:</p>
<ul>
<li>Aim to cover enough of the feature to model the majority use case, but also explore any worrying edge cases</li>
<li>Take plenty of notes when questions/concerns arise, possibly split into product/tech</li>
<li>Tests are not mandatory, but simple acceptance tests may be useful</li>
<li>Keep a release plan in mind and, if possible, split the spike into deployable commits (more on this later)</li>
</ul>
<h3 id="first-code-review">First Code Review</h3>
<p>Reviewing code is hard. A reviewer is expected to make sure the code is correct and of a high quality before it gets merged into the release branch. In order to do this effectively, it’s usually best to keep the size of the pull request to a minimum. But how then will a reviewer be able to get an end-to-end perspective of your implementation?</p>
<p>One answer is to split code reviews which <strong>validate a general approach</strong> from code reviews which <strong>accept code into production</strong>. Here, we’re doing the first type of review. It’s a review best suited to a senior member of the team; ideally someone who has a broad knowledge of the codebase.</p>
<p>Here’s what we like to do:</p>
<ul>
<li>Split the spike into commits which roughly represent separately releasable work areas. This helps to keep focused on the goal of minimizing risk when releasing.</li>
<li>Describe each one of these commits separately. A reviewer is then able to focus on smaller chunks of code, even if the overall pull request is quite large.</li>
<li>Create a simple release plan, paying special attention to any data migrations - these can be more time consuming and error prone than anticipated.</li>
<li>Include all the technical questions which arose while building the spike</li>
<li>Include screenshots</li>
</ul>
<p>Again, the goal at this point is to validate the approach, but without losing sight of how we’ll structure the feature for release. This code isn’t going to be merged into the release branch in it’s current form.</p>
<h3 id="iterate-on-feedback">Iterate on Feedback</h3>
<p>This takes a little more time if the pull request has already been split into separate commits, but git helps us re-write our branch. There’s plenty of information on how to go about this in the related post: <a href="https://drivy.engineering/git-rebase-edit-history/">“Editing your git history with rebase for cleaner pull requests”</a>.</p>
<p>Of course, there are lots of visual tools too (such as <a href="http://gitup.co/">Gitup</a>), which get the same job done without using the command line.</p>
<p>Once the branch is updated, we force push back to the same remote branch to preserve a clean commit history.</p>
<h2 id="minimizing-risk-when-releasing">Minimizing Risk When Releasing</h2>
<h3 id="test-preconditions">Test Preconditions</h3>
<p>Before starting to release any code, it can be worth verifying that things expected to be true in production <em>are</em> actually true. Let’s say our new feature is going to introduce behavior which depends on the <code class="language-plaintext highlighter-rouge">country</code> of active cars. We can check in the database to ensure that the expectation “an active car always has a country” is true, but that doesn’t give 100% confidence. It may be true a few seconds after activation, but not immediately.</p>
<p>What we can do in cases like this is introduce some logging where our feature will go:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="n">car</span><span class="p">.</span><span class="nf">active?</span> <span class="o">&&</span> <span class="n">car</span><span class="p">.</span><span class="nf">country</span><span class="p">.</span><span class="nf">blank?</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span><span class="p">.</span><span class="nf">warn</span> <span class="s2">"Active car </span><span class="si">#{</span><span class="n">car</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2"> doesn't have a country, which is required by feature #123"</span>
<span class="k">end</span></code></pre></figure>
<p>Once we have more confidence in this precondition, the logging can be replaced with a guard clause which raises an exception. Not only does this mean that we can be confident in our assumptions in production, but other developers will also understand immediately which preconditions are satisfied and benefit from the same confidence when coding.</p>
<h3 id="final-code-reviews">Final Code Reviews</h3>
<p>Now it’s time to get the feature into production. Typically, having split the original pull request, each commit can be <code class="language-plaintext highlighter-rouge">git cherry-pick</code>‘d to a new branch in turn and then improved to a production-ready state:</p>
<ul>
<li>Link to the original PR/issue for context</li>
<li>Add complete tests if they’re not already present</li>
<li>Ask the question: can this be rolled back if something goes wrong?</li>
<li>Split out migrations if they need to be run before the code is deployed</li>
<li>Soft release the feature behind a feature flipper if it shouln’t actually be visible until later</li>
</ul>
<p>This time, Github’s <a href="https://help.github.com/articles/requesting-a-pull-request-review/">suggested reviewers</a> facility is a good way to find someone on your team to review the code. The suggestions are based on git blame data, so they’ll be people who are familiar with the code being changed. Their goal is to confirm that the changes are safe to release. This should be a much quicker process, since the quantity of code is smaller, and the overall approach has already been agreed.</p>
<h3 id="checker-jobs--monitoring">Checker Jobs / Monitoring</h3>
<p>We often write small jobs to test the long term “postconditions” of a feature. Or in other words, ensuring ongoing data consistency. Usually this concerns aggregate data that is difficult to verify synchronously. For example, checker jobs might verify that there are no overlapping rentals for the same car, or that there are no gaps in the numbering of our tax documents.</p>
<p>These jobs usually send an email to the appropriate team if inconsistencies are detected. They’re cheap to create, and can help to catch unexpected outcomes before they become too problematic.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Remember, this is just a selection of ideas that we find work well for us. Your mileage may vary!</p>
<p>As developers, we sometimes find ourselves faced with feature requests that will take weeks of work and touch many areas of the codebase. This comes with increased risk in terms of how we spend our time and whether things break when we come to release.</p>
Mon, 31 Jul 2017 00:00:00 +0000
https://getaround.tech/best-practices-for-large-features/
https://getaround.tech/best-practices-for-large-features/How we are using member voice to improve UXJulia Maunier & Marion Crosnier<p>Whether it’s on social media, on the app stores, through emails or phone calls, we receive hundreds of messages from our users every day, and answer each of them.</p>
<p>If answering questions is good, fixing the original problem is even better. We truly believe in this virtuous circle as a customer centric company.</p>
<p>Since customer service data is the first accessible and actionable “member voice” data in every company, we started by focusing on it. In collaboration between product, customer relationship & data, we wanted to turn our volumes of tickets & phone calls into clear contact reasons, to identify pain points on which we should focus to improve user experience.</p>
<h1 id="qualify-customer-service-data">Qualify customer service data</h1>
<p>Here are the steps we used at Drivy to improve the way we deal with member voice. This is not a perfect process and might not work for you as it is, but we’ve been successful with this approach.</p>
<h2 id="step-1-define-a-common-tag-referential">Step 1: Define a common tag referential</h2>
<p>The very first step is to make a list of all the possible issues you can think of (replicating the customer journey helps), and gather it behind topics. If a topic seems too big, split it into smaller topics. Accept you won’t be able to be exhaustive, but don’t end up in a giant “Other” section. Turn these topics into tags to apply it on tickets or phone calls.</p>
<h2 id="step-2-apply-it-manually">Step 2: Apply it manually</h2>
<p>After checking with the Customer Service team that the tags made sense, we applied them on each ticket and phone call manually until we had enough data to run our analysis. Onboarding of front-line teams is key for the quality of the data as their input is the basis of our analysis.</p>
<p>We could then identify the contact reasons in each country ranked by volume, and understand clearly what pain points generated most contacts from our users.</p>
<h2 id="step-3-automatize-as-much-as-possible-the-tagging">Step 3: Automatize as much as possible the tagging</h2>
<p>Manual tagging can’t scale, and doesn’t enable us to know in advance why a user needs our help.</p>
<p>In order to achieve that, we revamped our contact form to reflect the contact reasons we observed from our manual test. This way, according to the contact reason the user selected, we could either push help articles, or let him contact us and prioritize his request.</p>
<h2 id="step-4-import-data-from-our-tools">Step 4: Import data from our tools</h2>
<p>In order to perform our analysis, we needed to have access to the necessary data from our ticketing, phone calls and satisfaction tools.</p>
<p>We imported the data in <a href="https://aws.amazon.com/redshift/">Redshift</a> thanks to an Extract Load and Transform process using <a href="https://airflow.incubator.apache.org/">Apache Airflow</a> and some Python scripts. The tables are updated on a regular basis.</p>
<figure>
<img alt="" src="/assets/posts/2017-07-18-from-member-voice-to-ux/chart.png" />
</figure>
<h2 id="step-5-make-it-visual">Step 5: Make it visual</h2>
<p>Once the data was available in Redshift, we wanted to know what were the issues faced by our users in each country at a glance, so anyone in the company could know it, without requiring to be a SQL expert.</p>
<p>We created dashboards on <a href="https://redash.io/">Redash</a> displaying the data we needed to understand our users better.</p>
<h2 id="step-6-prioritization-of-pain-points">Step 6: Prioritization of pain points</h2>
<p>However, it’s not all about volumes. A topic generating lots of contact is not necessarily the top source of insatisfaction.
We also implemented satisfaction surveys sent automatically after each ticket resolution. This way, we can identify easily which contact reasons generate insatisfaction, and work on improving our treatment processes and policies.</p>
<p>It’s the mix volume x insatisfaction that truly tells us where our focus should be. </p>
<h2 id="step-7-need-for-emotional-data">Step 7: Need for emotional data</h2>
<p>When focusing on a major pain point, we needed to understand precisely what were the issues faced by our users.
We are frequently spending time reading the tickets and listening to phone calls so that we can capture better our users perception and feelings, as well as precise the pain points we need to work on with product evolution.</p>
<h1 id="to-infinity-and-beyond">To infinity and beyond</h1>
<p>We started with the data from our customer service, but there are lots of other sources we’d love to include (app reviews, social media posts…).</p>
<p>Including these other sources will help us catch more than only pain points. Feedbacks on the product help us not only on reducing insatisfaction, but increase satisfaction and user delight.</p>
<p>Whether it’s on social media, on the app stores, through emails or phone calls, we receive hundreds of messages from our users every day, and answer each of them.</p>
Mon, 17 Jul 2017 00:00:00 +0000
https://getaround.tech/from-member-voice-to-ux/
https://getaround.tech/from-member-voice-to-ux/Running feature specs with Capybara and Chrome headlessTim Petricola<p>At Drivy, we’ve been using Capybara and PhantomJS to run our feature specs for years. Even with its issues, PhantomJS is a great way to interact with a browser without starting a graphical interface. Recently, Chrome added support for a <code class="language-plaintext highlighter-rouge">headless</code> flag so it could be started without any GUI. Following this announcement, the creator of PhantomJS even <a href="https://groups.google.com/d/msg/phantomjs/9aI5d-LDuNE/5Z3SMZrqAQAJ">announced</a> that he would be stepping down as a maintainer.</p>
<p>Setting feature specs to run with a headless version of Chrome means that our features specs can be executed in the same environment most of our users are browsing with. It is also supposed to improve memory usage and stability.</p>
<h2 id="installing-prerequisites-dependencies">Installing prerequisites dependencies</h2>
<p>Assuming you already have Chrome (59 or more recent for macOS/Linux, 60 or more recent for Windows) on your machine, you’ll also need to install <a href="https://sites.google.com/a/chromium.org/chromedriver/">ChromeDriver</a>. On macOS, you can install it with homebrew:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell">brew <span class="nb">install </span>chromedriver</code></pre></figure>
<p>If not already present in your application, add <code class="language-plaintext highlighter-rouge">selenium-webdriver</code> to your <code class="language-plaintext highlighter-rouge">Gemfile</code>:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">group</span> <span class="ss">:test</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s1">'selenium-webdriver'</span>
<span class="k">end</span></code></pre></figure>
<h3 id="configuring-capybara">Configuring Capybara</h3>
<p>Capybara provides a simple API to register a custom driver. You can do so in your test/spec helper file.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Capybara</span><span class="p">.</span><span class="nf">register_driver</span><span class="p">(</span><span class="ss">:headless_chrome</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">app</span><span class="o">|</span>
<span class="n">capabilities</span> <span class="o">=</span> <span class="no">Selenium</span><span class="o">::</span><span class="no">WebDriver</span><span class="o">::</span><span class="no">Remote</span><span class="o">::</span><span class="no">Capabilities</span><span class="p">.</span><span class="nf">chrome</span><span class="p">(</span>
<span class="ss">chromeOptions: </span><span class="p">{</span> <span class="ss">args: </span><span class="sx">%w[headless disable-gpu]</span> <span class="p">}</span>
<span class="p">)</span>
<span class="no">Capybara</span><span class="o">::</span><span class="no">Selenium</span><span class="o">::</span><span class="no">Driver</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="n">app</span><span class="p">,</span>
<span class="ss">browser: :chrome</span><span class="p">,</span>
<span class="ss">desired_capabilities: </span><span class="n">capabilities</span>
<span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>As <a href="https://developers.google.com/web/updates/2017/04/headless-chrome#cli">stated in the documentation</a>, the <code class="language-plaintext highlighter-rouge">disable-gpu</code> is needed to run Chrome as headless.</p>
<h3 id="using-chrome-headless">Using Chrome headless</h3>
<p>On an app running on Rails 5.1 with system test cases, use the provided DSL to use the driver:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">ApplicationSystemTestCase</span> <span class="o"><</span> <span class="no">ActionDispatch</span><span class="o">::</span><span class="no">SystemTestCase</span>
<span class="n">driven_by</span> <span class="ss">:headless_chrome</span>
<span class="k">end</span></code></pre></figure>
<p>Otherwise, use the more generic way of setting a javascript driver for Capybara:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Capybara</span><span class="p">.</span><span class="nf">javascript_driver</span> <span class="o">=</span> <span class="ss">:headless_chrome</span></code></pre></figure>
<h2 id="troubleshooting">Troubleshooting</h2>
<h3 id="empty-screenshots">Empty screenshots</h3>
<p>With Capybara, there is a possibility to take a screenshot during your tests (or automatically on a failure). This feature results in an empty gray image on headless Chrome 59 but the proper behavior is restored on Chrome 60 (in beta as of today).</p>
<h3 id="trigger-method"><code class="language-plaintext highlighter-rouge">trigger</code> method</h3>
<p>To prevent some issues in PhantomJS when elements would overlap, we had a lot of calls like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">find</span><span class="p">(</span><span class="s1">'.clickable_element'</span><span class="p">).</span><span class="nf">trigger</span><span class="p">(</span><span class="s1">'click'</span><span class="p">)</span></code></pre></figure>
<p>In Chrome, it is raising the following error as the <code class="language-plaintext highlighter-rouge">trigger</code> method is not supported:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Capybara</span><span class="o">::</span><span class="no">NotSupportedByDriverError</span><span class="p">:</span> <span class="no">Capybara</span><span class="o">::</span><span class="no">Driver</span><span class="o">::</span><span class="no">Node</span><span class="c1">#trigger</span></code></pre></figure>
<p>This can now safely be replaced by the straightforward <code class="language-plaintext highlighter-rouge">click</code> method:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">find</span><span class="p">(</span><span class="s1">'.clickable_element'</span><span class="p">).</span><span class="nf">click</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>You can see an example app on <a href="https://github.com/drivy/rails-headless-capybara">drivy/rails-headless-capybara</a>.</p>
<p>Even though we introduced Chrome headless very recently, we’re quite optimistic that it will lead to even less bugs in our application.</p>
<p>At Drivy, we’ve been using Capybara and PhantomJS to run our feature specs for years. Even with its issues, PhantomJS is a great way to interact with a browser without starting a graphical interface. Recently, Chrome added support for a <code class="language-plaintext highlighter-rouge">headless</code> flag so it could be started without any GUI. Following this announcement, the creator of PhantomJS even <a href="https://groups.google.com/d/msg/phantomjs/9aI5d-LDuNE/5Z3SMZrqAQAJ">announced</a> that he would be stepping down as a maintainer.</p>
Wed, 05 Jul 2017 00:00:00 +0000
https://getaround.tech/running-capybara-headless-chrome/
https://getaround.tech/running-capybara-headless-chrome/The Tech Recruitment Process At DrivyMarc G Gauthier<p>I recently saw <a href="https://medium.freecodecamp.com/welcome-to-the-software-interview-ee673bc5ef6">another article</a> highlighting the many ways in which recruitment in software development is broken. Whiteboard coding, random trivia, poorly trained interviewers… it’s all very painful and it seems to be the situation in a lot of places.</p>
<p>However there are companies trying to turn this around. For instance I loved the “<a href="https://github.com/poteto/hiring-without-whiteboards">Companies that don’t have a broken hiring process</a>” list, and I’m constantly working to make sure Drivy deserves its place in it.</p>
<p>Since this is still a major pain point, I decided to share how we handle recruitment for engineering positions at Drivy. I don’t think that it’s perfect or much out of the ordinary. I’m also convinced that it’s going to evolve as it has done in the past, but it’s been working well for us and we got good feedback so far!</p>
<h2 id="the-interviewing-process">The Interviewing Process</h2>
<h3 id="our-vision">Our Vision</h3>
<p>Overall there are two big things we want to check: making sure that the person has the technical capabilities to do the job, and then making sure that they will bring a lot to the company’s mission and culture.</p>
<p>Here is basically how it goes:</p>
<ul>
<li>Phone screening</li>
<li>Take home assignment</li>
<li>“Resume” interview</li>
<li>Technical interview</li>
<li>Product interview</li>
<li>Interview with another team</li>
<li>Finalizing the hire</li>
</ul>
<p>This might seem that there are a lot of steps… and maybe it’s true. However we feel that it’s good for both parties if they get a good look at what working together would be like.</p>
<p>In terms of timing, we try to be as fast as possible, so that even if you get to see a lot of people, it can be condensed in a very tight schedule, grouping some interviews together if needed. We all know that processes that last forever are a major pain for applicants. Also most interviews don’t last more than one hour, so overall it still seems reasonable.</p>
<h3 id="the-process-in-more-details">The Process In More Details</h3>
<p>Let me explain the process for fullstack or backend developers. Note that your mileage might slightly vary, we don’t want to be completely rigid as we’re still young and growing. However we follow this exact process in most cases.</p>
<h4 id="phone-screening">Phone Screening</h4>
<p>After applying, the applicant will quickly get a first skype call with someone in charge of recruitment. This first contact is usually a good place for the applicant to ask more information about the position and the company, as well as explaining why they’re interested in Drivy.</p>
<h4 id="take-home-assignment">Take Home Assignment</h4>
<p>If the applicant is still interested, they will be given an assignment to complete at home. We spent a lot of time trying to provide something that can be completed in an acceptable amount of time while still reflecting what the job will be. <a href="https://github.com/drivy/jobs/tree/master/backend">You can check it on Github</a>, it is based on our internal accounting system which is a massive part of the app since we are a marketplace and we have to deal with a lot of money moving around.</p>
<p>Once the applicant has done the assignment, it is reviewed by people in the engineering team. We mostly check if the applicant can write clear and simple object oriented code and is able to justify main decisions if necessary.</p>
<p>If it’s not considered good enough (the standard varies depending on the position), the process stops here and we try to give some insights on what to improve.</p>
<h4 id="resume-interview">“Resume” Interview</h4>
<p>The next step is an interview with one of the senior member of the team - most likely me. There we discuss what they’ve done in the past, the position, motivation to work for Drivy and so on.</p>
<p>If we didn’t talk about salary range during the screening, it will be discussed here.</p>
<h4 id="technical-interview">Technical Interview</h4>
<p>If this goes well, they move to an onsite technical interview with a couple of developers from the team. Depending on the position, the exact process and the people involved can vary, but the main objective is to talk about code.</p>
<p>The applicant is asked to bring code written before. It can be open source code, a side project, client work or a small subset of the codebase from a previous position (if this is something the candidate is allowed to do). We’ll sit around the applicant’s computer and challenge the choices made and how it turned out. We believe that it’s a good way to discover what a candidate is capable of. Of course we know that all code in a codebase can’t be perfect, but there’s a lot to be learned in the tradeoffs and teachings of old code.</p>
<p>We’re proud of our own code and often show pieces of it to candidates at this point in the process to see how they react to it and grasp it. This also helps them to get some confidence that they won’t be working on spaghetti code.</p>
<h4 id="product-interview">Product Interview</h4>
<p>After this there is an interview with someone working a lot with product management. This is important because we consider ourselves a product company, so we are looking for people interested in what we are building. It is also a good opportunity for applicants to ask more questions about the project.</p>
<h4 id="interview-with-another-team">Interview With Another Team</h4>
<p>Finally the applicant has a small interview with someone from another team - it could be the person responsible for international expansion, the head of communication or someone at customer support. This is a way to make sure every department is aligned on who we hire as well as show the applicant what the rest of their future colleagues look like. It’s also an opportunity to for the applicant to get insights about the company’s culture - not just the engineering team.</p>
<h4 id="finalizing-the-hire">Finalizing The Hire</h4>
<p>Once everybody agrees that the person would be a good fit, we ask for past references to contact. In every case so far it’s been a formality, but we prefer to be safe on this one.</p>
<p>If this goes well we discuss all the remaining topics, like finalizing the exact offer.</p>
<h2 id="our-experience-with-this-process">Our Experience With This Process</h2>
<p>Personally I think that this process works quite well. We avoid the pitfalls of whiteboard interviews, but still get a great sense of the technical capabilities of applicants.</p>
<p>It is a bit time consuming, but hiring is too important to be cutting corners, and I feel like the amount of time we ask of applicants remains reasonable. We also had a lot of people - that we hired or not - telling us that the process was a good experience because the exercices and conversations were interesting. The current team is also a great example that this process helps us find the right people.</p>
<p>Of course, like everything we do, this process will evolve and this post will probably be outdated soon. However the guiding principles of trying to have an interview process close to the reality of the job will remain the same.</p>
<h2 id="note-on-sharing-information-about-the-open-positions">Note On Sharing Information About The Open Positions</h2>
<p>I mostly talked about the process after a candidate applied, so here is a little information about how we hope to get candidate’s attention.</p>
<p>All <a href="https://www.drivy.com/jobs">our job offers</a> try to give a sense of what the position is going to be about. We don’t shy away from <a href="/about/">sharing info about our stack</a>, <a href="https://www.drivy.com/open">our projects</a> or <a href="/bug-management/">our internal processes</a>. We also want to be inclusive by not requiring a specific degree, opening remote for certain positions and making otherselves visibles to as many communities as we can.</p>
<figure>
<img alt="MySQL 5.7 presentation at Drivy" src="/assets/posts/hiring-process/michael_mysql_parisrb.jpg" />
<figcaption>
<a href="https://twitter.com/mickeyben">Michael</a> presenting MySQL 5.7 features during a ParisRB Meetup at Drivy
</figcaption>
</figure>
<p>We are present in meetups so that anyone can meet a Drivy employee and ask questions about our open positions and have an informal chat. For instance lately we hosted <a href="https://www.meetup.com/parisrb/events/231737339/">ParisRB</a> (large Ruby meetup), <a href="https://twitter.com/womenonrails/status/874325055395831811">Women On Rails</a> and <a href="https://www.meetup.com/Paris-Ruby-Workshop/events/236048396/">Paris Ruby Workshop</a>. Our developers also tend to go to <a href="https://drivy.engineering/android-makers/">various conferences</a> and get to chat with a lot of people there as well.</p>
<p>I recently saw <a href="https://medium.freecodecamp.com/welcome-to-the-software-interview-ee673bc5ef6">another article</a> highlighting the many ways in which recruitment in software development is broken. Whiteboard coding, random trivia, poorly trained interviewers… it’s all very painful and it seems to be the situation in a lot of places.</p>
Mon, 03 Jul 2017 00:00:00 +0000
https://getaround.tech/tech-recruitment-process/
https://getaround.tech/tech-recruitment-process/Designing state machinesAdrien Di Pasquale<p>State machines are a very powerful tool but are often underused in web development. The design process forces you to think hard about how you want to model your data, about the different objects lifecycles, about the way you want to expose your data and communicate with your whole team, and about the upcoming evolutions.</p>
<p>Going through this process takes a lot of efforts but is worthwile, it brings a lot of structure to your code and your team. Also, the actual implementation of a state machine is usually very simple.</p>
<h2 id="intro--state-machines-are-simple">Intro : State machines are simple</h2>
<p>A simplified state machine for a a <code class="language-plaintext highlighter-rouge">Movie</code> object can be represented like this :</p>
<figure>
<img alt="" src="/assets/posts/2017-06-16-designing-state-machines/Movie_state.svg" />
</figure>
<p>And this diagram was generated with the following Ruby code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># first, gem install 'state_machine'</span>
<span class="k">class</span> <span class="nc">Movie</span>
<span class="n">state_machine</span> <span class="ss">:state</span><span class="p">,</span> <span class="ss">:initial</span> <span class="o">=></span> <span class="ss">:in_production</span> <span class="k">do</span>
<span class="n">event</span> <span class="ss">:finish_shooting</span> <span class="k">do</span>
<span class="n">transition</span> <span class="ss">:in_production</span> <span class="o">=></span> <span class="ss">:in_theaters</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">movie</span> <span class="o">=</span> <span class="no">Movie</span><span class="p">.</span><span class="nf">new</span>
<span class="n">movie</span><span class="p">.</span><span class="nf">state</span> <span class="c1"># in_production</span>
<span class="n">movie</span><span class="p">.</span><span class="nf">finish_shooting!</span> <span class="c1"># will raise if something goes wrong</span>
<span class="n">movie</span><span class="p">.</span><span class="nf">state</span> <span class="c1"># in_theaters</span>
<span class="c1"># to generate the diagram : $ rake state_machine:draw CLASS=Movie FORMAT=svg</span></code></pre></figure>
<p><em>see the <a href="https://github.com/pluginaweek/state_machine">state_machine</a> gem for more information</em></p>
<h2 id="design-objectives">Design objectives</h2>
<p>In the context of a fast evolving product and a growing team, the aimed qualities of a state machine should be:</p>
<ul>
<li><strong>simple</strong> : so it’s easy to understand and feels natural for everyone, not only developers.</li>
<li><strong>useful</strong> : it should help developers build and maintain the app, not be an obstacle</li>
<li><strong>adaptable</strong> : it should be thought out to be evolutive</li>
</ul>
<p>There is no one-size-fits-all solution and a lot of questions will have many valid solutions. An infinity of state machines could represent your data, and you could make your app work with them. You need to pick the one that makes the most sense for your needs and your vision.</p>
<p>Here are some tips to help you make these decisions:</p>
<h2 id="tip-1--talk-with-everyone">Tip 1 : Talk with everyone</h2>
<p>Designing a state machine should be a collaborative process. It is important that developers share their opinions and agree on a structure, so they will be willing to use it afterwards.</p>
<p>It is also extremely important to go talk to people with other roles in your team, to understand how they talk about the data and how they interact with it.</p>
<p>Here is a quick example to illustrate the diversity of viewpoints:</p>
<figure>
<img alt="" src="/assets/posts/2017-06-16-designing-state-machines/MovieForSupplyTeam_state.svg" />
<figcaption>
a movie seen by the Netflix Supply Team
</figcaption>
</figure>
<figure>
<img alt="" src="/assets/posts/2017-06-16-designing-state-machines/MovieForMarketingTeam_state.svg" />
<figcaption>
a movie seen by the Netflix Marketing Team
</figcaption>
</figure>
<h2 id="tip-2--accept-that-some-choices-are-partial">Tip 2 : Accept that some choices are partial</h2>
<p>Unfortunately, when designing state machines it is often hard to reach an unanimous and universal truth. As pointed above, different teams opinions are all valid in their context. Also, as the product evolves, the truth evolves.</p>
<p>It is your responsibility to decide what to preserve from the different opinions and what you will go against. It is important that you have all the elements to decide, and make a conscious and reasoned choice, so you can justify it to other people.</p>
<p>Don’t rush, reaching consensus is a time-consuming process.</p>
<h2 id="tip-3--do-not-over-anticipate-the-future">Tip 3 : Do not over-anticipate the future</h2>
<p>In the context of a startup like Drivy, the product roadmap and the strategic directions are likely to change often. Some decisions will reveal to have been short-sighted, sub-objects may appear, you may have to add extra transitions for edge-cases, etc…</p>
<p>It is useful to think about the degrees of freedom your design leaves open. You can orient and pick these degrees in the directions you think are more likely to happen.</p>
<p>When you do not feel extremely confident about the forecast evolutions, it is a good advice to try and make the least engaging choices. It often boils down to creating the least states possible, because it is easier to split them later than to merge them (<em>from our experience at least, your mileage may vary</em>).</p>
<h2 id="tip-4--store-everything">Tip 4 : Store everything</h2>
<p>Storing only the current state on objects is dangerous. Investigate objects in corrupt states is complicated, as you cannot understand how they ended up there. Also, when you have to make changes to your state machine, you have much less flexibility on how to migrate objects because you cannot distinguish them.</p>
<p>We strongly recommend you archive all the different states, transitions and events that objects go through. A versioning library like <a href="https://github.com/airblade/paper_trail">the papertrail gem</a> can help in that matter.</p>
<p><em>This was initially presented as a <a href="https://github.com/adipasquale/state-machines-lightning-talk">talk at Paris.rb</a> on 05/07/2016</em></p>
<p>State machines are a very powerful tool but are often underused in web development. The design process forces you to think hard about how you want to model your data, about the different objects lifecycles, about the way you want to expose your data and communicate with your whole team, and about the upcoming evolutions.</p>
Mon, 26 Jun 2017 00:00:00 +0000
https://getaround.tech/designing-state-machines/
https://getaround.tech/designing-state-machines/Android Makers 2017 HighlightsRomain Guefveneu & Jean-Élie Le Corre<p>Android Makers is the biggest Android event in France, it occurred last month in Paris. It’s always a great time to connect and learn from the Android community. For this first edition, a lot of great speakers from all around the world were in Paris.
This post is not intended to dive into the details of each conference, but more about an overview, giving you enough insights to chose the right conference to playback.</p>
<h2 id="modules---octo">Modules - Octo</h2>
<p>Our friends at Octo talked about how they improved compilation time, test time and how to better split your app into reusable components.</p>
<p>They worked on the Meetic app, if you work on a big app or on multiple apps for the same company, you certainly faced the same kind of issue:</p>
<ul>
<li>Compilation time is growing up</li>
<li>It becomes slower and slower to launch tests</li>
<li>Multiple apps using the same component and repeating code</li>
</ul>
<p>They finally split the app into modules:</p>
<ul>
<li>It dramatically improved the build time</li>
<li>They could share the message component between 2 apps</li>
</ul>
<h2 id="one-year-of-clean-architecture-the-good-the-bad-and-the-bob---octo">One year of Clean Architecture: the Good, the Bad and the Bob - Octo</h2>
<p><a href="https://www.youtube.com/watch?v=IOzZQXZb91E">Watch on YouTube</a></p>
<p>Octo again, on how they applied the principle of Uncle Bob’s clean architecture on Android.</p>
<p>By splitting the responsibility of each layer of your app, you improve the testability and flexibility.</p>
<p>Takeaways:</p>
<ul>
<li>Easier to onboard new developers in big teams on big projects because of the conventions to follow</li>
<li>Be pragmatic, adapt the architecture to your need and team size, don’t over-engineer if it’s not necessary</li>
</ul>
<h2 id="make-your-app-work-offline---virtuo">Make your app work offline - Virtuo</h2>
<p><img src="../assets/posts/2017-05-17-android-makers/virtuo.jpg" alt="Virtuo" /><br />
<a href="https://www.youtube.com/watch?v=0WhDgKY9j-s">Watch on YouTube</a></p>
<p>Virtuo is a new generation car rental agency. Your smartphone replaces the old rental agency.</p>
<p>The main feature of the app is to be the virtual key responsible to open the car.
As the cars can be parked in an underground parking, the app must work offline.</p>
<p>Takeaways:</p>
<ul>
<li>You can use http headers <code class="language-plaintext highlighter-rouge">max-age</code> and <code class="language-plaintext highlighter-rouge">max-stale</code> to fine tune your client cache</li>
<li>UX matters: instead of downloading the virtual key in the background without telling the user, they enforce the user to click on a big “Download the key” button. That way, you are sure the key is on the phone when it is offline in the parking.</li>
</ul>
<h2 id="the-fabulous-journey-to-material-design-award---fabulous">The Fabulous Journey to Material Design Award - Fabulous</h2>
<p><img src="../assets/posts/2017-05-17-android-makers/fabulous.png" alt="Fabulous" /><br />
<a href="https://www.youtube.com/watch?v=8o6DnPPxD1I">Watch on YouTube</a></p>
<p>While I would prefer more conferences about UX and UI at Android Makers, this one by the co-founder of Fabulous was really great!</p>
<p>The particularity of Fabulous: be Android first. Why? There are a lot more users on Android, and they are willing to pay for great experiences.</p>
<p>To achieve such a great experience means that the whole company must be sensible to design and user experience.</p>
<p>Takeaways:</p>
<ul>
<li>Design first in the specs flow</li>
<li>Context is super important to re-engage users</li>
<li>Great illustrations give your app personality</li>
<li>You can A/B tests in the app and A/B test Google Play Page to improve conversion</li>
<li>Always take the user feedbacks into account when you iterate on your product</li>
</ul>
<h2 id="the-art-of-organizing-resources---philips-hue">The ART of organizing resources - Philips Hue</h2>
<p><a href="https://www.youtube.com/watch?v=AjSgAHZT9a0">Watch on YouTube</a></p>
<p>All computer scientists knows that naming things is hard. As your app grows, assets multiplies and it can quickly becomes a mess if you don’t follow strict naming rule.
Jeroen Mols, from Philips Hue, suggests this simple pattern:</p>
<p><img src="../assets/posts/2017-05-17-android-makers/whatwheredescriptionsize.jpg" alt="`WHAT_WHERE_DESCRIPTION_SIZE`" />
For example:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">activity_main</code> for activities layouts</li>
<li><code class="language-plaintext highlighter-rouge">linearlyaout_main_fragmentcontainer</code> for views</li>
<li><code class="language-plaintext highlighter-rouge">all_infoicon_small</code> for drawables</li>
</ul>
<p>It makes everything clear!</p>
<h2 id="taking-care-of-ui-test---novoda">Taking care of UI Test - Novoda</h2>
<p><a href="https://www.youtube.com/watch?v=dcWTq7MyrBQ">Watch on YouTube</a></p>
<p>Keep your tests clean!
When using Espresso, UI tests look like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">autoCompleteTextView_clickAndCheck</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Type text into the text view</span>
<span class="n">onView</span><span class="o">(</span><span class="n">withId</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">auto_complete_text_view</span><span class="o">))</span>
<span class="o">.</span><span class="na">perform</span><span class="o">(</span><span class="n">typeTextIntoFocusedView</span><span class="o">(</span><span class="s">"South "</span><span class="o">),</span> <span class="n">closeSoftKeyboard</span><span class="o">());</span>
<span class="c1">// Tap on a suggestion.</span>
<span class="n">onView</span><span class="o">(</span><span class="n">withText</span><span class="o">(</span><span class="s">"South China Sea"</span><span class="o">))</span>
<span class="o">.</span><span class="na">inRoot</span><span class="o">(</span><span class="n">withDecorView</span><span class="o">(</span><span class="n">not</span><span class="o">(</span><span class="n">is</span><span class="o">(</span><span class="n">mActivity</span><span class="o">.</span><span class="na">getWindow</span><span class="o">().</span><span class="na">getDecorView</span><span class="o">()))))</span>
<span class="o">.</span><span class="na">perform</span><span class="o">(</span><span class="n">click</span><span class="o">());</span>
<span class="c1">// By clicking on the auto complete term, the text should be filled in.</span>
<span class="n">onView</span><span class="o">(</span><span class="n">withId</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">auto_complete_text_view</span><span class="o">))</span>
<span class="o">.</span><span class="na">check</span><span class="o">(</span><span class="n">matches</span><span class="o">(</span><span class="n">withText</span><span class="o">(</span><span class="s">"South China Sea"</span><span class="o">)));</span>
<span class="o">}</span></code></pre></figure>
<p>With <a href="https://martinfowler.com/bliki/PageObject.html">PageObject Pattern</a>, it’ll look like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">autoCompleteTextView_clickAndCheck</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">SearchScreen</span> <span class="n">searchScreen</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SearchScreen</span><span class="o">();</span>
<span class="n">searchScreen</span><span class="o">.</span><span class="na">givenKeyword</span><span class="o">(</span><span class="s">"South "</span><span class="o">)</span>
<span class="o">.</span><span class="na">tapOn</span><span class="o">(</span><span class="s">"South China Sea"</span><span class="o">)</span>
<span class="o">.</span><span class="na">assertTextMatches</span><span class="o">(</span><span class="s">""</span><span class="nc">South</span> <span class="nc">China</span> <span class="nc">Sea</span><span class="err">"</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>It’s easier to read, reuse and maintain!</p>
<h2 id="deep-android-integration---uber">Deep Android Integration - Uber</h2>
<p><a href="https://www.youtube.com/watch?v=5C5bgY84WXw">Watch on YouTube</a></p>
<p>Ty Smith from Uber reminds us that a good user experience also consist in a deep system integration.
It’s not only about having a UI, but also about using right system APIs such as:</p>
<ul>
<li><a href="https://developer.android.com/training/app-indexing/deep-linking.html">Deep linking</a>: redirect URLs to your app</li>
<li><a href="https://developer.android.com/training/sync-adapters/index.html">SyncAdapter</a> & <a href="https://developer.android.com/training/id-auth/identify.html">AcountManager</a>: save users settings and information in the cloud</li>
<li><a href="https://developer.android.com/guide/topics/providers/content-providers.html">ContentProvider</a>: if you need to share data between apps</li>
</ul>
<p><em>Pro Tip</em>: You can listen for <a href="https://developers.google.com/android/reference/com/google/android/gms/tagmanager/InstallReferrerReceiver"><code class="language-plaintext highlighter-rouge">INSTALL_REFERRER</code></a> broadcast to show the relevant screen after an install!</p>
<h2 id="conclusion">Conclusion</h2>
<p>A lot of great talks for this first Android Makers conference in Paris. It’s always a pleasure to learn from other developers and other people in the industry.
It’s also a great moment to meet passionate people and to connect with others at the after-hours events.
See you next year!</p>
<p>Android Makers is the biggest Android event in France, it occurred last month in Paris. It’s always a great time to connect and learn from the Android community. For this first edition, a lot of great speakers from all around the world were in Paris.
This post is not intended to dive into the details of each conference, but more about an overview, giving you enough insights to chose the right conference to playback.</p>
Tue, 23 May 2017 00:00:00 +0000
https://getaround.tech/android-makers/
https://getaround.tech/android-makers/Story of a junior developer at DrivyJean Anquetil<p>Hey, I’m Jean, a Junior Full Stack Developer at Drivy. I joined the company after graduating from a two-month full stack program at <a href="https://www.lewagon.com" target="_blank">Le Wagon</a>. Except from being passionate about tech at large, I didn’t know anything about web development last year but from now on I am coding full time and I love it! Here is my feedback after 6 months at Drivy.</p>
<h1 id="dont-panic-if-you-dont-understand-anything">Don’t panic if you don’t understand anything</h1>
<p>The <a href="/about#our-tech-stack" target="_blank">stack</a> was definitely new to me, the only web dev experience I had was the Bootcamp. I had almost never heard about these things such as Sidekiq, Rspec, FactoryGirl, Webpack, Haml or the basic design patterns… But it doesn’t matter, I was here to get experience and to give in to panic wouldn’t have helped me into learning step-by-step.</p>
<p>At first I was asked to work on basic static pages, which seems like nothing but actually it made me feel quickly confident in my ability to contribute to the product. Besides that, I worked on asynchronous emails and then I completed my first product feature. Slowly but surely I’m discovering the codebase, the different services plugged to it, the good practices, and I’m finding my niche in the team!</p>
<p>After all, when you start working on a five-year-old project, anyone has to get one’s bearings, right?</p>
<h1 id="dont-be-afraid-to-ask">Don’t be afraid to ask</h1>
<p>I work with 13 brilliant developers and even if I have to keep in mind that I could make them loose time, I can’t expect them to keep an eye on me all the time. In other words, I guess they expect me to ask for help if needed. It seemed pretty tricky to me at first, I was often wondering if my questions were relevant.</p>
<p>I think the most important is to be honest with you and your colleagues. If you feel stuck then take a look at Stack Overflow, look for something similar in the codebase, read the documentation and if you still have no answers: that’s not a big deal, just ask your colleagues but the most important thing is to formulate well your issue.</p>
<p>Formulating my issue gives me a global view on it and it often <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" target="_blank">highlights new tracks to look at</a>.</p>
<h1 id="benefits-of-working-on-a-high-traffic-app">Benefits of working on a high traffic app</h1>
<h2 id="testing">Testing</h2>
<p>Working on an high traffic app such as Drivy also gives me the opportunity to face scalability matters. As we have millions+ users the smallest code update can bring bugs. So one of the first skills I had to improve when arriving in the company was testing.</p>
<p>I don’t work alone on the codebase so beyond the fact that my code has to be easily maintainable by the others, adding tests is also a way to prevent another developer from breaking what I just did. And honestly, writing tests is - according to me - one of the best answers to fight stress generated by deploying new features.</p>
<p>I also learnt to do benchmarks: what happens if I run this query on millions+ of records? Should I consider a denormalization for this data? So using benchmarks let me justify using this or that approach.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">n</span> <span class="o">=</span> <span class="mi">1000</span>
<span class="n">b</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="p">.</span><span class="nf">bm</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"Arel scope:"</span><span class="p">)</span> <span class="k">do</span>
<span class="n">n</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="no">Rental</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="mi">3577388</span><span class="p">).</span><span class="nf">reviews</span><span class="p">.</span><span class="nf">readable</span><span class="p">.</span><span class="nf">to_a</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"Denormalized scope:"</span><span class="p">)</span> <span class="k">do</span>
<span class="n">n</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="no">Rental</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="mi">3577388</span><span class="p">).</span><span class="nf">reviews</span><span class="p">.</span><span class="nf">is_readable</span><span class="p">.</span><span class="nf">to_a</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span><span class="p">;</span></code></pre></figure>
<p><em>Quickly comparing an Arel scope with a denormalized one.</em></p>
<h2 id="release-flow">Release Flow</h2>
<p>Another concept I quickly learnt not to ignore in a feature development: what is my release flow?</p>
<p>This is super important to consider when you start working on a new feature. Should I ship my migration first, then my code? Am I doing a rollbackable migration or not? Could it lock the database? What if users are browsing the page I am updating?</p>
<p>Thus, I’m always wondering if the feature I’m working on is splittable into smaller ones: that will be easier to review, easier to test and less painful to release.</p>
<figure>
<img src="/assets/posts/2017-05-15-story-of-a-junior-developer-at-drivy/release_flow_with_migration.jpg" alt="Release Flow with migration" />
</figure>
<p><em>Releasing first the migration in a separate commit then the code makes it easier to do a zero-downtime deployment.</em></p>
<h2 id="communication">Communication</h2>
<p>I realized that working as developer doesn’t mean keeping to yourself. For instance, I like to make an effort to prepare my pull requests: well explain the context, which design patterns I used, what my release flow is, and maybe give some screenshots.</p>
<p>That sounds naive but this is to make the reviewer comfortable and let them focus on what I did and why I did it this way. That also helps me to see my work’s big picture and feel justified in asking for advice.</p>
<figure>
<img src="/assets/posts/2017-05-15-story-of-a-junior-developer-at-drivy/pull_request_example.png" alt="Release Flow with migration" />
</figure>
<p><em>Pull Request with explanations.</em></p>
<p>Another important thing about communication is that you also work with non-developer.</p>
<p>As a Junior dev I can’t afford to loose time working on misunderstood requirements. So there is a challenge in converting the requirements into well defined specs by doing short, prepared and focused meeting especially with the help of our product managers.</p>
<h1 id="conclusion">Conclusion</h1>
<p>Finally, I strongly believe that working on a real project with a real team is the best way to keep learning and improving your skills: you are surrounded by smart people who let you focus on things that matter.</p>
<p>Day after day I feel more confident in bringing my contributions to the project I’m working on without neglecting the fact that I can have a serious impact on it.</p>
<p>Hey, I’m Jean, a Junior Full Stack Developer at Drivy. I joined the company after graduating from a two-month full stack program at <a href="https://www.lewagon.com" target="_blank">Le Wagon</a>. Except from being passionate about tech at large, I didn’t know anything about web development last year but from now on I am coding full time and I love it! Here is my feedback after 6 months at Drivy.</p>
Thu, 18 May 2017 00:00:00 +0000
https://getaround.tech/story-of-a-junior-developer-at-drivy/
https://getaround.tech/story-of-a-junior-developer-at-drivy/Code Simplicity - Value ObjectsNicolas Zermati<p>Understanding the application’s state at a given point in time is valuable. You
and your team must make efforts to keep the cognitive load required to reason
about its state as low as possible.</p>
<p>Application’s state is often based on classes such as <code class="language-plaintext highlighter-rouge">Numeric</code>, <code class="language-plaintext highlighter-rouge">String</code>,
<code class="language-plaintext highlighter-rouge">Array</code>, etc. In this article we’ll see how to abstract business-specific
objects on top of those primitive types.</p>
<h3 id="a-simple-specification">A simple specification</h3>
<p>I need to model a car. A car is simply defined by its serial number and its
mileage. In addition to this, a car will have an interface to update distance
that have been driven. When a car is created the serial number is generated
and the mileage is set to zero.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Car</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@serial_number</span> <span class="o">=</span> <span class="no">SerialNumber</span><span class="p">.</span><span class="nf">generate</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="vi">@mileage</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">drive</span><span class="p">(</span><span class="n">distance</span><span class="p">)</span>
<span class="vi">@mileage</span> <span class="o">+=</span> <span class="n">distance</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Here I used another module to generate the serial number. It isn’t the purpose
of the article so let’s ignore it for this time. To express the distance, I used
a <code class="language-plaintext highlighter-rouge">Numeric</code> instance. Indeed, nothing had been said explicitly in the
specification.</p>
<h3 id="fighting-implicitness">Fighting implicitness</h3>
<p>Here I implicitly expect the <code class="language-plaintext highlighter-rouge">distance</code> passed to the drive method to be
positive. It obiously is because a negative distance make no sense!</p>
<p>However, something looking obvious now to you might not look the same to someone
else or in the future. A code with a lot of implicit constraints is hard to trust
because for each change you’ll have to carry in your head all those implicit
constraints and make sure they are still enforced. I don’t know about you but
this looks scary as hell to me.</p>
<p>There is different way of fighting this implicitness. We could try to add
safeties to our code to mitigate the unexpected inputs. It would look like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">drive</span><span class="p">(</span><span class="n">distance</span><span class="p">)</span>
<span class="vi">@mileage</span> <span class="o">+=</span> <span class="n">distance</span><span class="p">.</span><span class="nf">abs</span>
<span class="k">end</span></code></pre></figure>
<p>Tada! No more problem having a negative number as argument!</p>
<p>This is, in my opinion, worse than the first version. Now there is some
misplaced code in the <code class="language-plaintext highlighter-rouge">Car</code> class. It raises questions that makes no sense.</p>
<ul>
<li>Why a distance would be negative?</li>
<li>Is that <code class="language-plaintext highlighter-rouge">#abs</code> call really needed?</li>
</ul>
<p>Those are hard questions, especially when it isn’t your code, when it is in a
critical part of the application, and it has been there forever. Those questions
are hard because some would find obvious that a distance must always be
positive.</p>
<p>Other <a href="http://archive.adaic.com/standards/83rat/html/ratl-04-04.html">programming</a> <a href="https://coq.inria.fr/library/Coq.Init.Nat.html">environments</a> helps you express that kind of
constraints using advanced type systems. Ruby, on the other hand, is more
permissive and the responsibility of making things explicit, relies on the
design you’ll come with.</p>
<h3 id="the-right-battlefield">The right battlefield</h3>
<p>The issue is that we have no place to express that implicit constraint about the
distance being positive. The car shouldn’t be responsible to manage this. Lets
fight the distance battle on a more appropriate battlefield: in a <code class="language-plaintext highlighter-rouge">Distance</code>
class.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Distance</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">if</span> <span class="n">value</span> <span class="o"><</span> <span class="mi">0</span>
<span class="k">raise</span> <span class="no">ArgumentError</span><span class="p">,</span> <span class="s2">"A distance must be positive"</span>
<span class="k">end</span>
<span class="vi">@value</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">+</span><span class="p">(</span><span class="n">other</span><span class="p">)</span>
<span class="k">unless</span> <span class="n">other</span><span class="p">.</span><span class="nf">kind_of?</span><span class="p">(</span><span class="no">Distance</span><span class="p">)</span>
<span class="k">raise</span> <span class="no">ArgumentError</span><span class="p">,</span> <span class="s2">"Only another distance can be added"</span>
<span class="k">end</span>
<span class="no">Distance</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">value</span> <span class="o">+</span> <span class="n">other</span><span class="p">.</span><span class="nf">value</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">attr_reader</span> <span class="ss">:value</span>
<span class="kp">protected</span> <span class="ss">:value</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Car</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@serial_number</span> <span class="o">=</span> <span class="no">SerialNumber</span><span class="p">.</span><span class="nf">generate</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
<span class="vi">@mileage</span> <span class="o">=</span> <span class="no">Distance</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">drive</span><span class="p">(</span><span class="n">distance</span><span class="p">)</span>
<span class="vi">@mileage</span> <span class="o">+=</span> <span class="n">distance</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>This <code class="language-plaintext highlighter-rouge">Distance</code> class isn’t perfect but the <code class="language-plaintext highlighter-rouge">Car</code> class is more robust than it
was and even more expressive. Distance is a <em>value object</em> that we created in
response to a <em>primitive obsession</em> <em>code smell</em>.</p>
<p>In this example, we made the concept of a distance explicit. It allowed us to
express the constraints related to the concept itself.</p>
<p>One could argue that it was shorter with the implicit version. It was shorter
to write. Code is read way more often than written. Once the <code class="language-plaintext highlighter-rouge">distance</code> class is
done, no need to read it each time you use it. And finally, if you only look at
the <code class="language-plaintext highlighter-rouge">Car</code> class, the last version express more and is safer.</p>
<h3 id="going-further">Going further</h3>
<p>Value objects are not only good for giving a home to implicit constraints. They
are also good to aggregate things that belong together. For instance, an amount
of money will need a currency and an amount. A value object can tie them
together and prevent operations mixing currencies.</p>
<p>Internet is
<a href="https://www.martinfowler.com/bliki/ValueObject.html">full</a>
<a href="https://en.wikipedia.org/wiki/Value_object">of</a>
<a href="https://medium.com/@franzejr/value-object-in-ruby-89b5e3b6b5f9">articles</a>
<a href="http://wiki.c2.com/?ValueObject">about</a>
<a href="https://blog.dnsimple.com/2016/10/overcoming-primitive-obsession/">value</a>
<a href="https://blog.dnsimple.com/2016/11/purposes-and-properties-of-value-objects/">objects</a>!
Read them all as each of them would give you a different perspective on this topic.</p>
<p>Understanding the application’s state at a given point in time is valuable. You
and your team must make efforts to keep the cognitive load required to reason
about its state as low as possible.</p>
Tue, 09 May 2017 00:00:00 +0000
https://getaround.tech/code_simplicity_value_objects/
https://getaround.tech/code_simplicity_value_objects/Code Simplicity - IntroductionNicolas Zermati<p>This is an introduction to a serie of articles about <em>code simplicity</em>. The
concept itself is a bit abstract but don’t worry: I aim to provide some good
examples and explainations for you to get something out of it!</p>
<p>In this first article I’ll share some context about that whole code simplicity
thing.</p>
<h3 id="building-applications">Building applications</h3>
<p>There are different kinds of applications. Most of the application I wrote
myself were web applications. They could be defined as <em>state</em> associated with
a list of <em>operations</em>.</p>
<p>The <strong>state</strong> of the application includes obviously what’s in the database as
well as the emails that have been sent, payments that have been made, logs,
cached contents, etc.</p>
<p>The <strong>operations</strong> transform the current state of your application to the new
one. For instance, when you Tweet something, you’re updating Twitter’ state.
Even when you display your feed, Twitter updates its state regarding the ads it
showed, the last tweets you’ve seen, etc.</p>
<p>The portion of <em>code simplicity</em> relating to the previous definitions is that
reading simple code helps you better understand the application’s state and how
it evolves over time. If you acknowledge the difference between state and
operations then you’ll tend to write code in an intention-revealing way that
will contribute to the understanding of future readers.</p>
<p>Keep in mind that we spend more time reading than writing code.</p>
<h3 id="why-does-it-matter">Why does it matter?</h3>
<p>If state and operations never needed to evolve we wouldn’t care so much about
making the code expressing them as simply as possible. But…</p>
<ul>
<li>application grows: new feature need to be added,</li>
<li>requirements change: old features need to be updated,</li>
<li>code isn’t perfect: bugs need to be fixed,</li>
<li>teams get bigger: new hires need to understand the application,</li>
<li>teams get smaller: remaining members need to be able to take over,</li>
<li>etc.</li>
</ul>
<p>When either state or operations are poorly organized, it becomes longer and
harder to deal with the previous list. Time is lost trying to untangle the past,
making those actions more expensive and sometime impossible to achieve.</p>
<h3 id="code-simplicity-finally">Code simplicity, finally!</h3>
<p>Now that I gave more context, I can afford to give you my definition!</p>
<blockquote>
<p>Code simplicity is a way to get changes in your application, through both
years and people, at a low and constant cost.</p>
</blockquote>
<p>I’m not saying that getting there is achieved only by code simplicity. I’m
saying that without simple code, you’ll have a hard time.</p>
<h3 id="whats-next">What’s next?</h3>
<p>As I said this article was an introduction. In the next articles we’re going to
explore examples showing situation where code simplicity could be enhanced. Here
are the list of the following articles:</p>
<ul>
<li><a href="/code_simplicity_value_objects/">Express the application’s state better with value objects</a></li>
<li><a href="/code_simplicity_command_pattern/">Split state from operations with the the Command pattern</a></li>
<li><a href="/code_simplicity_reading_levels/">Offer various levels of reading by (ab)using methods</a></li>
<li>Maintain invariants by raising often</li>
<li>Protect your state with preconditions</li>
<li>More to be announced!</li>
</ul>
<p>I’ll update the list with links as they are published!</p>
<p>This is an introduction to a serie of articles about <em>code simplicity</em>. The
concept itself is a bit abstract but don’t worry: I aim to provide some good
examples and explainations for you to get something out of it!</p>
Mon, 08 May 2017 00:00:00 +0000
https://getaround.tech/code_simplicity_introduction/
https://getaround.tech/code_simplicity_introduction/MySQL Evolution - From 5.6 to 8.0Michael Bensoussan<script async="" class="speakerdeck-embed" data-id="dc99328c1c8748b6b9443a6388eb82df" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>
<script async="" class="speakerdeck-embed" data-id="dc99328c1c8748b6b9443a6388eb82df" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>
Tue, 02 May 2017 00:00:00 +0000
https://getaround.tech/mysql-evolution/
https://getaround.tech/mysql-evolution/Editing your git history with rebase for cleaner pull requestsAdrien Siami<p>At Drivy, we make extensive use of pull requests to ensure that our code is up to our standards and to catch possible issues.</p>
<p>Reviewing big pull requests can get tedious, that’s why we try to make them as readable as possible. This means splitting them in small commits that all make sense individually, so you can read the pull request commit by commit and understand the general direction of the code change.</p>
<p>It’s also useful if you want to only show a part of your PR to some people. For instance, you might want the front-end developer to only look at the front-related commits.</p>
<h1 id="split-your-pr-commit-by-commit">Split Your PR Commit by Commit</h1>
<p>In a perfect world, you’d come up with a plan on how you want your PR to be split into commits, work on each commit sequentially, then submit your PR.</p>
<p>Unfortunately, this is not the world we live in, and more often than not, mistakes happen and your history quickly looks like this:</p>
<figure>
<img src="/assets/posts/2017-04-19-git-rebase-edit-history/clutter.png" alt="clutter" />
</figure>
<p>Thankfully, git is super powerful and allows to rewrite history, thanks to the dreaded <code class="language-plaintext highlighter-rouge">git rebase</code> command.</p>
<h1 id="git-rebase">Git rebase</h1>
<p>Git rebase has many usages. The main idea is that <code class="language-plaintext highlighter-rouge">git rebase</code> is used to apply a bunch of commits on top of a different base.</p>
<p>In this article I’ll focus on one use case we can encounter when trying to submit a readable PR: editing past commits.</p>
<h2 id="editing-commits">Editing commits</h2>
<p>Let’s imagine we’re building a car sharing platform ;)
We’re working on a big redesign of the “new car” form and our history looks like this:</p>
<p><img src="../assets/posts/2017-04-19-git-rebase-edit-history/history.png" alt="history" /></p>
<p>So far so good! But we just realised that we forgot to update a wording.
The commit is already far back in the history so we can’t use <code class="language-plaintext highlighter-rouge">git commit --amend</code>.</p>
<p>We could create another commit but wouldn’t it be much better to edit our “update wordings” commit as if we never forgot this wording to begin with? Let’s use <code class="language-plaintext highlighter-rouge">git rebase</code> to achieve just that.</p>
<h3 id="interactive-rebase">Interactive rebase</h3>
<p>We’re going to run <code class="language-plaintext highlighter-rouge">git rebase -i master</code>, meaning that we want to reapply our commits on top of master, but in interactive mode (<code class="language-plaintext highlighter-rouge">-i</code>). This will allow us to play around with each commit :</p>
<p><img src="../assets/posts/2017-04-19-git-rebase-edit-history/rebase.png" alt="rebase" /></p>
<p>Here, git opens our favorite text editor and asks us what to do with each commit.</p>
<p>By default, <code class="language-plaintext highlighter-rouge">pick</code> will just apply the commit, but we can update each commit line to tell another story. We can also reorder the lines to have the commits applied in a different order.</p>
<h3 id="choosing-a-commit-to-edit">Choosing a commit to edit</h3>
<p>In our current use case, the command that we want is <code class="language-plaintext highlighter-rouge">edit</code>, if we replace <code class="language-plaintext highlighter-rouge">pick</code> by <code class="language-plaintext highlighter-rouge">edit</code> on a commit line, when applying the commit, git will halt and yield control to us so we can do whatever we want.</p>
<p>Let’s do it!</p>
<p><img src="../assets/posts/2017-04-19-git-rebase-edit-history/edit.png" alt="edit" /></p>
<p>Now let’s just save the file and quit our editor.</p>
<p><img src="../assets/posts/2017-04-19-git-rebase-edit-history/mid_rebase.png" alt="rebase" /></p>
<p>We’re now back to when we commited this first commit, the 2 others haven’t been applied yet, and we can now do our changes.</p>
<h3 id="applying-our-changes">Applying our changes</h3>
<p>When we’re happy with our changes, we can add them and run <code class="language-plaintext highlighter-rouge">git commit --amend</code> to update the commit.</p>
<p>Afterwards, we have to run <code class="language-plaintext highlighter-rouge">git rebase --continue</code> to continue with the rebase and apply the next commits.</p>
<p>In the end, we’ll keep our 3 commits, but the one we edited now contains our latest changes.</p>
<p>Our history is clean, ready for review!</p>
<h1 id="conclusion">Conclusion</h1>
<p><code class="language-plaintext highlighter-rouge">git rebase</code> is super powerful, especially with its interactive mode. You can use it to do many things: reorder commits, merge commits together, edit past commits, split commits in several commits, remove commits completely, etc. If you want to know more about it, have a look at the <a href="https://git-scm.com/docs/git-rebase#_interactive_mode">official documentation</a>.</p>
<p>But as you know, with great power comes great responsibility. Rewriting the history could cause harm if you’re working on a shared branch and other developers are pulling your code, keep that in mind!</p>
<p>Before ending this article, here’s a last piece of advice: if you find yourself lost in a <code class="language-plaintext highlighter-rouge">git rebase -i</code> session and just want to return to the state before ever trying to rebase, the command you’re looking for is <code class="language-plaintext highlighter-rouge">git rebase --abort</code>.</p>
<p>Happy rebasing!</p>
<p>At Drivy, we make extensive use of pull requests to ensure that our code is up to our standards and to catch possible issues.</p>
Wed, 26 Apr 2017 00:00:00 +0000
https://getaround.tech/git-rebase-edit-history/
https://getaround.tech/git-rebase-edit-history/Instrumenting SidekiqMichael Bensoussan<p>As <a href="https://en.drivy.com">Drivy</a> continues to grow, we were interested in having more insights on the performance of our jobs, their success rate and the congestion of our queues. This would help us:</p>
<ul>
<li>Better organize our queues.</li>
<li>Focus our performance work on the slow and high throughtput jobs.</li>
<li>Eventually split some jobs in 2 or more jobs.</li>
<li>Add more background workers or scale our infrastructure so we stay ahead when our application is growing quickly.</li>
</ul>
<p>It also helps detect high failure rates and look at overall usage trends.</p>
<p>To setup the instrumentation we used a Sidekiq Middleware.<br />
Sidekiq supports middlewares, quite similar to Rack, which lets you hook into the job lifecycle easily.
Server-side middlewares runs around job processing and client-side middlewares runs before pushing the job to Redis.
In our case, we’ll use a server middleware so we can measure the time of the processing and know whether the job succeeded or failed.</p>
<p>We use <a href="https://github.com/influxdata/influxdb">InfluxDB</a> to store our metrics, so we’ll use the <a href="https://github.com/influxdata/influxdb-ruby">InfluxDB Ruby client</a> but you could easily adapt this code to use StatsD, Librato, Datadog or any other system.</p>
<h2 id="setting-up-the-influxdb-client">Setting Up The InfluxDB Client</h2>
<p>We instantiate our InfluxDB client with the following initialiser:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_INSTRUMENTATION"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"true"</span>
<span class="nb">require</span> <span class="s1">'influxdb'</span>
<span class="no">INFLUXDB_CLIENT</span> <span class="o">=</span> <span class="no">InfluxDB</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_DATABASE"</span><span class="p">],</span> <span class="p">{</span>
<span class="ss">udp: </span><span class="p">{</span>
<span class="ss">host: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_HOST"</span><span class="p">],</span>
<span class="ss">port: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_PORT"</span><span class="p">]</span>
<span class="p">},</span>
<span class="ss">username: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_USERNAME"</span><span class="p">],</span>
<span class="ss">password: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_PASSWORD"</span><span class="p">],</span>
<span class="ss">time_precision: </span><span class="s1">'ns'</span><span class="p">,</span>
<span class="ss">discard_write_errors: </span><span class="kp">true</span>
<span class="p">})</span>
<span class="k">end</span></code></pre></figure>
<p>We’re using UDP because we chose performance over reliability. We also added the <code class="language-plaintext highlighter-rouge">discard_write_errors</code> flag because UDP can return (asynchronous) errors when a previous packet was received. We don’t want any kind of availability issue in our InfluxDB server to cause our instrumentation to fail. We added this flag to the official client in the <a href="https://github.com/influxdata/influxdb-ruby/pull/182">following PR</a>.</p>
<p>Note that we use a global InfluxDB connection to simplify our exemple, but if you’re in a threaded environment you might want to use a <a href="https://github.com/mperham/connection_pool">connection pool</a>.</p>
<p>Once the connection is setup, we can start extracting the following metrics:</p>
<ul>
<li>The time the job was enqueued for - this will allow to identify queues congestion</li>
<li>The time the job took to perform</li>
<li>The success count</li>
<li>The fail count</li>
</ul>
<p>InfluxDB also supports the concept of <a href="https://docs.influxdata.com/influxdb/v1.2/concepts/glossary/#tag">tags</a>. Tags are indexed and are used to add metadata around the metrics. We’ll add the following tags:</p>
<ul>
<li>worker class name</li>
<li>queue name</li>
<li>Rails environment</li>
</ul>
<h2 id="creating-the-sidekiq-middleware">Creating The Sidekiq Middleware</h2>
<p>The middleware <a href="https://github.com/mperham/sidekiq/wiki/Middleware">interface</a> is similar to the one used by Rack - you implement a single method named <code class="language-plaintext highlighter-rouge">call</code>, which takes three arguments: <code class="language-plaintext highlighter-rouge">worker</code>, <code class="language-plaintext highlighter-rouge">item</code>, and <code class="language-plaintext highlighter-rouge">queue</code>:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">worker</code> holds the worker instance that will process the job</li>
<li><code class="language-plaintext highlighter-rouge">item</code> is a hash that holds information about arguments, the job ID, when the job was created and when it was enqueued, …</li>
<li><code class="language-plaintext highlighter-rouge">queue</code> holds the queue name the job is fetched from</li>
</ul>
<p>Here’s the skeleton of our middleware:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Drivy::Sidekiq::Middleware::Server::Influxdb</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">options</span><span class="o">=</span><span class="p">{})</span>
<span class="vi">@influx</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="ss">:client</span><span class="p">]</span> <span class="o">||</span> <span class="k">raise</span><span class="p">(</span><span class="s2">"influxdb :client is missing"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">queue</span><span class="p">)</span>
<span class="c1"># code goes here</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">elapsed</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
<span class="p">((</span><span class="no">Time</span><span class="p">.</span><span class="nf">now</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1000.0</span><span class="p">).</span><span class="nf">to_i</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Our middleware is instantiated with the InfluxDB client and we added a private helper to measure the time difference in milliseconds.</p>
<p>Now to the <code class="language-plaintext highlighter-rouge">call</code> code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="n">worker</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">queue</span><span class="p">)</span>
<span class="n">worker_name</span> <span class="o">=</span> <span class="n">msg</span><span class="p">[</span><span class="s1">'wrapped'</span><span class="p">.</span><span class="nf">freeze</span><span class="p">]</span> <span class="o">||</span> <span class="n">worker</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">to_s</span>
<span class="n">enqueued_for</span> <span class="o">=</span> <span class="n">elapsed</span><span class="p">(</span><span class="no">Time</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="n">msg</span><span class="p">[</span><span class="s1">'enqueued_at'</span><span class="p">]))</span>
<span class="n">start</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">tags</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">worker: </span><span class="n">worker_name</span><span class="p">,</span> <span class="ss">queue: </span><span class="n">queue</span><span class="p">,</span> <span class="ss">env: </span><span class="no">Rails</span><span class="p">.</span><span class="nf">env</span> <span class="p">}</span>
<span class="k">begin</span>
<span class="n">data</span> <span class="o"><<</span> <span class="p">{</span>
<span class="ss">series: </span><span class="s2">"sidekiq"</span><span class="p">,</span>
<span class="ss">tags: </span><span class="n">tags</span><span class="p">,</span>
<span class="ss">values: </span><span class="p">{</span> <span class="ss">count: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">enqueued_for: </span><span class="n">enqueued_for</span> <span class="p">}</span>
<span class="p">}</span>
<span class="c1"># Here the job is processing</span>
<span class="k">yield</span>
<span class="n">data</span> <span class="o"><<</span> <span class="p">{</span>
<span class="ss">series: </span><span class="s2">"sidekiq"</span><span class="p">,</span>
<span class="ss">tags: </span><span class="n">tags</span><span class="p">,</span>
<span class="ss">values: </span><span class="p">{</span> <span class="ss">success: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">perform: </span><span class="n">elapsed</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
<span class="vi">@influx</span><span class="p">.</span><span class="nf">write_points</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">rescue</span> <span class="no">Exception</span>
<span class="n">data</span> <span class="o"><<</span> <span class="p">{</span> <span class="ss">series: </span><span class="s2">"sidekiq"</span><span class="p">,</span> <span class="ss">tags: </span><span class="n">tags</span><span class="p">,</span> <span class="ss">values: </span><span class="p">{</span> <span class="ss">failure: </span><span class="mi">1</span> <span class="p">}</span> <span class="p">}</span>
<span class="vi">@influx</span><span class="p">.</span><span class="nf">write_points</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">raise</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>If the job succeeds we send a <code class="language-plaintext highlighter-rouge">success</code> point with our tags and the time the job ran (<code class="language-plaintext highlighter-rouge">perform</code>). If it fails, we only send a <code class="language-plaintext highlighter-rouge">failure</code> point with tags.</p>
<p>To load our middleware, we add this code to our Sidekiq initialiser:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">Sidekiq</span><span class="p">.</span><span class="nf">configure_server</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="p">.</span><span class="nf">server_middleware</span> <span class="k">do</span> <span class="o">|</span><span class="n">chain</span><span class="o">|</span>
<span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"INFLUX_INSTRUMENTATION"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"true"</span>
<span class="n">chain</span><span class="p">.</span><span class="nf">add</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">Sidekiq</span><span class="o">::</span><span class="no">Middleware</span><span class="o">::</span><span class="no">Server</span><span class="o">::</span><span class="no">Influxdb</span><span class="p">,</span> <span class="ss">client: </span><span class="no">INFLUXDB_CLIENT</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h2 id="visualising-our-metrics">Visualising Our Metrics</h2>
<p>To visualise our metrics, we use <a href="https://grafana.com/">Grafana</a>. It’s a great tool allowing us to graph metrics from different time series database. It can connect to <a href="https://grafana.com/grafana#unify">different backends</a>, supports <a href="http://docs.grafana.org/alerting/rules/">alerting</a>, <a href="http://docs.grafana.org/reference/annotations/">annotations</a> and dynamic <a href="http://docs.grafana.org/reference/templating/">templating</a>.</p>
<p>Templating allows us to add filters to our dashboards and using the tags we defined earlier we’ll be able to filter our metrics by environment, queue or job name.</p>
<figure>
<img src="/assets/posts/2017-03-20-sidekiq-instrumentation/filters.gif" alt="" />
</figure>
<p>This article is not about Grafana so we’ll only show you how to build one graph. Let’s say we want to graph the average and 99th percentile time our jobs are taking to process.</p>
<p>We’ll have this InfluxQL query to get the <code class="language-plaintext highlighter-rouge">mean</code> time:</p>
<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">SELECT</span> <span class="n">mean</span><span class="p">(</span><span class="nv">"perform"</span><span class="p">)</span> <span class="k">FROM</span> <span class="nv">"drivy"</span><span class="p">.</span><span class="nv">"sidekiq"</span>
<span class="k">WHERE</span> <span class="nv">"env"</span> <span class="o">=~</span> <span class="o">/^</span><span class="err">$</span><span class="n">environment</span><span class="err">$</span><span class="o">/</span>
<span class="k">AND</span> <span class="nv">"queue"</span> <span class="o">=~</span> <span class="o">/^</span><span class="err">$</span><span class="n">queue</span><span class="err">$</span><span class="o">/</span>
<span class="k">AND</span> <span class="nv">"worker"</span> <span class="o">=~</span> <span class="o">/^</span><span class="err">$</span><span class="n">job</span><span class="err">$</span><span class="o">/</span>
<span class="k">AND</span> <span class="err">$</span><span class="n">timeFilter</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="nb">time</span><span class="p">(</span><span class="err">$</span><span class="n">period</span><span class="p">)</span> <span class="n">fill</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span></code></pre></figure>
<p>Here we select the <code class="language-plaintext highlighter-rouge">mean</code> of the <code class="language-plaintext highlighter-rouge">perform</code> field that we filter by our tags then group it by time interval. We also use <code class="language-plaintext highlighter-rouge">fill(0)</code> to replace all the null points with <code class="language-plaintext highlighter-rouge">0</code>.</p>
<p>To get the 99th percentile we use the same query but replace <code class="language-plaintext highlighter-rouge">mean("perform")</code> by <code class="language-plaintext highlighter-rouge">percentile("perform", 99)</code>.</p>
<p>Here how this graph looks in Grafana:</p>
<figure>
<img src="/assets/posts/2017-03-20-sidekiq-instrumentation/job_sql.png" alt="" />
<figcaption>SQL Editor</figcaption>
</figure>
<figure>
<img src="/assets/posts/2017-03-20-sidekiq-instrumentation/job_graph.png" alt="" />
</figure>
<p>And here’s our whole dashboard:</p>
<figure>
<img src="/assets/posts/2017-03-20-sidekiq-instrumentation/dashboard.png" alt="" />
<figcaption>
You'll find our Grafana config in the following <a href="https://gist.github.com/mickey/cc0de597b763e40f4f8acff5b75b6858">gist</a>.
</figcaption>
</figure>
<h2 id="whats-next">What’s next</h2>
<p>The next step would probably be to add some alerting. At Drivy, we alert on the rate of failure and on the queue congestion so we can reorganize our queues or scale our workers accordingly. Grafana supports alerting but that’s probably out of the scope of this article.</p>
<p>Instrumenting Sidekiq has been a big win for our team. It helps us better organize our queues, follow the performance of our jobs, detect anomalies or investigate some bugs.</p>
<p>As <a href="https://en.drivy.com">Drivy</a> continues to grow, we were interested in having more insights on the performance of our jobs, their success rate and the congestion of our queues. This would help us:</p>
Thu, 20 Apr 2017 00:00:00 +0000
https://getaround.tech/sidekiq-instrumentation/
https://getaround.tech/sidekiq-instrumentation/API Driven AppsJean-Élie Le Corre<p>At Drivy, the product is often changing. To be as reactive as possible, we want to be fast and iterate a lot of features. For mobile teams, it’s a challenge to keep up the pace for our iOS and Android apps. You have to deal with the release cycle of the App Store for iOS, and with the users who don’t update their app to the last version on both platforms.</p>
<p>We use a lot of different technical solutions to make our apps flexible, here we will describe one of them: how to make your content dynamic and let your users use your latest features even if it’s not yet implemented in your native app.</p>
<h2 id="dynamic-content">Dynamic Content</h2>
<p>Recently we had to implement a new view helping our car owners to see all their requests at once. It’s quite a simple view:</p>
<p><img src="../assets/posts/2017-04-18-api-driven-apps/requests.png" alt="Requests" /></p>
<p>A single cell looks like this:</p>
<p><img src="../assets/posts/2017-04-18-api-driven-apps/request_cell.png" alt="Request cell" /></p>
<p>To fill the content, the usual way of doing it would be the API sending an array of <code class="language-plaintext highlighter-rouge">Requests</code> with raw data:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"avatar_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"…assets/avatar.jpg"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2017-02-11T14:03:23Z"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"driver_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jean-Élie Locataire"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"car_title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Tesla Model S"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"start_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2013-07-01T14:03:23Z"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"end_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2013-07-01T14:03:23Z"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"mileage"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mi">69</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"auto_cancel"</span>
<span class="p">}</span></code></pre></figure>
<p>Then, the app would format the dates, the distances, the price and the state to display all of them with style.</p>
<p>Instead, we used a <code class="language-plaintext highlighter-rouge">GenericItem</code> model:</p>
<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span>
<span class="w"> </span><span class="nl">"image_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"…assets/avatar.jpg"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Jean-Élie Locataire"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"subtitle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Tesla Model S"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"top_right_detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"02/11/2017"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"detail_text_html"</span><span class="p">:</span><span class="w"> </span><span class="s2">"from <strong>Wed, Feb 15, 2017</strong> at 07:00</span>
<span class="s2"> </span><span class="se">\n</span><span class="s2">to <strong>Sat, Feb 18, 2017</strong> at 07:30</span>
<span class="s2"> </span><span class="se">\n</span><span class="s2">to <strong>200 km</strong> included"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"bottom_left_detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"€69"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"bottom_right_detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Automatically cancelled"</span><span class="p">,</span>
<span class="w"> </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://www.drivy.com/requests/1"</span>
<span class="p">}</span></code></pre></figure>
<p>From now on, the app doesn’t even know what it manipulates, it could be a <code class="language-plaintext highlighter-rouge">Car</code>, a <code class="language-plaintext highlighter-rouge">Driver</code> or a <code class="language-plaintext highlighter-rouge">Request</code>.</p>
<p>Here is a schematic representation of the generic cell composed with an <code class="language-plaintext highlighter-rouge">UIImageView</code> and <code class="language-plaintext highlighter-rouge">UILabels</code>:</p>
<p><img src="../assets/posts/2017-04-18-api-driven-apps/generic_cell.png" alt="Generic Cell" />
The server is responsible for formatting all the content like the dates and price.
The apps send a <code class="language-plaintext highlighter-rouge">Accept-Language</code> header in every API call with the current locale of the device, that way the back end can localize the content accordingly.</p>
<p>We also use html with simplified tags in the middle details, allowing us to format dynamically the content. On the native side, almost all the fields are optional, the cell is adapting its height to the content.</p>
<p>Once we have this generic mechanism, we can leverage it for other part of the app. Here is our message cell, you can find an image, a title, details and top right details. The layout is different though, we use a <code class="language-plaintext highlighter-rouge">type</code> property on the <code class="language-plaintext highlighter-rouge">GenericItem</code> to tell the app which layout it must use to display the cell.</p>
<p><img src="../assets/posts/2017-04-18-api-driven-apps/message_cell.png" alt="" /></p>
<h2 id="dynamic-features">Dynamic Features</h2>
<p>You can see a <code class="language-plaintext highlighter-rouge">url</code> parameter attached to the generic item.
Whenever a user tap the cell, the app’s router parse this url, if we have a native view responding to this path, it is pushed on the stack. If the path is not yet handled by the app, it’s opened in an in-app web browser.</p>
<p>That way, the app can handle new features, whether you have not yet implemented them in the app, or if a user didn’t update the app yet.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Making natives apps flexible is useful if you want to be agile and iterate quickly on your app.
Generic content and dynamic navigation are only 2 technical solutions to achieve that, so far it helped us a lot!</p>
<p>At Drivy, the product is often changing. To be as reactive as possible, we want to be fast and iterate a lot of features. For mobile teams, it’s a challenge to keep up the pace for our iOS and Android apps. You have to deal with the release cycle of the App Store for iOS, and with the users who don’t update their app to the last version on both platforms.</p>
Tue, 18 Apr 2017 00:00:00 +0000
https://getaround.tech/api-driven-apps/
https://getaround.tech/api-driven-apps/Send Rails console commands to SlackAntoine Augusti<p>At Drivy, our main repository is a Ruby on Rails application that we run on Heroku. Sometimes, things don’t go as planned and we need to run one-off commands to fix a particular piece of data or to investigate a bit further about an issue. To do this, we use the <code class="language-plaintext highlighter-rouge">rails console</code> command in the production environment.</p>
<p>Let’s be clear: <strong>we reach for the console only if we have no other choice</strong>. We always deploy new code to fix bugs or use migrations if we need to update a large number of database rows. But as we still need it sometimes, we need to be sure this is a tool we can trust and control. Our rule is that if a command has been run more than twice, it needs to be automated in our back-office. We also have processes to limit the access to this feature to a group of people and rules in place to comply with our data privacy policy.</p>
<h2 id="reporting-on-console-commands">Reporting on console commands</h2>
<p>We want commands typed by authorised developers or system administrators to be made public and available in real-time. This serves multiple purposes:</p>
<ul>
<li><strong>be aware when this happen</strong>: commands should be executed manually only on special conditions. If we need to run commands to fix events often, we need to build something that can handle automatically this kind of events to avoid at all cost the need to run console commands.</li>
<li><strong>have an history of executed commands</strong>: if at some point we encounter an issue we had weeks ago, we can see how we fixed the issue by looking at the commands’ log.</li>
<li><strong>let developers discover commands</strong>: because commands are made public, developers often discover interesting ways to fix an issue. This leads to discussions and code improvements later.</li>
</ul>
<h2 id="hooking-into-the-rails-console">Hooking into the Rails console</h2>
<p>We use <a href="https://github.com/pry/pry">pry</a> locally, but the Rails console uses <code class="language-plaintext highlighter-rouge">irb</code> in staging or production. We needed a way to hook into <code class="language-plaintext highlighter-rouge">irb</code> and we used the fact that <code class="language-plaintext highlighter-rouge">irb</code> interacts with the standard output to override the behaviour of the <code class="language-plaintext highlighter-rouge">STDOUT</code> class. To know if we need to change the behaviour of the standard output, we check if we are running in a one-off Heroku dyno thanks to the environment variable <code class="language-plaintext highlighter-rouge">DYNO</code> set by Heroku.</p>
<p>We added an initializer which looks like this:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">is_staging_or_prod</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">.</span><span class="nf">production?</span> <span class="o">||</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span><span class="p">.</span><span class="nf">staging?</span>
<span class="n">dyno_in_run_mode</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'DYNO'</span><span class="p">,</span> <span class="s1">'nope'</span><span class="p">).</span><span class="nf">starts_with?</span><span class="p">(</span><span class="s1">'run'</span><span class="p">)</span>
<span class="n">dev_name</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'DEV_NAME'</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_staging_or_prod</span> <span class="o">&&</span> <span class="n">dyno_in_run_mode</span>
<span class="k">raise</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">Errors</span><span class="o">::</span><span class="no">DevNameNotSetError</span> <span class="k">if</span> <span class="n">dev_name</span><span class="p">.</span><span class="nf">blank?</span>
<span class="c1"># Override how printing to sdtout works by sending</span>
<span class="c1"># the output of stdout to a Slack webhook also.</span>
<span class="c1"># When writing commands in irb, irb prints to stdout</span>
<span class="k">class</span> <span class="o"><<</span> <span class="no">STDOUT</span>
<span class="kp">include</span> <span class="no">Drivy</span><span class="o">::</span><span class="no">Console</span><span class="o">::</span><span class="no">ReportCommand</span>
<span class="k">alias</span> <span class="ss">:usual_write</span> <span class="ss">:write</span>
<span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="n">usual_write</span><span class="p">(</span><span class="n">string</span><span class="p">)</span>
<span class="n">send_command_to_slack</span><span class="p">(</span><span class="n">dev_name</span><span class="p">,</span> <span class="n">string</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>And the <code class="language-plaintext highlighter-rouge">ReportCommand</code> class actually does the work of reading from the standard output history using <code class="language-plaintext highlighter-rouge">Readline::HISTORY</code> and sending the data to an external service (Slack for us). The code below gives the main logic, the complete code is available <a href="https://gist.github.com/AntoineAugusti/a379d4060da40e1ec4218a4e6e3974dc">in a gist</a>.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">module</span> <span class="nn">Drivy::Console::ReportCommand</span>
<span class="k">def</span> <span class="nf">send_command_to_slack</span><span class="p">(</span><span class="n">developer_name</span><span class="p">,</span> <span class="n">command_output</span><span class="p">)</span>
<span class="k">return</span> <span class="k">unless</span> <span class="n">has_command?</span> <span class="o">&&</span> <span class="n">has_output?</span><span class="p">(</span><span class="n">command_output</span><span class="p">)</span>
<span class="c1"># Documentation is at https://api.slack.com/docs/message-attachments</span>
<span class="n">fields</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="ss">title: </span><span class="s2">"Command"</span><span class="p">,</span>
<span class="ss">value: </span><span class="n">wrap_command</span><span class="p">(</span><span class="n">read_command</span><span class="p">),</span>
<span class="ss">short: </span><span class="kp">true</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="ss">title: </span><span class="s2">"Output"</span><span class="p">,</span>
<span class="ss">value: </span><span class="n">wrap_command</span><span class="p">(</span><span class="n">parse_output</span><span class="p">(</span><span class="n">command_output</span><span class="p">)),</span>
<span class="ss">short: </span><span class="kp">false</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="ss">title: </span><span class="s2">"Developer"</span><span class="p">,</span>
<span class="ss">value: </span><span class="n">developer_name</span><span class="p">,</span>
<span class="ss">short: </span><span class="kp">true</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="n">env_color</span><span class="p">,</span> <span class="n">env_title</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"#e74c3c"</span><span class="p">,</span> <span class="s2">"production"</span><span class="p">]</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">attachments: </span><span class="p">[</span>
<span class="p">{</span>
<span class="ss">fields: </span><span class="n">fields</span><span class="p">,</span>
<span class="ss">color: </span><span class="n">env_color</span><span class="p">,</span>
<span class="ss">footer: </span><span class="s2">"Console </span><span class="si">#{</span><span class="n">env_title</span><span class="si">}</span><span class="s2"> spy"</span><span class="p">,</span>
<span class="ss">footer_icon: </span><span class="s2">"https://drivy-prod-static.s3.amazonaws.com/slack/spy-small.png"</span><span class="p">,</span>
<span class="ss">ts: </span><span class="no">Time</span><span class="p">.</span><span class="nf">zone</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">to_i</span><span class="p">,</span>
<span class="ss">mrkdwn_in: </span><span class="p">[</span><span class="s2">"fields"</span><span class="p">],</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">slack_client</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span>
<span class="k">raise</span> <span class="s2">"Failed to notify Slack of console command, status: </span><span class="si">#{</span><span class="n">response</span><span class="p">.</span><span class="nf">status</span><span class="si">}</span><span class="s2">"</span> <span class="k">unless</span> <span class="n">response</span><span class="p">.</span><span class="nf">success?</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">has_command?</span>
<span class="no">Readline</span><span class="o">::</span><span class="no">HISTORY</span><span class="p">.</span><span class="nf">length</span> <span class="o">>=</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">has_output?</span><span class="p">(</span><span class="n">command_output</span><span class="p">)</span>
<span class="k">return</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">command_output</span><span class="p">.</span><span class="nf">instance_of?</span> <span class="no">String</span>
<span class="n">command_output</span><span class="p">.</span><span class="nf">strip</span><span class="p">.</span><span class="nf">start_with?</span> <span class="s2">"=>"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">read_command</span>
<span class="no">Readline</span><span class="o">::</span><span class="no">HISTORY</span><span class="p">[</span><span class="no">Readline</span><span class="o">::</span><span class="no">HISTORY</span><span class="p">.</span><span class="nf">length</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>We use the console thanks to our homemade Drivy CLI and not directly through the Heroku CLI. We will likely talk about our CLI in upcoming posts, it is a tool we use to manage our day-to-day operations (running commands, releasing, handling database migrations, managing content…). After configuring the Slack webhook integration, the final result looks like this:</p>
<p><img src="../assets/posts/2017-03-16-rails-console-spy/output.png" alt="" /></p>
<p>We’re pretty happy about this new tool because we gained a lot in visibility and confidence in our operations. We are always looking forward to improving our developers’ tooling.</p>
<p>At Drivy, our main repository is a Ruby on Rails application that we run on Heroku. Sometimes, things don’t go as planned and we need to run one-off commands to fix a particular piece of data or to investigate a bit further about an issue. To do this, we use the <code class="language-plaintext highlighter-rouge">rails console</code> command in the production environment.</p>
Thu, 16 Mar 2017 00:00:00 +0000
https://getaround.tech/rails-console-spy/
https://getaround.tech/rails-console-spy/Use Android's FileProvider to get rid of the Storage PermissionRomain Guefveneu<p>When you need to share a file with other apps, the easiest way could be to use the external storage as a temporary place where to save this file.
For example, if you need to take a picture with a camera app, you need to specify a file where the camera app will save the picture, and using external storage might be tempting.</p>
<p>However this solution has many drawbacks:</p>
<ul>
<li>You lose control of your file</li>
</ul>
<p>Because you put your file in a public directory, you can’t safely delete it nor control which app can read and modify it.</p>
<ul>
<li>You have to ask <code class="language-plaintext highlighter-rouge">WRITE_EXTERNAL_STORAGE</code> permission</li>
</ul>
<p>This could make many users afraid and may lead to a bad UX with runtime permissions.</p>
<ul>
<li>External storage quickly becomes a mess</li>
</ul>
<p>Since you can’t safely delete your shared files, you let them in the external storage forever.</p>
<h2 id="use-a-fileprovider">Use a FileProvider</h2>
<p>You may already use <code class="language-plaintext highlighter-rouge">ContentProvider</code> to share data with other apps, you can do the same with <a href="https://developer.android.com/training/secure-file-sharing/setup-sharing.html"><code class="language-plaintext highlighter-rouge">FileProvider</code></a> to share files!</p>
<p><code class="language-plaintext highlighter-rouge">FileProvider</code> is part of Support Library, available for all Android versions starting 2.3.
The main goal of this API is to temporary open a private file to some targeted apps: you keep the file in your private folder, and let some other apps read or even write it via a secured <code class="language-plaintext highlighter-rouge">ContentProvider</code>. Permissions are revoked when your activity is destroyed.</p>
<h2 id="implementation">Implementation</h2>
<h3 id="add-support-lib-dependency">Add support lib dependency</h3>
<p>In your app <code class="language-plaintext highlighter-rouge">build.gradle</code>, add this dependency:</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">compile</span> <span class="s1">'com.android.support:support-v4:<version>'</span></code></pre></figure>
<h3 id="specify-available-folders">Specify available folders</h3>
<p>Create an xml file (for example <code class="language-plaintext highlighter-rouge">file_provider_paths.xml</code>) in <code class="language-plaintext highlighter-rouge">xml</code> resources folder:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><paths</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span><span class="nt">></span>
<span class="nt"><files-path</span> <span class="na">name=</span><span class="s">"shared"</span> <span class="na">path=</span><span class="s">"shared/"</span><span class="nt">/></span>
<span class="nt"></paths></span></code></pre></figure>
<h3 id="define-a-provider">Define a Provider</h3>
<p>In your <code class="language-plaintext highlighter-rouge">ApplicationManifest.xml</code>, add this provider inside <code class="language-plaintext highlighter-rouge">application</code> node:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><provider</span>
<span class="na">android:name=</span><span class="s">"android.support.v4.content.FileProvider"</span>
<span class="na">android:authorities=</span><span class="s">"<your provider authority>"</span>
<span class="na">android:exported=</span><span class="s">"false"</span>
<span class="na">android:grantUriPermissions=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><meta-data</span>
<span class="na">android:name=</span><span class="s">"android.support.FILE_PROVIDER_PATHS"</span>
<span class="na">android:resource=</span><span class="s">"@xml/file_provider_paths"</span><span class="nt">/></span>
<span class="nt"></provider></span></code></pre></figure>
<p>Just set your <code class="language-plaintext highlighter-rouge">android:authorities</code>, like <code class="language-plaintext highlighter-rouge">com.drivy.android.myfileprovider</code>, and link the created xml resource file in <code class="language-plaintext highlighter-rouge">android:resource</code></p>
<p><em>ProTip</em>: Use <code class="language-plaintext highlighter-rouge">${applicationId}</code>in <code class="language-plaintext highlighter-rouge">android:authorities</code> to automatically use your package name: <code class="language-plaintext highlighter-rouge">${applicationId}.myfileprovider</code></p>
<h3 id="share-a-file">Share a file</h3>
<p>First thing to do: get the shared file’s Uri</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">Uri</span> <span class="n">sharedFileUri</span> <span class="o">=</span> <span class="nc">FileProvider</span><span class="o">.</span><span class="na">getUriForFile</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="o"><</span><span class="n">your</span> <span class="n">provider</span> <span class="n">auhtority</span><span class="o">>,</span> <span class="n">sharedFile</span><span class="o">);</span></code></pre></figure>
<p>Use the same provider authority as in your <code class="language-plaintext highlighter-rouge">ApplicationManifest.xml</code>.
The Uri will looks like this:
<code class="language-plaintext highlighter-rouge">content://com.drivy.android.myfileprovider/shared/myfile.jpg</code></p>
<p>You can now create a chooser intent:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">ShareCompat</span><span class="o">.</span><span class="na">IntentBuilder</span> <span class="n">intentBuilder</span> <span class="o">=</span> <span class="nc">ShareCompat</span><span class="o">.</span><span class="na">IntentBuilder</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="k">this</span><span class="o">).</span><span class="na">addStream</span><span class="o">(</span><span class="n">sharedFileUri</span><span class="o">);</span></code></pre></figure>
<p>And start it:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nc">Intent</span> <span class="n">chooserIntent</span> <span class="o">=</span> <span class="n">intentBuilder</span><span class="o">.</span><span class="na">createChooserIntent</span><span class="o">();</span>
<span class="n">startActivity</span><span class="o">(</span><span class="n">chooserIntent</span><span class="o">);</span></code></pre></figure>
<p>That’s it!</p>
<h2 id="one-last-thing-legacy-support">One last thing: Legacy support</h2>
<p>You need to manually grant permission for older Android versions.</p>
<h3 id="grant-permission-for-intent">Grant permission for intent</h3>
<p>Before sharing your file, you’ll have to manually grant the permission (read and/or write), for all applications targeted with your intent. Indeed, you can’t know which one the user will choose to share the file with.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="nc">PackageManager</span> <span class="n">packageManager</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">();</span>
<span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">ResolveInfo</span><span class="o">></span> <span class="n">activities</span> <span class="o">=</span> <span class="n">packageManager</span><span class="o">.</span><span class="na">queryIntentActivities</span><span class="o">(</span><span class="n">intent</span><span class="o">,</span> <span class="nc">PackageManager</span><span class="o">.</span><span class="na">MATCH_DEFAULT_ONLY</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">ResolveInfo</span> <span class="n">resolvedIntentInfo</span> <span class="o">:</span> <span class="n">activities</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">packageName</span> <span class="o">=</span> <span class="n">resolvedIntentInfo</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">.</span><span class="na">packageName</span><span class="o">;</span>
<span class="n">context</span><span class="o">.</span><span class="na">grantUriPermission</span><span class="o">(</span><span class="n">packageName</span><span class="o">,</span> <span class="n">uri</span><span class="o">,</span> <span class="n">permissions</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<h3 id="revoke-permissions-on-activity-destroy">Revoke permissions on activity destroy</h3>
<p>We can assume that, when returning back to your app and leaving the activity, the shared file has already been copied by the targeted app, and is not required anymore. You can revoke all permissions.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">context</span><span class="o">.</span><span class="na">revokeUriPermission</span><span class="o">(</span><span class="n">fileUri</span><span class="o">,</span> <span class="n">permissions</span><span class="o">);</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>FileProvider is a really convenient and elegant way to get rid of <code class="language-plaintext highlighter-rouge">WRITE_EXTERNAL_STORAGE</code>, I encourage you to use it: your app will be better without extra permissions.</p>
<h3 id="sources">Sources</h3>
<ol>
<li><a href="https://github.com/drivy/blog-android-fileprovider">Github project</a></li>
<li><a href="https://developer.android.com/training/secure-file-sharing/setup-sharing.html">Setting Up File Sharing - Android Developers</a></li>
</ol>
<p>When you need to share a file with other apps, the easiest way could be to use the external storage as a temporary place where to save this file.
For example, if you need to take a picture with a camera app, you need to specify a file where the camera app will save the picture, and using external storage might be tempting.</p>
Tue, 14 Mar 2017 00:00:00 +0000
https://getaround.tech/android-fileprovider/
https://getaround.tech/android-fileprovider/Taskqueues tipsAdrien Di Pasquale<p>Taskqueues are used to asynchronously run tasks (indistinctly called “jobs”). They are very useful to enqueue actions for later processing in order to preserve short response times. For example, during the signup you may want to send the confirmation mail asynchronously. Or you may have a slow task to generate an export file instead of doing it inline.</p>
<p>As a website grows, the need to use a taskqueue often arises. The general architecture usually looks like this:</p>
<p><img src="../assets/posts/2015-10-12-taskqueues-go-wild/taskqueues.svg" alt="" /></p>
<ul>
<li>The broker is a messaging queue (<a href="https://redis.io/">Redis</a>, <a href="https://www.rabbitmq.com/">RabbitMQ</a> …)</li>
<li>The webapp enqueues tasks upon specific requests</li>
<li>The CRON enqueues tasks at specific times (often called a <code class="language-plaintext highlighter-rouge">Clock</code> or a <code class="language-plaintext highlighter-rouge">Scheduler</code>)</li>
<li>Workers dequeue and process tasks.</li>
</ul>
<h2 id="frameworks">Frameworks</h2>
<p>All the main web languages have several: <a href="http://www.celeryproject.org/">Celery</a>, <a href="http://python-rq.org/">RQ</a>, <a href="https://mrq.readthedocs.io/en/latest/">MRQ</a> (Python), <a href="https://github.com/resque/resque">Resque</a>, <a href="http://sidekiq.org/">Sidekiq</a> (Ruby) …</p>
<p>The main qualities of a framework are:</p>
<ul>
<li><strong>Efficiency</strong>: Fast and cheap</li>
<li><strong>Reliability</strong>: All tasks get executed exactly the right number of times</li>
<li><strong>Visibility</strong>: Live monitoring, debugging tools, exception tracebacks …</li>
</ul>
<h2 id="common-problems">Common problems</h2>
<p>Workers generally execute the same codebase as the app. This is the source of many problems, since the codebase is not optimized for this context. Workers aim at high throughput but Ruby and Python default implementations are single-threaded. Also, tasks often interact with unreliable third party services, so they have to be very resilient.</p>
<p>We use a taskqueue extensively at Drivy, so we have some tips to share!</p>
<h2 id="tip-1-design-tasks-well">Tip 1: Design tasks well</h2>
<p>Most importantly, tasks should aim at being <strong>re-entrant</strong>. This means that they can stop in the middle and be ran again in another process. This is important because jobs may raise exceptions in the middle and be retried at a later time. Workers may also crash and restart.
This often means that your tasks should be stateless, and not expect the DB to be in a certain state at processing time. They should be responsible for checking the state before running their actions.</p>
<p>Tasks should try to be <strong>idempotent</strong>. This means that running them several times (consecutively or in parallel) should not change the final output. This is very convenient, so that you’re not too scared if a task gets enqueued or processed multiple times.</p>
<p>It is also a good practice that <strong>tasks require the least number of arguments possible</strong>. For instance, you can only send a model ID instead of sending the whole serialized object. This gives you better predictability and easier visibility.</p>
<p>Additionally, if you want workers to run in multiple threads, you should be careful to design thread-safe tasks. Specifically, you should pay attention to the libraries you use, they are often the culprits of unsafe calls.</p>
<h4 id="--avoid-class-level-calls">-> Avoid class level calls</h4>
<p>Example of unsafe Ruby code:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">SomeTask</span> <span class="o"><</span> <span class="no">ResqueTask</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">perform</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="no">Util</span><span class="p">.</span><span class="nf">some_opt</span> <span class="o">=</span> <span class="n">value</span>
<span class="no">Util</span><span class="p">.</span><span class="nf">do_something</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Classes are instanciated process-wise. So when you run multiple tasks in parralel, <code class="language-plaintext highlighter-rouge">Util.some_opt</code> will be the same process-wise. This can lead to many problems.</p>
<p><img src="../assets/posts/2015-10-12-taskqueues-go-wild/jobs_threads.svg" alt="" /></p>
<p>Here is the same code refactored not to use class level calls:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">SomeTask</span> <span class="o"><</span> <span class="no">ResqueTask</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">perform</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">util</span> <span class="o">=</span> <span class="no">Util</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">util</span><span class="p">.</span><span class="nf">do_something</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<h4 id="--avoid-mutable-instance-variables">-> Avoid Mutable instance variables</h4>
<p>Here is another example of unsafe code in Python:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">SomeTask</span><span class="p">(</span><span class="n">MRQTask</span><span class="p">):</span>
<span class="n">some_list</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">params</span><span class="p">):</span>
<span class="n">some_list</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="sh">"</span><span class="s">value</span><span class="sh">"</span><span class="p">])</span></code></pre></figure>
<p>When ran in parallel, <code class="language-plaintext highlighter-rouge">some_list</code> will be the same process-wise. A simple way to fix it is to instanciate missing params inside the task perform method:</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">SomeTask</span><span class="p">(</span><span class="n">MRQTask</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">params</span><span class="p">):</span>
<span class="n">some_list</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">some_list</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="sh">"</span><span class="s">value</span><span class="sh">"</span><span class="p">])</span></code></pre></figure>
<h2 id="tip-2-know-your-broker">Tip 2: Know your broker</h2>
<p>We often misunderstand the exact behaviour of our backend task storage systems. It is often a good idea to take the time to read the specs and the open issues of the system you use.</p>
<p>Each broker library has its own trade-offs in terms of delivery atomicity. <code class="language-plaintext highlighter-rouge">Exactly-once</code> is the guarantee that each message will only be delivered once.
Few systems can provide this, and some would say that it’s infeasible in a distributed environment.
Most systems provide <code class="language-plaintext highlighter-rouge">at-least-once</code> or <code class="language-plaintext highlighter-rouge">at-most-once</code> delivery guarantees. You therefore often have to handle redundant messages delivery.</p>
<p>Also, a broker should have a good resiliency to crashes. You don’t want to loose tasks on system crashes. If you use in-memory storage systems like Redis, you should backup regularly to the file system if you don’t want this to happen.</p>
<h2 id="tip-3-monitor-your-broker">Tip 3: Monitor your broker</h2>
<p>In some cases, tasks enqueuing calls may get entirely discarded: the webapp (or the CRON) tries to enqueue a task to the broker, but the call fails. This is obviously a very bad situation, as you’re going to have a very hard time trying to re-enqueue the lost tasks afterwards. If the volumes are high, or if the failures are silent, then this quickly turns into a catastrophic situation.</p>
<p>One situation where this can happen is when the broker exceeds its storage capacity. You should foresee this happening. A common issue causing this is to pollute your broker with metadata: very long arguments, results, logs, stacktraces …</p>
<p>Having network issues with the broker can also become a very painful point, especially since it’s often random. You should try and design your infrastructure to have as little latency as possible between the app and the broker.</p>
<p>A very good advice is thus to monitor your broker’s system in depth, looking at the different metrics and setting up alerts. You can also look for SaaS hosting for your brokers as they often provide out-of-the-box monitoring solutions.</p>
<h2 id="tip-4-specialize-your-workers">Tip 4: Specialize your workers</h2>
<p>Workers can have different configs depending on what type of tasks they perform. For instance:</p>
<ul>
<li>Computing Worker: 2Gb ram + 4 threads</li>
<li>I/O Worker: 256Mb ram + 100 threads</li>
</ul>
<p><img src="../assets/posts/2015-10-12-taskqueues-go-wild/workers.svg" alt="workers different configs" /></p>
<h2 id="tip-5-strategical-queuing">Tip 5: Strategical queuing</h2>
<p>You usually try and optimize your different workers and queues to be able to dequeue everything in time at the lowest cost possible.</p>
<p>Here is an example of a queuing strategy:</p>
<p><img src="../assets/posts/2015-10-12-taskqueues-go-wild/workers_and_queues.svg" alt="workers + queues" /></p>
<p><em>(the number on the arrows represent the queue priority for each worker)</em></p>
<p>There is no one-size-fits-all solution for optimizing this. You’ll have to iterate and find out what works best for your tasks with your specific workers. You’ll have to change it over time as you update tasks and their mean runtime evolves independently. Again, monitoring is absolutely necessary.</p>
<p>At Drivy we have decided not to name our queues after the bit of logic they handle. We don’t want to have a <code class="language-plaintext highlighter-rouge">mail</code> queue or a <code class="language-plaintext highlighter-rouge">car_photos_checks</code> queue. We think it’s more scalable to group tasks in queues depending on their properties: mean runtime, acceptable dequeuing delay. So we have queues like <code class="language-plaintext highlighter-rouge">urgent_fast</code> or <code class="language-plaintext highlighter-rouge">average_slow</code>.</p>
<h2 id="tip-6-anticipate-tasks-congestions">Tip 6: Anticipate tasks congestions</h2>
<p>Here is a common bad day scenario:</p>
<ul>
<li>Task A becomes 10 times slower (maybe because of network latency)</li>
<li>Task B keeps failing (maybe because some corrupt data got introduced in the DB)</li>
<li>Workers cannot dequeue everything in time</li>
</ul>
<p>These sort of situations will necessarily happen. You should not try and avoid them altogether but at least be monitoring this, and be ready to take some actions.</p>
<p>Your monitoring system should be able to alert you when queues don’t respect their SLAs anymore.
Being able to scale lots of worker quickly will help you. Try and know your limits in advance so you don’t overload a resource or consume all the network bandwidth. Auto-scaling is not easy to setup at all, don’t rely on it at the beginning.</p>
<h2 id="tip-7-anticipate-worker-crashes">Tip 7: Anticipate worker crashes</h2>
<p>Here is a common list of things that can go wrong:</p>
<ul>
<li>Exceptions</li>
<li>Hardware crashes</li>
<li>System reboots (e.g. Heroku apps restart everyday)</li>
</ul>
<p>You absolutely need to handle soft shutdowns: receive the signals, try and finish tasks in time, and requeue them otherwise.
Your workers should also feature an auto-restart feature, so that the congestion doesn’t get out of hand to quickly.</p>
<p>Memory leaks may also happen. Depending on the volumes, this may grow quickly and workers may crash. (be aware that Heroku Errors like <a href="https://devcenter.heroku.com/articles/error-codes#r14-memory-quota-exceeded"><code class="language-plaintext highlighter-rouge">Memory Exceeded</code></a> are not monitored by error trackers like new relic). Debugging memory leaks is very hard. It’s even harder in taskqueue systems, so try and find the right tools for your stack, and arm yourself with patience.</p>
<h2 id="tip-8-reliable-cron">Tip 8: Reliable CRON</h2>
<p>Syntax errors can go undetected: the CRON system is rarely ran in development and even less covered by tests.</p>
<p>You may also encounter runtime errors e.g.: argument computed on the fly fails. This can easily go undetected as we often run the clock as a background process of a proper worker.</p>
<p>To avoid this, you should try and keep the CRON as simple as possible: it should only enqueue tasks with hardcoded arguments. It should not fetch anything from external resources, like the DB.</p>
<p>Two good practices that can help:</p>
<ul>
<li>check the syntax of your CRON in your specs</li>
<li>monitor the CRON effect, possibly by enqueuing a ‘heartbeat’ task every minute and checking it’s been dequeued quickly in a separate monitoring tool.</li>
</ul>
<h2 id="tip-9-track-exceptions">Tip 9: Track exceptions</h2>
<p>Tasks <strong>will</strong> raise exceptions. You cannot and should not cover in advance all cases. User input, unexpected context, different environments are just a subset of the problems that you cannot forecast.</p>
<p>You should rather focus your efforts on tracking. Using a bug tracker service (Bugsnag, Sentry, ..) is a very good idea. You should have a middleware that logs all Exceptions to your bug tracker, and setup alerting from your bug tracker. You can then treat the bugs and create issues for each depending on their priority / urgency.</p>
<p>Here is how a bug tracker interface (Bugsnag) looks:
<img src="../assets/posts/2015-10-12-taskqueues-go-wild/bugsnag.png" alt="" /></p>
<h2 id="tip-10-have-a-coherent-retry-strategy">Tip 10: Have a coherent retry strategy</h2>
<p>Most tasks should be retried several times before being considered as properly failed. If your tasks respect the contracts from Tip 1, it should not be a problem to retry tasks by default.</p>
<p>Different tasks may have different retry strategies. All I/O calls (especially HTTP) should be expected to fail as a regular behaviour. You can implement increasing retries delays to handle temporarily unavailable resources.</p>
<h2 id="tip-11-check-your-db-connections">Tip 11: Check your DB connections</h2>
<p>Depending on the tasks, workers may hit the DB way harder than a regular web process. Try and estimate how hard before scaling your workers. You may also hit connections limits quickly: namely your SaaS provider connection limit, or your system’s <code class="language-plaintext highlighter-rouge">ulimit</code>.</p>
<p>A good practice is to use connection pools in your workers, and to be a good citizen: release the unused ones, reconnect on deconnections … Dimension these pools according to your DB limits.</p>
<p>Another very alleviating solution is to use slave databases in your workers: a task may then never slow down your product. However, you have to be careful with the possible replication lags. It is sometimes necessary to kill workers while the slaves are catching up with their masters.</p>
<h2 id="tip-12-dont-over-optimize">Tip 12: Don’t over-optimize</h2>
<p>Workers are very cheap nowadays, so you should be better off investing time into monitoring than optimizing your tasks and queries.</p>
<p><em>This was initially given as a meetup talk, see the slides here:
<a href="http://adipasquale.github.io/taskqueues-slides-2015">http://adipasquale.github.io/taskqueues-slides-2015</a>.</em></p>
<p>Taskqueues are used to asynchronously run tasks (indistinctly called “jobs”). They are very useful to enqueue actions for later processing in order to preserve short response times. For example, during the signup you may want to send the confirmation mail asynchronously. Or you may have a slow task to generate an export file instead of doing it inline.</p>
Mon, 13 Mar 2017 00:00:00 +0000
https://getaround.tech/taskqueues-tips/
https://getaround.tech/taskqueues-tips/Managing Bugs at DrivyMarc G Gauthier<p>It is very important to have a minimal number of bugs in production. I would love to say that we have absolutely no issues, but problems are bound to happen and it’s really a matter of reducing risk. The question is really about how fast and how efficiently can you react.</p>
<h2 id="detecting-bugs">Detecting Bugs</h2>
<h3 id="monitoring-errors">Monitoring Errors</h3>
<p>First we need to know if there are any problems. The more obvious solution is to be reactive to any user reports, this is why we have a dedicated Slack channel so that the customer support team can let us know about any issues:</p>
<p><img src="/assets/posts/bug/ask_a_tech.jpg" alt="" /></p>
<p>This is a very simple and light process that works pretty well for now. However this isn’t great when we have to rely on users to let us know about bugs! This is why we use <a href="https://www.bugsnag.com/">Bugsnag</a> that let us know of any 500 or JS error in our live environments:</p>
<p><img src="/assets/posts/bug/bugsnag.jpg" alt="" /></p>
<h3 id="detecting-possible-errors-using-metrics">Detecting Possible Errors Using Metrics</h3>
<p>In the examples above the error is pretty straightforward: someone tried to do something and it failed. However the most worrying bugs are the one that are failing silently. They are harder to detect and can cause a lot of problems. For instance if you have an issue with Facebook connect that fails 10% of the time silently, you will not see any 500 error… however you will see a decrease in KPIs related to the Facebook connect feature.</p>
<p>This is why we use business metrics to detect possible issues as it’s a great way to detect possible regressions. We use a wide variety of tools, depending on the situation, from <a href="https://support.google.com/analytics/answer/2790010?hl=en">Universal Analytics</a> to <a href="https://redash.io/">Redash</a>. This gives us a simple way to detect changes in patterns and react accordingly.</p>
<p><img src="/assets/posts/bug/redash.jpg" alt="" /></p>
<p>For performances and other technical monitoring, we use New Relic and Logmatic. We also have a setup with <a href="https://github.com/influxdata/telegraf">Telegraf</a>, <a href="https://www.influxdata.com/">Influxdb</a> and <a href="http://grafana.org/">Grafana</a> to check our time based metrics:</p>
<p><img src="/assets/posts/bug/grafana.jpg" alt="" /></p>
<p>We have a lot of others solution to be completely sure that everything works properly. For instance we will write “checker jobs”, which is basically a cron running frequently and checking if data is shaped as expected.</p>
<h2 id="reacting-to-bugs">Reacting To Bugs</h2>
<h3 id="notifications">Notifications</h3>
<p>We use <a href="https://aws.amazon.com/cloudwatch/">various</a> <a href="https://logmatic.io/">tools</a> with specific <a href="https://support.pagerduty.com/hc/en-us/articles/202828950-What-is-an-Escalation-Policy-">escalation policies</a> in order to react quickly to any issues. For instance <a href="https://www.pagerduty.com/">PagerDuty</a> will phone us if some metrics are getting bad, but we also built a Slack bot that let us know of less critical issues:</p>
<p><img src="/assets/posts/bug/cli.jpg" alt="" /></p>
<p>To make sure that every problem is taken care of, we have set up a new role we call the “Bugmaster” who is in charge of checking all issues.</p>
<h3 id="fixing-bugs">Fixing Bugs</h3>
<p>We work hard on <a href="https://zachholman.com/posts/deploying-software">reducing the cost of releasing to production</a>. If you can ship quickly and safely, you’re able to remove any bug quickly. To do so we constantly work our internal processes and tools. For instance we have a command line interface connected to Slack that allows us to release a new version of the website:</p>
<p><img src="/assets/posts/bug/release.jpg" alt="" /></p>
<p>Once a fix has been made, it’s important to have new automated tests to prevent regressions.</p>
<h2 id="not-having-bugs-in-the-first-place">Not Having Bugs In The First Place</h2>
<p>Detecting and fixing bugs is not the most fun part of the job… and it’s way better to have none! This is why we invest a lot in a solid test suite that runs on <a href="http://circleci.com/">CircleCI</a>, an <a href="http://marcgg.com/blog/2016/02/22/git-flow-heroku-pipelines/">efficient Git workflow</a>. We also focus on shipping small things quickly using <a href="https://zachholman.com/posts/deploying-software#prepare">feature flags</a> instead of a doing massive releases and, of course, we have a great team of individual that want to ship working software.</p>
<p>Our workflow and tools changed a lot over the years, but it’s getting more and more robust. I can honestly say that it is very rare when we’re caught off guard by a serious bug, which is great for our users.</p>
<p>Overall it feels great to be working on a project that is evolving quickly, but can keep a good level of quality.</p>
<p><em>This is a simplified update of an article originally posted on my personal website. You can <a href="http://marcgg.com/blog/2016/07/04/monitoring-bugs/">read the previous version here</a>.</em></p>
<p>It is very important to have a minimal number of bugs in production. I would love to say that we have absolutely no issues, but problems are bound to happen and it’s really a matter of reducing risk. The question is really about how fast and how efficiently can you react.</p>
Thu, 09 Mar 2017 00:00:00 +0000
https://getaround.tech/bug-management/
https://getaround.tech/bug-management/