Skip to content
Chris/0 edited this page Nov 6, 2018 · 5 revisions

The operation Embed enables what HAL+JSON calls the "hypertext cache pattern". A criticism of linked data schemes for APIs is that creating links for data that once was or could have been in the main body of the response causes the number of HTTP requests to explode. The "_embedded" key seeks to overcome this.

Please refer to this sample document for examples for the remainder of the documentation:

/// <summary>Defines HAL+JSON transformations for the application.</summary>
sealed class HalProfile
    : IHalProfile
{
    static readonly Uri _pool = new Uri("https://relations.fen.cimpress.io/pool", Absolute);
    static readonly Uri _events = new Uri("https://relations.fen.cimpress.io/events", Absolute);
    static readonly Uri _orderedItem = new Uri("https://relations.fen.cimpress.io/ordered-item", Absolute);
    static readonly Uri _asset = new Uri("https://relations.fen.cimpress.io/asset", Absolute);

    /// <inheritdoc/>
    void IHalProfile.OnTransformationMapCreating(ITransformationMap transformationMap)
    {
        transformationMap
            .Self<PrintJob>(pj => Route("GetByPrintJobId", new { pj.Id }))
            .LinkAndIgnore(_pool, pj => pj.Pool)
            .Link(_events, pj => Route("GetEventsForPrintJob", new { pj.Id }))
            .Embed(_orderedItem, pj => pj.Items, pj => Route("GetPrintJobItem", new { pj.Id }))
            .Link(_asset, pj => pj.Assets, a => new Constant(a.Uri) { Title = a.Role })
            .Ignore(pj => pj.Id, pj => pj.Watchers, pj => pj.Events, pj => pj.Assets);

        transformationMap
            .Self<PrintJobEventCollection, PrintJobEvent>(pjec => Route("GetEventsForPrintJob", new { id = pjec.PrintJobId }))
            .LinkElements("item", pje => Route("GetEvent", new { e.Id, e.PrintJobId }))
            .Link("up", pjec => Route("GetByPrintJobId", new { id = pjec.PrintJobId }))
            .Hoist(pjec => pjec.Count);

        transformationMap
            .Self<PrintJobEvent>(pje => Route("GetEvent", new { pje.Id, pje.PrintJobId }))
            .Link("collection", pje => Route("GetEventsForPrintJob", new { id = pje.PrintJobId }))
            .Link("up", e => Route("GetByPrintJobId", new { id = e.PrintJobId }))
            .Ignore(pj => pj.Id, pj => pj.PrintJobId);

        transformationMap
            .Self<ItemCollection, Item>(ic => Route("GetItemsForPrintJob", new { id = ic.PrintJobId }))
            .LinkElements("item", i => Const(i.ItemUri))
            .Link("up", ic => Route("GetByPrintJobId", new { id = ic.PrintJobId }))
            .Hoist(ic => ic.Count);

        transformationMap
            .Self<Item>(i => i.ItemUri)
            .Link(_asset, i => i.Assets, a => new Constant(a.Uri) { Title = a.Role })
            .Link("collection", i => Route("GetItemsForPrintJob", new { id = i.PrintJobId }))
            .Link("up", i => Route("GetByPrintJobId", new { id = i.PrintJobId }))
            .Ignore(pj => pj.ItemUri, pj => pj.PrintJobId, pj => pj.Assets);
    }
}

Given this modified example from the specification:

{
  "_links": {
    "self": { "href": "https://example.invalid/books/the-way-of-zen" },
    "author": { "href": "https://example.invalid/people/alan-watts" }
  }
}

We can see that retrieving the book resource (presumably with the title The Way of Zen), when represented like this, requires an additional HTTP request in order to retrieve the author (presumably named Alan Watts). This is not outright bad, given that a person is an independently addressable resource. Embedding the person resource looks like this:

{
  "_links": {
    "self": { "href": "https://example.invalid/books/the-way-of-zen" },
    "author": { "href": "https://example.invalid/people/alan-watts" }
  },
  "_embedded": {
    "author": {
      "_links": { "self": { "href": "https://example.invalid/people/alan-watts" } },
      "name": "Alan Watts",
      "born": "January 6, 1915",
      "died": "November 16, 1973"
    }
  }
}

This use can be seen on line 17 of the example.

.Embed(_orderedItem, pj => pj.Items, pj => Route("GetPrintJobItem", new { pj.Id }))

Embedding should be used judiciously, as one advantage of using linked data is that response bodies can become significantly smaller once all pertinent data are linked. If every individually addressable sub-value on a value is moved to being embedded, the response body will become even larger, due to the overhead of links and recursive embeds.

When embedding, consider a holistic view of the data owned by the API in question. For example, if the "people" resource in the example above embedded all of the books written by that person, the list of books would contain The Way of Zen, the "_embedded" section of which would include Alan Watts! In fact, every book would embed the same resource, wasting bytes and transmission time.

In short, link plenty and embed carefully.

Arrays

For developers unfamiliar with the HAL+JSON specification, its handling of arrays is likely to be surprising. Here is a modified example from the specification.

{
  "_links": {
    "self": { "href": "https://examples.invalid/orders" },
    "next": { "href": "https://examples.invalid/orders?page=2" },
    "find": { "href": "https://examples.invalid/orders{/id}", "templated": true }
  },
  "_embedded": {
    "item": [
      {
        "_links": {
          "self": { "href": "https://examples.invalid/orders/123" },
          "basket": { "href": "https://examples.invalid/baskets/98712" },
          "customer": { "href": "https://examples.invalid/customers/7809" }
        },
        "total": 30.00,
        "currency": "USD",
        "status": "shipped",
      },
      {
        "_links": {
          "self": { "href": "https://examples.invalid/orders/124" },
          "basket": { "href": "https://examples.invalid/baskets/97213" },
          "customer": { "href": "https://examples.invalid/customers/12369" }
        },
        "total": 20.00,
        "currency": "USD",
        "status": "processing"
      }
    ]
  },
  "count": 2,
  "currentlyProcessing": 14,
  "shippedToday": 20
}

Because a HAL+JSON response hangs on the keys "_links" and "_embedded", the top-level value cannot be an array. (Where would the keys and values go?) For that reason, arrays are considered to be embedding themselves. In this library, this embedding is handled unconditionally.

Clone this wiki locally