01 Tutorials

Languages:

Introduction

Many databases systems provide multi-tenant capabilities. They can contain multiple databases each with their own access controls. FaunaDB takes this much further by allowing any database to have multiple child databases. This enables an operator to manage a single large FaunaDB cluster, create a few top-level databases, and give full administrative access of those databases to associated teams. Each team is free to create as many databases as they need without requiring operator intervention. As far as the team is concerned, they have their own full FaunaDB cluster.

With the QoS system administrators can set the priority of their databases. An operator can then prioritize some products over others and concern themselves only with providing enough resources that all product needs are met. There is no need to provision, create, and maintain multiple, individual clusters.

This tutorial will walk through how multiple databases and child databases are created and maintained. In this example a company is using FaunaDB for customer data as well as internal data.

Create Top Level Databases

First, the operator will need to create a set of top-level databases that can be handed over to individual teams. In the following example we will create a “production” database and an “internal” database using the configured secret.


curl https://db.fauna.com/ \
    -u fnACQtTKDnAAAFV9J1ltUXeor90_XXgT560jad1-: \
    -d '{
          "map": {
            "lambda": "name",
            "expr": {
              "create_database": { "object": { "name": { "var": "name" } } }
            }
          },
          "collection": [ "production", "internal" ]
        }'

FaunaClient adminClient = FaunaClient.builder()
  .withSecret(adminKey)
  .build();

adminClient.query(
  Map(
    Arr(Value("production"), Value("internal")),
    Lambda(
      Value("name"),
      CreateDatabase(Obj("name", Var("name"))))));

var adminClient = new faunadb.Client({
  secret: adminKey
});

adminClient.query(
  q.Map(
    ["production", "internal"],
    function(name) {
      return q.CreateDatabase({ name: name });
    }));

val adminClient = FaunaClient(secret = adminKey)

adminClient.query(
  Map(
    Arr("production", "internal"),
    Lambda { name => CreateDatabase(Obj("name" -> name)) }))

$admin_client = Fauna::Client.new(secret: adminKey)

$admin_client.query do
  map ['production', 'internal'] do |name|
    create_database(name: name)
  end
end

var adminClient = new FaunaClient(secret: adminKey);

adminClient.Query(
  Map(
    Arr("production", "internal"),
    name => CreateDatabase(Obj("name", name))));

adminClient = FaunaClient(secret=adminKey)

adminClient.query(
  q.map_expr(
    lambda name: q.create_database({"name": name}),
    ["production", "internal"]
  ))

adminClient = f.NewFaunaClient(adminKey)

adminClient.Query(
    f.Map(
        f.Arr{"production", "internal"},
        f.Lambda(
            "name",
            f.CreateDatabase(f.Obj{"name": f.Var("name")}),
        ),
    ),
)

let adminClient = FaunaDB.Client(secret: adminKey)

adminClient.query(
    Map(
        collection: Arr("production", "internal"),
        to: { name in CreateDatabase(Obj("name" => name)) }
    )
)

HTTP/1.1 200 OK
{
  "resource": [
    {
      "ref": { "@ref": "databases/production" },
      "class": { "@ref": "databases" },
      "ts": 1436375112141542,
      "name": "production"
    },
    {
      "ref": { "@ref": "databases/internal" },
      "class": { "@ref": "databases" },
      "ts": 1436375112141542,
      "name": "internal"
    }
  ]
}

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

=> [
  {
    "ref": { "@ref": "databases/production" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "production"
  },
  {
    "ref": { "@ref": "databases/internal" },
    "class": { "@ref": "databases" },
    "ts": 1436375112141542,
    "name": "internal"
  }
]

Next we will create an admin key for each database that can be handed to each team.


curl https://db.fauna.com/ \
    -u fnACQtTKDnAAAFV9J1ltUXeor90_XXgT560jad1-: \
    -d '{
          "map": {
            "lambda": "db",
            "expr": {
              "create_key": {
                "object": { "role": "admin", "database": { "var": "db" } }
              }
            }
          },
          "collection": [ { "database": "production" }, { "database": "internal" } ]
        }'

FaunaClient adminClient = FaunaClient.builder()
  .withSecret(adminKey)
  .build();

Value key = adminClient.query(
  Map(
    Arr(
      Database(Value("production")),
      Database(Value("internal"))
    ),
    Lambda(
      Value("db"),
      CreateKey(
        Obj("role", Value("admin"), "database", Var("db")))))).get(1, TimeUnit.SECONDS); // block thread until key gets created

String internalKey = key.at(1).get(Field.at("secret").to(Codec.STRING));

var adminClient = new faunadb.Client({
  secret: adminKey
});

var internalKey;
adminClient.query(
  q.Map(
    [q.Database("production"), q.Database("internal")],
    function(db) {
      return q.CreateKey({ role: "admin", database: db });
    }))
  .then(function(key) { internalKey = key[1].secret; });

val adminClient = FaunaClient(secret = adminKey)

val keyF = adminClient.query(
  Map(
    Arr(Database("production"), Database("internal")),
    Lambda { db =>
      CreateKey(Obj("role" -> "admin", "database" -> db))
    }))

val key = Await.result(keyF, 1.second) // block thread until key gets created
val internalKey = key(Field(1, "secret").to[String]).get

$admin_client = Fauna::Client.new(secret: adminKey)

key = $admin_client.query do
  map [database('production'), database('internal')] do |db|
    create_key(role: 'admin', database: db)
  end
end

internalKey = key[1][:secret]

var adminClient = new FaunaClient(secret: adminKey);

var key = await adminClient.Query(
  Map(
    Arr(Database("production"), Database("internal")),
    db => CreateKey(Obj("role", "admin", "database", db))));

var internalKey = key.At(1).Get(Field.At("secret").To(Codec.STRING));

adminClient = FaunaClient(secret=adminKey)

key = adminClient.query(
  q.map_expr(
    lambda db: q.create_key({"role": "admin", "database": db}),
    [q.database("production"), q.database("internal")]
  ))

internalKey = key[1]["secret"]

adminClient = f.NewFaunaClient(adminKey)

var internalKey string

key, _ := adminClient.Query(
    f.Map(
        f.Arr{f.Database("production"), f.Database("internal")},
        f.Lambda(
            "db",
            f.CreateKey(
                f.Obj{"role": "admin", "database": f.Var("db")},
            ),
        ),
    ),
)

_ = key.At(f.ArrIndex(1).AtKey("secret")).Get(&internalKey)

let adminClient = FaunaDB.Client(secret: adminKey)

adminClient.query(
    Map(
        collection: Arr(Database("production"), Database("internal")),
        to: { db in
            CreateKey(
                Obj("role" => "admin", "database" => db)
            )
        }
    )
).await(timeout: .now() + 5)

let internalKey: String = key.get(1, "secret")


HTTP/1.1 200 OK
{
  "resource": [
    {
      "ref": { "@ref": "keys/104979509692858368" },
      "class": { "@ref": "keys" },
      "ts": 1436375112199704,
      "role": "admin",
      "database": { "@ref": "databases/production" },
      "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
      "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
    },
    {
      "ref": { "@ref": "keys/104979509693618791" },
      "class": { "@ref": "keys" },
      "ts": 1436375112199704,
      "role": "admin",
      "database": { "@ref": "databases/internal" },
      "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
      "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
    }
  ]
}

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509692858368" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/production" },
    "secret": "kqnPAi3Kj3ZgAAC0Hu51Ng2dtn0JcgP7Fb-Q_uzLGZE",
    "hashed_secret": "$2a$05$.saZ/E/JSuEEkbIfTU/GN.7iKEBBpBGePtWe/mZyL/rmf4uXeZ5oi"
  },
  {
    "ref": { "@ref": "keys/104979509693618791" },
    "class": { "@ref": "keys" },
    "ts": 1436375112199704,
    "role": "admin",
    "database": { "@ref": "databases/internal" },
    "secret": "kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI",
    "hashed_secret": "$2a$05$/FgxoctNLXMBjYAcea/dwOeyYLJtQLGiEdf2S/Lu6ZN7e6Gps6WF6"
  }
]

Per-Team Child Databases

Given the new keys, each team can create databases that fit their needs. In this case, production will not create any child databases, but the internal tools team will create a set of databases for the products they create.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "name",
            "expr": {
              "create_database": { "object": { "name": { "var": "name" } } }
            }
          },
          "collection": [ "personnel", "bulletin-board" ]
        }'

FaunaClient adminClient = FaunaClient.builder()
  .withSecret(internalKey)
  .build();

adminClient.query(
  Map(
    Arr(Value("personnel"), Value("bulletin-board")),
    Lambda(
      Value("name"),
      CreateDatabase(Obj("name", Var("name"))))));

var adminClient = new faunadb.Client({
  secret: internalKey
});

adminClient.query(
  q.Map(
    ["personnel", "bulletin-board"],
    function(name) {
      return q.CreateDatabase({ name: name });
    }));

val adminClient = FaunaClient(secret = internalKey)

adminClient.query(
  Map(
    Arr("personnel", "bulletin-board"),
    Lambda { name => CreateDatabase(Obj("name" -> name)) }))

$admin_client = Fauna::Client.new(secret: internalKey)

$admin_client.query do
  map ['personnel', 'bulletin-board'] do |name|
    create_database(name: name)
  end
end

var adminClient = new FaunaClient(secret: internalKey);

adminClient.Query(
  Map(
    Arr("personnel", "bulletin-board"),
    name => CreateDatabase(Obj("name", name))));

adminClient = FaunaClient(secret=internalKey)

adminClient.query(
  q.map_expr(
    lambda name: q.create_database({"name": name}),
    ["personnel", "bulletin-board"]
  ))

adminClient = f.NewFaunaClient(internalKey)

adminClient.Query(
    f.Map(
        f.Arr{"personnel", "bulletin-board"},
        f.Lambda(
            "name",
            f.CreateDatabase(f.Obj{"name": f.Var("name")}),
        ),
    ),
)

let adminClient = FaunaDB.Client(secret: internalKey)

adminClient.query(
    Map(
        collection: Arr("personnel", "bulletin-board"),
        to: { name in CreateDatabase(Obj("name" => name)) }
    )
)

HTTP/1.1 200 OK
{
  "resource": [
    {
      "ref": { "@ref": "databases/personnel" },
      "class": { "@ref": "databases" },
      "ts": 1436375112257866,
      "name": "personnel"
    },
    {
      "ref": { "@ref": "databases/bulletin-board" },
      "class": { "@ref": "databases" },
      "ts": 1436375112257866,
      "name": "bulletin-board"
    }
  ]
}

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

=> [
  {
    "ref": { "@ref": "databases/personnel" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "personnel"
  },
  {
    "ref": { "@ref": "databases/bulletin-board" },
    "class": { "@ref": "databases" },
    "ts": 1436375112257866,
    "name": "bulletin-board"
  }
]

Finally, we create a server key for each new internal database. These keys will be used within the app fronting the database.


curl https://db.fauna.com/ \
    -u kqnPAi4gDivAAAC0i5t-a9YCcRnljtU2SfMJ-kWSRYI: \
    -d '{
          "map": {
            "lambda": "db",
            "expr": {
              "create_key": {
                "object": { "role": "server", "database": { "var": "db" } }
              }
            }
          },
          "collection": [
            { "database": "personnel" },
            { "database": "bulletin-board" }
          ]
        }'

FaunaClient adminClient = FaunaClient.builder()
  .withSecret(internalKey)
  .build();

adminClient.query(
  Map(
    Arr(
      Database(Value("personnel")),
      Database(Value("bulletin-board"))
    ),
    Lambda(
      Value("db"),
      CreateKey(
        Obj("role", Value("server"), "database", Var("db"))))));

var adminClient = new faunadb.Client({
  secret: internalKey
});

adminClient.query(
  q.Map(
    [q.Database("personnel"), q.Database("bulletin-board")],
    function(db) {
      return q.CreateKey({ role: "server", database: db });
    }));

val adminClient = FaunaClient(secret = internalKey)

adminClient.query(
  Map(
    Arr(Database("personnel"), Database("bulletin-board")),
    Lambda { db =>
      CreateKey(Obj("role" -> "server", "database" -> db))
    }))

$admin_client = Fauna::Client.new(secret: internalKey)

$admin_client.query do
  map [database('personnel'), database('bulletin-board')] do |db|
    create_key(role: 'server', database: db)
  end
end

var adminClient = new FaunaClient(secret: internalKey);

adminClient.Query(
  Map(
    Arr(Database("personnel"), Database("bulletin-board")),
    db => CreateKey(Obj("role", "server", "database", db))));

adminClient = FaunaClient(secret=internalKey)

adminClient.query(
  q.map_expr(
    lambda db: q.create_key({"role": "server", "database": db}),
    [q.database("personnel"), q.database("bulletin-board")]
  ))

adminClient = f.NewFaunaClient(internalKey)

adminClient.Query(
    f.Map(
        f.Arr{f.Database("personnel"), f.Database("bulletin-board")},
        f.Lambda(
            "db",
            f.CreateKey(
                f.Obj{"role": "server", "database": f.Var("db")},
            ),
        ),
    ),
)

let adminClient = FaunaDB.Client(secret: internalKey)

adminClient.query(
    Map(
        collection: Arr(Database("personnel"), Database("bulletin-board")),
        to: { db in
            CreateKey(
                Obj("role" => "server", "database" => db)
            )
        }
    )
)

HTTP/1.1 200 OK
{
  "resource": [
    {
      "ref": { "@ref": "keys/104979509694379214" },
      "class": { "@ref": "keys" },
      "ts": 1436375112316028,
      "role": "server",
      "database": { "@ref": "databases/personnel" },
      "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
      "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
    },
    {
      "ref": { "@ref": "keys/104979509695139637" },
      "class": { "@ref": "keys" },
      "ts": 1436375112316028,
      "role": "server",
      "database": { "@ref": "databases/bulletin-board" },
      "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
      "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
    }
  ]
}

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

=> [
  {
    "ref": { "@ref": "keys/104979509694379214" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/personnel" },
    "secret": "kqnPAi3KlXEgAAC0OHSIyrIITuOMRfwre9owibw4nPU",
    "hashed_secret": "$2a$05$wEQ1p82hrSGAQUVH6uzLEuCVqQCrhqOjvRkNx/P78TGm9rhA4qS9a"
  },
  {
    "ref": { "@ref": "keys/104979509695139637" },
    "class": { "@ref": "keys" },
    "ts": 1436375112316028,
    "role": "server",
    "database": { "@ref": "databases/bulletin-board" },
    "secret": "kqnPAi4gE97wAAC0sl11pF8QcY_1w_am0-FX_hR3HEU",
    "hashed_secret": "$2a$05$YchPKx7CPvbL8Z9QgXkXG.cBRXo.ROmaHalJd4ryEEIvGaKgQQRpC"
  }
]

Conclusion

In this tutorial we walked through setting up a hierarchy of databases starting with two top-level, broadly scoped databases and continuing down to individual databases for internal products. Keys for the individual app databases are unable to access anything outside of their associated database.

FaunaDB’s tree-like multitenancy and QoS system allow organizations to build, operationalize, and maintain a single cluster capable of serving the needs of all of its organization’s products.

FaunaDB Cloud works identically. An organization can build their database hierarchy according to their structure without the overhead of operating their own FaunaDB cluster.