User management microservice (Part 4): Implementing REST API

April 18, 2017 · 7 min read – Last updated on August 10, 2020

In the previous part the data access layer along with the repositories were implemented, before that the domain model without having to rely on any framework specific class or feature and now time has come to add REST controllers on the top of that.

1. Support module for REST endpoints

In most of the projects I had worked on, there was a need to configure Spring MVC correctly for the controller layer. As Single Page Applications had become wide-spread in recent years, it was less and less frequent that I had to configure and develop a view layer (with JSPs or a with a template engine) within a Spring MVC app.

Nowadays, it’s rather typical to create fully REST back-ends consuming and producing JSON, which are then used by SPAs or mobile apps directly. All that said, I gathered the commons requirements of Spring MVC configuration that makes it possible to develop such back-ends.

  • Jackson is used for generating and consuming JSON
  • application/json is the default content type
  • ObjectMapper knows how to deal with Joda and the JSR-310 date/time API, it serializes dates in ISO format and it doesn’t serialize absent values (NON_ABSENT).
  • ModelMapper is used to convert from/to DTOs and model classes
  • There’s a custom exception handler for EntityNotFoundException and other common application level exceptions
  • Capture unmapped requests and handle them with the previously defined error response

1.2. Common REST configuration projects can re-use

The code is now on GitHub and there’s a new module springuni-commons-rest which has all those common utilities one might need for implementing REST controllers. Specialized RestConfiguration can be extended by modules and they can further refine the default configuration.

1.3. Error handling

Normal web applications present an easily consumable error page to end users. For a pure JSON-based REST back-end however this is not a requirement, as its clients are SPA or mobile apps.

For that reason, it’s desirable to respond to errors with a well-defined JSON structure (RestErrorResponse) front-ends can easily understand.

@Data
public class RestErrorResponse {

  private final int statusCode;
  private final String reasonPhrase;
  private final String detailMessage;

  protected RestErrorResponse(HttpStatus status, String detailMessage) {
    statusCode = status.value();
    reasonPhrase = status.getReasonPhrase();
    this.detailMessage = detailMessage;
  }

  public static RestErrorResponse of(HttpStatus status) {
    return of(status, null);
  }

  public static RestErrorResponse of(HttpStatus status, Exception ex) {
    return new RestErrorResponse(status, ex.getMessage());
  }

}

This one returns the HTTP error code, the textual representation of the HTTP error and a detail message to clients and RestErrorHandler takes care of generation the right response for application specific exceptions.

@RestControllerAdvice
public class RestErrorHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(ApplicationException.class)
  public ResponseEntity<Object> handleApplicationException(final ApplicationException ex) {
    return handleExceptionInternal(ex, BAD_REQUEST);
  }

  @ExceptionHandler(EntityAlreadyExistsException.class)
  public ResponseEntity<Object> handleEntityExistsException(final EntityAlreadyExistsException ex) {
    return handleExceptionInternal(ex, BAD_REQUEST);
  }

  @ExceptionHandler(EntityConflictsException.class)
  public ResponseEntity<Object> handleEntityConflictsException(final EntityConflictsException ex) {
    return handleExceptionInternal(ex, CONFLICT);
  }

  @ExceptionHandler(EntityNotFoundException.class)
  public ResponseEntity<Object> handleEntityNotFoundException(final EntityNotFoundException ex) {
    return handleExceptionInternal(ex, NOT_FOUND);
  }

  @ExceptionHandler(RuntimeException.class)
  public ResponseEntity<Object> handleRuntimeException(final RuntimeException ex) {
    return handleExceptionInternal(ex, INTERNAL_SERVER_ERROR);
  }

  @ExceptionHandler(UnsupportedOperationException.class)
  public ResponseEntity<Object> handleUnsupportedOperationException(
      final UnsupportedOperationException ex) {

    return handleExceptionInternal(ex, NOT_IMPLEMENTED);
  }

  @Override
  protected ResponseEntity<Object> handleExceptionInternal(
      Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {

    RestErrorResponse restErrorResponse = RestErrorResponse.of(status, ex);
    return super.handleExceptionInternal(ex, restErrorResponse, headers, status, request);
  }

  private ResponseEntity<Object> handleExceptionInternal(Exception ex, HttpStatus status) {
    return handleExceptionInternal(ex, null, null, status, null);
  }

}

1.4. Handling unmapped requests

In order to be able to handle unmapped requests, first we need to define a default handler and then set it up with RequestMappingHandlerMapping.

@Controller
public class DefaultController {

  @RequestMapping
  public ResponseEntity<RestErrorResponse> handleUnmappedRequest(final HttpServletRequest request) {
    return ResponseEntity.status(NOT_FOUND).body(RestErrorResponse.of(NOT_FOUND));
  }

}

RestConfiguration extends WebMvcConfigurationSupport, which provides customization hooks for fine tuning the MVC infrastructure.

@EnableWebMvc
@Configuration
public class RestConfiguration extends WebMvcConfigurationSupport {

  ...

  protected Object createDefaultHandler() {
    return new DefaultController();
  }
  
  ...

  @Override
  protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
    RequestMappingHandlerMapping handlerMapping = super.createRequestMappingHandlerMapping();
    Object defaultHandler = createDefaultHandler();
    handlerMapping.setDefaultHandler(defaultHandler);
    return handlerMapping;
  }

}

All we have to do after that is to override createRequestMappingHandlerMapping() and use setDefaultHandler() on the pre-created RequestMappingHandlerMapping.

2. REST endpoints for managing users

In part 1, I defined a bunch of restful endpoints for interacting with this user management service. In essence there’s nothing really special about creating RESTful endpoints with Spring MVC.

There are however some tiny details I’ve just recently realized.

  • As of Spring 4.3 there are a bunch of shorthand annotations for defining request handlers. @GetMapping is a composed annotation that acts a shortcut for @RequestMapping(method = RequestMethod.GET) so as its counterparts @PostMapping, @PutMapping, etc.
  • I found a library modelmapper for dealing with DTO from/to model class conversion. I used Apache Commons Beanutils for that before.
  • Controllers are manually registered to speed application initialization up. As I mentioned in part 3, that this app will be hosted on Google App Engine Standard environment there starting a new instance is vital
@RestController
@RequestMapping("/users")
public class UserController {

  private final UserService userService;
  private final ModelMapper modelMapper;

  public UserController(ModelMapper modelMapper, UserService userService) {
    this.modelMapper = modelMapper;
    this.userService = userService;
  }

  @GetMapping("/{userId}")
  public UserDto getUser(@PathVariable long userId) throws ApplicationException {
    User user = userService.getUser(userId);
    return modelMapper.map(user, UserDto.class);
  }

  ...

  @PostMapping
  public void createUser(@RequestBody @Validated UserDto userDto) throws ApplicationException {
    User user = modelMapper.map(userDto, User.class);
    userService.signup(user, userDto.getPassword());
  }

  ...

}

2.1. Mapping DTOs to model classes

Although ModelMapper is fairly automatic at finding matching properties, there are situations when it need some manual tweaking. One example for that is the user’s password. This is something we definitely don’t want to expose.

By defining custom property maps, this can be avoided easily.

import org.modelmapper.PropertyMap;

public class UserMap extends PropertyMap<User, UserDto> {

  @Override
  protected void configure() {
    skip().setPassword(null);
  }

}

When the ModelMapper instance gets created at that time, custom property mapping, converters, destination value providers and a couple of other things can be customized.

@Configuration
@EnableWebMvc
public class AuthRestConfiguration extends RestConfiguration {
  
  ...

  @Bean
  public ModelMapper modelMapper() {
    ModelMapper modelMapper = new ModelMapper();
    customizeModelMapper(modelMapper);
    modelMapper.validate();
    return modelMapper;
  }

  @Override
  protected void customizeModelMapper(ModelMapper modelMapper) {
    modelMapper.addMappings(new UserMap());
    modelMapper.addMappings(new UserDtoMap());
  }

  ...
}

2.2. Testing REST controllers

Testing REST controllers with Spring MVC has been made easy since MockMvc was introduced in Spring 3.2.

@RunWith(SpringJUnit4ClassRunner)
@ContextConfiguration(classes = [AuthRestTestConfiguration])
@WebAppConfiguration
class UserControllerTest {

  @Autowired
  WebApplicationContext context

  @Autowired
  UserService userService

  MockMvc mockMvc

  @Before
  void before() {
    mockMvc = MockMvcBuilders.webAppContextSetup(context).build()

    reset(userService)

    when(userService.getUser(0L)).thenThrow(NoSuchUserException)
    when(userService.getUser(1L))
        .thenReturn(new User(1L, "test", "test@craftingjava.com"))
  }

  @Test
  void testGetUser() {
    mockMvc.perform(get("/users/1").contentType(APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(jsonPath("id", is(1)))
        .andExpect(jsonPath("screenName", is("test")))
        .andExpect(jsonPath("contactData.email", is("test@craftingjava.com")))
        .andDo(print())

    verify(userService).getUser(1L)
    verifyNoMoreInteractions(userService)
  }

  ...
}

There are two ways how MockMvc can be built with MockMvcBuilders.
One is to do it through a web app context (as in this example) and the other way is to supply concrete controller instances to standaloneSetup(). I went for the former, because that’s a better fit for testing controllers once Spring Security gets integrated in the next part.

3. Next in this series

Building a user management microservice (Part 5): Authentication with JWT tokens and Spring Security