Overview of the OData Standard

Posted in

One of the most important aspects of modern development is interoperability. The ability to not only code something well but to make it compatible with a wide range of counterparts elevates even basic functions to something more.

How the industry implements interoperability is important – a standard is only as good as how many people accept it as a standard. More importantly, a standard is only good if it’s actually interoperable, and not merely a commonly accepted set of possible implementations (that is, it must be uniformly applied, not applied in specificity).

One data standard that has gained steam in recent years is OData (Open Data Protocol). What exactly is OData, and what does it look like in practice? In this piece, we’re going to dive into the OData standard, its history, and code samples to demonstrate its use in practice.

OData History as a Standard

In this article, we review OData, an ISO/IEC approved OASIS standard that defines a set of best practices for building and consuming RESTful APIs.

Microsoft first launched the OData standard in 2007 as the Open Data Protocol. While developing the standard, Microsoft released versions 1.0, 2.0, and 3.0 under the Microsoft Open Specification Promise, which is a Microsoft program that promises Microsoft won’t pursue any “Microsoft Necessary Claims” against developers who utilize OData in some general covered cases.

In 2014, Version 4.0 was standardized by the Organization for the Advancement of Structured Information Standards (OASIS). In 2015, OASIS submitted OData v4.0 and the associated OData JSON format v4.0 to ISO/IEC JTC 1, a technical committee under the International Organization for Standardization (ISO). It has since been ratified as an ISO standard.

The entire premise behind OData was the development of an open protocol for querying RESTful APIs, and ensuring that they are interoperable with one another. As such, OData leverages many Internet Engineering Task Force (IETF) RFCs, including HTTP 1.1 specifications, Atom publishing specifications, JSON, and more. OData is uniquely positioned for interoperability, considering how heavily it leverages existing technologies to do what it does.

What Does OData Look Like?

So what does OData actually look like? Because OData uses a lot of standard REST approaches and standard RFCs, a lot of its infrastructure should seem relatively familiar. OData uses URIs to identify its resources and offers a few fixed elements that are attached to the OData service proper.

The Service document lists the entities within the target service, the functions usable amongst those entities, and any singletons. This document is designed to allow hypermedia-like navigation of OData-integrating applications. The Metadata document describes the types, functions, sets, and actions that the OData service understands. This metadata provides a sort of contextual understanding to the client interacting with the OData service, allowing clients to query and interact with service entities.

These two documents are by far the most important elements of the OData system, as they help the clients to understand not only what the OData service is representing, but how to interact with those resources in a way that is understandable and serviceable.

In terms of the actual interaction with the OData service, because OData uses standard verbiage, its functionality is quite familiar:

  • GET – Used to retrieve the resource for rendering, transformation, etc. GET can call up a stream, a navigation property, a collection of entities – regardless of type, GET is used to call those resources.
  • POST – Used to create a new resource.
  • PUT – Used to update an existing resource. Note that using PUT replaces the contents of the resource entirely with the updated resource.
  • PATCH – Conversely, PATCH is used to update an existing resource in part only. Updating specific properties of the instance (or, in this case, a part of an instance) is done through PATC – replacing it in whole is done through PUT.
  • DELETE – Predictably, this deletes (or removes, as it is often referred to) the resource.

XOData: An OData API/Service visualizer and explorer to enable rapid prototyping, verification, testing, and documentation of OData APIs. Sample courtesy of PragmatiQa. View it here.

Code Examples

OData is perhaps best understood in practice. Throughout this document, we’re going to include some code examples from the documentation, and we’ll break out specific sections for greater understanding.

Return a Collection of Entities

First, let’s look at a basic request for a collection of entities. As denoted above, we’ll use the GET verb to do so:

GET serviceRoot/People

As a result, we get the following payload in response:

{
    "@odata.context": "serviceRoot/$metadata#People",
    "@odata.nextLink": "serviceRoot/People?%24skiptoken=8",
    "value": [
        {
            "@odata.id": "serviceRoot/People('russellwhyte')",
            "@odata.etag": "W/"08D1694BD49A0F11"",
            "@odata.editLink": "serviceRoot/People('russellwhyte')",
            "UserName": "russellwhyte",
            "FirstName": "Russell",
            "LastName": "Whyte",
            "Emails": [
                "Russell@example.com",
                "Russell@contoso.com"
            ],
            "AddressInfo": [
                {
                    "Address": "187 Suffolk Ln.",
                    "City": {
                        "CountryRegion": "United States",
                        "Name": "Boise",
                        "Region": "ID"
                    }
                }
            ],
            "Gender": "Male",
            "Concurrency": 635404796846280400
        },
        ......
        ,
        {
            "@odata.id": "serviceRoot/People('keithpinckney')",
            "@odata.etag": "W/"08D1694BD49A0F11"",
            "@odata.editLink": "serviceRoot/People('keithpinckney')",
            "UserName": "keithpinckney",
            "FirstName": "Keith",
            "LastName": "Pinckney",
            "Emails": [
                "Keith@example.com",
                "Keith@contoso.com"
            ],
            "AddressInfo": [],
            "Gender": "Male",
            "Concurrency": 635404796846280400
        }
    ]
}

While the response payload is pretty easily understood, there are a few interesting things to point out. First off, let’s look at this section of the payload:

{
    "@odata.context": "serviceRoot/$metadata#People",
    "@odata.nextLink": "serviceRoot/People?%24skiptoken=8",

These two links present some interesting metadata that helps clients navigate the resource as returned. First off, the @odata.context link gives the client some important property context in terms of its relation to the overall service. This, when given additional contextual data through the use of the odata.nextLink link (which defines that the returned data is only a subset of a greater collection, and also where to find the next elements of that collection), allows the client to interact with the payload and associated data in a much more effective way.

While this request is pretty clear, it’s also extremely heavy. Let’s try to make a specific request for an individual entity. In this example, the request is made using a specific ID:

GET serviceRoot/People('russellwhyte')

This will generate the following payload:

{
    "@odata.context": "serviceRoot/$metadata#People/$entity",
    "@odata.id": "serviceRoot/People('russellwhyte')",
    "@odata.etag": "W/"08D1694BF26D2BC9"",
    "@odata.editLink": "serviceRoot/People('russellwhyte')",
    "UserName": "russellwhyte",
    "FirstName": "Russell",
    "LastName": "Whyte",
    "Emails": [
        "Russell@example.com",
        "Russell@contoso.com"
    ],
    "AddressInfo": [
        {
            "Address": "187 Suffolk Ln.",
            "City": {
                "CountryRegion": "United States",
                "Name": "Boise",
                "Region": "ID"
            }
        }
    ],
    "Gender": "Male",
    "Concurrency": 635404797346655200
}

Note here that the ID is the same as the URL of the entity – it is a convention that the ID is identical to the resource link. In a situation like this, where the username is the same as the entity-id and thus the URL, it is possible that the entry will be duplicated across multiple fields. That doesn’t mean that they are the same thing or used in the same way, regardless of the repeated value.

Of additional note here is this section:

    "@odata.etag": "W/"08D1694BF26D2BC9"",

Here, we see an etag, which is an entity tag that is generated to reflect the current state the resource. This string value can then be used upon subsequent requests to see if the entity has had any change – due to how etags are generated, if the etag is different, something in the entity has been changed. This is useful for tracking state, but the tag is also an excellent tracking method for setting sources of truth.

You can also address an entity property simply by appending the path segment with the property name to the entity URL – for example, in the documentation, the following request is noted to retrieve the entity property Name from a serviceRoot entity:

GET serviceRoot/Airports('KSFO')/Name

This will result in the following payload response:

{
    "@odata.context": "serviceRoot/$metadata#Airports('KSFO')/Name",
    "value": "San Francisco International Airport"
}

Being able to request individual entity properties is another powerful tool to detect changing state. For instance, if the value of the state is pertinent to order fulfillment, tracking the entity-id for overall changes, and then verifying the property state for fulfilled shipping can be a great way to push status to a customer. This entity storage system has a million different uses, and it’s great to see implemented in this standard.

Querying Compatibilities

OData also supports quite a few options for querying data. These query options are added as a variable to the URL request and transform the payload and resultant data. For instance, we wanted to filter a GET request to a specific person we could use:

GET serviceRoot/People?$filter=FirstName eq ‘Scott’

In this case, the $filter= is used to determine what exactly is the filter target. From here, we can use eq to state that the filter must equal a stated value; in this case, “Scott.” This will output data for entries as long as the entry has the FirstName value “Scott.” This can be used to return very specific filtered responses when you only know an alias or a secondary name, but not the actual property value itself.

You can also use nested filters to return content alongside a filtered nested value. For instance, in the documentation, the following invocation is used:

GET serviceRoot/People?$expand=Trips($filter=Name eq 'Trip in US')

This is unique in that it returns “Trip in US”-attached entities in the form of the People URL output. In essence, a complex search function is filtered and returned in an easy to understand way with a relatively lightweight request.

There are a few more general query options that can affect the output proper. $orderby allows the requesting client to sort either in ascending order (using “asc”) or descending order (desc). $top allows the requester to include the number of items pulled from the collection to be rendered as part of the request, allowing for an at-a-glance view of the relationship of the output data to the entire collection. Conversely, $skip allows you to see the number of items in the collection that are not included in the results. $count allows the client to get the total number of matching resources in the response.

This is only a small sample of the types of queries that are usable in OData. Suffice it to say, OData has a wide range of query options.

Companies Using OData

Many companies use OData across a wide range of industry subsections. IBM uses OData both as part of its DB2 & Infomix, Data Server Gateway for OData, and WebSphere eXtreme Scale offerings, focusing on increasing client interoperability by exposing OData structures.

Microsoft obviously uses the standard quite actively, with support in Lightswitch, Dyanamics CRM, and more. SAP Gateway uses OData to connect “any programming language or model” to SAP applications. Webnodes CMS uses OData has one of its supported data formats.

OData is a very powerful option, and given that it’s part of an open set of standards the partner list is likely to grow significantly over time.

Conclusion

OData is a quite powerful option, and its value is only cemented further by the fact that it’s an open standard. Being able to have a standardized, interoperable solution for data that is also used widely by respected industry partners opens up development to greater consumability, expressibility, and plug-and-play cooperation with other solutions.

What do you think about OData? Is it the standard we’ve all been waiting for? Let me know in the comments below.