Monolithic 서버사이드 타입스크립트 세팅 03
이놈의 Hexo 블로그가 future
라는 옵션을 켜면 미래 글까지 보여준다는 걸 처음 알았다. 원래 2편이 12일에 올라가길 바랐던 글인데, 3시간 후
라는 이름으로 11일에 올라갔다, 귀찮기 때문에 내리진 않았다.
이 주제로 마지막 글이다. 이번 글에서는 도커라이징을 해서 로컬 개발 환경을 구성하고, 개발 서버, 운영 서버로 나눠서 배포할 수 있는 환경 구성을 해보려고 한다. 도커라이징과 관련된 내용은 과거에 개발 환경과 관련된 글을 썼는데, 그 글에서 조금 더 자세하게 확인할 수 있긴 하다. 지금까지 진행된 내용은 아래와 같다.
Express 기본 아키텍처 구성ESLint & Prettiertsconfig, webpack 설정Swagger- 개발 환경, 운영 환경 분리
- 도커라이징
위에서 언급한 것처럼 도커와 관련된 자세한 세부 사항들은 그전 글에서 확인하는 편이 낫다. 이번 글에서는 자세한 설명은 생략한다.
도커라이징과 개발, 운영 환경 분리
도커라이징, 개발 운영 환경 분리라는 주제는 하나의 주제로 진행된다. (배포까지 하는 글이 아니기 때문에 환경별로 도커라이징까지 하고 끝난다. 도커 배포 글은 Fargate로 배포하기를 참고해보면 좋을 것 같다.)
로컬 개발 환경 구성
우선 Dockerfiles
라는 폴더를 만든다. 로컬에서 작업할 도커 파일과, 개발 서버에 배포 하는 것과, 운영 환경에서 배포할 도커 파일 이렇게 세 개를 보통은 구성한다. 네이밍에는 항상 고민이 있긴 한데, 서버만 관리하는 입장에서 여러 도커 파일을 만들 때, 메인 애플리케이션 하나만 도커라이징 되는 경우가 제일 많았기 때문에 이름은 별 다른 거 없이 local.Dockerfile
, dev.Dockerfile
, prod.Dockerfile
로 만들어준다. 로컬 환경에서는 도커 내부와, 현재 작업하는 src
디렉토리가 공유되어야 한다. 별로 어려울 게 없으니 일단 구성해보자.
1 | # Dockerfiles/local.Dockerfile |
볼륨 공유와 포트포워딩은 docker-compose.yml
에서 설정할 것이다. 다만 위 설정에서 아쉬운 점은, 로컬 개발 환경이 완벽하게 운영, 개발 서버와 같지 않다는 점이다. 완전히 같아지려면, node_modules
를 도커 내에서 설치해야 하고, docker-compose.yml
에서 node_modules
를 공유하지 않는 방식으로 가야 한다. 방법이 있긴 한데 문제는 로컬 개발을 진행하면서 패키지 인스톨이 필요할 때마다 도커 컨테이너 내부에서 yarn add
를 해줘야 한다. 적당히 타협해서, 다음과 같이 진행했다.
데이터베이스와 기타 필요한 것들을 편하게 사용하기 위해서 docker-compose
를 로컬 개발 환경에서는 사용한다. 프로젝트 최상단에 docker-compose.yml
파일을 만든다.
1 | # docker-compose.yml |
내부 내용들에서 구체적인 부분들은 위에서 언급한 시리즈 2탄에서 자세하게 다룬다.
pgadmin
을 위해서 필요한 환경 변수는 두 가지이다.
1 | # envs/local.env |
위는 로컬 환경변수로 필요한 것들이다. 데이터베이스를 붙일 때 추가될 예정이지만, pgadmin
을 도커로 돌리게 되면 로그인을 하게 되는데, 로그인할 이메일과 비밀번호를 위 이름으로 설정해줘야 한다. pgadmin
을 위한 볼륨을 사용하고 있어서 만약 한 번 저 내용이 설정되고 나면 해당하는 volume 위치를 찾아서 설정을 변경해줘야 한다. 바뀔 일이 없어서 상관 없는데 만약 재설정 하고 싶다면, 그냥 해당 볼륨을 지우고 다시 빌드하는게 맘 편하다. 위 두가지 설정을 해주지 않으면 pgadmin
이 켜지지 않는다.
app
이라는 이름으로 서버를 띄울텐데, context
가 최상단에 위치해서, 이미지를 만드는 데 불필요한 부분들까지 모두 context
에 복사된다. .dockerignore
파일을 만들어서 해결해주도록 하자. 최상단에 .dockerignore
를 만들어준다. (로컬 개발 환경에서는 볼륨이 공유되어서 아래 내용들도 모두 존재하긴 한다. 다만 prod.Dockerfile
, dev.Dockerfile
에서 빌드될 때 불필요한 부분들을 넣었다.)
1 | # .dockerignore |
스크립트도 docker-compose
로 시작할 수 있게 바꿔주도록 하자. 파일이 변경되면 재시작되는 nodemon
을 설치하고, 타입스크립트를 바로 실행할 수 있는 ts-node
와, alias
설정을 반영해주는 tsconfig-paths
패키지를 설치한다.
1 | yarn add nodemon ts-node tsconfig-paths -D |
1 | // package.json |
yarn dev
명령어로 실행해볼 수 있다. 로컬 개발 환경이 잘 작동하고, 앱에서는 현재 만들어진 데이터베이스가 없기 때문에 데이터베이스가 없다는 에러가 뜰 수 있다. ORM 별로 다르겠지만, 이 글에서 사용한 TypeORM을 사용하고 있다. pg_admin
을 통해 원하는 데이터베이스 이름을 만들어주고 (또는 database
도커 컨테이너 내부로 가서 직접 만들어줘도 된다.) 실행하면 원활하게 실행이 된다.
TypeORM은 프로젝트 최상단에 ormconfig.json
을 두든, 앱 내부에서 설정 값을 넘겨줘야 한다. ormconfig.json
을 사용하면 데이터베이스 설정값을 여러개로 관리해야 하지만, 내부에 넘겨주게 되면, .env
에서 해결할 수 있다. 또한 내부에 뒀을 때 생기는 문제점들을 해결해 줄 수 있지도 않다. 따라서 createConnection
에 옵션을 넘기는 방식을 사용하겠지만, 내부에서 옵션을 직접 지정하면, entities 옵션을 설정할 때 빌드된 다음 과정을 고민해봐야 한다. 빌드 되면 해당 경로가 무의미 해진다. 따라서 경로로 설정해주는 것이 아니라 직접 모델을 넣어줘야 하는데, 그렇게 되면 minify
되는 이름들 때문에 테이블 이름을 온전하게 찾지 못하는 문제가 발생한다.
경로 지정을 해서 생기는 문제와, 직접 모델을 import
해줄 때 생기는 문제 중에 해결하기 쉬운 건 이름이 바뀌는 문제이다. 이 부분도 웹팩의 optimization
과 관련이 있다.
클래스 이름이 변경되는 것을 막기 위해서 다음 옵션을 TerserPlugin
에 추가한다.
1 | // ... |
src/configs/index.ts
와 src/loaders/dbLoader.ts
에 다음과 같이 설정 값을 추가한다.
1 | // src/configs/index.ts |
1 | // src/loaders/dbLoader.ts |
ormConfig
를 인자로 받는 함수를 만든 이유는 테스트 함수에서 컨넥트 할 때 같은 함수를 사용하고 싶어서이다.
지금까지 로컬 개발 환경에서 필요한 local.env
는 다음과 같다.
1 | PGADMIN_DEFAULT_EMAIL=your@email.com |
이제 서버가 잘 돌고 완전히 로컬에서 개발할 준비가 마쳐졌다.
현재 프로젝트는 아래와 같이 생겼다.
이 방식으로 하면, 최상단의
ormconfig.json
은 사용하지 않아도 된다.ormconfig.json
은 로컬 환경에서만 개발할 때는 사용하기 편하지만, 설정 값이 많아지는 건 귀찮은 일이다. 다만typeorm cli
를 사용하는 사람은 이 옵션이 필요하다. 주로 migration을 하는 일인데, 이 부분은ormconfig.json
의migrations
폴더를 지정하고 타입스크립트로 코드를 돌리도록 하면 해결 되긴 한다. 필자는 운영, 개발 서버는 pgAdmin을 사용해서 migration을 해주는 편이기 때문에 사용하지 않는다. 로컬 환경은 sync를 맞춰주는 걸로 한다. 다만 이 프로젝트에서는 지우지 않고migrations
를 사용하시는 분들을 위해 기본적인 세팅을 해뒀다.
이제 공백으로 남겨뒀던 dev.Dockerfile
과 prod.Dockerfile
을 작성해보자. 지난 번에 도커 베스트 프랙티스를 번역 했으니, 그걸 좀 활용해보자.
1 | # Dockerfiles/dev.Dockerfile |
실행 할 때 node_modules
를 새로 설치하는게 더 빠를까 COPY
하는 게 더 빠를까 고민을 했는데, 정확하지는 않지만, 이게 더 나을 것 같아서 위와 같이 작성했다. 아래는 prod.Dockerfile
이다.
1 | # Dockerfiles/prod.Dockerfile |
둘 모두 빌드가 잘 된다.
추가: 테스트 코드
Jest
를 사용한 테스트 코드 사용도 추가해보려고 한다. 아래 필요한 패키지를 추가로 설치한다.
1 | yarn add jest ts-jest @types/jest -D |
그 다음 package.json
에 타입스크립트 코드를 테스트할 수 있게 Jest
설정과 스크립트를 추가한다.
1 | // package.json |
collectCoverage
는 테스트 결과와 함게 커버리지를 커멘드라인으로 띄워 보여주는 옵션이다.
moduleNameMapper
는 테스트 코드 내에서 @/*
형태로 모듈을 임포트 할 수 있게 지정해주는 옵션이다.
--forceExit
옵션은 Jest를 종료 해야 하는 경우, 종료되지 않는 프로세스 때문에 꺼지지 않는 것을 막아주는데 이 옵션을 사용하려면 --detectOpenHandles
옵션이 필요하다. 이 옵션은 Jest가 깔끔하게 종료되지 않게 방해하는 것을 모아서 프린트해준다.
프로젝트 최상단에 tests
디렉토리를 만들고, src
프로젝트에서 테스트 해야 할 디렉토리와 동일한 이름으로 만든다. 필자는 보통 models
, services
를 테스트 하는 편이다. 테스트 되어야 하는 파일에 .test.ts
확장자로 테스트 파일을 만든다.
tsconfig
의 include
부분에도 tests/**/*.ts
를 추가한다.
테스트를 할 때 데이터베이스가 필요한 경우가 있다보니, 어떻게 해야 할지 고민을 하게 됐는데, 마찬가지로 docker-compose.test.yml
을 만들어서 전체 테스트를 실행하게 할려 했다. 실제로 해봤는데 생각보다 너무 느려서, localhost:5432
에 붙는 데이터베이스(yarn dev
했을 때 뜨는 데이터베이스도 localhost:5432
에 포트포워딩 되어있다.)에다가 testdb
라는 데이터베이스를 만들어서 진행했다. 이게 훨씬 빨라서 로컬 테스트는 그렇게 진행하기로 했다.
아래는 userService.test.ts
와 userModel.test.ts
이다.
1 | // tests/services/userService.test.ts |
1 | // tests/models/userModels.test.ts |
아래는 테스트에서 사용되는 ormConfig
이다.
1 | // tests/ormConfig.ts; |
테스트를 돌려보면 다음과 같이 리포트를 뽑아준다. 프로젝트 최상단에 coverage
라는 리포트도 웹 형태로 빌드해준다.
TADA~
자동으로 만들어주던 보고서
후기
정말 긴 시리즈였다. 중간 중간 추가해가면서 진행해서 글을 다듬는 것도 많았다. 잘 읽히게 쓰여진 건지 잘 모르겠지만, 아무튼 템플릿 프로젝트를 하나 완성했다. 이제 타입스크립트 보일러 플레이트는 이걸 계속 업데이트해가면서 쓸 계획이다.
Reference
- https://jestjs.io/docs/en/cli.html
- https://yonghyunlee.gitlab.io/temp_post/jest/
- https://hub.docker.com/_/postgres
- https://github.com/Radu-Raicea/Dockerized-Flask/wiki/%5BDocker%5D-Access-the-PostgreSQL-command-line-terminal-through-Docker
- https://www.freecodecamp.org/news/get-your-npm-package-covered-with-jest-and-codecov-9a4cb22b93f4/
Monolithic 서버사이드 타입스크립트 세팅 03
https://changhoi.kim/posts/backend/serverside-typescript-setting-03/