<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Tuomas Kareinen&apos;s blog</title>
  <link href="https://tkareine.org/atom.xml" rel="self"/>
  <link href="https://tkareine.org/"/>
  <updated>2026-03-12T17:54:59+02:00</updated>
  <id>https://tkareine.org/</id>
  <author>
    <name>Tuomas Kareinen</name>
  </author>

  <entry>
    <title type="html">Exclusive scheduled jobs using database locks</title>
    <link href="https://tkareine.org/articles/exclusive-scheduled-jobs-using-db-locks.html"/>
    <updated>2026-03-10T22:15:00+02:00</updated>
    <id>https://tkareine.org/articles/exclusive-scheduled-jobs-using-db-locks</id>
    <content type="html">&lt;p&gt;Using locks implemented as rows in an SQL database enables running scheduled background jobs in an application, providing a best-effort guarantee that only one application instance at a time runs a particular job. This is one possible solution, and it’s an appealing one because an SQL database usually serves as the primary database for the application – you don’t need any additional infrastructure services. The implementation achieves fault tolerance and is easy to understand and operate, but sacrifices load balancing as a trade-off. I describe how to do it and provide SQL operations as examples.&lt;/p&gt;

&lt;p&gt;Before I go into the solution, it’s worth emphasizing that the characteristics and requirements of running background jobs should drive any design. There are surprisingly many aspects to think about. Among the considerations are how jobs get created (are they triggered by events or a schedule), whether the system should attempt to run a particular job only once, need for fault tolerance, load balancing, and scalability; followed by computing resource requirements, and whether it is acceptable to share the computing resources with the application’s primary workload. See the &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/well-architected/design-guides/background-jobs&quot;&gt;Background jobs&lt;/a&gt; section of Microsoft Azure Well-Architected Framework for a good overview.&lt;/p&gt;

&lt;h2 id=&quot;exclusive-job-implementation&quot;&gt;Exclusive job implementation&lt;/h2&gt;

&lt;p&gt;The concept of an &lt;em&gt;exclusive job&lt;/em&gt; models the permission to run a particular job on only one application instance. I represent the model as a record having the following fields:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt;: Identifies a particular job. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DeleteOldTransactionIds&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CheckRemoteServiceHealth&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_instance_id&lt;/code&gt;: A static identifier of the application instance. An application instance can simply use a static UUID generated in memory at instance startup for all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt;s.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock_expires_at&lt;/code&gt;: A timestamp in the future indicating when an acquired lock can be treated as expired. The value is calculated by the application. I’ll describe its meaning shortly.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A scheduler triggers all the application instances to compete for the permission to run a certain job. The application relies on the database to provide atomic operations so that only one instance may insert or update a row for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exclusive_job&lt;/code&gt; table stores the rows. Here is the schema for PostgreSQL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exclusive_job&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;check&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;char_length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;between&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamptz&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The application defines the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; operation, which performs the following query to either acquire (insert row) or conditionally update the lock (update existing row). I use named parameters (such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$job_id&lt;/code&gt;) instead of positional parameters for easier reading (this is invalid SQL syntax as PostgreSQL allows positional parameters only):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exclusive_job&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ej&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conflict&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;update&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;excluded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;excluded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ej&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;or&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ej&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;excluded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ej&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;excluded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As said, a scheduler triggers the application code to run a job identified by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt; for each application instance at approximately the same time. The instances compete to acquire the lock for the job using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; operation. The first instance to execute the query will win. The row count of the query’s result set signals either winning the lock (count &amp;gt; 0) or losing the lock (count = 0). The instance that has acquired the lock gets the permission to run the job; other instances back off.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock_expires_at&lt;/code&gt; column gives the ability to run the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt; again in the next scheduled trigger. By relying on scheduled triggers and expiring locks, the design attains fault tolerance. The clocks of the application instances must be synchronized to make this work, but a small clock skew is tolerable.&lt;/p&gt;

&lt;p&gt;A lock expiry value should be large enough that all application instances have a window to compete for the lock simultaneously, and that the winning instance has sufficient time to complete the job before expiration. On the other hand, the value should be small enough to allow the instances to compete again in the next scheduled trigger. Getting lock expiry right is the delicate part of this design. For example, if a job takes at most a minute to complete and you schedule running the job once per hour, then a value of 5 minutes might be suitable.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; operation is reentrant, meaning the same application instance already holding the lock may acquire the lock again.&lt;/p&gt;

&lt;p&gt;Use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;updateLock&lt;/code&gt; operation to guard against overlapping executions of the same long-running job. Overlapping might happen when the initial expiry time is too small compared to the amount of work anticipated: usually the job might take a few minutes to complete, but sometimes there’s so much work that the job is still running when it’s time to trigger the next scheduled run. In that case, the application should split the job into parts and update the expiry time just before running each part. Here’s the SQL query for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;updateLock&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exclusive_job&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lock_expires_at&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The query allows only the application instance that has acquired the lock to update the expiry time. A positive row count of the result set indicates if the update was applied. Note that the query allows the instance holding the lock to update the lock even if the lock is already expired.&lt;/p&gt;

&lt;p&gt;An application instance that fails applying the update implies a problem where the lock has been expired and another instance has acquired it. This might be a symptom of using too small a value for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock_expires_at&lt;/code&gt;. I recommend aborting the job in a fail-fast manner if that happens.&lt;/p&gt;

&lt;p&gt;One could also define the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;releaseLock&lt;/code&gt; operation for the application instance holding the lock to release the lock explicitly:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exclusive_job&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job_instance_id&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;But using it would be safe only if you can guarantee that there cannot be other application instances still competing to acquire the lock for the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job_id&lt;/code&gt; in the same scheduled moment. An example scenario to avoid would be the following: instance A acquires a job’s lock, completes the job quickly, and releases the lock; this is followed by instance B acquiring the lock for the same job. Now instance B would run the same job again. Instead, it’s safer to just let the lock expire.&lt;/p&gt;

&lt;p&gt;Each of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;updateLock&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;releaseLock&lt;/code&gt; operations must be wrapped in a dedicated database transaction to obtain exclusive access to the guarded job. Don’t include other database queries inside those transactions.&lt;/p&gt;

&lt;h2 id=&quot;intended-usage-scenario&quot;&gt;Intended usage scenario&lt;/h2&gt;

&lt;p&gt;The implementation is designed for relatively lightweight jobs triggered by a scheduler. Each job should move affected state toward the desired state (eventual consistency and idempotent operations). The database manages state, making it easy to replicate the application to have many instances. You might run the application as a Deployment in Kubernetes, for example.&lt;/p&gt;

&lt;h2 id=&quot;design-trade-offs&quot;&gt;Design trade-offs&lt;/h2&gt;

&lt;p&gt;For design trade-offs, you lose the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Cannot guarantee that exactly one application instance runs a job per trigger. After acquiring a lock, but just before executing the job, an instance may get paused long enough for the lock to expire and another instance to acquire the lock, resulting in running the job twice. A stop-the-world pause by a garbage collector is one example. See &lt;a href=&quot;https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html&quot;&gt;How to Do Distributed Locking&lt;/a&gt; by Martin Kleppmann for an example and more interesting details. This is acceptable because the design described here uses locking as an efficiency optimization, not for correctness.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;No load balancing. There’s no mechanism to distribute load intelligently among application instances. But you can distribute jobs randomly by delaying the call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; with a small random duration. This also protects against the application instance having the greatest positive clock skew from always acquiring the lock.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The application runs background jobs alongside its primary workload. This resource sharing might harm the availability of the application.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You cannot scale resources for different background job types. The reason is the same as for not having load balancing.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You need to synchronize the clocks for all application instances. This shouldn’t be a big problem in practice by using NTP.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;It won’t work with event triggers. Fault tolerance relies on periodically repeated triggers.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You gain the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Best-effort job exclusivity as an efficiency optimization. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; operation ensures that only one application instance at a time may acquire a lock for a job. Connection pool usage does not affect lock handling.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; Using a large enough lock expiry time, the instance holding the lock should have time enough to complete the job for the common case. The nature of the job should allow running it many times, possibly concurrently, in the worst case.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fault tolerance: if an application instance terminates ungracefully while running a job, the lock will expire eventually and another instance will retry the job the next time the job gets triggered.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Easy to understand and to operate: the implementation uses one SQL table for storing state. If something goes wrong in operations, you can either delete the contents of the table or just let the locks expire.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I believe the exclusive jobs described here are interesting for simple background jobs that need to be repeated. Examples include cleanups, storing the health-check results of remote services in the database, and propagating state from one application to another in an eventually consistent fashion.&lt;/p&gt;

&lt;p&gt;You get quite a lot for one SQL table and some application code around it. The accidental complexity of this approach is low.&lt;/p&gt;

&lt;p&gt;I’m sure what I’ve presented is one variation of many similar tried-and-true solutions. My motivation was to document this particular variation, as I haven’t found any articles describing something like it.&lt;/p&gt;

&lt;p&gt;Finally, I’ve talked about the required atomic operations in the context of SQL databases, but there’s actually nothing specific to SQL here. For example, the design can be implemented on a document-oriented database, such as MongoDB. Further, the update functionality of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tryAcquireLock&lt;/code&gt; can be removed if the database supports deleting entries automatically upon lock expiry (see &lt;a href=&quot;https://www.mongodb.com/docs/manual/core/index-ttl/&quot;&gt;TTL indexes&lt;/a&gt; for MongoDB). So you have even more options for applying the design!&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Specifically, there’s no need for the &lt;a href=&quot;https://www.postgresql.org/docs/18/explicit-locking.html#ADVISORY-LOCKS&quot;&gt;advisory locks&lt;/a&gt; of PostgreSQL. Using an advisory lock on the session level can cause problems with a connection pool. For example, if the application got connection A from the pool to acquire the advisory lock, it wouldn’t be able to update the lock if it got connection B from the pool. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Suppressing duplicate requests in web services</title>
    <link href="https://tkareine.org/articles/suppressing-duplicate-requests-in-web-services.html"/>
    <updated>2021-12-24T11:50:00+02:00</updated>
    <id>https://tkareine.org/articles/suppressing-duplicate-requests-in-web-services</id>
    <content type="html">&lt;p&gt;Suppressing duplicate request processing prevents some forms of data
corruption, such as data duplication and lost data. Achieving
suppression together with client retries effectively establishes
&lt;em&gt;exactly-once&lt;/em&gt; request processing semantics in the system.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; In this
article, I present an imaginary web service built on the microservice
style design, inspect that and its clients together as a system, define
the duplicate request processing problem, and the general solution to
it. Finally, I’ll show one possible way to implement the solution.&lt;/p&gt;

&lt;p&gt;The microservices involved use synchronous requests to pull and push
data so that any part of the overall state is managed centrally in one
place (the conventional approach to microservices communicating with the
REST/RPC/GraphQL protocols).&lt;/p&gt;

&lt;h2 id=&quot;web-service-as-a-system&quot;&gt;Web service as a system&lt;/h2&gt;

&lt;p&gt;The imaginary web service manages education related entities: students,
employees, facilities, and so on.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; Typical management operations are
creating, reading, updating, and deleting entities. Here, we focus on
employees, their employment contracts, and related information.
Collectively, we’ll call those as “staff” entities and dedicate an
application named “Staff service” for processing them. There’s also an
identity provider (IdP) service that is used to authenticate both
students and employees. Because the IdP is provided as an external
service, we have another application, called “Users service”, that maps
our user identifiers to the IdP’s user identifiers. Finally, an API
gateway node serves as the reverse HTTP proxy for all inbound traffic to
APIs.&lt;/p&gt;

&lt;p&gt;Here’s a diagram of the web service from the viewpoint of the Staff API:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/imaginary-web-service-components-5fde4a03d3c18420849fea54f32802c6.svg&quot; alt=&quot;An imaginary web service comprising of the client, three internal services, and one external service&quot; title=&quot;An imaginary web service&quot; width=&quot;100%&quot; style=&quot;max-width: 500px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Because we need to allow employees to login to the service, the Staff
service needs to associate a user entity for an employee. This happens
by calling the API of the Users service, which hides the complexity of
the IdP’s User management API.&lt;/p&gt;

&lt;p&gt;Looking at the diagram, we can identify the following components and
group them:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The servers behind the public APIs: the API gateway, the IdP service,
microservices, and databases.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The clients accessing the public APIs: browsers running webapps and
integration clients that synchronize education related entities
between this system and another.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Network components: the internet where the clients connect from, the
private network of the web service, and virtual networks within the
hosts that run microservices inside containers.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The operations of the client-server APIs the microservers expose both
internally and externally can be grouped into:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Queries, which are for reading data. A query request does not inflict
any externally visible change to state of the server. Examples are
the HTTP GET and HEAD methods and the query operations of GraphQL.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Mutation, which are for writing data and triggering effects. A
mutation request causes externally visible change to the state of the
server. Examples are the HTTP POST/PUT/PATCH/DELETE methods and the
mutation operations of GraphQL.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Considering some of the typical technologies we use building web
services like this, we’ll likely use TCP as the connection-oriented
protocol for transferring data between hosts. When two hosts have agreed
to establish a TCP session, the protocol protects against packet
duplication with sequence numbers and acknowledgements, and data
integrity with checksums on IP packets. But a TCP session protects data
transfer only between two hosts. For instance, creating a new employee
involves adding a new user entity to the IdP service. When looking at
the communication path of that API operation, there will be four
separate TCP sessions (the numbers in red circles in the previous
diagram):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Between the browser and the API gateway: call the public Staff API to
create an employee&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Between the API gateway and the Staff service: forward the call to
the microservice&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Between the Staff service and the Users service: call the Users API
to create a new user entity to associate with the employee&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Between the Users service and the IdP service: call the User
management API to create the user entity to the IdP in order to allow
the employee to login, and provide user identifier mapping between
systems&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Another technology in general use is database transactions, especially
for SQL databases which usually come with the &lt;a href=&quot;https://en.wikipedia.org/wiki/ACID&quot;&gt;ACID&lt;/a&gt; properties. A
connection from the application server to the database sits on top of
TCP usually, and the database server guarantees that if the transaction
commits successfully, the app’s modifications to the data leave the
database in a consistent state. It’s another safeguard against data
corruption, but again between two components only. The creation of a new
employee in our web service involves two SQL transactions (the letters
in gray circles in the diagram above):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Staff service: add a row for the new employee&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Users service: add a row for the new user&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Turns out that any technology protecting only parts of the whole
communications path is not sufficient in protecting the whole
path. Let’s look at some possible problems.&lt;/p&gt;

&lt;h2 id=&quot;examples-of-problems-caused-by-not-protecting-the-whole-communications-path&quot;&gt;Examples of problems caused by not protecting the whole communications path&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Broken data integrity&lt;/em&gt;: Even though a TCP session uses checksums to
ensure two hosts transfer data unchanged over the communications
channel, it does not guard the application server from reading data
received or writing data to be sent via malfunctioning hardware
memory. Data corruption can occur.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Broken data confidentiality&lt;/em&gt;: A client that serves to integrate an
external and our imaginary web service sends the login password of the
employee along with the data in the request to create the employee to
the Staff API. TLS does protect the communication channel between any
two hosts with encryption, but it does not prevent the application
server from reading the password in clear text. Any process in the
server can read the password, actually.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Broken duplicate request processing suppression&lt;/em&gt;: A client requesting
to create a new employee using the Staff API encounters either a timeout
or receives a timeout related error response. What happens if the client
attempts to send the request again? From the client’s perspective, any
of the following might have happened to the original request:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The API gateway received the request, but the gateway crashed and
never sent the response to the client. The gateway might or might not
have forwarded the request to the Staff service before crashing.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The API gateway received the request, but the Staff service is down,
not accepting connections. The timeout for the expectation to receive
data in the client is lower than in the API gateway for the overall
connection attempts to the Staff service, and so the client closes
this request attempt.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The Staff service received the request and used an SQL transaction to
encompass sending its own request to the Users API for creating the
associated user entity. The Staff service received the success
response from the Users service, updated the employee row, and
committed the SQL transaction. But the Staff service crashed just
before responding back to the API gateway. Eventually, the gateway
times out the connection to the Staff service and sends a &lt;em&gt;504
Gateway Timeout&lt;/em&gt; error response to the client.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Like previously, but just after opening the encompassing SQL
transaction, the Staff service enters stop-the-world garbage
collection phase, which effectively pauses the whole service. This
makes the API gateway respond with &lt;em&gt;504 Gateway Timeout&lt;/em&gt; to the
client. After the garbage collection phase is over, the service
continues processing like nothing would have happened.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Like cases 2, 3, or 4, but it was the Users service that failed.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the five situations above are different forms of timeouts. In cases
3 and 4, the request was processed completely, but the client does not
get to know about it. If the client retries the original request, there
could be 0, 1, or 2 employees in the system. Here we presume, for the
sake of general argument, that the employee data the client sends does
not contain data that has uniqueness constraints (the username attribute
might be such, for example). It’s clear that TCP’s data correctness
mechanisms alone cannot guarantee that a request traversed over many
hops would be processed only once.&lt;/p&gt;

&lt;p&gt;In case 5, the system was left in an illegal state: there’s a user
entity in the IdP and a corresponding identifier mapping in the Users
service, but no associated employee entity in the Staff service. This
demonstrates that database transactions alone cannot guarantee that the
overall system was left in correct state.&lt;/p&gt;

&lt;p&gt;Both TCP and database transactions helped to ensure data correctness
between two components, but they didn’t guarantee that the overall
system was left in correct state.&lt;/p&gt;

&lt;p&gt;Even though I’m focusing on the duplicate request processing suppression
problem in this article, the general solution to all of them is the
same.&lt;/p&gt;

&lt;h2 id=&quot;the-end-to-end-argument&quot;&gt;The end-to-end argument&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;end-to-end argument&lt;/em&gt; is a design principle that guides where to
locate the implementation of a &lt;em&gt;function&lt;/em&gt; for the benefit of a
distributed system. The function in question can be duplicate message
suppression, data integrity, or data confidentiality, for
example. Saltzer, Reed, and Clark &lt;a href=&quot;https://web.mit.edu/Saltzer/www/publications/endtoend/endtoend.pdf&quot;&gt;articulated the
argument&lt;/a&gt; in 1981, and it goes as
follows:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The function in question can completely and correctly be implemented
only with the knowledge and help of the application standing at the
end points of the communication system. Therefore, providing that
questioned function as a feature of the communication system itself is
not possible. (Sometimes an incomplete version of the function
provided by the communication system may be useful as a performance
enhancement.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put other way, correct implementation of the function requires that the
client and the server at the ends of the communication path work
together in achieving the function.&lt;/p&gt;

&lt;p&gt;Going back to the earlier example problems, a way to guarantee data
integrity is to make the client to compute a hash over the request’s
payload data and to include the hash in the request. The application
servers, upon receiving the request, compute the hash and compare it to
the expected one in the request. If the computed hash equals the
expected hash, the server may process the request.&lt;/p&gt;

&lt;p&gt;Data confidentiality can be achieved by using end-to-end encryption.&lt;/p&gt;

&lt;p&gt;There’s no established way to suppress duplicate request processing. In
the &lt;a href=&quot;https://dataintensive.net/&quot;&gt;Designing Data-Intensive
Applications&lt;/a&gt; book, Martin
Kleppmann describes one approach. The system must be designed so that it
holds up exactly-once semantics for processing requests, and an
effective way to achieve this is to make operations &lt;em&gt;idempotent&lt;/em&gt;.&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Considering our earlier grouping of the operations of client-server APIs
into queries and mutations, we can ensure that queries are idempotent by
making sure they never affect state so that possible change in state is
visible to the client (for instance, request logging would be
permitted). Usually this is trivial to achieve with read database
queries if returning the data based on the current database state is
enough. This does forgo the ability for the client to request data about
the state in earlier moments, however; solving that would require
storing versioned data snapshots in the database.&lt;/p&gt;

&lt;p&gt;For mutations, Kleppmann proposes to include an operation identifier in
the request originating from the client. Upon receiving the request, the
server can query its database to see if an operation with this
identifier has been processed already. The server processes the request
only if there’s no existing row having the identifier. When processing
is about to finish, the server adds a row containing the identifier
indicating that the request has been completed. The operation identifier
can either be generated or derived from the input data, whichever is
more convenient for the business logic.&lt;/p&gt;

&lt;p&gt;Applying Kleppmann’s approach to suppress duplicate request processing,
in the context of the imaginary web service presented earlier, is the
last part of this article.&lt;/p&gt;

&lt;h2 id=&quot;applying-duplicate-request-suppression&quot;&gt;Applying duplicate request suppression&lt;/h2&gt;

&lt;p&gt;Let’s establish API design principles to support idempotency. I’ve
chosen to use GraphQL as the application-level protocol here, but the
principles are the same regardless of using another protocol, such as
REST or RPC.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;GraphQL query operations return the data based on the current state
of the server. It’s expected that a query with certain input
requested over time may return different data as output, reflecting
changes in the current state of the service by mutations.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;All GraphQL mutation operations must include operation identifier as
input, in a parameter called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt;. Two requests with the
same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value indicate that the requests are
duplicates. The client must generate the identifier as a random
&lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier&quot;&gt;UUIDv4&lt;/a&gt; value.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The server must apply the operation only once for a particular
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value, the first time the server receives a request
with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; it hasn’t processed yet.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The response to a GraphQL mutation operation with a particular
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value must always produce the same logical output. If
the server processed the mutation successfully, the response must
signal success for all requests having the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt;
value. Similarly, if the server completed processing with a failure,
all the responses to the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; must signal that
failure. In particular, a success response may contain output
reflecting the current state of the data, but that output might be
different when the client requests the same mutation again (another
mutation may have changed the data).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value must be passed as-is to dependent
services.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The principles apply to both public and internal APIs alike.&lt;/p&gt;

&lt;p&gt;I’ll go through the principles one-by-one, except for the first, which
should be self-sufficient.&lt;/p&gt;

&lt;p&gt;The 2nd principle enables distinguishing between two requests and to
tell whether they are for the same purpose, even if the input payload
would be otherwise be the same. This allows creating different user
entities sharing their name, for instance.&lt;/p&gt;

&lt;p&gt;As an example, here’s the GraphQL mutation to create a new employee in
the Staff API:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-graphql&quot; data-lang=&quot;graphql&quot;&gt;&lt;span class=&quot;k&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;createEmployee&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactionId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;addb372c-046f-43e8-c91f-1df1a30caaa1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Albert&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Wesker&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;employment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startsAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-08-12&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;personnelTypeCodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MANAGEMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# etc…&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# etc…&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The 3rd principle implements idempotency in the server logic, but it
isn’t enough for the client to implement retries for timed out
requests. That is covered by the 4th principle: it allows the client
retry the request until it gets to see the response.&lt;/p&gt;

&lt;p&gt;I think supporting client retries is one of the main selling points of
idempotency. It also explains why uniqueness constraints on entity
attributes are not enough to support duplicate request suppression. A
constraint on an attribute, such as username, does prevent clients from
creating duplicate user entities, but client retries are broken. The
following sequence diagram shows why:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/client-retries-when-only-uniqueness-constraint-4d432bd9c794001891ee1c176b26a501.svg&quot; alt=&quot;A sequence diagram showing how the client can receive an error when retrying a mutation operation&quot; title=&quot;A sequence diagram showing how the client can receive an error when retrying a mutation operation&quot; width=&quot;100%&quot; style=&quot;max-width: 550px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the diagram, the client requests creating a new employee with a
certain username. The service enforces that the username must be unique.
The request propagates via the API gateway to the application service,
and the service processes the request with success. But then the API
gateway crashes before it forwards the response to the client.
Eventually, the retry timeout in the client triggers and the client
sends the same request again. This time the client receives the
response, but it’s a failure: an employee with the supplied username
exists already. This is unexpected from the client’s perspective.&lt;/p&gt;

&lt;p&gt;An implementation of the 3rd and 4th principles in the server is an SQL
table for storing the outcomes of processed mutations. The database
schema could be like the following for &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction_operation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;CREATE_EMPLOYEE&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;UPDATE_EMPLOYEE&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;DELETE_EMPLOYEE&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;operation&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction_operation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jsonb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;created_at&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamptz&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;check&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_msg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here are some example rows to support further discussion:&lt;/p&gt;

&lt;div class=&quot;wide-table&quot;&gt;
  &lt;table class=&quot;code-table&quot;&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;id&lt;/th&gt;
        &lt;th&gt;operation&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt;error_msg&lt;/th&gt;
        &lt;th&gt;created_at&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;addb372c-046f-43e8-c91f-1df1a30caaa0&lt;/td&gt;
        &lt;td&gt;CREATE_EMPLOYEE&lt;/td&gt;
        &lt;td&gt;[&quot;549d9715-0949-4a57-b9fb-1c56eb8e5029&quot;]&lt;/td&gt;
        &lt;td /&gt;
        &lt;td&gt;2021-12-03 11:59:30.085 +0200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;abdb372c-026f-43e8-c91f-2df1b30d8aa1&lt;/td&gt;
        &lt;td&gt;UPDATE_EMPLOYEE&lt;/td&gt;
        &lt;td&gt;[&quot;549d9715-0949-4a57-b9fb-1c56eb8e5029&quot;]&lt;/td&gt;
        &lt;td /&gt;
        &lt;td&gt;2021-12-03 12:00:14.290 +0200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;abdb372c-026f-43e8-c91f-2df1b30d8aa2&lt;/td&gt;
        &lt;td&gt;UPDATE_EMPLOYEE&lt;/td&gt;
        &lt;td&gt;[&quot;549d9715-0949-4a57-b9fb-1c56eb8e5029&quot;]&lt;/td&gt;
        &lt;td&gt;invalid email&lt;/td&gt;
        &lt;td&gt;2021-12-03 12:03:43.110 +0200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11d36de7-0e36-475a-ae01-baa634010aa3&lt;/td&gt;
        &lt;td&gt;DELETE_EMPLOYEE&lt;/td&gt;
        &lt;td&gt;[&quot;549d9715-0949-4a57-b9fb-1c56eb8e5029&quot;]&lt;/td&gt;
        &lt;td /&gt;
        &lt;td&gt;2021-12-03 12:18:11.507 +0200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;addb372c-046f-43e8-c91f-1df1a30caaa4&lt;/td&gt;
        &lt;td&gt;CREATE_EMPLOYEE&lt;/td&gt;
        &lt;td /&gt;
        &lt;td&gt;duplicate employee username&lt;/td&gt;
        &lt;td&gt;2021-12-03 13:52:52.067 +0200&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; column stores the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; of a processed mutation
request. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operation&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target&lt;/code&gt; columns together enable storing
different kind of completed mutations in this single table; the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operation&lt;/code&gt; signifies the type of the mutation operation performed, and
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target&lt;/code&gt; column stores the primary key of the target entity as a
JSON array. For the employee entity, the primary key is just a single
UUID value, but for another entity type, such as school position, the
primary key might be the pair of employment id and school id. We can
store any primary key tuple, regardless of their component data types,
by encoding them as JSON arrays.&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; value in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; column tells that the operation was a
success. A string value present means that the operation in question
failed. For example, the third operation (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; ending
with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a2&lt;/code&gt;) completed with a failure to update a particular employee
entity, because email validation failed. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; column makes it
possible to resend the same error back to the client if the client
retries the operation with the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value. An
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; value can exist without an entity target id as well: the
last operation (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; ending with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a4&lt;/code&gt;) was a failure to
create a new employee. We may publish the identifier of a new entity
only after succeeding in entity creation.&lt;/p&gt;

&lt;p&gt;In general terms, the Staff service utilizes the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table as
follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Upon receiving a new mutation request, the service opens a database
transaction.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table contains a row with the same
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value as in the request, the service knows that the
request has been processed already, and now it only rebuilds the
response for the original processing outcome back to the client. The
response is either a success or failure, depending on if the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; column is populated or not:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; is present, the service builds a failure response
with a description why the mutation failed.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;error_msg&lt;/code&gt; is not present, the service builds a success
response. The response might include data about the entity after
the mutation is completed. If so, the service includes data about
the current state of the entity. Because a later mutation might
have changed the entity after reconstructing the response for an
older mutation, we settle for showing the current data available
(which might be nothing if the entity is already deleted). This is
what I meant earlier by &lt;em&gt;including the same logical output&lt;/em&gt; in the
4th API design principle.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table didn’t contain a row with this
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; value, the service knows that now is the first and
only time to process the request. The service must execute any calls
to remote services (doesn’t matter who owns them) within the context
of the open database transaction and expect errors. But rollbacking
the whole database transaction upon remote call error is not the
right way do it either: the service must still be able to append a
new row to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table in the end of the database
transaction.&lt;/p&gt;

    &lt;p&gt;This is where the &lt;a href=&quot;https://www.postgresql.org/docs/14/sql-rollback-to.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROLLBACK TO
SAVEPOINT&lt;/code&gt;&lt;/a&gt; SQL command is very
useful. Mark a savepoint within the transaction just before the point
of doing anything that you expect to raise an error. It an error does
happen, handle it gracefully, rollback to the savepoint, and remember
the error for the next step.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now the service has completed processing the mutation either with
success or failure. The service appends a new row to the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table accordingly.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The service commits the database transaction and responds to the
client.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The 5th API design principle concerns the ability to track the
propagation of change across services. If the Users service, coming
after the Staff service in the communication path of processing clients’
mutation requests, has completed a request with a certain
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt;, but the Staff service isn’t, we know that the Staff
service is malfunctioning.&lt;/p&gt;

&lt;p&gt;Continuing the earlier example of creating a new employee in the Staff
API (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createEmployee&lt;/code&gt; mutation), the Staff service might send a
GraphQL mutation like this to the Users service in order to create a
user entity to associate with the employee:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-graphql&quot; data-lang=&quot;graphql&quot;&gt;&lt;span class=&quot;k&quot;&gt;mutation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;createUser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactionId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;addb372c-046f-43e8-c91f-1df1a30caaa1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Albert&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Wesker&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# etc…&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This would be a call to a remote service in the 3rd step of the usage
description of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table we just went through. The service
making remote calls should utilize retries for timed out connection
attempts.&lt;/p&gt;

&lt;p&gt;Now I can justify my choice of naming for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; parameter.
I think duplicate request processing suppression and database
transactions share some of their goals. Especially, both aim to protect
against data corruption by guaranteeing that processing takes effect at
most once. But duplicate request suppression is not a form of
distributed transactions either. For example, it’s possible that the
Staff service might crash while processing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createEmployee&lt;/code&gt;
mutation, just after the User service has completed processing the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createUser&lt;/code&gt; mutation received from the Staff service. In that
situation, the Users service will have a row in its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transaction&lt;/code&gt; table
indicating completed request processing, but the same table in the Staff
service won’t contain a corresponding row for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createEmployee&lt;/code&gt;
mutation. The system will be left in an inconsistent state unless the
client retries the request until receiving a response.&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Note that because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; parameter is user input, its value
must be treated as unsafe and potentially malicious. Clients might
generate values that are not truly random, even though the values might
conform to the UUID format. This is why services must enforce
authorization for clients accessing their data.&lt;/p&gt;

&lt;h2 id=&quot;communicating-with-external-services&quot;&gt;Communicating with external services&lt;/h2&gt;

&lt;p&gt;End-to-end wise, the IdP service is the last service in the
communication path of creating a new employee. It’s part of the system,
but, being an external service, we cannot enforce our API design
principles to it. Is there anything we can do to prevent duplicate
request processing?&lt;/p&gt;

&lt;p&gt;Uniqueness constrains on entity attributes enforced by the API of the
external service do help, even if they don’t behave nicely with request
retries. For example, the IdP service in my imaginary web service might
enforce unique usernames for user entities. That effectively acts as a
duplication suppressor for request retries when attempting to create a
new entity. If you route all requests to the external service via your
own service acting as a &lt;em&gt;facade&lt;/em&gt;, you can anticipate username constraint
errors on retries and check if the user was created successfully on an
earlier attempt after all. In addition, you should have a mechanism to
suppress duplicate request processing in the client-facing side of the
facade service, especially if the service stores state about some of the
data in the external service (entity identifier mapping, like in the
Users service, for instance).&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The longer your web service operates and the more requests it handles,
the more important suppressing duplicate request processing
becomes. Faults can and eventually will happen in the components of your
system. Some of those faults will trigger your services receiving
duplicated requests. Idempotent request processing constitutes that
requesting the same operation with the same input many times over
applies the effect in the service only once. The trick is in the
identification of the input data, and I’ve shown one way to implement it
with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transactionId&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;There are many ways to go about this. In considering any approach, I’d
inspect it from the viewpoint of the client: how can you ensure that
it’s safe for the client at the start of the communications path to
retry requests, and that the response, when it finally arrives, has the
same content as the response to the first request that was actually
processed?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;In &lt;em&gt;exactly-once&lt;/em&gt; semantics, a system processes a message so that
the final effect is the same as if no faults occurred, even if the
operation was retried due to some fault. Thinking web services, that
theoretically necessitates the client to support infinite number of
retries, because the service might be unreachable when the client
sent its request. In turn, the server must guarantee &lt;em&gt;at-most-once&lt;/em&gt;
semantics in processing received requests: the server must detect
duplicate requests and process only the first of them either
completely or not at all. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;An &lt;em&gt;entity&lt;/em&gt; is an object that is defined primarily by its
identifying attribute (such as UUID). Two different entities might
have the same descriptive attributes (such as name). &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;An operation is &lt;em&gt;idempotent&lt;/em&gt; if, given the same input, you apply
it many times, and the effect is the same as if you applied it only
once. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;Request processing can be made more reliable between two services
with a message broker: the source service publishes requests as
messages to the broker, while the destination service consumes
messages and acknowledges consumed messages after completing
processing them. This is possible with &lt;a href=&quot;https://kafka.apache.org/documentation/#semantics&quot;&gt;Apache
Kafka&lt;/a&gt;, for example. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Standalone Git branch from subdirectory</title>
    <link href="https://tkareine.org/articles/standalone-git-branch-from-subdirectory.html"/>
    <updated>2021-04-05T15:00:00+03:00</updated>
    <id>https://tkareine.org/articles/standalone-git-branch-from-subdirectory</id>
    <content type="html">&lt;p&gt;Imagine you have a subdirectory of generated files and you want to store
them into Git as a standalone (orphan) branch. For example, you have
generated html files and associated CSS and JavaScript assets, with the
intention of publishing them as a &lt;a href=&quot;https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#publishing-sources-for-github-pages-sites&quot;&gt;GitHub Pages
site&lt;/a&gt;
from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; Git branch of the project. Git’s plumbing commands
allow automating storing the generated files into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch,
recreating the branch each time you publish. Here’s a Bash oneliner to
do that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git add &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt; build &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git update-ref refs/heads/gh-pages &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;git commit-tree &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Generated build&apos;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;git write-tree &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;build/&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git reset&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For that Bash command list, I presume that&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the generated files are already in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; subdirectory,&lt;/li&gt;
  &lt;li&gt;you include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt; file of the project, and&lt;/li&gt;
  &lt;li&gt;the Git index (staging area) is clear currently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s go over the parts of the command list.&lt;/p&gt;

&lt;h2 id=&quot;1-stage-the-generated-files-into-git-index&quot;&gt;1. Stage the generated files into Git index&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git add &lt;span class=&quot;nt&quot;&gt;--force&lt;/span&gt; build&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force&lt;/code&gt; switch to allow Git to add ignored files.&lt;/p&gt;

&lt;h2 id=&quot;2-create-a-git-tree-object-from-the-current-index&quot;&gt;2. Create a Git tree object from the current index&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git write-tree &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;build/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A Git tree object groups Git objects and stores the paths of the
objects. The tree object will be used to create a commit object in the
next step.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--prefix=build/&lt;/code&gt; option makes Git to treat the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; directory as
the root directory for the files within the directory. For example, a
file with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/dir/index.html&lt;/code&gt; path gets recorded with the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dir/index.html&lt;/code&gt; path inside the tree object.&lt;/p&gt;

&lt;p&gt;The command prints the name of the tree object to stdout (I’ll use the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$tree&lt;/code&gt; shell variable for that in the next step).&lt;/p&gt;

&lt;h2 id=&quot;3-create-a-git-commit-object-from-the-tree-object&quot;&gt;3. Create a Git commit object from the tree object&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git commit-tree &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Generated build&apos;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tree&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The command prints the commit object id to stdout (let’s put it into the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$commit&lt;/code&gt; variable).&lt;/p&gt;

&lt;h2 id=&quot;4-set-a-branch-to-refer-to-the-commit-object&quot;&gt;4. Set a branch to refer to the commit object&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git update-ref refs/heads/gh-pages &lt;span class=&quot;nv&quot;&gt;$commit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This overwrites the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch, if it exists already.&lt;/p&gt;

&lt;h2 id=&quot;5-reset-index-to-the-current-head&quot;&gt;5. Reset index to the current HEAD&lt;/h2&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git reset&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This needs to be done for clearing the index.&lt;/p&gt;

&lt;p&gt;Now the target branch, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;, contains a single orphaned commit,
using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; subdirectory as the root directory of the files.&lt;/p&gt;

&lt;p&gt;If you want to, you can store the previous commit of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;
branch as the parent of the next commit, but then there are edge cases
to consider: you’ll need to detect if the target branch exists already
and whether the new contents of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; directory differ between the
next and the previous commit (it doesn’t make sense to create a new
commit with an empty diff compared to the parent commit). Covering them
would require elaborate scripting compared to the Bash oneliner I went
through.&lt;/p&gt;

&lt;p&gt;As a real example, the &lt;a href=&quot;https://github.com/tkareine/hackers-tiny-slide-deck&quot;&gt;Hacker’s Tiny Slide
Deck&lt;/a&gt; project uses
this trick in storing the generated slides (an html file) and the
JavaScript bundle of the project into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch of the
project, from where GitHub Pages publishes them. The relevant Git
commands are in
&lt;a href=&quot;https://github.com/tkareine/hackers-tiny-slide-deck/blob/044da2e4d28eaca5db554a0d6f3c41c6d4e905ea/package.json#L33&quot;&gt;package.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a screenshot of &lt;a href=&quot;https://gitup.co/&quot;&gt;GitUp&lt;/a&gt; app’s map view of the
Git repository of Hacker’s Tiny Slide Deck, showing what the standalone
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/gh-pages-gitup-map-view-45b686290404e0a8aa21102352fd539b.png&quot; alt=&quot;A map view from the GitUp app showing the master and gh-pages branches&quot; title=&quot;A map view from the GitUp app&quot; width=&quot;195&quot; height=&quot;258&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The chapter titled &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Internals-Git-Objects&quot;&gt;Git
Objects&lt;/a&gt; from
the Pro Git book is a great resource for learning more about Git
internals.&lt;/p&gt;
</content>
  </entry><entry>
    <title type="html">Lightweight Node.js version switching</title>
    <link href="https://tkareine.org/articles/lightweight-nodejs-version-switching.html"/>
    <updated>2018-10-10T01:05:00+03:00</updated>
    <id>https://tkareine.org/articles/lightweight-nodejs-version-switching</id>
    <content type="html">&lt;p&gt;Recently, I’ve been paying attention to the time it takes my shell’s
init script to complete. Bash is notoriously slow, but since it’s
popular in scripting use, I keep using it. This leaves me to optimize my
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bashrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I program with Node.js frequently, so a Node.js version manager is an
essential tool. Upon investigating the execution time of my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bashrc&lt;/code&gt;, I
found that loading &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt; takes a lot of time:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; ~/.nvm/versions/node
v10.11.0	v8.12.0		v9.11.2

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NVM_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.nvm&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time source&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/brew/opt/nvm/nvm.sh&quot;&lt;/span&gt;

real	0m0.397s
user	0m0.270s
sys	0m0.134s

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nvm &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
0.33.11&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;400 ms for sourcing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm.sh&lt;/code&gt; is a way too big share of the time budget
I’d like to allocate for starting Bash in interactive mode. It’s a pity,
because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm&lt;/code&gt; is a quite nice tool.&lt;/p&gt;

&lt;p&gt;An alternative for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm&lt;/code&gt; is &lt;a href=&quot;https://github.com/nodenv/nodenv&quot;&gt;nodenv&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; ~/.nodenv/versions
10.11.0	8.12.0	9.11.2

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time eval&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;~/brew/bin/nodenv init -&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

real	0m0.070s
user	0m0.034s
sys	0m0.034s

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nodenv &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
nodenv 1.1.2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I can manage with 70 ms. This is the tool I chose to use as my Node.js
version manager for a while. But because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodenv&lt;/code&gt; utilizes shims to wrap
the executables of the selected Node.js version, a couple of problems
arise. The first is that after installing a new executable from global
npm package, you must remember to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodenv rehash&lt;/code&gt; to rebuild the
shims. Otherwise you can’t run the executable. The second is that you
lose access to the manual pages of the wrapped executables: a shim is an
indirection for the actual executable, causing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man&lt;/code&gt;’s manual page
search to miss the page. A demonstration of the problems:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--depth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
/Users/tkareine/.nodenv/versions/10.11.0/lib
&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; npm@6.4.1

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; marked
/Users/tkareine/.nodenv/versions/10.11.0/bin/marked -&amp;gt; /Users/tkareine/.nodenv/versions/10.11.0/lib/node_modules/marked/bin/marked
+ marked@0.5.1
added 1 package from 1 contributor &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;0.586s

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; marked

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;nodenv rehash

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; marked
/Users/tkareine/.nodenv/shims/marked

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;man &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; node
No manual entry &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;node

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;man &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; marked
No manual entry &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;marked&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I keep forgetting to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodenv rehash&lt;/code&gt; and I do would like to access
the manual pages of the executables of the selected Node.js version.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nvm&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodenv&lt;/code&gt; have a lot of features. While they are useful in some
scenarios, such as continuous integration setups, I’d be satisfied with
less in my development environment. The ability to install specific
Node.js versions and to switch between them easily, independently per
shell session, would be enough.&lt;/p&gt;

&lt;p&gt;In the Ruby community, &lt;a href=&quot;https://github.com/postmodern/ruby-install&quot;&gt;ruby-install&lt;/a&gt; and &lt;a href=&quot;https://github.com/postmodern/chruby&quot;&gt;chruby&lt;/a&gt; tools provide just
these features, and nothing more. The former is for installing Rubies
and the latter for switching between them. What’s great about this
arrangement of separate tools is that the switcher, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt;, is very
lightweight.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/nodenv/node-build&quot;&gt;node-build&lt;/a&gt;, part of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodenv&lt;/code&gt; project, is a dedicated Node.js
installer. It checks the digest of the downloaded Node.js package and
allows you to unpack it to any directory. This is good and I’ll keep
using it.&lt;/p&gt;

&lt;p&gt;For the version switcher, I didn’t find anything I liked. &lt;a href=&quot;https://github.com/moll/sh-chnode&quot;&gt;sh-chnode&lt;/a&gt; is
written in the same spirit as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt;, but includes some design
decisions I didn’t like personally.&lt;/p&gt;

&lt;p&gt;I ended up writing my own version switcher, even though there’s already
so many of them. But this one is fast to load, does one thing well, and
is suitable for me. :) Naming is hard, so I just call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt;. Let’s
see it in action:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; ~/.nodes
node-10.11.0	node-8.12.0	node-9.11.2

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time source&lt;/span&gt; ~/brew/opt/chnode/share/chnode/chnode.sh

real	0m0.007s
user	0m0.004s
sys	0m0.003s

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;chnode node-10

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;chnode
 &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; node-10.11.0
   node-8.12.0
   node-9.11.2

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;npm &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--depth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0
/Users/tkareine/.nodes/node-10.11.0/lib
└── npm@6.4.1

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; marked
/Users/tkareine/.nodes/node-10.11.0/bin/marked

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;man &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; node
/Users/tkareine/.nodes/node-10.11.0/share/man/man1/node.1

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;man &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; marked
/Users/tkareine/.nodes/node-10.11.0/share/man/man1/marked.1

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;chnode &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
chnode: 0.2.0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For me, &lt;a href=&quot;https://github.com/tkareine/chnode&quot;&gt;chnode&lt;/a&gt; is the tool comparable to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt; for Node.js
versions. Like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt;, the primary mechanism of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt; is to modify
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; environment variable to include the path to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt;
subdirectory of the selected Node.js version. But unlike &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt; does not modify any Node.js specific environment variable
(there’s no need).&lt;/p&gt;

&lt;p&gt;I didn’t implement auto-switching to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt;. The feature would switch
Node.js version to the version specified in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.node-version&lt;/code&gt; file if
the current working directory, or its parent, would have the file. You
might put such a file at a project’s root directory. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chruby&lt;/code&gt; has the
feature, but because I don’t use it, I dropped it.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt; supports &lt;a href=&quot;https://www.gnu.org/software/bash/&quot;&gt;GNU Bash&lt;/a&gt; and &lt;a href=&quot;https://www.zsh.org/&quot;&gt;Zsh&lt;/a&gt;, has good test coverage, and
allows you to display the selected Node.js version in the shell prompt
with ease. It’s MIT licensed. See the &lt;a href=&quot;https://github.com/tkareine/chnode#readme&quot;&gt;README&lt;/a&gt; for more.&lt;/p&gt;

&lt;p&gt;Finally, the total execution time of initializing my &lt;a href=&quot;https://github.com/tkareine/dotfiles/blob/master/.bashrc&quot;&gt;Bash
setup&lt;/a&gt; in interactive mode, including selecting a Node.js
version with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chnode&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;bash &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true

&lt;/span&gt;real	0m0.337s
user	0m0.240s
sys	0m0.083s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

</content>
  </entry><entry>
    <title type="html">Programming with minimum number of dependencies</title>
    <link href="https://tkareine.org/articles/programming-with-minimum-number-of-dependencies.html"/>
    <updated>2014-01-29T21:40:00+02:00</updated>
    <id>https://tkareine.org/articles/programming-with-minimum-number-of-dependencies</id>
    <content type="html">&lt;p&gt;When you’re concentrating on the essentials of your current programming task, you’ll want to avoid sidetracks as much as possible. Encounter a tricky subtask and you’ll start searching the web for a 3rd party code solving it for you. When you’re introducing additional dependencies without much further thought, you’re not considering their burden on maintenance.&lt;/p&gt;

&lt;p&gt;How often do you check for outdated dependencies (and actually do upgrade, doing all the required client side changes)? Do you have local modifications to any of the dependencies (I hope not, but if you do, how do you track the modifications in order to reapply them)? Once you’ve updated the dependencies, how do you make sure your code still works as intended (how comprehensive are your tests)? And then there’s the hell of library incompatibilities.&lt;/p&gt;

&lt;p&gt;You can think that dependencies are like loans you’ll have to take care of for the whole lifespan of the project. The interest rate varies for each dependency, so it pays off to justify having each of them.&lt;/p&gt;

&lt;p&gt;Sometimes, you can go a surprisingly long way without additional dependencies. I take occasional delight in programming with as few library dependencies as possible.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; It’s fun for the challenge, and it makes you think about your design.&lt;/p&gt;

&lt;p&gt;Recently, I needed a program to fix outdated identifiers in my customer’s MongoDB documents. These identifiers referred to documents in external service A. Each identifier was paired with another identifier for external service B, and luckily the latter ones were still correct. By querying the REST API of service A with service B identifier, I could find out the correct service A identifier and update the document in MongoDB.&lt;/p&gt;

&lt;p&gt;Because this was for an occasional maintenance need, I decided not to include the program as part of the application’s code. A command line tool felt better. Observing that both MongoDB and service A’s REST API speak JSON, all I essentially needed was the ability to communicate and handle JSON. For the REST API, the communication happens over socket connection. For MongoDB, you could use a driver for your programming language to talk with the database. But there was an alternative: because the query and insert operations the tool needed were simple, I could attach the tool to Mongo’s shell with a Unix pipe, evaluating database commands in JavaScript and reading the results as JSON via the pipe.&lt;/p&gt;

&lt;p&gt;I went to write the tool in Ruby. It turned out that I didn’t need external libraries at all. Ruby’s standard library has a decent (though verbose to use) HTTP library, a JSON parser and generator, and a great set of tools to work with processes and IO (just take a look at how versatile &lt;a href=&quot;https://ruby-doc.org/core-2.1.0/Kernel.html#method-i-spawn&quot;&gt;Kernel.spawn&lt;/a&gt; is!) I embedded the small amount of JavaScript needed for database operations straight into the program. User input escaping within the JavaScript commands was easy: just encode the input into JSON.&lt;/p&gt;

&lt;p&gt;Because there are no 3rd party libraries, there’s no need for dependency management. The tool is ready to use as long as you’ve Ruby installed.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;To demonstrate the implementation, I wrote a similar &lt;a href=&quot;https://gist.github.com/tkareine/8693458&quot;&gt;toy program&lt;/a&gt;. This one is for searching term definitions: if there are definitions matching the search term in MongoDB, the program shows them. Otherwise, the program searches the definition from &lt;a href=&quot;https://duckduckgo.com/api&quot;&gt;DuckDuckGo’s Instant Answer API&lt;/a&gt;, stores the new definition to MongoDB, and shows it.&lt;/p&gt;

&lt;p&gt;Healthy dependency management balances the risks and benefits. This article is not about doing it all by yourself, avoiding dependencies for the sake of it. Instead, you should consider each dependency, think what you benefit from it, and how it fits to the whole project.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://github.com/tkareine/jekyll-minibundle&quot;&gt;jekyll-minibundle&lt;/a&gt; is an example of this. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Ruby belongs to the customer’s development environment already, so it’s not a new dependency by itself. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Remote work calls for active participation</title>
    <link href="https://tkareine.org/articles/remote-work-requires-active-participation.html"/>
    <updated>2013-03-23T00:30:00+02:00</updated>
    <id>https://tkareine.org/articles/remote-work-requires-active-participation</id>
    <content type="html">&lt;p&gt;Remote work is sometimes a necessity. Be it for your circumstances or for whatever reason, you have weighted with your team that you doing remote work has more benefits than drawbacks. But working alone behind the wire steers you easily to isolation. You concentrate only on your tasks, forgetting what the rest of the team is doing.&lt;/p&gt;

&lt;p&gt;That’s no teamwork. Without participating to the team you are doomed to slow down your team, and, ultimately, your project.&lt;/p&gt;

&lt;p&gt;Being a remote team member calls for active participation. Listen to the others, discuss, and help them as soon as possible. Do you understand your task thoroughly? If not, do not rely on assumptions – ask for more details. Let others know what’s your status. It’s up to you to keep others informed, because nobody else knows what you are really doing. Be consistent and others will trust your doings.&lt;/p&gt;

&lt;p&gt;Team practices gain another level of importance for remote work. I can’t imagine a team functioning without daily meetings. Or deciding what are the next most important tasks to do without story planning, or improving as a team without retrospectives. Having such practices on a regular basis ensures at least a minimum level of communication between team members.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;As Inayali de Leon writes in &lt;a href=&quot;https://alistapart.com/article/becoming-better-communicators&quot;&gt;Becoming better communicators&lt;/a&gt;, we are emotional creatures, and digital communication removes the emotional cues that mitigate worry. We have to be aware of this. The better you know your team member, the better chance you have interpreting her feelings correctly.&lt;/p&gt;

&lt;p&gt;Because we tend to misunderstand messages, you should use a communication medium that reduces emotional cues the least. Prefer video over voice, and voice over text. Use at least voice communications for daily meetings. I have found out that dedicating a laptop for constant video connection to the rest of the team with &lt;a href=&quot;https://hangouts.google.com/&quot;&gt;Google Hangout&lt;/a&gt; is very beneficial.&lt;/p&gt;

&lt;p&gt;But even video communications is not a replacement for meeting people in person. You gain tremendously more understanding by taking part in story plannings and retrospectives in person. Meet the team regularly and make it a habit. The team evolves their own vocabulary and fellowship, and you must share it in order to be part of the team.&lt;/p&gt;

&lt;p&gt;Be active.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Pair programming is great for sharing techniques and practices, and solving problems together. It is possible to do that remotely with screen sharing tools like &lt;a href=&quot;https://www.teamviewer.com/&quot;&gt;TeamViewer&lt;/a&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Asset bundling with Jekyll</title>
    <link href="https://tkareine.org/articles/asset-bundling-with-jekyll.html"/>
    <updated>2013-02-22T03:15:00+02:00</updated>
    <id>https://tkareine.org/articles/asset-bundling-with-jekyll</id>
    <content type="html">&lt;p&gt;How do you ship the stylesheets and JavaScript sources of your &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;-built site? Shipping them as is, source file for source file, works, but causes the browser to request each of them separately from the backend. You want to consider concatenating all the stylesheet sources specific to your site into one file and then minifying that file. This is called &lt;em&gt;asset bundling&lt;/em&gt;. And you should apply the same for JavaScript sources, too. This reduces the number of requests the browser does upon initial page load, shortening the time it takes to load the page.&lt;/p&gt;

&lt;p&gt;Asset bundling has a related problem: caching. Generally, when the assets of your site change, you want the browser to fetch the latest version from the backend. The problem is in detecting when to use the version in the browser’s cache and when to refetch the latest version of the asset from the backend. This can be solved by setting the HTTP response headers so that html files are considered to be dynamic resources, refetched when changed. The asset files are regarded as static resources, having long caching period. Whenever the contents of an asset changes, we refer to a new static resource in the html file. The latter is called &lt;em&gt;cache busting&lt;/em&gt;. There’s two techniques to it: using a URL query parameter or a fingerprint in file path.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;It is up to you to solve both asset bundling and cache busting with Jekyll. Out of the box, Jekyll just copies each source as is to the generated site directory. This does not help asset bundling. And you handle the references to the assets in html files manually without any cache busting mechanism. Let’s see what we can do about these.&lt;/p&gt;

&lt;h2 id=&quot;jekyll-with-github-pages&quot;&gt;Jekyll with GitHub pages&lt;/h2&gt;

&lt;p&gt;With &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub pages&lt;/a&gt;, you let the service generate the site from your sources. The tradeoff is that GitHub runs Jekyll with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--safe&lt;/code&gt; switch, disabling plugins. This means you have to do with what Jekyll has by default.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;For bundling assets, there are two options. Either combine the assets manually, or use an external tool for it. I just put all JavaScript codes in a single file when there’s not too much of it. The latter option is the one I prefer for stylesheets, because I don’t want to write CSS by hand, anyway. I use &lt;a href=&quot;http://compass-style.org/&quot;&gt;Compass&lt;/a&gt; to author stylesheets with &lt;a href=&quot;https://sass-lang.com/&quot;&gt;Sass&lt;/a&gt; markup and direct Compass to compile the resulting CSS into a single compact file. The bad thing is that I have to add the compiled CSS files to git.&lt;/p&gt;

&lt;p class=&quot;update&quot;&gt;Update (9 March 2013): Another alternative is to concatenate assets with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; tags, as shown &lt;a href=&quot;https://developmentseed.org/blog/2011/09/09/jekyll-github-pages/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then you have to solve cache busting. Here’s one way to do it. In your content file, add a query parameter to the URL of the asset:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.baseurl }}assets/styles/screen.css?bust={{ site.time | date: &apos;%s&apos; }}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;screen, projection&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you generate the site, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bust&lt;/code&gt; parameter will have a timestamp from the moment of site generation.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/assets/styles/screen.css?bust=1419885211&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;screen, projection&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p class=&quot;update&quot;&gt;Update (29 December 2014): Changed the example above to use plain Unix timestamp as the cache bust value.&lt;/p&gt;

&lt;p&gt;The timestamp will change upon each site generation, likely updating too often compared to the frequency of changes you have for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;screen.css&lt;/code&gt;. Assets need timestamps to update only when the contents of the assets change. But at least timestamp generation is automatic, so the tradeoff might be okay. I guarantee you won’t remember to do it manually every time when needed.&lt;/p&gt;

&lt;p&gt;This was a path of compromises. But for a small number of stylesheets and JavaScript sources, I don’t think it is all that bad.&lt;/p&gt;

&lt;h2 id=&quot;jekyll-with-jekyll-minibundle&quot;&gt;Jekyll with jekyll-minibundle&lt;/h2&gt;

&lt;p&gt;In order to address the compromises discussed above, you have to use Jekyll with plugins. If you look at the &lt;a href=&quot;https://jekyllrb.com/docs/plugins/&quot;&gt;Jekyll plugin page&lt;/a&gt; and search for “asset”, you will find many plugins written for handling asset bundling.&lt;/p&gt;

&lt;p&gt;But for my own preferences, I found most of the existing plugins too complex to use. Neither did like to install a lot of transitive gem dependencies. So, I decided to write my own: &lt;a href=&quot;https://github.com/tkareine/jekyll-minibundle&quot;&gt;jekyll-minibundle&lt;/a&gt;. The plugin has no gem dependencies and it works with any minification tool supporting standard unix input and output.&lt;/p&gt;

&lt;p&gt;Let’s go through bundling JavaScript sources.&lt;/p&gt;

&lt;p&gt;First, you need to choose your minification tool. &lt;a href=&quot;https://github.com/mishoo/UglifyJS2&quot;&gt;UglifyJS2&lt;/a&gt; is a fast one. Install the tool of your choice and set the path to its executable in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$JEKYLL_MINIBUNDLE_CMD_JS&lt;/code&gt; environment variable. For example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JEKYLL_MINIBUNDLE_CMD_JS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/usr/local/share/npm/bin/uglifyjs --&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, install jekyll-minibundle with&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jekyll-minibundle&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and place the following line to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_plugins/minibundle.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jekyll/minibundle&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Place your JavaScript sources to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_assets/scripts&lt;/code&gt; directory in the site project.&lt;/p&gt;

&lt;p&gt;In your content file where you want the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to appear, place a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minibundle&lt;/code&gt; &lt;a href=&quot;https://github.com/Shopify/liquid/wiki/Liquid-for-Designers&quot;&gt;Liquid&lt;/a&gt; block:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;{% minibundle js %}
source_dir: _assets/scripts
destination_path: assets/site
assets:
- scrolling_menu
- program_table
- some_sharing
{% endminibundle %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here we specify that the output will be a JavaScript bundle with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scrolling_menu.js&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;program_table.js&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some_sharing.js&lt;/code&gt; as input sources from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_assets/scripts&lt;/code&gt; directory. These will be fed to the minifier in the given order. The output will be stored to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site/assets/site-&amp;lt;md5digest&amp;gt;.js&lt;/code&gt;. The plugin will insert the MD5 digest over the contents of the bundle as the fingerprint to the filename:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;assets/site-9a93bf1d8459c9a344a36af564b078a1.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The plugin supports the same mechanism for stylesheets. However, I still like to use Compass for stylesheets, because it has so many other benefits. Because Compass can handle bundling, the plugin only needs to copy the file and add a fingerprint to the filename.&lt;/p&gt;

&lt;p&gt;In order to do this, tell git to ignore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_tmp&lt;/code&gt; directory, and configure Compass to place the output to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_tmp/screen.css&lt;/code&gt;. Then, add this line to your content file for including the path to the bundle:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{% ministamp _tmp/screen.css assets/screen.css %}&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;screen, projection&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The resulting filename fill have the MD5 digest of the file as the fingerprint:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;link&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;assets/screen-2ef6d65c7f031e021a59eb5c1916f2f2.css&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;rel=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;media=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;screen, projection&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This approach works with &lt;a href=&quot;https://requirejs.org/docs/optimization.html&quot;&gt;RequireJS optimizer&lt;/a&gt;, too!&lt;/p&gt;

&lt;p&gt;Both the fingerprinting and asset bundling mechanisms work in Jekyll’s auto regeneration mode.&lt;/p&gt;

&lt;p&gt;The plugin has one more trick in its sleeves. If you set environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$JEKYLL_MINIBUNDLE_MODE&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;development&lt;/code&gt;, the plugin copies asset files as such to the destination directory, and omits fingerprinting. This is useful in development workflow, where you need the filenames and line numbers of the original asset sources.&lt;/p&gt;

&lt;p&gt;I have shown how to automate asset bundling and fingerprinting for cache busting with the plugin. In addition, we have gotten rid off all the compromises we had when using vanilla Jekyll: there is no need to store generated bundle files in git, and asset fingerprints change only when the contents of the assets change.&lt;/p&gt;

&lt;p&gt;You can read more about the plugin at its &lt;a href=&quot;https://github.com/tkareine/jekyll-minibundle&quot;&gt;project page in GitHub&lt;/a&gt;. Also, you might be interested in a &lt;a href=&quot;http://agilejkl.com/&quot;&gt;site&lt;/a&gt; that uses the plugin just like described above.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://developers.google.com/speed/docs/best-practices/caching#LeverageProxyCaching&quot;&gt;Google recommends&lt;/a&gt; cache busting with fingerprinting over using a query parameter. Some old proxy caches do not cache static files at all if the URL contains query parameters. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;However, you can work around this by generating your site locally and then pushing the generated files to GitHub. Then you’re not locked to Jekyll’s safe mode. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Why JavaScript needs module definitions</title>
    <link href="https://tkareine.org/articles/why-javascript-needs-module-definitions.html"/>
    <updated>2012-08-11T21:44:00+03:00</updated>
    <id>https://tkareine.org/articles/why-javascript-needs-module-definitions</id>
    <content type="html">&lt;p&gt;Me and my colleague &lt;a href=&quot;https://twitter.com/eeroan&quot;&gt;Eero Anttila&lt;/a&gt; are working in a project where we are using Eero’s &lt;a href=&quot;https://github.com/reaktor/jquery-continuous-calendar&quot;&gt;Continuous Calendar plugin for jQuery&lt;/a&gt; in the frontend. The plugin utilizes a set of date handling functions for formatting, parsing, and so on. The functions are grouped into objects (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateFormat&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateRange&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Locale&lt;/code&gt;) which are injected into the global window object. A very useful aspect of the functions is that they are immutable. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dateTimeObj.firstDateOfMonth()&lt;/code&gt; returns a new instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We found out that we could benefit from these functions in the application generally, needing date handling also elsewhere than in the calendar component.&lt;/p&gt;

&lt;p&gt;Our frontend loads with &lt;a href=&quot;https://requirejs.org/&quot;&gt;RequireJS&lt;/a&gt;, and we’ve been happy composing our application from small modules. Now, in order to get access to the date handling functions in our modules, we need either to ensure that Continuous Calendar gets loaded before our application’s modules, or we need to introduce optional AMD support for the date functions. Because it doesn’t make sense to load the whole Continuous Calendar just to get access to the functions, we decided add AMD support to them.&lt;/p&gt;

&lt;p&gt;The AMD community has devised common patterns for making a JavaScript module&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; to work simultaneously with AMD loaders, CommonJS, and traditional browser script loading. They are called as &lt;a href=&quot;https://github.com/umdjs/umd&quot;&gt;Universal Module Definition&lt;/a&gt; (UMD) patterns. Essentially, we are talking about inserting bootstrap code in the beginning of a module’s source file.&lt;/p&gt;

&lt;p&gt;Here’s an example how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; global object supports AMD loaders and traditional browser script loading:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;DateTime.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;amd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// AMD loading: define module named &quot;DateTime&quot; with no dependencies&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// and build it&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// traditional browser loading: build DateTime object without&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// dependencies and inject it into window object&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// above, `this` refers to window, the second argument is the factory&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// function&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// build DateTime and return it&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; factory executes without external dependencies. This is communicated in the code by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;define&lt;/code&gt; call having empty array as its second argument for the AMD case, and the factory function call having no arguments in the traditional browser loading case.&lt;/p&gt;

&lt;p&gt;However, for building &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateRange&lt;/code&gt;, we need jQuery, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateFormat&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt;:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;DateRange.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;amd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// AMD loading: define module named &quot;DateRange&quot; with dependencies&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// and build it&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DateRange&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;jquery&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DateFormat&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// traditional browser loading: build DateRange object with&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// dependencies and inject it into window object&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;factory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;jQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DateFormat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateFormat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// above, `this` refers to window, the second argument is the factory&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// function with dependencies&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// build DateFormat with the help of $, DateFormat, and DateTime, and&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// return it&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateRange&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateRange&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;What happens here? With AMD loader, such as RequireJS, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; block of the bootstrap code executes. There we call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;define&lt;/code&gt;, specifying a module named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateRange&lt;/code&gt; (the first argument), needing jQuery, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateFormat&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; as its dependencies (the array as the second argument). Eventually, after loading all the specified dependencies, the AMD loader calls the factory function (the third argument) with the dependencies as the arguments to the function.&lt;/p&gt;

&lt;p&gt;If were are not using an AMD loader, but loading the script in the browser traditionally with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt; block of the bootstrap applies. Before that, however, we have to ensure that we load modules in such an order that the dependencies of each module exist at the evaluation time of the module. That can be satisfied by careful organization of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags or bundling the modules in a single source file. In this case, jQuery, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateFormat.js&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime.js&lt;/code&gt; must be loaded before loading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateRange.js&lt;/code&gt;. When the browser evaluates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateRange.js&lt;/code&gt;, it calls the factory function with dependencies fetched from the global window object.&lt;/p&gt;

&lt;p&gt;I really like the factory function spelling out the dependencies as parameters to the function.&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; We get to know the dependencies just by looking at the function signature. In addition, we have located the change made to the global window object (if any) in one predefined place (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;else&lt;/code&gt; block). If we’re using an AMD loader, we avoid polluting the global window object altogether!&lt;/p&gt;

&lt;p&gt;The UMD pattern drives the module author to make at most one addition to the global window object. That’s a great guideline for organizing modules.&lt;/p&gt;

&lt;p&gt;Of course, it is up to the module author to play by these rules. There’s nothing preventing the factory function from referring to the window object for other dependencies or polluting the global window object. But why would the author want to surprise the users of the module?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;em&gt;Module&lt;/em&gt; meaning a JavaScript source file defining functionality that can be used elsewhere. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;The factory function is an application of &lt;a href=&quot;https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript&quot;&gt;Module Pattern with import mixins&lt;/a&gt;. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry><entry>
    <title type="html">Readable tests</title>
    <link href="https://tkareine.org/articles/readable-tests.html"/>
    <updated>2012-05-06T21:20:00+03:00</updated>
    <id>https://tkareine.org/articles/readable-tests</id>
    <content type="html">&lt;p&gt;When I go to explore unfamiliar code, I dig up its tests first. I hope the tests introduce me gently to the purpose of the code, covering the common use cases first, followed by edge conditions and more peculiar cases. I expect tests to reveal me the general behavior and purpose of the code. I don’t expect other documentation.&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Later, when I change the code by refactoring and adding new features, I don’t expect to modify most of the tests. Finding the place for writing new tests for the added feature is intuitive, because the structure of the tests guides me to proper location.&lt;/p&gt;

&lt;p&gt;That’s what good tests are like. The implied characteristics are introduction, documentation, and rigidity against changes. The fact that such tests protect you against regression bugs is almost an afterthought.&lt;/p&gt;

&lt;p&gt;I think readability is a good term for covering these features. Here’s a few guidelines for writing such tests.&lt;/p&gt;

&lt;h2 id=&quot;setup-state-make-claims-about-it&quot;&gt;Setup state, make claims about it&lt;/h2&gt;

&lt;p&gt;Say you have a class or webpage that needs to be tested in certain state. It is important to clearly separate state setup code from test assertions. The former answers to the question &lt;em&gt;“where are we?”&lt;/em&gt;, while the latter answers to &lt;em&gt;“what is it like?”&lt;/em&gt; I use terms &lt;em&gt;system-under-test&lt;/em&gt; to denote the state to be tested.&lt;/p&gt;

&lt;p&gt;The actual tests are just claims about the state of system-under-test. They cause no changes to the state (no side-effects). I use term &lt;em&gt;test claim&lt;/em&gt; for that.&lt;/p&gt;

&lt;p&gt;Together, a system-under-test and its test claims form a &lt;em&gt;“test context”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An example of such a test, written in JavaScript and using &lt;a href=&quot;https://mochajs.org/&quot;&gt;Mocha&lt;/a&gt; test framework:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;cart_page_spec.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;shopping cart page&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;when page is loaded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// system-under-test&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loadCart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;shows checkout button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;// test claim&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart button.checkout&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;visible&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;has no payment options&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .method&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;empty&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// more claims...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;when choosing to pay&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loadCart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;TestHelpers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clickCheckoutButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hides checkout button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart button.checkout&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hidden&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;has payment options&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .method&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;empty&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// more claims...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;Prepare the states of system-under-tests (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; blocks) in their setup codes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; blocks in the example above). Make sure to reset everything the tests depend on. This ensures that each system-under-test gets a fresh start, avoiding state leaks from others.&lt;/p&gt;

&lt;p&gt;For cleaning your tracks, you could have teardown code to be run after the tests of a specific test context. It is best to avoid teardowns, however, because they are easy to forget to write. It is better to write your setup code so that it ensures the world is in proper state for your tests to run.&lt;/p&gt;

&lt;p&gt;Try to separate tests so that each assertion makes a specific claim. You can use multiple assertions for a specific claim, however. Custom matchers help you with this, especially if you test a specific thing more than once.&lt;/p&gt;

&lt;p&gt;When you write your tests like this, you gain two benefits: you can run your tests in any order, and you get the choice to run the setup code for a system-under-test only once. For instance, Ruby’s &lt;a href=&quot;https://github.com/seattlerb/minitest&quot;&gt;MiniTest&lt;/a&gt; runs tests in random order, helping to catch tests that have side-effects in their claims. Mocha has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; block for running setup code for a system-under-test only once (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforeEach&lt;/code&gt; runs setup for each claim). This speeds up test execution.&lt;/p&gt;

&lt;p&gt;In addition, prefer active clauses for describing a system-under-test and its claims. An active clause clearly identifies that the claim is about the system-under-test. Also, words &lt;em&gt;should&lt;/em&gt; and &lt;em&gt;must&lt;/em&gt; are just noise: compare &lt;em&gt;“it has payment options”&lt;/em&gt; against &lt;em&gt;“it should have payment options”&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;test-state-transitions&quot;&gt;Test state transitions&lt;/h2&gt;

&lt;p&gt;Note that the states of the two test contexts in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cart_page_spec.js&lt;/code&gt; (above) differ only by the clicking of the checkout button. Why didn’t I just take the state of the first test context and modify that for the purposes of the latter test context? I chose to reset the world between them, because it gives us orthogonality (state changes in test context A do not get reflected in test context B). After a few state transitions, it becomes hard to keep track of the state changes happened so far. Ideally, you want to see the whole state of the current system-under-test in one glimpse. You achieve that by initializing the whole state in the setup block of the system-under-test.&lt;/p&gt;

&lt;p&gt;Now I can also reorder test contexts as I like. I can move the most common cases to the top of the test file and edge cases to the bottom.&lt;/p&gt;

&lt;p&gt;But sometimes it is useful to have state transitions between test contexts. For example, such a case might occur for input validation before checkout confirmation:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;cart_page_validation_spec.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;cart page validation&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;when entering invalid credit card number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loadCart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;TestHelpers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clickCheckoutButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .creditcard .number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;lolbal&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;nf&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;highlights credit card number as invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .creditcard .number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hasClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disables confirmation&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart button.confirm&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;disabled&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;and then entering valid credit card number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .creditcard .number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;4012888888881881&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// not mine, mind you&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

      &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;does not highlight credit card number as invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment .creditcard .number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hasClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

      &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;enables confirmation&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.cart .payment button.confirm&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;be&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;enabled&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;Essentially, here you test that validation mechanism handles the case of revalidating invalid input.&lt;/p&gt;

&lt;p&gt;I prefer to nest test contexts that depend on earlier ones. That communicates the intent of dependence clearly. It also keeps the number of nestings in check, because three or more nesting levels makes the test context difficult to read as whole.&lt;/p&gt;

&lt;h2 id=&quot;group-tests-by-semantics&quot;&gt;Group tests by semantics&lt;/h2&gt;

&lt;p&gt;If a set of tests are similar in semantics, you should group them together so that it is easy so see the difference between them:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;date_format_spec.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;date formatting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;non-date string&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;lolbal&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;empty object&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[{}]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;          &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;throws exception if given &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/^Invalid date: /&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;Those tests were about input argument validation. I would separate them from testing the happy path:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;date_format_spec.js (continued)&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;date formatting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Date object, with long weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;                  &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2010&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;  &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Wednesday May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Date object, with short weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;                 &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2010&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;short&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Wed May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Date object, without weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;                    &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2010&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;   &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;String presentation of date, with long weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2010-03-02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;  &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Wednesday May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;String presentation of date, with short weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2010-03-02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;short&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Wed May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;String presentation of date, without weekday&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2010-03-02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;weekday&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;   &lt;span class=&quot;na&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;May 2, 2010&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;formats &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// input argument validation tests are here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;By putting the expected input and output of each test case to its own line, possibly with a short description how the case differs from others, you can easily compare them and spot missing tests for edge conditions.&lt;/p&gt;

&lt;p&gt;When you adhere to writing a test claim for each test case, it becomes easy to see which particular test fails when you run the test suite.&lt;/p&gt;

&lt;p&gt;If your test framework of choice has expression syntax for test claim definition, you can avoid repeating the boilerplate code for each test claim. First, think a group of test cases and see what is common to them. Then, put the varying parts of the cases to a collection. Lastly, iterate the collection so that the body of the iteration becomes the test claim definition. This is what I did in the examples above.&lt;/p&gt;

&lt;p&gt;I think this improves readability a lot, because now I can put each test case to its own line, without the boilerplate code between them. This is a manifestation of &lt;a href=&quot;https://en.wikipedia.org/wiki/Don&apos;t_repeat_yourself&quot;&gt;Don’t Repeat Yourself&lt;/a&gt; (DRY) principle.&lt;/p&gt;

&lt;p&gt;But don’t take DRY to the extreme. You should aim for making tests readable, not as short as possible. This is why I separated the group of happy path tests from the group of argument validation tests.&lt;/p&gt;

&lt;h2 id=&quot;on-test-abstraction-levels&quot;&gt;On test abstraction levels&lt;/h2&gt;

&lt;p&gt;Choosing the most suitable abstraction level for testing your code is hard. There are many characteristics at play, some of which are at odds with each other: coverage, simplicity, execution speed, and maintenance. For example, if you choose the application user interface as the abstraction level for all your tests, you gain easier test code maintenance (architectural refactorings do not cause changes to tests), but lose in execution speed (all the application components will be used).&lt;/p&gt;

&lt;p&gt;Of course, it is about balance. Choose the characteristics that you desire most for testing a particural part of your application.&lt;/p&gt;

&lt;p&gt;I’d write tests for a date formatting component at the unit level, like in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;date_format_spec.js&lt;/code&gt;. It makes no sense to launch the whole application in order to test dates get formatted as expected: the user interface might change during development, and covering all the inputs makes the execution speed slow for such a low level component.&lt;/p&gt;

&lt;p&gt;On the other hand, if I had an application with Model-View-Controller architecture, I wouldn’t write tests for controllers, models, and views alone. Writing tests for a specific controller only would require using dummy implementations of associated models and views. Maintaining tests across refactorings would be laborious, because changes in the interfaces of controllers, models, or views would propagate to many tests. Instead, I would raise the abstraction level and write tests at the functional level. In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cart_page_spec.js&lt;/code&gt;, the web page with the related behavior is the functional level.&lt;/p&gt;

&lt;p&gt;You need tests to have confidence that everything works as expected. Isolate your tests from external interfaces of which output you cannot control. Otherwise, you lose that confidence. You can use fake or stub implementations for external interfaces. A fake implementation is easier to put in place if you first abstract the external interface behind your own component:&lt;/p&gt;

&lt;figure&gt;
&lt;figcaption&gt;rest.js&lt;/figcaption&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nf&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createProductionAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createTestAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createProductionAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postCheckout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ajax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/*...*/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createTestAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postCheckout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stubs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;postCheckoutResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;/figure&gt;

&lt;p&gt;Here I have a component of the frontend part of a web application, abstracting the REST API of the backend part. All the REST API calls in the frontend go through this component. In test environment, the component returns canned responses without actually sending requests. It is not a big leap to change the dummy response to fit a particular test’s needs, either.&lt;/p&gt;

&lt;p&gt;I dislike using mocks in tests and guiding code design. They end up being a maintenance burden everywhere I’ve worked with them.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;Like good code, writing good tests is hard and takes many iterations. I use these guidelines to steer me when I write tests, but I wouldn’t hesitate to drop following a particular guideline if it makes the end result more readable.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;The emphasis is on &lt;em&gt;expectation&lt;/em&gt;. For example, a hack in the middle of self-documenting code is unexpected. Thus, you should document any unexpected code. You can even isolate the hack to its own function with a descriptive name. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
  </entry>
</feed>
