Related entries and feeds: links and link expansion
While going through application scenarios for the ADO.NET Data Services Framework (Project Astoria) one of the first things we noticed is that data-centric applications usually want to bring down graphs of related resources in each interaction with the server. For example, if you are retrieving a resource that represents an "Event", you may want to also bring in the set of related Contact resources that are invited or the "Venue" resource where the event will take place. This write up briefly describes how we model associations between resources as Atom links and proposes a usage pattern of the atom:link element to support retrieving resource graphs in a single response. We're looking for feedback on the approach and also to get folks thinking about inlined content and whether it should be considered an extension to Atom.
More context on Astoria support for Atom here:
1. Links for modeling associations between resources
Related resources can be seen at the instance level as "links" in Atom terms. Of course, from the data application development perspective, it's interesting to make this discoverable at the service description (schema) level. In Astoria data services the underlying model is the Entity Data Model (EDM), which describes data in terms of "Entities" (instances of Entity Types) and associations between entities. In the context of the Atom interface, Entities are mapped to entries and Associations to links. So by looking at the service description a developer can discover the links that will be present in an entry of a given type.
We model related entries or feeds using a link with a "rel" attribute of "related", and with a "type" of either "application/atom+xml;type=feed" or "application/atom+xml;type=entry" depending on the cardinality of the other end of the association.
One tricky aspect is that we need to indicate which association it is. At the model level we have a "navigation property" that identifies the starting "end" of the association (e.g. "Attendees", "Venue"). We currently put that name in the "title" attribute of the link. That solution is not perfect, as we try not to overload constructs that are for human-readable content. However, the alternative is to use a custom attribute, and we've been trying not to introduce custom attributes unless absolutely needed. Another option would be to use different "rel" values to specify the relationship, which feels natural but makes it much less likely that generic processors will be able to do something interesting with it.
Do these trade-offs sound reasonable? Is any of the other options more appropriate?
Continuing with the Events sample, this is what an entry (/Events(456)) with links looks like:
<entry xml:base="https://localhost:81/EventsSample/" xmlns:d="https://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="https://www.w3.org/2005/Atom" m:type="EventsSample.Event">
<id>https://localhost:81/EventsSample/Events(456)</id>
<title type="text"></title>
<updated>2008-02-17T02:52:38Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Event" href="Events(456)" />
<link rel="related" type="application/atom+xml;type=entry" title="Venue" href="Events(456)/Venue" />
<link rel="related" type="application/atom+xml;type=feed" title="Attendees" href="Events(456)/Attendees" />
<content type="application/xml">
<d:EventID m:type="Int32">456</d:EventID>
<d:Name>Big Party</d:Name>
<d:NoteToAttendees>It's going to be a great party!</d:NoteToAttendees>
<d:DateAndTime m:type="DateTime">2008-03-05T06:00:00</d:DateAndTime>
</content>
</entry>
From the data modification perspective, links pointing to other resources in the service can be specified in the payload of POST and PUT operations, to establish links between the resource being manipulated and other existing resources.
2. Expanding links inline
As I summarized at the beginning of this note, we want to enable clients to request whole sub-graphs of data starting at some resource or set of resources. There are two aspects that need to be addressed: how does the client indicate that it wants one or more links expanded and how are the expanded links represented on the response.
How link expansion is requested is outside of the atom-syntax problem space, so I'll just briefly state what we currently do in case you have an opinion: data services support the query string option "$expand" to request link expansion. So you could say "/Events?$expand=Attendees " to retrieve all Events and all contacts that are attendees for each of them, or "/Events(456)?$expand=Attendees" to retrieve a single event (with key 456) and its attendees. Expand syntax allows for deep expands such as "Attendees/BestFriend" (expand Attendees, and on the expanded entry(es) expand BestFriend) and wide expands such as "Venue, Attendees/BestFriend" meaning expand two immediate links, and for the Attendees one further expand its BestFriend link.
For representing expanded links we put the expanded content inside the link element itself. According to section 4.2.7 of RFC 4287:
"The "atom:link" element defines a reference from an entry or feed to a Web resource. This specification assigns no meaning to the content (if any) of this element."
So it seems that adding content to the link element is not disallowed and at the same time it does not overlap with any existing semantics given to such construct. Based on that we thought it would be the perfect place for this information, as the link itself already contains the metadata about the link that we needed.
When a client indicates that the target of a link should be expanded, the server responds with the Atom representation of the resources pointed at by links wrapped in an <inline> element. For example, for "/Events(456)?$expand=Attendees,Venue" the response would be:
<entry xml:base="https://localhost:81/EventsSample/" xmlns:d="https://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="https://schemas.microsoft.com/ado/2007/08/dataservices/metadata " xmlns="https://www.w3.org/2005/Atom" m:type="EventsSample.Event">
<id>https://localhost:81/EventsSample/Events(456)</id>
<title type="text"></title>
<updated>2008-02-17T03:01:18Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Event" href="Events(456)" />
<link rel="related" type="application/atom+xml;type=entry" title="Venue" href="Events(456)/Venue">
<m:inline>
<entry m:type="EventsSample.Venue">
<id>https://localhost:81/EventsSample/Venues(789)</id>
<title type="text"></title>
<updated>2008-02-17T03:01:18Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Venue" href="Venues(789)" />
<link rel="related" type="application/atom+xml;type=entry" title="SalesContact" href="Venues(789)/SalesContact" />
<content type="application/xml">
<d:VenueID m:type="Int32">789</d:VenueID>
<d:Name>The Cool Place</d:Name>
<d:Description>Great place for parties!</d:Description>
<d:Capacity m:type="Int32">1500</d:Capacity>
<d:Type>Nightclub</d:Type>
</content>
</entry>
</m:inline>
</link>
<link rel="related" type="application/atom+xml;type=feed" title="Attendees" href="Events(456)/Attendees">
<m:inline>
<feed>
<title type="text">Attendees</title>
<id>https://localhost:81/EventsSample/Events(456)/Attendees</id>
<updated>2008-02-17T03:01:18Z</updated>
<link rel="self" title="Attendees" href="Events(456)/Attendees" />
<entry m:type="EventsSample.Contact">
<id>https://localhost:81/EventsSample/Contacts(123)</id>
<title type="text"></title>
<updated>2008-02-17T03:01:18Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Contact" href="Contacts(123)" />
<link rel="related" type="application/atom+xml;type=entry" title="BestFriend" href="Contacts(123)/BestFriend" />
<content type="application/xml">
<d:ContactID m:type="Int32">123</d:ContactID>
<d:FirstName>John123</d:FirstName>
<d:LastName>Doe123</d:LastName>
<d:EmailAddress>jd123@foo.com</d:EmailAddress>
<d:Phone>123-456-123</d:Phone>
<d:BirthDate m:type="Nullable`1[System.DateTime]">1990-04-01T00:00:00</d:BirthDate>
</content>
</entry>
<entry m:type="EventsSample.Contact">
<id>https://localhost:81/EventsSample/Contacts(124)</id>
<title type="text"></title>
<updated>2008-02-17T03:01:18Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Contact" href="Contacts(124)" />
<link rel="related" type="application/atom+xml;type=entry" title="BestFriend" href="Contacts(124)/BestFriend" />
<content type="application/xml">
<d:ContactID m:type="Int32">124</d:ContactID>
<d:FirstName>John124</d:FirstName>
<d:LastName>Doe124</d:LastName>
<d:EmailAddress>jd124@foo.com</d:EmailAddress>
<d:Phone>123-456-124</d:Phone>
<d:BirthDate m:type="Nullable`1[System.DateTime]">1990-05-01T00:00:00</d:BirthDate>
</content>
</entry>
<!-- more entries for contacts that will be -->
<!-- attendees in this party -->
</feed>
</m:inline>
</link>
<content type="application/xml">
<d:EventID m:type="Int32">456</d:EventID>
<d:Name>Big Party</d:Name>
<d:NoteToAttendees>It's going to be a great party!</d:NoteToAttendees>
<d:DateAndTime m:type="DateTime">2008-03-05T06:00:00</d:DateAndTime>
</content>
</entry>
I focused on the GET operations above. We think it would be better to stay away from attempting to support full modification operations on expanded graphs. In particular, we do not handle PUT on more than one entry at a time today. We do support POSTing an expanded graph, and we simply create all the nested entries and link them to the parent entry, creating the whole graph in a single operation.
Feedback in general about this approach would be greatly appreciated.
Pablo Castro
Technical Lead
Microsoft Corporation
This post is part of the transparent design exercise in the Astoria Team. To understand how it works and how your feedback will be used please look at this post.
Comments
- Anonymous
February 18, 2008
PingBack from http://www.biosensorab.org/2008/02/19/related-entries-and-feeds-links-and-link-expansion/ - Anonymous
February 18, 2008
Yes, using the Link element seems more descriptive and true to the spirit of Atom than a custom element like <gd:entryLink/> in GData. Interestingly for interop the GData approach uses the same number of nested elements for a child Entry or Feed as the MS approach above. - Anonymous
February 20, 2008
I was thinking about how to do this (drill into collections dynamically) when I was playing around with Astoria in Popfly. If I understand this correctly, it will really open things up nicely. I'm not really worried about ATOM conformance, so I'll let you guys work that part out! :-)Julie - Anonymous
February 21, 2008
Pablo Castro explains the reasoning behind wanting to add links to the Astoria payload so that it's - Anonymous
February 29, 2008
The comment has been removed - Anonymous
February 29, 2008
PS. Just remembered - atom:link@rel supports URIs - why not give the relation a URI? rel="related" does seem a bit redundant. - Anonymous
February 29, 2008
Hey Danny,Thanks for the pointer, I'll read on the GRDDL stuff.Re: links...yeah, I agree, rel="related" is not exactly adding information there. Probably a URI with a predictable form (to know what relationship it is) is the best choice.-pablo - Anonymous
March 05, 2008
I agree that using the Link element is a natural and understandable way to go. But to make the expand functionality complete and usable you must be able to filter on properties of the nested entities. So, something likeCustomer?$expand=SalesOrderHeader & $filter=SalesOrderHeader/TotalDue gt 1000.0Should be possible. - Anonymous
March 21, 2008
I second (or third :) the use of a URI for atom:link@rel. I known what you guys are striving for with the generic "related" rel type but the client already has to understand the schema of an "event" entry to do anything useful with it and to do the $expand thing they even have to know up front that events have related Venu & Attendees resources. So strongly typing the rel value seems like the more consisten thing to do.-steve - Anonymous
June 04, 2008
With respect to the expanding links inline, at Google we are considering using the atom:content element inside the atom:link element. The meaning of atom:content is already specified, and given the discussion on atom-syntax the consensus appears to be that atom:content appearing as a child of atom:link would qualify it as foreign markup and thus be valid. - Anonymous
November 15, 2009
Hi, I see this discussion is a bit old now, but FWIW we're using Atom in a project to develop a system for managing data and metadata for malaria research, and have hit some similar questions.In particular, I have been wondering if it was reasonable to expand related entries inline, and if so, what is best practice for doing so, so it is a relief to see others have considered doing something similar. Personally, I think it makes good sense, and the only question is, what element should you use as the direct child of the link element to contain the inline entry? We had tentatively plumped for stickiing the expanded atom:entry directly in the atom:link element, although this looks like it would violate the atom format spec, but it just seemed unnecessary to use some intermediate (like m:inline as you've done). I don't really mind using an intermediate, but if it is necessary it would be nice to have some consensus on what best practice is - I see Joe Gregorio has suggested using atom:content, although (without delving any deeper) I'm a bit confused as to why an atom:content element can be treated as foreign markup - the content model for atom:link is undefinedContent = (text|anyForeignElement)*, and anyForeignElement seems to exclude anything in the atom namespace.The other two questions we had were (1) how should we represent different types of link relationships, and (2) where should we stick the foreign markup that is our data?For (1) we chose to use rel attribute values, because this seemed to correspond to usage of rel attributes in HTML. We chose to use plain strings as rel attribute values (e.g. "chassis.study") but thought of using URIs as per Danny's suggestion. We haven't gone for URIs yet because we have no compelling reason for it, but would be happy to shift if there was a good reason.For (2) we've stuck the foreign markup in the atom:content element with type application/xml as you've done, which seemed the natural place to put it from reading the atom specs, and because it keeps the distinction between data and metadata a bit clearer, however were concerned that google's data APIs don't bother and stick the foreign markup directly within the atom:entry element. Maybe it doesn't matter, but it would be nice to have some guidance / consensus on this.I'd be grateful to hear any comments or followup on where you've gone with these issues.Cheers,Alistair