본문 바로가기

토이 프로젝트

채팅 애플리케이션 만들기 - v2.0

지난 1월 'React 코드 읽기'와 'graphQL Subscription 경험하기' 두 가지 주제로 채팅 앱 토이프로젝트 v1.0을 만들었습니다. 개발기간은 첫 커밋부터 약 2주 남짓으로 퇴근 이후와 주말의 여유시간을 투자하였습니다. 목표했던 경험과 함께 마무리되었지만, 최근 클라우드 및 관련 기술들을 공부하며 이 애플리케이션을 실습용 앱으로 전반적인 개선과 고도화하였습니다.

 

모든 코드는 github 에서 확인 가능합니다.

 

요구사항

요구사항은 기능/비기능 요구사항으로 나누었습니다.

기능 요구사항으로는 인증, 인가와 채팅방 목록 실시간 갱신 등입니다.

비기능 요구사항으로는 시스템 구조적인 개선컨테이너 이미지 개선 이 있습니다.

1. 실시간 서비스와 api 서비스 분리
2. 컨테이너 이미지 사이즈 개선
3. 회원 관련 기능 개선
  3-1. 회원가입
  3-2. 로그인
4. 채팅방 관련 기능 개선
  4-1. 마지막 메시지 및 안 읽은 메시지 개수 실시간 업데이트
  4-2. 방 입장, 방 생성 버튼, 방, 유저 이미지 등 전반적인 css 수정

 

실시간 서비스와 API 서비스 분리

기존 백엔드 서비스는 모두 nest 프레임워크로 구성된 단일 애플리케이션입니다. 백엔드 서비스의 기능 안에서도 성격에 따라 부하의 정도가 다를 것이며, 요구되는 하드웨어 스펙(또는 클러스터 구성에서 노드 개수)이 다를 것입니다. 예를들어 회원가입을 비롯한 대부분 백엔드 기능은 유저 1명 당 신규 가입과 로그인 시에만 요청이 발생하므로 상대적으로 낮은 트래픽이 예상되지만 채팅에 필요한 소켓연결과 채팅메시지 보내는 기능은 한 유저 내에서도 자주 발생할 것입니다. 또한 만약 앱 푸시 알림과 같은 3-party 기능이 추가된다면 이는 거의 실시간에 해당하는 요구사항일 수 있으므로 MQ를 사이에 둔 채 서비스가 분리될 수도 있겠다는 생각도 보았습니다.

MSA 같은 키워드를 의식하며 기능을 분리하진 않았습니다.
분리가 필요하다고 생각되는 곳에서만 서비스를 분리하였고 결과적으로 상태 유지 서비스(chat-service)와 상태 미유지 서비스(backend-service)로 분리되었습니다. 

 

위 같은 이유로 기존 채팅을 불러오고 발송하는 등, 기존 documentDB(mongoDB)와 통신했던 기능을 모아 chat-service를 분리하고 나머지에 해당하는 유저, 인증, 채팅방 등을 생성 관리하는 기능은 backend-service로 구성하였습니다. 새로운 서비스인 backend-service의 기술스택은 익숙함의 이유로 java/spring으로 채택하였으며, 데이터는 RDBMS(postgreSQL)에 저장하였습니다. 대부분의 기능이 backend-service로 이관되며 nestjs에서의 동일한 기능은 deprecated 처리 및 추후 삭제 예정입니다.

 

그리하여 V2.0의 시스템 구성은 아래와 같습니다. spring과 postgres 컨테이너가 추가되어 총 7개의 컨테이너로 구성됩니다.

V2.0 시스템 구성도

컨테이너 이미지 사이즈 개선

문제가 있다고 판단한 부분은 client  react 앱이었습니다. v1.0의 Dockerfile은 아래와 같습니다.

FROM node:lts-alpine

WORKDIR /app

COPY ./package.json ./
RUN npm i

COPY . .

RUN npm run build

CMD ["npm","start"]

기존의 방식에서 js 런타임으로 node.js를 사용했습니다. node 베이스 이미지에 모든 소스를 복사한 뒤, 패키지 설치 및 빌드하여 npm start 명령어를 실행합니다. 프로젝트는 Create React App으로 초기 구성을 했기 때문에 react-scripts 라이브러리의 start.js 에서 개발 서버를 생성하며 로컬에서 실행하는 것과 동일하게 앱을 실행합니다.

이 구성에서 처음 문제를 인지한 것은 이미지 크기였습니다. v1.0 코드 기준으로 약 1.2GB였었습니다. 큰 기능이 없는 조그만 앱임에도 비정상적인 이미지 크기임이 확실하여 개선점을 찾아보았습니다.

V2.0 소스를 기준으로 빌드 해보니 1.79GB인 모습. 빌드에는 약 5분이 소요되었습니다.

눈에 띄었던 것은 run npm run build 였습니다. 이 구성에서는 빌드 결과물을 사용하지 않고 로컬에서와 동일하게 앱을 실행하기 때문에, 완전히 무의미한(!) 레이어였습니다. 또한 npm start 를 쓰기 위해  package*.json 파일이 같이 빌드되어 크기의 대부분을 차지하게 되었습니다. 토이 프로젝트였고 비교적 신경 쓰지 않았던 영역이었지만, 비효율을 넘어 잘못된 구성이었습니다. 해결책으로 docker multi-stage build 전략을 통해 빌드 결과물만 이미지 빌드에 담았으며, 웹서버(nginx)에서 동작하도록 수정하였습니다.

FROM node:lts-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]

개선 이후 46.5MB로 약 40배의 차이를 보였습니다.

 

 

기타 기능적 요구사항들

- 회원가입과 로그인

기존 아이디만으로 구분하던 회원을 회원가입과 로그인 기능을 만들어 인증/인가를 구현하였습니다.

기본적으로 jwt방식을 사용하였으며 권한이 필요한 요청 시 Authorization 헤더는 Controller에서 직접 파싱 하였습니다.

과거 spring 인증/인가에 spring security 구성이 필수라고 생각했던 적이 있지만 이 프로젝트에서는 적용하지 않기로 하였습니다. 여기서는 jwt 방식으로 인증을 하는데, jwt 인증 기술 난이도에 비해 효율적으로 사용하기 위한 spring security 프레임워크 지식이 더 방대하다고 생각하기 때문입니다.

 

- 채팅방 구독 추가

기존에는 참여 중인 채팅방에 접속해야 구독을 시작하고 메시지를 수신하였습니다. 이번 개선에서 현재 참여 중인 모든 채팅방을 구독하고 마지막으로 전송된 메시지 및 아직 읽지 않은 메시지 개수를 표기하도록 하였습니다. 현재는 채팅방 목록에서의 마지막 메시지와 실제 채팅 메시지가 같은 MessageResponse 객체를 사용합니다.

 

 

여전히 미완성된 부분

v1.0에 이어서 역시 미루어야 할 것들이 있었습니다. 대부분은 지금 단계에서 불필요하다고 생각하거나, 이후 작업과 같이 변경하면 좋을 것 같은 것들이었습니다. 대표적인 예로 에러핸들링이 있습니다. 현재 런타임 예외에 대한 세분화나 핸들링이 되어있지 않습니다. 예를 들어 패스워드와 패스워드가 불일치한 상태로 회원가입 버튼을 누른다면 어떤 메시지도 없이 그저 동작하지 않습니다. 이 부분은 추후 모니터링 및 로깅을 구성하며 같이 고민할 예정입니다.

 

회고

v1.0에서 경험해보고 싶었던 주제를 정해서 만들어보며 (어떻게든) 동작하는 앱을 만들었고, 그 자체로도 좋은 경험이었습니다.

지난 몇달 사이에 클라우드 기술을 공부하며 동작 외 더 넓은 시야로 프로젝트를 바라보게 되었고 개선하는 과정에서 고민하는 것은 즐거운 경험이었습니다.

앞으로도 이 앱을 통해서 해보고싶은 경험들이 많습니다. 그리고 어느정도 명확해졌습니다. 역시나 부족한점이 많은 앱일 수 있지만 점점 개선해볼 예정입니다.

'토이 프로젝트' 카테고리의 다른 글

채팅 애플리케이션 만들기 - v1.0  (0) 2024.01.27