Tutorial: 04 OData

 OData is a protocol for build RESTfull API. Developed by Microsoft, it give you all pros & contras of Microsoft products. The most big contras is size of OData specification - 70 pages just for protocol.

In UnityBase OData implemented as a provider, so it is possible to use the UnityBase ubql protocol and OData at the same time. Below is a protocols comparison

Quick demo

Set up a Autotest application, run it, point your browser to DXODataClient sample

All you need to add support for OData for your application is to define OData endpoint in your model server-side js. By default all entities are accessible, but you can pass an entitySetMapping parameter during endpoint configuration to limit UB entities available through OData or create an alias for entities.

var OData = require('OData');

var endpoint = OData.registerEndpoint({
    endpointName: 'ODataV4',
    namespace: 'autotest',
    //requireAuth: false,
    skipOptimisticLock: true
    //,entitySetMapping: {
    //    tst_ODataRef: App.domain.byName('tst_ODataRef'),
    //    tst_ODataSimple: App.domain.byName('tst_ODataSimple')
    //}
});

OData & UBQL comparison

Here we provide comparison of OData operation and how its analog in UnityBase Query Language (UBQL).

OData define term Entity Data Model(EDM) - this is metadata, what describe Entity types - named structured types with a key. They define the named properties and relationships of an entity. Entities are instances of entity types (e.g. Customer, Employee, etc.), Entity sets is a collection of Entities accessible via external API.

UnityBase define Domain Model - this is metadata, what describe Entities - named structured types with a key. They define the named properties and relationships of an entity. Customer with ID = 10 is en instance of entity Customers.


OData UnityBase Mean
Entity Data Model(EDM) Domain Model The subject area to which the user applies a program
Entity type Entity Class in term of OOP
Entity sets Entity In OData Entity set is an alias for EntityType, which accessible via external API, in UnityBase entities, accessible via external API defined by Entity Level Security
Entity Entity instance Class instance in term of OOP

Each topic below contains a reference to OData specification topic number

Service Document (10.1)

OData

Request a top-level resources

GET TripPinServiceRW HTTP/1.1
//alias
GET TripPinServiceRW/$metadata HTTP/1.1

Response

{
    "@odata.context": "http://services.odata.org/V4/(S(jupsobosp4eut4c0web34mlj))/TripPinServiceRW/$metadata",
    "value": [{
        "name": "Photos",
        "kind": "EntitySet",
        "url": "Photos"
    }, {
        "name": "People",
        "kind": "EntitySet",
        "url": "People"
    }, {
        "name": "Airlines",
        "kind": "EntitySet",
        "url": "Airlines"
    }, {
        "name": "Airports",
        "kind": "EntitySet",
        "url": "Airports"
    }, {
        "name": "Me",
        "kind": "Singleton",
        "url": "Me"
    }, {
        "name": "GetNearestAirport",
        "kind": "FunctionImport",
        "url": "GetNearestAirport"
    }]
}

UnityBase

The first: to request a resources metadata user must be authenticated. This allow to return only resources, accessible for this user. The second: In general application need to know all metadata, so in case of OData we always request top level metadata first, and then send many-many request for retrieve Entity metadata. In UnityBase we have one URL to retrieve all Domain metadata:

GET getDomainInfo HTTP 1.1

Response is object, there keys in entity name, value is Entity definition, described by UnityBase entity definition JSON schema

{
    "domain": {
        cdn_address: {modelName: "CDN", caption: "Addresses", description: "Addresses directory", connectionName: "main",…},
        cdn_adminunit: {modelName: "CDN", caption: "Admin unit", description: "Admin unit (country, region, city)",…}
    },
    entityMethods: ...,
    i18n: ...,
    models: ..
}

Collection of Entities (10.2)

OData

Request collection

    GET TripPinServiceRW/People HTTP 1.1

Response

{
        "@odata.context": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/$metadata#People",
        "@odata.nextLink": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/People?%24skiptoken=8",
        "value": [{
            "@odata.id": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/People('ronaldmundy')",
            "@odata.etag": "W/\"08D2F981B3628397\"",
            "@odata.editLink": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/People('ronaldmundy')",
            "UserName": "ronaldmundy",
            "FirstName": "Ronald",
            "LastName": "Mundy",
            "Emails": ["Ronald@example.com", "Ronald@contoso.com"],
            "AddressInfo": [],
            "Gender": "Male",
            "Concurrency": 635844832868336535
        }, {
            "@odata.id": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/People('javieralfred')",
            "@odata.etag": "W/\"08D2F981B3628397\"",
            "@odata.editLink": "http://services.odata.org/V4/(S(my2yfeng3hubag32govlkna3))/TripPinServiceRW/People('javieralfred')",
            "UserName": "javieralfred",
            "FirstName": "Javier",
            "LastName": "Alfred",
            "Emails": ["Javier@example.com", "Javier@contoso.com"],
            "AddressInfo": [{
                "Address": "89 Jefferson Way Suite 2",
                "City": {
                    "CountryRegion": "United States",
                    "Name": "Portland",
                    "Region": "WA"
                }
            }],
            "Gender": "Male",
            "Concurrency": 635844832868336535
        }]
}

Questions we don't know answers for:

1) Do you see the strange URL part in response - (S(my2yfeng3hubag32govlkna3))? It is added to prevent cache of GET request. In UnityBase we simple do POST for data retrieve.

2) Do we ask for AddressInfo? No - we do not need it - we just want to display a table of our customers. But service return all details. For efficient operation of such structures on the client side we must normalize it using, for example [https://github.com/gaearon/normalizr].

UnityBase

Actually this is collection of entity instance

POST ubql HTTP 1.1
[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["*"]
}]

And response is:

[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["*"],
    "resultData": {
        "fields": ["ID", "lastName", "firstName", "birthDate"],
        "rowCount": 2,
        "data": [
            [3000000003501, "Simpson", "Homer", "1955-05-12T00:00Z"],
            [3000000003502, "Simpson", "Bart", "1975-09-02T00:00Z"]
        ]
    }
}]

This is a compact representation of response. It is very easy to transform it to the array of object on the client-side in the five line of code - see LocalDataStore.selectResultToArrayOfObjects

Entity (10.3)

Request for a single entity (one instance in term of UnityBase) by value of primary key attribute

OData

Request:

GET TripPinServiceRW/Photos(1) HTTP 1.1

Response:

{
    "@odata.context": "http://services.odata.org/V4/(S(33hnsbwj2xmyya0gjbaj0pdc))/TripPinServiceRW/$metadata#Photos/$entity",
    "@odata.id": "http://services.odata.org/V4/(S(33hnsbwj2xmyya0gjbaj0pdc))/TripPinServiceRW/Photos(1)",
    "@odata.editLink": "http://services.odata.org/V4/(S(33hnsbwj2xmyya0gjbaj0pdc))/TripPinServiceRW/Photos(1)",
    "@odata.mediaContentType": "image/jpeg",
    "@odata.mediaEtag": "W/\"08D2F99AE30057DE\"",
    "Id": 1,
    "Name": "My Photo 1"
}

UnityBase

Request:

POST ubql HTTP 1.1
[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["*"],
    ID: 3000000003501
}]

Response:

[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["*"],
    "resultData": {
        "fields": ["ID", "lastName", "firstName", "birthDate"],
        "rowCount": 1,
        "data": [
            [3000000003501, "Simpson", "Homer", "1955-05-12T00:00Z"]
        ]
    }
}]

UnityBase use the same data format for collection and for one row, so client data layer can be simplified.

Singleton (10.4)

There is no direct replacement of Singleton in the UnityBase. Entity data can be limited using Row Level Security and queried as a collection.

Collection of Derived Entities (10.5)

There is no direct replacement of Derived Entities. Entity data can be limited by adding filter condition to the main entity.

Collection of Projected Entities (10.7)

Return only a subset of properties

OData

Request:

GET TripPinServiceRW/People?$select=UserName,FirstName HTTP 1.1

Response:

{
    "@odata.context": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/$metadata#People(UserName,FirstName)",
    "@odata.nextLink": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/People?%24select=UserName%2c+FirstName&%24skiptoken=8",
    "value": [{
        "@odata.id": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/People('russellwhyte')",
        "@odata.etag": "W/\"08D2F9A17C9200B7\"",
        "@odata.editLink": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/People('russellwhyte')",
        "UserName": "russellwhyte",
        "FirstName": "Russell"
    }, {
        "@odata.id": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/People('scottketchum')",
        "@odata.etag": "W/\"08D2F9A17C9200B7\"",
        "@odata.editLink": "http://services.odata.org/V4/(S(iibotqzqvvabf02huka34l5x))/TripPinServiceRW/People('scottketchum')",
        "UserName": "scottketchum",
        "FirstName": "Scott"
    },

UnityBase

Request:

POST ubql HTTP 1.1
[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["firstName", "birthDate"]
}]

Response:

[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["firstName", "birthDate"],
    "resultData": {
        "fields": ["firstName", "birthDate"],
        "rowCount": 2,
        "data": [
            ["Homer", "1955-05-12T00:00Z"],
            ["Bart", "1975-09-02T00:00Z"]
        ]
    }
}]

Projected Entity (10.8)

OData

GET TripPinServiceRW/People(1)?$select=UserName,FirstName HTTP 1.1

UnityBase

POST ubql HTTP 1.1
[{
    "entity": "cdn_person",
    "method": "select",
    "fieldList": ["firstName", "birthDate"],
    "ID": 1
}]