Search code examples
faunadb

How to handle concurrency in faunadb


I've some backend APIs which connect to faunadb; I'm able to do everything I need with data but I've some serious doubts about concurrent modifications (which maybe are not strictly related to faunadb only, but I'd like to understand how to deal with it using this technology).

One example above all: I want to create a new document (A) in a collection (X) which is linked (via reference or other fields) to other documents (B and C) in another collection (Y); in order to be linked, these documents (B and C) must satisfy a condition (e.g. field F = "V"). Once A has been created, B and C cannot be modified (or the condition will be invalidated!). Of course the API to create the document A can run concurrently with the API used to modify documents B and C.

Here comes the doubt: what if, while creating the document A linked to document B and C, someone else changes field F of document B to something different from "V"? I could end up with A linked to a wrong document, because both APIs don't know what the other one is doing..

Do I need to use the "Do" function in both APIs to create atomic transactions? So I can:

  1. Check if B and C are valid and, if yes, create A in a single transaction
  2. Check if B is linked to A and, if it doesn't, modify it in a single transaction

Thanks everyone.


Solution

  • Fauna tries to present a consistent data view no matter or when your clients need to ask. Isolation of transaction effects is what matters on short time scales (typically less than 10ms).

    The Do function merely lets you combine multiple disparate FQL expressions into a single query. There is no conditional processing aspect to Do.

    You can certainly check conditions before undertaking operations, and all Fauna queries are atomic transactions: all of the query succeeds or none of its does.

    Arranging for intermediate query values in order to perform conditional logic does tend to make FQL queries more complex, but they are definitely possible:

    The query for your first API might look something like this:

    Let(
      {
        document_b: Get(<reference to B document>),
        document_c: Get(<reference to C document>),
        required_b: Select(["data", "required_field"], Var("document_b"),
        required_c: Select(["data", "other_required"], Var("document_c"),
        condition: And(Var("required_b"), Var("required_c")),
      },
      If(
        Var("condition"),
        Create(Collection("A"), { data: { <document A data }}),
        Abort("Cannot create A because the conditions have not been met.")
      )
    )
    

    The Let function allows you to compose named values for intermediate expressions, which can read or write whatever they need, along with logical operations that determine which conditions need to be tested. The value composition is followed by an expression which, in this example, tests the conditions and only creates the document in the A collection when the conditions are met. When the conditions are not met, the transaction is aborted with an appropriate error message.

    Let can nest Lets as much as required, provide the query fits within the maximum query length of 16MB, so you can embed a significant amount of logic into your queries. When the length of a single query is not sufficient, you can define UDFs that can be called, which allow you to store business logic that you can use any number of times.

    See the E-commerce tutorial for a UDF that performs all of the processing required to submit an order, check if there is sufficient product in stock, deduct requested quantities from inventory, set backordered status, and create the order.