본문 바로가기

프로젝트/Toy 프로젝트

[Toy 프로젝트] Commit Combo - Dev log

Commit Combo - Dev log

https://github.com/devxb/CommitCombo

 

GitHub - devxb/CommitCombo: 깃허브 커밋 기록을 아름답게 꾸미는 프로젝트 ⭐

깃허브 커밋 기록을 아름답게 꾸미는 프로젝트 ⭐. Contribute to devxb/CommitCombo development by creating an account on GitHub.

github.com

이 당시, 1일 1커밋 운동에 대해서 관심이 많았고, 실제로 알고리즘 문제를 하루에 1문제씩 풀며, 1일 1커밋을 하고있기도 했었다.

문제를 못 푸는 날은 옛날에 풀었던 문제를 커밋하며 1일 1커밋을 이어가려고 노력했었는데, 이를 좀 더 재밌게 하고자 만든 애플리케이션이다. 

 

애플리케이션을 만들때 코드 설계와 성능에 관해서 굉장히 많은 고민을 했는데, 그 내용이 점차 기억속에서 사라지는것 같아서 이를 정리하려고 한다. 또한, 배포하며 앞으로 추가될 기능과 이에 관한 설계이유, 고민등을 작성할것이다.

 

또한, 프론트엔드에서 발생했던 오류 (사용자 이름이 길 경우 잘린다던지..)에 대해서는 적지 않도록 하겠다.

설계

1. 카드 테마별 확장과 변경에 대비하기 위한 노력

 

설계 이유 :

내 애플리케이션은 사용자가 직접 카드의 색깔 조합을 선택하게 하지않고, 내가 만들어둔 색 조합에서 선택하도록 했다. 이 후에, 많은 테마가 추가될수있는 상황이였고(실제로 많은 테마가 추가되었다), 어떤식으로 변경될지 모르기 때문에, 설계의 중점은 추가되는 테마에대해 기존 코드의 변경을 최소화 하는것이였다.

 

프로젝트 초반에는 데코레이터 패턴을 이용해서, 테마의 부위별 재료들을 만들고, 이를 조합해서 사용하도록 했는데, 점차 많아지는 테마들 때문에, 테마 재료들을 조합하는 코드가 상당히 복잡해지는것이 느껴졌고 이는 좋은 설계가 아니라는 생각이 들었다.

 

'멋진' 디자인패턴을 버리고 객체지향의 강점인 다형성과 상속에 좀 더 초점을 맞춰 설계했고 아래와 같은 구조를 갖게되었다. 

코드

더보기
@Service
public class ThemeFactory{
	
	private List<Theme> themes;
	
	@Autowired
	public ThemeFactory(List<Theme> themes){
		this.themes = themes;
	}
	
	public Theme getTheme(String themeName){
		String[] themeArr = themeName.split("-"); // deprecate된 -mini api대응하기 위해 남겨놓음
		for(Theme theme : themes) if(themeArr[0].equals(theme.getThemeName())) return theme;
		return themes.get(themes.size()-1);
	}
	
}

package com.commitcombo.service.theme;

import org.springframework.stereotype.Component;

@Component
public abstract class Theme{
	
    protected String themeName;
    protected String nameTagColor;
    protected String commitComboColor;
    protected String comboCntColor;
    protected String backgroundColor;
    protected String comboBoxColor;
    protected String gradient;
	
	protected StringBuilder stringBuilder = new StringBuilder();
	
	public String getThemeName(){
		return this.themeName;
	}
	
    public String getNameTagColor(){
        return this.nameTagColor;
    }
    
	public String getCommitComboColor(){
        return this.commitComboColor;
    }
    
	public String getComboCntColor(){
        return this.comboCntColor;
    }
    
	public String getBackgroundColor(){
        return this.backgroundColor;
    }
    
	public String getComboBoxColor(){
        return this.comboBoxColor;
    }
    
	public String getGradient(){
        return this.gradient;
	}

}

@Component
public class Pink extends Theme{

    public Pink(){
        this.themeName = "Pink";
        this.nameTagColor = "#FFFFFF";
        this.commitComboColor = "#FFFFFF";
        this.comboCntColor = "url(#Gradient)";
        this.backgroundColor = "url(#Gradient)";
        this.comboBoxColor = "#FFFFFF";
		
		stringBuilder.append("<linearGradient id=\"Gradient\" gradientTransform=\"rotate(20)\">\n")
			.append("<stop offset=\"0%\" stop-color=\"#D387AB\"/> \n")
			.append("<stop offset=\"100%\" stop-color=\"#E899DC\"/> \n")
			.append("</linearGradient>");
        this.gradient = stringBuilder.toString();
    }


}

결과적으로 새로운 테마를 추가할때 기존의 코드를 수정할 필요없이, 새로운 테마의 색상에 해당하는 클래스만 추가하면 되는 구조가 완성되었다. 또한, 사용하는쪽 입장에서도, 테마의 이름을 기반으로 탐색하기때문에, themeFactory에 이름을 요청하기만 하면 되는 간단한 구조가 되었다.

개인적으로 지금까지 만족하는 설계중 하나이다.

 

2. DB연결 성능문제와 설계

설계이유:

프로젝트 초기에 가장큰 문제는 사용자의 요청에 대한 응답이 너무 오래걸린다는 점 이었다.

내 애플리케이션은 사용자가 빠르게 지나가기 쉬운 위치에 주로 사용되었고, 사용자의 요청에 빠르게 응답하지 못한다면, 못 보고 지나치기 쉬웠다.

이는 상당히 심각한 문제였고, 이를 해결하기위해 메소드별 수행 시간을 측정해본 결과, DB와 네트워크 연결 과정에서 시간이 오래 걸렸었다.

 

이를 해결하기위해 고민해본결과 요청마다 새로운 연결을 만들지 않고, 하나의 연결을 재사용하면 될것이라는 (Thread-safe하지 않은 위험한) 결론을 내렸다.

따라서, 싱글톤 객체안에 유일한 Connection인스턴스를 만들었고, 필요할때마다 이 Connection객체를 참조해 사용하도록 구현했다. 

 

결과적으로 속도는 눈에띄게 빨라졌으나, 2가지 문제점이 생겼다.

1. 하나의 Connection인스턴스를 장시간 사용함으로써 연결이 해제되었을때 발생하는 문제

2. 멀티 스레드 환경에서 Statement객체 동시 참조로 인한 병목현상 발생

 

연결을 잃었을때는 다시 회복시켜주면 되니 간단했는데, 문제는 2번이였다. 병목현상을 해결하려면, 사용자별로 다른 커넥션을 얻어야했고, 이는 다시 원점(DB연결 속도 문제)으로 돌아가는 행위였다.

 

(1번 문제 해결 코드)

더보기

서버에서 DB커넥션을 잃었을때 자동으로 회복해주는 코드이다.

package aboutDB;

import java.sql.*;

public class Stmt{

    private static Stmt instance = null;
    private static Connection con = null;

    private Stmt(){}

    public static Stmt getInstance(){
        if(instance == null){
            synchronized(Stmt.class){
                if(instance == null){
                    instance = new Stmt();
                    try{
                        String url = "jdbc:mysql://localhost:3306/commitcombo";
                        con = DriverManager.getConnection(url, "commitcomboDB", "Devxb@123");
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        return instance;
    }

    public synchronized void recoverConnection(){
        try{
            String url = "jdbc:mysql://localhost:3306/commitcombo";
            con = DriverManager.getConnection(url, "commitcomboDB", "Devxb@123");
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public synchronized Statement getStatement(){ 
        try{
            if(con == null) this.recoverConnection();
            return con.createStatement();
        } catch (SQLException SQLE){
            SQLE.printStackTrace();
            return null;
        }
    }

}

 

어쨋든, 해결법을 찾던중(지금은 어떤 계기로 발견했는지 기억은 안난다) DB연결 커넥션을 저장해뒀다가 사용하는 커넥션 풀 이라는 것을 알게되었고, hikari connection pool을 적용했다.

(정확히는 hikaricp를 따로 설정한게 아니라, hibernate를 사용하도록 리팩토링 하는 과정에서 hikaricp도 같이 딸려옴)

 

버그

3. 깃허브 camo캐시로 인한 버그

서비스 릴리즈 단계에서 유저 contribution count가 제대로 집계되지 않는것 처럼 보이는 문제가 발견되었다. 

-(1회 유저 contribution count가 저장된 후, 이후 부터는 서버에는 저장되는데 브라우저에는 contribution count가 업데이트 되지않았음)

처음엔 내 WAS에서 깃허브 API를 불러오는 부분에 문제가 있는줄 알았는데, 디버깅 결과 DB에는 유저 정보가 정확히 업데이트 되었다. 즉, 내 서버에서는 리소스에 대한 올바른 representation을 전달했으므로, 문제는 버그가 발생하는 클라이언트측(github의 readme.md)에 있었다.

 

"""

알아본 결과, github는 http프로토콜로 요청된 image타입을 github.camo***(정확한 이름은 모른다.) 프록시 서버를 거쳐서 클라이언트로 보내고, 이 과정에서 camo서버에 representation이 캐시되던것 이였다. 

"""

 

간단하게 해결했는데(쉽게 해결했다는 의미가아닌, etag나 캐시 유효기간 계산을 하지 않았다는 의미이다.), representation을 캐시 하지않도록 설정해버렸다.

(camo프록시 서버가 이미지의 애니메이션 재생또한 막았기 때문에, 캐시를 하지않아야 새로고침시 애니메이션 재생이 원활하게 되었다.)

'프로젝트 > Toy 프로젝트' 카테고리의 다른 글

[project] CommitCombo  (0) 2021.05.27