Entity
Everything in a Hiperspace is either entity, describes an entity or projects an entity with a different view. An entity is a durable data fact with optional values and/or extensions much like a table in a database, a document in a document store or a node (with properties) in a graph with connected edges.
The term 'entity' is used to distinguish it from other 'types' in Hiperspace, and because it provides declarative functionality that is not implemented in other language definitions
- Table : It provides definition of the type, views (that it projected to too) , joins (to other {Entity, Segment, Aspect}), calculated (but not stored) properties, and indexes.
- Class : It distinguishes between keys, values and extents (properties that are not stored), and logic for encoding/decoding to storage.
All sections are optional in a HiLang definition except the type name, but entities must have keys (entity Instrument (Id : Int32);
is a valid definition).
Calculations in an entity value are much like the default values in a relational definition, except that they can use externally defined functions and reference other values. Once calculated has been evaluated it is stored in Hiperspace and not evaluated again (unless the null value is assigned). NB the examples include Valid
as a value to allow validation to be overwritten - need to clear the value for update.
Calculations in an extension are evaluated when needed, and never stored with the object. These extensions make an entity behave much like a row in a spreadsheet table.
Entity definition
In the language of HiLang, entity is a base-type with Inheritance declared with a ":" following the name. Unlike a class declaration, equivalence is declared with '=' to the view.
The declaration `view Edge2 : Edge = Edge();' describes a view Edge2 that is a Edge and equivalent to an Edge. The view is a common addition to HiLang schema models to allow an entity to provide the Edge view twice.
entity ERP.Products.Product : Versioned
= Node (Name = Name, SKey = SKey, TypeName = "Product")
ERP.Products.Product is a Versioned and equivalent to Node
entity ERP.Client.Customer : Versioned
= Node (Name = Name, SKey = SKey, TypeName = "Customer"),
Edge (From = this, To = BillTo, TypeName = "BillTo"),
Edge2 (From = this, To = ShipTo, TypeName = "ShipTo")
(
Id : Int32
)
{
Name : String,
BillTo : ERP.Location.Address,
ShipTo : ERP.Location.Address,
}
ERP.Client.Customer is a Versioned and equivalent to Node and two Edge, to expose the BillTo and ShipTo references as graph references.
entity ERP.Sales.Order : Versioned
= Node (Name = "", SKey = SKey, TypeName = "Order"),
Edge (From = this, To = Customer, TypeName = "Order-Customer"),
Edge2 (From = Customer, To = this, TypeName = "Customer-Order"),
Edge3 (From = this, To = Store, TypeName = "Order-Store"),
Edge4 (From = Store, To = this, TypeName = "Store-Order"),
Edge5 (From = this, To = Salesperson, TypeName = "Order-Sales"),
Edge6 (From = Salesperson, To = this, TypeName = "Sales-Order")
(
OrderNumber : Int32
)
{
Customer : ERP.Client.Customer,
Store : ERP.Location.Store,
Salesperson : ERP.Sales.Salesperson,
...;
ERP.Sales.Order is a Versioned and equivalent to Node and six Edge to provide a graph view of an order with navigation (as a graph) to and from the referenced {Customer, Store, Salesperson}.
Keys
An entity can have any number of key parts, and those keys can be references to other entities and complex value types
entity ERP.Finance.Tarriff : Versioned
= Node (Name = From.Name + " to " + To.Name, SKey = SKey, TypeName = "Tarriff"),
Edge (From = From, To = To, TypeName = "Tarriff")
(
From : ERP.Location.Country,
To : ERP.Location.Country
)
...;
ERP.Finance.Tarriff is identified by From Country and To Country. Worth noting that the Tarriff defines an Edge between two countries - a graph view could be viewed as UK -> USA (Tarriff) without the ontological model including Tarriff as an intermediate node.
ERP.Location.Country
is defined as an entity, when referenced in a another entity it is not added as a value, but a reference with navigation and as needed loading. The entity exposes a Country as a key property, but the KeyType
structure includes a KeyRef<ERP.Client.Customer.KeyType, ERP.Client.Customer>
that includes the Customer's KeyType
and logic to load the full Country if needed.
Values
Each value can be a Field-type (like a key) or a calculation that is either provided by the application or derived prior to binding to a SubSpace
.
entity ERP.Sales.Order : Versioned
...
(
OrderNumber : Int32
)
{
Customer : ERP.Client.Customer,
Store : ERP.Location.Store,
Salesperson : ERP.Sales.Salesperson,
"all the order lines included in the order"
Lines : Set <ERP.Sales.OrderLine>,
Valid = Customer = null || changed(Customer) = true ||
Store = null || _changeStore = true ||
count(Lines.LineNumber) = 0 || _changeLines = true
? false : true
}
ERP.Sales.Order has references to {Customer, Store, Salesperson} (since they are declared as entities).
ERP.Sales.Order has has a Set
of ERP.Sales.OrderLine. Set
is a unique collection (keyed by its own key), where a List
could contains duplicates. Collections must be value types.
ERP.Sales.Order has a Valid value property that is calculated using the expression if it does not have a value. In this expression changed
is a function that was declared with %function (unary, changed, ERP.ERPFunctions.Changed, Boolean);
and implemented externally. The Valid
property is normally associated with Horizon filter like the one in ERPSpace example.
A Set
of entity referenced can be added like Approval : Set<Ref<Access.RBAC.Group>>
(in RBAC example) by using a utility value like
/* Helper to enable a set of references */
value Ref<T>
{
Value : T
};
Extents
Where a value can have a reference to an entity, extents references are either a reference segments , aspects, or a join to a set of entititied by by query.
Tariff : ERP.Finance.Tarriff (From = Store.Country, To = Customer.BillTo.Country)
is a join to ERP.Finance.Tarriff matching the To and From members to the Store and BillTo contries.
Invoice : ERP.Sales.Invoice
can either be a single reference to an aspect or a set of segment. In this example it is a single reference to a ERP.Sales.Invoice aspect (one, optional)
Tariff : ERP.Finance.Tarriff (From = Store.Country, To = Customer.BillTo.Country),
Shipping : ERP.Finance.Shipping (From = Store.Country, To = Customer.ShipTo.Country),
Invoice : ERP.Sales.Invoice,
Total = sum (Lines.Price),
ShippingCost = Shipping = null ? sum (Lines.Shipping) : sum (Lines.Shipping) + max (Shipping.Fee),
SalesTax = sum (Lines.Tax),
TariffTax = Tariff = null ? 0.0 : max(Tariff.Tax.Rate) * Total,
Bill = Total + ShippingCost + SalesTax + TariffTax,
Cost = sum (Lines.Cost),
Commission = Salesperson = null ? 0.0 : Profit * Salesperson.CommissionRate,
Profit = 0.0 + Total - Cost - Commission
Extents consume no space within the entity and evaluated when needed. Adding @Once
to a calculation causes the value to be cached.
In the Invoice example we've chosen to implement OrderLine
as a Set
of values, but could have implemented it as a segment with similar Behavior - the choice was made because we don't need to join Product
to OrderLine
, and avoids the need to add a validation horizon to order line to ensure that lines are not added after an Invoice has been created.
Other considerations
Hash
Hash assignment is optional for entity definitions because they will be generated as needed, but the order of statements within a HiLang schema model will determine the number assigned. Careful use of hash number assignment will avoid the need to remap a durable Hiperspace and provide forward compatibility.
Hash assignment is also important for compatibility with protobuf definitions that the source of a Hiperspace schema model.
entity ERP.Location.Address : Versioned
= Node (Name = "{ Street: '" + Street + "', PostalCode: '" + PostalCode + "'}", TypeName = "Address", SKey = SKey),
Edge (From = this, To = Country, TypeName = "Address-Country") # 42
(
Id : Int32 # 1
)
{
Street : String # 2,
Town : String # 3,
City : String # 4,
County : String # 5,
Country : ERP.Location.Country # 6,
PostalCode : String # 7,
Valid = Country = null || PostalCode = null ? false : true # 8
};
Documentation
It’s a good idea to include documentation for an entity since this information is generated into code where it can be used (by IntelliSense) to inform the developer of the meaning an entity or member and for automated documentation tools. It is also passed to UML modelling tools using the XMI export/import function.
Documentation is distinct from /*
comments */
that are just for the HiLang modeler, and not generated.
Name
The Name of an entity cannot have type parameters, since it must unique within the domain, but can be the same in different namespaces. entity ERP.Sales.Order
and entity ERP.Store.Order
are still unique, but since the Set in the DomainSpace
must havea unique name, they would be referenced from the domain space as SalesOrders
and StoreOrders
. if there is a class on the in the final part.
References to entities are always full qualified to avoid the accidental mutation that is common with other (programming) languages that use namespace for resolution.
Inheritance
Inheritance in HiLang can be thought of as automatic-copy of the definition of another element to avoid the duplication of definitions. Entities can inherit from multiple base types (as long as names don't clash).
Versioned
in entity ERP.Location.Address : Versioned
is a good example of inheritance since @Versioned type Versioned { "Flag for read horizon filter to hide when true" Deleted = false };
combines the @Versioned
property (used to add versioning support to generated code) and the common convention of a Deleted
flag to hide an object from the current (query) view while still available to historical views.