본문 바로가기

개발(합니다)/Java&Spring

Spring HATEOAS 특징과 설정 정리

반응형

REST API의 단점 


1. 엔드 포인트 URL 변경 시 모두 수정해야 합니다.

API의 엔드 포인트 URL이 정해지고 나면 변경하기가 어렵습니다.

API의 URL이 변경하면 모든 클라이언트가 똑같이 수정해주어야 합니다. 


2. 자원의 상태를 고려하지 않습니다.

작업을 수행이 가능한지 여부를 판단하는 로직이 클라이언트에도 포함되어 있어야 합니다.



이러한 단점을 보완한 방식이 HATEOAS입니다.



HATEOAS(Hypermedia As The Engine Of Application state)란

하이퍼 미디어 어플리케이션의 상태를 관리학 위한 매커니즘으로 사용하는 아이디어입니다.

LINK에 사용 가능한 URL을 리소스로 전달하여 클라이언트가 참고하여 사용 할 수 있도록 합니다.







참고


레벨 0. REST 도입 전

모든 전송과 응답을 POST로 하며 접근 가능한 엔드 포인트는 하나입니다.

HTTP의 body에 정보를 넣어 전송하는 기존의 리소스 전송 방식을 사용합니다.


레벨 1. 리소스 도입

REST를 도입하며 고유의 URI로 각각의 제공하는 자원을 주고 받습니다.

모든 자원을 제공함으로써 클라이언트는 다양한 자원과 포맷을 제공 받을 수 있습니다.

ex) JSON, XML


리소스를 어떻게 나누고 합칠 것인지를 고려해야 합니다.



레벨 2. HTTP

URL과 HTTP method를 적극적으로 활용합니다.

해당 HTTP method로 어떻게 요청 하든 같은 응답을 받을 수 있습니다.

에러가 발생하면 클라이언트에서 에러를 처리했던 방식에서 상태 코드를 활용하여 대처하기 대처합니다.

  -> 에러 시 서버가 해당 페이지로 이동했다면 지금은 클라이언트에서 상태 코드를 보고 해결합니다.


가장 널리 알려진 방법입니다.


method의 표준을 도입하고 에러 상황에 대한 상태 코드 전달과 대처를 고려해야 합니다.


레벨 3. Hypermedia Controls

하이퍼 미디어의 링크를 이용하는 방식입니다. 

서버가 클리이언트에게 자원을 보내면서 다음 작업을 할 수 있는 URL을 링크로 같이 보냅니다.

클라이언트는 링크를 확인하고 다음 작업을 할 수 있는 URL을 확인합니다.

레벨3으로 REST API의 URL 변경시 단점을 해결 할 수 있습니다.


어떻게 전달해야 링크를 보고 리소스를 찾아갈 수 있는 문서가 될 수 있는지를 고려해야 합니다.






스프링 HATEOAS 사용 방법

Spring maven 프로젝트로 진행하지 않고 Spring MVC 프로젝트로 구현했습니다.


pom.xml

<dependency>
            <groupId>org.springframework.hateoas</groupId>
            <artifactId>spring-hateoas</artifactId>
            <version>0.25.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.7</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.7</version>
        </dependency>

jackson 버전과 spring버전이랑 맞쳐주어야 한다.


Person.java

package com.otrodevym.test.hateoas;

import org.springframework.hateoas.ResourceSupport;

public class Person extends ResourceSupport {
    private int personId;
    private String name;
    private String address;

    public Person() {
        // TODO Auto-generated constructor stub
    }

    public Person(int personId, String name, String address) {
        super();
        this.personId = personId;
        this.name = name;
        this.address = address;
    }

    public int getPersonId() {
        return personId;
    }

    public void setPersonId(int personId) {
        this.personId = personId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
}
}


PersonService.java

package com.otrodevym.test.hateoas;


public interface PersonService {
    public Person getpersonDetail(String personId);
}


PersonServiceImpl.java

package com.otrodevym.test.hateoas;

import org.springframework.stereotype.Service;

@Service
public class PersonServiceImpl implements PersonService{

    @Override
    public Person getpersonDetail(String personId) {
        return new Person(1, "kim1", "서울");
    }
    
}


PersonController.java

package com.otrodevym.test.hateoas;

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

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/hateoas")
public class HateoasController {

    @Autowired
    private PersonService service;

    @GetMapping(value = "/{personId}")
    public ResponseEntity<Person> getPersonById(@PathVariable String personId) {
        Person person = service.getPersonDetail(personId);
        Link link = new Link("http://localhost:8080/spring-hateoas-test");
        Link self = linkTo(HateoasController.class).slash(person.getPersonId()).withSelfRel();
        Link job = linkTo(HateoasController.class).slash(person.getPersonId()).withRel("job");
        Link home = linkTo(HateoasController.class).slash(person.getPersonId()).withRel("home");
        person.add(link);
        person.add(self);
        person.add(job);
        person.add(home);
        System.out.println(person);
        return new ResponseEntity<Person>(person, HttpStatus.OK);
    }
}







반응형