Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Created part2 of restful zf2 apis posts
- Loading branch information
1 parent
54b8d3c
commit 3579d26
Showing
3 changed files
with
357 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,351 @@ | ||
<?php | ||
use PhlyBlog\AuthorEntity; | ||
use PhlyBlog\EntryEntity; | ||
|
||
$author = new AuthorEntity(); | ||
$author->setId('matthew'); | ||
$author->setName("Matthew Weier O'Phinney"); | ||
$author->setEmail("me@mwop.net"); | ||
$author->setUrl("http://mwop.net"); | ||
|
||
$entry = new EntryEntity(); | ||
|
||
$entry->setId('2013-02-12-restful-apis-with-zf2-part-2'); | ||
$entry->setTitle('RESTful APIs with ZF2, Part 2'); | ||
$entry->setAuthor($author); | ||
$entry->setDraft(false); | ||
$entry->setPublic(true); | ||
$entry->setCreated(new \DateTime('2013-02-12 05:42', new \DateTimezone('America/Chicago'))); | ||
$entry->setUpdated(new \DateTime('2013-02-12 05:42', new \DateTimezone('America/Chicago'))); | ||
$entry->setTimezone('America/Chicago'); | ||
$entry->setTags(array( | ||
'php', | ||
'rest', | ||
'http', | ||
'zf2', | ||
'zend framework', | ||
)); | ||
|
||
$body =<<<'EOT' | ||
<p> | ||
In my <a href="/blog/2013-02-11-restful-apis-with-zf2-part-1.html">last post</a>, | ||
I covered some background on REST and the Richardson Maturity Model, and some | ||
emerging standards around hypermedia APIs in JSON; in particular, I outlined | ||
aspects of Hypermedia Application Language (HAL), and how it can be used to | ||
define a generic structure for JSON resources. | ||
</p> | ||
<p> | ||
In this post, I cover an aspect of RESTful APIs that's often overlooked: | ||
reporting problems. | ||
</p> | ||
EOT; | ||
$entry->setBody($body); | ||
|
||
$extended=<<<'EOT' | ||
<h2>Background</h2> | ||
<p> | ||
APIs are useful when they're working. But when they fail, they're only | ||
useful if they provide us with meaningful information; if all I get is | ||
a status code, and no indication of what caused the issue, or where I might | ||
look for more information, I get frustrated. | ||
</p> | ||
<p> | ||
In consuming APIs, I've come to the following conclusions: | ||
</p> | ||
<ul> | ||
<li> | ||
Error conditions need to provide detailed information as to what went | ||
wrong, and what steps I may be able to take next. An error code with | ||
no context gives me nothing to go on. | ||
</li> | ||
<li> | ||
Errors need to be reported consistently. Don't report the error one way | ||
one time, and another way the next. | ||
</li> | ||
<li> | ||
<strong>DO</strong> use HTTP status codes to indicate an error happened. | ||
Nothing is more irksome than getting back a 200 status with an error | ||
payload. | ||
</li> | ||
<li> | ||
Errors should be reported in a format I have indicated I will Accept | ||
(as in the HTTP header). Perhaps the only think more irksome than a 200 | ||
status code for an error is getting back an HTML page when I expect | ||
JSON. | ||
</li> | ||
</ul> | ||
<h2>Why Status Codes Aren't Enough</h2> | ||
<p> | ||
Since REST leverages and builds on HTTP, an expedient solution for reporting | ||
problems is to simply use <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP status codes</a>. | ||
These are well understood by web developers, right? | ||
</p> | ||
<p> | ||
4xx error codes are errors made by the requestor, and are actually fairly | ||
reasonable to use for reporting things such as lack of authorization tokens, | ||
incomplete requests, unsupportable operations, or non-supported media types. | ||
</p> | ||
<p> | ||
But what happens when the error is on the server - because something has | ||
gone wrong such as inability to reach your persistence layer or credential | ||
storage? The 5xx series of status codes is sparse and wholly unsuited to | ||
reporting errors of these types -- <em>though you'll likely still want to use | ||
a 500 status to report the failure</em>. But what do you present to the consumer | ||
so that they know whether or not to try again, or what to report to you | ||
so that you can fix the issue? | ||
</p> | ||
<p> | ||
A status code simply isn't enough information most of the time. Yes, you | ||
want to define standard status codes so that your clients can perform | ||
reasonable branching, but you also need a way to communicate <em>details</em> | ||
to the end-user, so that they can log the information for themselves, display | ||
information to their own end-users, and/or report it back to you so you can | ||
do something to resolve the situation. | ||
</p> | ||
<h2>Custom Media Types</h2> | ||
<p> | ||
The first step is to use a custom media type. Media types are typically | ||
both a name as well as a structure -- and the latter is what we're after | ||
when it comes to error reporting. | ||
</p> | ||
<p> | ||
If we return a response using this media type, the client then knows how | ||
to parse it, and can then process it, log it, whatever. | ||
</p> | ||
<p> | ||
Sure, you can make up your own format -- and as long as you are consistent | ||
in using it, and you document it. But personally, I don't like inventing | ||
new formats when standard formats exist already. Custom formats mean that | ||
custom clients are required for working with the services; using a standard | ||
format can save effort and time. | ||
</p> | ||
<p> | ||
In the world of JSON, I've come across two error media types that appear to | ||
be gaining traction: <code>application/api-problem+json</code> and | ||
<code>application/vnd.error+json</code> | ||
</p> | ||
<h3>API-Problem</h3> | ||
<p> | ||
This particular media type is <a | ||
href="http://tools.ietf.org/html/draft-nottingham-http-problem-02">via the | ||
IETF</a>. Like HAL, it provides formats in both JSON and XML, making it | ||
a nice cross-platform choice. | ||
</p> | ||
<p> | ||
As noted already, the media type is <code>application/api-problem+json</code>. | ||
The representation is a single resource, with the following properties: | ||
</p> | ||
<ul> | ||
<li> | ||
"describedBy": a URL to a document describing the error condition (required) | ||
</li> | ||
<li> | ||
"title": a brief title for the error condition (required) | ||
</li> | ||
<li> | ||
"httpStatus": the HTTP status code for the current request (optional) | ||
</li> | ||
<li> | ||
"detail": error details specific to this request (optional) | ||
</li> | ||
<li> | ||
"supportId": a URL to the specific problem occurrence (e.g., to a log message) (optional) | ||
</li> | ||
</ul> | ||
<p> | ||
As an example: | ||
</p> | ||
<div class="example"><pre><code language="http"> | ||
500 Internal Error | ||
Content-Type: application/api-problem+json | ||
{ | ||
"describedBy": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html", | ||
"detail": "Status failed validation", | ||
"httpStatus": 500, | ||
"title": "Internal Server Error" | ||
} | ||
</div> | ||
<p> | ||
The specification allows a large amount of flexibility -- you can have your | ||
own custom error types, so long as you have a description of them. You can | ||
provide as little or as much detail as you want, and even decide what | ||
information to expose based on environment. | ||
</p> | ||
<p> | ||
I personally like to point to the HTTP status code definitions, and then | ||
provide request-specific detail; I find this gives quick and simple | ||
results that I can later shape as I add more detail to my API. However, | ||
it definitely encourages you to have unique error types with discrete | ||
URIs that describe them -- never a bad thing when creating APIs. | ||
</p> | ||
<h3>vnd.error</h3> | ||
<p> | ||
This is a <a href="https://github.com/blongden/vnd.error">proposed media | ||
type</a> within the HAL community. Like HAL, it provides formats in both | ||
JSON and XML, making it a nice cross-platform choice. | ||
</p> | ||
<p> | ||
It differentiates from API-Problem in a few ways. First, it allows, and even | ||
encourages, reporting collections of errors. If you consider PHP exceptions | ||
and the fact that they support "previous" exceptions, this is a powerful | ||
concept; you can report the entire chain of errors that led to the response. | ||
Second, it encourages pushing detail out of the web service; errors include | ||
a "logRef" that points to where the error detail lives. This is probably | ||
better illustrated than explained. | ||
</p> | ||
<p> | ||
The response payload is an array of objects. Each object has the following | ||
members: | ||
</p> | ||
<ul> | ||
<li> | ||
"logRef": a unique identifier for the specific error which can then be | ||
used to identify the error within server-side logs (required) | ||
</li> | ||
<li> | ||
"message": the error message itself (required) | ||
</li> | ||
<li> | ||
"_links": HAL-compatible links. Typically, "help", "describes", and/or | ||
"describedBy" relations will be defined here. | ||
</li> | ||
</ul> | ||
<p> | ||
As an example, let's consider the API-Problem example I had earlier, and | ||
provide a vnd.error equivalent: | ||
</p> | ||
<div class="example"><pre><code language="http"> | ||
500 Internal Error | ||
Content-Type: application/vnd.error+json | ||
[ | ||
{ | ||
"logRef": "someSha1HashMostLikely", | ||
"message": "Status failed validation", | ||
"_links": { | ||
"describedBy": {"href": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"} | ||
} | ||
} | ||
] | ||
</div> | ||
<p> | ||
vnd.error basically begs you to create custom error types, with documentation | ||
end-points that detail the source of the error and what you can do about it | ||
(this is true of API-Problem as well). | ||
</p> | ||
<p> | ||
The requirement to include "logRef" and have it be unique can be a stumbling | ||
block to implementation, however, as it requires effort for uniquely | ||
identifying requests, and logging. However, both the identification and | ||
logging can be automated. | ||
</p> | ||
<h2>Summary</h2> | ||
<p> | ||
Error reporting in APIs is as important as the normal resource payloads | ||
themselves. Without good error reporting, when an API raises errors, | ||
clients have difficulty understanding what they can do next, and cannot | ||
provide you, the API provider, with information that will allow you to | ||
debug on the server side. | ||
</p> | ||
<p> | ||
Error reporting needs to consist of two distinct elements: | ||
</p> | ||
<ul> | ||
<li> | ||
Return an HTTP error status appropriate to the error. Do <em>not</em> | ||
return a 200 response if an error occurred. | ||
</li> | ||
<li> | ||
Use a media-type and structure that is documented and parseable. | ||
</li> | ||
</ul> | ||
<p> | ||
For this latter point, while you <em>can</em> create your own error | ||
structure, I recommend using documented, accepted standards. This will | ||
make clients more re-usable, and make many of your decisions for you. | ||
</p> | ||
<p> | ||
Once you get more than a few error types, start documenting them, and | ||
pushing that documentation to specific, individual URL endpoints. Use | ||
those URLs to further enhance your error payloads, so that users can | ||
get a clear picture of causes and next steps. | ||
</p> | ||
<p> | ||
Which brings me to... | ||
</p> | ||
<h2>Next time</h2> | ||
<p> | ||
Nope, still haven't covered ZF2, but I'll start next time, when I cover | ||
the next topic: documenting your API. An undocumented API is a useless | ||
API, so it's good to start baking documentation in immediately. I'll | ||
survey some of the possibilities and how they can be implemented in | ||
ZF2 in the next installment, and then we can get our hands dirty with | ||
actual API development. | ||
</p> | ||
<h3>Updates</h3> | ||
<p> | ||
<em>Note: I'll update this post with links to the other posts in the series | ||
as I publish them.</em> | ||
</p> | ||
<ul> | ||
<li><a href="/blog/2013-02-11-restful-apis-with-zf2-part-1">Part 1</a></li> | ||
</ul> | ||
EOT; | ||
$entry->setExtended($extended); | ||
|
||
return $entry; |