Posts in "REST"

Integrating application data using Atom feeds

While Atom is often associated with things like blogs or news feeds, it turns out it’s also an excellent vehicle for integrating application data across your systems. At Infi we’re using this technique for several integration scenario’s for our customers, and it’s quickly becoming a preferred way of solving this kind of problem.

This post assumes a basic understanding of Atom (though you can probably follow it if you don’t). If you need to brush up your knowledge, you can read a basic introduction here.

Context

We often have a situation where a subset of users manage data that is exposed to a much larger set of different users. For example, we might have a website that deals with selling used cars from car dealers, where the car dealers manage the car data, such as pictures, descriptions, etc. This data is then viewed by a large group of users on the website. One of our customers has a similar model, though in a different domain.

As this customer has grown, more and more additional systems needed access to this data, such as several websites, external API’s and an e-mail marketing system. We used to do these integrations on an ad-hoc basis, using a custom-built solution for every integration (e.g. database integration, RPC, XML feeds). As you can imagine, this situation was becoming an increasing point of pain since it doesn’t really scale well in a couple of dimensions, such as performance, documentation, reward/effort, etc. The situation looked more or less like this:

current-status

Requirements

To remove some of this pain we decided on building something new, specifically having the following requirements:

Good performance. This means 2 things: first, we want low latency responses on requests on our integration system. Second, we don’t want high loads on the integration solution pulling down other parts of our system.

Low latency updates. It’s OK for the data to be eventual consistent, but under normal conditions we want propagation times to be low, say on the order of several minutes or less.

No temporal coupling. Consumers shouldn’t be temporally coupled to the integration system. This means they should be able to continue doing their work even when the integration endpoint is unavailable.

Support multiple logical consumers. All (new) integrations should be done through the new system, such that we don’t need to do any modifications for specific consumers.

Ease of integration. It should be easy for consumers to integrate with this new system. Ideally, we should only have to write and maintain documentation on the domain-specific parts and nothing else.

It turns out we can address all these requirements by combining two ideas: snapshotting the data on mutations and consumer-driven pub/sub with Atom feeds.

Solution

The core idea is that whenever an update on a piece of data occurs, we snapshot it, and then post it to an append-only Atom feed:

handling_mutation

This way we essentially build up a history of all the mutations inside the feed. The second part of the solution comes from organizing (paging) the feed in a specific way (this builds on top of the Feed Paging and Archiving RFC):

structure

In this case we divide the snapshots into pages of 20 entries each.

The value in this structure is that only the root and the currently ‘active’ pages (i.e. not completely filled pages) have dynamic content. All the other resources/URLs are completely static, making them indefinitely cacheable. In general, most of the pages will be full, so almost everything will be cacheable. This means we can easily scale out using nginx or any other caching HTTP proxy.

Consuming the feed

From a consumer point of view you’re gonna maintain your own copy of the data and use that to serve your needs. You keep this data up to date by chasing the ‘head’ of the feed. This is done by continuously following the rel=”previous” link in the feed documents.  Once you read the head of the feed (indicated by no entries, and a missing link rel=”previous”) you keep polling on that URL until a new entry appears.

Evaluating the solution

To see how this solution fulfills our requirements, let’s revisit them:

Good performance 

  • Since data is snapshotted, we don’t need to do expensive querying on our transaction-system database. This allows for both quick responses to in the integration system as well as isolation of our transactional system.
  • Because almost everything is cacheable, you can easily scale out and provide low-latency responses.

Low latency updates

  • The detection of changes to the data is event-based instead of some sort of large batch process to detect changes. This allows the data to flow quickly through the system and appear in the feed. Polling the head of the feed is not expensive and can therefore be done on minute or even second basis, so the clients themselves will also be able to notice the updates quickly.

No temporal coupling

  • First of all, the consumers are decoupled from the integration system because they have their own copy of the data so they don’t have to query the feed in real-time to serve their needs. Secondly, the integration system itself is also not coupled to the system containing the original data, since the snapshots are stored in the Atom feed.

Support multiple logical consumers

  • Multiple logical consumers are trivially supported by handing out the URL to the feed endpoint.
  • One problem is different consumers requiring different parts of the data.  Currently, we’ve solved this by always serving the union of all required data pieces to all the consumers. This isn’t exactly elegant, but it works (though we sometimes need to add fields). A better solution would be for clients to send an Accept header containing a mediatype that identifies the specific representation of the data they want.
  • We also built in rudimentary authentication based on pre-shared keys and HTTP basic authentication.

Ease of integration

  • This is where the whole Atom thing comes in. Since both Atom and paging techniques are standard, we only need to document the structure of our own data. From a client point of view, they can use any standard Atom reader to process the data.
  • To make things even more easy to integrate, we also created a custom JSON representation for Atom. This is useful for consumers on platforms that don’t have strong XML support.

Conclusion

As you can see, all our requirements are met by this solution. In practice, it also works very well. We’ve been able to reduce loads on our systems, get data quicker into other systems, and both for us and our partners it’s easier to implement integrations. We started out doing this for just one type of data (property information), but quickly implemented it for other types of data as well.

One of the challenges is pick the right level of granularity for the feeds.  Pick the granularity too narrow, and it becomes harder for a client to consume (needs to keep track of lots of feeds). Pick it too wide, and there will be a lot of updates containing little change. In our cases, common sense and some basic analysis of how many updates were expected worked out fine.

The main drawbacks we encounter are twofold. First, people not familiar with Atom and the paging standards sometimes had problems working with the feed structure. Especially when there’s no platform support for Atom or even basic XML, we sometimes still have to help out. Second, for some integrations maintaining a copy of the data on the consumer side proved to be a bit too much (or not even possible). For these situations we actually built a consumer of our own, which served data to these thinner clients.

Credits

The ideas in this post are not new. We were particularly inspired by EventStore, which also provides an Atom based API for exposing its event streams.

REST zonder media types is geen REST

Een veelal onbegrepen onderdeel van REST is de rol van media types. In deze post ga ik iets dieper in op deze media types en leg ik uit waarom een REST API waarvoor geen media types gedefinieerd zijn nooit een REST API kan zijn.

Om aan te geven hoeveel verwarring er is over de rol van media types in REST APIs: een van mijn helden, Fowler, zegt bijvoorbeeld dat het prima mogelijk is om een REST API aan te bieden zonder dat je gebruikt maakt van media types, terwijl Fielding juist zegt dat het gebruik van media types een voorwaarde is voor REST. Een van de redenen voor deze verwarring is dat REST verschillende dingen voor verschillende mensen betekent. Voor sommigen betekent het gebruik van HTTP verbs in je API, voor anderen betekent het het gebruik van JSON als data-uitwisselings formaat, voor weer anderen betekent dat je de client en server kunnen onderhandelen over het uitwisselingsformaat. Voor Fielding betekent het een architectuurstijl waarmee je applicaties op internet-schaal kunt bouwen. Hierbij bedoelen we met schaal niet (alleen) performance, maar ook de uitdagingen die ontstaan doordat er meerdere organisaties en mensen betrokken zijn. Nu heeft Fielding de term REST geintroduceerd/gedefinieerd dus als je je API RESTful wilt noemen, zul je wat mij betreft moeten voldoen aan de eisen die hij daarvoor gezet heeft.

Als je dit standpunt aanbrengt in discussies hoor ik vaak het argument “dat dat wel erg puristisch is”. Nu is het opzich waar dat purisme in softwareontwikkeling niet altijd behulpzaam is, maar te vaak wordt dat argument gebruikt om af te wijken van de standaarden. Een veel voorkomend voorbeeld is dat organisatisch die starten met SCRUM oid de methode iets aanpassen “omdat dat beter past bij de organisatie”. De holistische aard van dergelijke processen wordt daarbij dan genegeerd, en uiteraard faalt de adoptie van het proces. Ik kan hier nog even over doorgaan, maar dat is voor een latere post.

Eenzelfde situatie ontstaat bij REST. Als je het belang van media types niet inziet, gebruik je REST waarschijnlijk voor de verkeerde reden. Voor Fielding is REST een architectuurstyle die gebruikt kan worden voor “distributed hypermedia systems” die qua levensduur gebruikt worden “on the scale of decades”. De eerste vraag die je moet stellen als je een RESTful API wilt aanbieden is of je een dergelijk systeem gaat bouwen. Waarschijnlijk niet. Veel van de APIs die ik tegenkom zijn voor het aanbieden van generieke, goed gedefinieerde diensten aan externe partijen. Denk hierbij aan koppelingen met e-mail en payment providers, weerdiensten, reserveringssystemen, etc. Dit zijn over het algemeen een paar calls die een zeer specifieke functionaltieit aanbieden en die qua interface ook zeer stabiel zijn. RPC is zeer geschikt voor dergelijke koppeling. De constraints van REST zijn daarentegen overdreven voor dergelijke calls, voornamelijk omdat het gebruik van caching/intermediates voor dergelijke calls vaak niet eens wenselijk is en er nauw tevens nauw contact zal zijn tussen leverancier en consumer, waardoor individual evolvability van veel minder belang is. En dit zijn juist de twee grote voordelen van REST.

Maargoed, stel dat je nu toch hebt besloten om een RESTful API aan te bieden, kan dat dan kwaad? Het antwoord is zonder meer: Ja, het kan kwaad. En wel om de reden dat een REST API je veel meer development effort (en dus geld) zal kosten, dan een vergelijkbare API via bijvoorbeeld SOAP. Waarom? Omdat SOAP juist een standaard is voor het doen van RPCs. Als de functionaliteit die je aan wilt bieden dus veel weg heeft van RPC, is dit een zeer natuurlijke oplossing. Het enige wat je zult moeten documenteren is de semantiek van je API. Wat betekenen de nouns en de operaties, maar niet hoe er gepraat moet worden tussen client en server en hoe fundamentele data structuren er op de lijn uit zien.

Omdat SOAP een standaard is is er bovendien veel tooling en support op de markt. Met behulp van WSDLs en IDEs is het zeer eenvoudig om een koppeling te bouwen tussen een SOAP client/server. Visual Studio kan bijvoorbeeld uit jouw service class/interface een WSDL genereren die vervolgens door een PHP client geconsumed kunnen worden, waarbij je in 10 minuten een werkende oplossing hebt. Dit in contrast met REST, daar zul veel meer moeten documenteren, namelijk niet alleen de semantiek, maar ook hoe je door de API heen navigeert, en wat voor operaties er mogelijk zijn.

Er wordt soms gedacht dat in een REST API de HTTP verbs en status codes al een vooraf gedefinieerde betekenis hebben voor elke willekeurige URI/resource. Dit wordt ook vaak als voordeel aangehaald, want “elke client die HTTP spreekt kan met de API communiceren”. Als je er een beetje over nadenkt, zul je echter al snel tot de conclusie komen dat dit niet waar kan zijn. Hoe kun je nu met iets praten waarvan je niet weet wat het is, wat het kan of wat het doet. Het onderscheid ligt ‘m in horen vs verstaan. De client en server zullen elkaar wel horen, maar elkaar niet verstaan. Om elkaar ook te verstaan heb je meer informatie nodig. De toegevoegde waarde van het gebruik van uniforme verbs en status codes is dat intermediaries weten hoe ze de requests/result moeten behandelen, bijvoorbeeld dat een GET safe is, maar een POST niet. En dat een 4xx return code een client fout betekent. Overigens zijn deze definities onderdeel van HTTP, niet zozeer van REST. REST praat alleen over een “uniform interface” en het gebruik van de verbs en status codes in HTTP is hier een implementatie van.

En daarmee komen we bij de crux van deze post aan, namelijk dat je geen REST kunt doen zonder media types. De media types zijn namelijk de documentatie, en ook de enige documentatie van je API. Hierin staat hoe je door je API navigeert, wat de mogelijkheden zijn, en wat voor semantische betekenis alles heeft. Een client en server zullen daarom nooit iets gedaan krijgen zonder dat ze allebei het media type kennen. Als je het niet gelooft nodig ik je uit om bijvoorbeeld eens de AtomPub RFC er bij te pakken. Kijk bijvoorbeeld naar sectie 4.3 over de beschikbare verbs en wat ze doen, en sectie 4.4 dat de overige verbs geen betekenis hebben volgens die RFC. Ook Fielding zegt hier iets over in de context van HTML: “anchor elements with an href attribute create a hypertext link that, when selected, invokes a retrieval request (GET) on the URI corresponding to the CDATA-encoded href attribute.”.

Als er beslissingen gemaakt worden op basis van andere informatie, zoals bijvoorbeeld de URI structuur, dan wordt dit ook wel out-of-band informatie genoemd. Het gebruik van dergelijke informatie is niet RESTful, omdat de server onafhankelijk moet kunnen doorevolueren.

Als er in een API bijvoorbeeld gebruik van application/json of application/xml als Content-Type (zonder Link: <location>; rel=”profile”), dan heb je out-of-band informatie nodig om de informatie te interpreteren. Het probleem met deze types is namelijk dat deze niks zeggen over hun inhoud (behalve dat het geformateerd is als xml/json) en dus ook niet hoe het door de client geinterpreteerd moet worden. Ook hier wordt vaak de URI gebruikt als indicatie wat de data is, maar dat is dus niet RESTful.

REST is dus helaas geen silver bullet voor zelfdocumenterende APIs. Het geeft wel aan hoe je moet documenteren, namelijk met media types. Alle documentatie wordt vastgelegd in het media type, en als je deze niet aanbiedt is het dus onmogelijk om een RESTful API te bouwen. Het definieren van deze media types hoeft niet per se veel werk te zijn, maar het is altijd meer werk dan het documenteren van een SOAP API, omdat SOAP al een standaard biedt voor het aanroepen van acties en de representatie van well-known datatypes.

Dankzij deze documantie effort zal het bouwen van een REST API altijd meer werk zijn dan een corresponderende API in SOAP. Je zult dus een goede reden moeten hebben om dit te doen (“distributed hypermedia systems”, “on the scale of decades”). En als je die reden dan hebt, dan moet je ook de full monty gaan, dus inclusief media types (sterker nog, daar moet je mee beginnen), om die voordelen ook daadwerkelijk te bereiken. Doe je dit niet, dan zul je als resultaat een RPC API hebben, waar niemand standaard aan kan koppelen en je heel veel werk gedaan hebt wat ook al door frameworks/libraries opgelost wordt, namelijk RPC.

Het moraal van dit verhaal is dat hoewel het tegenwoordig hot is om RESTful APIs te bouwen, het verstandig is om hier terughoudend mee te zijn. De voordelen die het biedt zijn namelijk lang niet altijd nodig, terwijl het sowieso meer tijd, geld en energie gaat kosten. En dat is waarschijnlijk beter gespendeerd aan andere dingen.

PS. Als je niet aan SOAP wilt omdat je liever met JSON praat, zoals in de browser of op mobile devices, dan zijn er nog andere opties. Zo biedt WCF een JSON binding en zijn er SOAP to JSON proxies.