<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Productive</title>
    <description>The latest articles on DEV Community by Productive (@productive).</description>
    <link>https://dev.to/productive</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%2F7779%2F1ef9f950-bf4c-4781-8c4c-686d731b6e70.gif</url>
      <title>DEV Community: Productive</title>
      <link>https://dev.to/productive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/productive"/>
    <language>en</language>
    <item>
      <title>How we handled MySQL deadlocks in Productive</title>
      <dc:creator>Jean Petric</dc:creator>
      <pubDate>Mon, 11 Mar 2024 08:11:21 +0000</pubDate>
      <link>https://dev.to/productive/how-we-handled-mysql-deadlocks-in-productive-part-1-15ce</link>
      <guid>https://dev.to/productive/how-we-handled-mysql-deadlocks-in-productive-part-1-15ce</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;: note that I mostly redacted product’s internal details. In places I also used simplified explanations regarding MySQL indexes, especially where detailed explanations already exist elsewhere on the web. I provided links where appropriate.&lt;/p&gt;

&lt;p&gt;In the rest of this post I first provide a short context of how we do certain things at Productive. Then I briefly explain what deadlocks are, followed by the approach we took to fix them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronous jobs in Productive
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/productive/publishsubscribe-with-sidekiq-3n92"&gt;In Productive we use Sidekiq&lt;/a&gt; for scheduling asynchronous jobs. Some of these jobs are sending emails to users when they do not track their time. Others are batch jobs which need to update some financial data, copy certain objects in a transaction safe manner, etc. Starting a few months ago we’ve started noticing a surge of deadlocks in our application. In particular, these deadlocks started appearing for batched Sidekiq jobs which have to process a high number of relatively heavy database transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are deadlocks?
&lt;/h2&gt;

&lt;p&gt;Database deadlocks occur &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html" rel="noopener noreferrer"&gt;when two transactions cannot proceed as one holds a lock that the other one needs&lt;/a&gt;. There is nothing inherently wrong with deadlocks, as long as they do not impact your database in a way that you cannot process certain transactions at all. You do need, however, to have a mechanism in place to retry deadlocked transactions. It is generally sufficient to retry only the second transaction (the one that caused the deadlock), as MySQL only aborts that transaction to release the lock, allowing the first transaction to continue.&lt;/p&gt;

&lt;p&gt;Finding deadlocks’ root causes in production is difficult. Deadlocks are difficult to reproduce (as they often depend on the throughput of your transactions at any given moment). Enabling extra logging specifically for deadlocks is also tricky as they produce logs which are large in size. Even when you find transactions and the locks they hold, an appropriate course of action for their mitigation is almost never immediately clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The experiment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prep work
&lt;/h3&gt;

&lt;p&gt;As the number of deadlocks in our application started to increase month on month, we had to do something to prevent more serious issues. We started by setting up a separate temporary environment which was a very close copy of our production environment. We also did certain code changes to the product so we can run “simulations” of the problematic Sidekiq job easier. We also hooked up the environment to our monitoring system so that we could capture logs. As soon as we started mass running our jobs we immediately began noticing deadlocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deadlock detection
&lt;/h3&gt;

&lt;p&gt;There are more ways to detect deadlocks. We used two. First is &lt;code&gt;innodb status&lt;/code&gt; which displays operational information about a storage engine, amongst which &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/show-engine.html" rel="noopener noreferrer"&gt;the latest occurring deadlocks&lt;/a&gt;. Here is how an output from &lt;code&gt;innodb status&lt;/code&gt; looks like:&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%2Fc3aowvz5mlq77ih5vown.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%2Fc3aowvz5mlq77ih5vown.png" alt="Image showing the output of innodb status" width="753" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The statement only shows one, the latest detected deadlock, with prints details about the table on which the deadlock occurred, as well as the records locked and the type of locks used. This information is reported for both transactions that participated in the deadlock.&lt;/p&gt;

&lt;p&gt;The second place to find about deadlocks is the &lt;code&gt;performance_schema&lt;/code&gt; database that keeps the records of &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/performance-schema.html" rel="noopener noreferrer"&gt;all database locks that are granted&lt;/a&gt;. Here is an example output of the &lt;code&gt;data_locks&lt;/code&gt; table which shows all currently granted locks (the data in this table is "live" which means that locks appear and disappear as statements get executed):&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%2Fcdkxgs4dqxdpm85rqbzz.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%2Fcdkxgs4dqxdpm85rqbzz.png" alt="Image showing the output of data_locks table within performance_schema database" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From that output you can figure out whether some SQL statements hold too many rows locked, as this can sometimes lead to deadlocks (at least it did in our case).&lt;/p&gt;

&lt;h3&gt;
  
  
  Analysing the deadlocks
&lt;/h3&gt;

&lt;p&gt;Analysing the statements involved in deadlocks and looking at the locks they were holding didn’t really help us in addressing the chunks of code responsible for their execution. But even if it did, we would need to find a way to ensure that all transactions &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html" rel="noopener noreferrer"&gt;execute statements in the same order&lt;/a&gt; to avoid deadlocks.&lt;/p&gt;

&lt;p&gt;Looking at the output of &lt;code&gt;innodb status&lt;/code&gt; we found out which indexes were being used and on which tables. The output of &lt;code&gt;innodb status&lt;/code&gt; is fairly straightforward and merely reveals which SQL transaction is being involved in a deadlock, which index and what type of lock was used. Every new deadlock overwrites &lt;code&gt;innodb status&lt;/code&gt;, so by refreshing the status you can analyse which deadlocks occur.&lt;/p&gt;

&lt;p&gt;From the output we observed that MySQL would pick two different indexes on the same table at different times. In both cases indexes were indexing a single column (simple index). It’s worth noting that depending on which index is being used, different records will end up being locked, which is the way MySQL ensures the integrity of data.&lt;/p&gt;

&lt;p&gt;Knowing the indexes and type of locks that were causing the deadlocks helped us to further analyse &lt;code&gt;performance_schema&lt;/code&gt;, in particular its &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html" rel="noopener noreferrer"&gt;&lt;code&gt;data_locks&lt;/code&gt; table&lt;/a&gt;. This table holds live information about all locks being granted. Inside we found the offending locks and what index records were being locked by them (note that in MySQL it’s not rows that are being locked, &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html" rel="noopener noreferrer"&gt;but rather index records&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Index optimisation
&lt;/h3&gt;

&lt;p&gt;Seeing the table helped us to realise that by using these two indexes individually, MySQL had to lock wider ranges of index records than it is really necessary. For example, our job needed to update one record &lt;code&gt;D&lt;/code&gt;, but MySQL locked all records between &lt;code&gt;B&lt;/code&gt; and &lt;code&gt;F&lt;/code&gt;, as that was the smallest index record it could lock. As more rows than needed were being locked, and the throughput of our Sidekiq jobs was high, this led to a high number of deadlocks.&lt;/p&gt;

&lt;p&gt;As a result of our analysis we concluded that we needed to optimise these two indexes. As the problematic MySQL statements were using both indexed columns in their &lt;code&gt;WHERE&lt;/code&gt; conditions, we decided to drop these indexes.&lt;/p&gt;

&lt;p&gt;Instead, we opted in for a composite index containing both of these columns. By doing that we hypothesised that less rows will be locked due to more fine grained index records. Combining two columns narrows down the search MySQL has to do to find the records that need to be altered, incidentally locking less rows. &lt;strong&gt;By executing the experiment in our testing environment we reduced deadlocks from 50.000 to 0.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The second experiment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New deadlocks
&lt;/h3&gt;

&lt;p&gt;After re-running the experiment again, with our new composite index, we started noticing deadlocks again, but on a different table. Symptoms were very similar to the ones before, so we took a similar approach looking at the &lt;code&gt;innodb status&lt;/code&gt; and locks and deciding to drop simple indexes, finally replacing them with a composite index. Repeating the experiment we found that the number of deadlocks actually increased two fold (from about 300 to 600) with the new composite index, so we decided not to keep the composite index here.&lt;/p&gt;

&lt;p&gt;The increased number of deadlocks in the second experiment was confusing so we decided to dig deeper. It wasn’t until we looked at our database schema that we noticed a &lt;code&gt;BEFORE INSERT&lt;/code&gt; database trigger. Inside there was a statement with two column conditions in the WHERE condition. One of these columns held a pattern whose typical value for most of our clients was “{n}”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trying composite indexes again
&lt;/h3&gt;

&lt;p&gt;Unlike in our first experiment where we reduced 50.000 deadlocks to 0 by adding a composite index, the same approach didn't help us in this situation. The problem was no variability in the column holding a pattern (&lt;code&gt;&amp;gt;98%&lt;/code&gt; of pattern was &lt;code&gt;{n}&lt;/code&gt;), so MySQL was unable to narrow down the search and lock less records. As a result, MySQL had to lock almost the entire table, so deadlocks were inevitable. We couldn’t find other columns to form a more suitable index or to change the trigger. But as the number of deadlocks for the second table was relatively low, we decided not to do any changes.&lt;/p&gt;

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

&lt;p&gt;In the end we decided to live with some deadlocks. It turned out that in production we ended up with some 50 deadlocks when the particular jobs are at their peak, but often much less than that. As a matter of fact, we've been monitoring deadlocks for the last couple of months and week on week we get about 16 of them at most. However, it’s also worth mentioning that optimising indexes might not always be a suitable solution for deadlocks. As shown above, it depends on a case by case basis.&lt;/p&gt;

&lt;p&gt;But what is also important is that having deadlocks is not necessarily a bad thing, as long as you have a strategy to retry transactions and database can operate normally. All in all, three months in and counting, more than 50.000 deadlocks were resolved by a simple index modification.&lt;/p&gt;

&lt;p&gt;To conclude, we found a way to address deadlocks beyond what the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks-handling.html" rel="noopener noreferrer"&gt;official MySQL documentation suggests&lt;/a&gt;. Optimising indexes was a cheap but effective way to reduce deadlocks. However, adding indexes judiciously pays off even better than finding yourself later removing them due to issues they may cause.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>database</category>
      <category>performance</category>
      <category>development</category>
    </item>
    <item>
      <title>Integrations Series: Authentication And Connection</title>
      <dc:creator>antoniobajivic</dc:creator>
      <pubDate>Wed, 21 Feb 2024 13:32:57 +0000</pubDate>
      <link>https://dev.to/productive/integrations-series-authentication-and-connection-3l2j</link>
      <guid>https://dev.to/productive/integrations-series-authentication-and-connection-3l2j</guid>
      <description>&lt;h2&gt;
  
  
  Integrations are a big part of any software, but &lt;strong&gt;they’re crucial to the functionality of a tool like Productive&lt;/strong&gt;.
&lt;/h2&gt;

&lt;p&gt;That’s why we decided to dedicate a post series to discussing the importance of integrations in Productive. From enhancing functionality by connecting different tools to streamlining processes and improving user experience, integrations make life easier. We’ll showcase one of our most-used integrations, the one between Productive and Xero, which allows users to seamlessly transfer invoices and synchronize payments between the two tools.&lt;/p&gt;

&lt;p&gt;Your agency uses Productive for managing client projects and project financials. At month’s end, you face the daunting task of issuing and sending invoices upon invoices, then replicating the process in Xero. Afterward, you also have to individually mark each payment as received.&lt;/p&gt;

&lt;p&gt;And this is where the power of integrations kicks in. With one simple integration, invoices are synced between Xero and Productive, and any payment received in Xero is automatically recorded in Productive.&lt;/p&gt;

&lt;p&gt;The integration between Productive and Xero looks interesting, right?&lt;/p&gt;

&lt;p&gt;But before actually using the integration, we need to set it up first, and that’s the main focus of this blog post! We’re exploring the implementation of the OAuth 2.0 protocol.&lt;/p&gt;

&lt;p&gt;Let’s dive right in!&lt;/p&gt;

&lt;h2&gt;
  
  
  First, How Do You Connect Xero and Productive?
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 (Open Authorization 2.0) is an authentication protocol that is considered an industry standard.&lt;/p&gt;

&lt;p&gt;Utilizing the OAuth protocol, Productive is granted access to the Xero account without accessing the user’s credentials.&lt;/p&gt;

&lt;p&gt;Key features of the OAuth 2.0 protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Granular access control&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication without sharing credentials&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Token-based authentication (&lt;code&gt;access_token&lt;/code&gt; &amp;amp; &lt;code&gt;refresh_token&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In the following steps, the authentication process is explained:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The user authorizes Productive, granting access to their own data on the Xero platform:
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  2. After successful authentication, the user is redirected back to the &lt;code&gt;redirect_uri&lt;/code&gt; defined in the previous step with two values added as query params:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;code&lt;/code&gt; → short-lived token that needs to be exchanged for &lt;code&gt;access_token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state&lt;/code&gt; → serves as protection against Cross-Site Request Forgery (CSRF) attacks: if the received state attribute value does not match the value submitted in the previous step, the authentication process is terminated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensuring the redirection of users back to the Productive app is a crucial aspect of the OAuth flow due to the sensitivity of the information contained in the &lt;code&gt;redirect_uri&lt;/code&gt;.&lt;br&gt;
To ensure the user’s redirection to the correct location, we have securely stored the redirect URI within the Xero app.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Exchanging the verification code (&lt;code&gt;code&lt;/code&gt;) for an access token (&lt;code&gt;access_token&lt;/code&gt;):
&lt;/h4&gt;

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

&lt;h4&gt;
  
  
  4. Retrieving the generated tokens:
&lt;/h4&gt;

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

&lt;p&gt;&lt;code&gt;access_token&lt;/code&gt; is used to call the Xero API, while the &lt;code&gt;refresh_token&lt;/code&gt; is used to refresh the access token once it has expired.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Retrieving and selecting the tenant whose resources are being accessed:
&lt;/h4&gt;

&lt;p&gt;Each user can have multiple Xero organizations (referred to as tenants).&lt;br&gt;
Xero requires that the &lt;code&gt;xero_tenant_id&lt;/code&gt; field is sent as a header param in every HTTP request.&lt;/p&gt;

&lt;p&gt;The following code snippet shows the retrieval of all available tenants, from which the user later selects a tenant for current integration in the Marketplace settings:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Now, Let's Create an Integration
&lt;/h2&gt;

&lt;p&gt;In Productive, as well as in many other applications, there is a list of applications called the marketplace. In the &lt;em&gt;marketplace&lt;/em&gt;, customers choose the integration they want to use. When the integration is selected, the connection process begins by clicking the “Connect app” button. The connection flow will be demonstrated using the example of Xero.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. Creating and Connecting An Integration
&lt;/h3&gt;

&lt;p&gt;When establishing an integration, it’s generated in the system. However, to ensure its successful creation, it must align with the authentication requirements on the backend. All integrations require a redirect URI. This URI is used to redirect to Productive after connecting the integration to adjust the settings of each integration. After generating the URI, the integration creation process begins.&lt;/p&gt;

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

&lt;p&gt;If the organization has branches or subsidiaries, you’ll first need to select the subsidiary (branch) for which you want to use Xero.&lt;/p&gt;

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

&lt;p&gt;When creating an integration on the backend, authentication verification is performed, and if all requirements are met, the integration process begins.&lt;/p&gt;

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

&lt;p&gt;During the integration connection, the customer is directed to the interface of the system they want to integrate with, where they also need to provide information to verify if the user is existing and valid. Once completed, the process returns to the marketplace via the redirect URI, initiating the setup of the integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Redirect
&lt;/h3&gt;

&lt;p&gt;After the customer is redirected to Productive, the data obtained from the external tool is set on the integration model. For example, Xero requires that there is always a code and org representing the code and organization to which the integration connects and exports data in Xero.&lt;/p&gt;

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

&lt;p&gt;Once the integration model has been updated, a call to the backend occurs again to update the integration with new data.&lt;/p&gt;

&lt;p&gt;After updating the model, if there are no errors, we set the parameters of the model to transition to the integration setup route, i.e., editing the integration settings.&lt;/p&gt;

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

&lt;p&gt;With successful authentication and integration connection, we’re finishing the first part of the integration posts series. As described in this blog post, understanding OAuth 2.0 becomes not just a necessity but a powerful tool to enhance user experience, safeguard sensitive information, and foster a more trustworthy digital ecosystem. After successful authentication, due to settings related to the external system redirect, in this case, Xero, it brings us back to Productive to continue with further integration setup. &lt;/p&gt;

&lt;p&gt;Before we end this blog post, I want to emphasize that the backend part was written by my colleague &lt;a class="mentioned-user" href="https://dev.to/bartic"&gt;@bartic&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In our next post, we’ll break down data mapping and show you why it’s a must-know for smooth integrations. Don’t miss out!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>ember</category>
      <category>rails</category>
    </item>
    <item>
      <title>Publish/Subscribe with Sidekiq</title>
      <dc:creator>štef</dc:creator>
      <pubDate>Wed, 21 Feb 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/productive/publishsubscribe-with-sidekiq-3n92</link>
      <guid>https://dev.to/productive/publishsubscribe-with-sidekiq-3n92</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Our Rails application is an old monolith that relies heavily on after/before callbacks to trigger code that has side effects. This usually means that one model can change records of another model. I suspect that we all have come across this "callback hell" at least once in our professional life.&lt;/p&gt;

&lt;p&gt;We needed to introduce a new service for search. As we settled on using &lt;a href="https://www.meilisearch.com/"&gt;meilisearch&lt;/a&gt;, we needed a way to sync updates on our models with the records in meilisearch. We could've continued to use callbacks but we needed something better.&lt;/p&gt;

&lt;p&gt;We settled on a Publish/Subscribe (Pub/Sub) pattern. &lt;br&gt;
Pub/Sub is a design pattern where publishers broadcast messages to topics without specifying recipients, and subscribers listen for specific topics without knowing the source, promoting loose coupling.&lt;/p&gt;
&lt;h2&gt;
  
  
  Exploring Pub/Sub Solutions
&lt;/h2&gt;

&lt;p&gt;While searching for a solution we came across these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://edgeguides.rubyonrails.org/active_support_instrumentation.html"&gt;ActiveSupport::Notification&lt;/a&gt;: A Rails component for defining and subscribing to events within an application​.&lt;br&gt;
-&amp;gt; Active::Support is synchronous by default. It does not handle errors and retries out of the box. We would need to use a background job handler (like Sidekiq) to make it asynchronous.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/krisleech/wisper"&gt;Wisper&lt;/a&gt;: A Ruby gem providing a decoupled communication layer between different parts of an application​&lt;br&gt;
-&amp;gt; I personally dislike wisper. I used it in the past and dislike the way of defining subscribers in a global way. I wanted topics to be arbitrary and each class to define what to subscribe for itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dry-rb.org/gems/dry-events/1.0/"&gt;Dry-events&lt;/a&gt;: A component of the dry-rb ecosystem focused on event publishing and subscription​&lt;br&gt;
-&amp;gt; This gem looks like a framework on top of which I would need to create my own Pub/Sub. It also does not do asynchronous execution out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We finally settled on Sidekiq!&lt;br&gt;
Sidekiq is a background job processing tool, which we already use in our application and we figured a way to use it for processing Pub/Sub messages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Implementation is quite straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PubSub&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscriberJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
    &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:pub_sub&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constantize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;

  &lt;span class="c1"&gt;# Publish a message to topic&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# @param topic [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @param message [Hash]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SubscriberJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Subscribe a class + handler to a topic&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# @param topic [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @param class_name [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @param handler [String]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;async: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@subscribers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# return subscribers for the topic&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# @param topic [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @return [Array&amp;lt;Hash&amp;gt;] { class_name: String, handler: String}&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@subscribers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Subscribe a class + handler to a topic&lt;/span&gt;
  &lt;span class="c1"&gt;#&lt;/span&gt;
  &lt;span class="c1"&gt;# @param topic [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @param class_name [String]&lt;/span&gt;
  &lt;span class="c1"&gt;# @param handler [String]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;async: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@subscribers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="vi"&gt;@subscribers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;handler: &lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Usage is also quite straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Search::Tasks::Indexer&lt;/span&gt;
   &lt;span class="nc"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

     &lt;span class="no"&gt;MeilisearchClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reindex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_meilisearch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskForm&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationForm&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;item: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Experience
&lt;/h2&gt;

&lt;p&gt;After 6 months of using this method, I can safely say that it works as intended.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It handles unexpected errors like network errors gracefully&lt;/li&gt;
&lt;li&gt;It was easy to use where we needed it&lt;/li&gt;
&lt;li&gt;Classes that handle the incoming messages are separated from the rest of the models and are easy to unit test&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📖 Sidestory: Rethinking Pub-Sub&lt;/strong&gt;&lt;br&gt;
Recently &lt;a href="https://dev.to/buha"&gt;Buha&lt;/a&gt; wrote &lt;a href="https://dev.to/productive/a-close-call-with-real-time-how-rethinking-pub-sub-saved-the-day-52kp"&gt;a blog post&lt;/a&gt; how he gave up on the Pub/Sub approach. It is a good read to see the downsides of this approach.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;We solved the problem of pub/sub messaging in our product with the help of Sidekiq. It proved to be reliable and of high quality, and the implementation itself is not complicated. &lt;/p&gt;

&lt;p&gt;What are your experiences with Sidekiq? Are you using something else?&lt;/p&gt;

</description>
      <category>api</category>
      <category>rails</category>
      <category>ruby</category>
      <category>development</category>
    </item>
    <item>
      <title>Engineering Ladder - Being in The Middle Sucks?</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Tue, 23 Jan 2024 07:59:12 +0000</pubDate>
      <link>https://dev.to/productive/engineering-ladder-being-in-the-middle-sucks-2ei9</link>
      <guid>https://dev.to/productive/engineering-ladder-being-in-the-middle-sucks-2ei9</guid>
      <description>&lt;p&gt;You might be wondering to what "middle" am I referring to? &lt;/p&gt;

&lt;p&gt;It's the middle I'm currently in, the middle of the &lt;a href="http://www.engineeringladders.com/"&gt;Engineering Ladder&lt;/a&gt;, focusing on the &lt;strong&gt;Tech Lead&lt;/strong&gt; role. This role sits between the &lt;strong&gt;Developer&lt;/strong&gt;/&lt;strong&gt;Individual Contributor&lt;/strong&gt; and "&lt;em&gt;higher&lt;/em&gt;" roles like &lt;strong&gt;Staff Engineer (SE)&lt;/strong&gt;  or &lt;strong&gt;Engineering Manager (EM)&lt;/strong&gt;, combining aspects of both. &lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Tech Lead&lt;/strong&gt; is still contributing to the code base and has its own fair share of tasks/milestones/deliverables, but he is not anymore working "&lt;em&gt;on its own&lt;/em&gt;". He has a team of people to lead, to watch over the codebase/architecture, to approve technical specifications, to watch over the product and more. &lt;/p&gt;

&lt;p&gt;As I'm diving into this role, I wanted to give you my cent or two on what I think is the most interesting part of the role "&lt;em&gt;upgrade&lt;/em&gt;". It's about the &lt;strong&gt;Tech Leads relationships&lt;/strong&gt; to both the &lt;strong&gt;Developers on one&lt;/strong&gt; and the &lt;strong&gt;SEs and EMs on the other side&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Just to be clear, &lt;strong&gt;this won't be 1:1&lt;/strong&gt; to all Tech Lead roles, their responsibilities and their descriptions. This is just my point of view on being a &lt;strong&gt;Tech Lead&lt;/strong&gt; at &lt;strong&gt;Productive&lt;/strong&gt; and the way we see this role.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Lead - baby EM?
&lt;/h2&gt;

&lt;p&gt;If you read books or posts about management, you probably read something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Being a Tech Lead is an exercise in leading without authority.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;without authority&lt;/strong&gt; part is pretty true imo. Your work starts to depend on other people but at the same time you don't really have some authority over them. You can and must have the situation in control but if the situation gets out of hand, you can't resolve it on your own - you need your EM. Again, this depends on the level that your company expects you to work on as a TL.&lt;/p&gt;

&lt;p&gt;For me and the way I see it, the most interesting and challenging part of being a Tech Lead is exactly that, the subtle intro to a management role.&lt;/p&gt;

&lt;p&gt;It's a role where you should really start thinking a lot about the people you work with.&lt;/p&gt;

&lt;p&gt;Several relations come into play and thats mostly bcz you have to watch both up and down from where you're standing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Looking up the 🪜 - Staff Engineers and Engineering Managers
&lt;/h3&gt;

&lt;p&gt;Your relationship with your EM must be build on solid ground. Your EM has to be a person you enjoy working with, you have a lot to learn from and a person you respect for the things he has done and still is doing. If you are not on the same page, it's less likely that you will benefit from this relationship as much as you could in a "good setup".&lt;br&gt;
In my case, both of my EMs over the time were people I worked with from the first day in Productive. Over the years while working here, I learned a lot from them. The technical aspect is not as important here as the whole overview on how they get things done is - how do they approach problems and how do they solve them.&lt;br&gt;
Other than having a good relationship with you SE/EMs, the main point here is that &lt;strong&gt;you have to trust their calls and support them publicly&lt;/strong&gt;, to help them set those ideas in action. If you don't believe that what they are doing is going to help either you, your team or your organization, you will do a "bad job" here. That's because you in the first place won't accept those new ideas and implement them into your workflows and processes. How could we then expect the rest of our team to do so? &lt;strong&gt;One should lead by example&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking down the 🪜 - Software Engineers
&lt;/h3&gt;

&lt;p&gt;On the other side, you also have to have really good relations with the people you "manage", the people in your team. In my case, being a TL for the Core Team, there are 3 Backend (BE) and 4 Frontend (FE) engineers (including me and the FE TL).&lt;br&gt;
It is essential for us to know the strengths and weaknesses of our peers so that we could assign them the tasks in which they will prosper and in which they could learn and gain knowledge.&lt;br&gt;
You also need to know them in order to know how to best approach them and to set your EMs ideas in motion. How to present those ideas to them in a way that they also see a benefit in them and not just "something new" they have to do.&lt;br&gt;
These relationships and the work you make on this front will tell you if you are fit to be in management or not. Working with people is not for everyone and those who do it should do it for the right reasons - and that's basically to do it for the people you are managing and seeing them grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking at yourself 🪞
&lt;/h3&gt;

&lt;p&gt;This is really important. Don't forget to look on yourself - on your motivation, on your moral and your overall "standards". &lt;br&gt;
You could be great with both your SE/EM and your team members, but &lt;strong&gt;you shouldn't just be a proxy between them&lt;/strong&gt;. &lt;br&gt;
Have your opinions, question your EMs ideas, question your team members and look at things without bias. Not everything proposed by any side should be accepted blindly - as I said before, you should know all the details of the changes you are implementing so that you could expect people to follow.&lt;br&gt;
For each idea that you are working on you should know what is the thing/problem your are solving, how are you solving it, what are the benefits of doing this and what could you come by down the road.&lt;br&gt;
Those are the topics that you will most likely be going over with your EM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take home from this 🙃
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ask for Feedback
&lt;/h3&gt;

&lt;p&gt;What I think that people in this role, actually any new position, would benefit the most is &lt;strong&gt;feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For me, feedback is always welcome. Often times I even ask for it before the scheduled 1-on-1s, 360s or other kinds of performance reviews.&lt;/p&gt;

&lt;p&gt;It should be pretty obvious when a newly promoted TL starts doing some of his new chores and that can go in both directions. Anyway it goes, it's great when your EM has your back. When he shares positive feedback about the good things you are doing and how to make the "bad" things better, how to handle them in a more appropriate way. &lt;/p&gt;

&lt;p&gt;So my dear EMs, watch on your TLs, look out what they are doing, tailor your 1-on-1's to check the progress and their thoughts. Try remembering when you went thru this phase and what helped you to adjust to this new role. &lt;/p&gt;

&lt;h4&gt;
  
  
  Tell the people around you what's up
&lt;/h4&gt;

&lt;p&gt;I noticed that my communication to some of my peers lately changed. In our chat there was a lot more work related messages - asking for status updates, asking for estimates, proposing them changes in their workflows, asking for explanations on decision, ... But I never actually told them why did I start making all those questions, asking for answers and started giving advice on workflows/processes. &lt;/p&gt;

&lt;p&gt;So I wrote them a message explaining that now, as a TL and owner of some milestones, I have to have a better overview of their work and that we will have to communicate more often. &lt;br&gt;
That I will probably be annoying them with status checks on tasks - trying not to micromanage tho. &lt;br&gt;
That they can involve me in their Github Pull Requests (PRs) where they usually wouldn't - not as a reviewer, just as a subscriber.&lt;br&gt;
I told them that I'm here to help them get unstuck with their task, at least try to help and to further delegate the issue. &lt;br&gt;
That they can always ping me if they needed advice. &lt;br&gt;
And I ended it with - "&lt;strong&gt;give me feedback&lt;/strong&gt; at any point, correct me if doing something wrong or if you don't agree with me on something".&lt;/p&gt;

&lt;p&gt;And when I start a conversation with someone new, this is now one of the first messages I start the conversation with.&lt;/p&gt;

&lt;p&gt;That seems to be working well so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  All in all
&lt;/h2&gt;

&lt;p&gt;Ain't gonna lie, this really is a journey with a lot of downs and occasional ups. It's a change that sets you far out of your comfort zone. Just when you think you figured something out, you spot a flaw and find yourself two steps back.&lt;/p&gt;

&lt;p&gt;Thoughts of going back to being an individual contributor sound great most the times you hit a low 😅 But if you remember how you learned any new thing, that probably looked a lot like this: you get out of your comfort zone, fail a few or a lot of times but eventually you figure it out. We must get out of our comfort zone to learn and to grow.&lt;/p&gt;

&lt;p&gt;Ultimately, this trip to management is not irreversible, you can always take a step back and give it another shot later. Reflect to this experience and try to get the max out of it so that you know better next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, does it suck to be in the middle?
&lt;/h2&gt;

&lt;p&gt;If you ask me, depends on the day 😅 I would say &lt;strong&gt;no it doesn't&lt;/strong&gt;, but damn, it sure isn't easy.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>development</category>
    </item>
    <item>
      <title>Watch Out When Overriding Memoized Methods</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Mon, 08 Jan 2024 09:34:33 +0000</pubDate>
      <link>https://dev.to/productive/watch-out-when-overriding-memoized-methods-5dj7</link>
      <guid>https://dev.to/productive/watch-out-when-overriding-memoized-methods-5dj7</guid>
      <description>&lt;p&gt;This post was inspired by a 🐛 that was caused by not thinking enough when overriding a memoized method. Once we noticed the buggy behaviour, it took me some &lt;em&gt;binding.pry&lt;/em&gt; time to figure out what was going on.&lt;/p&gt;

&lt;p&gt;Lets check out what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code snippets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FormAction&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
      &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

      &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

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

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolate_properties&lt;/span&gt;
      &lt;span class="n"&gt;interpolatable_attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpolate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolatable_attributes&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;NotImplementedError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="vi"&gt;@form_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:attributes&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;stringify_keys!&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;  
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple &lt;strong&gt;Ruby&lt;/strong&gt; class which is the base class for the specific actions we need to implement for our &lt;strong&gt;Rails app&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The action for which we figured out the bug was the action of creating comments on various objects. Its code looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateComment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;FormAction&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolatable_attributes&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;commentable_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much code is actually shown but just enough to spot the bug 🐛. Found it?&lt;/p&gt;

&lt;h3&gt;
  
  
  What was going on
&lt;/h3&gt;

&lt;p&gt;Basically what was happening when running &lt;code&gt;CreateComment#execute_action&lt;/code&gt; was the following:&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;execute_action&lt;/code&gt; we firstly need to interpolate some attributes - defined by the specific action class. In this case, for the &lt;code&gt;CreateComment&lt;/code&gt; action that was only the &lt;code&gt;'body'&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="c1"&gt;# form_attributes = { 'body' =&amp;gt; 'non interpolated' } &lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's fine - we do some logic, the &lt;code&gt;'body'&lt;/code&gt; attribute gets interpolated and that value is set as the new value for the &lt;code&gt;'body'&lt;/code&gt; key in the form_attributes hash.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;
  &lt;span class="c1"&gt;# form_attributes = { 'body' =&amp;gt; 'interpolated' }&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, as the implementation of the &lt;code&gt;CreateComment&lt;/code&gt; class uses &lt;code&gt;super.merge(...)&lt;/code&gt; what we get is actually a new hash each time the method is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;
  &lt;span class="n"&gt;interpolate_properties&lt;/span&gt;

  &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="c1"&gt;# process method called with { 'body' =&amp;gt; 'non interpolated' }&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next time, after the interpolation is over, the method &lt;code&gt;form_attributes&lt;/code&gt; is called when passing that hash to our form object and in that moment, the form object gets a newly generated hash that doesn't have the interpolated &lt;code&gt;'body'&lt;/code&gt; value. So the interpolation was actually pointless as would any other modification of the &lt;code&gt;form_attributes&lt;/code&gt; hash prior to the &lt;code&gt;form.process&lt;/code&gt; call be.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution 🐛🔨
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Actions&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateComment&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;FormAction&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_attributes&lt;/span&gt;
      &lt;span class="vi"&gt;@form_attributes&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;commentable_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added memoization to the &lt;code&gt;CreateComment#form_attributes&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Simple as that and when you think about it, also pretty obvious that it needed to look like this from the start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;p.s. These snippets maybe look a bit too simple and memoization probably looks like not needed but it's a stripped version of the real classes. The &lt;code&gt;form_attributes&lt;/code&gt; method is called and modified quite a few times 🙃&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>development</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Close Call with Real-Time: How Rethinking Pub-Sub Saved the Day</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Fri, 15 Dec 2023 08:43:10 +0000</pubDate>
      <link>https://dev.to/productive/a-close-call-with-real-time-how-rethinking-pub-sub-saved-the-day-52kp</link>
      <guid>https://dev.to/productive/a-close-call-with-real-time-how-rethinking-pub-sub-saved-the-day-52kp</guid>
      <description>&lt;p&gt;All of this started while I was working on our new feature -  &lt;strong&gt;Automations&lt;/strong&gt; 🤖. In a nutshell, Automations allow customers to set up actions triggered under specific conditions. Some of the currently implemented actions are sending slack messages, creating and updating tasks, or posting comments on objects. &lt;/p&gt;

&lt;p&gt;I wanted that actions that modify tasks and comments would send a message over our real-time system so that our frontend clients (browser, mobile, desktop app) could pick that up and show those changes as they occur.&lt;/p&gt;

&lt;p&gt;Currently, our app’s real-time updates are tied to POST/PATCH/DELETE requests. We have a controller extension &lt;code&gt;Extensions::Broadcastable&lt;/code&gt; that hooks on &lt;code&gt;save_form&lt;/code&gt; and &lt;code&gt;destroy_resource&lt;/code&gt; methods and sends a real-time event if the action was successful. However, this approach wasn't suitable for my automation actions, as they don't go thru controllers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api::V2::Extensions::Broadcastable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_form&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;broadcast_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'create'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'created'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'updated'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy_resource&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;broadcast_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deleted'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As I was digging into this topic, I somehow changed the scope of my task from making the automations actions “live” to revamping our whole broadcasting architecture. I wanted to move that logic out of controllers to a place where I could catch all the changes - which would also catch the automations actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  📖 Sidestory: A Recent Development in Pub-Sub
&lt;/h4&gt;

&lt;p&gt;Just recently, &lt;a href="https://dev.to/d4be4st"&gt;Stef&lt;/a&gt; implemented a Publish-Subscribe architecture in our Rails app. He made it while revamping our search feature. That was a pretty cool moment for the team and it sounded really useful. After the presentation about it, we immediately started thinking what of our current code could've been done with Pub-Sub. No one really revamped anything by the time I was working on this real-time topic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🧐 Exploring Different Approaches
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Callbacks
&lt;/h4&gt;

&lt;p&gt;Yeah... altho we all know that callbacks are evil, I couldn’t at least think about them, just for a moment... &lt;/p&gt;

&lt;h4&gt;
  
  
  2. Forms instead of Controllers
&lt;/h4&gt;

&lt;p&gt;Both the controller actions and my automation actions use the same forms to handle our data. So, why wouldn't I hook on forms and send my real-time messages from there.&lt;br&gt;
This approach was bugging me a bit as we don't use Form objects in all the places we are actually changing our data. So this wouldn't make the whole app feel "live" but I would cover more places than what we have currently. That sounded promising to me.&lt;/p&gt;

&lt;p&gt;Wanted to pitch my thoughts to the rest of the Core team. Thru that discussion, a lightbulb moment happened 💡 &lt;/p&gt;
&lt;h4&gt;
  
  
  3. Embracing Pub/Sub
&lt;/h4&gt;

&lt;p&gt;I was mind-blown 🤯 That is exactly what I wanted!!&lt;br&gt;&lt;br&gt;
Publishing events happens for every change. Ofc, except the places we are using methods that skip callbacks (update_column, update_all, ...) as Pub/Sub and the aspect of publishing changes essentially is hooked to callbacks - but that’s a topic for itself 🫠&lt;/p&gt;
&lt;h3&gt;
  
  
  Making a PoC
&lt;/h3&gt;

&lt;p&gt;As with all big changes in our codebase, and generally in our product, I was putting this code behind a &lt;a href="https://dev.to/productive/decoupling-deployment-from-release-with-feature-toggles-7lo"&gt;Feature Flag (FF)&lt;/a&gt;.&lt;br&gt;
Simply, when one would have the &lt;code&gt;pubSubBroadcasting&lt;/code&gt; FF enabled I would skip sending the real-time events from the controller actions and I would handle the published events accordingly. If you didn't have that FF, nothing changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::V2::Tenanted::TasksController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiController&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Extenstensions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcastable&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_broadcast?&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;FeatureFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pub-sub-broadcasting'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Made a few Subscribers that would listen for all the &lt;strong&gt;task and comment related changes&lt;/strong&gt; and simply handle them as sending a real-time event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Core::Tasks::Broadcaster&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Realtime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcaster&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task.delete'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comment.upsert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_comment_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;PubSub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comment.delete'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:on_comment_upsert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ofc, added RSpec tests and set up some widgets on New Relic to cover the difference in number of events that we are sending now - as we knew that we are going to send more events now.&lt;/p&gt;

&lt;p&gt;Basically that was it. The next step was to slowly propagate this FF over all organizations, check our New Relic metrics and see if nothing breaks. Once we would release that change to all of our user base, we should cover the remaining objects and make subscribers for their Pub/Sub events too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Showing off with it
&lt;/h3&gt;

&lt;p&gt;As I was pretty proud of this solution, I kinda talked a lot about it and so naturally it popped up in a 1on1 meeting with my EM &lt;a href="https://dev.to/ilucin"&gt;Lucin&lt;/a&gt;. He wanted to discuss that a bit more so I told him the same thing I wrote here. My vibe was like &lt;em&gt;"Isn't that great? Our APP will be LIVE, all the data would be in sync."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;His response was "But do we really want that?". &lt;/p&gt;

&lt;p&gt;That wasn't the response that I was looking for 🙃&lt;/p&gt;

&lt;p&gt;What I didn't know was that our frontend client, once it gets socket messages, depending on the screen the user is, has to make additional API calls so that all the required data could be fetched again. So, a great deal of my real-time events actually ends up generating additional requests to our server and in a way, we are just generating a lot more traffic (self DDoSing?). As I didn't know about this, I wasn't even paying attention to those metrics along the way. &lt;/p&gt;

&lt;h3&gt;
  
  
  One step forward, two steps back
&lt;/h3&gt;

&lt;p&gt;Let's get better data so that we can make a better call on this.&lt;/p&gt;

&lt;p&gt;I took a period of one week from our logs and checked the number of the POST/PATCH/DELETE requests versus the number of dispatched publish events. This in the end would roughly be the same number as the events we are sending over the socket in the current and in the new way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tasks endpoint
controller POST/PATCH/DELETE actions - 211k
task.upsert + task.delete publishes  - 263k
&amp;gt; thats ~25% more real-time events

Companies endpoint
controller POST/PATCH/DELETE actions      -  5.2k
company.upsert + company.delete publishes - 20.1k
&amp;gt; thats ~4 times more real-time events

Deals endpoint
controller POST/PATCH/DELETE actions -    31k
deal.upsert + deal.delete publishes  - 1_728k
&amp;gt; thats ~55 times more real-time events!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wasn't really aware of how badly this could end up. I was making a PoC out of this for &lt;strong&gt;tasks&lt;/strong&gt; and &lt;strong&gt;comments&lt;/strong&gt; and you can see here that there wasn't such a big difference. 25% more events was okay, I knew it would be more.&lt;/p&gt;

&lt;p&gt;But look at our &lt;strong&gt;deals&lt;/strong&gt; endpoint for example - 55 times more events would be sent. That would add up to the traffic we sent over sockets, to our infrastructure bill for that services, and I don't want to imagine the number of API calls generated as a result of this - by ourselves...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deals have that much traffic because a lot of other objects update financial data on deals so that was understandable too, it immediately came to us...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Back to the drawing board
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Pub/Sub shouldn't be a bad call for this
&lt;/h4&gt;

&lt;p&gt;As this is the part of our code that gets all the changes in our data, when wanting to make a frontend client to be as live as it gets, this should be a good call. The solution would be in not sending all the changes over sockets but filter them by relevant and not relevant. This way we would surely see a drop of those mad &lt;strong&gt;company&lt;/strong&gt; and &lt;strong&gt;deal&lt;/strong&gt; numbers.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Send all the needed data to front?
&lt;/h4&gt;

&lt;p&gt;As mentioned before, each real-time message already contains the object that was changed. The issue here is that there are a lot of screens in our client and we can't know all the contexts our users are in and what additional data should be sent - which is exactly why our client makes API calls when receiving some socket messages.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Why didn't I just resolve the problem I had
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;To make those actions "live" in our frontend clients (browser, mobile, desktop APP), I wanted to plug them into our real-time system.&lt;br&gt;
So, why didn't I just put a bit of explicit calls to the code of my automations actions where I would just call the class that sends the real-time message.&lt;br&gt;
But no, I wanted to play smart and to fix a problem that wasn't really there in the first place - to make everything more live &lt;em&gt;while no one was asking for it&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Aftermath
&lt;/h3&gt;

&lt;p&gt;So yeah, the Pub/Sub usage in our real-time part of the app is put on pause until we leverage things up.&lt;/p&gt;

&lt;p&gt;I went on with the &lt;strong&gt;3rd solution&lt;/strong&gt; and added 5 lines of code - a call to the Broadcaster class is one line and I have 5 events over 4 classes to send...&lt;/p&gt;

&lt;p&gt;This was a nice learning opportunity for me and I would say that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When having concrete problems, stick to handling them and resolve them first&lt;/li&gt;
&lt;li&gt;I had the data the whole time in New Relic, I should've prepare better &lt;/li&gt;
&lt;li&gt;It's not bad to be explicit in code, not everything should be an abstraction, generalization, metaprogramming, ... &lt;em&gt;Hope to write on this point a bit more soon&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good thing in this story is that we didn't actually do any damage with this and we didn't lose a lot of time. &lt;/p&gt;

&lt;p&gt;Anyone faced similar problems? If yes, how did you deal with them?&lt;/p&gt;

</description>
      <category>api</category>
      <category>rails</category>
      <category>development</category>
      <category>programming</category>
    </item>
    <item>
      <title>Custom Fields: Give Your Customers the Fields They Need</title>
      <dc:creator>Nikola Buhinicek</dc:creator>
      <pubDate>Wed, 06 Dec 2023 10:20:00 +0000</pubDate>
      <link>https://dev.to/productive/custom-fields-give-your-customers-the-fields-they-need-41pi</link>
      <guid>https://dev.to/productive/custom-fields-give-your-customers-the-fields-they-need-41pi</guid>
      <description>&lt;p&gt;Here at Productive, &lt;strong&gt;we’re building an operating system for digital agencies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But, because each agency is different (think type, size, services they offer, the way they’re set up as an organization…), they need customization options for their workflows. So it’s pretty hard to model all those needs and use cases through a unified data model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If only there were a way to let them shape those models to their own needs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvm0ccd18zdv5n5yq5fm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvm0ccd18zdv5n5yq5fm.png" alt="Customer feature requests" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s say that one of our customers, &lt;em&gt;ACME digital agency&lt;/em&gt; wants to keep track of their employees’ nicknames and to be able to search them by that field. Other than that, they would also like to keep track of their birthdays and be able to sort them and group them by that date.&lt;/p&gt;

&lt;p&gt;To me, as a developer, this sounds as simple as it gets—add two new columns to the &lt;code&gt;people&lt;/code&gt; table, open those attributes to be editable over the API and send them back in the response.&lt;br&gt;
But should we do that? Should we add all kinds of fields to our models even if those fields are going to be used only by a handful of our customers?&lt;/p&gt;

&lt;p&gt;Let me show you how we tackled this type of feature request and made a pretty generic system around it.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Did Our Customers Want?
&lt;/h3&gt;

&lt;p&gt;It was pretty clear to us what our customers wanted, and that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to be able to &lt;strong&gt;add additional fields&lt;/strong&gt; to some of our models (People, Projects, Tasks, …)&lt;/li&gt;
&lt;li&gt;to have &lt;strong&gt;various data types on those fields&lt;/strong&gt; (text, number, or date)&lt;/li&gt;
&lt;li&gt;to be able to &lt;strong&gt;search, sort, or even group&lt;/strong&gt; by those fields&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Our Approach
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Custom Field Model
&lt;/h3&gt;

&lt;p&gt;As we’re building a RESTful &lt;a href="https://developer.productive.io/"&gt;API&lt;/a&gt; that’s formatted by the &lt;a href="https://jsonapi.org/"&gt;JSON:API specification&lt;/a&gt; and store our data in a &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/"&gt;MySQL8&lt;/a&gt; relational database, a few things were pretty straightforward – we need a new model and we’ll name it &lt;strong&gt;Custom Field&lt;/strong&gt; (naming wasn’t an issue here 🥲).&lt;/p&gt;

&lt;p&gt;The main attributes of that model should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="n"&gt;data_type_id&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;integer&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="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;enumeration&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;described&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
&lt;span class="n"&gt;customizable_type&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; 
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tells&lt;/span&gt; &lt;span class="n"&gt;us&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;custom&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;could&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;ve&lt;/span&gt; &lt;span class="n"&gt;also&lt;/span&gt; &lt;span class="n"&gt;been&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;enumeration&lt;/span&gt; &lt;span class="err"&gt;😅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How To Store the Field Values?
&lt;/h3&gt;

&lt;p&gt;OK, so now that we know how to define custom fields, how can we know which value someone assigned to a custom field for some object? And where to store that information?&lt;/p&gt;

&lt;p&gt;Three possible solutions came to mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add a limited number of &lt;em&gt;custom_field&lt;/em&gt; columns to our models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can add a few &lt;code&gt;custom_field&lt;/code&gt; columns to our models and that will work for some of our customers but there will always be others that need few extra fields. Adding numerous columns to our models surely isn’t the best solution, we can do better than this 😅&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add a join table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As mentioned before, while relying on a relational database, a join table sounds like the go-to approach. That table would be a simple join table between the custom field and a polymorphic target (yay, Rails 🥳). Other than those foreign keys, we would have a column to store the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add a single JSON column to our models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sounded as flexible as it gets. It would be a simple map where the key would be the custom field ID and the value would be the assigned value for that custom field.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8izj6admv3lmmufdpt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8izj6admv3lmmufdpt1.png" alt="Solutions" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why We Ended Up Choosing JSON
&lt;/h3&gt;

&lt;p&gt;The first solution was just too limited so we discarded that one immediately and focused on the remaining two solutions.&lt;/p&gt;

&lt;p&gt;On one hand, a better design would be to have the custom field values represented by a model but on the other hand, we won’t actually do much with that data. That would just be data that our users set on our objects, data that isn’t important for our business logic. So a simple JSON column didn’t sound bad either.&lt;/p&gt;

&lt;p&gt;The searching and sorting aspect of this feature request was probably the most important one for us. That was supposed to work as fast as it gets, without being a burden to our performance.&lt;/p&gt;

&lt;p&gt;That’s why we implemented both solutions, tested a lot of searching/sorting/grouping scenarios (we’ll go through that in more detail soon), and then checked the metrics.&lt;/p&gt;

&lt;p&gt;The faster solution was the second one, the one with the JSON column, and that made sense to us. That solution doesn’t use &lt;code&gt;JOIN&lt;/code&gt; clauses in SQL since the values are written directly in the searched table and can be queried in the &lt;code&gt;WHERE&lt;/code&gt; clause. Luckily for us, MySQL8 supports a bunch of &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/json-functions.html"&gt;great functions to work with JSON columns&lt;/a&gt; (&lt;code&gt;JSON_EXTRACT&lt;/code&gt;, &lt;code&gt;JSON_UNQUOTE&lt;/code&gt;, &lt;code&gt;JSON_CONTAINS&lt;/code&gt; and others).&lt;/p&gt;
&lt;h3&gt;
  
  
  Great! Now that we know how to store the custom field values too, let’s dig into the coding.
&lt;/h3&gt;

&lt;p&gt;From a development point of view, we did the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added a new model, &lt;strong&gt;Custom Field&lt;/strong&gt;, and implemented CRUD operations that can be called over the API&lt;/li&gt;
&lt;li&gt;Wrote schema migrations that added a JSON column – &lt;code&gt;custom_fields&lt;/code&gt; – to some of our models (people, projects, tasks, …)&lt;/li&gt;
&lt;li&gt;Opened the &lt;code&gt;custom_fields&lt;/code&gt; attribute so it can be edited over the API&lt;/li&gt;
&lt;li&gt;Wrote a generic validation that checks if all the values in the &lt;code&gt;custom_fields&lt;/code&gt; hash have the appropriate data type&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;custom_fields&lt;/code&gt; attribute to the API response of the appropriate models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was most of the work we needed to do to be able to manage custom fields in our models.&lt;/p&gt;

&lt;p&gt;But…what about the searching and sorting aspect of custom fields?&lt;/p&gt;
&lt;h3&gt;
  
  
  Searching Through Custom Field Values
&lt;/h3&gt;

&lt;p&gt;We already had a generic solution written for &lt;a href="https://developer.productive.io/index.html#header-filtering"&gt;searching over the API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We have a format of sending query params for searching, like &lt;code&gt;filter[attribute][operation]=value&lt;/code&gt;. For searching through custom fields, we wanted to keep the same format so we ended with a quite similar one – &lt;code&gt;filter[custom_fields][custom_field_id][operation]=value&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We had to add an if-else statement that would handle the custom fields filtering in a different way than filtering through other attributes as the format contained one additional argument — &lt;code&gt;custom_field_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What was different in the filtering logic was that we have to load the custom field that’s being filtered by and check what data type its values are. That’s needed to cast the values into numbers or dates—text values don’t make a difference.&lt;/p&gt;

&lt;p&gt;So the query params and its SQL query counterparts, based on custom field type, would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* for a text custom field and query filter[custom_fields][1][eq]=abc */&lt;/span&gt;
&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%abc%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a number custom field and query filter[custom_fields][2][eq]=42 */&lt;/span&gt;
&lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."2"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a date custom field and query filter[custom_fields][3][eq]=2022-11-01 */&lt;/span&gt;
&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."3"'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2022-11-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* The -&amp;gt;&amp;gt; operator is an alias for JSON_UNQUOTE(JSON_EXTRACT(...)) */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sorting by Custom Field Values
&lt;/h3&gt;

&lt;p&gt;The concept of &lt;a href="https://developer.productive.io/index.html#header-sorting"&gt;sorting&lt;/a&gt; by attributes is something we also already tackled by abstracting logic.&lt;/p&gt;

&lt;p&gt;The only thing that changes when sorting by custom fields is that we first need to cast the values and then sort by them.&lt;/p&gt;

&lt;p&gt;Once again, there’s a small change in the format for custom fields sorters (&lt;code&gt;sort=custom_fields[custom_field_id]&lt;/code&gt;) compared to when sorting by a standard attribute (&lt;code&gt;sort=attribute&lt;/code&gt;). We need to handle the custom_fields sorters separately because we have to load the desired custom_field and check its type.&lt;/p&gt;

&lt;p&gt;Then the &lt;code&gt;ORDER BY&lt;/code&gt; statement, based on custom field types, looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* for a text custom field and query sort=custom_fields[1] */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="cm"&gt;/* for a number custom field and query sort=-custom_fields[2]
Noticed the - on the begining? That defines the order of sorting - DESC or ASC */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."2"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* for a date custom field and query sort=custom_fields[3] */&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."3"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grouping by Custom Field Values
&lt;/h3&gt;

&lt;p&gt;This was a fun one. The main point here was that you should include the custom fields as some kind of columns stated in the &lt;code&gt;SELECT&lt;/code&gt; statement so that you could later use those columns in the &lt;code&gt;GROUP BY&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;To get the custom field in the &lt;code&gt;SELECT&lt;/code&gt; statement, you have to create a virtual column for it. All we needed to do was to extract the values of the grouped custom field and give that virtual column an alias so that we could reference it in the &lt;code&gt;GROUP BY&lt;/code&gt; statement. For the column alias we went with the format &lt;code&gt;custom_fields_{custom_field_id}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a custom field with &lt;code&gt;id=x&lt;/code&gt;, this is done as following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."x"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;custom_fields_x&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have the virtual column defined, the grouping part gets done simply, by adding the &lt;code&gt;GROUP BY&lt;/code&gt; statement with the earlier mentioned alias.&lt;/p&gt;

&lt;p&gt;So in the end, you get a &lt;code&gt;SQL&lt;/code&gt; query like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* query group=custom_fields[1] */&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;custom_fields&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'$."1"'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;custom_fields_1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;custom_fields_1&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Our Customers Got
&lt;/h3&gt;

&lt;p&gt;A simple way to define Custom Fields:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguvx6aizi5m9xhrs3qwo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguvx6aizi5m9xhrs3qwo.png" alt="Custom fields UI" width="800" height="378"&gt;&lt;/a&gt;&lt;br&gt;
And a place to assign values to their fields:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oyesva4yxmac6r9w50l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2oyesva4yxmac6r9w50l.png" alt="Assigning custom fields" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summa Summarum
&lt;/h2&gt;

&lt;p&gt;We made it possible for our customers to define custom fields in our data models. Also, we made it possible to search, sort and group by those fields.&lt;br&gt;
It wasn’t long before we had even more requests that built upon our custom fields architecture. The fields we supported at first were okay, but now our customers wanted more field types. They wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to have dropdown custom fields&lt;/li&gt;
&lt;li&gt;to have relational custom fields&lt;/li&gt;
&lt;li&gt;a field where the values would be objects from one of our existing data models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But before we dig into that, let’s give some time for this basics to sink in. I’ll be back soon with another blog post in which I cover how we solved that new set of feature requests&lt;/p&gt;

</description>
      <category>rails</category>
      <category>development</category>
      <category>programming</category>
      <category>buildingproductive</category>
    </item>
    <item>
      <title>Decoupling Deployment From Release With Feature Toggles</title>
      <dc:creator>Davor Tvorić</dc:creator>
      <pubDate>Wed, 06 Dec 2023 09:47:41 +0000</pubDate>
      <link>https://dev.to/productive/decoupling-deployment-from-release-with-feature-toggles-7lo</link>
      <guid>https://dev.to/productive/decoupling-deployment-from-release-with-feature-toggles-7lo</guid>
      <description>&lt;p&gt;Deploying new features is a nerve-racking task, &lt;strong&gt;but it must be done&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an issue we’ve all faced or will inevitably face in our careers. Allow me to set the scene…&lt;/p&gt;

&lt;p&gt;You’ve finished your work on some features, fixed some bugs, etc. You’re feeling good because some features were a bit more complicated. You decide it’s high time to let your users experience these changes. You bundle up all your teams’ changes and deploy them to the production server. A couple of hours later, disaster strikes! There’s a critical bug on a feature you’ve worked on. Now, you have to revert to the old version of the application or deploy a new one, but without your changes.&lt;/p&gt;

&lt;p&gt;As these situations can have a huge impact, reacting quickly is key. Unfortunately, developers usually learn how to handle this situation once it happens, so it only prolongs the agony.&lt;/p&gt;

&lt;p&gt;Deploying and releasing at the same time isn’t such an uncommon practice because, at the beginning of a project, you want your users to see the changes immediately. Many projects continue using this process, but as the project grows, it makes sense to decouple the deployment of the code and the release process (activating changes to users).&lt;/p&gt;

&lt;p&gt;Even if this hasn’t happened to you yet, we can all agree that it’s a very stressful situation, and establishing a process for handling this is something you want to do ahead of time. &lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;how much&lt;/strong&gt; ahead of time are we talking about here?&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%2Fsyqbgtbf0icop8zojak7.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%2Fsyqbgtbf0icop8zojak7.png" alt="An image of Batman thinking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature toggles (aka feature flags)
&lt;/h2&gt;

&lt;p&gt;There are many ways to handle these types of outages, and it’s important to establish a rollback procedure. In addition to that, we also use something called &lt;strong&gt;feature toggles&lt;/strong&gt;. It’s not something unique to us, but it’s a pretty sweet thing to have in your toolbox. For those unfamiliar with feature toggles, it’s a mechanism that allows you to turn a feature on or off for a user &lt;strong&gt;without needing to deploy the whole application&lt;/strong&gt;. It’s really simple, but it’s huge!&lt;/p&gt;

&lt;p&gt;Although it’s called a “feature toggle”, it doesn’t necessarily mean that you have to use them for features only. You can use them basically for anything. Whether it’s A/B testing, refactoring a part of the screen, or navigating a user to a different page entirely! I’m sure you can think of a couple of more examples that would be useful to you.&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%2Fbghum3mrk19rx7j4dza7.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%2Fbghum3mrk19rx7j4dza7.png" alt="An example of some feature toggles in Productive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This also allows us to oversee which users can access a certain change. This is helpful when trying to collect feedback or just observing how users interact with something you’ve developed. Once you’ve established your feature toggle system, you don’t need to put in continuous work to keep it going. It’s also a great way to manage the lifecycle of the change by progressively rolling it out to your canary, beta, and all users! &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%2Fu6zbx5fajn9uu8xxlj1v.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%2Fu6zbx5fajn9uu8xxlj1v.png" alt="An image that represents a possible segmentation of users, ranging from internal users, beta release, early adopters and all users"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You gain a lot more control over who sees what, and the best part is that turning toggles on or off can be done by anyone! Someone like a product manager or customer support person could decide to turn a toggle on because it’s just something that the customer needs. After a feature has been fully developed, the toggle can be globally released to all customers, and everyone can enjoy the stable version.&lt;/p&gt;

&lt;p&gt;The number of annoyed users will decrease, and valuable feedback on changes will increase. &lt;/p&gt;

&lt;p&gt;It’s also worth mentioning that you can base your whole QA process on the toggles. We can test a change by turning a toggle on, gathering feedback, and then turning the toggle off. When you have such fine-grained control, testing on production isn’t a big deal! We also use these toggles in other environments, so you can isolate a change to a specific set of users on any server.&lt;/p&gt;

&lt;p&gt;It’s a win-win for everyone!&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, but what about development?
&lt;/h2&gt;

&lt;p&gt;The main question that’s probably going around in your head is how the feature toggles affect the development of a feature.&lt;/p&gt;

&lt;p&gt;Feature toggles do complicate things a bit when writing code, but if you spend some time on planning, it can be done elegantly in most cases. You must be careful to add every change behind the toggle so nothing leaks when the toggle isn’t turned on. Other people working on the feature must also be aware of using the toggle since it probably will span through multiple files and can sometimes be forgotten.&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%2F8zs94wzodwqkm4vjif5t.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%2F8zs94wzodwqkm4vjif5t.png" alt="A simple code example that adds a calculates a currency conversion on a cost if a toggle is turned on"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, it’s not that complicated, but you gain a lot. As I’ve already mentioned, it can be used for basically anything, and complexity may increase, but it really is worth it:&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%2F8owslxug3cic5fonqjws.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%2F8owslxug3cic5fonqjws.png" alt="An image with two code examples, one showing a possible handling for refactoring and another one navigating to another screen based on a feature toggle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The added complexity isn’t such a big drawback. You’re getting a lot more out of feature toggles, and if that means that developers have to be a bit more careful when writing and reviewing code, so be it. It’s much less stressful for a developer when they know a feature can be turned off if it’s causing problems. This doesn’t mean we approve of sloppy code, but sometimes, these things happen.&lt;/p&gt;

&lt;p&gt;To conclude, feature toggles (once established) don’t affect the development process as much, but they offer a lot in return.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alleviating the downsides
&lt;/h2&gt;

&lt;p&gt;The first one does introduce technical debt, but if we create a task for deleting a toggle after it’s been globally released, we can be sure that it will be deleted as soon as possible. The person who created the toggle is responsible for deleting it, which is a great way to handle this efficiently. In any case, deleting a toggle usually takes significantly less time than developing it!&lt;/p&gt;

&lt;p&gt;As for the other issue, there are two ways to handle this. We can do either of these two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Release the first toggle globally and delete its references in the code if the feature is ready to be released or&lt;/li&gt;
&lt;li&gt;Merge the toggles and consider them to be the same feature from now on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not an ideal situation, but it can happen. The solution mostly depends on the state of the features, so it’s impossible to determine the best way without knowing all the facts. Either way, both solutions can simplify things for development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decisions, decisions
&lt;/h2&gt;

&lt;p&gt;For us, implementing feature toggles was a no-brainer. Considering all of the benefits like the control, progressive rollouts, and others, it seriously outweighs the downsides it introduces. Even the downsides can be managed to an extent.&lt;/p&gt;

&lt;p&gt;It’s important that &lt;strong&gt;everyone&lt;/strong&gt; in the company understands the whole process and embraces why we’re doing it in such a way. It’s an essential part of a company like ours since every department is involved in the release process of product updates. Once people are aware of this, building an admin system to control this is up to you and your company’s needs. &lt;/p&gt;

&lt;p&gt;Here’s an example of our own. We call it the “Backoffice”:&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%2Ft3gjnv4pmwapdn6zi7xf.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%2Ft3gjnv4pmwapdn6zi7xf.png" alt="An example of our "&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;THERE ARE ~200 ACTIVE FEATURE TOGGLES IN OUR CODEBASE&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a developer, I’m grateful we can release features quickly and stress-free, knowing we have some wiggle room.&lt;/p&gt;

&lt;p&gt;As for you, I hope I’ve shown you why it’s a good idea to think about something like this if you don’t use a similar mechanism. If you need a bit more convincing, you can read more about it in &lt;a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer"&gt;an article by Martin Fowler&lt;/a&gt;! Even if you’re not in a position to make your feature toggle system, there are a lot of external tools and libraries that might help you implement the same!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>buildingproductive</category>
      <category>deployment</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
