Spring-Intern-Series 1

4 minute read

2021년 3월 겨울방학 기간에 인턴으로 일했던 곳과 인연이 지속되어 봄학기에도 함께 하기로 하였다.

개인적으로는 겨울방학에 한 프로젝트를 더 이어가고 싶었지만 비지니스적이나 내부적인 결정으로 다른 프로젝트를 진행하게 되었다.

그 프로젝트 역시 겨울 방학동안 진행된 프로젝트였지만 내가 관여한 부분이 없어서 프로젝트 주제와 완성된 페이지외에는 잘 몰랐다.

가장 첫번째 미션은 기존 코드 유지, 보수하여 확장성있는 코드로 만들기 였다.

지금까지 내 코드, 혹은 같은 프로젝트 팀원의 코드 정도만 봐봤지 아예 다른 프로젝트의 다른 사람의 코드를 보고 건드려본적은 없었기 때문에 걱정이 이만저만이 아니었다.

그래도 전에 만드셨던 분들이 문서화와 이슈정리를 굉장히 잘해두셨기 때문에 코드를 보고, 내부 흐름을 이해하는 데 그리 어렵지 않았다.

사실 이렇게 말했지만 거의 일주일에서 이주일동안 코드만 보고 개선점을 생각했던 것 같다

결과적으로 나와 이사님이 머리를 맞대고 고민한 결과 다음과 같은 문제점들을 발견했고, 수정하기로 했다.

  1. 프로젝트 레포지토리 관리
  2. 내부 코드들의 재사용성 부족
  3. 확장 가능성 및 유지 보수성 부족
  4. 일부 코드의 콜백지옥

개인 프로젝트가 아닌 회사의 프로젝트이기 때문에 프로젝트의 자세한 사항이나 세부적인 소스코드의 내용은 첨부하지 못했습니다. 양해 바랍니다.

1. 프로젝트 레포지토리 관리

해당 프로젝트는 유저의 종류에 따라 제공되는 데이터, 화면이 다르다. 그래서인지 프로젝트의 관리를 User1의 frontend-backend User2의 frontend-backend 이런 식으로 구성하였다.

이를 그림으로 도식화 해보면 아래와 같다.

legacy1

이런 아키텍처를 이용한다면 User별로 따로 관리 할 수 있다는 측면에서 데이터, 기능들을 나누기 쉽다는 장점이 있다. 하지만 프로젝트가 확장되면서 User1과 User2 모두에게 제공되어야하는 데이터나 로직, 뷰들에서 각각의 프로젝트에서 중복되게 관리를 해줘야하는 문제가 있다.

또한 User의 종류가 n명으로 늘어나게 된다면 n개의 프로젝트가 생성되어야한다는 측면도 확장성 면에서 개선이 필요했다.

개선될 아키텍처는 다음과 같다.

legacy2

이렇게 단일 frontend와 backend를 유지하면서 User에 따라 변경되어야 할 뷰나 데이터 및 로직은 내부적으로 관리하고, 공통된 부분의 관리를 수월하게 하려고 한다.

2. 내부 코드들의 재사용성 부족

코드상에서 여러번 사용되는 동일한 코드들이 발견되었다. 예를 들면 유저의 비밀번호를 해싱하여 디비에 저장하는 부분에서 매번 bcrypt모듈을 사용하여 salt값을 주어 해싱하고 있었다.

이를 하나의 함수형태로 만들어서 관리하고, 내부적으로 이를 불러쓰는 형태로 바꿔준다면 좋을 것같다고 생각했다.

3. 확장 가능성 및 유지 보수성 부족

이는 2번의 내용과 큰 틀에서는 비슷한 내용이다.

해당 프로젝트의 코드들이 단일 레이어(layer) 즉. 라우팅관련 부분부터 디비 접속 및 쿼리 부분까지 모두 동일 함수 내에서 동작하고 있다.

본 코드는 내부 흐름을 유사하게 만든 수도코드입니다.

//  회원가입
router.post('/new', function(req, res, next) {
    const new_user = req.body;
 
    let id = new_user.id;
    let password = new_user.password;
    let userType = new_user.userType;
		...

    const saltRounds = 10; // any salt number

    // password hashing
    bcrypt.genSalt(saltRounds, function(err, salt){
        bcrypt.hash(password, salt, function(err, hash){

            //insert new user
            let sql = `INSERT INTO user(id, password, userType ... ) 
            VALUES( ${(id)}, ${(hash)}, ${userType}, ...)`
            DBconn.query(sql, function(err, result){
                if(err) throw err;
                console.log('Success to insert user');
            });
        })
    });
    res.end();
});

위 코드와 같이 url 엔드포인트 진입 시작점부터 쿼리문 수행후 응답까지 모두 한 함수 내부에 존재한다. 또한 bcrypt의 사용부분이 내부의 쿼리문을 제외하고 동일한 형태로 다른 곳에서도 사용되고 있었다.

지금까지는 큰 문제가 없었을지라도 프로젝트가 확장되었을 때 한 기능의 조그만 변화가 매우 큰 재앙을 일으킬 수도 있는 문제의 소지가 있기 때문에 개선될 필요가 있다.

또한 2번에서 언급했던 재사용성의 부족이 이와 같은 코드 구조 때문이었고, 만일 해싱부분의 로직이나 일부를 변경해야할때 해싱작업이 일어나는 모든 코드를 찾아서 변경해야하는 문제점이 있다.

4. 일부 코드의 콜백 지옥

일부 코드에서 중첩된 콜백이 사용되어 가독성이 낮았다.

이는 트랜젝션처리 부분에서 더욱 극심했다.

router.get('/something', function(req, res, next) {     

		some logic...

    let sql1= "SELECT * FROM ~ WHERE ~= ~";
    DBconn.query(sql1, function(err, result){
					
				some logic...

        let sql2= "SELECT ~FROM ~ WHERE ~ = ~";
        DBconn.query(sql2, function(err, result){
            
						some logic...

            let sql3= "SELECT ~ FROM ~ WHERE ~ = ~";
            DBconn.query(sql3, function(err, result){

                ////////////////////////////////////////////////////////////////
                ///////// some logic a lot!

                DBconn.query("SELECT * FROM ~ ", function(err, result){

	                  ...something to do...

                    res.end();
                });
            });
        });
    });
});

위 코드와 같이 순차적으로 처리되어야하는 쿼리들이 있을때 콜백지옥이 극심해진다.

또한 위 코드를 보면서 발견하게 된 것인데 해당 코드는 트랜젝션을 보장하지 못한다.

mysql모듈에서 query함수는 트랜젝션 처리를 위한 것이 아니고 이를 위해 제공되는 다른 함수가 존재한다. 따라서 위 코드는 쿼리문의 순차적인 부분은 보장하지만 에러가 발생시 롤백에 대한 부분은 보장하지 못한다.

5. 해결방안

문제점들을 파악했으니 이제 알맞은 해결책을 찾아야한다.

1번을 제외하고 2~4번에 대한 해결 방안으로 OOP 컨셉을 가져가기로 결정했다.

OOP의 높은 확장성, 유지 보수성 등이 가장 큰 이유였다. 물론 처음 시도할 때 클래스 설계라던지 기초 작업에서 이전보다 많은 비용이 들겠지만 앞으로의 확장 가능성을 봤을때 충분히 감내할 만했다.

구체적으로는 행위의 주체 혹은 대상들을 클래스로 정의하고, 그들이 할 수 있는 일들을 메소드의 형태로 제공되게 할 것이다. 이 과정에서 상속, 다형성등 다양한 OOP적 개념이 필요할 것으로 예상된다.

또한 클래스 이외에도 layer별로 역할을 나눠 따로 관리하려고 한다.

레이어드 아키텍처의 패턴을 따라가면서 각각의 역할을 구분짓고 나눠 관리하려고 한다.

layerd

이미지 출처 : https://www.jianshu.com/p/1241f19905f9

이 모든 것들과 다른 종합적인 부분을 고려하여 기존의 프로젝트의 내부 로직과 데이터들은 그대로 가져간채 프레임워크, 구조등을 새롭게 바꾸기로 하였다.

프레임워크로는 Nest Js를 사용했다.

Node 기반의 서버사이드 프레임워크들의 구조적인 문제를 해결해주고 typescript를 통한 OOP를 지원해주기 때문에 선정하였다. 물론 Java Spring이나 다른 프레임워크들도 있었지만 런닝커브가 있어서 익숙하고, 레퍼런스가 많은 것을 선택했다.

6. 앞으로

이후 포스팅에서 기존 프로젝트를 마이그래션하는 과정에서 구체적으로 어떻게 OOP를 적용했고, 어떻게 개선했는지 다뤄보려고 한다.

7. 기존 프로젝트 진행자들에게….

막상 글을 다 쓰고 다시 읽어보니 너무 기존 프로젝트가 별로라는 식으로 써진 것같아서 좀 미안해졌다.

사실 기존 코드들과 문서들을 보면서 그동안 이전 동료들이 치열하게 고민하고, 노력했던 부분들이 있었고, 그 노력들이 없었다면 내가 이렇게 개선점을 생각할 수도 없었을 것이다.

또한 보면서 많이 배웠고, 그들의 생각을 간접적으로나마 나눠볼 수 있어서 좋은 시간이었다.

마지막으로 같이 인턴하면서 해당 프로젝트를 수행했던 동료들에게 감사한다는 말을 전하고 싶다.

Leave a comment