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.
{
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.
{
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:
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!