Restful web services
Dans la pratique
KONG TO
Code matters most
Summary
• Theory
• Principles
• CRUD
• FAQ
• Sample
Definition
REST for “REpresentation State Transfer is not a protocol, nor a format,
but an architecture style, a convention or an approach to construct
web services, on top of HTTP.
It is used to develop Resources Oriented Application. Applications that
are complied with this architecture is said RESTful”.
Contraints
• Client-server
• The separation of concerns enables independent evolution of the Client and the Server.
• Stateless
• The communication between Client and Server must be stateless between requests. Each
request from a client should contain all the necessary information for the service to complete
the request. all session state data should then be returned to the client at the end of each
request.
• Cache
• All responses from the Server should be explicitly labeled as cacheable or non-cacheable.
• Interface / Uniform Contract
• The Service and all Clients must share a single uniform technical interface.
• Layered System
• Allow for Intermediaries / Middleware.
Terminology
• Resource
• The Resource is the key abstraction of information in REST. Any information
that can be named / identified via a unique, global id (the URI) - is a Resource.
• Representation
• Any given Resource can have multiple Representations; these capture the
current state of the Resource in a specific format.
• At the lowest level, the Representation is “a sequence of bytes, plus
representation metadata to describe these bytes”.
• Hypermedia Types
• There are several Hypermedia types we can use for our Representations, and
we can build our own if we need to. Note that JSON is not a Hypermedia.
Principles
• Resources are identified by URI
• Every resource is identified by a unique identifier.
• Uniform interface, use HTTP verbs for CRUD operations on resources
• Use common set of verbs : GET, PUT, POST, DELETE, PATCH.
• Response represents resource (json, xml)
• Response is the representation of the resource.
• Links to other resources
• Within response, there are links to other resources (links, next, last, otherResource).
• Stateless services
• So the server does not maintain the states of the clients. This means that you cannot use server session storage and you have to authenticate
every request. Don’t confuse session storage and persistence into database.
• Each request from a client should contain all the necessary information for the service to complete the request. All session state data should
then be returned to the client at the end of each request.
• Cacheable, if you can
• So you don’t have to serve the same requests again and again
• Layered system
• To increase scalability, the REST system is composed of hierarchical layers. Each layer contains components which use the services of
components which are in the next layer below. So you can add new layers and components effortless. Intermediary components or middleware
must be transparent and independent.
CRUD basics
• Create : POST
• Retrieve : GET
• Update : PUT ou PATCH
• Delete : DELETE
FAQ
• Trigger an action on server side with a GET?
• E.g. : Triggering a batch or a notification
• Create a resource with a GET?
• Read a resource with a POST?
• E.g Search with complex criteria
• Update with DELETE, or Delete with PUT?
Following standards makes intent explicit. Refer to the RFC-626
Sample
GET /user/new
GET /new_user.x
POST /user/new
POST /user/delete
GET /delete_user.x?id=123
GET /user?id=1
GET /user/id/1
GET /get/user/by/profile/id
GET /get/user/address/by/city?city=paris
GET /find/user/by/name?name=Bob
PUT /user/update?id=100
GET /user/findBy?name=Bob&age=29
POST /user
GET /user/2
DELETE /user/2
PUT /user/2
GET
/user/address?city=paris
RESTfull Sample
@RestController
@RequestMapping("/address")
public class AdressController {
@Autowired
private AddressService addressService;
@PostMapping(value = "")
public AddressDto create(@RequestBody AddressDto address) {
return addressService.save(address);
}
@PutMapping(value = "/{id}")
public void update(@PathVariable Long id, @RequestBody AddressDto address) {
addressService.updateById(id, address);
}
@DeleteMapping(value = "/{id}")
public void delete(@PathVariable Long id) {
addressService.deleteById(id);
}
@GetMapping(value = "")
public Collection<AddressDto> findAll() {
return addressService.findAll();
}
}
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Address, Status } from '../address/address.model';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable()
export class AddressService {
private url = 'api/address';
constructor(private http: HttpClient) { }
create(address: Address): Observable<Address> {
return this.http.post<Address>(this.url, user, httpOptions);
}
update(address: Address): Observable<Address> {
return this.http.put<Address>(this.url + '/' + address.id, address, httpOptions);
}
delete(address: Address): Observable<void> {
return this.http.delete<void>(this.url + '/' + address.id, httpOptions);
}
addresses(): Observable<Array<Address>> {
return this.http.get<Array<Address>>(this.url, httpOptions);
}
}
RESTfull Client Sample
RESTfull Sample
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping(value = "")
public UserDto create(@RequestBody UserDto user) {
return userService.save(user);
}
@PutMapping(value = "/{username}")
public void updateById(@PathVariable String username, @RequestBody UserDto user) {
userService.updateByUsername(username, user);
}
@DeleteMapping(value = "/{username}")
public void deleteByUsername(@PathVariable String username) {
userService.deleteByUsername(username);
}
@GetMapping(value = "", params = {"username"})
public UserDto findByUsername(@RequestParam String username) {
return userService.findByUsername(username);
}
@GetMapping(value = "")
public Collection<UserDto> findAll() {
return userService.findAll();
}
}
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { User, Status } from '../user/user.model';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable()
export class UserService {
private url = 'api/user';
constructor(private http: HttpClient) { }
create(user: User): Observable<User> {
return this.http.post<User>(this.url, user, httpOptions);
}
update(user: User): Observable<User> {
return this.http.put<User>(this.url + '/' + user.username, user, httpOptions);
}
delete(user: User): Observable<void> {
return this.http.delete<void>(this.url + '/' + user.username, httpOptions);
}
users(): Observable<Array<User>> {
return this.http.get<Array<User>>(this.url, httpOptions);
}
}
RESTfull Client Sample
Json-patch
JSON Patch is a format for describing changes to a JSON document.
It can be used to avoid sending a whole document when only a part has
changed.
{
"username": "uncle",
"firstname": "kong",
"lastname": "to",
" roles ": ["user"]
}
[
{ "op": "replace", "path": "/username", "value": "donkey" },
{ "op": "add", "path": "/roles", "value": ["admin"] },
{ "op": "replace", "path": "/roles/0/name", "value": "guest" }
{ "op": "add", "path": "/age", "value": 25 }
{ "op": "remove", "path": "/firstname" }
]
{
"username": "donkey",
"lastname": "to",
"age": 25,
"roles": ["guest", "admin"]
}
Json-patch sample
@RestController
@RequestMapping("/address")
public class AdressController {
@Autowired
private AddressService addressService;
@PostMapping(value = "")
public AddressDto create(@RequestBody AddressDto address) {
return addressService.save(address);
}
@PutMapping(value = "/{id}")
public void update(@PathVariable Long id, @RequestBody AddressDto address) {
addressService.updateById(id, address);
}
@PatchMapping(value = "/{id}")
public void patch(@PathVariable Long id, @RequestBody List<JsonPatchOperation> operations) {
this.addressService.patch(id, operations);
}
@PatchMapping(value = "/{id}", params = {"patchById"})
public void patchById(@PathVariable Long id, @RequestBody JsonPatch jsonPatch) {
this.addressService.patchById(id, jsonPatch);
}
@DeleteMapping(value = "/{id}")
public void delete(@PathVariable Long id) {
addressService.deleteById(id);
}
@GetMapping(value = "")
public Collection<AddressDto> findAll() {
return addressService.findAll();
}
}
Json-patch sample
public class AddressService {
private AddressRepository addressRepository;
public AddressService(AddressRepository addressRepository) {
this.addressRepository = addressRepository;
}
public void updateById(Long id, AddressDto userDto) {
Optional<AddressEntity> entity = addressRepository.findById(id);
entity.ifPresent(u -> {
BeanUtils.copyProperties(AddressMapper.from(userDto), u);
addressRepository.save(u);
});
}
public void patchById(Long id, JsonPatch jsonPatch) {
this.findById(id)
.ifPresent(existing -> {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode patched = jsonPatch.apply(objectMapper.convertValue(existing, JsonNode.class));
AddressEntity patchedUser = objectMapper.treeToValue(patched, AddressEntity.class);
addressRepository.save(patchedUser);
} catch (JsonPatchException | JsonProcessingException e) {
throw new IllegalStateException();
}
});
}
}
Json-patch authorized operation
public class AddressService {
private AddressRepository addressRepository;
public AddressService(AddressRepository addressRepository) {
this.addressRepository = addressRepository;
}
public void patch(Long id, List<JsonPatchOperation> operations) {
addressRepository.findById(id)
.ifPresent(existing -> {
ObjectMapper objectMapper = new ObjectMapper();
operations.stream().forEach(op -> {
try {
JsonNode node = objectMapper.readValue(op.toString(), JsonNode.class);
// allow only add operation
if ("add".equals(node.get("op"))) {
JsonNode patched = op.apply(objectMapper.convertValue(existing, JsonNode.class));
AddressEntity patchedUser = objectMapper.treeToValue(patched, AddressEntity.class);
addressRepository.save(patchedUser);
}
} catch (IOException | JsonPatchException e) {
throw new IllegalStateException();
}
});
});
}
}

Restful webservices

  • 1.
    Restful web services Dansla pratique KONG TO Code matters most
  • 2.
  • 3.
    Definition REST for “REpresentationState Transfer is not a protocol, nor a format, but an architecture style, a convention or an approach to construct web services, on top of HTTP. It is used to develop Resources Oriented Application. Applications that are complied with this architecture is said RESTful”.
  • 4.
    Contraints • Client-server • Theseparation of concerns enables independent evolution of the Client and the Server. • Stateless • The communication between Client and Server must be stateless between requests. Each request from a client should contain all the necessary information for the service to complete the request. all session state data should then be returned to the client at the end of each request. • Cache • All responses from the Server should be explicitly labeled as cacheable or non-cacheable. • Interface / Uniform Contract • The Service and all Clients must share a single uniform technical interface. • Layered System • Allow for Intermediaries / Middleware.
  • 5.
    Terminology • Resource • TheResource is the key abstraction of information in REST. Any information that can be named / identified via a unique, global id (the URI) - is a Resource. • Representation • Any given Resource can have multiple Representations; these capture the current state of the Resource in a specific format. • At the lowest level, the Representation is “a sequence of bytes, plus representation metadata to describe these bytes”. • Hypermedia Types • There are several Hypermedia types we can use for our Representations, and we can build our own if we need to. Note that JSON is not a Hypermedia.
  • 6.
    Principles • Resources areidentified by URI • Every resource is identified by a unique identifier. • Uniform interface, use HTTP verbs for CRUD operations on resources • Use common set of verbs : GET, PUT, POST, DELETE, PATCH. • Response represents resource (json, xml) • Response is the representation of the resource. • Links to other resources • Within response, there are links to other resources (links, next, last, otherResource). • Stateless services • So the server does not maintain the states of the clients. This means that you cannot use server session storage and you have to authenticate every request. Don’t confuse session storage and persistence into database. • Each request from a client should contain all the necessary information for the service to complete the request. All session state data should then be returned to the client at the end of each request. • Cacheable, if you can • So you don’t have to serve the same requests again and again • Layered system • To increase scalability, the REST system is composed of hierarchical layers. Each layer contains components which use the services of components which are in the next layer below. So you can add new layers and components effortless. Intermediary components or middleware must be transparent and independent.
  • 7.
    CRUD basics • Create: POST • Retrieve : GET • Update : PUT ou PATCH • Delete : DELETE
  • 8.
    FAQ • Trigger anaction on server side with a GET? • E.g. : Triggering a batch or a notification • Create a resource with a GET? • Read a resource with a POST? • E.g Search with complex criteria • Update with DELETE, or Delete with PUT? Following standards makes intent explicit. Refer to the RFC-626
  • 9.
    Sample GET /user/new GET /new_user.x POST/user/new POST /user/delete GET /delete_user.x?id=123 GET /user?id=1 GET /user/id/1 GET /get/user/by/profile/id GET /get/user/address/by/city?city=paris GET /find/user/by/name?name=Bob PUT /user/update?id=100 GET /user/findBy?name=Bob&age=29 POST /user GET /user/2 DELETE /user/2 PUT /user/2 GET /user/address?city=paris
  • 10.
    RESTfull Sample @RestController @RequestMapping("/address") public classAdressController { @Autowired private AddressService addressService; @PostMapping(value = "") public AddressDto create(@RequestBody AddressDto address) { return addressService.save(address); } @PutMapping(value = "/{id}") public void update(@PathVariable Long id, @RequestBody AddressDto address) { addressService.updateById(id, address); } @DeleteMapping(value = "/{id}") public void delete(@PathVariable Long id) { addressService.deleteById(id); } @GetMapping(value = "") public Collection<AddressDto> findAll() { return addressService.findAll(); } }
  • 11.
    import { Injectable} from '@angular/core'; import { Observable, of } from 'rxjs'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Address, Status } from '../address/address.model'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable() export class AddressService { private url = 'api/address'; constructor(private http: HttpClient) { } create(address: Address): Observable<Address> { return this.http.post<Address>(this.url, user, httpOptions); } update(address: Address): Observable<Address> { return this.http.put<Address>(this.url + '/' + address.id, address, httpOptions); } delete(address: Address): Observable<void> { return this.http.delete<void>(this.url + '/' + address.id, httpOptions); } addresses(): Observable<Array<Address>> { return this.http.get<Array<Address>>(this.url, httpOptions); } } RESTfull Client Sample
  • 12.
    RESTfull Sample @RestController @RequestMapping("/user") public classUserController { @Autowired private UserService userService; @PostMapping(value = "") public UserDto create(@RequestBody UserDto user) { return userService.save(user); } @PutMapping(value = "/{username}") public void updateById(@PathVariable String username, @RequestBody UserDto user) { userService.updateByUsername(username, user); } @DeleteMapping(value = "/{username}") public void deleteByUsername(@PathVariable String username) { userService.deleteByUsername(username); } @GetMapping(value = "", params = {"username"}) public UserDto findByUsername(@RequestParam String username) { return userService.findByUsername(username); } @GetMapping(value = "") public Collection<UserDto> findAll() { return userService.findAll(); } }
  • 13.
    import { Injectable} from '@angular/core'; import { Observable, of } from 'rxjs'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { User, Status } from '../user/user.model'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; @Injectable() export class UserService { private url = 'api/user'; constructor(private http: HttpClient) { } create(user: User): Observable<User> { return this.http.post<User>(this.url, user, httpOptions); } update(user: User): Observable<User> { return this.http.put<User>(this.url + '/' + user.username, user, httpOptions); } delete(user: User): Observable<void> { return this.http.delete<void>(this.url + '/' + user.username, httpOptions); } users(): Observable<Array<User>> { return this.http.get<Array<User>>(this.url, httpOptions); } } RESTfull Client Sample
  • 14.
    Json-patch JSON Patch isa format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed. { "username": "uncle", "firstname": "kong", "lastname": "to", " roles ": ["user"] } [ { "op": "replace", "path": "/username", "value": "donkey" }, { "op": "add", "path": "/roles", "value": ["admin"] }, { "op": "replace", "path": "/roles/0/name", "value": "guest" } { "op": "add", "path": "/age", "value": 25 } { "op": "remove", "path": "/firstname" } ] { "username": "donkey", "lastname": "to", "age": 25, "roles": ["guest", "admin"] }
  • 15.
    Json-patch sample @RestController @RequestMapping("/address") public classAdressController { @Autowired private AddressService addressService; @PostMapping(value = "") public AddressDto create(@RequestBody AddressDto address) { return addressService.save(address); } @PutMapping(value = "/{id}") public void update(@PathVariable Long id, @RequestBody AddressDto address) { addressService.updateById(id, address); } @PatchMapping(value = "/{id}") public void patch(@PathVariable Long id, @RequestBody List<JsonPatchOperation> operations) { this.addressService.patch(id, operations); } @PatchMapping(value = "/{id}", params = {"patchById"}) public void patchById(@PathVariable Long id, @RequestBody JsonPatch jsonPatch) { this.addressService.patchById(id, jsonPatch); } @DeleteMapping(value = "/{id}") public void delete(@PathVariable Long id) { addressService.deleteById(id); } @GetMapping(value = "") public Collection<AddressDto> findAll() { return addressService.findAll(); } }
  • 16.
    Json-patch sample public classAddressService { private AddressRepository addressRepository; public AddressService(AddressRepository addressRepository) { this.addressRepository = addressRepository; } public void updateById(Long id, AddressDto userDto) { Optional<AddressEntity> entity = addressRepository.findById(id); entity.ifPresent(u -> { BeanUtils.copyProperties(AddressMapper.from(userDto), u); addressRepository.save(u); }); } public void patchById(Long id, JsonPatch jsonPatch) { this.findById(id) .ifPresent(existing -> { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode patched = jsonPatch.apply(objectMapper.convertValue(existing, JsonNode.class)); AddressEntity patchedUser = objectMapper.treeToValue(patched, AddressEntity.class); addressRepository.save(patchedUser); } catch (JsonPatchException | JsonProcessingException e) { throw new IllegalStateException(); } }); } }
  • 17.
    Json-patch authorized operation publicclass AddressService { private AddressRepository addressRepository; public AddressService(AddressRepository addressRepository) { this.addressRepository = addressRepository; } public void patch(Long id, List<JsonPatchOperation> operations) { addressRepository.findById(id) .ifPresent(existing -> { ObjectMapper objectMapper = new ObjectMapper(); operations.stream().forEach(op -> { try { JsonNode node = objectMapper.readValue(op.toString(), JsonNode.class); // allow only add operation if ("add".equals(node.get("op"))) { JsonNode patched = op.apply(objectMapper.convertValue(existing, JsonNode.class)); AddressEntity patchedUser = objectMapper.treeToValue(patched, AddressEntity.class); addressRepository.save(patchedUser); } } catch (IOException | JsonPatchException e) { throw new IllegalStateException(); } }); }); } }