레거시 폭파 프로젝트 이야기 (두 달간의 회원 가입 서비스 레거시 개선 회고)
새로운 요구사항
올해 초 팀에 새로운 미션이 떨어집니다. 불법적 자금 세탁을 막기 위해서 AML(Anti Money Laundering. 불법자금세탁방지 심사)을 적용해야 하며 가입 단계에서 AML을 위하여 가입 단계에서 추가적인 정보를 입력받아야 했습니다.
올해 초 작업한 개인 셀러의 경우 AML 적용을 위해서 몇 개의 정보만 추가로 입력받고, 회원 정보에 AML 솔루션 연동하면 되었습니다.
그러나 사업자 셀러의 경우 AML을 위해서 가입단계에서 입력받을 정보가 상당히 늘어났습니다. 기존처럼 하나의 가입 페이지에서 가입 정보를 세 단계로 나누어서 목적에 맞게 입력 단계를 구분하기로 하였습니다.
셀러 가입 대행 서비스가 있을 정도로 셀러의 가입에서 많은 정보를 수집합니다. 따라서 임시 저장 기능을 추가하여 가입자가 추가로 확인이 필요해 가입을 완료하지 못할 경우를 대비하여 편의성을 높이는 작업이 필요하였습니다.
레거시를 폭파하기까지
클린코드와 거리가 멀지만 이미 비즈니스 상에서 훌륭한 역할을 하고 있다면 그 자체만으로 큰 가치를 갖고 있다고 생각합니다.
(Source 좌. Twitter)
그러나 회사가 창립 이래 존재했던 사업자 셀러 가입의 경우 13년간 유지보수(땜질) 되면서 소프트웨어의 복잡성이 올라갔습니다. (1) 가입 단계를 세 단계로 구분 (2) 중간 저장 제공이라는 두 가지 요구사항을 더더욱 수용하기 어려워졌습니다.
따라서 기존의 레거시 페이지에서 회사의 MSA 정책에 맞추어 기존 모놀리틱 사업자 셀러 가입을 회원 마이크로 서비스로 옮기기로 하였습니다.
기존 서비스 상황
사업자 셀러의 경우 창사 이후 개편이 이루어지지 않았기에 12년 넘은 로직이었습니다. 가입의 경우 개편 작업하기가 만만치 않기에 일단 한번 만들어놓은 서비스에 계속 덕지덕지 유지보수를 하는 상황이었습니다.
- 10년 전 UI로 현재 서비스와 이질적인 트랜드에 벗어난 가입 UI입니다.
- JDK 1.7, iBatise 로 되어있습니다.
- Spring Boot가 아닌 SKT에서 개발한 자체 프레임워크로 되어있습니다.
- 코드 수정 후 로컬에 빌드하려면 최소 7분의 시간이 소요되어 개발 생산성을 낮춥니다.
- 히스토리 관리가 되지 않아서 가입 정책 정리가 전혀 되어있지 않았습니다.
- 가입 성공 시 쿠폰 발급 등 제거된 정책이나 코드에서 제거되지 않아서 의미 없는 메서드를 호출합니다.
- 회사 창사 이래로 개편 없이 땜질 개발하여 각종 if 문이 코드 내 남발하는 상황입니다.
- 코드 복잡도가 높아 특정 값의 경우 DB 저장까지 레이어가 최대 9번 연속 호출해야 합니다.
- 모놀리틱 배포라 개발자가 직접 배포하는 것이 아닌 배포 요청을 통해 배포한다.
- 배포 요청부터 상용 배포까지 최소 30분의 시간이 소요됩니다.
- JSP에 JAVA 로직이 상당수 존재하였습니다.
어떻게 레거시를 청산할까?
기본적으로 기존 사업자 셀러 가입은 Form Submit으로 동작합니다. 같은 요청에 대해서 레거시와, 신규 서비스의 응답 결과를 비교하는 섀도잉도 불가능합니다.
테스트 코드를 작성해서 하나씩 리팩터링하는 것도 상당히 어려웠습니다. 이미 수십년간 if for로 길어질대로 길어진 코드입니다.
우아한 방법은 없었습니다. 코드를 읽고 하나 하나 클릭하고 입력하며 동작을 파악하면서 기존 서비스를 파악하였습니다.
레거시를 폭파하면서 생긴 어려움
1. 새로 구현해야하는 자바스크립트
기존 가입 페이지는 JQuery처럼 자체적으로 자바스크립트를 사용하기 용의하게 만든 자체적으로 개발한 자바스크립트 라이브러리를 사용하였습니다. 그러나 만들어진 지 오래 되었기에 더이상 유지보수 되지 않는 라이브러리입니다. 이를 JQuery로 새롭게 개발하여야 했습니다.
기존 코드를 기계적으로 JQuery에 맞추어서 변환하면 되지 않을까 싶지만, 제약조건에 맞지 않은 잘못된 검증, 누락된 검증 로직, 제거된 입력 폼에 대한 의미 없는 검증, 사용성을 저해하는 UI 동작 스크립트 등 크고 작게 수정해야 할 부분이 존재했기에 자바스크립트 코드를 그대로 변환할 수 없었습니다.
2022년에 JQuery가 웬 말인가 싶습니다. 실제로 많은 팀이 Vue를 이용하여 프론트, 백 분리를 진행하고 있습니다. 그러나 회원의 서비스는 프론트 백 분리 일정이 뒤로 미루어져 있으며, 관리의 용의성이라는 목적으로 아직도 JQuery + JSP로 프론트를 구성하고 있습니다.
2. 수 많은 검증로직
가입을 위해서 필수적으로 입력하는 140개의 데이터 필드가 존재합니다.
검증 로직은 사용자가 입력값이 규칙에 맞게 입력했는지 확인하는 과정입니다.
가입 단계에서 사용자에게 정확한 정보를 주기 위해서라면 하나의 입력값에 대해서 다음과 같은 고려사항에 맞추어 세부 검증을 한 후, html에 메시지를 보여주거나 alert로 알려주어야 합니다.
- 값을 누락하고 가입을 수행하는 경우
- 값을 강제로 누락하고 가입을 수행하는 경우
- 최대 길이를 초과하여 키 입력한 경우(Key-Press)
- 최대 길이를 초과하여 입력한 경우(Key-Press)
- 최소 길이를 입력한 입력 후 가입을 수행하는 경우
- 최대 길이를 강제로 입력 후 가입을 수행하는 경우
- 금칙어를 입력한 경우(Key-Press)
- 금칙어를 강제로 입력 후 가입을 수행하는 경우
- 허용하지 않은 문자를 키 입력한 경우(Key-Press)
- select, radio의 경우 허용하지 않은 값으로 강제로 설정한 경우
HTML input 태그의 Radio의 경우 기본값이 설정되어있지만, 자바스크립트로 Radio 선택을 해제할 수 있습니다. 또한, input 태그의 value에 자바스크립트로 값을 줄 수 있습니다. 그렇다면 키를 입력할 때마다 길이를 초과했는지 등등을 검증하는 로직은 우회됩니다. 값을 저장하는 시점에 잘못된 값을 강제로 지정했는지 검증을 해야 합니다.
자바스크립트에서 검증하더라도 서버 쪽에서 한 번 더 추가 검증을 합니다. 대부분 자바스크립트에서 수행한 검증을 그대로 진행합니다. 하지만 비밀번호와 같이 민감 정보는 HTTPS 통신을 하더라도 별도로 암호화를 해서 전달합니다. 민감 정보를 서버 쪽에서 복호화함으로써 부적절한 방법으로 암호화하였는지 검증하는 자바스크립트에는 없지만 서버 쪽에만 존재하는 검증 로직이 있습니다.
가입 단계에 입력할 게 많으니 기본적으로 작성해야 할 검증 로직이 상당히 많았습니다. 처음에는 쉽게 생각하였으나 아침 8시에 출근해서 밤 11시까지 하루종일 제이쿼리를 일주일간 꼬박 작업하여도 부족한 시간이었습니다.
3. IE 대응
개발 완료를 이 주일 정도 앞두고 IE 대응을 해야 한다는 이야기를 들었습니다. IE 종료와는 별개로 꽤 많은 사용자가 전 달에 IE로 방문했다는 지표가 있었으며 올해는 공식적으로 IE를 지원해야 한다는 것이었습니다. IE를 고려하고 개발하지 않았으며 수 천 라인의 JS를 이미 개발한 후여서 암담했습니다.
처음에는 IE 미지원 자바스크립트로 인해 기본적인 AJAX 통신조차 안 되던 상황이었습니다. 다행히 팀의 다른 분이 IE 지원을 위해서 도움을 주셨습니다. str.replace, name selector 등등 이런 것도 안되나 싶은 것이 많았습니다.
QA 기간에 수정 건을 대응하다가 습관적으로 IE 지원 안 되는 방식으로 자바스크립트 개발을 했다가 다시 QA 수정 건으로 올라온 적이 있습니다. 이제는 IE 지원을 그만하였으면… 생각이 간절하였습니다.
4. 하드 코딩된 상수
기존 가입의 경우 select 태그의 값, API 응답 값, 가입 시 기본 설정값 등 무수히 많은 값이 01, 1111, Y와 같이 문자열 상수로 되어있었습니다.
API 응답의 경우 간단하게 컨트롤러의 응답 반환 값을 보거나 너무 오래된 외부 연동의 경우 DB에 이력을 저장한다면 해당 DB 호출 기록을 조회하거나 API 응답 결과를 파악하였습니다.
그러나 가입 시 객체에 수백 개의 기본값으로 설정하는 값의 경우 의미를 알기 힘들었습니다. 특히 HTML input 테그에 기본적으로 값이 설정된 경우 축약어가 사용되었고, 어떤 상황에서 값이 변하는지 파악하기 어려웠습니다. 특히 축약어의 경우 왜 만들어졌고, 어떤 의미인지 관련 히스토리가 존재하지 않기 때문에 특별한 방법이 없이 많은 가입을 해보고, 일일이 테이블을 조회해가며 값이 변하는지, 어떤 조건에서 변하는지 파악해야 했습니다. 이런 과정을 통해서 파악한 값의 경우 주석 혹은 위키 문서를 남김으로써 관련 데이터 의미를 파악하였습니다.
그 외에 도메인 상수면 enum으로 정의하여 하드 코딩 값을 제거해 나갔습니다.
5. 빙하처럼 숨겨진 정책
가입이라는 간단히 검증을 잘하고 DB에 밀어 넣으면 되는 서비스인 것 같지만 실제로는 서비스를 시작하는 첫 시작인 만큼 생각보다 복합한 로직이 빙하처럼 숨어있습니다.
발견한 수많은 빙산 아래의 정책 중에서 한 가지 소개해 보겠습니다.
- 가입을 완료한 후 이메일인증 대기 상태에서 셀러오피스 로그인한 경우 → 토큰 방식 로그인
- 핸드폰 번호 인증 혹은 이메일 인증 완료로 가입하고 셀러 오피스에 로그인한 경우 → 인증 쿠키 생성
위의 로그인 정책적인 차이 때문에 새롭게 개발한 증빙서류 업로드 페이지에서 토큰 방식 로그인을 처리하지 못하고 오류 페이지를 보여주었습니다. 특히 셀러오피스의 경우 다른 프로젝트의 코드이기에 기존 레거시 코드만 봐서는 알 수 없는 부분이었습니다.
대체로 정책 관련해서 생기는 문제의 경우 어느 정도 개발이 끝나가는 상황에서 발생하기에 막판 개발이 바빠지는 데 일조하였습니다.
6. 기존 코드 분석의 어려움
기존 코드에서 중요한 부분은 가입 로직입니다. 비단 사용자 입력 값뿐만 아니라 구매 등급 등 많은 기본 데이터를 저장합니다. 가입 시 수많은 데이터를 저장하는데 서비스 오픈 이후 뒤늦게 하나의 값이라도 잘못 저장한다면 낭패도 이런 낭패가 없을 것입니다.
그렇기에 기존 가입 로직을 잘 분석해야 했습니다. 기존의 모놀리틱 자체 자바 웹 프레임워크는 기본적으로 복잡한데 여기에 수십 년간 유지 보수된 서비스입니다. 더구나 아키텍처가 중간에 변경되면서(도메인 기반에서 레이어 기반으로) 코드의 호출 뎁스가 많고(최대 11 뎁스 까지 봤습니다.) 여러 클래스에 분산되어있었습니다. 하나하나 기능마다 호출하는 메서드를 위키에 작성하고 어떤 기능을 하는지 분석하고 새로운 서비스에 구현하였습니다.
해당 작업을 마친 이후 다양한 케이스로 기존 가입을 진행하여 신규 가입과 DB에 저장된 값을 비교하였습니다. 신규 가입은 중간저장 정보를 Redis에 저장하기에 가입 정보 Json을 넣고 테스트하고 싶은 부분만 변경해가며 테스트할 수 있었지만, 기존 가입은 일일이 입력해야 하는 번거로움이 있었습니다.
성과
(우) 개편 후 사업자 셀러 약관 페이지
216 클래스가 생성&수정, 23,311라인이 추가, 256라인 제거하였습니다.
- 신규 UI를 적용하였습니다.
- 중간 저장 기능을 제공합니다.
- SKT 구 프레임워크에서 Spring Boot로 전환하였습니다.
- 로컬 빌드에 최대 30초로 빌드 시간이 기존 대비 최소 14배 줄었습니다.
- 깜깜이 정책에서 가입 정책을 기획에서 정리하여 개발하였습니다.
- 가입시 사용하지 않은 코드를 제거하였습니다.
- 회원 가입시 잘못 저장하는 값을 전부 수정하였습니다.
- 인증 토큰은 DB에서 Redis로 전환하였습니다.
- 개발자가 직접 배포하며 상용 배포 완료까지 최대 5분의 시간이 소요됩니다.
- JSP에 존재하던 JAVA 로직을 제거하여 레이어 아키텍처에 맞추어 개발하였습니다.
마치며
2주 80시간 근무제. 주 52시간 최대 근무 가능하니 최대 등록 가능 시간은 104시간입니다.
(*88시간은 워라벨이 좋아서가 아니라 휴일 이틀이 있어서 최대로 등록할 수 있는 시간이 88시간이었습니다.)
프로젝트를 하면서 9주간 52시간을 채워서 일하였습니다.
레거시를 마이크로서비스로 이관하는 가장 쉬운 방법은 코드를 CNP(Copy & Paste, 복붙 방법론) 방법론을 사용하는 것입니다.
(램브란트. 포도원 품꾼의 비유)
존 러스킨의 ‘나중에 온 이 사람에게도’ 책 제목의 배경이기도 한 성경의 포도원 비유가 있습니다.
오전 9시에 온 사람에게도, 오후 5시에 온 일꾼에게도 동일한 임금을 지불하는 포도원 주인을 보고 일찍 온 일꾼이 불평하는 이에게 이는 ‘내 뜻에 달린 것이다’ 라고 말하는 주인 이야기가 나옵니다.
이를 현재 하고 있는 일에 대입해서 바라본다면, 먼저 일한 사람, 그리고 나중에 팀에 합류한 사람에게 과실을 나누길 위해서라면 먼저 온 사람이 좋은 코드, 개선된 서비스를 만들어야 나중에 온 이에게 동일한 과실을 나눌 수 있을 것입니다.
먼저 온 자의 수고가 길고, 해야 할 일이 많습니다. 그러나 포도원 주인의 ‘내 뜻에 달린 것이다’ 처럼 나눌 수 있는 자가 되는지, 아니면 현재에 머무는지는 전적으로 저의 뜻에 달린 것일 것입니다.
이번 작업을 통해서 먼저온 자에게나 나중온 자에게 동일한 나눔의 기쁨을 누릴 수 있기를 원합니다. <><