Spring

[Spring] Restful Api example (with Hateoas) 구현

코리늬 2020. 7. 29. 17:06

환경

소스코드

Member

@Entity
@Table(name = "member")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
@EqualsAndHashCode(of = "id")
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private Grade grade;

    private Member(String name, Integer age, Grade grade){
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
}

MemberRepository

@Transactional(readOnly = true)
@RepositoryRestResource
public interface MemberRepository extends JpaRepository<Member, Long> {

    Member findByName(@Param("name") String name);
}
@RepositoryRestResource
  • 컨트롤러를 만들지 않아도 내부적으로 Rest Api가 만들어진다.

  • 옵션을 추가로 줄 수 있다.

    (collectionResourceRel = "account", path = "account") 옵션을 주게된다면.

    "account": { // collectionResourceRel 에 추가한 속성
          "href": "http://localhost:8080/account{?page,size,sort}",
          "templated": true
    },
    
    path = url path를 말한다 -> 8080/ 뒤에 account
    

MemberHateoas

public class MemberHateoas extends RepresentationModel<MemberHateoas>{

    private final String name;

    private final Integer age;

    //JSON 타입을 받을 JsonCreator 생성자 생성
    @JsonCreator
    public MemberHateoas(@JsonProperty("name") String name, @JsonProperty("age") Integer age) {
        this.name = name;
        this.age = age;
    }
}
@JsonCreator
  • Jackson이 name, age에 대한 POJO 인스턴스를 어떻게 만들지에 대한 정의를 시작하겠다는 의미다.

  • 이때 생성자를 통해 생성하거나 setter를 통해 생성한다. jackson이 해당 함수를 통해 객체를 생성하고 필드를 생성과 동시에 채운다. 이렇게 생성자나 팩토리 메소드를 통해 필드 주입까지 끝내버리면 setter 함수가 필요 없게 된다. jackson을 통해 역직렬화한 immutable한 객체를 얻을 수 있는 것이다.

@JsonProperty
  • 생성자를 포함한 함수는 컴파일 되면 파라미터의 이름이 var0, var1 처럼 임의적으로 바뀐다.
  • 그래서 jackson이 파싱된 key, value를 찾을 수 없기 때문에 JsonProperty를 사용해 명시해줘야한다.

MemberController

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;
    private final MemberRepository memberRepository;

    @GetMapping("/api")
    public EntityModel<Member> insertMember(@RequestParam(value = "name") String name, @RequestParam(value = "age") Integer age, Grade grade){

        Member member = memberService.addMember(name, age, grade);

        EntityModel<Member> memberEntityModel = new EntityModel<>(member);
        memberEntityModel.add(linkTo(methodOn(MemberController.class).insertMember(name, age, grade)).withSelfRel());

        return memberEntityModel;
    }
}
  • linkTo와 methodOn 을 사용해 controller에 가짜 링크 메소드 콜을 만들어 EntityModel에 붙인다(add)
  • withSelfRel()이 Link 인스턴스를 만들어 준다.

MemberService

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;

    public Member addMember(String name, Integer age, Grade grade) {

        Member findMember = memberRepository.findByName(name);

        if(findMember == null){

            Member member = Member.builder()
                    .name(name)
                    .age(age)
                    .grade(grade.GOLD)
                    .build();

            return memberRepository.save(member);
        }

        return findMember;
    }
}

작성을 완료했으니 값이 어떻게 나오는지 출력해보자.

localhost:8080

  • root 경로의 하위에 어떠한 자원이 있는지 알려준다.

    • members를 가지고 있다.
  • 호출방법(/members)을 알려준다.

/memebers

  • 맨 아래 Page 처리 목록이 있다.

    페이징 처리를 한 적이 없는데??

    • MemberRepository의 JpaRepository가 PagingAndSortingRepository를 상속받기 때문에 가능하다.

    • 페이징 파라미터를 추가하면 _links아래 다음과 같이 추가가된다.

    • self를 제외하고, first, prev, last가 추가된 것을 확인 할 수 있다.

     

/members/1

  • 멤버 한 명에 대한 상세조회를 하고 싶다면 이와 같이 하면 된다.

POST 생성 요청

/members/search

  • MemeberRpository에 findByName 쿼리 메소드를 추가했었다.
  • 그로인해 _links 아래 search 가 추가된 것을 볼 수 있다.

클릭을 하면 마찬가지로 사용방법에 대한 정의가 나온다.

자동적으로 쿼리가 바인딩 처리 된 것을 확인 할 수 있다.

/members/search/findByName?name=n1tjrgns 이런식으로 검색을 하면 된다.

샘플 완성

 

P.S

jpa로 hateoas 예제를 작성한김에 spring-guides에 PR을 날려봤으나 일주일째 묵묵부답이다..ㅎ

오픈소스 기여하기는 힘드네

https://github.com/spring-guides/gs-accessing-data-rest/pull/32

 

참고

https://deview.kr/2017/schedule/212

https://spring.io/guides/gs/rest-hateoas/