1. 개요

이 기사는 Spring REST 서비스에서 발견 가능성구현 과 HATEOAS 제약 조건 충족에 초점을 맞출 것 입니다.

이 기사는 Spring MVC에 초점을 맞춥니다. 우리의 기사  An Intro to Spring  HATEOAS는 Spring Boot에서 HATEOAS를 사용하는 방법을 설명합니다.

2. 이벤트를 통한 발견 가능성 분리

웹 계층의 별도 측면 또는 관심사로서의 검색 가능성 은 HTTP 요청을 처리하는 컨트롤러와 분리되어야 합니다 . 이를 위해 컨트롤러는 응답을 추가로 조작해야 하는 모든 작업에 대해 이벤트를 발생시킵니다.

먼저 이벤트를 생성해 보겠습니다.

public class SingleResourceRetrieved extends ApplicationEvent {
    private HttpServletResponse response;

    public SingleResourceRetrieved(Object source, HttpServletResponse response) {
        super(source);

        this.response = response;
    }

    public HttpServletResponse getResponse() {
        return response;
    }
}
public class ResourceCreated extends ApplicationEvent {
    private HttpServletResponse response;
    private long idOfNewResource;

    public ResourceCreated(Object source, 
      HttpServletResponse response, long idOfNewResource) {
        super(source);

        this.response = response;
        this.idOfNewResource = idOfNewResource;
    }

    public HttpServletResponse getResponse() {
        return response;
    }
    public long getIdOfNewResource() {
        return idOfNewResource;
    }
}

그런 다음 컨트롤러는 2 개 간단한 작업으로 - id로 찾기생성 :

@RestController
@RequestMapping(value = "/foos")
public class FooController {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private IFooService service;

    @GetMapping(value = "foos/{id}")
    public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
        Foo resourceById = Preconditions.checkNotNull(service.findOne(id));

        eventPublisher.publishEvent(new SingleResourceRetrieved(this, response));
        return resourceById;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void create(@RequestBody Foo resource, HttpServletResponse response) {
        Preconditions.checkNotNull(resource);
        Long newId = service.create(resource).getId();

        eventPublisher.publishEvent(new ResourceCreated(this, response, newId));
    }
}

그런 다음 분리된 리스너의 수에 관계없이 이러한 이벤트를 처리할 수 있습니다. 이들 각각은 고유한 특정 사례에 초점을 맞추고 전체 HATEOAS 제약 조건을 충족하는 데 도움이 될 수 있습니다.

리스너는 호출 스택의 마지막 객체여야 하며 직접 액세스할 필요가 없습니다. 따라서 그들은 공개되지 않습니다.

3. 새로 생성된 리소스의 URI를 검색 가능하게 만들기

에서 설명하고있는 바와 같이 HATEOAS에 이전 게시물 , 새로운 자원을 만드는 작업은에 해당 리소스의 URI를 반환해야 위치 HTTP 헤더 응답.

리스너를 사용하여 이를 처리합니다.

@Component
class ResourceCreatedDiscoverabilityListener
  implements ApplicationListener<ResourceCreated>{

    @Override
    public void onApplicationEvent(ResourceCreated resourceCreatedEvent){
       Preconditions.checkNotNull(resourceCreatedEvent);

       HttpServletResponse response = resourceCreatedEvent.getResponse();
       long idOfNewResource = resourceCreatedEvent.getIdOfNewResource();

       addLinkHeaderOnResourceCreation(response, idOfNewResource);
   }
   void addLinkHeaderOnResourceCreation
     (HttpServletResponse response, long idOfNewResource){
       URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().
         path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri();
       response.setHeader("Location", uri.toASCIIString());
    }
}

이 예에서 우리가 사용하고있는 ServletUriComponentsBuilder 현재 요청을 사용하여 도움 -. 이렇게 하면 아무 것도 전달할 필요가 없으며 단순히 정적으로 액세스할 수 있습니다.

API가 ResponseEntity를 반환 하면 위치 지원을 사용할 수도 있습니다 .

4. 단일 리소스 얻기

단일 리소스 를 검색할 때 클라이언트는 해당 유형의 모든 리소스를 가져오기 위해 URI를 검색할 수 있어야 합니다.

@Component
class SingleResourceRetrievedDiscoverabilityListener
 implements ApplicationListener<SingleResourceRetrieved>{

    @Override
    public void onApplicationEvent(SingleResourceRetrieved resourceRetrievedEvent){
        Preconditions.checkNotNull(resourceRetrievedEvent);

        HttpServletResponse response = resourceRetrievedEvent.getResponse();
        addLinkHeaderOnSingleResourceRetrieval(request, response);
    }
    void addLinkHeaderOnSingleResourceRetrieval(HttpServletResponse response){
        String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri().
          build().toUri().toASCIIString();
        int positionOfLastSlash = requestURL.lastIndexOf("/");
        String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash);

        String linkHeaderValue = LinkUtil
          .createLinkHeader(uriForResourceCreation, "collection");
        response.addHeader(LINK_HEADER, linkHeaderValue);
    }
}

링크 관계의 의미는 여러 마이크로포맷 에서 지정되고 사용 되지만 아직 표준화되지 않은 "컬렉션" 관계 유형을 사용 합니다.

링크 헤더는 대부분의 HTTP 헤더를 사용 중 하나 인 검색 기능의 목적을 위해. 이 헤더를 만드는 유틸리티는 충분히 간단합니다.

public class LinkUtil {
    public static String createLinkHeader(String uri, String rel) {
        return "<" + uri + ">; rel=\"" + rel + "\"";
    }
}

5. 루트에서 발견 가능성

루트는 전체 서비스의 진입점입니다. 클라이언트가 API를 처음 사용할 때 접하게 되는 것입니다.

HATEOAS 제약 조건이 전체적으로 고려되고 구현되어야 하는 경우 여기에서 시작해야 합니다. 따라서  시스템의 모든 주요 URI는 루트에서 검색할 수 있어야 합니다.

이제 이에 대한 컨트롤러를 살펴보겠습니다.

@GetMapping("/")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) {
    String rootUri = request.getRequestURL().toString();

    URI fooUri = new UriTemplate("{rootUri}{resource}").expand(rootUri, "foos");
    String linkToFoos = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection");
    response.addHeader("Link", linkToFoos);
}

물론 이것은 Foo Resources 에 대한 단일 샘플 URI에 초점을 맞춘 개념의 예시입니다 . 실제 구현은 마찬가지로 클라이언트에 게시된 모든 리소스에 대한 URI를 추가해야 합니다.

5.1. 검색 가능성은 URI 변경에 관한 것이 아닙니다.

이것은 논란의 여지가 있는 지점일 수 있습니다. 한편 HATEOAS의 목적은 클라이언트가 API의 URI를 발견하도록 하고 하드코딩된 값에 의존하지 않도록 하는 것입니다. 반면에 – 이것은 웹이 작동하는 방식이 아닙니다. 예, URI가 검색되지만 책갈피도 됩니다.

미묘하지만 중요한 차이점은 API의 진화입니다. 이전 URI는 여전히 작동해야 하지만 API를 발견할 클라이언트는 새 URI를 발견해야 합니다. 이를 통해 API는 동적으로 변경되고 좋은 클라이언트는 다음과 같은 경우에도 잘 작동합니다. API 변경 사항.

결론적으로 - 평온한 웹 서비스의 모든 URI를 고려해야한다해서 c를 OOL의 URI를 (그리고 URI를 냉각 변화를하지 않는 ) - 즉, API를 진화 할 때 HATEOAS 제약 조건을 준수하는 것이 매우 유용하지 않습니다 것을 의미하지 않는다.

6. 발견 가능성에 대한 주의 사항

이전 기사에 대한 일부 논의에서 언급했듯이 검색 가능성의 첫 번째 목표는 문서를 최소한으로 사용하거나 전혀 사용하지 않고 클라이언트가 응답을 통해 API를 사용하는 방법을 배우고 이해하도록 하는 것입니다.

사실, 이것은 문서 없이도 우리가 모든 새 웹 페이지를 사용하는 방식인 아주 먼 이야기로 간주되어서는 안 됩니다. 따라서 REST의 맥락에서 개념이 더 문제가 되는 경우 가능한지 여부의 문제가 아니라 기술 구현의 문제여야 합니다.

하지만 기술적으로 우리는 아직 완벽하게 작동하는 솔루션과는 거리가 멉니다. 사양과 프레임워크 지원은 여전히 ​​발전하고 있기 때문에 약간의 타협을 해야 합니다.

7. 결론

이 기사에서는 Spring MVC를 사용하는 RESTful 서비스의 컨텍스트에서 발견 가능성의 일부 특성 구현을 다루었고 루트에서 발견 가능성의 개념을 다루었습니다.

이 모든 예제와 코드 스니펫의 구현은 GitHub 에서 찾을 수 있습니다. 이것은 Maven 기반 프로젝트이므로 가져오기 및 그대로 실행하기 쉬워야 합니다.

REST footer banner