In this page
Namespaces
Worlds let one GreyCat server run several graphs concurrently. Each world is a namespace: the same program, the same types and the same API, but its own data. The server, its workers, its memory budget and its storage stay shared, so ten namespaces in one server cost far less than ten servers.
Typical uses:
- the data of several stakeholders in one server: each customer, site or partner works in its own world and never sees the others’ data
- a sandbox next to production, to try an import or a migration on a copy of the real data
- a reference graph shared by everyone (a power grid topology, a product catalogue) with one world per team that adds its own data on top
Choosing the mode
| Need | Mode | Base |
|---|---|---|
| Data that shares nothing with the other namespaces | standalone |
none |
| Common reference data, plus each namespace’s own changes | overlay |
main, or a reference world |
| A frozen copy of the production data | snapshot |
main |
A standalone world starts like a new store: its module variables are empty nodes of its own. An overlay world over a reference reads the reference for everything it has not modified, so an update of the reference reaches every namespace at once, except for the nodes a namespace changed itself.
Onboarding a stakeholder
A stakeholder gets a world and an identity limited to it:
@role("modeler", "public", "api", "world");
@expose
@permission("admin")
fn onboard(tenant: String, user: String, password: String) {
World::create(tenant, WorldMode::standalone, null);
Identity::create_with_password(user, "modeler", password);
Identity::set_world(user, tenant); // the world a client should send
Identity::set_worlds(user, Array<String> { tenant }); // the worlds its calls may run in
}
From then on, the calls of modeler-a run in tenant-a when they carry the world: tenant-a header:
curl -H "Authorization: $TOKEN" -H "world: tenant-a" -X POST -d '[]' http://localhost:8080/project::count
# 1
curl -H "Authorization: $TOKEN" -H "world: tenant-b" -X POST -d '[]' http://localhost:8080/project::count
# 403 {"message":"the caller may not use the world of the `world` header"}
The same settings are available from the command line:
greycat user world modeler-a tenant-a # Identity::set_world("modeler-a", "tenant-a")
greycat user worlds modeler-a tenant-a # Identity::set_worlds("modeler-a", ["tenant-a"]); "-" for every world
greycat user show modeler-a
The two settings of an identity
| Setting | Meaning | Default |
|---|---|---|
Identity::set_world(name, world) |
The identity’s default world. It only describes the identity: the server reads it for no call. A client reads Identity::current().world after login and sends it in the world header. |
null: main |
Identity::set_worlds(name, worlds) |
The worlds the identity’s calls may run in, besides main. A call landing in another world fails: 403 over HTTP, an error from spawn and Scheduler::add. |
null: every world. []: main only |
Two kinds of callers use a world whatever the list says: an identity holding admin, and the world’s creator. World::list() returns only the worlds the caller may use.
Every identity may use main. A call without a
worldheader runs in main, whatever the identity’s default world. For a strict multi-tenant server, keep main free of tenant data (reference data only), or checkWorld::current()in the exposed functions that must not run in main.
Permissions
The world permission lets an identity create worlds, and reset or remove the worlds it created. The admin role holds it; the built-in user role does not. Grant it through a role of your own, like modeler above. A creator may use the worlds they created, so a modeler can fork their own scenarios from their namespace:
@expose
fn what_if(name: String) {
// base the scenario on the caller's namespace, not on main
World::create(name, WorldMode::overlay, World::current());
}
The
baseargument names the base explicitly:nullmeans main, even when the task runs in another world. PassWorld::current()to fork the world you are in.
Sharing a scenario
A world is not tied to the task that filled it. Once a scenario is computed, keep the world and let others read it:
- Allow the readers to use it:
Identity::set_worlds("reader-b", Array<String> { "tenant-a", "tenant-a-whatif" }). - They call the same API with the
world: tenant-a-whatifheader, and see the scenario as if it were the real data: dashboards, charts and exports work unchanged. - Show them what the scenario changed with the World Diff tools.
Sharing a world shares its data, not a copy of it. A shared overlay world keeps following its base for the nodes it did not modify, so the scenario stays up to date with the real data it did not change.
Removing a world needs its creator or an admin, and fails while another world is based on it or a task still runs in it.