Using the Document Store

This page explains everything you need to know when developing a Cloud Function that makes use of the Document Store.

What is the Document Store?

A Document Store is a type of database designed to manage data in flexible, self-contained records. Instead of using rigid tables with fixed columns, it stores information in a format that allows each record to have its own unique structure based on the developer's needs.

Each Cloud Function has the following characteristics in relation to the Document Store:

  • It has its own, separate store and can only read and write data to its own store.
  • It can only access a single store, meaning that a DB collection is not shareable among Cloud Functions.
  • The document store can accept any data in JSON format. This means that if you store documents in the store, for example via a Cloud Function, you must ensure that your data can be converted to JSON.
  • A local document store is automatically provided for you when you develop your Cloud Function.
  • The data will be available until the function deletes it, since it's based on persistent storage.
📘

Store Availability

  • The store is not available from the developer web form (IXON CDK Ingress) (see the picture in this section), but it can be called via a Cloud Function by using the Function field as explained in the article.
  • When developing, the local store is reset at each restart.
  • The store is only available from ixoncdkingress>=0.0.10

When starting your Cloud Function, you should see the following messages:

ixoncdkingress - INFO - Starting DocumentDB server
ixoncdkingress - INFO - DocumentDB server started successfully

These messages show that the document store has successfully started and can be used in your Cloud Function.

How to use the DocumentDBClient

The DocumentDBClient can be extracted from the Cloud Function Context by accessing document_db_client from the context:

from ixoncdkingress.function.context import FunctionContext

@FunctionContext.expose
def document_test_entrypoint(context: FunctionContext):
    print(context.document_db_client)

From here you can perform a variety of functions to create, read, update and delete documents in your document store.

🚧

Nullability

As mentioned above, the store is not always available. So the document_db_client can be None. Be sure to check for this when developing your Cloud Function.

The store can be None in these scenarios:

  • The document_db_client may be None when running the Cloud Function in development mode and using the local debug form without specifying a company. A company ID and a company name are the only required fields:

If the DocumentDBClient is available, you will see something like <DocumentDBClient company=4444-5555-3333-1111-9999,> printed in the console.

  • When running the cloud function in development mode, a MongoDB container is automatically spun up. This can fail and if the MongoDB container fails to boot or if there is any other reason why the local function cannot connect to the container, the store will be None.
❗️

Timezones

All dates, times and datetimes will be returned as naive objects (i.e. unaware of timezone) by the DocumentDBClient. All times and datetimes are stored in UTC and should be interpreted as such. When storing times and datetimes in Document Store, always use UTC.

The DocumentDBClient contains the following functions:

FunctionParametersDescriptionMinimal ixoncdkingress version
insert_onedocument: DictInserts a single document into the store0.0.10
insert_manydocument: Iterable[Dict]Inserts multiple documents into the store0.0.10
update_onefilter_map: Mapping, update: MappingUpdates a single document from the store0.0.10
update_manyfilter_map: Mapping, update: MappingUpdates multiple documents from the store0.0.10
delete_onefilter_map: MappingRemoves one document from the store0.0.10
delete_manyfilter_map: MappingRemoves multiple documents from the store0.0.10
find_onefilter_map: MappingSame as find but only returns a single document Note: common method for Timeseries clients, inherited from PyMongo.0.0.10
findfilter_map: MappingPerform a query on the database with a specific filter Note: common method for Timeseries clients, inherited from PyMongo.0.0.10

The filter_map can be used to find specific documents by their keys, while the update Mapping can be used to update specific keys of one or more documents. Here is an example of some of the functions you can use:

from typing import List, TypedDict
from bson.json_util import dumps

from ixoncdkingress.function.context import FunctionContext

@FunctionContext.expose
def document_find_text(context: FunctionContext, id: int) -> str | None:
    # Find one document by its ID.
    # Return None if it doesn't exist, otherwise return its JSON representation.
    document = context.document_db_client.find_one(filter_map={'id': id})
    if document is None:
        return None
    return dumps(document)

@FunctionContext.expose
def document_find_all(context: FunctionContext) -> str:
    # Find all documents in the store
    return dumps(context.document_db_client.find())

@FunctionContext.expose
def document_add_or_update_text(context: FunctionContext, id: int, text: str) -> None:
    # Check whether the document already exists
    existing = context.document_db_client.find_one(filter_map={'id': id})

    # If the document already exists, update its text;
    # otherwise insert a new document.
    if existing is not None:
        context.document_db_client.update_one({'id': id}, {'$set': {'text': text}})
    else:
        context.document_db_client.insert_one({'id': id, 'text': text})

class Document(TypedDict):
    id: int
    text: str

@FunctionContext.expose
def document_insert_many(context: FunctionContext, documents: List[Document]) -> None:
    # Insert the documents
    context.document_db_client.insert_many(documents)
<script>
  import { onMount } from "svelte";

  export let context; // Assuming context is passed in as a prop for backend interactions
  let client; // Initialize the client for backend communication

  // Modular function to call backend functions
  async function callBackendFunction(functionName, parameters = {}) {
    try {
      const response = await client.call(functionName, parameters);
      console.log(response);
      return response; // Return response for further processing
    } catch (error) {
      console.error(`Error calling ${functionName}:`, error);
    }
  }

  onMount(() => {
    client = context.createBackendComponentClient();

    // Sequentially call backend functions with await inside async function
    (async () => {
      await callBackendFunction("functions.document_find_all");
      await callBackendFunction("functions.document_add_or_update_text", {
        id: 0, // Example ID, ensuring it's an integer to match Python backend
        text: "Hello, World!",
      });
      await callBackendFunction("functions.document_find_all");
      await callBackendFunction("functions.document_find_text", {
        id: 0,
      });
      await callBackendFunction("functions.document_insert_many", {
        documents: [
          { id: 1, text: "First text" },
          { id: 2, text: "Second text" },
        ],
      });
      await callBackendFunction("functions.document_find_all");
    })();
  });
</script>

<main>
  <h1>Frontend Component</h1>
  <p>Open the terminal to see the results of the backend calls.</p>
</main>

From here, you can play around with inserting, reading, updating and deleting documents.

Inspecting the Document Store contents

You can easily inspect the contents of your local Document Store using MongoDB Compass GUI or MongoDB For VS Code.


What’s Next

To read more about advanced document querying please read: