서비스 리액트 전환기

이번 포스팅에서는 기존 링크스타터 서비스를 React로 전환하게 된 이유, 전환 과정에서 어려웠던 점과 이를 해결해나간 과정들을 소개하고자 합니다.

서비스 리라이팅 계기

2022년 7월. 링크스타터 개발팀은 서비스를 다시 구현하기로 하였습니다. 비즈니스 모델 변경으로 인한 서비스 리뉴얼과 더불어 UI/UX 디자인 개편이 예정되어 있었고, 이에 맞춰서 DB 설계와 서버 및 클라이언트 개발까지 새롭게 진행하기로 하였습니다. 기존의 시스템을 버리고 모든 것을 처음부터 새로 만드는 일이었습니다.

리라이팅 계기

이게 무엇을 하는 코드인가요?

웹 개발자로서 일한 6개월 동안 홈페이지의 여러 가지 기능을 추가하고 개선하면서 변화가 필요하다고 생각했습니다. 단순히 ‘다른 사람이 작성한 코드가 별로다.’에서 파생된 결정은 아니었습니다. 알파 서비스를 런칭했던 2020년 9월부터 리뉴얼이 결정된 2022년 7월까지 약 30번가량 비즈니스 모델이 변경되었고 이에 지금은 사용되고 있지 않지만, 미래에 사용할지도 모르는코드가 많았고, 많은 외주업체의 손을 거쳐 간 서비스이니만큼 여러 가지 스타일이 뒤섞여져 있는 상황에 문서화 또한 되어있지 않은 상황이었습니다. 새로 합류한 개발자가 코드를 보고 이해하는 데 오랜 시간이 걸렸습니다. 정식 서비스를 론칭하기전 이러한 불필요한 코드를 과감하게 쳐내야 했습니다.

허울뿐인 프레임워크

서비스의 백엔드는 Flask를 이용하여 구현되어 있었고, 프론트엔드는 Flask의 템플릿 언어인 Jinja2가 사용되고 있었습니다. 하지만 서비스는 이러한 프레임워크를 사용하는데서 오는 이점을 전혀 살리지 못하고 있었습니다.

Flask의 라우팅 기능을 제외한 다른 기능은 사용되고 있지 않았으며, 관심사가 분리되어 있지 않고 모든 코드를 한 곳에 작성하여 가독성뿐만 아니라 유지보수에도 어려움을 주고 있었습니다.

## 기업이 공고 상세페이지 열람할때
@app.route('/companies/jobs/<job_id>')
def company_job_detail_view(job_id):
		sql1 = """공고 상세페이지 데이터를 불러오는 SQL쿼리"""
		sql2 = """기업 정보를 불러오는 SQL쿼리"""
    sql3 = """해당 공고 북마크 내역을 불러오는 SQL쿼리"""
    sql4 = """해당 공고 지원 내역을 불러오는 SQL쿼리"""

		data1 = pysql.execute(sql1)
		data2 = pysql.execute(sql2)
		data3 = pysql.execute(sql3)
		data4 = pysql.execute(sql4)
		
		"""
		데이터 가공하는 코드
    """
	
    return render_template('company_job_detail.html', manipulated_data)

...

## 개인이 공고 상세페이지 열람할때
@app.route('/people/jobs/<job_id>')
def company_job_detail_view(job_id):
		sql1 = """공고 상세페이지 데이터를 불러오는 SQL쿼리"""
		sql2 = """기업 정보를 불러오는 SQL쿼리"""
    sql3 = """해당 공고 북마크 내역을 불러오는 SQL쿼리"""
    sql4 = """해당 공고 지원 내역을 불러오는 SQL쿼리"""

		data1 = pysql.execute(sql1)
		data2 = pysql.execute(sql2)
		data3 = pysql.execute(sql3)
		data4 = pysql.execute(sql4)
		
		"""
		데이터 가공하는 코드
    """
	
    return render_template('person_job_detail.html', manipulated_data)

...

## 구직자 상세페이지
@app.route('/people/<person_id>')
def person_detail_view(person_id):
		sql = """구직자 상세페이지 데이터를 불러오는 SQL쿼리"""
		data = pysql.execute(sql)
		
		"""
		데이터 가공하는 코드
    """
	
    return render_template('person_person_detail.html', manipulated_data)

위에 예시 코드와 같이 링크스타터 시스템이 가지고 있던 문제점은 아래와 같습니다.

  1. 한개의 페이지에서 실행되는 모든 작업을 하나의 함수에서 처리하고 있었습니다.
  2. SQL 쿼리는 JOIN문을 사용하지 않고 불러온 후 메모리에 올려놓고 가공하여 사용하고 있었습니다.
  3. 주제별로 구분하지 않고 모든 코드를 하나의 파일에 저장하여 사용하고 있었습니다.
  4. 코드의 재사용 없이 같은 작업을 하는 코드를 반복하여 작성하고 있었습니다.

이런 ‘하드코딩’은 프론트엔드에서 역시 마찬가지였습니다. 기업이 자신의 공고를 열람할 때 보이는 화면과 구직자가 기업의 공고를 열람할 때 보이는 화면은 다른 점이 존재합니다. 링크스타터는 각각 self_job_detail.html, person_job_detail.html과 같이 직관적이지 않은 네이밍 컨벤션을 적용하여 관리하고 있었고, 공통된 부분을 재사용하기보다는 하드 코딩하여 사용하고 있었습니다. 만약 ‘공고 상세 페이지’의 디자인이 변경된다면 여러개의 파일을 수정해야 했습니다. 이렇게 관리되고 있는 html파일은 약 400개가 넘어갔고 더 이상 효과적인 유지보수가 불가능할 지경에 이르렀습니다.

배포의 어려움

프론트엔드와 백엔드가 분리되어 있지 않은 서비스의 단점은 배포입니다. 사내 프론트엔드 개발자가 UI를 수정하면 시스템 전체를 다시 배포하는 과정을 거쳐야합니다. 너무 비대해진 서비스를 배포하는 데 15분가량 소요되었기에 ‘버튼을 오른쪽으로 1px만 옮겨주세요’라는 요청도 매우 부담스러운 상황이 되었습니다.

과도하게 느린 속도와 서버 비용

동시 접속 사용자가 50명 미만, MAU가 3,000명 정도인 서비스의 서버 비용은 약 150만 원이 청구되고 있었습니다. 페이지별 로딩 속도는 평균 9초에 육박하였습니다. 페이지가 리프레시되거나 클라이언트 상에서 데이터를 업데이트하였을 때 변경된 데이터가 적용된 화면을 보기 위해서 모든 static 파일을 다시 다운로드하는 과정을 거쳤습니다. 이에 페이지별로 평균 20mb의 데이터를 다운로드 받기 시작하였고, 한 사람이 몇 번의 클릭만으로 엄청난 서버 비용을 청구하는 상황이 되었습니다.

채용의 어려움

Jinja2와 같은 비주류 스킬 스택의 단점은 채용이 어렵다는 점에 있습니다. 대부분의 개발자는 Jinja2라는 템플릿 언어가 존재한다는 것을 모르고 있었고, 팀에 합류하여 작업을 하다가 단기간에 퇴사하기도 하였습니다. Vue나 Angular와 같은 프레임워크였다면 프레임워크 사용법이 다를 뿐 큰 그림에서 접근법은 비슷하였지만, Jinja2와 같은 템플릿언어는 HTML을 직접 하드 코딩한다는 점에서 거부감이 심했던 것으로 판단됩니다.

리액트를 선택한 이유

리액트는 프론트엔드 영역에서 가장 인기 있는 프레임워크입니다. 가장 많은 수요가 있는 프레임워크로서 채용이 비교적 수월할 뿐만 아니라 가장 많은 레퍼런스가 존재한다는 점에서 작업 시 이점이 가장 크다고 생각했습니다. 또한 기존 프론트엔드 개발자들 역시 모두 리액트를 사용할 수 있었다는 점에서 가장 좋은 선택이라고 생각했습니다.

Next.js를 사용하지 않은 이유

Next.js 역시 인기 있는 프레임워크입니다. 리액트를 기반으로 만들어진 프레임워크로서 리액트를 알고 있다면 러닝 커브도 높지 않고 빠르게 적용할 수 있다는 장점이 있습니다. 리액트와 Next.js 사이에서 많이 고민하였습니다.

비즈니스 모델의 구조상 SEO가 중요하지 않았습니다. 링크스타터는 폐쇄형으로서 양방향 헤드헌팅 모델입니다. 구직자는 이력서를, 기업은 공고를 등록하면 링크스타터의 운영팀이 확인 후 추천해 주는 모델입니다. 그렇기에 ‘구직자 이력서’ 또는 ‘기업 공고’ 링크가 존재해야지만 접속할 수 있었기에 외부에 노출이 되지 않는 사이트의 특성상 SEO를 신경 쓸 이유가 없었습니다.

또한 모든 프론트엔드 개발자가 Next.js로 실무 경험이 없었다는 점에서 리라이팅 기간이 1달밖에 주어지지 않았던 프론트엔드 개발자들은 리액트로 개발하기로 하였습니다.

개발 과정에서의 고민

기존의 서비스를 점진적으로 변경하는 작업이었다면 매우 어려운 일이 되었을 것입니다. 다행히도 비즈니스 모델의 변경에 따른 서비스 리뉴얼과 UI/UX 디자인 변경이 겹치면서 백지에 새로운 그림을 그리는 작업을 하게 되었습니다.

리라이팅에 착수하기 전 이미 새로운 디자인 시안을 전달받았기에 좀 더 큰 그림을 그려가며 작업할 수 있었습니다. 순차적으로 전달받았다면 만들었던 컴포넌트를 새로운 디자인에 맞게 수정하는 작업을 했을 수도 있지만 모든 디자인을 한 번에 전달받았기에 컴포넌트 단위로 UI 작업을 한 후 페이지별 작업을 하는 Bottom-Up 방식으로 접근하였습니다.

테스트 코드

프론트엔드를 리라이팅하면서 테스트 코드에 대한 고민을 많이 하였습니다. 테스트 코드가 꼼꼼하게 작성된 경우 새로운 기능을 추가하거나 수정할 때 이에 대한 검증을 빠르게 할 수 있다는 장점이 있습니다. 하지만 UI 변경이 잦은 프론트엔드의 경우 테스트 코드는 말 그대로 ‘테스트를 위한 코드’로 변질될 수 있다는 단점도 존재합니다.

또한 링크스타터와 같이 비즈니스 모델이 자주 변경된다면 그에 따라 비즈니스 로직이 자주 변경되게 되고 결국 코드뿐만 아니라 테스트 코드를 수정하는 작업을 하여야 합니다. 이는 배보다 배꼽이 크게 되어버리는 상황이 됩니다. 실제로 서비스 리뉴얼이 결정되고 새로운 디자인 작업이 끝나고 코드 리라이팅 작업이 착수된 이후에도 몇 번의 기획 변경이 있었습니다. 결국 이와 같은 이유로 애플리케이션의 동작을 확인해 볼 수 있는 E2E 테스트를 포기하였습니다.

최종적으로 컴포넌트의 UI 검증을 위하여 Storybook을 사용하기로 하였습니다. 각각의 공통 컴포넌트들과 페이지별 Storybook 코드를 작성하였습니다. 페이지 단위 스토리를 작성한 이유는 프로덕션 또는 개발 환경에서 특정 화면을 지속적으로 확인해 보아야 하는데 그때마다 해당 페이지에 접속하는 로직을 수행하여야 하기 때문입니다.

리라이팅 후

서비스 성능

전환 전

레거시 라이트하우스 점수

레거시 라이트하우스 점수

전환 후

전환 후 라이트하우스 점수 1

전환 후 라이트하우스 점수 2

생각보다 낮은 점수에 매우 당황했습니다. Lighthouse 점수가 서비스의 성공 여부를 뜻하지는 않지만, UX 적인 측면에서 사용자들에게 더 좋은 서비스를 제공하는 데 도움이 되기 때문에 이 부분을 최적화하고자 하였습니다. Lighthouse가 언급했던 애플리케이션의 문제점은 아래와 같습니다.

라이트하우스 문제점

언급된 문제점에는 간단한 해결법이 존재했습니다. 리소스를 gzip 형태로 압축하여서 제공하도록 제시하고 있습니다. 이를 해결하기 위하여 사용 중인 Vite의 설정을 수정하였습니다. 파일의 크기가 1kb가 넘어가면 압축하도록 설정하였습니다.

import viteCompression from 'vite-plugin-compression';

export default () => {
  return {
    plugins: [viteCompression()],
  };
};

압축하였을 때 데이터 전송 자원을 70%가량 줄일 수 있었습니다.

수정 후 라이트하우스 점수 4

수정 후 라이트하우스 점수 5

아쉬웠던 점

처음 목표했던 정식 서비스 오픈일은 7월 17일이었습니다. 2021년 7월 17일 베타 서비스를 런칭하였기에 2022년 7월 17일은 1주년 기념이자 리뉴얼 적용일로서 적합하다고 생각됐습니다. 하지만 재단장하기로 결심한 후에도 비즈니스 모델을 정하지 못하여 2주가량 기획 회의만 하였습니다. 6월 마지막 주가 되었음에도 불구하고 무엇을 팔 것인가?에 대한 결정을 내리지 못하였습니다. 그렇기에 디자인 시안도 늦어졌고 개발 착수 또한 늦어졌습니다.

결국 정식 서비스 오픈일을 8월 1일로 미루고 작업을 시작하였는데, QA 시간까지 고려한다면 약 2주 만에 모든 작업을 끝내야 했습니다. 그렇기에 기능들을 빨리 쳐내는 것에 급급했고 세세하게 확인하지 못한 부분도 많았습니다. 또한 운영팀과의 소통도 전무하여 세 번째 사용자인 운영팀의 의견을 전혀 반영하지 못한 채 개발하게 되었습니다.

후기

리뉴얼이 끝났지만, 아직 해야 할 일이 많습니다. 당분간 새로운 기능을 추가하는 일은 없으나 빠르게 작업을 하면서 생겼던 문제점을 해결하고 리팩토링을 통하여 코드 퀄리티를 개선하는 작업을 하고자 합니다. 또한 리액트를 공부할 때는 Create-React-App을 사용하여 코드에 대해 고민만 하였는데 최적화 작업을 진행하면서 빌드 툴 역시 다룰 줄 알아야 한다는 것을 깨달았습니다.


Written by@Michael Hur
데이터 기반 의사결정을 하는 프론트엔드 개발자입니다.

GitHubLinkedIn