본문 바로가기

개발/알파벳-다시만들기

뱃지 서비스 리팩토링 [2], 빈으로 등록하고 캐싱곁들이기...

반응형

사실 뱃지 서비스는 2가지가 있습니다.

바로 학생 뱃지와 코드포스 뱃지입니다.

학생 뱃지는 디비에서 코드포스 뱃지는 외부 api에서 정보를 가져옵니다.

이번 포스팅에서는 코드포스 뱃지 리팩토링을 진행하겠습니다.

 

1단계 리팩토링, [코드에서 의미 보여주기]

객체가 너무 많은 역할을 맡고 있는 부분을 분리해 봅시다. 또 구현 대신 의미를 보여주게 만들어봅시다.

아래는 원본 코드입니다.

더보기
package uhs.alphabet.domain.badge;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.*;

public class CfUser {
    private final String handle;
    private final String color;
    private static final String codeforcesUserInfoUrl = "https://codeforces.com/api/user.info?handles=";
    private static final Charset charset = Charset.forName("UTF-8");
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final Map<String, String> colorMap = new HashMap<>();
    static {
        colorMap.put(CodeForcesRank.Legendary_Grandmaster.getLowerValue(), "url(#grad1)");
        colorMap.put(CodeForcesRank.International_Grandmaster.getLowerValue(), "red");
        colorMap.put(CodeForcesRank.Grandmaster.getLowerValue(), "red");
        colorMap.put(CodeForcesRank.International_Master.getLowerValue(), "orange");
        colorMap.put(CodeForcesRank.Master.getLowerValue(), "orange");
        colorMap.put(CodeForcesRank.Candidata_Master.getLowerValue(), "violet");
        colorMap.put(CodeForcesRank.Expert.getLowerValue(), "blue");
        colorMap.put(CodeForcesRank.Specialist.getLowerValue(), "cyan");
        colorMap.put(CodeForcesRank.Pupil.getLowerValue(), "green");
        colorMap.put(CodeForcesRank.Newbie.getLowerValue(), "grey");
    }

    public CfUser(final String handle, final String color) {
        this.handle = handle;
        this.color = color;
    }

    public static CfUser of(final String handle) {
        String rank = null;
        try {
            URL url = new URL(codeforcesUserInfoUrl+handle);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            // 3초 타임 아웃
            urlConnection.setReadTimeout(3000);
            InputStream inputStream = urlConnection.getInputStream();
            byte[] bytes = inputStream.readAllBytes();
            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            String s = charset.decode(buffer).toString();
            Map<String, Object> stringObjectMap = mapper.readValue(s, new TypeReference<Map<String, Object>>() {});
            ArrayList<HashMap<String, Object>> result = (ArrayList<HashMap<String, Object>>) stringObjectMap.get("result");
            HashMap<String, Object> map = result.get(0);
            rank = (String) map.get("rank");
            rank = rank.toLowerCase();
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }

        String color = colorMap.get(rank);
        if (color == null) color = "grey";

        return new CfUser(handle, color);
    }


    public String getHandle() {
        return handle;
    }

    public String getColor() {
        return color;
    }
}

유저가 외부 api 요청도하고, 응답으로 내려온 json 파일 매핑도 하고, 랭크에 맞는 색까지 매핑합니다.

역할을 모두 짤라 냅니다.

 

    public static CfUser of(final String handle) {
        String data = CodeforcesClient.getData(handle);
        String rank = CodeforcesMapper.getRank(data);
        String color = colorMap.get(rank);
        if (!isExistColor(color)) return new CfUser(handle, "grey");
        return new CfUser(handle, color);
    }

리팩토링을 거쳐 읽을만해졌습니다.

이제는 코드에서 의미가 보입니다.

 

2단계 리팩토링, [MVC패턴 적용]

스프링 프로젝트인 만큼 빈으로 등록해봅시다.

1. 유저 객체에는 필요한 상태값만 남기고 전부 지웁니다(of 메소드 등)

2. of 메소드에 있던 로직은 뱃지 서비스 객체로 전부 옮깁니다(기능 구현은 도메인 객체에 맡기고 서비스에서 이를 실행시킬겁니다)

3. 코드 건드리는김에 코드포스 데이터를 가져오는 인터페이스 하나 생성해둡시다

 

3번 구현은 ~(추가예정)

패키지 구조도 다듬었습니다. 클래스 이름도 이해하기 쉽게 재정비했습니다.

 

3단계 리팩토링, [캐싱]

서비스 레이어에서는 외부 api요청을 캐싱도 할겁니다

*캐싱은 요청 결과를 저장해두고 재활용하는 행위입니다. 유저의 랭크가 잘 변하지 않는다는 점을 관찰해 이를 적용했습니다

(대회가 하루에 한번뿐이라 수명도 하루로 잡았습니다)

 

    public String  makeCodeforcesBadge(String handle) {
        Cache<String, String> codeforcesCache = cacheManager.getCache("codeforcesCache", String.class, String.class);
        if (codeforcesCache.containsKey(handle)) return codeforcesCache.get(handle);
        String data = codeforcesClient.getData(handle);
        String rank = codeforcesMapper.getRank(data);
        CodeforcesBadgeFileStream badgeFileStream = new CodeforcesBadgeFileStream();
        String badge = codeforceBadgeFactory.makeBadge(handle, colorMap.get(rank), badgeFileStream);
        codeforcesCache.put(handle, badge);
        return badge;
    }

캐시는 ehache를 적용했습니다.

레디스 등 캐시 서버를 띄울까 고민했지만(실제로 로컬에서는 띄워서 테스트했습니다) 과하다는 생각이 들었습니다.

(컴퓨터 한대에서 서버 하나 겨우 돌아가는데 캐시 서버가 필요한가... 프로그램 돌리는 메모리만 아깝지..라는 생각에..ㅋㅋ)

 

다음에는 외부 api요청을 비동기 처리 해보겠습니다(안할수도 있어요ㅎㅎ...)

반응형