노인복지관 프로젝트 문제점과 개선 계획

기존 프로젝트 구조와 배포 방식의 문제점을 정리하고, 개선을 위한 계획을 세워보겠습니다.

작년 11월에 개발하기 시작한 노인복지관 프로젝트와 관련해서 그동안 프로젝트 노션에는 작업 내용을 정리했지만 블로그에 포스팅한 적은 없었습니다. 프로젝트 개편을 앞두고 있는 현재, 처음부터 풀스택 개발을 혼자 하면서 느꼈던 불편한 부분들을 정리하고 앞으로 어떤 방향으로 개선해 나갈지 계획을 세우고 글로 남겨볼까 합니다.

단일 레포 구조 도입 목적

처음 프로젝트를 시작할 때, 프론트엔드와 백엔드를 분리하지 않고 하나의 Github Repo에 구성했습니다. 개인 프로젝트이기 때문에 처음부터 완벽한 구조를 갖추기보다는 빠르게 개발을 진행하고 싶었던 마음이 컸습니다.

  1. MERN 스택을 사용하면서 프론트엔드와 백엔드를 분리하지 않고 하나의 레포지토리로 개발해서 개발 속도를 높인다.
  2. 프로젝트 배포시 서버와 클라이언트를 함께 배포해서 배포에 드는 리소스를 줄인다.

하지만 1차 배포가 끝나고, 유지보수 기간에 들어서면서 단일 레포 구조의 단점이 더욱 크게 느껴지기 시작했습니다.

장점이 장점이 아니었다.

노인복지관 기존 구조

이미지를 보면 정말 많은 문제점들이 눈에 띕니다. 하나씩 살펴보겠습니다.

시간이 흐를수록 개발 속도가 느려진다.

프로젝트 초반에는 개발속도가 정말 빨랐습니다. 혼자 로컬에서 프론트엔드와 백엔드를 동시에 개발하고 작업이 완료되면 변경된 결과물 기록을 위해 github main branch에 push하고, 배포는 로컬에서 수동으로 진행했습니다.

// local CLI command
docker build . --platform linux/amd64 -t [프로젝트 이름]
docker push [프로젝트 이름]
cd
cd 노인복지관
ssh -i [key.pem] ec2-user@[ec2 public ip]

// ec2 CLI command
docker stop [프로젝트 이름]
docker rm [프로젝트 이름]
docker pull [프로젝트 이름]
docker run -d -p 80:80 [프로젝트 이름]

이렇게 진행하다보니, 시간이 지날수록 로컬에서 작업한 내용을 ec2에 배포하는 과정이 번거로워졌습니다. 배포가 오래걸리기도 했고, ec2에 배포하는 과정에서 오류가 발생하면 디버깅하기도 어려워서 수없이 낑낑거리며 시간을 보낸 기억이 있습니다. 그리고 위 command를 보면, 배포를 하기 위해 수동으로 docker를 stop하고 rm하는 과정이 있는데, 이로 인해 배포 중간에 서비스 중단 시간이 길어지는 문제도 있었습니다.

제일 큰 문제는, 클라이언트 프로젝트 빌드 결과물을 서버 프로젝트 루트 디렉토리에 넣어서 단일 docker image를 ec2에서 실행하는 배포 방식이었습니다. 초기에 인프라 지식이 없어서 최대한 간단하고 빠르게 적용할 수 있는 방식을 적용한 것인데.. 너무 큰 패착이었습니다.

  1. 클라이언트의 변경사항이 있을 때마다 서버도 함께 빌드하고 재배포해야 한다. 그래서 당연하게도 서버 프로젝트의 빌드 시간이 늘어나고, 단일 docker image의 용량이 커지는 문제가 있다.
  2. 비슷한 맥락으로 서버에 변경사항이 있을 때마다 클라이언트도 함께 중단 후 재배포되어서 서비스가 중단되는 문제있다.(무중단 배포가 아닌 이상 당연한 현상일까요..?)

단일 레포, 단일 브랜치 관리의 장점을 못 살린다.

단일 레포에 MERN 스택 프로젝트를 구성하면서 초기에는 개발 속도가 정말 빨랐습니다. React, Node.js, Express.js 모두 저에게 비교적 익숙한 JS 기반이기도하고, 러닝 커브가 다른 스택들에 비해서 높지 않아 백엔드 관련 지식이 거의 없는 상태에서 학습 후 백엔드 개발을 하기가 비교적 수월했습니다.

그리고 개발 외적으로 추가 리소스를 사용하지 않고 개발을 진행할 수 있었습니다. git 전략, 코드 리뷰, 브랜치 전략 등 을 고민할 필요가 없었고, 프론트엔드를 개발하다가 백엔드 개발로 넘어갈 때 branch를 변경하는 등의 추가 작업이 필요 없었습니다.

하지만 시간이 흐를수록, 어쩌면 당연하게도 위 장점들은 장점이 아니었습니다. 코드의 품질도 나빠지고, 얕게 학습하고 백엔드 구성을 했기 때문에 보안 문제나 네트워크 문제 등이 발생했을 때 대처하기가 어려웠습니다. git 전략이나 브랜치 전략도 없었기 때문에, 프로젝트 유지 보수 기간에 버전 관리나 과거 버전의 코드를 참고하기도 어려웠습니다.

여기에 초기에는 생각하지 못했던 지점인데 단일 레포에서 JS 기반의 기술 스택들을 사용하다보니, 각 프로젝트에서 중복으로 사용되는 로직들, 예를 들어서 util 함수나 상수, TS를 사용한다면 타입 정의 파일 등을 공통으로 관리할 수 있을까 고민했던 적이 있습니다. Common 프로젝트를 만들어서 공통 로직을 관리하고, 각 프로젝트에서 Common 프로젝트를 참조하는 방식으로 구성하면 되지 않을까 생각했지만, 완전히 분리되어 있는 프로젝트 패키지 구조와 의존성이 있는 배포 방식 때문에 적용하지 못 했습니다.

그래서 앞으로의 계획은?

일단 목표를 정했습니다.

  1. 공통 모듈 관리하는 프로젝트를 만든다.
  2. 수동 작업을 최소화하고, 자동화 환경을 구축한다.
  3. 추후 함께 작업할 팀원이 합류시 동일한 환경에서 원할하게 프로젝트 참여 가능하도록 환경을 갖춘다.

공통 모듈 관리하는 프로젝트를 만든다.

📦root
 📂client
 📂server

 ... 기타 프로젝트 config

위와 같은 현 프로젝트 환경에서 공통 모듈 프로젝트의 2가지 개발 및 관리 방식을 고민했습니다.

  1. 공통 모듈 프로젝트를 위한 새로운 레포지토리를 만들어서 관리하고, npm package로 배포해 각 프로젝트에서 사용한다.
  2. 기존 프로젝트를 모노레포 환경으로 변경해서 공통 모듈 프로젝트를 추가하고, 각 프로젝트에서 공통 모듈을 참조해서 사용한다.

두 방식 모두 단점이 있습니다. 1번 방식은 기존 레포와 분리되어 있어 유지보수 및 관리가 힘들고, 2번 방식은 모노레포에 대한 경험이 없어서 초기 학습과 적용에 시간이 걸릴 것으로 예상됩니다.

초기에 시간 투자를 해서 자동화 환경을 잘 구축한다면 장기적으로 봤을 때 모노레포 도입이 더 효율적이고, 유지보수 및 관리 측면에서 더 좋을 것 같아 최종적으로 2번 방식을 선택했습니다.

📦root
 📂 .github
 📂 workflows
 release-client.yml
 release-server.yml
 📂 packages
 📂 common
 📂 server
 📂 client
 📂 admin

 ... 기타 프로젝트 config

공통 모듈 프로젝트는 다음과 같은 기능을 제공할 예정입니다.

  1. 공통으로 사용되는 util 함수, 상수, 타입 정의 파일을 관리한다.
  2. 각 React 프로젝트에서 사용할 공통 컴포넌트를 관리한다.
  3. Emotion 또는 styled-components를 사용할 때, 공통으로 사용할 스타일을 관리한다.

모노레포 툴과 git, 브랜치 전략이 고민인데, 내용이 많이 길어져서 이와 관련해서는 다음 글에서 자세히 다루어보겠습니다. 관련 내용을 간단하게 요약하면,

  1. npm vs lerna vs yarn workspace
  2. git flow vs TBD

수동 작업을 최소화하고, 자동화 환경을 구축한다.

기존에는 Vite + React 프로젝트를 서버 프로젝트의 루트 디렉토리에 빌드하고, 서버 프로젝트를 단일 docker image에 Copy하고 node를 실행했습니다. 그리고 로컬에서 수동으로 실행한 docker image를 ec2에서 pull하고 실행하는 방식으로 배포를 진행했습니다.

// 기존 vite.config.js
export default defineConfig({
  ... 생략 ...
  build: {
    manifest: true,
    outDir: "../server/public", // 빌드 결과물을 서버 프로젝트 루트 디렉토리에 저장
    rollupOptions: {
      input: {
        main: './index.html',
      },
    },
  },
});
```

```bash
// 기존 Dockerfile
FROM node:lts-alpine

WORKDIR /app

COPY package*.json ./

COPY client/package*.json client/
RUN npm run install-client --omit=dev

COPY server/package*.json server/
RUN npm run install-server --omit=dev

COPY client/ client/
RUN npm run build --prefix client

COPY server/ server/

USER node

CMD [ "npm", "start", "--prefix", "server"]

EXPOSE 8000

이렇게 로컬에서 수동으로 배포했던 작업들을 아래의 방식들을 적용해서 자동화할 예정입니다.

  1. Github Actions를 사용해서 CD 환경을 구축한다.
  2. Github Actions에서 ec2에 접속할 수 있는 방식을 결정하고 적용한다.
  3. Docker Hub를 사용해서 각 프로젝트의 이미지를 저장하고, ec2에서 개별 이미지를 pull해서 실행하는 방식으로 배포 환경을 구축한다.
  4. Nginx를 사용해서 클라이언트와 서버를 분리하고, 서버에서 클라이언트로 요청을 프록시하는 방식으로 서비스를 운영한다.

이 과정을 대략적으로 이미지로 정리해보았습니다.

노인복지관 예상 구조

추후 함께 작업할 팀원의 온보딩 소요 시간을 줄인다.

모노레포 환경을 구축해서 새로운 팀원이 합류했을 때 동일한 환경에서 쉽고 원할하게 프로젝트에 참여할 수 있도록 환경을 구축 할 예정입니다. 예를 들어서, eslint, prettier, commitlint, stylelint, tsconfig 등 각 프로젝트에서 사용되는 패키지ㄴ들의 config 파일들을 프로젝트 root에서 관리해, 각 프로젝트에서 참조해서 처음 프로젝트에 합류하더라도 전체 프로젝트 환경을 쉽게 이해할 수 있게 됩니다.

마치며

이번 글에서는 간단하게 프로젝트의 문제점과 개선 계획을 정리해보았습니다. 다음 글 부터 모노레포 환경을 구축하면서 겪은 과정과 배포 자동화 환경을 구축하는 과정을 다뤄보겠습니다. 아직 아쉬운 부분과 개선할 부분도 많고 관련 지식도 많이 부족합니다. 그래서 실제 적용뿐만 아니라 관련 내용을 글로 정리하기도 쉽지 않지만, 프로젝트를 개편하면서 코드 레벨에서부터 인프라, 배포 등 에 관한 관점까지 시야를 넓힐 수 있는 좋은 기회가 될 것 같습니다.


0개의 댓글

로그인하고 댓글을 작성하세요.

로딩 중...