<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Artlist</title>
    <description>The latest articles on Forem by Artlist (@artlist).</description>
    <link>https://forem.com/artlist</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4808%2Fd855822c-34e5-4953-8a71-753c3e44f6fa.jpeg</url>
      <title>Forem: Artlist</title>
      <link>https://forem.com/artlist</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/artlist"/>
    <language>en</language>
    <item>
      <title>React Testing Simplified</title>
      <dc:creator>Dan Jackson</dc:creator>
      <pubDate>Mon, 03 Oct 2022 12:47:46 +0000</pubDate>
      <link>https://forem.com/artlist/react-testing-simplified-3i2m</link>
      <guid>https://forem.com/artlist/react-testing-simplified-3i2m</guid>
      <description>&lt;p&gt;Javascript testing has a tendency to become unnecessarily complex - there's nearly &lt;em&gt;always&lt;/em&gt; multiple solutions to the same problem. &lt;/p&gt;

&lt;p&gt;This article aims to cover our chosen route at Artlist, the thinking behind these choices, and some simple code examples to get started with React Testing Library and Jest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why test a web frontend?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Confidence
&lt;/h3&gt;

&lt;p&gt;When creating and maintaining increasingly complex web apps, we need the confidence that our app isn't going to fall over at a moment's notice. We need assurance that future changes will not break our components. &lt;/p&gt;

&lt;p&gt;Depending on an app's user base; preventing bugs can save a company &lt;em&gt;vast&lt;/em&gt; amounts of money in the long-run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;p&gt;Writing tests forces us to think about how a feature will be used. Each valid and invalid path through a feature can be described using tests. This also means that requirements can be easily proven.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;As a developer working on a new part of a shared codebase, how do you &lt;em&gt;know&lt;/em&gt; what each file/component does? &lt;/p&gt;

&lt;p&gt;If written correctly, tests provide a &lt;strong&gt;specification&lt;/strong&gt; of a feature. This is why tests are often saved with a &lt;code&gt;.spec&lt;/code&gt; extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clicking save button submits the form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clicking close button closes modal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Jest
&lt;/h3&gt;

&lt;p&gt;As Jest is the default test runner for React Testing Library, we will use this for code examples.&lt;/p&gt;

&lt;p&gt;Jest allows us to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;describe&lt;/code&gt; our test suite&lt;/li&gt;
&lt;li&gt;Create each &lt;code&gt;test&lt;/code&gt; case&lt;/li&gt;
&lt;li&gt;Define what we &lt;code&gt;expect&lt;/code&gt; each test case to do
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;complex calculations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 + 1 = 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Mocking
&lt;/h4&gt;

&lt;p&gt;Jest also provides utilities to mock functions and modules. Mocks replace functionality with a function that Jest can use to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a mock function was called &lt;/li&gt;
&lt;li&gt;How many times it was called &lt;/li&gt;
&lt;li&gt;Which parameters it was called with
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;mockFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockFn&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mocks can also be useful in components that contain external functionality that we don't want to use in our tests. If you &lt;em&gt;do&lt;/em&gt; need to retain functionality use &lt;code&gt;jest.spyOn&lt;/code&gt; - this can call the original function but return a mock function.&lt;/p&gt;




&lt;h2&gt;
  
  
  React Testing Library
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://testing-library.com/react" rel="noopener noreferrer"&gt;React Testing library&lt;/a&gt; is a set of helpers that can be used in conjunction with a test runner such as &lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; to render React components and make assertions on the resulting DOM elements.&lt;/p&gt;

&lt;p&gt;The main functions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;render&lt;/code&gt;: Renders a react component&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getBy*&lt;/code&gt;/&lt;code&gt;findBy*&lt;/code&gt;/&lt;code&gt;queryBy*&lt;/code&gt;: A collection of matcher functions to find elements in the DOM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;userEvent&lt;/code&gt;: Emulate user events such as click or type&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React Testing library is best suited to creating &lt;strong&gt;Unit&lt;/strong&gt; and &lt;strong&gt;Integration&lt;/strong&gt; tests; isolated sections of our app that can be tested individually without the influence of components higher up the tree.&lt;/p&gt;




&lt;h3&gt;
  
  
  Unit Test
&lt;/h3&gt;

&lt;p&gt;Unit tests are best performed on &lt;strong&gt;pure components&lt;/strong&gt; - components that will always give the same output given the same props. These kind of tests are usually performed on basic visual components that are used to form larger structures and sections in our app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button text is displayed correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Click&lt;/span&gt; &lt;span class="nx"&gt;Me&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Click Me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Integration Test
&lt;/h3&gt;

&lt;p&gt;An integration test proves that multiple components work correctly together. In the context of React, this usually consists of rendering a compound component, and interacting with it in some way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./handleSubmit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./handle-submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;can submit form with first and last name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firstname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lastname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jackson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dan Jackson&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;handleSubmit&lt;/code&gt; is an imaginary function that would be used inside our &lt;code&gt;Form&lt;/code&gt; component. By calling &lt;code&gt;jest.mock('./handle-submit')&lt;/code&gt;, we are replacing the actual implementation with a mock function. Without a mock, we &lt;em&gt;cannot&lt;/em&gt; test in this way.&lt;/p&gt;

&lt;p&gt;We think it's important to create components in a &lt;strong&gt;composable&lt;/strong&gt; way; making sure that responsibility is split into child components and not aggregated in one place. You should not need to render an entire page just to test one form control.&lt;/p&gt;




&lt;h3&gt;
  
  
  End-to-End?
&lt;/h3&gt;

&lt;p&gt;E2E testing involves testing that entire pages function correctly when interacted with. This kind of testing is outside the realms of React Testing Library, and here at Artlist we have Automation Engineers working on adding the E2E framework &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; to our infrastructure.&lt;/p&gt;

&lt;p&gt;Whilst E2E testing inherently provides the most overall coverage (and therefore code confidence), it is still important to test your application at various different levels. Kent C. Dodds has an &lt;a href="https://kentcdodds.com/blog/static-vs-unit-vs-integration-vs-e2e-tests" rel="noopener noreferrer"&gt;Excellent Article&lt;/a&gt; explaining how utilising a variety of test types can be beneficial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcaqbcrs0nboyk6pbfhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcaqbcrs0nboyk6pbfhg.png" alt="Crude diagram showing test types"&gt;&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  Dynamic Content
&lt;/h2&gt;

&lt;p&gt;Components using animations or transitions can be difficult to test due to the the varying DOM output at any given time. For example, you may have an animated notification that appears after a delay, or an exit animation that doesn't remove an element from the DOM until that animation has finished.&lt;/p&gt;

&lt;p&gt;Take Artlist's download button for example:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiscjiov2p4bwl91cvb1d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiscjiov2p4bwl91cvb1d.gif" alt="artlist download animation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;React Testing Library has various async utilities for this exact purpose, such as &lt;code&gt;waitFor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitFor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clicking download shows success notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download song&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Download Successful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;waitFor&lt;/code&gt; tells jest to wait until the assertions inside the callback are fulfilled, or the test timeout has been reached.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not use Enzyme?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://enzymejs.github.io/enzyme/" rel="noopener noreferrer"&gt;Enzyme&lt;/a&gt; is another popular React testing utility - but it operates in a fundamentally different way. With Enzyme, we are given the ability to test the internals of our components, such as &lt;strong&gt;props&lt;/strong&gt;, &lt;strong&gt;state&lt;/strong&gt;, and &lt;strong&gt;lifecycle&lt;/strong&gt; methods.&lt;/p&gt;

&lt;p&gt;React Testing Library on the other hand, gives no access to implementation details; instead providing ways to render components and interact with them. This means a steeper learning-curve, but tests that are closer to real-world user interactions.&lt;/p&gt;




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

&lt;p&gt;Here at Artlist we are always on the lookout for ways to make our developer's lives easier; and utilising a tried-and-true React testing framework to provide code confidence is one of the ways we are working towards that goal.&lt;/p&gt;

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

&lt;p&gt;Check out the official &lt;a href="https://testing-library.com/docs/" rel="noopener noreferrer"&gt;React Testing Library docs&lt;/a&gt; for in depth explanations, a full API reference, and more advanced topics such as configuration options.&lt;/p&gt;

&lt;p&gt;Happy testing!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>testing</category>
    </item>
    <item>
      <title>How to migrate data when refactoring a monolith to microservices (with zero downtime)</title>
      <dc:creator>Meitar Bruner</dc:creator>
      <pubDate>Mon, 12 Sep 2022 11:14:41 +0000</pubDate>
      <link>https://forem.com/artlist/how-to-migrate-data-when-refactoring-a-monolith-to-microservices-with-zero-downtime-5d31</link>
      <guid>https://forem.com/artlist/how-to-migrate-data-when-refactoring-a-monolith-to-microservices-with-zero-downtime-5d31</guid>
      <description>&lt;p&gt;Microservices architecture is a well-known subject that is covered a lot in the tech industry.&lt;/p&gt;

&lt;p&gt;Every person who ever tried breaking a monolith, probably found there aren't a lot of articles that speak about one of the greatest pains when moving from monolith to microservices - Data migrations.&lt;/p&gt;

&lt;p&gt;After making some mistakes with migrations, and the fact that we decided to do it with zero downtime, I'll be glad to share some strategies, tools, and best practices we've learned for this kind of migration. Let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what are data migrations?
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://en.wikipedia.org/wiki/Data_migration" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;: "&lt;em&gt;Data migration is the process of selecting, preparing, extracting, and transforming data and permanently transferring it from one computer storage system to another.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's understand what are the risks when migrating data:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Having corrupted data (data that violates your new service expectations).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Losing data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating data duplications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The new data can contain mistakes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The risks when switching services with the aim of zero downtime:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Data might return differently than expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The new service might not handle the requested rates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New data that was created and updated won't be available anymore in the old system.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, we will cover the practices to lower these risks. It is important to mention that you can probably migrate your data without all the practices I am going to cover, but keep in mind that having all of them will lower your risk to a minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  A. Preparations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Understand the data you are going to transfer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Learning the old entities you are going to transfer, the relationships between them, and how they will be represented in the new database is the first step. Also, it's important to understand the size (# of records &amp;amp; bytes) you are going to transfer.&lt;/p&gt;

&lt;p&gt;Knowing the nature of the data will save you a lot of time when creating your new API and your migration process.&lt;/p&gt;

&lt;p&gt;In case you designed or built your new API, you probably already did it.&lt;/p&gt;

&lt;p&gt;If not, pay attention to the relations and the properties of the objects: do you need all the properties/columns? Will one object from the old database be represented by 2 objects in your new database? Or vice versa, a joined object from 2 SQL tables can become one object in the new database.&lt;/p&gt;

&lt;p&gt;There are many possibilities so I won't try to cover them all. The main point is, that you should understand the data you are about to transfer and its scale, so you'll be able to create your new service properly and understand how each piece of data will be mapped to your new database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm9v5441wpx5silgh9t6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm9v5441wpx5silgh9t6.png" alt="monolithic architecture" width="596" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the new API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An important approach I'd recommend embracing is to never access your new database without the API layer.&lt;/p&gt;

&lt;p&gt;This is very important because accessing the database without the server layer and then using the same data from it can cause some mutations and problems you want to avoid (risk #1).&lt;/p&gt;

&lt;p&gt;For example, your new API may have a restriction for some string property to be camelCase only, which the old system did not enforce. In that case, if you create a migration without going through the API layer, the new database will be corrupted with data that violates your new API rules.&lt;/p&gt;

&lt;p&gt;In that case, unless you don't have your API ready, it is better to not insert new data into it.&lt;/p&gt;

&lt;p&gt;Create your API first, better covered with DTO validations and integration/e2e tests, then move to the migration phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmwbbgzqki0p5u0jg2ey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmwbbgzqki0p5u0jg2ey.png" alt="New microservice" width="668" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try to be incremental as possible (1 feature at a time).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As we mentioned, the migration process comes with a risk, so it is very important to try and do it in small steps rather than migrating everything at once.&lt;/p&gt;

&lt;p&gt;Think if you can migrate only some part of the data from the old database to the new one, so at least some endpoints from the old monolith could be replaced at the beginning.&lt;/p&gt;

&lt;p&gt;You can lower the risk even more by starting with the feature which has the least traffic.&lt;/p&gt;

&lt;p&gt;In Artlist for example, we had a case when we needed to migrate all data related to the user's favorite songs, artists, and albums, including folders users created for themselves.&lt;/p&gt;

&lt;p&gt;In that case, we decided to do it step-by-step for each object and chose to start with &lt;em&gt;favorite artists&lt;/em&gt; because this feature has the smallest traffic and was less used by users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider using third-party tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this point, you already know the old structure and the new expected structure of your data. Plus, you already created the API which is connected to the new database, and decided about what objects you would like to migrate first to your new database.&lt;/p&gt;

&lt;p&gt;Before you dive into implementing big guns (developing huge code implementations), this phase is a reminder that for all the next phases, it is always good to search for some known, tested third-party tools, packages, or services that already implemented what you are about to do.&lt;/p&gt;

&lt;p&gt;Because every problem is different and many people have different stacks and cloud services, I'm avoiding pointing to a specific solution. Just remember a lot of these problems are already solved by many developers, and they might have thought about issues we haven't.&lt;/p&gt;

&lt;h2&gt;
  
  
  B. Get to work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Synced create/update/delete operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrkgcpy5hkfo8xzma6wb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrkgcpy5hkfo8xzma6wb.png" alt="Sync strategy" width="800" height="937"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we are starting for real. The first thing we would like to do is start writing to the new database and have it always synced with the old one. Remember we are aiming for zero downtime, so at the moment we will switch to the new service and database, it should have all the old data migrated to it.&lt;/p&gt;

&lt;p&gt;The best way to do it is to call the new API from the old system whenever a mutation operation is happening (create, update or delete). As we mentioned above, we would like to go through the API and not update the database directly, to prevent corrupted data from being transferred.&lt;/p&gt;

&lt;p&gt;Another important thing is from where in the old system you are calling to your new API. Having the calls as close as you can to the old database (like shown in the graphs above), will lower the risk of losing the call due to some error.&lt;/p&gt;

&lt;p&gt;Keep in mind that now your new API will start handling requests, so it is better to verify that it can handle the rates of the old service and database.&lt;/p&gt;

&lt;p&gt;Another great layer you can add, that will lower the risk even more, is sending the operation to some queueing component with an acknowledgment mechanism. That way you will never lose the call (risk #2) unless the new API processes it, plus you will be able to throttle calls if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a job for history data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good, we now have our new database populated with every new data piece inserted by real users! Also, updates and deletes are synced with the old database.&lt;/p&gt;

&lt;p&gt;Now it's time to migrate all the data that was inserted before the operations sync.&lt;/p&gt;

&lt;p&gt;We will need a job that can fetch data from the old database and then create an API call to insert it into the new service. Once you have this component, you can start and run it to migrate all the past data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;*It is much safer to read from a replica and not directly from the production database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Depending on the size of your data, running the job can be time-consuming and take minutes, hours, or even days in extreme cases. Also, if you already implemented the queuing mechanism, it might be worth using it with this job as well, just create new operations to the queue you already have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0jews8gr36vpsoo7z1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0jews8gr36vpsoo7z1a.png" alt="History data migration process" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a mechanism to prevent duplications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is also important to create a mechanism that prevents inserting duplications in your new service.&lt;/p&gt;

&lt;p&gt;Let's say that your migration setup is broken, and your job creates 2 POST calls to the new API, for every 1 object from the old database (risk 3#).&lt;/p&gt;

&lt;p&gt;A way to prevent the new API from creating duplicates is by checking if the unique identifier of the object is already located in the database, and then only if it isn't, insert it.&lt;/p&gt;

&lt;p&gt;If this object should be able to have duplication (1 to many, many to many), you can save a property with the old ID of the object in the new database object. Doing it will let you do a quick check if this object already exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  C. Some more protection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Verification tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You now have your new database populated and synced with all past and present data.&lt;/p&gt;

&lt;p&gt;Before you do the final switch from the old monolith to your new service, consider doing some verification tests. Verification tests can be created in many ways, but there are 2 that can be very useful:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Tests that validate that the databases are synced (prevent risks #1-4).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test that validates that the end user will get the same data when you switch to the new service.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first ones can be checked by a job, which will go over all data objects from the old database, and check if the same data exists and returns as expected from the new service.&lt;/p&gt;

&lt;p&gt;The second one can be validated statistically with real traffic. For every GET call that fetches data from the old monolith, you can make a call to the new service, and then before/after returning the data to the consumer, send the received objects to some component that will compare them and store if the result is expected or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a backup mechanism&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a developer, you know anything might happen. That's why it is worth considering a backup mechanism.&lt;/p&gt;

&lt;p&gt;What it means is that everything we just did from the old monolith to the new API, we can also create in the opposite direction (sync calls from the new database to the old system, history sync, etc.).&lt;/p&gt;

&lt;p&gt;That way, in case the worst happens, we will have a mechanism to roll back to the old known system.&lt;/p&gt;

&lt;h2&gt;
  
  
  D. Switch and monitor
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Connect the consumer to the new service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are one step before the final switch, if you did the comparison tests, you already have the GET calls in your consumer. If not, this is the time to implement these calls to the new service along with switching the mutation calls (create/update/delete) to use the new service.&lt;/p&gt;

&lt;p&gt;A thing worth considering in this phase, is a gradual rollout mechanism, to test what will happen to users if they'll be switched to the new service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollout 100% &amp;amp; monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it, we can now switch all calls to the new service. In case you have some feature flags related to a rollout, you can now get rid of them.&lt;/p&gt;

&lt;p&gt;A new service with maybe new technology, can raise some problems we might not even think about, that's why it is important to keep and monitor the new service and the consumers using it even more during that phase.&lt;/p&gt;

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

&lt;p&gt;We covered the risks, and the practices meant to lower the risks, of migrating from one service to a new service and database, while doing it with zero downtime.&lt;/p&gt;

&lt;p&gt;Migrations can be scary, but using the practices mentioned in this article, will lower the potential risks and issues.&lt;/p&gt;

&lt;p&gt;Even though I learned all these practices, there is a disclaimer that we didn't use all of them. Implementing them all comes with the cost of resources like time, which sometimes you do not have.&lt;/p&gt;

&lt;p&gt;In that case, you should analyze your situation, and think which practices you can afford, and which would be less valuable.&lt;/p&gt;

</description>
      <category>data</category>
      <category>migration</category>
      <category>microservices</category>
      <category>database</category>
    </item>
    <item>
      <title>Rings and Onion in Your Next Node Application</title>
      <dc:creator>Raphael Ben Hamo</dc:creator>
      <pubDate>Sun, 07 Aug 2022 13:40:36 +0000</pubDate>
      <link>https://forem.com/artlist/rings-and-onion-in-your-next-node-application-5aga</link>
      <guid>https://forem.com/artlist/rings-and-onion-in-your-next-node-application-5aga</guid>
      <description>&lt;p&gt;Sometimes we need to create a new service or refactor the existing one. Most of the time, we spend a couple of hours initializing the application - Node application setup, creating files, folders, etc. The way we structure the folders’ skeleton can be something similar to other services in our repos or based on a service we have done in the past.&lt;/p&gt;

&lt;p&gt;This moment is highly important! If we stop and take the time to plan before starting this process, it will be worth it in the long run.&lt;/p&gt;

&lt;p&gt;In this article, we will do it by creating the folders’ skeleton using the Clean Architecture approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why software architecture is so important&lt;/li&gt;
&lt;li&gt;About Clean Architecture&lt;/li&gt;
&lt;li&gt;Node API Service with Clean Architecture&lt;/li&gt;
&lt;li&gt;Folders skeleton&lt;/li&gt;
&lt;li&gt;Summary &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Software Architecture Is so Important
&lt;/h2&gt;

&lt;p&gt;In a large project, we want it to be easy to maintain, stable (not quick &amp;amp; dirty), and open to new features as quickly as possible.&lt;/p&gt;

&lt;p&gt;To achieve that, we need to separate our files and folders into components with different responsibilities that can change independently without affecting other components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Architecture
&lt;/h2&gt;

&lt;p&gt;In short, Clean Architecture is a system architecture guideline proposed by Robert C. Martin (Uncle Bob). &lt;br&gt;
You can read about it &lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://betterprogramming.pub/the-clean-architecture-beginners-guide-e4b7058c1165" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://www.techtarget.com/whatis/definition/clean-architecture" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main concept can be shown by Robert Martin’s illustration:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dycqznshrbupyi0tyb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dycqznshrbupyi0tyb9.png" alt="Image by Robert C. Martin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each circle represents a different area in our software. According to the dependency rule, the outer layer can depend on inner layers but not on any layer outer from it, which means that as a general rule, the deeper the layer, the less it is prone to changes.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt3tz5kimxt2q4d23n4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpt3tz5kimxt2q4d23n4o.png" alt="Image by Robert C. Martin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this is a little bit abstract, let us demonstrate what it looks like in Node API Service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node API Service with Clean Architecture
&lt;/h2&gt;

&lt;p&gt;We will start creating a Node service that has an API and Database. &lt;br&gt;
In Clean Architecture, we will have these layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Domain layer&lt;/strong&gt; - the abstract layer, responsible for all of our business data, our business functions and entities, but abstracted - interfaces and abstract classes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application layer&lt;/strong&gt; - the logic layer, each file here is responsible for a flow or use case in our project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adapter layer&lt;/strong&gt; - the API presentation layer, which includes routes, controllers, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure layer&lt;/strong&gt; - database configurations, implementations of the entities, providers, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyod4lwhiumvysyuh49h2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyod4lwhiumvysyuh49h2.png" alt="Image from TechTarget"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Folders skeleton
&lt;/h2&gt;

&lt;p&gt;The layers break down into these folders: domain, use cases, API, and infrastructure.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13y4vqet09hfl77i8p09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13y4vqet09hfl77i8p09.png" alt="Screenshot by author"&gt;&lt;/a&gt;&lt;br&gt;
In the Artlist world, it can be implemented as a service for managing artists and songs.&lt;/p&gt;

&lt;p&gt;First, let us see what it looks like for each folder:&lt;br&gt;
&lt;strong&gt;domain&lt;/strong&gt; - contains files such as enums, interfaces, models, etc.&lt;br&gt;
In Artlist, it would be the place for artist and song properties and abstract classes of the logic for each flow.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndd6j5j0savfkovt9q5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndd6j5j0savfkovt9q5g.png" alt="Screenshot by author"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;usecases&lt;/strong&gt; -  contains files of the project logic, flow, and use cases.&lt;br&gt;
Each file can present a separate flow, like "download song use-case" or all song use-cases.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1dc9gx6w15mue8tjiss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1dc9gx6w15mue8tjiss.png" alt="Screenshot by author"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;api&lt;/strong&gt; - contains files such as controllers, interceptors, guards, etc.&lt;br&gt;
For Artlist, this is the API for the client side to get the artist and song data. &lt;br&gt;
Here we can call from the controller 'a to use case 'a.x' and use case 'a.y'. If we do so, we make a dependency from the &lt;strong&gt;adapter layer&lt;/strong&gt; to the &lt;strong&gt;application layer&lt;/strong&gt;. That is ok because the dependency is from the outer layer to a deeper layer.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9taa277wgf3eajvigicw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9taa277wgf3eajvigicw.png" alt="Screenshot by author"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;infrastructure&lt;/strong&gt; - contains database configurations - table entity implementations, external providers, DAL repositories, etc.&lt;br&gt;
Here we can implement the abstract classes declared in the domain layer - also, the database entities and ORM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg382bcd4cqofsxuw5pc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg382bcd4cqofsxuw5pc.png" alt="Screenshot by author"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have a basic folder structure for Node API service using Clean Architecture.&lt;/p&gt;

&lt;p&gt;You can save it as a template repo and start other services from it.&lt;/p&gt;

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

&lt;p&gt;In this article, we learned the basics of Clean Architecture. First, we translated it to the Node world, then demonstrated how to build a project skeleton for Node API service using Clean Architecture and finally showed how it can be implemented in the Artlist world.&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>node</category>
      <category>architecture</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ML Configuration Management</title>
      <dc:creator>Sephi Berry</dc:creator>
      <pubDate>Thu, 28 Jul 2022 10:40:00 +0000</pubDate>
      <link>https://forem.com/artlist/ml-configuration-management-4hde</link>
      <guid>https://forem.com/artlist/ml-configuration-management-4hde</guid>
      <description>&lt;p&gt;This post follows our blog describing our &lt;a href="https://dev.to/artlist/lessons-learned-on-the-road-to-mlops-22lj"&gt;ML Ops manifest&lt;/a&gt;. In this post we will dive into the our configuration management within our ML projects    &lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Working in a real-world business environment, requires moving codes between research/development, test and production environments, which are crucial for development velocity. While doing so, it is important to allow for a common language &amp;amp; standards between various AI &amp;amp; Development teams, for frictionless deployment of codes. Additionally configuration management assist in:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ML work with many parameters, &lt;/li&gt;
&lt;li&gt;hyper params etc. , &lt;/li&gt;
&lt;li&gt;we want to separate config from code (12 factor app)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These days, it comes without a surprise that there are several OpenSource (OS) configuration frameworks that can be utilized for this. After reviewing several options (including &lt;a href="https://hydra.cc/" rel="noopener noreferrer"&gt;Hydra&lt;/a&gt;), we decided on &lt;a href="https://www.dynaconf.com/" rel="noopener noreferrer"&gt;dynaconf&lt;/a&gt;, since it fulfilled our requirements of being:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python based&lt;/li&gt;
&lt;li&gt;Simple&lt;/li&gt;
&lt;li&gt;Easily configurable and extendable&lt;/li&gt;
&lt;li&gt;Allow for overriding and cascading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbljmsryuxo7o4vyp6q2k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbljmsryuxo7o4vyp6q2k.jpeg" alt="Moving from Dev to Prod"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Environment
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.artlsit.io" rel="noopener noreferrer"&gt;Artlist&lt;/a&gt; runs in multiple cloud environments, however currently most of the ML workloads run on GCP. Following GCP best practices, we have set up different projects for each environment, thus allowing for strict isolation between them, in addition to enabling billing segmentation. This separation needs to be easily propagated into the configuration, for seamless code execution.&lt;/p&gt;

&lt;p&gt;In this post we review our configuration in relation to&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Basic implementation&lt;/li&gt;
&lt;li&gt;
Advance Templating
&lt;/li&gt;
&lt;li&gt;Simple Overriding&lt;/li&gt;
&lt;li&gt;
&lt;a href="//#project-vs.-module-settings"&gt;Project vs. Module settings&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Updating Configurations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let’s see how &lt;code&gt;dynaconf&lt;/code&gt; can help out with this.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Implementation
&lt;/h3&gt;

&lt;p&gt;We decided to work with configuration settings that are stored in external &lt;code&gt;toml&lt;/code&gt; files, which are easily readable and are becoming one of the &lt;a href="https://peps.python.org/pep-0680/" rel="noopener noreferrer"&gt;de-facto standards&lt;/a&gt; in python.  &lt;/p&gt;

&lt;p&gt;A code snippet from our basic configuration file is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[default]&lt;/span&gt;
&lt;span class="py"&gt;PROJECT_ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@format artlist-{this.current_env}"&lt;/span&gt;
&lt;span class="py"&gt;BASE_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“my_feature_name”&lt;/span&gt;
&lt;span class="py"&gt;BASE_PIPELINE_NAME_GCP&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@jinja {this.BASE_NAME | replace('_', '-')}"&lt;/span&gt;
&lt;span class="py"&gt;BUCKET_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"@format {this.BASE_NAME}--{this.current_env}"&lt;/span&gt;

&lt;span class="nn"&gt;[dev]&lt;/span&gt;
&lt;span class="py"&gt;SERVICE_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service-account1@artlist-dev.iam.gserviceaccount.com"&lt;/span&gt;

&lt;span class="nn"&gt;[tst]&lt;/span&gt;
&lt;span class="py"&gt;SERVICE_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service-account2@artlist-tst.iam.gserviceaccount.com"&lt;/span&gt;

&lt;span class="nn"&gt;[prd]&lt;/span&gt;
&lt;span class="py"&gt;SERVICE_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"service-account3@artlist-prd.iam.gserviceaccount.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's break it down.&lt;br&gt;&lt;br&gt;
Whenever &lt;em&gt;dynaconf&lt;/em&gt; runs - it runs in a specific environment. The default environment is called DEVELOPMENT. However, since we wanted to move easily between the environments (and GCP resources), we changed the naming convention of the environments (to a  3 letter acronym = dev, tst, prd), so we can readily reference the relevant GCP project while specifying the environment.&lt;br&gt;&lt;br&gt;
Using the &lt;a href="https://www.dynaconf.com/configuration/#env_switcher" rel="noopener noreferrer"&gt;&lt;code&gt;env_swithcher&lt;/code&gt;&lt;/a&gt;, we can indicate to &lt;em&gt;dynaconf&lt;/em&gt; which configuration to load and what GCP project to access with the following line: &lt;br&gt;
 &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PROJECT_ID = "@format artlist-{this.current_env}"&lt;br&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;p&gt;Using the &lt;code&gt;@format&lt;/code&gt; as a prefix to the string, we can parse the parameter that is within the curly brackets. For example, if the current environment is set to ‘dev’ the PROJECT_ID variable will be &lt;code&gt;artlist-dev&lt;/code&gt;, thus accessing only the resources from the &lt;code&gt;dev&lt;/code&gt; project,  whereas if the environment is set to ‘prd’ the PROJECT_ID will be &lt;code&gt;artlist-prd&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Accessing the rest of the relevant variables is based on the &lt;a href="https://github.com/toml-lang/toml" rel="noopener noreferrer"&gt;various sections in the toml&lt;/a&gt; file.&lt;br&gt;&lt;br&gt;
For example, referencing the &lt;em&gt;Production Service Account&lt;/em&gt; (SA) will be by accessing the &lt;code&gt;SERVICE_ACCOUNT&lt;/code&gt; variable which is under the [prd] section&lt;/p&gt;
&lt;h3&gt;
  
  
  Advance Templating
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Dynaconf&lt;/em&gt; includes the ability to work with &lt;a href="https://jinja.palletsprojects.com/" rel="noopener noreferrer"&gt;&lt;code&gt;Jinja&lt;/code&gt; templating&lt;/a&gt; - this can be useful for manipulating strings. GCP has a quark that requires naming containers within the GCP registry so as not to have ‘_’ (underscore) as separators, but rather ‘-’ (hyphen). And since we wanted to sync our registry and the artifacts coming out of &lt;a href="https://cloud.google.com/vertex-ai/docs/pipelines/introduction" rel="noopener noreferrer"&gt;&lt;em&gt;Vertex AI pipelines&lt;/em&gt;&lt;/a&gt; (that are stored within buckets / Cloud Storage), we were able to keep the python naming convention of ‘_’ , while converting the strings to the GCP convention when required.&lt;br&gt;&lt;br&gt;
Using the &lt;em&gt;jinja&lt;/em&gt;’s &lt;a href="https://jinja.palletsprojects.com/en/3.0.x/templates/?highlight=replace#jinja-filters.replace" rel="noopener noreferrer"&gt;text replace&lt;/a&gt; method we can easily alter the text as necessary:&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;BASE_PIPELINE_NAME_GCP = "@jinja {this.BASE_NAME | replace('_', '-')}"&lt;br&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Simple Overriding
&lt;/h3&gt;

&lt;p&gt;Another useful feature of &lt;code&gt;dynaconf&lt;/code&gt; is that you can easily override the configuration using local settings. This is very convenient since local settings for development doesn’t need to be checked into source control, while the general settings should be synced to the entire team.&lt;br&gt;&lt;br&gt;
All that is required to differentiate between the settings is to add the &lt;code&gt;.local&lt;/code&gt; &lt;a href="https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffix" rel="noopener noreferrer"&gt;suffix&lt;/a&gt; to the file name, see example below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;General Settings - settings.toml
&lt;/li&gt;
&lt;li&gt;Local Settings - settings.local.toml
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever &lt;code&gt;dynaconf&lt;/code&gt; identifies the suffix &lt;code&gt;.local.toml&lt;/code&gt; it will overwrite the variables configuration that exists in the &lt;code&gt;settings.toml&lt;/code&gt; with the loaded &lt;code&gt;settings.local.toml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps21dh0nqxqdkgsqeyfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps21dh0nqxqdkgsqeyfi.png" alt="overriding example"&gt;&lt;/a&gt;&lt;br&gt;
An example for overwriting with local credentials&lt;/p&gt;
&lt;h3&gt;
  
  
  Project vs. Module settings
&lt;/h3&gt;

&lt;p&gt;Our ML framework is &lt;a href="https://www.kubeflow.org/" rel="noopener noreferrer"&gt;KubeFlow&lt;/a&gt; (hosted by &lt;em&gt;GCP VertexAI pipelines&lt;/em&gt;), which requires various configurations: some at the component level (which are reused independently in various pipelines), while others are at the pipeline/project/cross-component level. To load both settings, we can use another feature of &lt;code&gt;dynaconf&lt;/code&gt; which can define a specific file name template that will automatically be loaded by &lt;code&gt;dynaconf&lt;/code&gt;. Here is our implementation practice:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any configurations that are at the project level will be written in the project settings - &lt;code&gt;settings_prj.toml&lt;/code&gt; (see &lt;a href="https://www.dynaconf.com/configuration/#settings_file-or-settings_files" rel="noopener noreferrer"&gt;dynaconf settings_files&lt;/a&gt; configuration)&lt;/li&gt;
&lt;li&gt;Any configurations that are at the component level will be written in the component settings - &lt;code&gt;settings.toml&lt;/code&gt; .&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Updating Configurations
&lt;/h3&gt;

&lt;p&gt;Sometimes there is a need to update the configuration during runtime, this can be challenging since the entire configuration is loaded immediately when the library is called. To do so, we can use a decorator to update the configuration. Assuming the &lt;code&gt;cfg&lt;/code&gt; is the configuration settings, we can write the following decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@input_to_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;input_to_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_override&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;[decorator] override config parameters with function inputs
   Args:
       config: Dynaconf configuration / settings to be updated
       wrapped_func ([function]): [the function to capture it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s input and push to the config]
       sequence_override: configures the option for overriding keys or merging the values
   &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
       &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
           &lt;span class="nf"&gt;_override_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sequence_override&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sequence_override&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
           &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;wrapped_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

       &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this blog post, we have laid out our configuration implementation using &lt;code&gt;dynacof&lt;/code&gt; library. We saw how we&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Used the basic setup of &lt;code&gt;dynaconf&lt;/code&gt; configuration&lt;/li&gt;
&lt;li&gt;Synced our GCP project with &lt;code&gt;dynaconf&lt;/code&gt; environments&lt;/li&gt;
&lt;li&gt;Worked with the advanced &lt;code&gt;dynaconf&lt;/code&gt; settings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In our next posts, we will extend our description of the various elements that have been incorporated into our ML project workflow, while developing our internal base library, which include standardization of:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Accessing our secrets (using &lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;GCP Secret Manager&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Conduct our experiment tracking (using &lt;a href="https://clear.ml/" rel="noopener noreferrer"&gt;ClearML&lt;/a&gt; )&lt;/li&gt;
&lt;/ul&gt;

&lt;h6&gt;
  
  
  # Image copyright
&lt;/h6&gt;

&lt;p&gt;The banner image was co-created using &lt;a href="https://openai.com/dall-e-2/" rel="noopener noreferrer"&gt;DALLE2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mlops</category>
      <category>dynaconf</category>
      <category>configuration</category>
      <category>ai</category>
    </item>
    <item>
      <title>Lessons learned on the road to MLOps</title>
      <dc:creator>Amit Bendor </dc:creator>
      <pubDate>Thu, 07 Jul 2022 09:49:45 +0000</pubDate>
      <link>https://forem.com/artlist/lessons-learned-on-the-road-to-mlops-22lj</link>
      <guid>https://forem.com/artlist/lessons-learned-on-the-road-to-mlops-22lj</guid>
      <description>&lt;h2&gt;
  
  
  The start
&lt;/h2&gt;

&lt;p&gt;Hello and welcome to our series of posts on MLOps at Artlist!&lt;/p&gt;

&lt;p&gt;The goal of this series is to share our MLOps journey (which is still undergoing) and how we applied our vision and perspective to a &lt;strong&gt;real-world production infrastructure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the very early days of our department, when I (Amit), just joined the company and was the first Data science employee, I had the rare opportunity to stop and think with myself about how to "build things right" this time. &lt;/p&gt;

&lt;p&gt;After managing a few data science teams and projects, and seeing failures and success stories I felt like I had a quite clear vision of the “values” which should guide us as we build our infrastructure and practices. &lt;/p&gt;

&lt;p&gt;It took me about 2 days of just distilling and spilling out into a notion page all of my thoughts and we were ready to go.&lt;/p&gt;

&lt;p&gt;At that time the buzzword “MLOps” was rising in popularity and we felt like it has many similar notions to ours - although some of them are vague in terms of implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core values
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A battle of ultimate goals
&lt;/h3&gt;

&lt;p&gt;Before starting with values we need to understand our main goals. they will be our "guiding star" whenever we have a decision to make.&lt;/p&gt;

&lt;p&gt;If I had to choose 1 superior goal it would be “&lt;strong&gt;business impact&lt;/strong&gt;”. Quite a high level but eventually we want to bring value to our users and to impact the company’s bottom line.&lt;/p&gt;

&lt;p&gt;Also, we can measure it for every decision we're taking - for example what features to focus on next? which implementation to choose for our pipeline tool? we can answer all of those with an estimation of the business impact.&lt;/p&gt;

&lt;p&gt;In order to get to this goal - we can say we’d like to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Independent&lt;/strong&gt; - can bring value without relying on the development teams or data engineers (ideally)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus on DS&lt;/strong&gt; - in order to bring our unique value to the company, we need to do mostly data science/research work and not general engineering tasks.   &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can figure out these two goals are conflicting with each other - and this is exactly the fine line each team needs to define. We defined our own guidelines as you’ll see below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iq1luiaubcd4mk9q798.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iq1luiaubcd4mk9q798.jpg" alt="Complex"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The modern Data Science team
&lt;/h3&gt;

&lt;p&gt;One of our first decisions was to build the team the &lt;strong&gt;“modern way”&lt;/strong&gt;. And by that I mean we decided we’re not going for the traditional way algorithm teams used to provide some dirty research code - and letting a software engineer decipher and convert it into a running production code. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz608p6bu5hewpni4dei2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz608p6bu5hewpni4dei2.png" alt="Team Goals"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We decide to rather strive towards a “&lt;a href="https://towardsdatascience.com/fcds-b2d2e6b08d34" rel="noopener noreferrer"&gt;Full Cycle Data Science&lt;/a&gt;” paradigm, where we take ownership over every activity related to our core data science activity.&lt;/p&gt;

&lt;p&gt;What does it means exactly? What are the bounds?&lt;/p&gt;

&lt;p&gt;We call it the “&lt;strong&gt;Up to the API&lt;/strong&gt;” approach- all activities from research, to automation using pipelines and holding APIs to externalize our predictions.&lt;/p&gt;

&lt;p&gt;What not?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any operational backend/frontend work&lt;/li&gt;
&lt;li&gt;Data engineering ETLs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Turning “values” into infrastructure
&lt;/h2&gt;

&lt;p&gt;Ok, so now let's move on to the most important subject - values.&lt;br&gt;
You might be asking - so the rest of the articles just going to be a bunch of clichés? I promise not. let’s see how we took every “value” and created &lt;strong&gt;actionable decisions&lt;/strong&gt; &amp;amp; &lt;strong&gt;guidelines&lt;/strong&gt; we use &lt;strong&gt;every day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ry5envzt94d37g8zf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3ry5envzt94d37g8zf5.png" alt="Core values"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Enabling creativity
&lt;/h3&gt;
&lt;h4&gt;
  
  
  What does it means?
&lt;/h4&gt;

&lt;p&gt;Inspired by our company, we believe that researchers should “&lt;strong&gt;push the boundaries&lt;/strong&gt;” -  and that our MLOps infrastructure should &lt;strong&gt;enable&lt;/strong&gt; it.&lt;/p&gt;

&lt;p&gt;We truly believe we should enable the &lt;strong&gt;Maximum&lt;/strong&gt; flexibility in the research side of our work - because this is our &lt;strong&gt;core&lt;/strong&gt; &lt;strong&gt;value&lt;/strong&gt; in the company.&lt;/p&gt;
&lt;h4&gt;
  
  
  Decisions
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Research with notebooks/scripts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 As long as it supports fast iterations - use Jupyter notebooks, scripts, or a combination of both&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CYOF (Choose your own framework)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 Prefer Tensorflow over PyTorch - not a problem. Use the framework/tools that make the most sense to you&lt;/p&gt;


&lt;h3&gt;
  
  
  Simplicity
&lt;/h3&gt;
&lt;h4&gt;
  
  
  What does it means?
&lt;/h4&gt;

&lt;p&gt;We already talking about conflicts so here is another one.&lt;/p&gt;

&lt;p&gt;We don’t want to activate our human decision-making process for operations that are outside of our core contribution.&lt;/p&gt;

&lt;p&gt;Therefore, we decided to give &lt;strong&gt;minimal&lt;/strong&gt; flexibility on the “engineering side” of our work.&lt;/p&gt;
&lt;h4&gt;
  
  
  Decisions
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;One implementation &lt;sup&gt;TM &lt;/sup&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 minimal set of tools and just one implementation for 3 variants of our main components: pipeline, API, Python package&lt;/p&gt;

&lt;p&gt;Example: for API use only FastAPI &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write once &lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  How is it being reflected?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Write code templates (cookiecutters in Python) - to provide a fully functional environment - full of nifty automations&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use it when starting every project&lt;/li&gt;
&lt;li&gt;Look below the links section for a few examples&lt;/li&gt;
&lt;li&gt;Deep dive post is coming up soon&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Standard operations - a library we developed covering “must-haves” of any script or notebook. (production or research)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish a good standard&lt;/li&gt;
&lt;li&gt;Includes: Logging, configuration, and tracking modules&lt;/li&gt;
&lt;li&gt;Deep dive post is coming up soon&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Everything is containerized&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A standard way to deploy our code in different services without changing anything (!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5b75soryjgficxcm1xd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5b75soryjgficxcm1xd.png" alt="Standard ops"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Transparency (of tools &amp;amp; infra)
&lt;/h3&gt;
&lt;h4&gt;
  
  
  What does it means?
&lt;/h4&gt;

&lt;p&gt;This one is connected to the previous value. (simplicity)&lt;/p&gt;

&lt;p&gt;We want things to “just work” without touching them when possible.&lt;/p&gt;

&lt;p&gt;We rather sacrifice flexibility and cost &lt;/p&gt;
&lt;h4&gt;
  
  
  Decisions
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;No DevOps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 We’ll prefer serverless, self-managed, cloud-native solutions. Even in the cost of flexibility or cost of operation&lt;/p&gt;

&lt;p&gt;👉 For example we chose vertex pipelines as our main pipelines tool and GCP native monitoring for those features.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fewest interactions - we chose libraries and implementations that don’t require the least lines of code to run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;from aistdops.logging import logger logger.info()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;over&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;logger = Logger(), logger.info()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  How is it being reflected?
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Cloud - prefer managed services, serverless&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choice of transparent libraries - for example, ClearML for experiment tracking as it logs most things *&lt;em&gt;implicitly *&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qajuaddu7g399cws0l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qajuaddu7g399cws0l.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjsh6szo557g68bbelbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjsh6szo557g68bbelbp.png" alt="Transparent implementation"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Robustness + Well engineered
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What does it means?
&lt;/h4&gt;

&lt;p&gt;So frankly, we really don’t like waking up in the middle of the night from a pager duty call.&lt;/p&gt;

&lt;p&gt;Therefore, we’re doing all we can to provide testable, robust solutions&lt;/p&gt;

&lt;h4&gt;
  
  
  Decisions
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Invest in testing code, data validation, and model monitoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose production-ready tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Track everything we can&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To build a lineage map, reproducibility&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Reduce production risks as possible&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prefer batch jobs over online APIs&lt;/li&gt;
&lt;li&gt;Always have a fallback&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h4&gt;
  
  
  How is it being reflected?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Data validation - with great expectations&lt;/li&gt;
&lt;li&gt;Tracking experiments with ClearML&lt;/li&gt;
&lt;li&gt;Embrace best practices in core components - configuration management, logging, dataset and model management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;In the next articles in this series, we'll go deeper into our core implementations of those values into our infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  About the writer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0axvjvefm4exu5z9lrm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0axvjvefm4exu5z9lrm5.png" alt="Amit Bendor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amit is the head of data science at Artlist. He’s an active contributor to the Association of software architecture and Cloud security alliance organizations.&lt;/p&gt;

&lt;p&gt;If you google his name, you’ll see he is talking about technology at every given chance.&lt;/p&gt;

&lt;p&gt;Co-hosting the award-winning podcast “Osim Tochna” in Israel, recording videocasts, and speaking at conferences and meetups.&lt;/p&gt;

&lt;p&gt;When he is not running into walls with the VR headset, he is spreading the world of AI for developers with &lt;a href="//cloudaiworld.com"&gt;cloudaiworld.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>cloud</category>
      <category>ai</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Building a team's culture</title>
      <dc:creator>Asaf David</dc:creator>
      <pubDate>Wed, 11 May 2022 11:37:20 +0000</pubDate>
      <link>https://forem.com/artlist/building-a-teams-culture-n83</link>
      <guid>https://forem.com/artlist/building-a-teams-culture-n83</guid>
      <description>&lt;p&gt;Twenty years ago, I was a platoon commander in a classified unit in the army (that is no longer classified). Back then, making decisions and applying authority was straightforward. Officers were to make most decisions, pushing them down the command chain through non-officer commanders all the way to the soldiers. Decision enforcement in that ecosystem was called "a command."&lt;/p&gt;

&lt;p&gt;There was no place for discussion once a decision (command) was made, and the expectation was that it would get fulfilled right away, or else…&lt;br&gt;
This thick layer of "simple" soldiers who are untrained in the military decision-making process and unaware of much of the intel requires a top-down and highly hierarchical management approach. Add to that combat conditions where soldiers put their life at risk, and the need for such a hierarchy with high discipline only intensifies.&lt;/p&gt;

&lt;p&gt;Fast forward to a year ago. As I approached building my R&amp;amp;D team at Artlist - my first position as a team leader in the tech industry - I was trying to define &lt;strong&gt;my approach to managing and leading&lt;/strong&gt; such a team. Obviously, there are many differences between leading an infantry team and a team of software developers, so you can already understand that much of the conclusion would be very different from what I described above.&lt;/p&gt;

&lt;p&gt;In this article, I'll try to lay out some of the dilemmas I had and insights I gained regarding decision-making and authority as a team leader in R&amp;amp;D.&lt;/p&gt;

&lt;p&gt;Before I dive in, I think some context is needed about the company I work for, my position, and my personal history.&lt;/p&gt;

&lt;p&gt;In 2016, Artlist was founded as a bootstrap to enable video creators to find high-quality music for their videos with a revolutionary licensing subscription. Since then, it has grown tremendously, acquiring two companies and reaching 300 employees at the beginning of 2022. It has also changed its goal to support the whole content creator economy, not only video creators.&lt;/p&gt;

&lt;p&gt;As for my position, my team is in charge of two of the company's web applications, artlist.io and artgrid.io, and while writing this article, we are shifting into a group-of-teams structure, so my title is changing from team lead to group manager :-)&lt;/p&gt;

&lt;p&gt;Now here's an abridged version of my personal history to give you the whole picture. Besides being in the army, before becoming a software developer, I had a pretty enjoyable career in the food &amp;amp; beverages industry, mainly in management positions such as managing a bar, leading the on-trade market in one of the biggest wine &amp;amp; spirits importers in my country while managing 12 employees and a few million dollars of sales per year, running my own restaurant (and losing all my money), and finally establishing and leading a new online wine &amp;amp; spirits e-commerce site.&lt;/p&gt;

&lt;p&gt;Now that we've set the context, let's discuss the key factors that have impacted my approach to management. I'll also star* the insights I have derived from these factors and will try to construct from them my holistic culture and attitude as a manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Team = Leverage
&lt;/h2&gt;

&lt;p&gt;The basic idea of a team is leverage. Yes, I can write code, but I'm limited by my own velocity, knowledge and experience. Leading a team enables you to accomplish more, thanks to an increased workforce and, more importantly, the increased shared knowledge and expertise. As such, the team's ability is not only defined by its members' velocity but also by the type of knowledge each member might hold.&lt;/p&gt;

&lt;p&gt;There are a few key factors to maximizing your team's leverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diversity*
&lt;/h3&gt;

&lt;p&gt;When you have a diverse team, each member's added value holds great significance. Conversely, when all team members are the same, the added value of a single team member is lost.&lt;/p&gt;

&lt;p&gt;Of course, there are many other benefits of having a diverse team, but since this is not the main topic of this article, I'll leave it aside and perhaps address it more in-depth in the future. I'll also leave aside the option of growing your team's size, as it is more of an external solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Velocity
&lt;/h3&gt;

&lt;p&gt;Another vital factor in optimizing your team's leverage is increasing each team member's velocity, and I find that two main actions increase an individual's production capability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;personal development plan&lt;/strong&gt;* focusing on areas that simultaneously benefit the employee and the company. Such a plan should be defined and owned by the team member with the full support of their manager, and I will take the story of one of my team members to illustrate this point. Parish is a great front-end developer and has worked at Artlist for several years. However, despite his experience, Parish lacked ownership skills, resulting in a tendency to push things to production with insufficient quality, thus introducing new bugs that required him to go back to his developments repeatedly. When one of our QA engineers told me that he needs to run some QA on the site every time Parish deploys to production, the severity of the situation became clear to me. Once I had Parish understand the problem and its impact on the company, we raised some suggestions for improving the quality of his work and avoiding future issues. Long story short, a month and a half later, the feedback I got from external stakeholders was how satisfied they were with Parish's work, and he had reduced his QA cycles to almost zero. While this example might seem simplistic, I find it to be the general case in most PDPs, both for technical and soft skills.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frequent candid feedback&lt;/strong&gt;* - This is quite a big topic on which many books have been written, so I'll try to avoid going deep into this rabbit hole and simplify it by stating the following: When we receive frequent, ongoing candid feedback, we can alter our behavior and actions in a way that helps us grow at a faster pace. And the compound interest of this growth holds tremendous value to us and our surroundings. I'll just add a few tips for increasing feedback loops in your team:&lt;/p&gt;

&lt;p&gt;a. Start with the why - Show them the benefits they can reap from it. &lt;/p&gt;

&lt;p&gt;b. Do it gradually and through a controlled process until you are 100% sure you can expand it.&lt;/p&gt;

&lt;p&gt;c. Teach the team how to give feedback and how to receive it.&lt;/p&gt;

&lt;p&gt;d. Practice a lot with the team. For example, when someone gave another team member some feedback, I asked someone else to give feedback on how the feedback was given. Then I asked for feedback on the feedback's feedback and so on. It was excellent practice for providing feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Agility
&lt;/h3&gt;

&lt;p&gt;While Artlist has just grown to more than 300 employees, it still has a dominant start-up approach, focusing on innovation and our ability to react to changes and decisions as fast as possible. This point is crucial in defining the culture of my team as it's a key factor in the success or failure of any start-up. To emphasize this point, we can look at the last year from my team's point of view. Product-wise, we started by taking ownership of three websites - artlist.io, artgrid.io and the Artlist jobs site. But since then, we ended up rebuilding Artlist's primary site (artlist.io), are now working on a significant product change, and are starting to build a new product from scratch.&lt;br&gt;
But the product side of things is only one part of the story. Agility is required from an HR perspective. In fast-paced start-ups, the need to adjust the organizational structure is an ongoing process. For example, only a year ago, I had zero team members, while today, my team consists of 16 members and is about to break into several groups and add more team/group members.&lt;br&gt;
So what does this all mean to our forming culture? First and foremost, team members must be flexible and adjust quickly to any new initiative or structure. To enable this, they need to have a good understanding of the high-level plan and goals. They also have to be connected to the "why" at all times and understand the reasons for each task and what we aim to achieve.&lt;br&gt;
I found that there are two things I can do to support this behavior - high &lt;strong&gt;transparency&lt;/strong&gt;* and high &lt;strong&gt;ownership&lt;/strong&gt;*. While the former is relatively straightforward and means that team members are familiar with the decisions as well as the decision-making process, the latter is very significant and not always obvious. Perhaps explaining how I give tasks to a team member will shed some light here. Here's the basic rule: Provide enough information to enable your team member to make their own decisions on how to complete the task. Then, understanding the big picture, why this task is needed and how it helps us, they will make wiser decisions and take ownership of the mission along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creativity
&lt;/h3&gt;

&lt;p&gt;Start-ups are highly reliant on continuous innovation, which is synonymous with creativity. But how does one create an environment that increases a team's creativity or, at least, its creative results?&lt;br&gt;
These questions have already been answered in the highly recommended book 'No Rules Rules' by Erin Meyer and Reed Hastings, in which they describe how much of this can be achieved by &lt;strong&gt;reducing the rules&lt;/strong&gt;* you apply to your employees, increasing their responsibility, ownership, decision-making, and problem-solving. This isn't an easy solution, and I'll address it later on in this article.&lt;br&gt;
Increasing each member's part in the whole team's decision-making, transparency, and &lt;strong&gt;putting more power and influence in their hands&lt;/strong&gt;* will also increase the creativity and inspire them to question and criticize anything we do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledgeable workers
&lt;/h3&gt;

&lt;p&gt;Unlike most of my prior management experience - leading soldiers, sales representatives, bartenders, and so on - which didn't require any formal education or high analytical skills of my subordinates, software developers tend to be pretty damn smart. Some have completed a demanding degree in CS, while others are more autodidacts, but they all have high knowledge and skills in software development. Such workers require a specific management approach to get the most out of them. And by "getting the most out of them," I mean not only from the company's point of view but also the workers' perspective, as they tend to focus on self-growth and learning. This brings me back to several points I've already discussed before in this article, such as having a &lt;strong&gt;personal development plan&lt;/strong&gt;* and &lt;strong&gt;ownership&lt;/strong&gt;* . It also means that the team members can have a highly &lt;strong&gt;active part in the team's decision-making process&lt;/strong&gt;*. Such decisions can vary from mission-oriented to prioritization to the culture itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;So let's take a quick look at the key points we have gathered so far regarding team building and management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diversity&lt;/li&gt;
&lt;li&gt;Personal development plan&lt;/li&gt;
&lt;li&gt;Frequent candid feedback&lt;/li&gt;
&lt;li&gt;Transparency&lt;/li&gt;
&lt;li&gt;Ownership&lt;/li&gt;
&lt;li&gt;Rules Reduction&lt;/li&gt;
&lt;li&gt;Putting more power and influence in the individual hands&lt;/li&gt;
&lt;li&gt;Make them an active part of the team decision-making process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trying to connect the dots, I think the first thing that jumped into my mind was that many of these points are focused on empowering each member. Some even overlap, and others were considered for management only in my low-tech experience.&lt;br&gt;
That made me rethink what it meant to lead such a team. How much should the leading approach be egalitarian vs. hierarchical? If my team consists of such intelligent people, how much of the decision-making should I make?&lt;br&gt;
Here are my conclusions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The team as a whole is smarter than any single member, including me.&lt;br&gt;
This doesn't mean that I, as team leader, won't be making decisions. But it does mean that in many cases, my role should be enabling the team to make decisions in a healthy manner, rather than making them on my own.&lt;br&gt;
Such an approach can result in better decisions and empower the team simultaneously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Much of my focus should be on establishing good communication within the team.&lt;br&gt;
This point, which is far from easy to achieve, serves many aspects of a team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It helps the team reach better decisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It creates better alignment, which reduces problems and time waste, thus increasing velocity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It enables frequent candid feedback between team members, which helps each of them improve and grow, thus increasing the team's leverage. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Looking at these two conclusions places the manager in a new position. If back in the day, I would have thought that the leader's place was on top of the pyramid, making the decisions and controlling everything; nowadays, I believe the leader's best spot is actually between the team members, forming the "glue" that connects all the members and guides them in the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aftermath
&lt;/h2&gt;

&lt;p&gt;A year forward, looking at my team, I have to say that we are on the right track. &lt;br&gt;
Our team is highly diverse and comprises 15 members from Israel, the UK, Russia, Belarus, Bangladesh and one newcomer from Ukraine (celebrating a year in Israel now). &lt;br&gt;
Many of the team's decisions are made by its members, from prioritization through processes to culture. Evidence for that was my recent three-and-a-half week-long vacation, during which the team performed amazingly well as if I was not needed at all (note to self: take long vacations more often). &lt;br&gt;
Open, candid feedback between all members is taking place, much is still to be done in this area, but we are heading in the right direction.&lt;/p&gt;

&lt;p&gt;There are still many challenges ahead of us in implementing this culture, and most of this thought process is taken with the team, and soon probably with my new team leads. It seems that each of the factors of this approach requires more analysis as you grow with it. For example, diversity requires more thought about adjusting each member's culture to the team culture.&lt;br&gt;
But I am sure that with the current state of mind combined with the super talent of each team member, we as a team will achieve all that and more.&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>culture</category>
      <category>management</category>
    </item>
  </channel>
</rss>
