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://..."
으로 하드코딩을 해주고 있는 모습입니다.
그런데 이런 방식으로 하드 코딩을 진행할 시, 그냥 코드를 짜면서도 찝찝한 것도 문제지만(사실 저한테는 이게 제일 컸습니다. 🤣) 이외에도 여러가지 단점이 발생할 수 있습니다.
- 만약 공공 API 스펙이 변경되었을 때, 일일이 URL이 저장된 문자열을 찾아서 변경해야 한다. 지금은 혼자 하는 프로젝트라서 몇 개 안되지만 코드가 쌓이고 쌓였을 때는 모든 파일을 돌아다니며 문자열 하나 수정 또한 버거운 작업이 될 것이다.
- URL 자체로만 봤을 때, 이것이 무엇을 요청하는 URL인지 알기 힘들다.
https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/
이것은 인게임 플레이어 이름으로 유저의 정보를 검색하는 api이지만, 이 문자열 자체로는 의미를 파악하기가 어렵다.- [제 프로젝트의 경우 발생하는 문제] 지금은 한국 서버를 대상으로 한 플레이어만 전적 검색이 가능하지만, 만약 유럽 또는 북미 서버로 요구사항이 변경된다면, 그때는 모든 url을 대대적으로 손봐야한다. -> 국제화 문제
그렇다면 위의 단점들을 어떻게 해결할 수 있을까요?
저는 SpringBoot의 메세지, 국제화 기능으로 해결을 시도해보았습니다.
메세지 기능이란 여기저기서 공통적으로 사용되는 메세지(문자열)들을 한 곳에서 관리하는 기능입니다. 여기서 한 곳에서 관리하는 파일의 이름이 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입니다. LOCALE은 null
로 설정 시, 디폴트로 설정된 값인 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://
옆에 kr
이 na1
으로 달라진 것 뿐입니다.
MessageSource
를 주입해주는 과정은 아까와 같이 생성자 주입을 해주었으니 생략하겠습니다. 메세지를 호출하는 getMessage
부분만 보겠습니다.
String msgEn = messageSource.getMessage("summoner.puuid.by-summoner-name", new Object[]{summonerName},LOCALE.ENGLISH);
이번에는 3번째 인자인 LOCALE에 LOCALE.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(한국, 북미) 에 맞는 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
으로 등록하는 방법이 떠오르는데, 이거는 또 시도해 보겠습니다.
읽어주셔서 감사합니다. 틀린 부분 지적과 궁금한 점 댓글 환영합니다!