Definition – Conventions – Hands-On



Definition – Conventions – Hands-On

0 0


rest_in_peace

Short speech about REST

On Github webplatformz / rest_in_peace

REST in peace

Inhalt

  • Einführung 2'
  • Basics 10'
  • Konventionen 20'
  • Hands-On
    • Design your API 10'
    • Review 5'
    • Pause 10'
    • REST@SAM 5'
    • Postman 5'
    • Implementation 20'
    • Review 15'
  • Deep Dive mit Aussicht 10'
  • QA Session 10'

Definition

"Representational state transfer (REST) is an architectural style consisting of a coordinated set of architectural constraints applied to components, connectors, and data elements, within a distributed hypermedia system."

Definition for humans

A concrete implementation of a REST Web service follows four basic design principles:

  • Use HTTP methods explicitly.
  • Be stateless.
  • Expose directory structure-like URIs.
  • Transfer XML, JavaScript Object Notation (JSON), or both.

Standard?

REST ist eigentlich ein Paradigma mit "Quasi Standard".

So ist ein Web-Dienst, der lediglich unveränderte Seiteninhalte nach dem Internetstandard HTTP anbietet, bereits REST-konform.

Warum REST?

  • State im Protokoll
  • Skalierbarkeit
  • Standard durch HTTP
  • Caching durch HTTP

Methoden

Methode Sicher Idempotent GET X X HEAD X X PUT X POST PATCH OPTIONS X X DELETE X

Basic Example

GET http://transport.opendata.ch/v1/locations

==>
{
    "stations": []
}

Live Example

URL:
GET
Examples 
 1   2   3 

Antwort:

Antwort

URL-Parameter

  • Einschränkung der Resultmenge sowie Inhalt
  • Auch bei POST, DELETE etc. erlaubt
  • API Definition als Referenz
  • UTF-8 und URL encoded
  • Paging startet bei "1" und nicht bei "0"

GET     /connections?from=oerlikon&to=wallisellen&limit=2

DELETE  /connections?from=oerlikon&to=wallisellen&limit=2

Weitere Beispiele

GET  https://api.github.com/repos/bmillo/recycle_calendar

GET  http://isvat.appspot.com/DE/197493877/

GET  api.openweathermap.org/data/2.5/weather?q=Zurich,ch

Conventions

  • Welche Methode?
  • Benennung der Ressourcen
  • Naming im JSON
  • Request Payload

Welche Methode?

POST GET PUT DELETE

!=

CRUD

GET & DELETE

Entspricht dem READ und DELETE von CRUD.

GET     http://transport.opendata.ch/v1/stations
DELETE  http://transport.opendata.ch/v1/stations/1234

POST & PUT

Beide Methoden können für CREATE und UPDATE verwendet werden.

POST http://transport.opendata.ch/v1/stations

{
    name : "Zürich Hauptbahnhof"
}

PUT http://transport.opendata.ch/v1/stations/8765

{
    name : "Zürich Hauptbahnhof"
}

WTF?

POST & PUT

Der Schlüssel liegt in der Idempotenz

"Send and send and send my friend, it makes no difference in the end."

- James Beninger

POST & PUT

POST ist nicht idempotent:

POST http://transport.opendata.ch/v1/stations

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4444

Was passiert, wenn ich diesen Request mehrmals ausführe?

POST & PUT

POST http://transport.opendata.ch/v1/stations

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4444

and again!

POST http://transport.opendata.ch/v1/stations

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4445

Wir haben soeben dieselbe Location zwei mal erstellt.

POST & PUT

PUT ist idempotent:

PUT http://transport.opendata.ch/v1/stations/4444

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4444

Was passiert, wenn ich diesen Request mehrmals ausführe?

POST & PUT

PUT http://transport.opendata.ch/v1/stations/4444

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4444

and again!

PUT http://transport.opendata.ch/v1/stations/4444

{
    name : "Zürich Hauptbahnhof"
}

==>
201 Created
Location: /stations/4444

Wir haben die erste Location einfach überschrieben.

POST & PUT

Bei POST lassen wir den Server entscheiden, unter welcher Location die Resource erstellt wird.

Bei PUT sagen wir dem Server, unter welcher Location die Resource erstellt/überschrieben werden soll.

PUT     http://transport.opendata.ch/v1/locations/4444
POST    http://transport.opendata.ch/v1/locations

Achtung: Natürlich liegt es an der Implementation des Servers, ob PUT & POST auch wirklich idempotent ist oder nicht!

POST

POST darf verwendet werden, wenn die Query Parameter eine gefährlich grosse Länge annehmen könnten. Zum Beispiel, wenn ich n Rufnummern einem Standort zuweisen möchte:


PUT    /numbers/1,2,3,4,5,6,7,...,756879895 

{
    "locationId" : 1337
}


POST    /numbers/locationAssignment

{
    "numberIds" : [ 1,2,3,... ],
    "locationId" : 1337
}

Benennung der Ressourcen

Verwende immer Nomen, um die Ressourcen zu benennen.

Verwende immer Plural, ausser es handelt sich wirklich um eine einzelne Ressource und nicht eine Collection.

Benennung der Ressourcen

Wir exposen mit einer REST API die Ressource =Nomen

und nicht die Operationen darauf =Verben

Die HTTP Methode sagt uns, was wir auf dieser Ressource tun.


POST    /locations                  // DO

POST    /locations/createLocation   // DON'T

DELETE  /locations/1234             // DO

DELETE  /deleteLocation/1234        // DON'T

JSON

// nope

{
    employee : {
        employeeId   : 111,
        employeeName : "John"
    }
}

skip the prefix!


{
    employee : {
        id      : 111,
        name    : "John"
    }
}

Request Payloads

GET und DELETE haben keinen Payload.

Einschränkungen für die Antwort (z.B. search-parameters) gehören in die URL-Parameter.

GET /locations?canton=Aargau&county=Baden

                        

Hands-On

Design your API

REST @ SAM

JAX-RS with Jersey

Automatisches Serialisieren und Deserialisieren

  • JSON mit Jackson
  • XML mit JAXB

Der Accept-Header gibt an, wie die Antwort aussehen soll.

Accept: application/json
Accept: application/xml

                        

Serializing and Deserializing

Beispiel

                        
GET     /employees

Accept: application/json
 
@GET
public Employee[] getEmployees() { ... }

==>
[{ name : "John Wayne" }, ..., ]

                        

Base Path

@Path("/regservice/employees")
public class EmployeeRestService { ... }

                        

Path Params

Client:

GET /employees/1234

                        

Server:

@GET
@Path("/{employeeId}")
public Employee getEmployee(@PathParam("employeeId")
        String employeeId)

==>
{ id : "1234", name : "John Wayne" }

                        

Query Params

Client:

GET /employees?name=John

                        

Server:

@GET
public Employee getEmployees(@QueryParam("name") 
        String name)

==>
{ id : "1234", name : "John Wayne" }

                        

Request Payload

Client:

POST /employees
{
    name : "Chuck Norris"
}

Server:

@POST
public void createEmployee(Employee employee)

==>
204 No Content

Postman

Live Demo

Hands On!

Deep Dive

  • Expand
  • Patch
  • Head
  • Options
  • Versioning
  • Error Handling
  • HATEOAS

Expand

GET /employees/123

==>
{
    id: 123,
    name: "John",
    surname : "Doe",
    departmentId : 1234
}
                    

Expand

GET /employees/123?expand=department

==>
{
    id: 123,
    name: "John",
    surname : "Doe",
    department : {
        id: 1234,
        name: "MYA",
        street: "Hardturmstrasse 4",
        zipCode: 8005,
        city: "Zürich"
    }
}
                    

PATCH

  • Partielles Update einer Ressource
  • PATCH muss atomar arbeiten
  • Nicht idempotent

PATCH wird zurzeit noch nicht flächendeckend unterstützt

PATCH

Initialer Request

GET /employees/123

==>
{
    id: 123,
    name: "John",
    prename : "Doe",
    departmentId : 1234,
    salary : 5000
}
                     

PATCH

Nach RFC 6902

PATCH /employees/123
Content-Type: application/json-patch+json

[
    { op: "replace", path: "salary", value: 6000 },
    { op: "replace", path: "name", value: "Joe" }
]

==>
200 OK
                    

PATCH

GET /employees/123

==>
{
    id: 123,
    name: "Joe",
    prename : "Doe",
    departmentId : 1234,
    salary : 6000
}
  

HEAD

Die HEAD Methode entpspricht einem GET-Aufruf, jedoch wird dabei der Body der Response weggelassen.

Die Header der HEAD Response müssen identisch zu dem Header der GET Response sein.

HEAD /employees/123

==>
200 OK
Content-Length: 2885
  

OPTIONS

Die OPTIONS Methode erlaubt dem Client die Optionen und Anforderungen des Servers bzw. einer Ressource zu erhalten.

OPTIONS /employees
==>
200 OK  
Allow: HEAD,GET,PUT,DELETE,OPTIONS
  

ABER: OPTIONS ist nur bei wenigen Rest-API's implementiert.

Versionierung

Versionierung

Als Teil vor der Ressource

GET http://transport.opendata.ch/v1/locations
                        

Als URL-Parameter

GET http://transport.opendata.ch/locations?v=1
                        

Versionierung

Versionierung durch Accept Header

GET http://transport.opendata.ch/locations
Accept: application/vnd.opendata.v1+json
                        

Versionierung durch Custom Header

GET http://transport.opendata.ch/locations
api-version: 1
                        

Error Handling

Für die Fehlerkommunikation zwischen Server und Client sind die HTTP Statuscodes vorgesehen.

  • 4xx Status Codes für Fehler des Clients
  • 5xx Status Codes für Fehler des Servers

400 Bad Request

Die Anfrage ist fehlerhaft aufgebaut

POST /employees

{
    name : "John Doe";
    salary : 5000
}

==>
400 Bad Request

401 Unauthorized

Der Client muss sich authentisieren

In der Response schickt der Server ein WWW-Authenticate Header, um die Authentisierung zu veranlassen.

GET /secrets

==>
401 Unauthorized
WWW-Authenticate: Basic realm="My Server"

404 Not Found

Die Angefragte Ressource kann nicht gefunden werden

GET /employees/999999999

==>
404 Not Found

405 Method Not Allowed

Das Bearbeiten der Anfrage wird vom Server für diese Ressource nicht bereitgestellt. z.B. PUT auf einer Readonly Ressource

PUT /readOnlyResource/1

{
    title : "RESTful Web Services Cookbook"
    pages : 314
}

==>
405 Method Not Allowed

422 Unprocessable Entity

Die geschickten Daten sind syntaktisch korrekt, werden jedoch wegen semantischer Fehler abgelehnt

POST /employees

{
    employeesname : "John Doe",
    employeessalary : 5000
}
==>
422 Unprocessable Entity

418 I'm a teapot

Ein Teekrug erhält eine Anweisung zum Kaffee brühen.

Aprilscherz der IETF aus dem Jahre 1998

500 Internal Server Error

Sammel Statuscode welcher bei unerwarteten Fehlern geschmissen wird

POST /employees

{
    name : "John Doe"
    salary : 5000
}
==>
500 Internal Server Error

HATEOAS

  • Hypermedia as the Engine of Application State
  • Fixer Einstiegspunkt in eine API
  • Hypertext zum Navigieren der API
  • Weitere Follow-Up Links in der Response
  • Client braucht kein Wissen über die Architektur der API

HATEOAS

GET /employees/123
==>
{
    id: 123,
    name: "John",
    departmentId : 1234,
    salary : 6000
    links : [{
        rel: "self",
        href: "/employees/123"
    },
    {
        rel: "boss",
        href: "/employees/632"
    }]
}

QA & Feedback