REST API Level3을 위한 HATEOAS 설정

 

Hateoas란?

HATEOAS(Hypermedia As The Engine of Application State)는 웹 API를 실제로 "RESTful"로 만드는 REST 애플리케이션 아키텍처의 제약 조건입니다. 기본적으로 요청에 대해 서버는 데이터만 클라이언트에 보냅니다. HATEOAS를 사용하면 응답에 데이터뿐만 아니라 해당 데이터와 관련된 가능한 작업도 링크 형식으로 포함됩니다.

 

Leonard Richardson이 제시한 REST 성숙도 모델

출처: https://grapeup.com/blog/how-to-build-hypermedia-api-with-spring-hateoas

 

- 레벨 0

API 구현은 HTTP 프로토콜을 사용하지만 전체 기능을 활용하지는 않습니다. 또한 리소스에 대한 고유 주소가 제공되지 않습니다.

method : POST / URI : /movie

 

- 레벨 1

리소스에 대한 고유 식별자가 있지만 리소스에 대한 각 작업자에는 고유한 URL이 있습니다

method : POST / URI : /movie/1/delete

 

- 레벨 2

동작을 설명하는 동사 대신 HTTP 메소드를 사용합니다. 예를 들어 레벨 1처럼 URI에 delete를 표기하여 작업자를 나타내지않고, 대신 delete 메소드를 사용합니다.

method : DELETE / URI : /movie/1

 

- 레벨 3

HATEOAS라는 용어가 도입됨. 간단히 리소스에 하이퍼미디어를 도입합니다. 이를 통해 가능한 작업에 대해 알려주는 응답에 링크를 배치할 수 있으므로 API를 통해 탐색할 수 있는 가능성이 추가됩니다.

method : DELETE / URI : /movie/1

 

 

Level2에서는 단순히 데이터 영역만을 표기하여 응답해줍니다. Level3의 Hypermedia Controls 부터는 데이터 영역 뿐만 아니라 링크 영역을 통해 자원에 호출 가능한 API 정보를 반영하여 표현합니다. 링크영역에서 반영 되는 개념이 바로 HATEOAS 입니다. 

{
    ----- 데이터 영역 -----
    "id": 1,
    "name": "Kenneth",
    "joinDate": "2024-08-17T12:58:26.575+00:00",
    ----- 데이터 영역 -----
    
    ----- 링크 영역 -----
    "_links": {
        "self": {
            "href": "http://localhost:8088/users/1"
        },
        "all-users": {
            "href": "http://localhost:8088/users"
        }
    }
    ----- 링크 영역 -----
}

 

HATEOAS 프로젝트 설정 및 구현

 

 

▶ pom.xml (build.gradle)

## Maven
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-hateoas</artifactId>  
</dependency>

## Gradle
implementation 'org.springframework.boot:spring-boot-starter-hateoas'

 

먼저 프로젝트에 Hateoas를 추가해 줍니다.

 

▶ UserController.java

@GetMapping("/users/{id}")
    public ResponseEntity<EntityModel<User>> retrieveUser(
           @Parameter(description = "사용자 ID", required = true, example = "1") @PathVariable int id
    ) {
        User user = userDaoService.findOne(id);
		
        // 사용자가 없을 경우, 예외를 발생시킨다.
        if( user == null ) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }

        // 단일로 link를 만들때
        // EntityModel entityModel = EntityModel.of(user);
        // WebMvcLinkBuilder linTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
        // entityModel.add(linTo.withRel("all-users"));    // http://localhost:8080/users -> all-users

		// 다수로 link를 만들때
        return ResponseEntity.ok().body(
                EntityModel.of(user)
                        .add(linkTo(methodOn(this.getClass()).retrieveUser(id)).withSelfRel()) // http://localhost:8080/users/1 -> self
                        .add(linkTo(methodOn(this.getClass()).retrieveAllUsers()).withRel("all-users")) // http://localhost:8080/users -> all-users
        );
    }

 

EntityModel<T> 클래스를 이용하고, Static Method로 객체를 만들기 때문에 EntityModel.of()를 통해서 객체르 생성합니다.

add()메소드를 통해서 link를 추가할 수 있습니다. linkTo(methodOn(Controller.class).method(argument)) 이런 형식으로 추가하면 API의 URI가 매핑됩니다.

link에 대한 이름은 withSelfRel()과 withRel()이 있습니다.

withSelfRel() 메소드는 self로 지정되는데 호출되는 자기 자신에 대한 정보를 표현합니다.

withRel()는 withRel("명칭") 형태로 사용하며 매개변수로 지정된 값이 이름으로 표현됩니다.

 

▶ 실행결과

{
    "id": 1,
    "name": "Kenneth",
    "joinDate": "2024-08-20T14:37:49.307+00:00",
    "_links": {
        "self": {
            "href": "http://localhost:8088/users/1"
        },
        "all-users": {
            "href": "http://localhost:8088/users"
        }
    }
}

 

Hateoas를 적용하여 실행한 결과 입니다. 다음과 같이 _links에 호출 가능한  API 정보를 반영 되었습니다. 

이번 포스팅은 간단하게 Hateoas 적용 방법을 정리해 봤습니다.

해당 Full Source는 아래 Git 주소를 통해서 확인 가능합니다. 여기까지 지루한 글 읽어주셔서 감사합니다!

 

https://github.com/LuckyStrike1989/restful-web-service

 

 

'BackEnd > Spring&SpringBoot' 카테고리의 다른 글

[Spring] SiteMesh란?  (0) 2025.04.05
[Spring] Validation API 유효성 체크  (0) 2024.08.04

+ Recent posts