[Spring] Spring HATEOAS란? – EntityModel, ModelAssembler 구현
by 우와한개발자
1. HATEOAS(Hypermedia as the Engine of Application State)란?
- 서버가 클라이언트에게 리소스 상태와 함께 관련된 링크를 포함한 응답을 반환
- 클라이언트 디커플링(Decoupling) 강화 : URL을 하드코딩하지 않아도 됨
- 유지보수성 개선 : URL이 바뀌어도 클라이언트 코드 수정 불필요
- 표준화 : API 사용법을 응답 자체에서 파악 가능
// 일반 REST 응답
{ "id": 1, "name": "홍길동" }
// HATEOAS 응답 → 다음에 할 수 있는 행동(링크)을 함께 제공
{
"id": 1,
"name": "홍길동",
"_links": {
"self": { "href": "/v1/users/1" },
"update": { "href": "/v1/users/1" },
"delete": { "href": "/v1/users/1" },
"users": { "href": "/v1/users" }
}
}
2. Spring HATEOAS 주요 클래스
| 클래스 | 설명 |
| EntityModel<T> | 단일 리소스 + 링크를 감싸는 모델 |
| CollectionModel<T> | 컬렉션 리소스 + 링크를 감싸는 모델 |
| RepresentationModelAssembler | Entity → EntityModel 변환 담당 |
| WebMvcLinkBuilder | 링크 생성 빌더 (linkTo, methodOn) |
3. Spring HATEOAS 구현
의존성 추가
↓
ModelAssembler 생성 (Entity → EntityModel 변환 + 링크 추가)
↓
Controller 수정 (ModelAssembler 적용)
1) pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
2) ModelAssembler 생성
// assembler/UserModelAssembler.java
@Component
public class UserModelAssembler
implements RepresentationModelAssembler<UserDto, EntityModel<UserDto>> {
@Override
public EntityModel<UserDto> toModel(UserDto dto) {
return EntityModel.of(dto,
// self : 자기 자신 링크
linkTo(methodOn(UserController.class).getUserById(dto.getId())).withSelfRel(),
// update : 수정 링크
linkTo(methodOn(UserController.class).updateUser(dto.getId(), null)).withRel("update"),
// delete : 삭제 링크
linkTo(methodOn(UserController.class).deleteUser(dto.getId())).withRel("delete"),
// users : 전체 목록 링크
linkTo(methodOn(UserController.class).getAllUsers()).withRel("users")
);
}
}
3) Controller 수정
@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserModelAssembler assembler; // 어셈블러 주입
// 단건 조회 → EntityModel로 감싸서 반환
@GetMapping("/{id}")
public ResponseEntity<EntityModel<UserDto>> getUserById(@PathVariable Long id) {
UserDto dto = userService.getUserById(id);
return ResponseEntity.ok(assembler.toModel(dto)); // 어셈블러 적용
}
// 전체 조회 → CollectionModel로 감싸서 반환
@GetMapping
public ResponseEntity<CollectionModel<EntityModel<UserDto>>> getAllUsers() {
List<EntityModel<UserDto>> users = userService.getAllUsers()
.stream()
.map(assembler::toModel)
.collect(Collectors.toList());
return ResponseEntity.ok(
CollectionModel.of(users,
linkTo(methodOn(UserController.class).getAllUsers()).withSelfRel())
);
}
}
4) 응답 결과
// GET /v1/users/1
{
"id": 1,
"name": "홍길동",
"email": "hong@example.com",
"_links": {
"self": { "href": "<http://localhost:8080/v1/users/1>" },
"update": { "href": "<http://localhost:8080/v1/users/1>" },
"delete": { "href": "<http://localhost:8080/v1/users/1>" },
"users": { "href": "<http://localhost:8080/v1/users>" }
}
}
'프레임워크 > Spring' 카테고리의 다른 글
블로그의 정보
우와한개발자 님의 블로그
우와한개발자