개발강의정리/Spring

[스프링 기반 REST API 개발] 3-2. 스프링 HATEOAS 적용

nineDeveloper 2020. 1. 1.
728x90

스프링 기반 REST API 개발

3. HATEOAS와 Self-Decribtive Message 적용

포스팅 참조 정보

GitHub

공부한 내용은 GitHub에 공부용 Organizations에 정리 하고 있습니다

해당 포스팅에 대한 내용의 GitHub 주소

실습 내용이나 자세한 소스코드는 GitHub에 있습니다
포스팅 내용은 간략하게 추린 핵심 내용만 포스팅되어 있습니다

https://github.com/freespringlecture/spring-rest-api-study/tree/chap03-02_hateoas_apply

해당 포스팅 참고 인프런 강의

https://www.inflearn.com/course/spring_rest-api/dashboard

실습 환경

  • Java Version: Java 11
  • SpringBoot Version: 2.1.2.RELEASE

2. 스프링 HATEOAS 적용

@EnableEntityLinks, @EnableHypermediaSupport 와 같은 애노테이션들을 사용해야지
HATEOAS를 사용할 수 있는데 스프링 부트가 HATEOAS를 자동으로 다 설정을 해주므로 그냥 사용할 수 있음

EvnetResource 만들기

스프링 HATEOAS에서 링크정보를 지원해주는 기능

  • 이벤트를 이벤트리소스로 변환하여 리소스를 만들어서 밖으로 내보내줌
  • 빈이 아니므로 매번 새롭게 컨버팅 해서 사용해야됨
  • JSON Arraysunwrapped가 안되는 문제가 있음

extends ResourceSupport 의 문제

  • @JsonUnwrapped로 해결:
    • beanSerializerjson으로 만들때 자동으로 객체 변수를 참조하여 만드므로 event로 감싸지는데 wrappingunwrapping 하여 꺼내줌
  • extends Resource<T>로 해결:
    • 이미 @JsonUnwrapped 가 적용되어 있어 코드량을 줄일 수 있음

Content typeapplication/hal+json 이면 클라이언트들이 _links 필드에 링크정보를 들고있겠구나 예상할 수 있음
링크정보를 바탕으로 링크를 Parsing 할 수 있음

셀프링크 자동설정

보통 셀프링크의 경우 EventResource 마다 매번 설정을 해줘야 하므로 EventResource에 추가

package me.freelife.rest.events;

import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;

/**
 * 스프링 HATEOAS를 사용해 이벤트를 이벤트리소스로 변환하여 리소스를 만들어서 밖으로 내보내줌
 */
public class EventResource extends Resource<Event> {

    public EventResource(Event event, Link... links) {
        super(event, links);
        // add(new Link("http://localhost:8080/api/events" + event.getId()));
        // 셀프 링크 생성 위와 동일한 링크
        add(linkTo(EventController.class).slash(event.getId()).withSelfRel());
    }
}

테스트 할 것

응답에 HATEOAprofile 관련 링크가 있는지 확인

  • self (view)
  • update (만든 사람은 수정할 수 있으니까)
  • events (목록으로 가는 링크)

스프링 HATEOAS 적용 코드 작성

1. EventController 에 HATEOAS 적용


...

@Controller
@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_UTF8_VALUE)
public class EventController {

    ...

    @PostMapping
    public ResponseEntity createEvent(@RequestBody @Valid EventDto eventDto, Errors errors) {
        if(errors.hasErrors())
            return ResponseEntity.badRequest().body(errors);

        eventValidator.validate(eventDto, errors);
        if(errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors);
        }

        //EventDto에 있는 것을 Event 타입의 인스턴스로 만들어 달라
        Event event = modelMapper.map(eventDto, Event.class);
        //저장하기 전에 유료인지 무료인지 여부 업데이트
        event.update();
        Event newEvent = this.eventRepository.save(event);
        //EventController의 id에 해당하는 링크를 만들고 링크를 URI로 변환
        //API에 events에 어떤 특정한 ID 그 ID가 생성된 이벤트에 Location Header에 들어감
        ControllerLinkBuilder selfLinkBuilder = linkTo(EventController.class).slash(newEvent.getId());
        URI createdUri = selfLinkBuilder.toUri();
        EventResource eventResource = new EventResource(event); //이벤트를 이벤트리소스로 변환
        eventResource.add(linkTo(EventController.class).withRel("query-events"));
        eventResource.add(selfLinkBuilder.withRel("update-event")); // 셀프 링크와 메서드는 같지만 사용하는 메서드만 다름
        // createdUri 헤더를 가지고 201응답을 만듬
        return ResponseEntity.created(createdUri).body(eventResource);
    }
}

2. 기존 테스트 코드에 HATEOAS 테스트 코드 추가


...

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class EventControllerTests {

    ...

    @Test
    @TestDescription("정상적으로 이벤트를 생성하는 테스트")
    public void createEvent() throws Exception {

        ...

        mockMvc.perform(post("/api/events/")

                ...

                .andExpect(jsonPath("_links.self").exists())
                .andExpect(jsonPath("_links.query-events").exists())
                .andExpect(jsonPath("_links.update-event").exists())

        ;
    }

    ...

}
728x90

댓글

💲 추천 글