[SpringBoot] Messages를 사용해서 url 하드코딩을 피해보자

최재혁·2022년 9월 18일
0
post-thumbnail
post-custom-banner

🤨 들어가며...

RIOT API를 사용하여 롤 전적검색 사이트를 개발 중...

요청을 보낼 URL은 매번 달라지는데, 저는 매번 바뀌는 URL을 클래스에 String 으로 하드코딩하고 있었습니다.

[유저의 puuid로 매치 전적을 가져오는 api]

public class MatchV5Api {
	private final String urlStr = 	"https://asia.api.riotgames.com/lol/match/v5/matches/";
/**
*	....
*/
	public List<String> matchIdsByPuuid(String puuid, int start, int end) throws IOException {
        URL url = new URL(urlStr + "by-puuid/" + puuid + "/ids?start=" + start + "&count=" + end);

[유저의 인게임 닉네임으로 puuid를 가져오는 api]

public class SummonerV4Api {


    private final String urlStr = "https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/";
    /**
    * ....
    **/
    public SummonerV4Dto summonerV4ApiBySummonerName(String summonerName) throws IOException {

        // 한글이 깨지는 문제로 인해 소환사 이름을 인코딩을 해야함
        String encoded = URLEncoder.encode(summonerName, StandardCharsets.UTF_8);
        URL url = new URL(urlStr + encoded);

이렇게 각 클래스마다 private final String urlStr = "https://..." 으로 하드코딩을 해주고 있는 모습입니다.

그런데 이런 방식으로 하드 코딩을 진행할 시, 그냥 코드를 짜면서도 찝찝한 것도 문제지만(사실 저한테는 이게 제일 컸습니다. 🤣) 이외에도 여러가지 단점이 발생할 수 있습니다.

URL을 하드코딩했을 시 단점

  1. 만약 공공 API 스펙이 변경되었을 때, 일일이 URL이 저장된 문자열을 찾아서 변경해야 한다. 지금은 혼자 하는 프로젝트라서 몇 개 안되지만 코드가 쌓이고 쌓였을 때는 모든 파일을 돌아다니며 문자열 하나 수정 또한 버거운 작업이 될 것이다.
  2. URL 자체로만 봤을 때, 이것이 무엇을 요청하는 URL인지 알기 힘들다. https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/ 이것은 인게임 플레이어 이름으로 유저의 정보를 검색하는 api이지만, 이 문자열 자체로는 의미를 파악하기가 어렵다.
  3. [제 프로젝트의 경우 발생하는 문제] 지금은 한국 서버를 대상으로 한 플레이어만 전적 검색이 가능하지만, 만약 유럽 또는 북미 서버로 요구사항이 변경된다면, 그때는 모든 url을 대대적으로 손봐야한다. -> 국제화 문제

그렇다면 위의 단점들을 어떻게 해결할 수 있을까요?

저는 SpringBoot의 메세지, 국제화 기능으로 해결을 시도해보았습니다.


messages.properties

메세지 기능이란 여기저기서 공통적으로 사용되는 메세지(문자열)들을 한 곳에서 관리하는 기능입니다. 여기서 한 곳에서 관리하는 파일의 이름이 messages.properties가 됩니다. (설정으로 변경 가능)

원래 스프링부트가 아닌 스프링에서는, MessageSource를 스프링 빈으로 등록하여 사용해야 했습니다. 그 이후, MessageSource 에 메세지 파일들을 추가해야 했지요.

하지만 스프링부트는 관련 설정만 추가하면 자동적으로 메세지 파일들을 인식할 수 있습니다. 먼저, 스프링부트가 메세지 파일을 인식할 수 있도록 설정을 해주겠습니다.

스프링부트에 메세지 파일 등록

application.yml

spring:
 messages:
  basename: messages		

이는 스프링부트가 관리하는 메세지 파일에 messages.properties, messages_en.proerties을 등록하는 과정입니다. 파일 명에서 "messages"를 찾고, LOCALE 값에 따라서 messages_en 을 호출할 것인지, messages를 호출할 것인지 달라집니다. LOCALE은 이후에 설명하겠습니다.

만약 메세지 파일이 messages.properties 하나라면 등록하지 않아도 자동으로 인식됩니다. 저는 messages.properties 파일은 국내용, messages_en.properties은 북미용으로 생성할 생각입니다.

참고

messages 파일은 yml을 사용할 수 없다고 합니다.

저는 처음 messages.yml 로 만들어서 굉장히 헤맸으니 이 포스트를 읽으시는 여러분들은 messages.properties 를 사용하시길 바랍니다..

메세지 파일 생성

메세지는 이름=값 형식으로 이루어져 있습니다.

messages.properties

summoner.puuid.by-summoner-name=https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/{0}

match.id.by-summoner-puuid=https://asia.api.riotgames.com/lol/match/v5/matches/by-puuid/{0}/ids?start={1}/&count={2}
match.info.by-match-id=https://asia.api.riotgames.com/lol/match/v5/matches/{0}

보시면 메세지 이름으로 URL의 용도를 파악할 수 있게끔 설계했습니다. 예를 들어, summoner.puuid.by-summoner-name 메세지는 summoner 이름(인게임 플레이어 이름)으로 summoner의 puuid를 얻는 url을 호출합니다.

URL 맨 뒤의 {0}Argument로 원하는 인자를 전달해줄 수 있습니다.

메세지 사용

먼저 등록된 메세지를 사용하기 위해서는 스프링 빈으로 등록된 MessageSource가 필요합니다. 저는 생성자 주입을 해주겠습니다.

@RequiredArgsConstructor
public class SummonerV4Api {

    private final MessageSource messageSource;
    /**
    * ....
    **/

그 다음 등록한 메세지를 호출하기 위해서, getMessage 메소드를 사용합니다.

String msg = messageSource.getMessage("summoner.puuid.by-summoner-name", new Object[]{summonerName},null);

getMessage의 첫번째 인자는 등록한 메세지의 이름, 두번째 인자는 넘겨줄 파라미터(파라미터가 여러 개일 수도 있어서 함수의 원형은 Object[] 타입으로 선언되어 있습니다.), 세번째 인자는 지역을 나타내는 LOCALE입니다. LOCALEnull로 설정 시, 디폴트로 설정된 값인 messages.properties 파일에서 메세지를 가져옵니다.

예를 들어, summnoerName이 developer라면, urlStr2 문자열에는 summoner.puuid.by-summoner-name/{0} 에서 {0}을 developer 로 치환한 값인 summoner.puuid.by-summoner-name/developer 가 저장됩니다. (실제로는 UTF-8 인코딩을 거칩니다.)


국제화

만약 찾고 싶은 플레이어가 한국 서버가 아닌 북미 서버의 유저라면 어떻게 할까요? 매번 호출하는 API를 변경하기는 너무나 번거롭습니다. 이를 위해 LOCALE 값이 달라졌을 때 대응되는 메세지 파일을 생성하겠습니다.

messages_en.properties

summoner.puuid.by-summoner-name=https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/{0}

이 API는 북미 서버의 유저 정보를 플레이어 이름으로 검색하는 API입니다. 이전과 달라진 점은 https:// 옆에 krna1으로 달라진 것 뿐입니다.

국제화 메세지 사용

MessageSource를 주입해주는 과정은 아까와 같이 생성자 주입을 해주었으니 생략하겠습니다. 메세지를 호출하는 getMessage부분만 보겠습니다.

String msgEn = messageSource.getMessage("summoner.puuid.by-summoner-name", new Object[]{summonerName},LOCALE.ENGLISH);

이번에는 3번째 인자인 LOCALELOCALE.ENGLISH를 넘겨주었습니다. 그러면 어떤 일이 벌어질까요? 아까는 null값을 넘겨주었더니 디폴트로 설정된 messages.properties에서 메세지를 가져왔습니다. 하지만 이번에는 다릅니다.

LOCALE.ENGLISH를 넘겨주면 messages_en.properties 파일이 있는지 먼저 뒤지고, 일치하는 파일이 존재하면 거기서 메세지를 가져옵니다. 만약 그런 파일이 없으면, 설정된 디폴트 메세지에서 가져오게 됩니다. 스프링은 상당히 다양한 국제화 메세지를 지원하니, 더 궁금하신 분들은 Locale 클래스를 한번 참고하시길 바랍니다.

public final class Locale implements Cloneable, Serializable {

    private static final  Cache LOCALECACHE = new Cache();

    public static final Locale ENGLISH = createConstant("en", "");

    public static final Locale FRENCH = createConstant("fr", "");

    public static final Locale GERMAN = createConstant("de", "");

    public static final Locale ITALIAN = createConstant("it", "");

    public static final Locale JAPANESE = createConstant("ja", "");
    
    // .....

스프링이 LOCALE 정보를 아는 법

저희는 지금까지 메세지 파일 등록을 통해 각각의 Locale(한국, 북미) 에 맞는 URL을 설정했습니다. 그런데 스프링은 어떻게 Locale 정보를 알 수 있을까요?

스프링은 HTTP Request 헤더Accept-Language정보를 가지고 Locale 정보를 파악합니다.

[Request Heade 예시]

{
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
    "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
    "Accept-Charset": "application/x-www-form-urlencoded; charset=UTF-8",
    "Origin": "https://developer.riotgames.com",
    ...
}    

만약 Accept-Language 는 언어에 대한 우선순위를 나타내는데, 큰 값일수록 우선 순위가 높습니다. 위의 예시에서는 ko; q=0.9 로서 우선 순위가 제일 높기 때문에 한국어를 지원하는 메세지 파일이 있는 경우, 그 메세지 파일을 통한 로직이 수행됩니다. 한국어를 지원하지 않는다면, en-US; q=0.8 이 그 다음 우선 순위기 때문에 그에 대응하는 메세지 파일을 찾게 되겠죠.

저는 위의 예시에서는 LOCALE 값을 직접 전달해줬지만, 실제 서비스에서는 HTTP Request에서 Accept-Language를 넘겨줘야 할 것 같습니다.


마무리

오늘은 하드코딩을 피하기 위해 메세지 파일을 직접 등록하고, 사용해 봤습니다. 당장은 손이 더 많이 가는 것 같지만 지금부터라도 이런 습관을 들여야 나중에 고생하지 않겠죠..? 메세지 파일 설정 말고도, Enum으로 등록하는 방법이 떠오르는데, 이거는 또 시도해 보겠습니다.

읽어주셔서 감사합니다. 틀린 부분 지적과 궁금한 점 댓글 환영합니다!

profile
잘못된 고민은 없습니다
post-custom-banner

0개의 댓글