새소식

Project

프로젝트 중간 점검

 

 

개인 프로젝트 설계 - 요구사항과 플로우차트

개인 프로젝트 설계 - DB 설계와 와이어프레임 만들기

 

 

프로젝트 방향에 대한 고민

 

노래방에서 부를 노래에 대한 고민을 해결하기 위한 아이디어에서 출발했던 프로젝트..

설계를 마치고 코드를 작성하면서 문제가 발생했다.

 

노래방의 노래 데이터는 공식적으로 제공되는 API가 없어 직접 브랜드별로 데이터를 크롤링하거나 개인이 제공하는 API를 사용해야 한다.

우선 내가 선택한 방법은 API를 사용하는 것이었는데, 응답으로 받는 데이터에는 음악의 앨범 이미지가 없었다.

이건 각 노래방 사이트에서도 제공하지 않는 데이터였기에 여기서 1차 고민에 빠졌다.

 

 

1. 이미지 없이 텍스트로만 정보를 제공해야 할까?

 

하지만 이 방법은 기획 내용과 거리가 있고, 무엇보다 이미지가 없는 웹사이트는 예쁘지가 않다.

만족할 결과물을 만들기 위해서 앨범 이미지는 필수적인 요소였다.

 

 

2. 이미지만 다른 API를 이용해 따로 얻을 수 있을까?

 

일단 물음에 대한 답은 YES다.

Spotify, Last.fm, iTunes, ManiaDB 등 음원 정보를 제공하는 API가 있었다.

이 중에서 다음 항목들을 만족하는 API를 선별했다.

 

  • 이미지 제공 여부
  • 무료 서비스
  • 국내외 다양한 음원 데이터 보유
  • 호출 제한 기준

 

 

결국 선택한 API는 Spotify였는데, 여기까지 생각하다 보니 큰 문제가 있었다.

바로 유저가 검색한 음악(노래방 API)과 일치하는 이미지(Spotify Web API, 이하 Spotify API)를 가져와야 하는 것이었다.

 

여느 음원 사이트가 그렇듯, Spotify API 역시 키워드 검색을 통해 결과를 조회한다.

문제는 같은 키워드를 사용했을 때, 두 API의 응답 결과가 다르다는 것이다.

 

예를 들면 "파이팅 해야지"로 검색했을 때의 타이틀이

노래방 API로는 "파이팅 해야지 (feat. 이영지)"라면

Spotify API로는 "Fighting (Feat. Lee Young Ji)"로 결과가 나온다.

 

여러 가지 방법으로 시도를 해봤으나 결국 두 데이터가 "같은 음악"인지 100% 판별할 수 없다는 생각이 들었고 어떻게 해야 할지 선택을 해야 했다.

그래서 내리게 된 결론.. 노래방이라는 테마에 집착하지 말고 본래 구현하고자 했던 기능인 검색과 추천에 초점을 맞추자!

Spotify API만 활용하기로 결정하고 코드를 싹 뜯어 고쳤다. (+ 플로우 차트와 DB도 함께 수정..)

 

 

 

Spotify API를 적용하면서 생긴 문제들

 

하지만 인생은 그렇게 호락호락하지 않지.

적용해보니 역시나 해결해야 될 다른 문제들이 있었다.

 

 

1. 필요한 응답 데이터만 객체로 변환하기

 

원하는 결과를 얻기 위해서는 요청 url과 파라미터를 설정해야 한다.

그렇게 응답받은 데이터는 내게 필요하지 않은 정보까지 포함된 엄청난 양의 JSON 데이터다.

이 JSON 데이터는 괄호 안에 괄호가 있고, 또 그 괄호 안에 괄호가 있고.. 아주 복잡한 구조였다.

 

JSON 구조에 맞춰 객체를 만들면 한 번에 변환이 되겠지만 불필요한 데이터까지 담게 되어 비효율적인 방식이다.

그래서 다음과 같이 node별로 탐색하여 필요한 데이터만 뽑아내어 객체로 변환했다.

private static SpotifyArtist jsonToArtist(String jsonData) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(jsonData);

        SpotifyArtist artist = new SpotifyArtist();

        String name = rootNode.get("name").asText();
        artist.setName(name);

        JsonNode imagesNode = rootNode.get("images");
        for (JsonNode images : imagesNode) {
            String url = images.get("url").asText();
            artist.setImageUrl(url);
        }

        String popularity = rootNode.get("popularity").asText();
        artist.setPopularity(popularity);

        JsonNode genresNode = rootNode.get("genres");
        List<String> genreList = new ArrayList<>();
        for (JsonNode genres : genresNode) {
            String genre = genres.asText();
            genreList.add(genre);
        }
        artist.setGenreList(genreList);

        String id = rootNode.get("id").asText();
        artist.setId(id);

        return artist;
}

 

위 코드는 아티스트의 정보를 요청했을 때 응답받은 JSON 데이터를 객체로 변환하는 부분이다.

아티스트 정보는 내가 활용하고자 하는 데이터의 양이 상대적으로 적어 이렇지만, 음원 정보와 같이 객체가 더 많은 필드를 가지고 있다면 코드는 훨씬 길어지고 각각의 JSON 구조도 달라 메서드를 재활용할 수도 없었다.

결과적으로 JSON의 객체 변환 코드만 해도 양이 매우 많아지는 것이다.

 

또한 요청 URL 관련 상수만 따로 정리를 했는데 기능이 추가될 때마다 추가해야 할 상수가 많아져서 이게 맞나 싶은 생각이 들었다.

public class SpotifyURLConstants {

    public static final String SEARCH = "https://api.spotify.com/v1/search?";
    public static final String RECOMMENDATION = "https://api.spotify.com/v1/recommendations?";
    public static final String ARTIST = "https://api.spotify.com/v1/artists";
    public static final String NEW_RELEASE = "https://api.spotify.com/v1/browse/new-releases?";
    public static final String ALBUM = "https://api.spotify.com/v1/albums/";

    //search(1~50), recommendation(1~100), new_release(1~50)
    public static final String LIMIT = "limit=";

    /**
     * ARTIST(ONLY ID of the artist required)
     */
    public static final String ONE_ARTIST = "/"; // v1/artists/aaa
    public static final String SEVERAL_ARTISTS = "?ids="; // ids=aaa,bbb,ccc (max 50)
    public static final String RELATED_ARTIST = "/related-artists"; // v1/artists/aaa/related-artists

    /**
     * SEARCH(query and type required)
     */
    public static final String QUERY = "q=";
    public static final String TYPE_ALBUM = "type=album";
    public static final String TYPE_ARTIST = "type=artist";
    public static final String TYPE_TRACK = "type=track";

    /**
     * RECOMMENDATION
     */
    public static final String SEED_ARTIST = "seed_artists=";
    public static final String SEED_GENRE = "seed_genre=";
    public static final String SEED_TRACK = "seed_tracks=";

    public static final String MIN_POPULAR = "min_popularity="; //0~1
    public static final String MAX_POPULAR = "max_popularity="; //0~1

    public static final String MIN_TEMPO = "min_tempo="; //BPM
    public static final String MAX_TEMPO = "max_tempo="; //BPM

    public static final String MIN_ENERGY = "min_energy="; //0~1
    public static final String MAX_ENERGY = "max_energy="; //0~1

    public static final String MIN_ACOUSTICNESS = "min_acousticness="; //0~1
    public static final String MAX_ACOUSTICNESS = "max_acousticness="; //0~1

    public static final String MIN_DANCEABILITY = "min_danceability="; //0~1
    public static final String MAX_DANCEABILITY = "max_danceability="; //0~1

    public static final String MIN_INSTRUMENT = "min_instrumentalness="; //0~1
    public static final String MAX_INSTRUMENT = "max_instrumentalness="; //0~1
}

 

 

좀 더 간단하게 사용할 수 있는 방법이 없을까 하던 중..

 

 

엄청난 게 있었다. 이 라이브러리를 쓰면 훨씬 간단하게 API 호출이 가능했다.

몇 차례 테스트 후 이걸 사용해야겠다 싶어 기존 코드를 다시 또 뜯어고쳤다.

뭔가 엄청난 시간 손실이 있었던 것 같지만 JSON 데이터 처리에 대한 깊은 고민의 시간을 가진 걸로 위안 삼았다.

 

 

2. 응답 데이터가 영어로 반환

 

해외 서비스다 보니 응답 데이터가 모두 영어였다.

내가 만드는 서비스는 국내 유저 대상이니 한국어로 데이터를 받아야 했다.

이 부분은 요청 header에 Accept-Language를 "ko-KR"로 보내 해결할 수 있었다.

private static final String HEADER_NAME = "Accept-Language";
private static final String HEADER_VALUE = "ko-KR";

public static List getMusicList(MusicSearchCondition condition, int page) {
	Paging<Track> trackPaging = spotifyApi.searchTracks(condition.getKeyword())
                    .limit(LIMIT)
                    .offset(LIMIT * (page - 1))
                    .setHeader(HEADER_NAME, HEADER_VALUE)
                    .build()
                    .execute();
                    
	//이후 로직 생략
}

 

 

3. Access Token 처리 방식

 

이건 정말 큰 고민이었다.

 

Spotify API는 OAuth 2.0 인증 방식을 사용한다.

이 말은 곧 Access Token이 있어야 음악 검색 등의 요청이 가능하다.

 

출처: Spotify Web API 공식 문서

 

 

출처: Spotify Web API 공식 문서 - Authorization Code Flow

 

 

 

공식 문서 및 다른 사람들의 코드를 참고했을 때 대부분 접근 권한을 사용자에게 주고, 사용자에게 권한이 있는 경우 요청이 처리되도록 하였다. Spotify 계정으로 사용자가 로그인을 하면 Access Token을 발급받고 이후 서비스를 사용하는 방식으로 이루어지기 때문이다.

 

이 경우 일장일단이 있는데, 사용자의 Spotify 앨범 접근 및 음악 재생 등이 가능하지만 사용자의 Spotify 계정이 별도로 필요하다.

 

회원가입을 한 웹사이트에서 또 다른 사이트에 가입을 하도록 하고 싶지 않았고, API로 음원 관련 정보만 요청할 것이기 때문에 Client Credential 방식을 적용하여 토큰을 얻도록 했다.

 

출처: Spotify Web API 공식 문서 - Client Credentials Flow

 

 

이렇게 하면 이용 가능한 요청에 제한이 있지만 별도의 Spotify 계정이 필요하지 않다.

 

사용자의 요청이 들어오면 Access Token을 확인하고, 토큰이 없는 경우에만 발급받은 후 요청을 처리하는 로직을 구현했다.

 

 

이 이후로도 크고 작은 문제들이 있었지만 그건 다음 포스트에서 작성해야겠다. 글이 너무 길어지는 바람에 :(

배포는 언제쯤 할 수 있을까..?

 

 

Contents

Copied URL!

Liked this Posting!