[Spring boot] Spring Data Rest를 이용한 REST API 개발 2

지난 포스트에서 Spring Data Rest를 이용해 Domain과 Repository Interface만을 구현하여 HATEOAS에 준수한 REST API 서버를 개발하였습니다.

그러나 REST API를 설계하다보면, HATEOAS의 구조가 마음에 들지 않을 수 있습니다. 예를 들면, 데이터가 비어있을 때 클래스의 정보가 나온다거나, 이 외의 정보를 추가하고 싶다거나, 만약 User 정보를 가져다 준다면, 패스워드 정보와 같은 민감한 정보는 숨겨야할 것입니다.

이번 포스트에서는 Spring Data Rest에 Controller + Service 조합을 넣어서 나만의 REST API를 구현하여 API 서버를 만들어보는 시간을 가져보도록 하겠습니다.

 

RepositroyRestController

지난 포스트에서 사용했던 프로젝트를 이어서 사용하여 여기에 controller 패키지를 추가하여, ItemController 라는 클래스를 만들어보도록 하겠습니다.

Repository에서 사용했던 @RepositroyRestResource를 지우고, Controller에서 @RepositoryRestController를 입력하면, HATEOAS 형태가 갖춰진 응답 형태가 기본적으로 갖춰지게 됩니다. 다만, 이 때 사용하는 어노테이션에서는 @RestController 처럼 @ResponseBody 어노테이션이 삽입되어 있지 않기 때문에, 클래스 위쪽이나 메소드 위쪽에 @ResponseBody 어노테이션을 입력해줘야 합니다.

한 가지 더, 주의해야 할 사항이 있다면, @RepositoryRestController를 사용하게 되면, 매핑하는 URI 형식이 Spring boot data rest에서 정의하는 REST API 형식에 맞아야 합니다. 해당 부분에 대해서는 아래의 글에서 확인하실 수 있습니다.

2020/05/19 - [Programming/Spring] - [Spring boot] REST API의 기초와 설계

 

[Spring boot] REST API의 기초와 설계

Spring boot가 기존의 Spring에 비해 다양한 설정들을 자동화 시켜 개발자가 설정해야 할 부분을 줄이고, 임베디드 톰캣을 탑재하여 더 쉬운 개발들이 가능해졌다는 것을 알았습니다. 이번 포스트에�

blog.neonkid.xyz

기존에 @RepositoryRestResource를 입력하면 제공했던 URI 형식과 같게 제공해야 해당 Controller의 메소드가 기존의 기본 API를 Overriding 하기 때문에, 만약 일부 메소드에서는 기본적으로 제공하는 메소드를 그대로 이용하고 싶다면 Controller에서 해당 메소드를 별도로 구현하지 않으시면 됩니다.

위 코드는 기존의 응답 형태에서 많은 URL를 제공했었는데, 해당 URL 중에서 요청한 URL을 제외한 나머지 URL을 제거하고 클라이언트에게 응답해주는 코드입니다.

PageMetadata 객체를 이용해 전체 페이지 수와 현재 페이지 번호, 총 상품 수 드의 페이지 정보를 담고, PagedModel을 이용하여 컬렉션 페이지의 리소스 정보를 추가적으로 제공해주도록 합니다. 마지막으로 필요한 링크를 linkTo 메소드를 이용하여 요청한 각각의 상품의 링크만을 나타낼 수 있도록 selfRel 메소드를 이용하였습니다.

그러면 이렇게, self URL을 제외한 나머지 URL은 표시가 안되서 좀 더 깔끔한 결과값이 나오게 되는 것이죠. 이렇게 여러분들이 원하는 Result 클래스를 만들거나 원하는 응답값을 정할 때는 Controller를 구현하여, REST API URI 형식에 맞추면 이미 구현되어 있는 Data Rest에서 Overriding 되어, 작동하는 것을 알 수 있습니다.

 

 

JsonIgnore

상품 모델말고, 다른 모델에 대해서도 한 번 다뤄보겠습니다. 만약 상품 모델을 만들었고, 상품을 구입하기 위해서는 사용자가 있어야 합니다. 사용자에 대한 모델을 한 번 만들어보도록 하겠습니다.

User Entity를 설계할 때는 여러가지 방법이 있습니다. User마다 PK를 주고, Unique 속성을 넣어, ID의 중복을 방지하는 방법, 혹은 User ID를 PK로 설정하는 방법 등이 있는데요. 여기서는 User마다 PK를 주는 방식으로 Entity를 구성하였습니다.

Controller와 Repository는 Item 클래스와 똑같이 구현하시면 됩니다.

$ curl -X POST -H "Content-Type: application/json" --data '{ "userid": "neonkid", "password": "1234" }' http://localhost:8080/api/users | json_pp

POST 메소드를 사용해 사용자 한 개를 등록하게 되면, Item API처럼 똑같이 결과값이 모델 형태의 JSON으로 변환되어 출력이 됩니다. 

그런데, GET 메소드를 이용해서 사용자를 조회했을 때, 패스워드도 같이 출력이 됩니다. 보통 사용자의 비밀번호는 서버에서 사용자의 식별을 위해 사용되어야 하고, 그 이외에는 사용할 수 없어야 합니다. 물론 DB에서 직접 조회하는 방법도 있겠지만, REST API의 경우, 외부에서 사용할 수 있는 포인트 지점이 되기 때문에 이러한 정보는 주지 않는 것이 좋겠죠?

JsonIgnore의 사용은 간단합니다. 아까 만든 User 도메인 클레스에서 Password 멤버 변수에 JsonIgnore만을 추가해주면 끝입니다.

그러면 이렇게 Password 부분은 제외하고, 클라이언트에게 던져주고 있음을 알 수 있습니다.

 

 

Event Binding

사용자의 추가/수정은 한 서비스에 있어, 중요한 사항입니다. 새로운 고객이 들어왔다는 것이 될 수도 있고, 고객의 정보를 변경했다는 점의 기록을 남길 수 있다면 좋겠죠.

그러나 클라이언트에서 제공하는 현재 시간이 맞지 않을 수도 있고, 네트워크 지연으로 시간이 달라질 수도 있기 때문에 해당 작업을 서버에서 해준다면, 좀 더 정확하고, 확실하게 정보 기록으로 남을 것입니다.

Spring boot data rest에서는 여러 메소드의 이벤트 발생 시점을 후킹하여 원하는 데이터를 검사하여, 이를 행위로 만들 수 있는 이벤트 어노테이션을 제공합니다.

  • BeforeCreateEvent
  • AfterCreateEvent
  • BeforeSaveEvent
  • AfterSaveEvent
  • BeforeDeleteEvent
  • AfterDeleteEvent
  • BeforeLinkSaveEvent
  • AfterLinkSaveEvent
  • BeforeLinkDeleteEvent
  • AfterLinkDeleteEvent

간단히 설명을 드리면, Create는 새로운 데이터를 생성했을 때의 이벤트, Before와 After는 해당 이벤트가 발생하기 전과 후로 바인딩 됩니다. Save는 기존 데이터의 업데이트, Link의 경우 관계(1:1, N:N)를 가진 링크를 수정/삭제했을 때의 이벤트를 말합니다.

먼저 이벤트를 받을 클래스를 한 개 만들어줍니다. 이 때, 클래스를 이름은 어떤 Entity의 이벤트를 바인딩할지를 구분하기 쉽게 하기 위해 도메인 모델 + EventHandler라는 이름으로 설정해줍니다. 어차피 메소드에 있는 파라미터로 구분짓는 것이 가장 빠르겠지만, 코드를 보기 전에 이 코드는 무엇을 하는 역할이다. 라고 표현해주는 것이 가장 좋겠습니다.

@RepositoryEventHandler는 그저 이벤트 핸들링만 해주는 어노테이션이므로 해당 Event Handler를 이용하기 위해 Spring IoC에 의존성을 주입해줘야만 동작하므로 @Component, @Bean 등을 이용하여 의존성 주입을 해주도록 합시다.

이렇게 POST 메소드로 사용자 등록을 시도하면, 위와 같이 EventHandler 클래스에서 구현한대로, Log가 찍히는 것을 알 수 있습니다.

 

 

마치며...

Spring Data Rest를 이용한 REST API는 가능한 REST API의 표준 중 URI 부분을 지켜주면서, 이에 대한 반환 값을 커스터마이징 하기 용이하다는 느낌을 받았습니다. 약간의 강제성이 부여되어 있어, 개발하는 데 불편함을 느낄 수도 있겠지만 어떻게 보면, 개발자 입장에서 큰 규모의 서비스를 개발/운영할 때 유지보수를 좋게 하기 위한 목적이라고 생각한다면, 그리 나쁘지 않은 선택일 수도 있을 것 같습니다.

comments powered by Disqus

Tistory Comments 0