How It Works

The To-Do web app example continues, and we take a look at the benefits Itergia Core offers when dealing with multiple clients.

Connecting Two Clients

In Using It, we showed how to build a To-Do list app with Itergia Core. The implementation was a straight-forward web app.

Here is the example in two clients, allowing you to set devices off line:

Querying Off-Line

When you perform a query, you are also telling the client to start subscribing to changes to affected objects. With reactive web frameworks, the result is an array that behaves as if you were re-issuing the query all the time.

While offline, new queries will return failure by default, rather than returning partial results. Itergia Core provides a locally consistent view of data, to make reasoning about query results easier, while accepting that different clients may have different views at any one time.

Mutating Off-Line

If the user performs an action while the client is offline, the mutation is applied locally, and queued for transmission on reconnect.

You can get the status of any individual applied mutation, to see if it is pending, applied/rejected locally, or applied/rejected at the authoritative cell. Rejected applications are the ones that caused some error (e.g. the mutator function threw an exception.)

Local applications are tentative, and the authoritative cell may create a different result given the same mutation, e.g. if other clients have made changes in the meantime. Itergia Core does not mandate Conflict-Free Replicated Data Types, but they are the natural choice in distributed systems.

Modelling the real intent is important, and Itergia Core generates stable identifiers to help.

Reconnecting

When the client reconnects to the server, it transmits the locally queued mutations. Once the authoritative cell has applied (or rejected) all of them, the client fetches all relevant changes since it became offline.

As long as you are subscribed to queries, and use stable identifiers for lists and items, there is nothing you have to do to support it.

Permissions and Multiple Users

The step beyond the schema owner being the only allowed account, is assigning ownership to lists.

In Itergia Core, users either have no access, read-only or read/write access to a complete object. A user without read access will simply not see the object, as if it didn't exist.

We add a field carrying the owner account identifier, and a definition for per-list access scopes.

message List {
  // ...
  itergia.Principal owner = 3;
}

message ListAccessScope {
  string list_id = 1;
}

Then we add security policies to both assign the owner's list permission, and limit read access.

Using the built-in delegation system, once you have defined the access scopes, delegations can be used to create groups of users, roles and other hierarchies.

# ...

access_scope_types:
  list:
    message_name: todo.ListAccessScope

object_types:
- name: List
  authorization:
    computed_scopes:
    - account_principal: fields.owner
      binding:
        kind: list
        parameters:
          list_id: fields.id

    required_scopes:
    - binding:
        kind: list.read
        parameters:
          list_id: fields.id

intents:
- name: addItem
  authorization:
    required_scopes:
    - binding:
        kind: list.additem
        parameters:
          list_id: fields.listId

# ...

Transit Proxy

Itergia Core supports paths of cells longer than just one, we call it a transit path. Along the way from the client to the authoritative cell, transit cells may cache and apply mutations, just like the client itself does. This allows you to model failure domains, e.g. the devices within one building continuing to work together, even if the upstream connection is down.

The only change required, is pointing the client to the transit cell, analogously to configuring an HTTP proxy:

const client = new ItergiaClient({ transit: "transitcell.example.com" });

In Itergia Core, each transit cell has access to the data of each user who has configured it, but no more.

Summary

Our aim is not to tell you the one true way of writing collaborative apps with off-line support, but to provide one simple way of doing it. A method that is easy to reason about, while still giving powerful benefits in the long run. A data storage and synchronization platform that scales from prototype to production with minimal fuzz.

Itergia Core is under active development, and we value any feedback and use-cases you want to share.