Entity model

Entity (or data) model is the core part of the domain model that captures the real-world entities and the rules that determine how data can be created, changed, and deleted.

The persistent data of a domain model is described in the form of entity types. Actual data is stored by creating instances of entity types.

An entity type (or shortly an entity) may have stored primitives and relations, calculated fields and calculated relations that are referred to as members of an entity.

We are aware that entity and entity type are not the same. However, for ease of reading, we use the word entity instead of entity type. When talking about instances, we always use the term entity instance.

Entity

To define an entity, use the entity keyword.

Syntax:

entity [abstract] <name> [extends <entity>, ...] {
    [member] ...
}

where the <name> is the name of the entity, and the entity members are defined between { and }. The keyword extends indicates that an entity is inherited from another entities. See the section Inheritance for more details.

Example:

entity Person {
}

The example above defines an empty entity named Person. This entity has no member declarations (fields or relations).

An entity can be defined as an abstract using the keyword abstract. An abstract entity cannot be instantiated, it is intended to be inherited by other entities.

Example:

entity abstract Customer {
}

Primitive field

An entity instance can store primitive values in its fields. A field can be interpreted as a container in a specific entity instance where data values can be stored (e.g. numbers or text values like "red" or "blue").

A field definition of an entity specifies the name and the type of the data that are stored in the entity instances (e.g. color is the name of the field of the Pencil entity and this field has a string type).

A primitive field cannot store multiple primitive values, but only a single primitive value. To put it another way, there is no list of primitives.

Use the field keyword to specify a primitive field within an entity.

Syntax:

field [required] <primitive> <name> [default:<default>];

where <primitive> is the name of a domain model primitive and <name> is the referable name of the primitive field.

The keyword required means that the value of the field must be specified, so the value is not allowed to be undefined. Each newly created entity instance must set this field.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the field is set according to the default value expression. See Expression later.

The self variable cannot be used in default expressions.

Example:

entity Person {
    field required String firstName;
    field required String lastName;
    field String midName;
    field Title title default:Title#MX;
}

The example above defines an entity named Person. This entity has four primitive fields. firstName and lastName are two required strings, midName is an optional string with no default value and title is an optional enumeration field with a default value.

Identifier

Identifiers are regular primitive fields, but it is ensured that the value of the field for each entity instance is different (unique). Note that, the undefined value is considered different from any value. (Undefined is also considered to be different from undefined.)

Use the identifier keyword to specify a unique field within an entity.

Syntax:

identifier [required] <primitive> <name> [default:<default>];

where <primitive> is the name of a domain model primitive and <name> is the referable name of the identifier. Any primitive type can be used as an identifier, except primitives of binary base type.

The keyword required means that the value of the identifier must be specified, so the value is not allowed to be undefined.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the identifier is set according to the default value expression. See Expression later.

The self variable cannot be used in default expressions.

Example:

entity User {
    identifier required String email;
}

The example above defines email as a required string and serves as an identifier. This means you can find exactly one User if you know an email address.

An important feature of entity instances is that they are uniquely identifiable, i.e. each instance of an entity has a (universally) unique system generated identifier that never changes. This system generated identifier is not available in JSL and should not be confused with the identifier members discussed in this chapter.

Relations

Before explaining relations between entities we need to understand references and collections.

When a new entity instance is created, its space is allocated in the persistent storage. To access an entity instance, we need a reference. The reference simply points to the entity instance (which is created in persistent storage).

References do not have to point to an entity instance, or in other words references can be undefined.

When you pass a reference value to another reference, the two references will point to the same entity instance. If you delete the entity instance through one of the references, both references will have an undefined value.

A collection is a set of references. Collections always contain unique references, which means that there are no two references in a collection that point to the same entity.

Collections cannot be undefined, but collections can be empty. A collection cannot contain undefined reference. Once an entity instance is deleted, references to it are deleted from all collections.

Unidirectional relation

Relation is a reference defined within an entity. The entity instance containing the reference is the owner of the relation. The owner has access to the instance pointed to by the reference.

Relations can be not only references to a single instance, but also collections. If the relation is a collection, the owner can access a set of instances at the same time.

Relations can be unidirectional or bidirectional.

An unidirectional relation can only be navigated in one direction: from the owner to the target of the relation. Navigation means that, at run-time, the owner of the relation gets the instance(s) of the target of the relation.

Use the relation keyword to specify a relation between two entities. The syntax of the unidirectional relation is as follows.

Syntax:

relation [required] <entity>[[]] <name> [default:<default>];

where <entity> is the name of an entity to which the relation is targeted. The <name> is used to identify the relation within the entity, it is commonly referred to as role name.

The optional [] behind the <entity> indicates that the relation is a collection rather than a single reference to one <entity> instance. In other words, the cardinality of the relation is a collection.

The keyword required means that the value of the relation must be specified, so the value is not allowed to be undefined. Each newly created entity instance must set this field. The keyword required is not allowed for collections.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the field is set according to the default value expression. See Expression later.

Example:

entity Customer {
	relation Order[] orders;
}

entity Order {
	relation required Customer;
}

The example above defines two unidirectional relations. orders is defined within Customer entity and can refer to a list of Order entity instances that belong to a particular customer. customer relation is defined within the Order entity and targets the customer who made the purchase.

Bidirectional relation

A bidirectional relation can be navigated in both directions.

The syntax of the bidirectional relation is an extension of unidirectional syntax and it has two forms. The first one is:

Syntax:

relation [required] <entity>[[]] <name> opposite:<opposite>;

The opposite keyword is used to link two relations into a bidirectional relation. The opposite keyword must be used on both sides of the bidirectional relation.

Both ends of a bidirectional relation cannot be required at the same time.

Example:

entity Customer {
	relation Order[] orders opposite:customer;
}

entity Order {
	relation required Customer customer opposite:orders;
}

Another form of bidirectional relationship declaration is as follows.

Syntax:

relation [required] <entity>[[]] <name> opposite-add:<opposite>[[]];

The opposite-add keyword with a simple <opposite> is used to create a bidirectional relation and inject the opposite relation to an already defined entity without changing the original definition of the entity.

The opposite-add keyword with an <opposite> and [] injects a collection in the target entity.

The most useful feature of opposite-add is that it can be used with models that should not be modified. If you import a model, you can still create bidirectional relationships between your entities and the entities you imported.

The example below defines a bidirectional relation. Defining the product relation in the OrderItem entity also adds a collection to the Product entity named orderItems.

Example:

entity OrderItem {
	relation required Product product opposite-add:orderItems[];
}

entity Product {
}

The model in the above example is equivalent to the following example.

entity OrderItem {
	relation required Product product opposite:orderItems;
}

entity Product {
    relation OrderItem orderItems[] opposite:product;
}

Composition

A composition or composite field is a member whose type is an entity. A composition can be considered as a specific type of reference where the owner of the composition contains the referenced entity instance(s).

An important feature of composition is that after deleting the owner, all entity instances in its composite fields are also deleted. Compositions can also be thought of as entity instances that are not created within the shared space of the persistent storage, but within the owner entity instance.

Use the field keyword to specify a composite field within an entity.

Syntax:

field [required] <entity>[[]] <name> [default:<default>];

where <entity> is the name of an entity and <name> is the referable name of the composition.

The optional [] indicates that the composite field stores a list of entity instances rather than a single <entity> instance. In other words, the cardinality of the field is a collection.

The keyword required means that the value of the field must be specified, so the value is not allowed to be undefined. Each newly created entity instance must set this field. The keyword required is not allowed for collections.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the field is set according to the default value expression. See Expression later. Note that using the default value of the composition copies the entities specified in the default expression.

Example:

entity Address {
    field required String line;
    field required String city;
    field required country;
}

entity Customer {
    field required Address address;
}

The example defines the Address entity with its primitive fields, and each Customer instance must have exactly one composite Address.

Calculated members

Calculated members provide a flexible mechanism to read their value. The value of a calculated member cannot be set, in other words calculated members are read only. However, calculated members can be read as if they are stored members, but they have a special expression called getter expression. The getter expression is used to return the value of the calculated member.

Use the field or relation keywords along with the <= symbol to specify a calculated member within an entity.

Syntax:

field <primitive> <name> <= <expression>;

where <primitive> is the name of a domain model primitive, and <name> is the referable name of the calculated field, or

Syntax:

relation <entity>[[]] <name> <= <expression>;

where the <entity> is the name of an entity, and <name> is the referable name of the calculated relation. The optional [] indicates that the calculated relation returns a collection rather than a single reference to one <entity>.

The getter <expression> returns the value of the calculated member. See Expression later.

Note that the calculated members use the <= symbol instead of the assignment operator (=) to define the getter expression.

Note that the required keyword cannot be used for calculated members.

Example:

entity Person {
    field required String firstName;
    field required String lastName;

	field String fullName <= self.firstName + " " + self.lastName;
}

The example above defines two stored fields and a calculated member. The name of the calculated member is fullName and its value is calculated by concatenating the firstName and lastName with a space in the middle. Note the self keyword in the getter expression which refers to the current Person instance.

Example:

entity Customer extends User, Person {
	relation Order[] undeliveredOrders <=
		self.orders.filter(order | order.status != OrderStatus#PAID);
}

In the second example, the calculated relation undeliveredOrders returns all orders that have not yet been delivered but have been ordered by the customer.

Fetching strategy

The fetching strategy determines the timing of fetching related entities from persistent storage. The entities in the compositions and in the relationships are collectively called related entities. There are two ways available for related entities.

  • Eager fetching is a technique where related entities are fetched at the same time as the owner entity is retrieved from persistent storage. In other words, the related entities are embedded into the owner entity. This approach can be beneficial when dealing with simple getter expressions and only a few related objects, as it helps eliminate the need for additional fetches and improves the application’s performance.

  • Lazy fetching is a technique where related entities are not fetched at the same time as the owner entity is retrieved from persistent storage. Instead, they are retrieved only when requested for the first time. In other words, the value is calculated and retrieved upon request. This approach ensures that related entities are loaded from persistent storage only as they are needed.

By default, related entities are retrieved using a lazy fetching strategy.

Fetching modifier

The default fetching strategy of the entity relations and compositions can be altered by applying the eager modifier. It is important to note that applying eager modifier does not modify the business logic, but significantly impacts the performance of the application.

To apply the eager fetching strategy on an entity relation or composition, use the eager modifier with true value. To apply the lazy fetching strategy on an entity relation or composition, use the eager modifier with false value.

Example:

entity Customer {
	relation Order[] orders eager:true;
}

In the above example, the instances of the orders relation will be loaded when the order entity is fetched. In other words, the Order entity instances referenced by the orders relation will be fetched together with the Customer entity instance.

The eager fetching strategy can also be applied to calculated relations.

Please note that the fetching strategy modifier is not applicable for primitive fields. Instead, use primitive return type instance queries. See Instance query chapter later.

Query

A query is a request that retrieves a primitive or entities. Each query has a return type, a name and a query expression, which is used to calculate the return value.

There are two types of queries

  • static query and

  • instance query

Static query

Static queries are executed within the scope of the model, and not in the scope of an entity. Since static queries do not operate on entity scope, static queries cannot use self variable in their expression.

Use the query keyword to specify a static query.

Syntax:

query <primitive> <name> ([<primitive> <parameter> [= <value>], ...]) <= <expression>;

where <primitive> is the name of a domain model primitive that will be returned, and <name> is the referable name of the query, or

Syntax:

query <entity>[[]] <name> ([<primitive> <parameter> [= <value>], ...]) <= <expression>;

where the <entity> is the name of an entity that will be returned, and <name> is the referable name of the query. The optional [] indicates that the query returns a list of <entity> instances rather than a single reference to one <entity>.

The <expression> calculates the return value of the query whenever it is requested. See Expression later.

Note that queries do not use the assignment operator (=) but the (<=) symbol to set the value.

The optional comma separated list in the query definition is the parameter list of the <expression>. The parameters can be used in the <expression>. <primitive> is the type of parameter which can be a domain model primitive only. <parameter> is the name of the parameter. An optional default <value> can be specified for the parameters. Default values are used when the calculated member is evaluated with one or more missing arguments.

Example:

query Order[] ordersBetween(Integer min = 0, Integer max = 100) <=
    Order.all().filter(o | o.price > min and  o.price < max);

The example above shows the query ordersBetween, which has two parameters (min and max). It returns the orders from all orders in the application with a price between min and max.

This is an example of calling a static query:

Example:

entity Customer {
	relation Order[] allOrdersFrom1000to2000 <= ordersBetween(min = 1000, max = 2000);
}

Instance query

Instance queries are executed within the scope of an entity. The entity on which the query is executed can be named "self" in the query expression.

The syntax of the instance query declaration is the same as the syntax of the static query declaration with the addition of an on keyword.

Syntax:

query <primitive> <name> ([<primitive> <parameter> [= <value>], ...]) on <entity> <= <expression>;

or

Syntax:

query <entity>[[]] <name> ([<primitive> <parameter> [= <value>], ...]) on <entity> <= <expression>;

where the <entity> is the name of an entity that can be referred with the self variable.

Example:

query Order[] ordersBetween(Integer min = 0, Integer max = 100) on Customer <=
    self.orders.filter(o | o.price > min and  o.price < max);

The example above shows the query ordersBetween, which has two arguments (min and max). It returns the orders of a customer with a price between min and max. If no arguments are specified when executing ordersBetween, the query returns orders between 0 and 100. For more details about expressions, see the chapter Expression.

An instance query can be performed on an entity instance by prefixing the instance query name with the . character. This is an example of calling an instance query.

Example:

entity Customer {
	relation Order[] orders;
	relation Order[] ordersFrom1000to2000 <= self.ordersBetween(min = 1000, max = 2000);
}

Note the difference between the instance and the static query examples. The instance query uses the self variable as the starting point for navigation. It collects all the orders belonging to the given customer and then runs the filter condition. In case of the static query example, the starting point of the static query is all the orders stored in the application. Then the static query also runs the filter condition to select the orders between the two price limits.

Inheritance

Inheritance is a mechanism by which more specific entities incorporate structure of the more general entities (called parent entities).

Entities may inherit identifiers, stored fields, calculated fields, relations and entity constraints from their parent entities. An entity and its parent entity are in IS-A relation, the entity can replace its parent entity anywhere.

Inherited members of an entity which were defined in the parent behave as if they were defined in the entity itself.

An entity may be the parent of any number of other entities and may have any number of parents. An entity should not be inherited from itself, either directly or indirectly.

An entity cannot have an inherited and a non-inherited member with the same name. Thus, overriding members is not allowed. In addition, an entity cannot inherit two members with the same name from two different parents.

The extends keyword in the entity declaration indicates that an entity is inherited from another entities.

Example:

entity abstract Customer {
    field required Address address;
	relation Order[] orders opposite customer;
}

entity Enterprise extends Customer {
	field required String name;
}

In the above example the Enterprise entity inherits the field and the relation of the Customer entity and defines a name field.

Example:

entity User {
	identifier required Email email;
}

entity abstract Customer {
    field required Address address;
	relation Order[] orders opposite customer;
}

entity Person extends Customer, User {
	field required String firstName;
	field required String lastName;
}

In the second example above the Person entity inherits the email identifier of the User entity and inherits two other members from the Customer entity. This is an example of multiple inheritance.