지난번에 Monorepo 설정을 이후로 백엔드 설정을 준비해야합니다. 그러기 위해서 현재 진행할 줍줍이라는 프로젝트의 디렉토리 구조를 먼저 간단하게 정의해보죠.
mono_velog
|- /apps
|- /zoop_frontend
|- /zoop_backend
|- /shared
|- .env
|- docker-compose.yml
앞서 모노레포 설정을 다룰 때, mono_velog/apps/**
의 프로젝트들은 package로서 다뤄지게 설정을 해뒀습니다.
그래서 zoop_frontend
, zoop_backend
는 각각 nextJs, nestJs 프로젝트로서 하나의 패키지가 될 예정이죠.
shared
디렉토리에는 데이터베이스와 관련된 도커파일과 백엔드에서 ORM으로 사용할 Prisma의 schema.prisma
파일을 둘 예정입니다.
.env
파일은 데이터베이스 유저 정보와 데이터베이스 URL같은 여러 프로젝트에서 사용할 정보들을 담아두겠습니다.
docker-compose.yml
도커로 컨테이너화 시킬 여러 프로젝트들을 일괄적으로 관리 및 실행시키기 위해 apps/
디렉토리에 생성하겠습니다.
일단은 백엔드 환경부터 다룰 예정이기 때문에, zoop_frontend
폴더는 다음에 만들도록 하겠습니다.
NestJs는 서버 어플리케이션을 구축하기 위한 Node.js 기반 프레임워크입니다. Express에서 자주 사용하는 시스템들을 쉽게 이용할 수 있도록 각종 모듈들을 지원합니다.
Prisma는 ORM(Object Relational Mapping)의 한 종류입니다.
여기서 ORM은 데이터베이스의 Schema를 객체로 매핑을 해주는 것인데요, NestJs에서 CRUD를 구현하기 위해 별도의 데이터베이스 명령어를 사용할 필요가 없다는 편의성을 제공합니다.
또, schema.prisma
를 통해 각 모델을 정의하고, prisma generate
명령어를 통해 해당 schema 파일을 기반으로 NestJs에서 사용할 수 있는 메서드 및 타입에 대한 정의들을 제공합니다. 따라서 개발 과정에 있어서도 상당한 편의를 제공해주죠.
Docker는 이미지화된 어플리케이션을 컨테이너에서 실행시킬 수 있게 해주는 가상화 플랫폼인데요, 여기서 어플리케이션의 Dockerfile
을 통해 이미지를 만듭니다.
Docker 이미지 안에는 어플리케이션을 실행시키기 위한 파일들을 담게되어, 도커의 컨테이너에서 실행시킬 수 있습니다. 덕분에 OS X에서 개발한 어플리케이션을 Linux 환경에 베포해도 결국 Docker의 컨테이너에서 작동하는 것이기 때문에 큰 문제없이 작동합니다.
본격적으로 데이터베이스에 대한 설정을 마치기 전에, 데이터베이스에 생성될 모델들에 대한 정의를 schema.prisma
파일에 작성해줘야 합니다.
그러기 위해선 Zoop Zoop을 위한 모델들을 설계할 필요가 있겠죠.
우선 제가 생각한 ZoopZoop을 위한 모델들은 다음과 같습니다:
model User {
id Int @id @default(autoincrement())
email String @unique
username String
refreshToken String?
expressions Expression[]
}
우선 Zoop에서는 OAuth 기반의 로그인만 지원하여 사용자의 회원가입에서 불필요한 고민을 줄일 예정입니다. 여기서 추가로 호칭을 위한 username을 추가로 입력받아 데이터베이스에 저장합니다.
그리고 refreshToken을 저장해서 token이 refresh가 필요할 때 검증 후 Update하도록 구현할 예정입니다.
그리고 각 User는 각자가 주운 표현, Expression[]
을 생성할 수 있습니다.
model Expression {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
content String
meaning String?
media String?
category Category? @default(en)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cases Case[]
userNyam UserNyam[]
}
Expression은 사용자가 줍줍한 표현에 대한 데이터입니다. 각 표현은 어떠한 표현을 주웠는지에 대한 content
, 그 표현에 대한 뜻 meaning
를 가장 기본적으로 가지게 됩니다.
추가적으로 있으면 좋겠다 생각한 정보들은
media
어떤 매체를 통해 해당 표현을 주웠는지.
category
어떠한 언어 종류인지, (영어, 일본어, 스페인어, 중국어, 여자어(?) 등)
정도가 되겠네요.
여기서 category
필드의 타입으로 쓰인 Category
는 enum
타입으로 맨 아래에서 정의하도록 하겠습니다.
createdAt
은 오늘 줍줍한 표현을 필터하고, 나중에 구현하고싶은 data visualization파트에서 사용될 수 있을것 같습니다.
cases
는 유저가 줍줍한 표현을 실생활에서 써먹은 문장과 뜻을 담기위한 필드입니다. 아래에서 Case
모델을 생성할 예정입니다.
userNyam
은 여태 주워들은 표현들을 대상으로 "오늘 표현을 익히겠다!" 라고 생각해서 선택받은 표현들과, 그 케이스를 저장할 model입니다. 마찬가지로 아래에서 구현 예정입니다.
위쪽의 userId
, user
은 User
모델과의 관계를 위해 정의된 필드입니다.
model Case {
id Int @id @default(autoincrement())
expressionId Int
expression Expression @relation(fields: [expressionId], references: [id])
content String
meaning String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userNyam UserNyam[]
}
Case 모델입니다. 마찬가지로 중요한 필드는 content
, meaning
입니다. 각각 User가 주운 표현을 써먹은 문장과 그 문장의 의미를 나타냅니다.
그 외의 정보는 Expression
모델과의 관계, UserNyam
모델과의 관계를 위해 사용됩니다.
model UserNyam {
id Int @id @default(autoincrement())
expressionId Int
expression Expression @relation(fields: [expressionId], references: [id])
caseId Int?
case Case? @relation(fields: [caseId], references: [id])
createdAt DateTime @default(now())
}
UserNyam 모델입니다. Expression
과의 관계를 메인으로 해서 생성되고, Case
데이터가 연결되면 해당 도전은 성공으로 판단됩니다.
enum Category {
en
jp
tr
wo
}
저는 Categroy enum을 위와같이 정의했는데요, en은 영어, jp는 일본어, tr은 유행어, wo 여자어(?) 정도로 간단하게 정의내렸습니다.
이제 밑에서 다룰 datasource와 generator를 적절하게 설정해주면, prisma generate
명령어를 통해 프로젝트 node_modules
에 @prisma/client
라는 모듈을 추가할 수 있게 됩니다.
해당 모듈에는 schema.prisma
에서 정의내린 model에 대한 object들을 타입을 담고 있기에, 개발 과정을 편하게 만들어줍니다.
최근에 알게된 사실인데, typescript에서는 enum 타입의 사용을 지양하고 있다고 합니다.
이는 javascript에 존재하지 않는 문법임에도 불구하고 번들러에서 Tree shaking이 되지 않기 때문인데요, 그 때문에 객체에
as const
를 적용하여 Union 타입을 추출해 사용하는것을 권장한다고 합니다.
이와 관련해서는 다른 포스팅에서 자세히 다뤄보도록 하겠습니다.
datasource는 매핑할 데이터베이스의 정보를 의미합니다. 저는 postgresql을 사용할 예정이고, Database URL은 env파일에 저장된 url을 불러오도록 하겠습니다.
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator는 앞서 prisma generate
명령어를 통해 생성될 Prisma Client를 생성하기 위한 설정입니다.
generator client {
provider = "prisma-client-js"
}
기본적으로 prisma-client-js를 provider
로 사용하고, 직접 구현한 provider
를 사용할 수도 있습니다.
현재 generator의 기본적인 설정은 위와같이 했지만, 두가지 옵션을 더 가집니다.
generator client {
provider = "prisma-client-js"
output = "node_modules/@prisma/client"
binaryTargets = ["native"]
}
output
은 client 모듈이 생성될 경로입니다. 저는 모노레포를 통해 다른 디렉토리에 schema.prisma
파일이 있으므로 재설정의 필요성도 보입니다.
binaryTargets
는 prisma client와 OS와의 호환을 위해 어플리케이션이 돌아갈 OS를 지정해줍니다. 다만 native
라는 값으로 지정하면 prisma가 알아서 지정한다고 합니다.
위에 작성된 output
, binaryTargets
의 값은 모두 default값으로, 굳이 위처럼 작성하지 않아도 됩니다.
model, datasource, generator에 대한 설정을 마친schema.prisma
파일은 다음과 같습니다:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
username String
refreshToken String?
expressions Expression[]
}
model Expression {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
content String
meaning String?
media String?
category Category? @default(en)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
cases Case[]
userNyam UserNyam[]
}
model Case {
id Int @id @default(autoincrement())
expressionId Int
expression Expression @relation(fields: [expressionId], references: [id])
content String
meaning String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userNyam UserNyam[]
}
model UserNyam {
id Int @id @default(autoincrement())
expressionId Int
expression Expression @relation(fields: [expressionId], references: [id])
caseId Int?
case Case? @relation(fields: [caseId], references: [id])
createdAt DateTime @default(now())
}
enum Category {
en
jp
tr
wo
}
PostgreSQL은 Docker의 postgres 이미지를 이용하여 컨테이너로 이용할 예정입니다.
단, container 안에서 prisma의 원활한 사용을 위해 postgres이미지에 prisma를 설치합니다. 그리고 환경변수로 env파일에 있는 데이터베이스 URL 정보를 넘기고, schema.prisma를 컨테이너로 복제하는 과정까지 Dockerfile
에 작성하도록 하겠습니다.
FROM postgres:latest
# Install prisma
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get install -y nodejs
RUN npm install -g prisma
# Set environment variable
ENV DATABASE_URL={DATABASE_URL}
# Copy schema.prisma.
COPY schema.prisma ./
우선 postgres 이미지를 베이스 이미지로 사용하고, 컨테이너 내부에 prisma를 전역으로 설치하는 코드를 실행시킵니다.
다음으로는 컨테이너 내부의 DATABASE_URL
환경변수를 env 파일의 DATABASE_URL
값으로 설정하고, 같은 폴더(docker build context 기준)에 있는 schema.prisma
파일을 컨테이너 내부로 복제해줍니다.
여기서 docker build context 기준으로 같은 폴더에 있다고 했습니다.
docker에서는 context를 기준으로 복사할 파일을 설정할 수 있습니다.
가령 부모 디렉토리에 있는 파일이나. 형제 디렉토리에 있는 파일을 컨테이너로 복사하고 싶을 때, build context를 설정해줌으로써 복사를 가능하게 할 수 있죠.
이렇게 Prisma와 PostgreSQL의 Dockerfile 설정을 완료했습니다.
다음시간에는 zoop_backend에 nestjs를 설치하고 Dockerfile
과 docker-compose.yml
파일을 작성하여 데이터베이스와 같이 도커로 베포하는 과정을 다루겠습니다.
잘못된 정보나 지식, 오탈자 제보는 언제든지 환영합니다 😎