본문 바로가기

Backend/Spring & SpringBoot

Hypermedia-Driven RESTful Web Services : Spring HATEOAS 구현방법 [2]

728x90

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

represntationModel 상속 관계

- 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를 만들어서 넘겨준다.

이런 구조에서 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 결과


# 참고자료

 

Spring HATEOAS

Commercial support Business support from Spring experts during the OSS timeline, plus extended support after OSS End-Of-Life. Publicly available releases for critical bugfixes and security issues when requested by customers.

spring.io

 

GitHub - spring-projects/spring-hateoas-examples: Collection of examples on how (and why) to build hypermedia-driven apps with S

Collection of examples on how (and why) to build hypermedia-driven apps with Spring HATEOAS - GitHub - spring-projects/spring-hateoas-examples: Collection of examples on how (and why) to build hype...

github.com

 

728x90