Schema and Types

Behind each object persisted to and loaded from Baqend there is a schema which describes the structure of its instances. It specifies which attributes of an object will be tracked and saved (e.g. Todo.name, their types (e.g. String and optionally constraints (e.g. not null).

The types that Baqend supports can be classified in five categories.

  • Entities are the objects themselves, i.e. instances conforming to the schema
  • References are references (i.e. links, foreign keys) to other entities.
  • Embeddables are objects that are embedded within other objects (i.e. value objects).
  • Primitives are native types like String, Numbers, Dates and JSON.
  • Collections are lists, sets and maps containing any of the previous data types.

Data Modelling

This section introduces the two basic ways to create your domain model with Baqend:

  • Using the dashboard, you can define attributes and specify relationships between different entities using a visual interface.
  • Alternatively, you can also define and update your model programmatically, for example as part of automated tests.

Data Modeling With The Dashboard

Here is an example for creating the data model of Todo objects in the dashboard:

Schema Tutorial

Under the hood, Baqend stores data in MongoDB. However, in contrast to data modelling in MongoDB, Baqend supports a rich schema that is checked and validated whenever data ist stored. By using the JSON data types Baqend objects can have arbitrary schemaless parts.

Tip: Best practices for schemaless and schema-rich data modelling can both be applied in Baqend by mixing data types with JSON.

Programmatic Data Modeling

To manipulate your domain model through application code, you first have to connect to your app and authenticate yourself as superuser like so:

// Create entity manager factor object:
let emf = new DB.EntityManagerFactory({ host: 'your-app-name' });
// Create a db instance that shares its authentication token with the meta model (see below)
let db = emf.createEntityManager(true);
// Wait for the db initialization
await db.ready();
// Login with a user who has admin permissions
await db.User.login("admin", "password");

Then, you have to retrieve the meta model (i.e. the schema) of your app:

// Get your app's metamodel
let metamodel = emf.metamodel;

Using the metamodel, you can now update your schema in various ways. For example, you can create a new entity type (Person) and add a string attribute to it (name):

// You can now add new types to this metamodel
const personType = new DB.metamodel.EntityType('Person', metamodel.entity(Object));
metamodel.addType(personType);
//  And lastly you can add attributes to your generated types
personType.addAttribute(new DB.metamodel.SingularAttribute('name', metamodel.baseType(String)));

When you have specified all desired changes, you have to save your changes to apply them to your Baqend instance:

await metamodel.save();

Embedding vs Referencing

The major decision when modelling data in Baqend is the choice between embedding and referencing.

With embedding, related content is stored together. This is also called denormalization as the data might be duplicated in multiple places. Embedding is useful for:

  • Modelling contains relationships. For example a shipping address is "contained" in an invoice object.
  • Modelling one-to-many (1:n), aggregation and composition relationships. For example a Todo list is composed of multiple todo items.

The advantage of embedding is that data can be read in one chunk making retrieval more efficient. The downside is that whenever embedded objects are contained in multiple parent objects, more than one update has to be made in order to keep all instances of the embedded object consistent with each other.

Data modelling JSON format

With referencing, dependent data is not embedded, but instead references are followed to find related objects. In the world of relational database systems this is called normalization and the references foreign keys. Referencing is a good choice if:

  • Data is used in multiple places.
  • For many-to-many (n:m) relationships. For example a "friends with" relationship would best be modelled by a list of references to friend profile objects.
  • Deep hierarchies have to be modelled, e.g. the namespace of a file system.

The downside of referencing is that multiple reads and updates are required if connected data is changed. With the depth-parameter you can, however, load and save entities with all its references. See references.

Entity Objects

In general there are two types of objects. The first type - Entities - are those objects which have their own identity, version and access rights. They can be directly saved, loaded and updated. Each entity has its own unique id. The id is immutable and set at object creation time.

var todo = new DB.Todo({name: 'My first Todo'});
console.log(todo.id); //'84b9...'

Instead of relying on automatic generation, objects can also have a custom id. This allows to assign ids that are memorable and meaningful.

var todo = new DB.Todo({id: 'Todo1', name: 'My first Todo'});
console.log(todo.id); //'Todo1'
todo.save();
Note: The save call will be rejected, if the id already exists!

References

Entity objects can reference other entities by reference, i.e. their id. Referenced objects will not be persisted inside another entity, instead only a reference to the other entity is be persisted.

var firstTodo = new DB.Todo({name: 'My first Todo'});
var secondTodo = new DB.Todo({name: 'My second Todo'});

firstTodo.doNext = secondTodo;

To save a reference, you just call the save() method on the referencing entity.

//the todo instance will automatically be serialized to a object reference
firstTodo.save();

Internally, the reference is converted to a string like /db/Todo/84b9... and persisted inside the referencing entity. The referenced entity will not be saved by default. You can pass the depth options flag to the save the complete object graph by reachability.

//will also save secondTodo, since it is referenced by firstTodo
firstTodo.save({depth: true});

When an entity is loaded from Baqend, referenced entities will not be loaded by default. Instead an unresolved entity (hollow object) is set for the referenced entity. If you try to access attributes of an unresolved entity, an object is not available error will be thrown.

//while loading the todo, the reference will be resolved to the referenced entity
DB.Todo.load('7b2c...').then(function(firstTodo) {
  console.log(firstTodo.name); //'My first Todo'
  console.log(firstTodo.doNext.name); //will throw an object not available error
});

The isReady field indicates if an entity is already resolved.

DB.Todo.load('7b2c...').then(function(firstTodo) {
  console.log(firstTodo.doNext.isReady); //false
});

Calling load() on an unresolved entity resolved it, i.e. the referenced object is loaded.

firstTodo.doNext.load(function() {
  console.log(firstTodo.doNext.isReady); //true
  console.log(firstTodo.doNext.name); //'My second Todo'
});

If the object graph is not very deep, references can easily be resolved by reachability.

//loading the todo will also load the referenced todo
DB.Todo.load('7b2c...', {depth: true}).then(function(firstTodo) {
  console.log(firstTodo.name); //'My first Todo'
  console.log(firstTodo.doNext.name); //'My second Todo'
});

For further information on persisting and loading strategies see the Deep Loading chapter.

Embedded Objects

The second type of objects are embedded objects. They can be used within an entity or a collection like a list or map. They do not have an id and can only exist within an entity. Embedded objects are saved, loaded and updated with their owning entity and will be persisted together with it. Embedded objects thus have the structure of a object but the behaviour of a primitive type (e.g. a String). This concept is also known as value types, user-defined types or second class objects.

Embedded objects can be created and used like entity objects.

var activity = new DB.Activity({start: new Date()});
console.log(activity.start); //something like 'Tue Mar 24 2015 10:46:13 GMT'
activity.end = new Date();

Since embeddables do not have an identity, they hold neither an id, version nor acl attribute.

var activity = new DB.Activity({start: new Date()});
console.log(activity.id); //undefined

To actually persist an embedded object you have to assign the embedded object to an entity and save that outer entity.

var activity = new DB.Activity({start: new Date()});
var todo = new DB.Todo({name: 'My first Todo', activities: [activity]});
todo.save();

Primitives

Primitives types are the basic attribute types and known from programming languages. Whenever an entity is saved, all attribute values will be checked against the types described by the schema. This is one of the biggest advantages of having a schema: data cannot easily be corrupted as its correct structure is automatically enforced by the schema. Please note that the JSON data type gives you full freedom on deciding which parts of a object should be structured and which parts are schema free. The following table shows all supported attribute types of Baqend and their corresponding JavaScript types.

Baqend Primitive JavaScript type Example Notes
String String "My Sample String"
Integer Number 456 64bit integer. Fractions are deleted
Double Number 456.456 64bit floating point numbers
Boolean Boolean true
DateTime Date(<datetime>) new Date() The date will be normalized to GMT.
Date Date(<date>) new Date('2015-03-15') The time part of the date will be stripped out.
Time Date(<datetime>) new Date('2015-01-15T13:30:00Z') The date part of the date will be stripped out and the time will be saved in GMT.
File File(<fileId>) new File('/file/www/my.png') The file id points to an uploaded file.
GeoPoint DB.GeoPoint(<lat>, <lng>) new DB.GeoPoint(53.5753, 10.0153) You can get the current GeoPoint of the User with GeoPoint.current(). This only works with an HTTPS connection.
JsonObject Object {"name": "Test"} Semistructured JSON is embedded within the entity. Any valid JSON is allowed.
JsonArray Array [1,2,3]

Collections

Collections are typed by a reference, embedded object class or a primitive type. The Baqend SDK supports 3 type of collections, which are mapped to native JavaScript arrays, es6 sets and maps:

Baqend Collection Example Supported element Types
collection.List new DB.List([1,2,3]) or
new Array(1,2,3)
All non-collection types are supported as values
collection.Set new DB.Set([1,2,3]) or
new Set([1,2,3])
Only String, Boolean, Integer, Double, Date, Time, DateTime and References are allowed as values. Only this types can be compared by identity.
collection.Map new DB.Map([["x", 3], ["y", 5]]) or
new Map([["x", 3], ["y", 5]])
Only String, Boolean, Integer, Double, Date, Time, DateTime and References are allowed as keys.
All non collection types are supported as values.

For all collection methods see the MDN docs of Array, Set and Map

Proceed to next Chapter: Queries