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 기반 프로젝트이므로 가져오기 및 그대로 실행하기 쉬워야 합니다.