Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

On its own, information represented in resource objects is simply simply media. However, in order to encourage client exploration and access to related resources we  we need to add to add hypermedia to  to our resources. RESTful API developers often abandon hypermedia (the "H" in HATEOAS) implementation  implementation because it can be considered more effort than it is worth. The SRP HTTP Framework makes this much easier through the use of automation and dedicated services.

Table of Contents

Using the the AddLinkRelationship Service

In our How do I create a resource? article  article we introduced the the GetObject service  service as a primary method for creating resource objects. To keep our documentation focused on creating a resource objects, we did not introduce hypermedia. Now we will use the same code sample and introduce a new service: AddLinkRelationship. The The AddLinkRelationship service  service creates the the HAL _links reserved  reserved property:

Code Block
languagebp
API customers.ID.GET

    KeyID           = EndpointSegment

    ColumnNames     = 'FIRST_NAME' : @FM : 'LAST_NAME' : @FM : 'ADDRESS' : @FM : 'CITY' : @FM : 'STATE' : @FM : 'ZIP'
    PropertyNames   = 'firstName' : @FM : 'lastName' : @FM : 'address' : @FM : 'city' : @FM : 'state' : @FM : 'zipCode'
    // Create a JSON object in memory.
    objResource     = HTTP_Resource_Services('GetObject', 'CUSTOMERS', KeyID, ColumnNames, PropertyNames)

    If Error_Services('NoError') then
        // Add _links sub-properties for HAL implementation.
        HTTP_Resource_Services('AddLinkRelation', objResource, 'self', FullEndpointURL)
        HTTP_Resource_Services('AddLinkRelation', objResource, 'collection', ParentURL)
    end

    If Error_Services('NoError') then
        // Serialize the JSON object.
        jsonResource    = HTTP_Resource_Services('GetSerializedResource', objResource)
        // Set the response body with the serialized JSON object and set the Content-Type response header.
        HTTP_Services('SetResponseBody', jsonResource, False$, 'application/hal+json')
    end else
        // There is an error condition so call the SetResponseError service.
        HTTP_Services('SetResponseError', '', '', 500, Error_Services('GetMessage'), FullEndpointURL)
    end

end api

By using the the FullEndpointURL and the  and the ParentURL prepopulated  prepopulated variables, we automatically have the the self and  and collection relations ready to add to the the _links property property:

Code Block
languagejs
{
   "address":"6649 N Blue Gum St",
   "city":"New Orleans",
   "firstName":"James",
   "lastName":"Butt",
   "state":"LA",
   "zipCode":"70116",
   "_links":{
      "self":{
         "href":"https://www.examples.org/api/customers/1"
      },
      "collection":{
         "href":"https://www.examples.org/api/customers"
      }
   }
}

...

Using the AddEmbeddedResources Service

HAL also supports the the _embedded reserved  reserved property, which is a way to include partial or whole resources within the primary resource object. These embedded resources are themselves stand-alone resources with their own endpoints. The usual reason they are embedded is so the client does not have to make multiple requests to get this same resource information. This is common with collection APIs (e.g., GET /customers). The following code sample uses the the GetObjects service  service to return an array of of customers resource  resource objects. These are then embedded into the primary resource object using the the AddEmbeddedResources service service:

Code Block
languagebp
API customers.GET

    // Create the primary resource object.
    objResource     = HTTP_Resource_Services('GetObject')
    If Error_Services('NoError') then
        // Create the sub-resource objects.
        objSubResources = HTTP_Resource_Services('GetObjects', 'CUSTOMERS', '', 'FIRST_NAME' : @FM : 'LAST_NAME', 'firstName' : @FM : 'lastName', '', '', '', FullEndpointURL)

        If Error_Services('NoError') then
            // Pass the sub-resource object list to the AddEmbeddedResources service. Use the 'customers' label to identify the type of sub-resource.
            HTTP_Resource_Services('AddEmbeddedResources', objResource, 'customers', objSubResources)
        end

        If Error_Services('NoError') then
            // Serialize the JSON object.
            jsonResource    = HTTP_Resource_Services('GetSerializedResource', objResource)
            // Set the response body with the serialized JSON object and set the Content-Type response header.
            HTTP_Services('SetResponseBody', jsonResource, False$, 'application/hal+json')
        end else
            // There is an error condition so call the SetResponseError service.
            HTTP_Services('SetResponseError', '', '', 500, Error_Services('GetMessage'), FullEndpointURL)
        end
    end

end api

...

Code Block
languagejs
{
   "_embedded":{
      "customers":[
         {
            "firstName":"Harrison",
            "lastName":"Haufler",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/266"
               }
            }
         },
         {
            "firstName":"Haydee",
            "lastName":"Denooyer",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/271"
               }
            }
         },
         {
            "firstName":"Heike",
            "lastName":"Berganza",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/254"
               }
            }
         },
         {
            "firstName":"Helga",
            "lastName":"Fredicks",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/202"
               }
            }
         },
         {
            "firstName":"Herman",
            "lastName":"Demesa",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/125"
               }
            }
         },
         {
            "firstName":"Herminia",
            "lastName":"Nicolozakes",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/287"
               }
            }
         },
         {
            "firstName":"Hillary",
            "lastName":"Skulski",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/186"
               }
            }
         },
         {
            "firstName":"Howard",
            "lastName":"Paulas",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/132"
               }
            }
         }
      ]
   }
}

Using the the GetDatabaseItems Service

We We previously introduced the  introduced the GetDatabaseItem service service. There is a similar service called called GetDatabaseItems and  and it is also a member of the HTTP_Resource_Services module module. Like the the GetDatabaseItem service service, the the GetDatabaseItems service  service is deprecated but we do not intend to remove it. For certain use cases it is quite handy for producing collection resources with embedded content:

...

This approach doesn't provide a way to customize the name of the embedded resource (e.g., customers, vendors, etc.). Instead, it just defaults to to item. Here is an example of the resource object:

Code Block
languagejs
{
   "_embedded":{
      "item":[
         {
            "firstName":"Harrison",
            "lastName":"Haufler",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/266"
               }
            }
         },
         {
            "firstName":"Haydee",
            "lastName":"Denooyer",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/271"
               }
            }
         },
         {
            "firstName":"Heike",
            "lastName":"Berganza",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/254"
               }
            }
         },
         {
            "firstName":"Helga",
            "lastName":"Fredicks",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/202"
               }
            }
         },
         {
            "firstName":"Herman",
            "lastName":"Demesa",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/125"
               }
            }
         },
         {
            "firstName":"Herminia",
            "lastName":"Nicolozakes",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/287"
               }
            }
         },
         {
            "firstName":"Hillary",
            "lastName":"Skulski",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/186"
               }
            }
         },
         {
            "firstName":"Howard",
            "lastName":"Paulas",
            "_links":{
               "self":{
                  "href":"https://www.examples.org/api/customers/132"
               }
            }
         }
      ]
   },
   "_links":{
      "self":{
         "href":"https://www.examples.org/api/customers"
      }
   }
}

Using the the AddFormAction Service Service

The services described above add hypermedia in the form of URI relations using using the reserved properties fromthe from the HAL specification (specifically the specifically _links and  and _embedded reserved properties). While URI relations provide valuable hypermedia to the client, many developers have expressed a desire for additional metadata to better instruct clients how to call the URI. For example, a URI by itself doesn't inform the client which HTTP methods are supported. Granted, the the OPTIONS method  method is intended to provide this information, but other requirements might necessitate out-of-band knowledge. This causes our APIs to lose their self-documenting character.

Mike Kelly, the author of the HAL specification, comments that  that omitting additional metadata in the HAL specification was "intentional" in order to keep it "focused on linking". However, he also suggests that "HAL is therefore a good candidate for use as a base media type on which to build more complex capabilities". This has spurred developers of RESTful APIs to shore up this gap through alternative alternative hypermedia types  types (such as as api+json, collection+json, hyper+json, and and siren+json). Unfortunately, none of them have the maturity and broad acceptance that HAL does. Also, some of them over complicate the task of adding simple hypermedia to a resource. HAL, on the other hand, is relatively lightweight and easy to implement and consume.

Therefore, the SRP HTTP Framework uses the HAL specification as its base media type and the AddLinkRelationship, AddEmbeddedResources AddEmbeddedResources, and GetDatabaseItems services  GetDatabaseItems services produce HAL compliant hypermedia. However, in those cases when a developer really wants to provide extra metadata to the client, we have provided the the AddFormAction service service. The The AddFormAction service  service adds a custom hypermedia structure inspired by the work Ben Greenberg and his team did for the Comcast Xfinity APIs (you can watch his presentation on this this here). To better explain the the AddFormAction service service, we have a dedicated article called How do I add hypermedia controls to a resource?.

...