Web-API-Design jenseits von REST und Request/Response

Post on 22-Jan-2018

189 views 2 download

Transcript of Web-API-Design jenseits von REST und Request/Response

Lars Röwekamp | CIO New Technologies | open knowledge GmbH

#WISSENTEILEN@_openKnowledge

@mobileLarson

Web APIs jenseits von REST & Request/Response

ÜBER OPEN KNOWLEDGEBranchenneutrale Softwareentwicklung und IT-Beratung

#WISSENTEILEN

ÜBER MICH

Wer bin ich - und wen ja, wie viele?

• CIO New Technologies • Enterprise & Mobile • Autor, Speaker, Coach & Mentor

• Snowboard & MTB Enthusiast• Mehrfacher Vater, einfacher Ehemann

Lars Röwekamp (a.k.a. @mobileLarson)

#WISSENTEILEN

LR

Living in a RESTful World

#WISSENTEILEN

lang laufende Anfragenserverseitige Ereignissekomplexe Abfragen

„Sorry, ich bin leider nicht so gut in ...“*

*REST @work

„But it‘s notREST if youdo (not) ...“

Mein Server und ich ...

#WISSENTEILEN

„WT#!?“ *

(*a.k.a. „i don‘t care as long as it works for me.“)

#WISSENTEILEN

lang laufendeAnfragen

#WISSENTEILEN

Request-Response Modell

#WISSENTEILEN

Request-Response Modell

#WISSENTEILEN

Request-Response Modell

#WISSENTEILEN

I had a Dream

#WISSENTEILEN

Reactive-Flow Modell

#WISSENTEILEN

public interface ReactiveMongoDBOperations {

<T> Mono<T> find (Query query, Class<T> entityClass);

<T> Flux<T> findAll(Query query, Class<T> entityClass);

<T> Mono<T> insert(T objectToSave, String collectionName);

<T> Mono<T> insert(Mono<? extends T> objectToSave);

}

#WISSENTEILEN

RouterFunction<?> route = route(GET("/persons/{id}"), request -> {Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id")).map(Integer::valueOf).then(repository::find);

return Response.ok().body(fromPublisher(person, Person.class));}));

#WISSENTEILEN

RouterFunction<?> route = route(GET("/persons/{id}"), request -> {Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id")).map(Integer::valueOf).then(repository::find);

return Response.ok().body(fromPublisher(person, Person.class));}).and(route(GET("/persons"), request -> {Flux<Person> people = repository.findAll();return Response.ok().body(fromPublisher(people, Person.class));

}));

#WISSENTEILEN

RouterFunction<?> route = route(GET("/persons/{id}"), request -> {Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id")).map(Integer::valueOf).then(repository::find);

return Response.ok().body(fromPublisher(person, Person.class));}).and(route(GET("/persons"), request -> {Flux<Person> people = repository.findAll();return Response.ok().body(fromPublisher(people, Person.class));

}).and(route(POST("/persons"), request -> {Mono<Person> person = request.body(toMono(Person.class));return Response.ok().build(repository.insert(person));

}));

#WISSENTEILEN

„Are u kidding?“?

#WISSENTEILEN

HTTP /1.0 202 AcceptedLocation: http://....

#WISSENTEILEN

Fire & Forget*

#WISSENTEILEN

Polling Modell

#WISSENTEILEN

@PUT@Consumes(MediaType.APPLICATION_JSON)public Response processOrder(Order order) throws ... {

UUID orderId = orderService.process(order);return Response

.accepted(). // HTTP accepted 202

.location(new URI(„orders/“ + orderId + „/status“))

.build();}

Fire & Forget* mit JAX-RS

(* eigentlich Polling!)

#WISSENTEILEN

@GET @Path("/{orderNo}") @Produces(MediaType.APPLICATION_JSON) public Response getOrder(@PathParam("orderNo") UUID orderNo) {

Order order = orderService.getOrder(orderNo);if (order.isEmpty()) {return Response.noContent().build();

} else {return Response.ok().entity(order).build();

} }

Polling mit JAX-RS

#WISSENTEILEN

Long-Polling

#WISSENTEILEN

Long Polling Modell

#WISSENTEILEN

@POSTpublic void createCustomer(Customer customer,

@Suspended AsyncResponse asyncResponse) {

Response response = createCustomer(customer);asyncResponse.resume(response);

}

asynchronous JAX-RS 2.0

#WISSENTEILEN

@POSTpublic void createCustomer(Customer customer,

@Suspended AsyncResponse asyncResponse) {

asyncResponse.setTimeout(10, TimeUnit.SECONDS);asyncResponse.setTimeoutHandler(ar -> ar.resume(

Response.status(Status.SERVICE_UNAVAILABLE).entity("Operation timed out").build()));

Response response = createCustomer(customer);asyncResponse.resume(response);

}

asynchronous JAX-RS 2.0

#WISSENTEILEN

@POSTpublic void createCustomer(final Customer customer,

@Suspended final AsyncResponse asyncResponse) {

asyncResponse.setTimeout(10, TimeUnit.SECONDS);asyncResponse.setTimeoutHandler(...);

executor.execute(() -> {Response response = createCustomer(customer);asyncResponse.resume(response);

});}

asynchronous JAX-RS 2.0

#WISSENTEILEN

@POSTpublic void createCustomer(final Customer customer,

@Suspended final AsyncResponse asyncResponse) {

asyncResponse.register(throwable -> {if (throwable != null) {LOG.error("Operation failed.", t);return;

}LOG.info("Operation successful.");

});...

}

asynchronous JAX-RS 2.0

#WISSENTEILEN

Client client = ClientBuilder.newBuilder().build().target("https://...").request().async().get(new InvocationCallback<Result>() {public void completed(Result result) {...

}public void failed(Throwable error) {...

}});

asynchronous JAX-RS 2.0

#WISSENTEILEN

Piggy-Back

#WISSENTEILEN

Piggy Bag Modell

#WISSENTEILEN

HTTP/1.0 200 OKServer: ...MIME-Version: 1.0Content-Type: multipart/x-mixed-replace;boundary=EOM

--EOMContent-Type application/json{...}--EOMContent-Type application/octet-stream{...}--EOM

Piggy Back mit REST

#WISSENTEILEN

Piggy Back mit JAX-RS 2.0

(*nur via eigenem MessageBodyWriter)

#WISSENTEILEN

public class BooksResource {

@Produces("multipart/mixed")public Map<String, Object> getBooks() {

Map<String, Object> map= new LinkedHashMap<String, Object>();

map.put("text/xml", new JaxbBook());map.put("application/json", new JSONBook());map.put("application/octet-stream", imageInputStream);return map;

}...

}

Piggy Back mit APACHE CXF

#WISSENTEILEN

serverseitigeEreignisse

#WISSENTEILEN

serverseitigeEreignisse

#WISSENTEILEN

#WISSENTEILEN

#WISSENTEILEN

#WISSENTEILEN

Was steckt dahinter?

• Paradigmenwechsel in der Kommunikation• Abkehr vom klassischen Request/Response

• Client meldet sich am Server an• Server kann Client jederzeit “Daten“ schicken• Session kann von beiden Seiten beendet werden

Serverseitige Ereignisse

#WISSENTEILEN

Server-Sent Events

#WISSENTEILEN

Server-Sent Event Modell

#WISSENTEILEN

Was sind Server-Sent Events?

• senden von Daten vom Server zum Client • nutzt normales HTTP Protokoll• built-in Support für Reconnection• via JS, falls kein Browser Support vorhanden

• lediglich UTF-8 Daten (keine Binärdaten)• Limitierung der offenen Connections

Server-Sent Event 1x1

#WISSENTEILEN

HTTP/1.0 202 AcceptedContent-Type: text/event-stream

event: event-nameid: unique iddata: custom data

Server-Sent Event Protokoll

#WISSENTEILEN

var eventSource = new EventSource(url);eventSource.addEventListener("event-name", function(e) {

...

});

Server-Sent Event JS Client

#WISSENTEILEN

@WebServlet(urlPatterns = "/status", asyncSupported = true)public class SseServlet extends HttpServlet {

...protected void doGet(HttpServletRequest req, HttpServletResponse res)

throws IOException {AsyncContext context = req.startAsync();... // put context to queue and finish doGet (connect)

}

protected void asyncProcessing() {context.getResponse().getWriter();

}}

Server-Sent Event mit AsyncServlet*

(*SSE „hand-made“ Edition)

#WISSENTEILEN

// register process...private SseBroadcaster channel;

@GET@Path("subscribe")@Produces(MediaType.SERVER_SENT_EVENTS) // "text/event-stream"public void subscribe(@Context Sse sse, @Context SseEventSink eventSink) {String uuid = UUID.randomUUID().toString();OutboundSseEvent event = sse.newEventBuilder().id(uuid).data(“Subscription accepted. ID – “+ uuid).build();

eventSink.send(event).channel.register(eventSink);

}

Server-Sent Event mit Java EE 8*

(*SSE „Standard“ Edition)

#WISSENTEILEN

// broadcast to registerd client...private SseBroadcaster channel;

public void update() {

OutboundSseEvent event = sse.newEventBuilder().name(...).data(myJsonObject).mediaType(APPLICATION_JSON_TYPE).build();

channel.broadcast(event);

}

Server-Sent Event mit Java EE 8*

(*SSE „Standard“ Edition)

#WISSENTEILEN

WebSockets

#WISSENTEILEN

Websocket Modell

#WISSENTEILEN

Was ist WebSockets?

• Full-Duplex Real-Time Kommunikation• extrem leichtgewichtig dank HTTP Upgrade • breiter Browser-Support

• Client eröffnet die Session• Client und/oder Server sendet Nachricht(en)• Client und/oder Server beendet Session

WebSockets 1x1

#WISSENTEILEN

#WISSENTEILEN

var socket = new WebSocket("ws://host:8080/path/");

socket.onopen = function(event) {...}socket.onmessage = function(event) {...}socket.onclose = function(event) {...}socket.onerror = function(event) {...}socket.send("Message-Inhalt");

WebSocket JS Client

#WISSENTEILEN

@ServerEndpoint("/socket")public class MySocketEndpoint {

@OnOpenpublic void open(Session newSession) {try {newSession.getBasicRemote().sendText("hello, thx for connecting");

} catch (IOException | EncodeException e) {...}}

@OnMessagepublic void receive(String message) { ... }

}

WebSocket mit Java EE 7+

#WISSENTEILEN

komplexeAbfragen

#WISSENTEILEN

// Bestellungen: Kaffee mit Milch zu 2 €

GET /orders?type=coffee&ingredient=milk&price=2

Komplexe Abfragen mit REST

btw: UND oder ODER oder UND/ODER?

#WISSENTEILEN

// Bestellungen: Kaffee mit Milch oder günstiger als 2 €

GET /orders?type=coffee;ingredient=milk,price<=2

Komplexe Abfragen mit REST

btw: Klammerung?

#WISSENTEILEN

// Bestellungen: Kaffee mit Milch oder günstiger als 2 €

// RQL query ... (must possibly be encoded!)GET /orders?query=

and(eq(type,coffee),or(eq(ingredients,MILK),lt(price,2))

// RQL alternative query – FIQL (URI friendly) - ... GET /orders?query=

type==coffee;(ingredients==MILK,price=lt=2)

Komplexe Abfragen mit RQL*

*https://github.com/persvr/rql

#WISSENTEILEN

Resource Query Language

• Object-Style Query Language• FIQL Superset (erweiterbar)

• Spezifikation & JS Parser (Client & Server)• JS Array, SQL, MongoDB, Elastic Search• Java Parser + JPA Criteria Builder

Komplexe Abfragen mit RQL*

*https://github.com/persvr/rql

#WISSENTEILEN

Komplexe Abfragen mit REST

#WISSENTEILEN

Wer genau ist eigentlich dieser „Luke“?

GET /people/1

Komplexe Abfragen mit REST

#WISSENTEILEN

Wer genau ist eigentlich dieser „Luke“ und in welchen Filmen hat er mitgespielt?

GET /people/1/films (falls es der Endpoint anbietet)

GET /films/1GET /films/2GET /films/3

Komplexe Abfragen mit REST

#WISSENTEILEN

Wer genau ist eigentlich dieser „Luke“ und in welchen Filmen hat er mitgespielt und auf welchen Raumschiffen ist er darin gefahren?

GET /people/1/starships (falls es der Endpoint anbietet)

GET /starships/12GET /starships/22

Komplexe Abfragen mit REST

#WISSENTEILEN

Wie alt ist Luke und welche Haarfarbe hat er? Mit welchem Spruch wurden die Filme eröffnet, in denen er mitgespielt hat? Und wie teuer waren überhaupt die Raumschiffe auf denen er gefahren ist?

GET /?F#CKING/detail (n+1 problem)GET /?F#CKING/all (job problem)

Komplexe Abfragen mit REST

#WISSENTEILEN

Umdenken, du musst!

#WISSENTEILEN

Motivation:

„ We see a conflict between the desire to load all information in a single round trip while keeping REST resources well isolated.”

Komplexe Abfragen: „Umdenken du musst!“

(Facebook, 2012)

#WISSENTEILEN

Motivation:

Komplexe Abfragen: „Umdenken du musst!“

(Facebook, 2012)

#WISSENTEILEN

GraphQL Idee:

• Beliebige Abfragen via Objekt-Graph auf dem ich navigieren kann.

• Lieferung des Abfrage-Results in einem einzigen Round-Trip.

Komplexe Abfragen: „Umdenken du musst!“

#WISSENTEILEN

#WISSENTEILEN

#WISSENTEILEN

#WISSENTEILEN

Was zeichnet GraphQL aus?

• hierarchicial• product centric• strongly typed & instrospective• client specific queries• application-layer protokol

GraphQL Charakteristika

#WISSENTEILEN

Fragen, was du willst,du musst!

#WISSENTEILEN

#WISSENTEILEN

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

{person(name:„Luke“) {

haircolor, age

films {name,openiningCrawl

}

starships {name, price

}}

}

Komplexe Abfragen mit GraphQLWie alt ist Luke und welche Haarfarbe hat er?

Mit welchem “Spruch“ wurden die Filme eröffnet in denen er mitgespielt hat?

Und wie teuer waren überhaupt die Schiffe auf denen er gefahren ist?

#WISSENTEILEN

#WISSENTEILEN

#WISSENTEILEN

Woher weiß das Backend was ich will?

• API Schema als gemeinsame Basis• Query als Anfrage vom Client

• Step 1: parse (Query into an abstract syntax tree)*• Step 2: validate (against Schema)**• Step 3: execute (Resolve Functions)

GraphQL und das Backend

(*syntaktisch korrekt , **semantisch korrekt)

#WISSENTEILEN

// A very simple GraphQL schema example

type Character {name: String! // non nullableappearsIn: [Episode]! // array of ..., non nullable

}

type Starship {id: ID! // unique ID name: String!length(unit: LengthUnit = METER): Float // parameter

}

GraphQL und das Backend

#WISSENTEILEN

GraphQLvs.REST Aggregation?

Aggregation?

Aggregation?

#WISSENTEILEN

GraphQLvs.REST

Aggregation!

#WISSENTEILEN

Der GraphQL Server

• Query Parser• Data Fetcher• Data Aggregator

• Chaching von Result Graphs• Aktuallisierung von Result Graphs

GraphQL und das Backend

#WISSENTEILEN

Muss ich das alles selber bauen?

• Nein! Es gibt ... • Clients, Server, DB, Libraries, Tools ...

für Java, JavaScript, Python, Ruby, PHP, C/C++, GO, Scala, .Net, SQL, ...

> http://graphql.org> https://github.com/chentsulin/awesome-graphql

GraphQL und das Backend

#WISSENTEILEN

Cool, ist das wirklich alles so einfach?

• relative neue Technologie (im Fluss)

• Chaching?• Dupletten?• Information Hiding?• Versionierung? (vs. deprecated)

GraphQL und Ich

#WISSENTEILEN

? ? ?FRAGEN

#WISSENTEILEN

Kontakt

LARS RÖWEKAMPCIO NEW TECHNOLOGIES

lars.roewekamp@openknowledge.de+49 (0)441 4082 – 101

@mobileLarson@_openknowledge

OFFENKUNDIGGUT

#WISSENTEILEN

Bildnachweise

#01: © kenishirotie – shutterstock.com#13: © RichVintage – istockphoto.com#18: © pathdoc – shutterstock.com#67: © jpgfactory – istockphoto.com

All other pictures inside this presentation orginate frompixabay.com.

#WISSENTEILEN