Spring REST
Ing. César Alcívar Aray
REST
              ¿Qué es REST?
•   REpresentational State Transfer (REST) es un estilo
    arquitectónico para la Web. REST especifica un conjunto de
    limitaciones. Estas limitaciones aseguran que los clientes
    puedan interactuar con los servidores de forma flexible.
     Terminologías comunes
•   Servidor: Provee servicios. Exponer servicios que pueden ser
    consumidos por los clientes
•   Cliente: Consume servicios. Puede ser un brower o cualquier sistema
•   Recurso: Cualquier información puede ser un recurso. Una persona,
    una imagen, o producto
•   Representación: Una forma específica de representar un recurso. Por
    ejemplo, el recurso producto puede ser representado a través de JSON,
    XML, HTML. Diferentes clientes pueden solicitar diferentes
    representaciones de un recurso
•   Client-Server: existe una separación entre los clientes y los
         Limitaciones REST
    servidores
•   Stateless: la comunicación entre el cliente y el servidor debería
    ser sin estados.
•   Layered System: Múltiples capas jerárquicas como Gateways,
    firewalls y proxy pueden existir entre el cliente y el servidor
•   Cacheble: las solicitudes al servidor debería ser declaradas como
    cacheable o no chachebles
•   Uniform Interface: cada recurso tiene un identificador. En el
    caso de los servicios Web, usamos URI, por ejemplo,
    /users/Jack/todos/1
•   Hypermedia as the engine of application state: El consumidor
Spring REST
       Proyecto Spring Boot
•   Creamos un proyecto Spring Boot llamado SpringRestBasic con
    el paquete com.poo.isi
•   Agregamos el starter: Web
Creamos el Primer controller
     con su método
   @RestController
   public class BasicController {
         @GetMapping("/welcome")
         public String welcome() {
                return "Hello String Rest";
         }
   }
                  Anotaciones
•   @RestController: es una combinación de @ResponseBody y
    @Controller. típicamente usadas para crear REST Controllers
•   @GetMapping(‘’/welcome’’): Es un shortcut de
    @RequestMapping(method = RequestMethod.GET)
Resultado de la ejecución
       Retornando un objeto
•   Creamos una clase llamada WelcomeBean con el atributo
    message, como se muestra a continuación:
               public class WelcomeBean {
                          private String message;
                          public WelcomeBean() {}
                            public WelcomeBean(String message) {
                                      super();
                                      this.message = message;
                            }
                   // getters and setters
               }
Controller Method para retornar
           un objeto
@RestController
public class BasicController {
        @GetMapping("/welcome-with-object")
        public WelcomeBean welcomeWithObject() {
                return new WelcomeBean("Hello String Rest");
        }
}
Resultado de la ejecución
                Controller Method con
                   @PathVariable
@RestController
public class BasicController {
         @GetMapping("/welcome-with-parameter/name/{name}")
         public WelcomeBean welcomeWithParameter(@PathVariable String name) {
                 return new WelcomeBean("Hello, " + name);
         }
}
Resultado de la ejecución
Creando un recurso
     TODO
Request methods, operaciones
       Http Request Method         Operation
                             Obtener detalles de un
              GET
                                   recurso
                                Crear un nuevo
             POST
                                   recurso
                                Reemplazar un
              PUT
                                   recurso
             PATCH           Actualizar un recurso
            DELETE            Eliminar un recurso
                                     La clase Todo
public class Todo {
            private int id;
             private String user;
             private String desc;
             private Date targetDate;
             private boolean isDone;
             public Todo() {}
               public Todo(int id, String user, String desc, Date targetDate, boolean isDone) {
                           super();
                           this.id = id;
                           this.user = user;
                           this.desc = desc;
                           this.targetDate = targetDate;
                           this.isDone = isDone;
               }
    // getters and setters
}
                      La clase TodoService
@Service
public class TodoService {
                 private static List<Todo> todos = new ArrayList<Todo>();
                 private static int todoCount = 3;
                 static {
                                   todos.add(new Todo(1,"Jack","Learn Spring MVC",new Date(),false));
                                   todos.add(new Todo(2,"Jack","Learn Struts",new Date(),false));
                                   todos.add(new Todo(3,"Jill","Learn Hibernate",new Date(),false));
                 }
                 public List<Todo> retrieveTodos(String user){
                                  System.out.println("Cacheable");
                                  List<Todo> filteredTodos = new ArrayList<Todo>();
                                  for(Todo todo:todos) {
                                                    if(todo.getUser().equals(user)) {
                                                                      filteredTodos.add(todo);
                                                    }
                                  }
                                  return filteredTodos;
                 }
                 public Todo addTodo(String name,String desc,Date targetDate,boolean isDone) {
                                 Todo todo = new Todo(++todoCount,name,desc,targetDate,isDone);
                                 todos.add(todo);
                                 return todo;
                 }
                 public Todo retrieveTodo(int id) {
                                   for(Todo todo:todos) {
                                                    if(todo.getId()==id)
                                                                      return todo;
                                   }
                                   return null;
                 }
      Obteniendo un Todo list
@RestController
public class TodoController {
        @Autowired
        private TodoService todoService;
        @GetMapping("/users/{name}/todos")
        public List<Todo> retrieveTodos(@PathVariable String name){
                return todoService.retrieveTodos(name);
        }
}
  Obteniendo un Todo específico
@RestController
public class TodoController {
        @GetMapping("/users/{name}/todos/{id}")
        public Resource<Todo> retrieveTodo(@PathVariable String name,@PathVariable int id){
                return todoService.retrieveTodo(id);
        }
                     Agregando un Todo
@RestController
public class TodoController {
             …
             @PostMapping("users/{name}/todos")
             public ResponseEntity<?> add(@PathVariable String name,@RequestBody Todo todo){
                          logger.info("Todo -> {}",todo);
                          Todo createdTodo = todoService.addTodo(name, todo.getDesc(), todo.getTargetDate(), todo.isDone());
                          if(createdTodo==null)
                                        return ResponseEntity.noContent().build();
                           URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                                                     .path("/{id}").buildAndExpand(createdTodo.getId()).toUri();
                           return ResponseEntity.created(location).build();
             }
}
•   @PostMapping(“/users/{name}/todos”): Mapea el método HTTP Request con un
    método POST
•   ResponseEntity<?>: Un HTTP post request debería idealmente retornar el URI
    del recurso creado.
•   ReponseEntity.noContent().build(): Usado para retornar que la creación del
    recurso falló
•   ServletUriComponentsBuilder.fromCurrentRequest().path(“/
    {id}”).buildAndExpand(createdTodo.getId()).toUri(): URI creado para el recurso
•   ResponseEntity.create(location).build(): Retorna el estatus 201 (CREATED)
Response Status
Postman para crear un Todo
Exception Handling
                  Introducción
•   Cuando ocurre algún error, nosotros queremos retornar una
    buena descripción del problema que sucedió cuando se
    consumió un servicio.
Consumiendo un recurso que no
           existe
Creamos una Custom Exception
public class TodoNotFoundException extends RuntimeException {
       /**
        *
        */
       private static final long serialVersionUID = 1L;
       public TodoNotFoundException(String msg) {
               super(msg);
       }
}
@RestController
public class TodoController {
          …
          @GetMapping("/users/{name}/todos/{id}")
          public Todo retrieveTodo(@PathVariable String name,@PathVariable int id){
                   Todo todo = todoService.retrieveTodo(id);
                   if(todo==null)
                             throw new TodoNotFoundException("Todo Not Found");
                  return todo;
         }
}
        Personalizando el mensaje de
                    error
public class ExceptionResponse {
         private Date timestamp = new Date();
         private String message;
         private String details;
         public ExceptionResponse(String message, String details) {
                 super();
                 this.message = message;
                 this.details = details;
         }
    …
}
                Controlando
           TodoNotFoundException
@ControllerAdvice
@RestController
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
        @ExceptionHandler(TodoNotFoundException.class)
        public ResponseEntity<ExceptionResponse> toNoFound(TodoNotFoundException ex){
                 ExceptionResponse response = new ExceptionResponse(ex.getMessage(), "Any detail");
                 return new ResponseEntity<ExceptionResponse>(response,HttpStatus.NOT_FOUND);
        }
•   extends ResponseEntityExceptionHandler: nosotros extendemos
    la clase ResponseEntityExceptionHandler, la cual es la clase
    base que provee excepciones centralizadas de clases
    ControllerAdvice en Spring MVC
•   @ExceptionHandler(TodoNoFoundException.class): Define el
    método que gestionara las excepciones
    TodoNotFoundException
•   ExceptionResponse exResponse = new
    ExceptionResponse(ex.getMessage(),”Any details”): esto crea
    un mensaje personalizado
                 Controlando todas las
                      Exceptions
@ExceptionHandler(Exception.class)
       public final ResponseEntity<ExceptionResponse> handleAllExceptions(
                          Exception ex, WebRequest request) {
                  ExceptionResponse reponse =new ExceptionResponse(new
Date(),ex.getMessage(),request.getDescription(false));
                  return new
ResponseEntity<ExceptionResponse>(reponse,HttpStatus.INTERNAL_SERVER_ERROR);
         }
Validaciones
                   Introducción
•   Un buen servicio siempre valida los datos antes de procesarlos;
    Bean Validation API es usado para validar servicios
•   El Bean Validation API provee un núemro de anotaciones que
    pueden ser usadas para validar jeans. La especificación JSR 349
    es definida en Bean Validation API 1.1. Hibernate-validator es
    una referencia de esa implementación
        La anotación @Valid
@PostMapping("/users")
     public User createUser(@Valid @RequestBody User user) {
            userRepository.save(user);
            return user;
     }
      Anotaciones de validación
@Entity
public class User {
        @Id
        @GeneratedValue
        private Integer id;
        @Size(min=2,message="Name should have atleast 2 characters")
        private String name;
        @Past
        private Date birthdate;
…
Método para gestionar las validaciones de las
          propiedades de objetos
@ControllerAdvice
@RestController
public class ReponseExceptionHandler extends ResponseEntityExceptionHandler {
…
             @Override
             protected ResponseEntity<Object> handleMethodArgumentNotValid(
                                 MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest
request) {
                     ExceptionResponse reponse =new ExceptionResponse(new
Date(),ex.getMessage(),ex.getBindingResult().toString());
                       return new ResponseEntity<Object>(reponse,HttpStatus.BAD_REQUEST);
             }
…
}
Internacionalización
         Internacionalización
•   Internacionalización es el proceso de desarrollar aplicación y
    servicios que puedan ser personalizados para diferentes
    lenguajes y culturas del mundo. también conocido como
    localization
•   En Spring Boot necesitamos agregar un LocaleResolver y un
    ResourceBundleMessageSource como fuente de mensajes
                     En la clase main
@Bean
        public LocaleResolver localeResolver() {
                  SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
                  sessionLocaleResolver.setDefaultLocale(Locale.US);
                  return sessionLocaleResolver;
        }
        @Bean
        public ResourceBundleMessageSource messageSource() {
                  ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
                  messageSource.setBasename("messages");
                  return messageSource;
        }
•   sessionLocaleResolver.setDefaultLocale(Locale.US): Se
    establece la configuración por defecto de Locale.US
•   messageSource.setBasenames(“messages”): establecemos el
    nombre base de los archivos de mensajes (messages.properties,
    messages_fr.properties)
•   messageSource.setUseCodeAsDefaultMessage(true): si este
    mensaje no esta, entonces el código es retornado como mensaje
    por defecto
•   messages.properties:
      •   welcome.message=Welcome
          in English
•   message_fr.properties:
      •   welcome.message=Bonjour
@RestController
public class HelloController {
            @Autowired
            private MessageSource messageSource;
            …
            @GetMapping("/hello-world-internacional")
            public String helloWorldInternationalized(@RequestHeader(name="Accept-Language",required=false) Locale
locale) {
                     return messageSource.getMessage("welcome.message", null, locale);
            }
}
•   @RequestHeader(value=“Accept-Language”,required=false)
    Locale local: el objeto Locale se establece en el header de la
    solicitud.
•   messageSource.getMessage(“welcome.message”,null,localte):
    messageSource es inyectado dentro del controller. Nosotros
    obtenemos en mensaje de la configuración regional del header.
  Content Negotiation -
Implemening Support for
         XML
<dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
          </dependency>
Configuring Auto Generation
 of Swagger Documentation
           Generating a Swagger
               specification
•   Springfox Swagger puede ser utilizado para generar
    documentación de los servicios RESTful
  Dependencias
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.4.0</version>
  </dependency>
  <dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.4.0</version>
  </dependency>
@Configuration
@EnableSwagger2
public class SwaggerConfig {
        @Bean
        public Docket api() {
                return new Docket(DocumentationType.SWAGGER_2)
                               .select()
                               .apis(RequestHandlerSelectors.any())
                               .paths(PathSelectors.any()).build();
        }
}
•   @Configuration: Define a archivo de configuración de Spring
•   EnableSwagger2: La anotación habilita el soporte a Swagger
•   Docket: Una clases para la configuración de generación de
    Swagger documentation
•   .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
    : Incluye documentación a todos los paths
http://localhost:8080/v2/api-docs
Swagger-UI
@ApiOperation(value = "Retrieve all users",
                     response = User.class,
                     produces = "application/json")
     @GetMapping("/users")
     public Iterable<User> retrieveAllUsers(){
             return userRepository.findAll();
     }
                   t
          A               B
         Dato            Dato
       Siguiente       Siguiente   null
null   Anterior        Anterior
            t
   A               B
  Dato            Dato
Siguiente       Siguiente   null