API регистрации становится RESTful

API регистрации становится RESTful

1. обзор

В последних нескольких статьяхthe Registration series here on example мы создали большую часть необходимой нам функциональности в стиле MVC.

Теперь мы собираемся перевести некоторые из этих API-интерфейсов на более REST-подход.

2. ОперацияRegister

Начнем с основной операции Register:

@RequestMapping(value = "/user/registration", method = RequestMethod.POST)
@ResponseBody
public GenericResponse registerUserAccount(
      @Valid UserDto accountDto, HttpServletRequest request) {
    logger.debug("Registering user account with information: {}", accountDto);
    User registered = createUserAccount(accountDto);
    if (registered == null) {
        throw new UserAlreadyExistException();
    }
    String appUrl = "http://" + request.getServerName() + ":" +
      request.getServerPort() + request.getContextPath();

    eventPublisher.publishEvent(
      new OnRegistrationCompleteEvent(registered, request.getLocale(), appUrl));

    return new GenericResponse("success");
}

Итак, как это отличается от оригинальной MVC-ориентированной реализации?

Вот оно:

  • запрос теперь правильно сопоставлен с HTTP POST

  • теперь мы возвращаем правильный DTO и маршалируем его с помощью аннотации@ResponseBody непосредственно в теле ответа

  • мы больше не занимаемся обработкой ошибок в методе

Мы также удаляем старыйshowRegistrationPage() - он не нужен просто для отображения страницы регистрации.

3. registration.html

С этими изменениями нам теперь нужно изменитьregistration.html на:

  • используйте Ajax для отправки регистрационной формы

  • получить результаты операции в формате JSON

Вот оно:



form


4. Обработка исключений

Наряду с более RESTful API логика обработки исключений также станет более зрелой.

Мы используем тот же механизм@ControllerAdvice для чистой обработки исключений, создаваемых приложением - и теперь нам нужен новый тип исключения.

ЭтоBindException, который выдается, когдаUserDto подтвержден (если он недействителен). Мы переопределим методResponseEntityExceptionHandler по умолчаниюhandleBindException(), чтобы добавить ошибки в тело ответа:

@Override
protected ResponseEntity handleBindException
  (BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    logger.error("400 Status Code", ex);
    BindingResult result = ex.getBindingResult();
    GenericResponse bodyOfResponse =
      new GenericResponse(result.getFieldErrors(), result.getGlobalErrors());

    return handleExceptionInternal(
      ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}


Нам также нужно будет обработать наш собственныйExceptionUserAlreadyExistException - который выдается, когда пользователь регистрируется с уже существующим адресом электронной почты:

@ExceptionHandler({ UserAlreadyExistException.class })
public ResponseEntity handleUserAlreadyExist(RuntimeException ex, WebRequest request) {
    logger.error("409 Status Code", ex);
    GenericResponse bodyOfResponse = new GenericResponse(
      messages.getMessage("message.regError", null, request.getLocale()), "UserAlreadyExist");

    return handleExceptionInternal(
      ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
}




5. GenericResponse

Нам также необходимо улучшить реализациюGenericResponse, чтобы удерживать эти ошибки проверки:

public class GenericResponse {

    public GenericResponse(List fieldErrors, List globalErrors) {
        super();
        ObjectMapper mapper = new ObjectMapper();
        try {
            this.message = mapper.writeValueAsString(fieldErrors);
            this.error = mapper.writeValueAsString(globalErrors);
        } catch (JsonProcessingException e) {
            this.message = "";
            this.error = "";
        }
    }
}

6. UI - Поле и глобальные ошибки

Наконец, давайте посмотрим, как обрабатывать как полевые, так и глобальные ошибки с помощью jQuery:

var serverContext = [[function register(){
    $(".alert").html("").hide();
    var formData= $('form').serialize();
    $.post(serverContext + "/user/registration",formData ,function(data){
        if(data.message == "success"){
            window.location.href = serverContext +"/successRegister.html";
        }
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "/emailError.html";
        }
        else if(data.responseJSON.error.indexOf("InternalError") > -1){
            window.location.href = serverContext +
              "/login.html?message=" + data.responseJSON.message;
        }
        else if(data.responseJSON.error == "UserAlreadyExist"){
            $("#emailError").show().html(data.responseJSON.message);
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#"+item.field+"Error").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"
"); }); } }

Обратите внимание, что:

  • Если есть ошибки проверки - тогда объектmessage содержит ошибки поля, а объектerror содержит глобальные ошибки

  • Мы отображаем каждую ошибку поля рядом с ее полем

  • Мы отображаем все глобальные ошибки в одном месте в конце формы

7. Заключение

Цель этой быстрой статьи - привести API в более RESTful-направлении и показать простой способ работы с этим API в интерфейсе.

Сам интерфейс jQuery не является целью - просто потенциальный клиент, который может быть реализован в любом количестве JS-сред, в то время как API остается точно таким же.

full implementation этого руководства можно найти вthe github project - это проект на основе Eclipse, поэтому его должно быть легко импортировать и запускать как есть.