Get started with computed fields

The following tutorial demonstrates the capabilities and utility of Computed Fields. This tutorial uses a hypothetical e-commerce application as an example. In the application, there are three main data models, Customer, Product and Order.

One of the new business requirements is to implement a customer rewards program. To implement this, you store customer award points in individual Customer documents in points field. Following is an example of what a customer document looks like.

{
  id: "359752336484077633",
  coll: Customer,
  ts: Time("2024-01-31T17:13:41.762Z"),
  name: "Susan Nelson",
  address: {
    street: "5677 Strong St.",
    state: "CA",
    city: "San Rafael",
    country: "USA",
    zip: 97652
  },
  points: 120
}

The business requirement is to give customers gold status when they acquire over 100 points. You can use Fauna computed fields to check if a Customer has reached a gold status. The following FSL code demonstrates how to define a computed field to calculate a customer status.

collection Customer {
  compute status:String = (
    doc => (
        if( doc.points > 100 ) {
            "gold"
        } else {
            "standard"
        }
    )
  )

  index byStatus {
    terms [.status ]
  }
}

Notice that in the previous FSL code block, an index called byStatus is added. With this index, you can query customers by their status.

Following is an example of querying all customers with gold status.

Customer.byStatus( "gold")
{
  data: [
    {
      id: "359752336484075585",
      coll: Customer,
      ts: Time("2024-01-31T23:29:41.920Z"),
      status: "gold",
      name: "Robert Carter",
      address: {
        street: "5677 Strong St.",
        state: "CA",
        city: "San Francisco",
        country: "USA",
        zip: 94106
      },
      balance: 134,
      age: 54,
      points: 120
    },
    {
      id: "359752336484077633",
      coll: Customer,
      ts: Time("2024-01-31T23:29:41.920Z"),
      status: "gold",
      name: "Susan Nelson",
      address: {
        street: "5677 Strong St.",
        state: "CA",
        city: "San Rafael",
        country: "USA",
        zip: 97652
      },
      balance: 19,
      age: 42,
      points: 148
    }
  ]
}

Advanced usage of Computed Fields

You can also return sub queries in the computed fields. The following query example demonstrates this. The query returns recent orders for a customer and the customer information.

First, define a computed field for order with the following FSL code.

collection Customer {

  compute orders:Array<Order> = (
    doc => { Order.byCustomer( doc ).take(5).toArray() }
  )
}

Then, query a customer.

Customer.byName("Susan Nelson"){ name, address, orders }
{
  data: [
    {
      name: "Susan Nelson",
      address: {
        street: "5677 Strong St.",
        state: "CA",
        city: "San Rafael",
        country: "USA",
        zip: 97652
      },
      orders: [
        {
          id: "359752336494562369",
          coll: Order,
          ts: Time("2023-11-01T21:34:29.356Z"),
          customer: Customer.byId("359752336484077633"),
          status: "pending",
          created: Time("2023-03-20T20:50:13.496678Z"),
          product: Product.byId("359752336480927809")
        },
        {
          id: "359752336494566465",
          coll: Order,
          ts: Time("2023-11-01T21:34:29.356Z"),
          customer: Customer.byId("359752336484077633"),
          status: "pending",
          created: Time("2023-03-20T20:50:13.496678Z"),
          product: Product.byId("359752336480928833")
        },
        {
          id: "359752336495610945",
          coll: Order,
          ts: Time("2023-11-01T21:34:29.356Z"),
          customer: Customer.byId("359752336484077633"),
          status: "error",
          created: Time("2023-03-20T21:34:29.068515Z"),
          product: Product.byId("359752336481976385")
        }
      ]
    }
  ]
}

This example outlines the advantages of utilizing Computed Fields, illustrating the potential limitations and challenges associated with embedding customer orders directly in the customer document.

Limitations of direct embedding

Scalability Concerns

Direct embedding restricts the number of orders that can be stored, which limits the ability to scale the system.

Increased Read/Write Overhead

The growth in document size by adding orders increases the resource demands for read and write operations.

Complexity in Data Management

Maintaining the embedded orders requires complex application logic, increasing the potential for errors and data management challenges.

Benefits of using Computed Fields

Enhanced Scalability

By using Computed Fields, the system can handle an increasing number of orders without affecting the size of the customer document, enhancing scalability.

Optimized Resource Utilization

Computed Fields minimize the need for large read and write operations, improving system performance and reducing operational costs.

Simplified Application Logic

Offloading data management tasks to Computed Fields simplifies the application code, reducing complexity and the risk of errors.

No data migrations are required. The query response is a single JSON Document, ready to be consumed by the application, without the must map multiple rows with redundant columns into an object.

Although Computed Fields can be indexed, it is a requirement that the anonymous function defined in the computed field must be a pure function to be indexed. This means you can not index a computed field performing a read. If you try to define an index on the "orders" field in the above e-commerce example, the Fauna service rejects that schema change with the following error:

Error: Invalid database schema update.
    error: Computed fields used in indexes or constraints cannot access collections
    at *computed_field:Customer:orders*:2:3
      |
    2 |   Order.byCustomer(doc).take(5).toArray()
      |   ^^^^^
      |

Best practices for adding Computed Fields

There are important considerations when adding computed fields to an existing collection. Suppose you want to add the following computed field to the Customer collection:

compute nameFirstChar = (
  doc => { doc.name.toUpperCase().slice(0, 1) }
)

The name field is nullable. If documents in the Customer collection have no name field, the field’s value is null. This causes an error:

Customer.all()
invalid_null_access

error: Cannot access `toUpperCase` on null.
at *computed_field:Customer:volume*:2:12
  |
2 |   doc.name.toUpperCase().slice(0, 1)
  |            ^^^^^^^^^^^
  |
hint: Null value created here
  |
2 |   doc.name.toUpperCase().slice(0, 1)
  |       ^^^^
  |

The full collection scan failed because the computed field depends on a field that doesn’t exist in at least one document in the collection.

To avoid this issue, you can use the optional chaining operator. The following FSL code demonstrates how to use the optional chaining operator to avoid the error.

compute volume = (
  doc => { doc.name?.toUpperCase().slice(0, 1) }
)

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!