4Developers 2015: Dlaczego 99% firm, które tworzą API RESTowe kłamie? - Bartek Andrzejczak,...
Transcript of 4Developers 2015: Dlaczego 99% firm, które tworzą API RESTowe kłamie? - Bartek Andrzejczak,...
DLACZEGO 99% FIRM, KTÓRE TWORZĄAPI RESTOWE KŁAMIE?
/ Bartek Andrzejczak @baandrzejczak
O MNIEdev @ twitter github blog @
AGENDAGeneza kłamstwaCo to jest HATEOAS?Jak ważny jest HATEOAS?Czy HATEOAS jest mi potrzebny?HATEOAS od strony serweraHATEOAS od strony klienta
MOŻE POWTÓRZYMY ANGIELSKI?
GOOGLE0:01
AMBROSE LITTLE0:20
CZYM WŁAŚCIWIE JEST REST?This chapter introduces and elaborates the
Representational State Transfer (REST)architectural style for distributed hypermedia
systems (...)
(...) a style is a named set of constraints onarchitectural elements that induces the set of
properties desired of the architecture.
"Architectural Styles and the Design ofNetwork-based Software Architectures"
Roy T. Fielding
OGRANICZENIA NARZUCANE PRZEZ RESTPodział klient-serwerBezstanowość serweraCacheJednolity interfejs
Identyfikacja zasobówManipulacja zasobami przez reprezentacjeSamoopisujące się wiadomościHATEOAS
Podział na warstwyCode-on-demand (Opcjonalne)
HYPERMEDIA AS THE ENGINE OFAPPLICATION STATE
Role, artefakty, zdarzenia oraz reguły Scrumasą niezmienne i choć możliwe jest
wykorzystanie tylko wybranych jegoelementów, wynikiem takiego postępowania
nie będzie Scrum. Scrum istnieje tylko wswojej pełnej postaci i sprawdza się doskonale
w roli ramy dla innych technik, metodyk czypraktyk.
Scrum Guide
JAK WAŻNY JEST HATEOAS?In order to obtain a uniform interface,
multiple architectural constraints are neededto guide the behavior of components. REST is
defined by four interface constraints:identification of resources; manipulation of
resources through representations; self-descriptive messages; and, hypermedia as theengine of application state. These constraints
will be discussed in Section 5.2.
"Architectural Styles and the Design ofNetwork-based Software Architectures"
Roy T. Fielding
The name “Representational State Transfer” is intended toevoke an image of how a well-designed Web application
behaves:a network of web pages (a virtual state-machine)where the user progresses through the application byselecting links (state transitions)resulting in the next page (representing the next state of theapplication) being transferred to the user and rendered fortheir use
CO DAJE NAM HATEOAS?
KIEDY HATEOAS JEST STRATĄ CZASU?Brak wyraźnego flow aplikacjiCRUDMałe API
ZALETYSterowanie przepływem danych w aplikacjiSterowanie dostępnymi podzasobamiLuźniejsze związanie serwera i klientaDodatkowa dokumentacja API (jednak niewystarczająca)
WADYWięcej pracyWięcej transferuBrak ekspertów w temacieTworzenie API dla nieistniejącego klienta
DLA JAKIEGO KLIENTA NADAJE SIĘ HATEOAS?
FORMAT LINKÓW
HTML<a href="http://swapi.co/api/films/1/">A New Hope</a>
JSON API "name": "Luke Skywalker", "height": "1.72 m", "mass": "77 Kg", "hair_color": "Blond", "links": "species": "self": "http://swapi.co/api/species/1/", "all": "http://swapi.co/api/species/"
PAYPAL API "name": "Luke Skywalker", "height": "1.72 m", "links": [ "href": "http://swapi.co/api/people/1/", "rel": "self", "method": "GET" , "href": "http://swapi.co/api/species/1/", "rel": "species", "method": "GET" ]
VERTICALRESPONSE API "name": "Luke Skywalker", "height": "1.72 m", "mass": "77 Kg", "hair_color": "Blond", "links": "self": "url": "http://swapi.co/api/people/1/" , "homeworld": "url": "http://swapi.co/api/planets/1/"
HYPERTEXT APPLICATION LANGUAGE (HAL) "name": "Luke Skywalker", "height": "1.72 m", "mass": "77 Kg", "hair_color": "Blond", "_links": "self": "href": "http://swapi.co/api/people/1/", "title": "Luke Skywalker" , "http://swapi.co/api/rels/species": "href": "http://swapi.co/api/species/1/", "title": "Human"
HYPERTEXT APPLICATION LANGUAGE (HAL) "_links": "self": "href": "/" , "curies": [ "name": "ht", "href": "http://haltalk.herokuapp.com/rels/rel", "templated": true ], "ht:users": "href": "/users"
JSON-LD "name": "Luke Skywalker", "height": "1.72 m", "mass": "77 Kg", "hair_color": "Blond", "@self": "http://swapi.co/api/people/1/", "@species": "http://swapi.co/api/species/1/",
NAGŁÓWKI HTTPLink: <http://swapi.co/api/species/1/>; rel="species"
HATEOAS NA SERWERZE
JERSEY DECLARATIVE LINKING
NAJPROSTSZY PRZYKŁAD@Path("/people")public class PeopleResource @GET public List<Person> list() ... @GET @Path("/id") public Person get(@PathParam("id") String personId) return new PeopleRepository().find(personId);
public class Person public String name; @InjectLink(resource=PeopleResource.class) public URI self;
"name": "Luke Skywalker", "self": "http://swapi.co/api/people"
PARAMETRY ŚCIEŻEK@Path("/people/id")public class PersonResource ...public class Person public String name; @InjectLink( resource=PlanetResource.class, bindings= @Binding("$resource.homeworldId") ) public URI homeworld; @JsonIgnore public String homeworldId;
"name": "Luke Skywalker", "homeworld": "http://swapi.co/api/planets/1"
STAŁE ŚCIEŻKIpublic class Root @InjectLink("/films", rel="films") public URI films;
"films": "http://swapi.co/api/films"
LINKI W NAGŁÓWKACH@InjectLinks( value=@InjectLink("planets/$resource.homeworldId"), rel="homeworld")public class Person public String name; @JsonIgnore public String homeworldId;
HTTP 200 OKAllow: GET, HEAD, OPTIONSContentType: application/jsonVary: AcceptLink: <http://swapi.co/api/planets/1>; rel="homeworld"
"name": "Luke Skywalker"
PLUSYWybór pomiędzy linkami w nagłówkach i wreprezentacjach
MINUSYBrak możliwości przesyłania tylko niektórychlinkówSilne połączenie reprezentacji z zasobamiAnnotation hell
JAX-RS 2.0
Tylko imperatywna konstrukcja linków.@Path("/people/id")public class PersonResource @GET public Response get(@PathParam("id") String personId) return Response .ok(new PeopleRepository().find(personId)) .links(Link.fromResource(PersonResource.class) .rel("self").build(personId)) .build();
HTTP 200 OKContentType: application/jsonLink: <http://swapi.co/api/people/1>; rel="self"
Ścieżki można pobierać z metod...@Path("/people/id")public class PersonResource @GET public Response get(@PathParam("id") String personId) return Response .ok(new PeopleRepository().find(personId)) .links(Link .fromMethod(PersonResource.class, "films") .rel("films").build(personId)) .build();
@GET @Path("/films") public Response films() ...
HTTP 200 OKContentType: application/jsonLink: <http://swapi.co/api/people/1/films>; rel="films"
...lub hardcodować@GETpublic Response get(@PathParam("id") String personId) Person person = new PeopleRepository().find(personId); String fatherId = person.findFather(); return Response .ok() .links(Link .fromPath("/people/id") .rel("father").build(fatherId)) .build();
HTTP 200 OKContentType: application/jsonLink: <http://swapi.co/api/people/darthVader>; rel="father"
"name": "Luke Skywalker"
PLUSYBrak ingerencji w klasy reprezentacjiImplementacja przez wiele różnych frameworków
MINUSYPłaska struktura linkówNagłówek trudniej jest analizować niżreprezentację
SPRING-HATEOAS
Aby dodawać linki do klasy, musi ona rozszerzać klasęResourceSupport - linki staną się częścią reprezentacji.
public class Person extends ResourceSupport public String name; public String height; public String mass; public String hair_color;
Linki dodajemy bezpośrednio do reprezentacjiimport static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
@Controllerpublic class PersonController @RequestMapping("/people/id") @ResponseBody public HttpEntity<Person> get(@PathVariable("id") String personId) Person person = new PeopleRepository().find(personId); person.add( linkTo(methodOn(PersonController.class).get(personId)) .withSelfRel() ); return new ResponseEntity<Person>(person, HttpStatus.OK);
Wynik: "name": "Luke Skywalker", "height": "1.72 m", "mass": "77 Kg", "hair_color": "Blond", "_links": "self": "href": "http://swapi.co/api/people/1"
PLUSYMinimalna ingerencja w klasy reprezentacjiMożliwość zagnieżdżania linkówObsługa różnych formatów linkówWsparcie JAX-RS
MINUSYNarastająca logika w kontrolerach, w przypadkulinkowania bardziej skomplikowanych struktur(np. kolekcji)
HATEOAS W KLIENCIE
JAVA (JAX-RS)
Linki w reprezentacjach "name": "Luke Skywalker", "links": "homeworld": "uri": "http://swapi.co/api/planets/1"
final Person table = target.request().get(Person.class);
URI homeworldURI = table.getLinks().getHomeworld().getUri();WebTarget homeworldTarget = client.target(homeworldURI);
Linki w nagłówkachHTTP 200 OKContentType: application/jsonLink: <http://swapi.co/api/planets/1>; rel="homeworld"Link: <http://swapi.co/api/species/1>; rel="species"
"name": "Luke Skywalker"
final Response response = target.request().get();
URI homeworldURI = response.getLink("homeworld").getUri();URI speciesURI = response.getLink("species").getUri();
JAVASCRIPT (ANGULARJS)
Linki w reprezentacjach "name": "Luke Skywalker", "links": "father": "uri": "http://swapi.co/api/people/2"
var personResource = $resource("/api/people/:personId");
var luke = personResource.query(personId: 1, function () var lukesFather = $resource(luke.links.father) .query(null, function () console.log(lukesFather); ););
Linki w nagłówkachHTTP 200 OKContentType: application/jsonLink: <http://swapi.co/api/people/2>; rel="father"
"name": "Luke Skywalker"
var personResource = $resource("/api/people/:personId");
var luke = personResource.query(personId: 1, function () var lukesFather = firstPerson.resource("father") .query(null, function () console.log(lukesFather); ););
Link: </people>; rel="people"; actions="[ 'name':'add','method':'POST', 'name':'list','method':'GET' ]"
$http.get("/").success( function (root) var peopleList = root.links.people.list(); root.links.people.add( "name": "Darth Vader", "height": "1.8" ); );
https://github.com/bandrzejczak/bpm-console-gui
https://github.com/bandrzejczak/bpm-console-rest
PODSUMOWANIEHATEOASMaszyna stanówRosnące wsparcie bibliotek
SWAPI.CO
PYTANIA?
DZIĘKI!