Spring - 국제화, Json 필터링, HATEOS, HAL, RestAPI 버전관리

JungWooLee·2022년 9월 24일
0

Spring

목록 보기
4/5
post-thumbnail

1. 국제화

Accept language header 를 통해 국제화 -i18n 을 해보자

만들어진 RestAPI 가 전세계 사용자들이 사용한다고 하였을 때 customize 하려면 어떻게 해야할까?

✔ : i18n(18개의 언어 Internationalization)

  • Accept-Language 를 통하여 i18n 을 사용
  • Accept-Language 는 사용자가 선호하는 언어와 지역을 나타냄

가령 다음과 같은 메시지를 반환하는 컨트롤러에 국제화를 적용해보고자 한다면?

Spring 과 부트에서 국제화를 다루는 정석적인 방법은 파일들을 messages.properties 로 정의하는것이다


다음과 같이 리소스 파일 안에 messages.properties 를 생성한다 (파일명이 틀리면 스프링이 인식하지 못함)

프로퍼티에서 적용한 메시지를 사용하고자 한다면 MessageSource 를 사용해주어야한다
이는 메시지의 파라미터화, 국제화를 지원하는 인터페이스이다

MessageSource 에서 메시지를 받아오고자 할 때 getMessage 를 통하여 메시지를 받아올 수 있다

Args : any variables to replace
Default Message : 기본 메시지
Locale : 사용자 로케일 정보

  • 만약 사용자가 Accept header 를 주었다면 해당 로케일 정보가 locale 에 담김


Properties 에서 정의한 메시지가 잘 나오는 것을 확인

이제 다음 언어로 넘어가 국제화를 진행, messages_{i18n코드}.properties 를 생성

한글이 깨져요?

  1. Configuration을 통한 메시지소스의 set Encoding 을 UTF-8 으로 바꾸기
  2. 헤더의 Accept-Charset 을 통하여 UTF-8로 설정해보기
  3. 파일 인코딩 변경
  4. application.properties 에서 인코딩 설정하기

저의 경우 전역 인코딩은 UTF-8 로 되어있지만 프로퍼티 파일에 대한 인코딩은 ISO-8859-1 이여서 인코딩에 실패하였었던 것


2. RestAPI 버전관리

만약 RestAPI 의 대폭적인 변화가 이루어졌을 때
→ 버전 업

  • 즉각적으로 버전업된 rest api를 배포한다 ?
    → 이미 사용중인 유저는 올바른 response를 받지 못함
  • Api 에 변화가 이루어졌을 때 사용자들에게 버전업을 강요해서는 안된다
    이러한 이유 때문에 rest API의 버전관리가 필요하다. 사용자가 새로운 버전에 맞게 변화할 수 있도록 버전을 통해 관리한다

버전 관리의 방법
1. url 의 변경 (ex: api/v1/user → api/v2/user)
2. Request Parameter
3. Header
4. Media Type

1. 먼저 url 의 변경을 통한 버전 관리를 알아보자 (Twitter)

V2 에서는 Name 이라는 객체를 추가적으로 생성하여 성과 이름을 구분할 수 있도록 하였다


2. Request Parameter 를 통한 버전관리 (Amazon)

다음은 아마존에서 주로 사용하는 쿼리파라미터를 통한 버전관리이다

  • @GetMapping 의 params 프로퍼티에서는 쿼리 파라미터의 값이 해당하는 값일 때만 수행하도록 한다 (물론 디스패처서블릿이 이를 매핑시켜줌)


아니라면 하나의 컨트롤러에서 @RequestParam 을 통하여 version 값에 따른 변경을 해주어도 가능하다.

이 점은 취향 차이?

3. Header 를 통한 버전관리 (Microsoft)

  • @GetMapping 의 또다른 프로퍼티인 header를 지정하여 해당 헤더값일 때에 매핑시키도록 한다

  • 특이점으로는 헤더에 버전값이 들어가기 때문에 일반적인 사용자는 버전에 대한 정보를 알 수 없음

4. Media Type 을 통한 버전관리 (a.k.a content negotiation / accept header)

  • 이런 방법도 있다 ? 하고 넘어가는게 좋을 것같다. 가장 큰 단점은 application/{type} 에 따라 메서드의 개수가 점점 불어날것이기 때문에 좋은 방법일까 싶다?

비교해보기

네가지 방법중 선택하게 될 때에 고려해야 할 점은 다음과 같다

  • Uri pollution : 1,2 번 방법을 채택하였을 때, uri의 길이가 늘어남에 따라 관리가 힘들 수 있다
  • Http Header의 오용 : 3,4 번의 경우 Http 프로토콜에서 버전관리를 하도록 권장되어 만들어진것이 아니기 때문에 헤더의 잘못된 사용법이라고 보는 곳도 많다
  • 캐싱 문제 : 캐싱은 url 에 따라 적용이 되는데 3,4 번의 경우 똑 같은 url 를 사용하기 때문에 url만 보고 캐싱하면 안되고 header 정보 또한 비교하여 캐싱하여야 한다. 즉, 생산성이 별로?
  • 브라우저에서 동작 문제 : 3,4번의 경우 API 테스터 (POSTMAN 등)를 사용하지 않는 이상 브라우저에서 다른 버전의 api 를 사용할 수 없다. 아니 헤더가 적용되지 않는다면 어떤 버전이든 사용하지 못할지도?
  • API 문서화 : 일반적으로 api 문서 자동화 도구는 header 에 따른 자동 문서화를 적용시키지 않는다. 1,2 번의 경우 url 이 다르기 때문에 다른 메서드로 인식하여 적용되겠지만 3,4 의 경우header 를 비교하여 문서화 해주는 도구를 써야할 것이다.

🤔 완벽한 솔루션은 없지만 1 번이 좋지 않을까 한다. Uri pollution 이 최소화 되기도 하고 다른 문제들에 해당되지 않기 때문이다. 직관적이고 사용성이 좋다고나 할까?

✔ 다만 필수적으로 하나의 기업에서는 같은 버저닝을 통하여 일관성을 유지하여야 한다! 사용자에게도 개발자의 유지보수에도 이는 권장된다


3. HATEOS (Hypermedia as the Engine of Application State)

하이퍼미디어를 애플리케이션의 상태를 관리하기 위한 메커니즘으로 사용

아니 대체 무슨 뜻…?

  • Rest API를 사용하는 클라이언트가 전적으로 서버와 동적인 상호작용이 가능하도록 하는것
  • Data를 볼수도, 액션을 수행할 수도 있게 됨! (링크를 통하여)

적용법

  • 커스텀 포맷을 통한 적용 (유지하기 힘듦… 모든 구조를 메서드 마다 짜야하기 때문)
  • 정석적인 방법 → HAL (JSON Hypertest Application Language) : API의 리소스에서 링크를 갖도록 하는 심플한 포맷 → 모든 API에 일관성을 유지할 수 있음 ! 쉽고 좋음

부트에서는 간단하게 디펜던시를 추가하여 구현이 가능하다 (POM.XML)

데이터와 링크를 같이 보내기 위해서는 EntityModelWebMvcLinkBuilder 를 이해할 필요가 있다

  • 엔티티모델은 도메인 객체와 링크를 래핑하는 클래스
  • WebMvcLinkBuilder 는 spring mvc controller 로 가르키는 링크를 쉽게 만들어주는 클래스

WebMvcLinkBuildermethodOn 을 통하여 타겟 메서드를 가르키도록 하고 linkTo 를 이용하여 링크를 만들어준다

  • Link.withRel 또는 자기 Rel 없이 하고 싶다면 selfRel 으로 지정하면 된다


HAL 을 이용하여 HATEOS를 구현


4. Rest API 커스터마이징

  • 직렬화 (Serialization) : object 를 stream (json)으로 변환하는 것
    • Json 직렬화 : Jackson

자바 빈JacksonConverter 를 통하여 json 객체로 만들어 반환하게 되는데 만약 반환된 값중 제외시키고자 하는 필드가 있다면 어떻게 할까?

🤔 가령 id 와 같은 index 들은 사용자에게 제공하지 않는 편이 좋을 것이다 (보안상의 문제.. sql injection 이라도 하면 어쩌겠는가)

이러한 문제를 해결하기 위해 커스터 마이즈가 필요하다

  1. 필드 이름을 바꾸고자 할 때 (엔티티에서 관리하는 필드이름과 사용자에게 제공되는 필드이름을 다르게 하고자 할 때)
    → Response 에 필드를 지정한다 (@JSONProperty 를 통하여)
  1. 지정한 필드만 반환하고자 할 때
    1. Static 필터링 : @JsonIgnoreProperties, @JsonIgnore → 패스워드 등과 같이 전역에서 필터링을 적용하여 제외시키고자 할 때
    2. Dynamic 필터링 : @JsonFilter (FilterProvider) → 특정 상황에서 제외해야하는 경우

1. 전역 필터링


다음과 같은 빈이 있을 때 @JsonIgnore 를 붙여준 필드는 제외시킬 수 있다

그렇다면 리스트에서는 어떨까?


리스트에서도 정상적으로 필드가 제외됨을 알 수 있다

@JsonIgnoreProperties 를 통하여 클래스에서 필드값들을 지정하여 제외시킬 수도 있다

2. 다이나믹 필터링

같은 빈에 대하여 각각의 컨트롤러에서 다른 값들을 보내고 싶다면

가령 field1, 2, 3 가 있다했을 때

첫번째 컨트롤러에서는 field 2를 제외시키고
두번째 컨트롤러에서는 field 2와 3을 제외시키고 싶다면?

MappingJacksonValue : json으로 매핑될때에 필터를 걸어 특정 필터만 매핑되도록 할 수 있음

  1. MappingJacksonValue 를 통해 빈을 넣고 매핑할 객체를 지정

  2. SimpleBeanPropertyFilter 를 통하여 제외 시키고자 하는 필드를 지정

  3. FilterProvider 를 통해 세롭게 필터를 생성

  4. MappingJacksonValue 에 생성한 필드를 세팅

  5. JacksonConverter가 필터를 적용시켜 값을 반환

✔ 필터를 지정한 객체(DAO) 가 있다면 해당 객체에 Json 필터가 적용되었음을 스프링에게 알려야함

0개의 댓글