Apigility

Agile APIs

Matthew Weier O'Phinney / @mwop
apigility.org / @Apigility

You need an API

Many teams, many languages

Many languages

Communicate with partners and clients

B2B

Mobile applications

Mobile

Obstacles
along the way

(APIs are Hard)

The devil is in the details

Devil in the details

1: Format: Old school RPC?

RPC

Or Embrace the new hawtness: REST?

REST

2: Error reporting

  • HTTP Status?
  • How are details reported?

3: HTTP method negotiation

HTTP negotiation

4: Content negotiation

Content negotiation

5: Validation

Validation failed!

6: Authentication

Authenticated!

7: Authorization

Unauthorized!

8: Versioning

Version all the things!

9: Discovery and documentation

Document all the things!

Stumbling block: Hypermedia what?

links

In short:
APIs are hard!

(You may get lost
along the way!)

Get a Map!

Apigility

Opinionated

or,
We choose
so you don't have to

1: Format: JSON

Specifically, Hypermedia Application Language (HAL)

application/hal+json


{
    "_links": {
        "self": { "href": "/session/1" }
    }
    "session_id": 1,
    "title": "Apigility: Agile APIs",
    "_embedded": {
        "speaker": {
            "_links": { "self": { "href": "/speaker/1" } }
            "speaker_id": 1,
            "name": "Matthew Weier O'Phinney"
        }
    }
}
            

2: Error Reporting

Specifically Problem Details for HTTP APIs (API Problem)

application/problem+json


{
    "type": "/api/problems/forbidden",
    "title": "Forbidden",
    "detail": "Your API key is missing or invalid.",
    "status": 403,
    "authenticationUrl": "/api/oauth"
}
            

3: HTTP Method Negotiation


POST /session HTTP/1.1
Content-Type: application/xml

Foo
            

405 Method Not Allowed
Allow: GET
            

OPTIONS


OPTIONS /session HTTP/1.1
Content-Type: application/xml
            

200 OK
Allow: GET
            

4: Content Negotiation: Accept


GET /session HTTP/1.1
Accept: application/xml
            

406 Not acceptable
Content-Type: application/problem+json

{
    "type": "/api/problems/content",
    "title": "Not acceptable",
    "detail": "This API can deliver application/vnd.zend-con.v1+json, application/hal+json, or application/json only.",
    "status": 406
}
            

4: Content Negotiation: Content-Type


POST /session HTTP/1.1
Content-Type: application/xml

1
            

415 Unsupported Media Type
Content-Type: application/problem+json

{
    "type": "/api/problems/content",
    "title": "Unsupported Media Type",
    "detail": "This API can accept application/vnd.zend-con.v1+json, application/hal+json, or application/json only.",
    "status": 415
}
            

5: Validation


PATCH /session/1 HTTP/1.1
Content-Type: application/json

{ "title": {"foo":"bar"} }
            

422 Unprocessable Entity
Content-Type: application/problem+json

{
    "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
    "title": "Unprocessable Entity",
    "detail": "Failed validation",
    "status": 422,
    "validation_messages": {
        "title": "Invalid title; must be a non-empty string"
    }
}
            

6: Authentication

  • HTTP Basic and Digest (for internal APIs)
  • OAuth2 (for public APIs)
  • Event-driven, to accommodate anything else
  • Return a problem response early if invalid credentials are provided

Authentication


GET /session/1 HTTP/1.1
Authorization: Basic foobar
Accept: application/json
            

401 Unauthorized
Content-Type: application/problem+json

{
    "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
    "title": "Unauthorized",
    "detail": "Unauthorized",
    "status": 401
}
            

7: Authorization

  • Public by default
  • Restrict by service and/or specific HTTP methods
  • Return a problem response early if the identified user does not have authorization

Authorization


GET /session/1 HTTP/1.1
Accept: application/json
            

403 Forbidden
Content-Type: application/problem+json

{
    "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
    "title": "Forbidden",
    "detail": "Forbidden",
    "status": 403
}
            

8: Versioning by Default

  • URL-based* versioning: /v1/session, /v2/session
  • Mediatype versioning:
    Accept: application/vnd.status.v2+json
  • Code is versioned by namespace.
* For the lazy

9: Document all the things!

  • Why services exist
  • Why methods are exposed
  • What request and response payloads should contain

Export documentation

  • Export captured documentation
  • Export other captured settings, such as Content Negotiation, allowed HTTP methods, authorization requirements, etc.
  • Export to the format of your choice

Bonus: Hyperlinking: Pagination

  • Invoked automatically by returning Zend\Paginator\Paginator.

{
    _links: {
        self: { href: "/api/session?page=3" },
        first: { href: "/api/session" },
        last: { href: "/api/session?page=14" },
        prev: { href: "/api/session?page=2" },
        next: { href: "/api/session?page=4" }
    }
}
            

and to make things easier …

Use what you want

No framework to learn

Write your own code, however you want …

… but ZF2 is under the hood.

Made of many ZF2 modules!

  • zf-api-problem
  • zf-apigility
  • zf-apigility-admin
  • zf-configuration
  • zf-content-negotiation
  • zf-content-validation
  • zf-development-mode
  • zf-hal
  • zf-mvc-auth
  • zf-oauth2
  • zf-rest
  • zf-rpc
  • zf-versioning
  • and more!

Extend via...

  • event listeners
  • services

To sum up...

  • APIs provide many details to lose yourself in
  • Get a map! Try out tools like Apigility!

Get involved!

Thank You!

Matthew Weier O'Phinney
@mwop
http://apigility.org/