Skip to main content

Persistence and Collaboration

Introduction

By default Variable state is purely client-side, meaning its source of truth is the browser memory. It also means that refreshing the browser page resets Variable state, and that each user has their own "copy" of the data. To customize the behavior, Variable accepts a store property. This allows changing Variable's source of truth, thereby enabling features like data persistence and collaborative interactions across users.

The rest of the page goes in-depth into available stores and how they can be used in your application.

BackendStore

This type of store changes the source of truth for a given Variable to live on the server, rather than on the client. This has two main benefits:

  • the data can be persistent across page refreshes or even across server restarts
  • since the data lives server-side, we can "sync" it across clients to enable collaboration

To use a BackendStore, simply create a store instance and attach it to a Variable:

from dara.core import Variable
from dara.core.persistence import BackendStore
from dara.components import Input

# Tag the variable to be "collaborative" and have a shared server state
collab_variable = Variable(default=1, store=BackendStore())

# Input value will automatically be kept in sync "live" for all users
Input(value=collab_variable)

Under the hood the BackendStore mechanism is very simple:

  • the store data is initialized with the default Variable value
  • on initial page load, the Variable value is fetched from the server
  • whenever a client makes an update, it makes a request to the server to update the state, which then notifies all other clients about the new state

You can interact with the store programmatically to read, write or delete data from the store.

from dara.core import Variable
from dara.core.persistence import BackendStore
from dara.components import Input

store = BackendStore()
collab_variable = Variable(default='default value', store=store)

# get current value in the store
value = await store.read()
# store can also be accessed from the variable
value = await variable.store.read()

# write a new value to the store
await store.write('new value')

# delete the existing value from the store
await store.delete()

# get all value from the store - see `scope` section below for details
await store.get_all()

The behavior of the store can be customized by passing arguments to the BackendStore.

scope

Scope controls whether the data in a BackendStore is shared among all users (scope='global') or separate and specific to a given user (scope='user').

When using global scope, the state is shared and synced among the users so a change being made to an attached Variable in one client will immediately be propagated to other users. In this mode, the get_all API method will return a dictionary of the shape {'global': 'value'}.

When using user scope, each user has their own copy of the data. A change made to an attached Variable in one client will only be propagated to other sessions open for that particular user, e.g. among all currently open tabs for that user. In this mode, the get_all API returns a dictionary with user values keyed by the user identifiers, e.g. {'user1': 'value1', 'user2': 'value2'}.

danger

In user scope, the API methods read, write and delete can only be used in an authenticated context, i.e. within py_components, DerivedVariable resolvers or actions. This is because these APIs will look up the currently operating user and act on their behalf, e.g. write would update the value stored only for that particular user.

backend

You can select a particular backend implemention to be used for storage. By default BackendStore uses the InMemoryBackend implementation which simply stores the data in-memory.

Alternatively, you can use the FileBackend which uses a JSON file for storage. This enables persisting the data across server restarts and easy access to the data for external processes.

from dara.core import Variable
from dara.core.persistence import BackendStore, FileBackend
from dara.components import Input

collab_variable = Variable(
default='default value',
store=BackendStore(
backend=FileBackend(path='path/to/file.json')
)
)

You can also choose to provide a custom backend implementation as long as it extends the PersistenceBackend class.