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

96
Lars Röwekamp | CIO New Technologies | open knowledge GmbH #WISSENTEILEN @_openKnowledge @mobileLarson Web APIs jenseits von REST & Request/Response

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

Page 1: 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

Page 2: Web-API-Design jenseits von REST und Request/Response

ÜBER OPEN KNOWLEDGEBranchenneutrale Softwareentwicklung und IT-Beratung

#WISSENTEILEN

Page 3: Web-API-Design jenseits von REST und Request/Response

Ü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

Page 4: Web-API-Design jenseits von REST und Request/Response

Living in a RESTful World

Page 5: Web-API-Design jenseits von REST und Request/Response
Page 6: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

lang laufende Anfragenserverseitige Ereignissekomplexe Abfragen

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

*REST @work

Page 7: Web-API-Design jenseits von REST und Request/Response

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

Page 8: Web-API-Design jenseits von REST und Request/Response

Mein Server und ich ...

#WISSENTEILEN

„WT#!?“ *

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

Page 9: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

lang laufendeAnfragen

Page 10: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Request-Response Modell

Page 11: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Request-Response Modell

Page 12: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Request-Response Modell

Page 13: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

I had a Dream

Page 14: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Reactive-Flow Modell

Page 15: Web-API-Design jenseits von REST und Request/Response

#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);

}

Page 16: Web-API-Design jenseits von REST und Request/Response

#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));}));

Page 17: Web-API-Design jenseits von REST und Request/Response

#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));

}));

Page 18: Web-API-Design jenseits von REST und Request/Response

#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));

}));

Page 19: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

„Are u kidding?“?

Page 20: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

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

Page 21: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Fire & Forget*

Page 22: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Polling Modell

Page 23: Web-API-Design jenseits von REST und Request/Response

#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!)

Page 24: Web-API-Design jenseits von REST und Request/Response

#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

Page 25: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Long-Polling

Page 26: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Long Polling Modell

Page 27: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

@POSTpublic void createCustomer(Customer customer,

@Suspended AsyncResponse asyncResponse) {

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

}

asynchronous JAX-RS 2.0

Page 28: Web-API-Design jenseits von REST und Request/Response

#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

Page 29: Web-API-Design jenseits von REST und Request/Response

#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

Page 30: Web-API-Design jenseits von REST und Request/Response

#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

Page 31: Web-API-Design jenseits von REST und Request/Response

#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

Page 32: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Piggy-Back

Page 33: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Piggy Bag Modell

Page 34: Web-API-Design jenseits von REST und Request/Response

#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

Page 35: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Piggy Back mit JAX-RS 2.0

(*nur via eigenem MessageBodyWriter)

Page 36: Web-API-Design jenseits von REST und Request/Response

#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

Page 37: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

serverseitigeEreignisse

Page 38: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

serverseitigeEreignisse

Page 39: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 40: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 41: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 42: Web-API-Design jenseits von REST und Request/Response

#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

Page 43: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Server-Sent Events

Page 44: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Server-Sent Event Modell

Page 45: Web-API-Design jenseits von REST und Request/Response

#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

Page 46: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

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

event: event-nameid: unique iddata: custom data

Server-Sent Event Protokoll

Page 47: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

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

...

});

Server-Sent Event JS Client

Page 48: Web-API-Design jenseits von REST und Request/Response

#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)

Page 49: Web-API-Design jenseits von REST und Request/Response

#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)

Page 50: Web-API-Design jenseits von REST und Request/Response

#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)

Page 51: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

WebSockets

Page 52: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Websocket Modell

Page 53: Web-API-Design jenseits von REST und Request/Response

#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

Page 54: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 55: Web-API-Design jenseits von REST und Request/Response

#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

Page 56: Web-API-Design jenseits von REST und Request/Response

#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+

Page 57: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

komplexeAbfragen

Page 58: Web-API-Design jenseits von REST und Request/Response

#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?

Page 59: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

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

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

Komplexe Abfragen mit REST

btw: Klammerung?

Page 60: Web-API-Design jenseits von REST und Request/Response

#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

Page 61: Web-API-Design jenseits von REST und Request/Response

#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

Page 62: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Komplexe Abfragen mit REST

Page 63: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Wer genau ist eigentlich dieser „Luke“?

GET /people/1

Komplexe Abfragen mit REST

Page 64: Web-API-Design jenseits von REST und Request/Response

#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

Page 65: Web-API-Design jenseits von REST und Request/Response

#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

Page 66: Web-API-Design jenseits von REST und Request/Response

#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

Page 67: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Umdenken, du musst!

Page 68: Web-API-Design jenseits von REST und Request/Response

#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)

Page 69: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Motivation:

Komplexe Abfragen: „Umdenken du musst!“

(Facebook, 2012)

Page 70: Web-API-Design jenseits von REST und Request/Response

#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!“

Page 71: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 72: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 73: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 74: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Was zeichnet GraphQL aus?

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

GraphQL Charakteristika

Page 75: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Fragen, was du willst,du musst!

Page 76: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 77: Web-API-Design jenseits von REST und Request/Response

#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?

Page 78: Web-API-Design jenseits von REST und Request/Response

#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?

Page 79: Web-API-Design jenseits von REST und Request/Response

#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?

Page 80: Web-API-Design jenseits von REST und Request/Response

#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?

Page 81: Web-API-Design jenseits von REST und Request/Response

#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?

Page 82: Web-API-Design jenseits von REST und Request/Response

#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?

Page 83: Web-API-Design jenseits von REST und Request/Response

#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?

Page 84: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 85: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 86: Web-API-Design jenseits von REST und Request/Response

#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)

Page 87: Web-API-Design jenseits von REST und Request/Response

#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

Page 88: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

GraphQLvs.REST Aggregation?

Aggregation?

Aggregation?

Page 89: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

GraphQLvs.REST

Aggregation!

Page 90: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Der GraphQL Server

• Query Parser• Data Fetcher• Data Aggregator

• Chaching von Result Graphs• Aktuallisierung von Result Graphs

GraphQL und das Backend

Page 91: Web-API-Design jenseits von REST und Request/Response

#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

Page 92: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Cool, ist das wirklich alles so einfach?

• relative neue Technologie (im Fluss)

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

GraphQL und Ich

Page 93: Web-API-Design jenseits von REST und Request/Response

#WISSENTEILEN

Page 94: Web-API-Design jenseits von REST und Request/Response

? ? ?FRAGEN

#WISSENTEILEN

Page 95: Web-API-Design jenseits von REST und Request/Response

Kontakt

LARS RÖWEKAMPCIO NEW TECHNOLOGIES

[email protected]+49 (0)441 4082 – 101

@mobileLarson@_openknowledge

OFFENKUNDIGGUT

#WISSENTEILEN

Page 96: Web-API-Design jenseits von REST und Request/Response

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