<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rob Sutter</title>
    <description>The latest articles on DEV Community by Rob Sutter (@rtsrob).</description>
    <link>https://dev.to/rtsrob</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F427810%2Fc0431602-32e9-4a5c-9a70-6aec63e73b5e.png</url>
      <title>DEV Community: Rob Sutter</title>
      <link>https://dev.to/rtsrob</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rtsrob"/>
    <language>en</language>
    <item>
      <title>Applying test-driven development to your database</title>
      <dc:creator>Rob Sutter</dc:creator>
      <pubDate>Thu, 03 Feb 2022 16:31:33 +0000</pubDate>
      <link>https://dev.to/fauna/applying-test-driven-development-to-your-database-4fn6</link>
      <guid>https://dev.to/fauna/applying-test-driven-development-to-your-database-4fn6</guid>
      <description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Test-driven_development" rel="noopener noreferrer"&gt;Test-driven development&lt;/a&gt; (TDD) is a popular software development methodology that aims to reduce defects and improve the ease of maintaining code. In this post, you learn how to apply TDD to write queries and expressions with the Fauna Query Language (FQL). You learn a general pattern for TDD with FQL, then apply that pattern to implement a &lt;code&gt;Split()&lt;/code&gt; function that accepts &lt;em&gt;string&lt;/em&gt; and &lt;em&gt;delimiter&lt;/em&gt; parameters and returns an array of substrings “split” at each occurrence of the delimiter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;p&gt;To follow along with this post you must have access to a Fauna account. You can &lt;a href="https://dashboard.fauna.com/accounts/register?utm_source=Devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rob_tdd" rel="noopener noreferrer"&gt;register for a free Fauna account&lt;/a&gt; and benefit from &lt;a href="https://fauna.com/pricing?utm_source=Devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rob_tdd" rel="noopener noreferrer"&gt;Fauna’s free tier&lt;/a&gt; while you learn and build, and you won’t need to provide payment information until you are ready to upgrade.&lt;/p&gt;

&lt;p&gt;This post assumes you are familiar enough with Fauna to create a database and client key. If not, please complete the &lt;a href="https://docs.fauna.com/fauna/current/learn/quick_start/client_quick_start?lang=javascript&amp;amp;utm_source=Devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rob_tdd" rel="noopener noreferrer"&gt;client application quick start&lt;/a&gt; and return to this post.&lt;/p&gt;

&lt;p&gt;This post also assumes you have &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; v12 or later in your development environment. This post uses &lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; and JavaScript, but you can apply the principles to your preferred test tooling in any &lt;a href="https://docs.fauna.com/fauna/current/drivers/?utm_source=Devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rob_tdd" rel="noopener noreferrer"&gt;supported language&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The source code used in this post is &lt;a href="https://github.com/fauna-labs/tdd-fql-jest" rel="noopener noreferrer"&gt;available in Fauna Labs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TDD overview
&lt;/h2&gt;

&lt;p&gt;TDD begins with writing a single failing test. For example, if you are implementing a &lt;code&gt;Split()&lt;/code&gt; function that accepts a string and a delimiter and returns an array of strings “split” at each occurrence of the delimiter, you would test that &lt;code&gt;Split("127.0.0.1", ".")&lt;/code&gt; is equal to &lt;code&gt;["127", "0", "0", "1"]&lt;/code&gt;. When you first run your test suite, the test fails, because you have not yet implemented the &lt;code&gt;Split()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Next, you implement the functionality of a single “unit” of code, in this case, the &lt;code&gt;Split()&lt;/code&gt; function. You run your tests as you make changes, and when the tests pass, you have implemented the code to cover the specific use case that the test specifies.&lt;/p&gt;

&lt;p&gt;With passing tests, you can add more tests to cover additional use cases, or you can refactor your existing code for performance or readability improvements. Because your tests are in place and passing, you can make changes with more confidence that your code is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your environment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setting up your database
&lt;/h3&gt;

&lt;p&gt;Create a new database in your Fauna account and create a key for that database with the &lt;em&gt;Server&lt;/em&gt; role. Save the key to add to your application.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new Node.js application in an empty directory and install the &lt;a href="https://www.npmjs.com/package/faunadb" rel="noopener noreferrer"&gt;Fauna JavaScript driver&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init --yes
npm install faunadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add Jest to your application.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev jest babel-jest @babel/core @babel/preset-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit &lt;a href="https://github.com/fauna-labs/tdd-fql-jest/blob/b956595b0f3ab6e09f66b6000fd878f88af69236/package.json#L6-L9" rel="noopener noreferrer"&gt;&lt;em&gt;package.json&lt;/em&gt;&lt;/a&gt; to add the following &lt;em&gt;“test”&lt;/em&gt; and &lt;em&gt;“test:watch”&lt;/em&gt; scripts.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test:watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest --watchAll"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file &lt;a href="https://github.com/fauna-labs/tdd-fql-jest/blob/main/babel.config.js" rel="noopener noreferrer"&gt;&lt;em&gt;babel.config.js&lt;/em&gt;&lt;/a&gt; in the root directory of your project with the following content.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}}]],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file &lt;a href="https://github.com/fauna-labs/tdd-fql-jest/blob/main/jest.config.mjs" rel="noopener noreferrer"&gt;&lt;em&gt;jest.config.mjs&lt;/em&gt;&lt;/a&gt; in the root directory of your project with the following content.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;collectCoverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;coverageDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coverage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;coverageProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;rootDir&amp;gt;/.jest/setEnvVars.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file &lt;a href="https://github.com/fauna-labs/tdd-fql-jest/blob/main/.jest/setEnvVars.example.js" rel="noopener noreferrer"&gt;&lt;em&gt;.jest/setEnvVars.js&lt;/em&gt;&lt;/a&gt; and paste the following code, replacing &lt;em&gt;&lt;/em&gt; with the value of your admin key and &lt;em&gt;db.fauna.com&lt;/em&gt; with your &lt;a href="https://docs.fauna.com/fauna/current/learn/understanding/region_groups" rel="noopener noreferrer"&gt;Region Group endpoint&lt;/a&gt;. Add this file to your &lt;em&gt;.gitignore&lt;/em&gt; to avoid committing secrets to your repository.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_ADMIN_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;secret_key&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_DOMAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;db.fauna.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run test -- --passWithNoTests&lt;/code&gt; to check that you have configured your application to use Jest. You should receive a response “&lt;strong&gt;No tests found, exiting with code 0&lt;/strong&gt;.”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h2&gt;
  
  
  Testing partial FQL expressions
&lt;/h2&gt;

&lt;p&gt;You test partial FQL expressions from JavaScript by writing JavaScript functions that generate FQL expressions. You then &lt;a href="https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export" rel="noopener noreferrer"&gt;export&lt;/a&gt; these functions so that both your code and your tests can import and use them. This supports the “&lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;don’t repeat yourself&lt;/a&gt;” or “DRY” principle of software development in your FQL queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  General pattern
&lt;/h3&gt;

&lt;p&gt;The following five steps form the general pattern for testing partial FQL expressions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the input to your expression as one or more constants.&lt;/li&gt;
&lt;li&gt;Define the expected output of your expression as a constant.&lt;/li&gt;
&lt;li&gt;Construct your FQL expression.&lt;/li&gt;
&lt;li&gt;Evaluate your FQL expression in Fauna and save the actual output.&lt;/li&gt;
&lt;li&gt;Compare the actual output to the expected output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your test passes when the comparison in the final step is successful. This comparison may be equality, inequality, or any other comparison allowed by your testing framework. In Jest, these comparisons are called &lt;a href="https://archive.jestjs.io/docs/en/22.x/expect" rel="noopener noreferrer"&gt;matchers&lt;/a&gt;; your testing framework may have different terminology.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparing your test harness
&lt;/h3&gt;

&lt;p&gt;Make a stub for your implementation by creating a file named &lt;em&gt;fauna/lib/split.js&lt;/em&gt; and pasting the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives your test an undefined implementation to call, guaranteeing your test fails.&lt;/p&gt;

&lt;p&gt;Next, create a file &lt;em&gt;fauna/test/split.test.js&lt;/em&gt; in your project. Since you run your tests against an actual Fauna environment, you must import the &lt;a href="https://npmjs.com/package/faunadb" rel="noopener noreferrer"&gt;Fauna JavaScript driver&lt;/a&gt; and create a new Fauna client by adding the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faunadb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faunaClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_ADMIN_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_DOMAIN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db.fauna.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_SCHEME&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;checkNewVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, import the Split() function stub that you want to test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Split&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/split&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy and paste the following general framework code into &lt;em&gt;split.test.js&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Define the input to your expression as one or more constants.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Define the expected output of your expression as a constant.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Construct your FQL expression.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Evaluate your FQL expression in Fauna and save the actual output.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;faunaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Compare the actual output to the expected output.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you have a complete test harness, but your tests will not yet run because of the placeholder values. In the next section, you replace these with real values based on the specified behavior of your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a single failing test
&lt;/h3&gt;

&lt;p&gt;The following examples specify the expected behavior of your Split() implementation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Split(ip_address, '.')&lt;/code&gt; correctly separates an IPv4 address into four octets.&lt;/li&gt;
&lt;li&gt;When &lt;em&gt;delimiter&lt;/em&gt; is not found, &lt;code&gt;Split(string, delimiter)&lt;/code&gt; returns an array with one element, &lt;em&gt;string&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;When the string contains only &lt;em&gt;delimiter&lt;/em&gt;, &lt;code&gt;Split(string, delimiter)&lt;/code&gt; returns an empty array.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;string&lt;/th&gt;
&lt;th&gt;delimiter&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'127.0.0.1'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'.'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;['127', '0', '0', '1']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'Hello, world!'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'.'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;['Hello, world!']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'................'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'.'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To implement the first test, modify the general framework code in &lt;em&gt;fauna/test/split.test.js&lt;/em&gt; with the properties from your first specification. Your implementation may vary but should be similar to the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Split correctly separates an IPv4 address into four octets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Define the input to your expression as one or more constants.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Define the expected output of your expression as a constant.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Construct your FQL expression.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Evaluate your FQL expression in Fauna and save the actual output.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;faunaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Compare the actual output to the expected output.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous code expects &lt;code&gt;Split()&lt;/code&gt; to split the string &lt;code&gt;‘127.0.0.1'&lt;/code&gt; into an array of four strings at each decimal point/period, yielding &lt;code&gt;['127', ‘0', '0', '1']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before moving on to implement &lt;code&gt;Split()&lt;/code&gt;, check that your test runs using &lt;code&gt;npm run test&lt;/code&gt;. It’s okay if it fails. This is the expected behavior since you haven’t implemented the functionality of &lt;code&gt;Split()&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;If your tests do not yet run, check that you have included all parts of the test harness and the test itself. Your complete &lt;em&gt;fauna/test/split.test.js&lt;/em&gt; should look as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faunadb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Split&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/split&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;faunaClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_ADMIN_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_DOMAIN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;db.fauna.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAUNADB_SCHEME&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;checkNewVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Split correctly separates an IPv4 address into four octets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Define the input to your expression as one or more constants.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Define the expected output of your expression as a constant.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Construct your FQL expression.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;delimiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Evaluate your FQL expression in Fauna and save the actual output.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;faunaClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Compare the actual output to the expected output.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now that your test harness is set up, run your tests in watch mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run test:watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0oq9utgo81tr1ffe310.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0oq9utgo81tr1ffe310.png" alt="Output of npm run test:watch with failing tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In watch mode, test tooling &lt;em&gt;watches&lt;/em&gt; your source and test files and runs the relevant test suite when changes are detected. You immediately see the results of running the test suite whenever you save your work, which tightens the developer feedback loop and improves your productivity.&lt;/p&gt;

&lt;p&gt;With your tests running in watch mode, it’s time to implement the actual &lt;code&gt;Split()&lt;/code&gt; function so that your tests pass. Replace the stub you created earlier in &lt;em&gt;fauna/lib/split.js&lt;/em&gt; with the following code. This implementation uses the built-in FQL action &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/findstrregex?lang=javascript&amp;amp;utm_source=Devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=rob_tdd" rel="noopener noreferrer"&gt;&lt;code&gt;FindStrRegex&lt;/code&gt;&lt;/a&gt; to split the parameter &lt;code&gt;str&lt;/code&gt; at each occurrence of &lt;code&gt;sep&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;faunadb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faunadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Concat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;FindStrRegex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Lambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Var&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;FindStrRegex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;]+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="nc"&gt;Lambda&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nc"&gt;Var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you save your file, Jest automatically re-runs the test suite, and this time, it passes. Congratulations, you’ve written your first TDD with FQL!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u1yn2k8t8e8wcsf60bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u1yn2k8t8e8wcsf60bi.png" alt="output of npm run test:watch with passing tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Review and next steps
&lt;/h2&gt;

&lt;p&gt;In this post, you learned how to apply TDD to write and test FQL expressions. You wrote failing tests for a &lt;code&gt;Split()&lt;/code&gt; function that accepts &lt;em&gt;string&lt;/em&gt; and &lt;em&gt;delimiter&lt;/em&gt; parameters and returns an array of substrings “split” at each occurrence of the delimiter. Finally, you implemented the &lt;code&gt;Split()&lt;/code&gt; function while running unit tests in watch mode.&lt;/p&gt;

&lt;p&gt;To test your knowledge, try implementing tests for the remaining two specifications. Once you have those tests in place and passing, try to refactor the implementation of &lt;code&gt;Split()&lt;/code&gt;. Notice how TDD allows you to safely refactor your code without introducing regressions.&lt;/p&gt;

&lt;p&gt;For more tooling and sample code, check out &lt;a href="https://github.com/fauna-labs" rel="noopener noreferrer"&gt;Fauna Labs&lt;/a&gt; on GitHub. We can’t wait to see what you test and build!&lt;/p&gt;

</description>
      <category>tdd</category>
      <category>testing</category>
      <category>database</category>
      <category>fauna</category>
    </item>
    <item>
      <title>Migrating your GraphQL schema with Fauna</title>
      <dc:creator>Rob Sutter</dc:creator>
      <pubDate>Fri, 10 Sep 2021 19:00:26 +0000</pubDate>
      <link>https://dev.to/fauna/migrating-with-graphql-4596</link>
      <guid>https://dev.to/fauna/migrating-with-graphql-4596</guid>
      <description>&lt;p&gt;Modern applications frequently change as you deliver features and fixes to customers. In this series, you learn how to implement migrations in &lt;a href="https://fauna.com"&gt;Fauna&lt;/a&gt;, the data API for modern applications. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/fauna/evolving-the-structure-of-your-fauna-database-5704"&gt;first post in this series&lt;/a&gt; introduces a high-level strategy for planning and running migrations. The &lt;a href="https://dev.to/fauna/migrating-with-user-defined-functions-12cp"&gt;second post&lt;/a&gt; demonstrates how to implement migration patterns using &lt;a href="https://docs.fauna.com/fauna/current/api/fql/user_defined_functions"&gt;user-defined functions (UDFs)&lt;/a&gt; written in the &lt;a href="https://docs.fauna.com/fauna/current/api/fql/"&gt;Fauna Query Language (FQL)&lt;/a&gt;. In this post, you learn specific considerations for migrating Fauna databases that you access via GraphQL and how to apply the techniques from the previous posts.&lt;/p&gt;

&lt;p&gt;All of the code in this series is available on GitHub in &lt;a href="https://github.com/fauna-labs/migrations"&gt;Fauna Labs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration scenario
&lt;/h2&gt;

&lt;p&gt;This post implements the same migration scenario you used in the previous post. Your Fauna database has a collection for firewall rules and you have an updated requirement to manage inbound traffic from &lt;em&gt;an arbitrary number&lt;/em&gt; of IP address ranges. To satisfy this requirement, you must migrate the &lt;em&gt;ipRange&lt;/em&gt; field type from an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#string"&gt;FQL string&lt;/a&gt; to an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#array"&gt;FQL array&lt;/a&gt; of strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;p&gt;To follow along with this post you must have access to a Fauna account. You can &lt;a href="https://dashboard.fauna.com/accounts/register"&gt;register for a free Fauna account&lt;/a&gt; and benefit from &lt;a href="https://fauna.com/pricing"&gt;Fauna’s free tier&lt;/a&gt; while you learn and build. You do not need to provide payment information until you upgrade your plan.&lt;/p&gt;

&lt;p&gt;You do not need to install any additional software or tools. All examples in this post can be run in the web shell and the GraphQL playground in the &lt;a href="https://dashboard.fauna.com"&gt;Fauna dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post assumes you have read and understood the previous posts in this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create and populate your database
&lt;/h3&gt;

&lt;p&gt;Create another new database in the &lt;a href="https://dashboard.fauna.com"&gt;Fauna dashboard&lt;/a&gt;. Do not select the "Pre-populate with demo data" checkbox, and do not re-use the database from the previous post.&lt;/p&gt;

&lt;p&gt;Save &lt;a href="https://github.com/fauna-labs/migrations/blob/main/graphql/schemas/initial-schema.graphql"&gt;&lt;code&gt;initial-schema.graphql&lt;/code&gt;&lt;/a&gt; to your computer. In your new database in the Fauna dashboard, select the &lt;em&gt;GraphQL&lt;/em&gt; tab, choose &lt;em&gt;Import Schema&lt;/em&gt;, and upload &lt;em&gt;initial-schema.graphql&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;ipRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the &lt;em&gt;Collections&lt;/em&gt; tab and notice that Fauna creates an empty &lt;em&gt;FirewallRule&lt;/em&gt; collection.&lt;/p&gt;

&lt;p&gt;Navigate to the &lt;em&gt;Functions&lt;/em&gt; tab and choose &lt;em&gt;New Function&lt;/em&gt;. Enter &lt;em&gt;migrate_firewall_rule&lt;/em&gt; as the &lt;em&gt;Function Name&lt;/em&gt; and leave the &lt;em&gt;Role&lt;/em&gt; set to the default &lt;em&gt;None&lt;/em&gt;. Paste the following FQL in the field &lt;em&gt;Function Body&lt;/em&gt; and choose &lt;em&gt;Save&lt;/em&gt; to create your UDF. Note that this is the same UDF you created in the previous post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;migrate_firewall_rule&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["firewall_rule_ref"],
    Let(
      { 
        doc: Get(Var("firewall_rule_ref")),
        ipRange: Select(["data", "ipRange"], Var("doc"))
      },
      If(
        IsArray(Var("ipRange")),
        Var("doc"),
        Update(
          Var("firewall_rule_ref"),
          { data: { ipRange: [Var("ipRange")] } }
        )
      )
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the &lt;em&gt;GraphQL&lt;/em&gt; tab, copy the mutation from &lt;a href="https://github.com/fauna-labs/migrations/blob/main/graphql/populate-firewall-rules.graphql"&gt;&lt;code&gt;populate-firewall-rules.graphql&lt;/code&gt;&lt;/a&gt;, and paste it into the query editor. Choose the "play" button and Fauna runs your mutation and returns the &lt;code&gt;_id&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; for each sample firewall rule.&lt;/p&gt;

&lt;p&gt;Return to the &lt;em&gt;Collections&lt;/em&gt; tab and confirm that your &lt;em&gt;FirewallRule&lt;/em&gt; collection now contains three documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encapsulation
&lt;/h2&gt;

&lt;p&gt;Encapsulation is built into GraphQL via strongly typed queries and mutations. Choose &lt;em&gt;Schema&lt;/em&gt; in the GraphQL Playground and review the modified schema that Fauna generates for your database. Note that Fauna makes the following relevant modifications to your schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copies the &lt;code&gt;type FirewallRule&lt;/code&gt; you provide to an &lt;code&gt;input FirewallRule&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Adds &lt;code&gt;_id&lt;/code&gt; and &lt;code&gt;_ts&lt;/code&gt; fields to the &lt;code&gt;type FirewallRule&lt;/code&gt; you provide.&lt;/li&gt;
&lt;li&gt;Generates one GraphQL query - &lt;code&gt;findFirewallRuleById&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Generates three GraphQL mutations - &lt;code&gt;createFirewallRule&lt;/code&gt;, &lt;code&gt;updateFirewallRule&lt;/code&gt;, and &lt;code&gt;deleteFirewallRule&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can override or redefine everything that Fauna generates for you, including GraphQL &lt;code&gt;input&lt;/code&gt; types. You can also &lt;a href="https://docs.fauna.com/fauna/current/api/graphql/functions"&gt;specify UDFs as custom resolvers for both queries and mutations&lt;/a&gt;. Together, these characteristics form the basis of your migration strategy with GraphQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating in steps
&lt;/h2&gt;

&lt;p&gt;You must create UDFs for each relevant query and mutation the first time you perform a migration with GraphQL. Except for the final step, each individual step leaves your database in an equivalent state that does not require any downtime.&lt;/p&gt;

&lt;p&gt;For your first migration, you perform the following steps in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Specify a resolver for your query.&lt;/li&gt;
&lt;li&gt;Specify your &lt;code&gt;input&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Specify a resolver for each mutation.&lt;/li&gt;
&lt;li&gt;Migrate in one step:

&lt;ul&gt;
&lt;li&gt;Modify your UDFs to accept the new shape of your data.&lt;/li&gt;
&lt;li&gt;Modify your &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; definitions in your GraphQL schema and replace your schema in Fauna.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You perform subsequent migrations by modifying the relevant UDFs and uploading a new schema in a single step. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Use tooling for the final step! Fauna provides the &lt;a href="https://github.com/fauna-labs/fauna-schema-migrate"&gt;Fauna Schema Migrate&lt;/a&gt; tool and a &lt;a href="https://github.com/fauna-labs/serverless-fauna"&gt;Serverless Framework plugin&lt;/a&gt; to help you manage your resources in Fauna as code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For all migrations, it is less risky if you perform your migration during an application downtime period. If you are using infrastructure as code (IaC) tools, a single migration should typically take only a few seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step one - Specify your first resolver
&lt;/h3&gt;

&lt;p&gt;The first change you make to your database is to explicitly specify a UDF as a resolver for your query. You modify your query before your mutations because it does not require specifying an &lt;code&gt;input&lt;/code&gt;. This way you change only one thing at a time and check for correctness, supporting the principle of &lt;em&gt;migrating in steps&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Return to the &lt;em&gt;Functions&lt;/em&gt; tab and again choose &lt;em&gt;New Function&lt;/em&gt;. This time enter &lt;em&gt;find_firewall_rule_by_id&lt;/em&gt; as the &lt;em&gt;Function Name&lt;/em&gt; and leave the &lt;em&gt;Role&lt;/em&gt; set to the default &lt;em&gt;None&lt;/em&gt;. Paste the following FQL in the field &lt;em&gt;Function Body&lt;/em&gt; and choose &lt;em&gt;Save&lt;/em&gt; to create your UDF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;find_firewall_rule_by_id&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["id"],
    Get(
      Ref(Collection("FirewallRule"), Var("id"))
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your schema and explicitly define the &lt;code&gt;findFirewallRuleByID&lt;/code&gt; query by adding the following definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;findFirewallRuleByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;find_firewall_rule_by_id&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two points to note about this definition:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Everything except for &lt;code&gt;@resolver(name: "find_firewall_rule_by_id")&lt;/code&gt; is copied directly from the schema that Fauna generates. This means you are not &lt;em&gt;creating&lt;/em&gt; a new query, but are &lt;em&gt;directing&lt;/em&gt; an existing query to use a specific UDF with the &lt;code&gt;@resolver&lt;/code&gt; directive.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@resolver&lt;/code&gt; directive takes one parameter, &lt;code&gt;name&lt;/code&gt;, whose value is the name of the UDF Fauna invokes to process the query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Save your schema and return to the &lt;em&gt;GraphQL&lt;/em&gt; tab. Choose &lt;em&gt;Replace Schema&lt;/em&gt;, choose &lt;em&gt;Replace&lt;/em&gt;, and select your modified schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step two - Create an &lt;em&gt;input&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;When you upload a GraphQL schema, Fauna copies any &lt;code&gt;type&lt;/code&gt; definitions to a corresponding &lt;code&gt;input&lt;/code&gt; definition. If you explicitly define this input in your schema, Fauna uses the definition you specify.&lt;/p&gt;

&lt;p&gt;Copy the following &lt;code&gt;input FirewallRuleInput&lt;/code&gt; definition and add it to your schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRuleInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;ipRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note this definition is identical to the generated schema in your Fauna dashboard. For your own migrations, you can copy the &lt;code&gt;input&lt;/code&gt; definition from the schema Fauna displays in the &lt;em&gt;GraphQL&lt;/em&gt; tab.&lt;/p&gt;

&lt;p&gt;Save your schema again, return to the &lt;em&gt;GraphQL&lt;/em&gt; tab, and replace your schema with the newest version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step three - Specify resolvers for mutations
&lt;/h3&gt;

&lt;p&gt;Now that you have an &lt;code&gt;input&lt;/code&gt; definition, you can specify resolvers for your mutations. Create UDFs for each mutation using the following FQL:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;create_firewall_rule&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["data"],
    Create(
      Collection("FirewallRule"),
      Var("data")
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;update_firewall_rule&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["id", "firewall_rule_input"],
    Update(
      Ref(Collection("FirewallRule"), Var("id")),
      { data: Var("firewall_rule_input") }
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;delete_firewall_rule&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    "id",
    Delete(
      Ref(Collection("FirewallRule"), Var("id"))
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to your schema to specify resolvers for each mutation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;createFirewallRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRuleInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;create_firewall_rule&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;updateFirewallRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRuleInput&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;update_firewall_rule&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;deleteFirewallRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;delete_firewall_rule&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save your schema and replace the schema in the &lt;em&gt;GraphQL&lt;/em&gt; tab of the Fauna dashboard. At this point, you have &lt;em&gt;exactly&lt;/em&gt; the same functionality you started with. Because each query and mutation has an explicitly defined UDF as its resolver, you are now ready to perform your migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final schema - migrate all at once
&lt;/h3&gt;

&lt;p&gt;The final step in a migration is to update the logic in your UDFs, modify your &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; definitions in your schema, and upload your schema to Fauna. Because GraphQL is strongly typed, you must complete these actions in a single step. The simplest way to do this is by accepting some small amount of downtime for your application while you migrate, typically less than one minute. Techniques for migrating with zero downtime are beyond the scope of this post.&lt;/p&gt;

&lt;p&gt;First, update the logic in your UDFs to handle the new shape of your data, reusing the same UDFs from the previous post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: You do not have to update the &lt;code&gt;create_firewall_rule&lt;/code&gt; UDF! Because GraphQL enforces the schema, any call to the &lt;code&gt;createFirewallRule&lt;/code&gt; mutation already has the proper updated shape.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;find_firewall_rule_by_id&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["id"],
    Call(
      "migrate_firewall_rule", 
      Ref(Collection("firewall_rules"), Var("id"))
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;update_firewall_rule_by_id&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["id", "new_rule"],
    Let(
      {
        ref: Ref(Collection("migrate_firewall_rule"), Var("id")),
        doc: Update(
          Var("ref"),
          Var("new_rule")
        )
      },
      Call("migrate_firewall_rule", Var("ref"))
    )        
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;delete_firewall_rule&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
  Lambda(
    ["id"],
    Let(
      {
        ref: Ref(Collection("migrate_firewall_rule"), Var("id")),
        doc: Call("migrate_firewall_rule", Var("ref"))
      },
      Delete(Var("ref"))
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, modify the &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; declarations in your GraphQL schema to reflect the new shape of your data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;ipRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FirewallRuleInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;ipRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, save your schema and replace the schema in the &lt;em&gt;GraphQL&lt;/em&gt; tab of the Fauna dashboard. That's it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating the migration
&lt;/h3&gt;

&lt;p&gt;You can follow the steps laid out in the section &lt;a href="https://dev.to/fauna/migrating-with-user-defined-functions-12cp#confirming-zero-defects"&gt;Confirming zero defects&lt;/a&gt; in the previous post to verify that your migration was successful.&lt;/p&gt;

&lt;p&gt;You can also use GraphQL queries and mutations to confirm that you observe the expected behavior. Navigate to the &lt;em&gt;Collections&lt;/em&gt; tab in the Fauna dashboard and copy the &lt;code&gt;id&lt;/code&gt; of an existing, unmigrated document in the &lt;em&gt;FirewallRule&lt;/em&gt; collection. Return to the &lt;em&gt;GraphQL&lt;/em&gt; tab and run the following query, replacing &lt;code&gt;&amp;lt;FIREWALL_RULE_ID&amp;gt;&lt;/code&gt; with the &lt;code&gt;id&lt;/code&gt; you copied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;findFirewallRuleByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;FIREWALL_RULE_ID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;ipRange&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The firewall rule should be displayed with an array value for &lt;em&gt;ipRange&lt;/em&gt;. Return to the &lt;em&gt;Collections&lt;/em&gt; tab and verify that the document now has an array value for &lt;em&gt;ipRange&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;When performing migrations with GraphQL, you apply the same principles that you apply when migrating with FQL. Before you perform your first GraphQL migration, you must specify &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, and &lt;code&gt;@resolver&lt;/code&gt; definitions in your GraphQL schema. Because GraphQL strongly enforces types via the schema definition, GraphQL migrations typically require some small amount of downtime as you replace your UDFs and schema.&lt;/p&gt;

&lt;p&gt;This post leaves your underlying data unchanged until you access it, a pattern known as "just-in-time" (JIT) data migration. The final post in this series discusses JIT and two additional techniques for migrating your data and indexes with Fauna, along with sample code and guidance on choosing an appropriate approach for your application.&lt;/p&gt;

</description>
      <category>fauna</category>
      <category>database</category>
      <category>migrations</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Migrating with user-defined functions</title>
      <dc:creator>Rob Sutter</dc:creator>
      <pubDate>Fri, 03 Sep 2021 13:15:05 +0000</pubDate>
      <link>https://dev.to/fauna/migrating-with-user-defined-functions-12cp</link>
      <guid>https://dev.to/fauna/migrating-with-user-defined-functions-12cp</guid>
      <description>&lt;p&gt;Modern applications frequently change as you deliver features and fixes to customers. In this series, you learn how to implement migrations in &lt;a href="https://fauna.com"&gt;Fauna&lt;/a&gt;, the data API for modern applications. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/fauna/evolving-the-structure-of-your-fauna-database-5704"&gt;first post in this series&lt;/a&gt; introduces a high-level strategy for planning and running migrations. In this post, you learn how to implement migration patterns using &lt;a href="https://docs.fauna.com/fauna/current/api/fql/user_defined_functions"&gt;user-defined functions (UDFs)&lt;/a&gt; written in the &lt;a href="https://docs.fauna.com/fauna/current/api/fql/"&gt;Fauna Query Language (FQL)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All of the code in this series is available on GitHub in &lt;a href="https://github.com/fauna-labs/migrations"&gt;Fauna Labs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration scenario
&lt;/h2&gt;

&lt;p&gt;Imagine you use an application to manage computers and appliances that communicate on your company's network. One of your domain objects is a firewall rule, which permits or denies inbound traffic to a resource on a given port from a specific range of IP addresses. The range of IP addresses is provided using &lt;a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing"&gt;CIDR notation&lt;/a&gt; and stored in your Fauna database as an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#string"&gt;FQL string&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this post, you have an updated user requirement to permit or deny inbound traffic on a particular port from &lt;em&gt;an arbitrary number&lt;/em&gt; of IP address ranges. To satisfy this requirement, you must migrate the &lt;em&gt;ipRange&lt;/em&gt; field type to an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#array"&gt;FQL array&lt;/a&gt; of strings. This demonstrates a common migration, changing a singleton to a collection as requirements evolve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;p&gt;To follow along with this post you must have access to a Fauna account. You can &lt;a href="https://dashboard.fauna.com/accounts/register"&gt;register for a free Fauna account&lt;/a&gt; and benefit from &lt;a href="https://fauna.com/pricing"&gt;Fauna’s free tier&lt;/a&gt; while you learn and build. You do not need to provide payment information until you upgrade your plan.&lt;/p&gt;

&lt;p&gt;You do not need to install any additional software or tools. All examples in this post can be run in the web shell in the &lt;a href="https://dashboard.fauna.com"&gt;Fauna dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create and populate your database
&lt;/h3&gt;

&lt;p&gt;Create a new database in the &lt;a href="https://dashboard.fauna.com"&gt;Fauna dashboard&lt;/a&gt;. Do not select the "Pre-populate with demo data" checkbox.&lt;/p&gt;

&lt;p&gt;Select the &lt;em&gt;Collections&lt;/em&gt; tab and choose &lt;em&gt;New collection&lt;/em&gt;. Enter &lt;em&gt;firewall_rules&lt;/em&gt; as the &lt;em&gt;Collection Name&lt;/em&gt;, leave the default settings for &lt;em&gt;History Days&lt;/em&gt; and &lt;em&gt;TTL&lt;/em&gt;, and choose &lt;em&gt;Save&lt;/em&gt; to create your new collection.&lt;/p&gt;

&lt;p&gt;Select the &lt;em&gt;Shell&lt;/em&gt; tab (&amp;gt;_) to open the web shell. Copy and paste the following FQL into the editor window and choose &lt;em&gt;Run Query&lt;/em&gt; to add three basic firewall rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do(
    Create(Collection("firewall_rules"), { data: { action: "Allow", port: 80, ipRange: "0.0.0.0/0", description: "Universal HTTP access" } }),
    Create(Collection("firewall_rules"), { data: { action: "Allow", port: 443, ipRange: "0.0.0.0/0", description: "Universal HTTPS access" } }),
    Create(Collection("firewall_rules"), { data: { action: "Allow", port: 22, ipRange: "192.0.2.0/24", description: "Allow SSH from company headquarters" } })
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="//images.contentful.com/po4qc9xpmpuh/1lrumPsZ4k5tjK5fKVK0EX/c132b5cb2066041b26add33742c558c7/populate.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.contentful.com/po4qc9xpmpuh/1lrumPsZ4k5tjK5fKVK0EX/c132b5cb2066041b26add33742c558c7/populate.png" alt="Populating sample firewall rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the &lt;em&gt;Collections&lt;/em&gt; tab and select the &lt;em&gt;firewall_rules&lt;/em&gt; collection. You should see three documents, each describing one of the previous firewall rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encapsulating your data
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://fauna.com/blog/evolving-the-structure-of-your-fauna-database#encapsulating-your-data"&gt;first post in this series&lt;/a&gt;, you learn always to access your data via &lt;a href="https://docs.fauna.com/fauna/current/api/fql/user_defined_functions"&gt;user-defined functions (UDFs)&lt;/a&gt;. Before migrating your database, create UDFs that provide create, retrieve, update, and delete functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Do not abstract your UDFs to generic functions that wrap all operations across all collections! This approach introduces additional complexity and tightly couples your UDFs. Creating one UDF for each data access pattern aligns your UDFs with your business rules, making them easier to manage and more likely to be correct. This is especially true if you access your database via GraphQL, as you learn in the next post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create
&lt;/h3&gt;

&lt;p&gt;Select the &lt;em&gt;Functions&lt;/em&gt; tab in the Fauna dashboard and choose &lt;em&gt;New function&lt;/em&gt;. Enter &lt;em&gt;create_firewall_rule&lt;/em&gt; as the &lt;em&gt;Function Name&lt;/em&gt;, leave the default value for &lt;em&gt;Role&lt;/em&gt;, and paste the following FQL as the &lt;em&gt;Function Body&lt;/em&gt;. Choose &lt;em&gt;Save&lt;/em&gt; to create the UDF in your database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        "new_rule",
        Create(
            Collection("firewall_rules"),
            Var("new_rule")
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a UDF that accepts one parameter object, &lt;em&gt;new_rule&lt;/em&gt;, and stores its value as a new document in the collection &lt;em&gt;firewall_rules&lt;/em&gt;. This mirrors the format of the &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/create"&gt;FQL &lt;code&gt;Create()&lt;/code&gt; primitive&lt;/a&gt;, which accepts a collection name and a parameter object.&lt;/p&gt;

&lt;p&gt;Passing a single object containing the required parameters is a best practice. Do not deconstruct the object into multiple parameters. Deconstructing can lead to incompatibilities, and future calls may fail if they do not provide the right parameters in the right order.&lt;/p&gt;

&lt;p&gt;Test your UDF by selecting the &lt;em&gt;Shell&lt;/em&gt; tab (&amp;gt;_) and pasting the following FQL query into the code editor. Choose &lt;em&gt;Run Query&lt;/em&gt; to call your function and create a new firewall rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("create_firewall_rule", { data: { action: "Deny", port: 25, ipRange: "0.0.0.0/0", description: "Deny SMTP" } })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return to the &lt;em&gt;Collections&lt;/em&gt; tab and select the &lt;em&gt;firewall_rules&lt;/em&gt; collection. You should now see four documents, including the new rule. Copy the &lt;code&gt;id&lt;/code&gt; of the new rule for use in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieve
&lt;/h3&gt;

&lt;p&gt;Select the &lt;em&gt;Functions&lt;/em&gt; tab in the Fauna dashboard and choose &lt;em&gt;New function&lt;/em&gt;. This time, enter &lt;em&gt;get_firewall_rule&lt;/em&gt; as the &lt;em&gt;Function Name&lt;/em&gt;, leave the default value for &lt;em&gt;Role&lt;/em&gt;, and paste the following FQL as the &lt;em&gt;Function Body&lt;/em&gt;. Choose &lt;em&gt;Save&lt;/em&gt; to create the UDF in your database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id"],
        Get(
            Ref(Collection("FirewallRule"), Var("id"))
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a UDF that accepts one parameter object, &lt;em&gt;id&lt;/em&gt;, and retrieves the entire associated document. Test your new UDF by selecting the &lt;em&gt;Shell&lt;/em&gt; tab (&amp;gt;_) and pasting the following FQL query into the code editor, replacing &lt;em&gt;&lt;/em&gt; with the &lt;code&gt;id&lt;/code&gt; you copy from the output of running your &lt;em&gt;create_firewall_rule&lt;/em&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("get_firewall_rule", "&amp;lt;some_id&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose &lt;em&gt;Run Query&lt;/em&gt; to call your UDF. Your function should return the firewall rule you provided, along with its &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#ref"&gt;reference&lt;/a&gt; and &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#timestamp"&gt;timestamp&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;

&lt;p&gt;Return to the &lt;em&gt;Functions&lt;/em&gt; tab and create another function named &lt;em&gt;update_firewall_rule&lt;/em&gt; with the following FQL as the &lt;em&gt;Function Body&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id", "new_rule"],
        Update(
            Ref(Collection("firewall_rules"), Var("id")),
            Var("new_rule")
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This UDF differs slightly from the first two. It accepts two parameters, the &lt;code&gt;id&lt;/code&gt; of the document to update, and a data object &lt;em&gt;new_rule&lt;/em&gt; containing the fields to be updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Updates in Fauna are not destructive. The fields of the provided document are merged with the existing document. To remove a field from a document, set the value of the field to &lt;code&gt;null&lt;/code&gt; when updating the document.&lt;/p&gt;

&lt;p&gt;You can prove this by testing your new UDF. In the web shell, paste the following FQL query, again replacing &lt;em&gt;&lt;/em&gt; with the &lt;code&gt;id&lt;/code&gt; you copy when creating your firewall rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("update_firewall_rule", ["&amp;lt;some_id&amp;gt;", { data: { action: "Allow", description: null } }])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose &lt;em&gt;Run Query&lt;/em&gt; to call your UDF. Your function should return the updated document, with the &lt;em&gt;action&lt;/em&gt; field modified to "&lt;em&gt;Allow&lt;/em&gt;", the &lt;em&gt;description&lt;/em&gt; field removed, and the &lt;em&gt;port&lt;/em&gt; and &lt;em&gt;ipRange&lt;/em&gt; fields unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete
&lt;/h3&gt;

&lt;p&gt;Create another UDF named &lt;em&gt;delete_firewall_rule&lt;/em&gt; from the &lt;em&gt;Functions&lt;/em&gt; tab with the following FQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id"],
        Delete(
            Ref(Collection("firewall_rules"), Var("id"))
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test your UDF by pasting the following FQL in the web shell, replacing &lt;em&gt;&lt;/em&gt; with the &lt;code&gt;id&lt;/code&gt; you copy in a previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("delete_firewall_rule", "&amp;lt;some_id&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose &lt;em&gt;Run Query&lt;/em&gt; to call your UDF, and Fauna removes the document from your database. You should have three documents in your &lt;em&gt;firewall_rules&lt;/em&gt; collection and four UDFs - one for each CRUD primitive.&lt;/p&gt;

&lt;p&gt;At this point, you should modify your client code to call the UDFs you create and avoid all calls to the FQL primitives. You have not changed any functionality in your application, but you are now prepared to make changes safely and perform migrations!&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating in steps
&lt;/h2&gt;

&lt;p&gt;Migrating in steps reduces the risk of each stage of your migration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a UDF that accepts a reference to a previous version of an object and updates it to the new format. &lt;/li&gt;
&lt;li&gt;Modify the four UDFs you create in the previous section to call the migration function. &lt;/li&gt;
&lt;li&gt;Create another UDF that verifies that your migration was successful. This is similar to a unit test of your migration UDF. &lt;/li&gt;
&lt;li&gt;Remove any fields that you have deprecated with your migration.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Populating new field values
&lt;/h3&gt;

&lt;p&gt;In the previous post, you learn that the first step in a migration is creating a UDF that populates the value of your new field from existing data according to your business logic. In this example, the business rule is to convert the string stored in &lt;em&gt;ipRange&lt;/em&gt; to an array with one element, the existing value.&lt;/p&gt;

&lt;p&gt;Create a UDF named "migrate_firewall_rule" with the following FQL code as the body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["firewall_rule_ref"],
        Let(
            { 
                doc: Get(Var("firewall_rule_ref")),
                ipRange: Select(["data", "ipRange"], Var("doc"))
            },
            If(
                IsArray(Var("ipRange")),
                Var("doc"),
                Update(
                    Ref(Var("firewall_rule_ref")),
                    { data: { ipRange: [Var("ipRange")] } }
                )
            )
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UDF accepts a reference, retrieves the specified document, and extracts the &lt;em&gt;ipRange&lt;/em&gt; field. It determines whether the &lt;em&gt;ipRange&lt;/em&gt; field is already an array. If so, it returns the unmodified document. If not, it updates the value of the &lt;em&gt;ipRange&lt;/em&gt; field in the document to an array consisting of one element, the current value of &lt;em&gt;ipRange&lt;/em&gt;. The &lt;code&gt;IsArray()&lt;/code&gt; check makes this function idempotent. You always receive the same result no matter how many times you apply it to a document.&lt;/p&gt;

&lt;p&gt;Note that this function accepts a Fauna reference as a parameter, &lt;strong&gt;not&lt;/strong&gt; a string &lt;em&gt;id&lt;/em&gt;. This function is invoked inside Fauna from other UDFs, so using the Fauna data type simplifies both writing and calling the function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating your UDFs
&lt;/h3&gt;

&lt;p&gt;After you save your migration UDF, you must update the UDFs you create in the previous section. The order of operations in each function differs based on the nature of the operation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create
&lt;/h4&gt;

&lt;p&gt;You have two options when modifying your &lt;em&gt;create_firewall_rule&lt;/em&gt; UDF. You can check the input format, make the value of &lt;em&gt;ipRange&lt;/em&gt; an array if it is a string, and then create a document with the new info. However, this is awkward for large documents with nested objects, and duplicates the functionality you create with &lt;em&gt;migrate_firewall_rule&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A simpler approach is to write the object as is and then apply the migration. Because Fauna is schemaless, you have the flexibility to perform both tasks in a single transaction, without restrictions on field types.&lt;/p&gt;

&lt;p&gt;Replace the body of your &lt;em&gt;create_firewall_rule&lt;/em&gt; UDF with the following FQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["new_rule"],
        Let(
            { doc: Create(Collection("firewall_rules"), Var("new_rule")) },
            Call("migrate_firewall_rule", Select(["ref"], Var("doc"))
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function works even when the provided document is in the new format, because &lt;em&gt;migrate_firewall_rule&lt;/em&gt; is idempotent. You can verify this by calling your UDF from the web shell.&lt;/p&gt;

&lt;p&gt;First, invoke your function passing a string for &lt;em&gt;ipRange&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("create_firewall_rule", {
  data: {
    action: "Deny",
    port: 25,
    ipRange: "0.0.0.0/0",
    description: "Unencrypted SMTP"
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the returned value is the updated version of your document with an array as the value for &lt;em&gt;ipRange&lt;/em&gt;, even though you provided a string. You can verify that the document is stored this way on the &lt;em&gt;Collections&lt;/em&gt; tab.&lt;/p&gt;

&lt;p&gt;Return to the &lt;em&gt;Shell (&amp;gt;_)&lt;/em&gt; tab and invoke your function again, this time with an array for &lt;em&gt;ipRange&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("create_firewall_rule", {
  data: {
    action: "Allow",
    port: 21,
    ipRange: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
    description: "Allow FTP within private subnets"
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your function succeeds, and stores your document exactly as provided. &lt;/p&gt;

&lt;h4&gt;
  
  
  Retrieve
&lt;/h4&gt;

&lt;p&gt;Two characteristics of your &lt;em&gt;migrate_firewall_rule&lt;/em&gt; UDF make adding the migration to your &lt;em&gt;get_firewall_rule&lt;/em&gt; UDF simple.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;em&gt;migrate_firewall_rule&lt;/em&gt; UDF is idempotent, so you can apply it to a document any number of times and get the same result.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;migrate_firewall_rule&lt;/em&gt; UDF returns the document in the updated format, and all UDFs return the value of the last statement in the function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Taken together, this means that your &lt;em&gt;get_firewall_rule&lt;/em&gt; UDF is written as a wrapper to your &lt;em&gt;migrate_firewall_rule&lt;/em&gt; UDF, replacing the application-friendly &lt;em&gt;id&lt;/em&gt; field with its associated Fauna-native reference.&lt;/p&gt;

&lt;p&gt;Replace the body of your &lt;em&gt;get_firewall_rule&lt;/em&gt; UDF with the following FQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id"],
        Call(
            "migrate_firewall_rule", 
            Ref(Collection("firewall_rules"), Var("id"))
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the &lt;em&gt;Collections&lt;/em&gt; tab and copy the &lt;em&gt;id&lt;/em&gt; fields from two documents: one with a string value for &lt;em&gt;ipRange&lt;/em&gt; and one with an array value for &lt;em&gt;ipRange&lt;/em&gt;. Return to the web shell and invoke your &lt;em&gt;get_firewall_rule&lt;/em&gt; twice, replacing &lt;em&gt;&lt;/em&gt; and &lt;em&gt;&lt;/em&gt; with their respective values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("get_firewall_rule", "&amp;lt;string_id&amp;gt;")
Call("get_firewall_rule", "&amp;lt;array_id&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note again that each function call returns a document with an array value for &lt;em&gt;ipRange&lt;/em&gt;. Return to the &lt;em&gt;Collections&lt;/em&gt; tab and verify that both documents now have array values for &lt;em&gt;ipRange&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update
&lt;/h4&gt;

&lt;p&gt;Updates in migrations work similarly to creates. First, make the provided changes to your document, then call the migration function to ensure the document is in the correct format.&lt;/p&gt;

&lt;p&gt;Replace the body of your &lt;em&gt;update_firewall_rule&lt;/em&gt; UDF with the following FQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id", "new_rule"],
        Let(
            {
                ref: Ref(Collection("migrate_firewall_rule"), Var("id")),
                doc: Update(
                    Var("ref"),
                    Var("new_rule")
                )
            },
            Call("migrate_firewall_rule", Var("ref"))
        )        
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the &lt;em&gt;Collections&lt;/em&gt; tab and copy the &lt;em&gt;id&lt;/em&gt; from a document with a string value for &lt;em&gt;ipRange&lt;/em&gt;. Return to the web shell and invoke your &lt;em&gt;update_firewall_rule&lt;/em&gt; function, replacing &lt;em&gt;&lt;/em&gt; with the value you copied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("update_firewall_rule", ["&amp;lt;some_id&amp;gt;", { data: { description: null } }])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query removes the &lt;em&gt;description&lt;/em&gt; field from the document while the migration UDF updates the value of &lt;em&gt;ipRange&lt;/em&gt; to be an array.&lt;/p&gt;

&lt;h4&gt;
  
  
  Delete
&lt;/h4&gt;

&lt;p&gt;Deletes are different from the other three operations. In order to migrate the data in a document during a delete, you must first apply the migration and &lt;em&gt;then&lt;/em&gt; delete the document.&lt;/p&gt;

&lt;p&gt;Replace the body of your &lt;em&gt;delete_firewall_rule&lt;/em&gt; UDF with the following FQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["id"],
        Let(
            {
                ref: Ref(Collection("migrate_firewall_rule"), Var("id")),
                doc: Call("migrate_firewall_rule", Var("ref"))
            },
            Delete(Var("ref"))
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the &lt;em&gt;Collections&lt;/em&gt; tab and copy the &lt;em&gt;id&lt;/em&gt; fields from two documents: one with a string value for &lt;em&gt;ipRange&lt;/em&gt; and one with an array value for &lt;em&gt;ipRange&lt;/em&gt;. Return to the web shell and invoke your &lt;em&gt;delete_firewall_rule&lt;/em&gt; twice, replacing &lt;em&gt;&lt;/em&gt; and &lt;em&gt;&lt;/em&gt; with their respective values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("delete_firewall_rule", "&amp;lt;string_id&amp;gt;")
Call("delete_firewall_rule", "&amp;lt;array_id&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that both function calls return the document as it existed at the time of deletion with array values for &lt;em&gt;ipRange&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Why should you migrate a document if you're deleting it anyway? If you do not access your database via GraphQL, a migration is strictly optional. However, it is considered a best practice to modify before delete since it provides a consistent return object shape and better supports &lt;a href="https://docs.fauna.com/fauna/current/tutorials/temporality.html"&gt;temporality&lt;/a&gt;. Migrating delete functionality &lt;em&gt;is&lt;/em&gt; required, however, if you access your database via GraphQL. The next post in this series addresses the reason for this requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirming zero defects
&lt;/h3&gt;

&lt;p&gt;In this example, you overwrite the existing field of a document with a new value rather than create a new field. How do you compare the new value to the previous value and ensure correctness in your migration?&lt;/p&gt;

&lt;p&gt;Fauna provides temporality features that enable you to compare the history of a document at different points in time. Fauna retains thirty days of history data on your collections by default. This value can be increased or decreased both when a collection is created and at a later point in time. &lt;/p&gt;

&lt;p&gt;The following FQL uses temporality to compare the values of &lt;em&gt;ipRange&lt;/em&gt; in the same document at two points in time: the time of the last update, and one millisecond prior. It checks that the current value is an array, the previous value is a string, and the current value is equal to an array with one element, the previous value.&lt;/p&gt;

&lt;p&gt;Note that this particular function only handles the happy path of a simple migration. It assumes that the tested document exists and was migrated in the last update without a change to the value of &lt;em&gt;ipRange&lt;/em&gt;, only its format. &lt;/p&gt;

&lt;p&gt;Create a UDF named "validate_migration" with the following FQL code as the body.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query(
    Lambda(
        ["ref"],
        Let(
            { 
                new_doc: Get(Var("ref")),
                new_ts: Select(["ts"], Var("new_doc")),
                old_ts: Subtract(Var("new_ts"), 1),
                old_doc: At(Var("old_ts"), Get(Var("ref"))),
                new_ipRange: Select(["data", "ipRange"], Var("new_doc")),
                old_ipRange: Select(["data", "ipRange"], Var("old_doc"))
            },
            And(
                IsArray(Var("new_ipRange")),
                IsString(Var("old_ipRange")),
                Equals(
                    Var("new_ipRange"),
                    [Var("old_ipRange")]
                )
            )
        )
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the &lt;em&gt;Collections&lt;/em&gt; tab and copy the &lt;em&gt;id&lt;/em&gt; fields from two documents: one that you migrated from a string &lt;em&gt;ipRange&lt;/em&gt; and one you created with an array &lt;em&gt;ipRange&lt;/em&gt;. Return to the web shell and invoke your &lt;em&gt;validate_migration&lt;/em&gt; twice, replacing &lt;em&gt;&lt;/em&gt; and &lt;em&gt;&lt;/em&gt; with their respective values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Call("validate_migration", Ref(Collection("FirewallRule"), "&amp;lt;string_id&amp;gt;"))
Call("validate_migration", Ref(Collection("FirewallRule"), "&amp;lt;array_id&amp;gt;"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that calling with &lt;em&gt;&lt;/em&gt; returns &lt;em&gt;true&lt;/em&gt; and calling with &lt;em&gt;&lt;/em&gt; returns &lt;em&gt;false&lt;/em&gt;. You can also manually modify the value of a document and note its impact on the result of the &lt;em&gt;validate_migration&lt;/em&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing deprecated fields
&lt;/h3&gt;

&lt;p&gt;Instead of migrating the value of &lt;em&gt;ipRange&lt;/em&gt;, you could choose to create a new field &lt;em&gt;ipRangeList&lt;/em&gt; and leave the existing field unmodified. In this case, the next step is to update all documents with a value for &lt;em&gt;ipRange&lt;/em&gt; and set it to &lt;code&gt;null&lt;/code&gt;, remove the field.&lt;/p&gt;

&lt;p&gt;This doesn't apply to the current migration because you overwrite the existing field. More information on this topic is provided in the final post in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and roles
&lt;/h2&gt;

&lt;p&gt;Note that without using the &lt;em&gt;migrate_firewall_rule&lt;/em&gt; UDF your retrieve function would need permission to write to your collection. This violates the separation of concerns and least privilege principles. By offloading migration responsibilities to a separate UDF, your business logic UDFs only need permission to call the &lt;em&gt;migrate_firewall_rule&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;UDFs are a powerful tool for decoupling your business logic from your migration logic. UDFs enable a number of additional techniques, including fine-grained access to resources and comparing different versions of documents at call time.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/fauna/migrating-with-graphql-4596"&gt;next post in this series&lt;/a&gt; shows you how to perform migrations when you access Fauna via &lt;a href="https://docs.fauna.com/fauna/current/api/graphql/"&gt;the Fauna GraphQL API&lt;/a&gt;. If you are not a GraphQL user, the final post in the series provides code and considerations for migrating your data and indexes.&lt;/p&gt;

</description>
      <category>fauna</category>
      <category>database</category>
      <category>migrations</category>
      <category>udfs</category>
    </item>
    <item>
      <title>Evolving the structure of your Fauna database</title>
      <dc:creator>Rob Sutter</dc:creator>
      <pubDate>Thu, 02 Sep 2021 23:17:40 +0000</pubDate>
      <link>https://dev.to/fauna/evolving-the-structure-of-your-fauna-database-5704</link>
      <guid>https://dev.to/fauna/evolving-the-structure-of-your-fauna-database-5704</guid>
      <description>&lt;p&gt;Modern applications change frequently as you deliver features and fixes to customers. These changes in data access patterns require changes to the structure and content of your data, even when using a schemaless database like Fauna.&lt;/p&gt;

&lt;p&gt;In this series, you learn how to implement migrations in &lt;a href="https://fauna.com"&gt;Fauna&lt;/a&gt;, the data API for modern applications. This post introduces a high-level strategy for planning and running migrations. In &lt;a href="https://dev.to/fauna/migrating-with-user-defined-functions-12cp"&gt;the second post&lt;/a&gt;, you learn how to implement the migration patterns using &lt;a href="https://docs.fauna.com/fauna/current/api/fql/user_defined_functions"&gt;user-defined functions (UDFs)&lt;/a&gt; written in the &lt;a href="https://docs.fauna.com/fauna/current/api/fql/"&gt;Fauna Query Language (FQL)&lt;/a&gt;. The &lt;a href="https://dev.to/rtsrob/migrating-with-graphql-4596"&gt;third post&lt;/a&gt; presents special considerations for migrating your data when you access Fauna via &lt;a href="https://docs.fauna.com/fauna/current/api/graphql/"&gt;the Fauna GraphQL API&lt;/a&gt;. In the final post, you learn strategies and patterns for migrating your data and explore the impact migrations have on your &lt;a href="https://docs.fauna.com/fauna/current/api/fql/indexes"&gt;indexes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning for migration
&lt;/h2&gt;

&lt;p&gt;Planning is the most important element of a successful database migration. Preparing to evolve your data &lt;em&gt;before&lt;/em&gt; you need to perform a migration reduces the risk of a migration and decreases the implementation time. Consider the four following key areas to prepare your database:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encapsulate your data by limiting all access to your data to UDFs.&lt;/li&gt;
&lt;li&gt;Migrate in steps to minimize the risk and allow for continuous uptime.&lt;/li&gt;
&lt;li&gt;Choose a data update strategy based on your application's specific needs.&lt;/li&gt;
&lt;li&gt;Assess the impact on your indexes to minimize performance impact.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Encapsulating your data
&lt;/h2&gt;

&lt;p&gt;Always accessing your data via UDFs is generally a best practice with Fauna. Only allow clients to call specific UDFs, and prevent client-side calls to primitive operations like &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/create"&gt;Create&lt;/a&gt;, &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/match"&gt;Match&lt;/a&gt;, &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/update"&gt;Update&lt;/a&gt;, and &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/delete"&gt;Delete&lt;/a&gt;. This separates your business logic from the presentation layer, which provides several benefits. UDFs can be unit-tested independently of the client, ensuring correctness. Because UDFs encapsulate your business rules, you can write them once and call them from different clients, platforms, and programming languages.&lt;/p&gt;

&lt;p&gt;The following diagram represents a client side call to the FQL &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/create"&gt;&lt;code&gt;Create()&lt;/code&gt; primitive&lt;/a&gt; to create a document in a collection called &lt;em&gt;notifications&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/po4qc9xpmpuh/2nnwJu7T77LfiY4BZujj8c/7bfc42b237f4bca3a7ebba31dd7c01ef/client-side-create-primitive.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/po4qc9xpmpuh/2nnwJu7T77LfiY4BZujj8c/7bfc42b237f4bca3a7ebba31dd7c01ef/client-side-create-primitive.png" alt="Client side create primitive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suppose you receive a new requirement to add a field to each document that indicates whether a notification is urgent? You can modify the client code and publish a new version, but what about users who do not upgrade? What if the field is required to work with a downstream dependency? How do you handle existing documents and outdated clients? This single change can cascade and create additional complexity, which can lead to errors. That complexity multiplies when you combine multiple changes.&lt;/p&gt;

&lt;p&gt;Instead, create a UDF, &lt;em&gt;create_notification&lt;/em&gt;, and call that UDF directly from your client code with an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/functions/call"&gt;FQL &lt;code&gt;Call()&lt;/code&gt; statement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/po4qc9xpmpuh/5SjIRVq2nScxdlZ6M4loyj/c0425085fffac41bba913c32acbcbdc3/initial-create-notification-udf.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/po4qc9xpmpuh/5SjIRVq2nScxdlZ6M4loyj/c0425085fffac41bba913c32acbcbdc3/initial-create-notification-udf.png" alt="Initial create notification UDF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this change, newer versions of the UDF can accept calls from any version of the client. If the UDF expects a field and it is not provided, the UDF can set a reasonable default or calculated value and continue to completion. The second post in this series constructs a more complete example and shows how to test for existence of fields in UDF calls.&lt;/p&gt;

&lt;p&gt;UDF-first development provides a number of other benefits, including support for attribute-based access control (ABAC), feature flags, versioning, and more. See &lt;a href="https://fauna.com/blog/getting-started-with-fql-faunadbs-native-query-language-part-4"&gt;this guide&lt;/a&gt; for additional information on UDFs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating in steps
&lt;/h2&gt;

&lt;p&gt;Migrating in steps minimizes the risk of migrations and can allow for continuous uptime. Where possible, each step should change only one aspect of your database structure or data. A general pattern for updating the data type of a field has three steps: populating the values of the new field from existing data, confirming zero defects, and optionally removing any deprecated fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  Populating new field values
&lt;/h3&gt;

&lt;p&gt;The first step in a migration is creating a UDF that populates the value of your new field from existing data according to your business logic. The new value can be calculated using existing fields in the document, a calculated value, or other reasonable default.&lt;/p&gt;

&lt;p&gt;Consider a field &lt;em&gt;ipRange&lt;/em&gt; that stores a range of IP addresses in &lt;a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing"&gt;CIDR notation&lt;/a&gt; as an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#string"&gt;FQL string&lt;/a&gt;. You can create a new field &lt;em&gt;ipRanges&lt;/em&gt; that stores the existing value as an &lt;a href="https://docs.fauna.com/fauna/current/api/fql/types#array"&gt;FQL array&lt;/a&gt; without modifying the existing &lt;em&gt;ipRange&lt;/em&gt; field.&lt;/p&gt;

&lt;p&gt;You should call this UDF any time you access documents that have not yet been migrated, regardless of which &lt;a href="https://fauna.com/blog/evolving-the-structure-of-your-fauna-database#choosing-a-data-update-strategy"&gt;data update strategy&lt;/a&gt; you choose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirming zero defects
&lt;/h3&gt;

&lt;p&gt;Next, create a second UDF that compares the value of your new field to the existing values. This UDF ensures that your data is migrated correctly. When you call this UDF varies based on your chosen &lt;a href="https://fauna.com/blog/evolving-the-structure-of-your-fauna-database#choosing-a-data-update-strategy"&gt;data update strategy&lt;/a&gt;. Regardless of the strategy you choose, this UDF gives you the confidence that your migration succeeded on the document level. It also allows you to remove deprecated fields in the next step without worrying about data loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing deprecated fields
&lt;/h3&gt;

&lt;p&gt;Removing deprecated fields is the final step in a migration. This step is strictly optional, but recommended. Removing deprecated fields reduces your data storage costs, particularly if those fields are indexed. It also removes unnecessary complexity from your data model. This is particularly helpful if you access Fauna via GraphQL, as you must explicitly define each field in your schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a data update strategy
&lt;/h2&gt;

&lt;p&gt;You choose when to update your data, and how much of your data to update, based on your own use case and access patterns. There are three general data update strategies: just-in-time, immediate, and throttled batch updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Just-in-time updates
&lt;/h3&gt;

&lt;p&gt;Just-in-time (JIT) updates wait until the first time a document is retrieved or altered to apply outstanding migrations. JIT updates check whether the specified document has been migrated and, if not, calls the UDF you specify before proceeding. The UDF-first pattern described in &lt;a href="https://fauna.com/blog/evolving-the-structure-of-your-fauna-database#encapsulating-your-data"&gt;Encapsulating your data&lt;/a&gt; enables JIT updates without requiring the client to know a change has been made.&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/po4qc9xpmpuh/1VA17L8jNVOZVNuqQP5Ptm/1e000b6cb1cbce7f4e89e4e2fd9a480d/post-migration-create-notification-udf.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/po4qc9xpmpuh/1VA17L8jNVOZVNuqQP5Ptm/1e000b6cb1cbce7f4e89e4e2fd9a480d/post-migration-create-notification-udf.png" alt="Post migration create notification UDF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JIT updates are most appropriate for use cases where documents are accessed one at a time or in very small groups. They are especially good for documents with the optional &lt;code&gt;ttl&lt;/code&gt; (time-to-live) field set, as these documents may age out of your database before they need to be migrated. &lt;/p&gt;

&lt;p&gt;If you regularly retrieve many documents in a single query, JIT updates can degrade the performance of your query. In this case, you should choose between immediate and throttled batch updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immediate updates
&lt;/h3&gt;

&lt;p&gt;Immediate updates greedily apply your migration UDF to every matching document in your database in a single Fauna transaction. This simplifies future document retrieval, and maintains the performance of queries that return large data sets.&lt;/p&gt;

&lt;p&gt;However, immediate updates require you to access and modify every document affected by your migration at once. If you infrequently access large portions of your data, this can create unnecessary costs. If you have indexes over the existing or new fields, these indexes must also be re-written along with your updated documents. &lt;/p&gt;

&lt;p&gt;See &lt;a href="https://fauna.com/blog/evolving-the-structure-of-your-fauna-database#assessing-the-impact-on-your-indexes"&gt;Assessing the impact on your indexes&lt;/a&gt; for additional consideration for indexes and migrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throttled batch updates
&lt;/h3&gt;

&lt;p&gt;Throttled batch updates provide the benefits of JIT and immediate updates, but do so at the cost of additional complexity. Throttled batch updates use an external program to apply your migration UDF to groups (or "batches") of documents at a slower rate. You manipulate that rate by modifying either the number of documents in each batch or the period of time between each batch. This enables you to tune the time to completion, allowing you to migrate your entire data set without imposing any performance penalties.&lt;/p&gt;

&lt;p&gt;If a request is made to access or alter a document that has not been migrated while your batch is still processing, you apply your migration UDF to that document first, just as you do with a JIT update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assessing the impact on your indexes
&lt;/h2&gt;

&lt;p&gt;You cannot modify the &lt;code&gt;terms&lt;/code&gt; or &lt;code&gt;values&lt;/code&gt; of indexes, including &lt;a href="https://docs.fauna.com/fauna/current/api/fql/indexes#binding"&gt;binding objects&lt;/a&gt;, once they have been created. If you have an index over a previously existing field and want to use it for the newly migrated field, you must create a new index.&lt;/p&gt;

&lt;p&gt;Fauna also limits concurrent index builds to 24 per account, not per database. Attempting to exceed this limit results in an HTTP 429 error that your application must handle. Additionally, index builds for collections with more than 128 documents are handled by a background task. This means that your transaction will complete successfully quickly, but you will not be able to query the index until it has finished building.&lt;/p&gt;

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

&lt;p&gt;Successful data migrations depend heavily on planning. Use UDFs to encapsulate business logic, including performing any necessary data migrations. Break your migration in small steps with duplicated results to reduce risk at each stage. Finally, assess the impact on your indexes and develop a strategy for updating your data.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/fauna/evolving-the-structure-of-your-fauna-database-5704"&gt;next post in this series&lt;/a&gt;, you learn how to implement the previous migration pattern using UDFs written in FQL.&lt;/p&gt;

</description>
      <category>fauna</category>
      <category>database</category>
      <category>migrations</category>
    </item>
  </channel>
</rss>
