Spring에서 Hateoas를 구현하는 방법에 대해서 알아보겠다.
## 환경 : Spring Boot + IntellJ + Maven
0) Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
1) Link 를 만들기 위하여 WebMvcLinkBuilder 활용
Link link = new Link("http://localhost:8080/api/actors/1");
// 링크를 만들때 위와 같이 하드코딩을 하지 않도록 WebMvcLinkBuilder를 사용
// # WebMvclinkBuilder를 사용하여 링크를 만든다.
// Link Object에는 rel와 href를 넣는다.
// rel : 이름 / href : 실제 링크
// linkTo() : methodOn에 컨트롤러 이름을 주고, 그 컨트롤러 메서드 이름을 준다.
// withSelfRes() : self를 주려면 이렇게 / 아니면 .withRel("~") 이렇게 준다.
Link lnk = WebMvcLinkBuilder
.linkTo( methodOn(WebController.class).getAllAlbums( ) )
.withSelfRel(); // .withRel("albums");
2) Representation models
- Spring Hateoas에서 여러가지 class를 제공, 제일 상단에 존재하는 Representation Model을 사용하여 Representation을 만든다.
- Representation Model는 links를 추가하기 위한 다양한 메서드를 가지고 있음. 또한 links를 가지고 있는 컨테이너 라고 보면 된다.
# 코드예시 : Entity & Model
## ActorEntity
@Entity
public class ActorEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String birthDate;
@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(
name = "actor_album",
joinColumns = @JoinColumn(name = "actor_id"),
inverseJoinColumns = @JoinColumn(name = "album_id"))
private List<AlbumEntity> albums;
}
## ActorModel => DTO로 사용
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Relation(collectionRelation = "actors", itemRelation = "actor")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ActorModel extends RepresentationModel<ActorModel>
{
private Long id;
private String firstName;
private String lastName;
private String birthDate;
private List<AlbumModel> albums;
}
- ActorEntity는 Entity로 그대로 사용.
- ActorModel은 RepresentationModel을 상속받아서 사용하기 때문에 링크추가 이건, DTO로 사용함.
# 참고 : DTO (Data Transfer Object)란?
- Presentation layer 와 Business Layer와 전달해주는 거. Business 로직이 담긴 Entity다 보내줄 필요없으니깐 DTO를 만들어서 넘겨준다.
3) JPA entity를 DTO로 변환시켜주는 역할을 해주는 "Representation model Assembers"
- RepresentationModelAssembler 인터페이스를 구현한 구현체는 RepresentationModelAssemblerSupport 이다
- Spring에서 제공해주는 것...
- toModel() : 하나의 리소스 바꿀때, toCollectionModel() : 하나 이상일때 사용
## ActorModelAssembler
@Component
public class ActorModelAssembler extends RepresentationModelAssemblerSupport<ActorEntity, ActorModel> {
public ActorModelAssembler() {
super(WebController.class, ActorModel.class);
}
// 1. toModel : 하나일때~
@Override
public ActorModel toModel(ActorEntity entity) {
ActorModel actorModel = instantiateModel(entity); // 인스턴스생성
actorModel.add( linkTo(methodOn(WebController.class) // link를 추가해준다.
.getActorById(entity.getId()))
.withSelfRel() );
actorModel.setId(entity.getId()); // setter사용해서 copy한다.
actorModel.setFirstName(entity.getFirstName());
actorModel.setLastName(entity.getLastName());
actorModel.setBirthDate(entity.getBirthDate());
actorModel.setAlbums(toAlbumModel(entity.getAlbums()));
return actorModel;
}
//2. toCollectionModel :하나 이상일때, ActorEntity를 ActorModel로 변경해준다.
@Override
public CollectionModel<ActorModel> toCollectionModel(Iterable<? extends ActorEntity> entities){
CollectionModel<ActorModel> actorModels = super.toCollectionModel(entities);
actorModels.add(linkTo(methodOn(WebController.class)
.getAllActors())
.withSelfRel());
return actorModels;
}
}
4) Controller
- /api/actors : 모든 actor 조회
- /api/actors/{id} : 특정 아이디를 가진 actor조회
@RestController
public class WebController {
@Autowired
private ActorRepository actorRepository;
@Autowired
private ActorModelAssembler actorModelAssembler;
@GetMapping("/api/actors")
public ResponseEntity<CollectionModel<ActorModel>> getAllActors() {
List<ActorEntity> actorEntities = (List<ActorEntity>) actorRepository.findAll();
return new ResponseEntity<>(
actorModelAssembler.toCollectionModel(actorEntities),
HttpStatus.OK);
}
@GetMapping("/api/actors/{id}")
public ResponseEntity<ActorModel> getActorById(@PathVariable("id") Long id){
return actorRepository.findById(id)
.map(actorModelAssembler::toModel)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
5) http://localhost:8080/api/actors/1 : response 결과
# 참고자료
'Backend > Spring & SpringBoot' 카테고리의 다른 글
[스프링 인 액션 3장] Spring JDBC 사용 (0) | 2022.05.22 |
---|---|
Spring Boot Test하기 [1] (0) | 2022.05.08 |
Hypermedia-Driven RESTful Web Services : HATEOAS란? [1] (0) | 2022.04.18 |
[Spring Security] Spring Boot에서 Spring Security 설정하기 (Database) (0) | 2022.04.13 |
[Spring Security] Spring Boot에서 Spring Security 설정하기 (in-memory) (0) | 2022.04.12 |