현재 NestJS + Prisma 조합으로 백앤드를 운영중이다. 요즘 읽기 부하가 늘어남에 따라 replica DB를 따로 생성하여 read 동작은 모두 replica DB에서, 나머지 동작은 master DB에서 사용하고 싶었다. 그래서 doc를 찾던 중, 아직은 prisma에서는 정식으로 replica DB를 지원하는 공식 문서는 없었다. prisma의 기본 정신이 빠른 백앤드 서버 제작을 목표로 해서 그런가? 이건 좀 많이 아쉬웠다. 옆동네 typeORM은 잘 지원해 주던데. 아직 오픈이슈인걸로 봐서는 언젠간 개발해 주지 않을까 싶다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
"replication": {
"master":
{
"host": "master 주소",
"port": 3306,
"username": "root",
"password": "test#123",
"database": "master"
},
"slaves": [
{
"host", "slave 주소",
"port": 3306,
"username": "root",
"password": "test#123",
"database": "master"
}
]
}
|
cs |
typeORM의 replica DB 사용법
그래서 방법을 찾던 중, prisma MiddleWare의 항목에서 쿼리 수준에서 무언가 설정을 할 수 있는 것을 알게 되었다. prisma.$use 내부의 콜백 함수의 파라미터인 params, next를 사용해서 분류를 할 수 있는 것이다! 그래서 param안에 있는 다양한 메서드 또는 타입을 이용하여 prisma의 동작을 미들웨어 제어할 수 있다. 아래는 params에 사용할수 있는 대표적인 항목들이다.
model | database의 table 목록 |
action | findFirst, updateMany 등 prisma에서 사용할 수 있는 action |
args | action의 매개변수에 들어갈 것들 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import minimatch from 'minimatch';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
private readOnlyInstance: PrismaClient;
private NODE_ENV = process.env.NODE_ENV;
constructor() {
super();
// prod일 경우 새 읽기 전용 오브젝트 생성
if (this.NODE_ENV === 'prod')
this.readOnlyInstance = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL_REPLICA,
},
},
});
}
async onModuleInit() {
await this.$connect();
if (this.NODE_ENV === 'prod') await this.readOnlyInstance.$connect();
// prod일 경우 읽기전용 DB 사용 (find, query, count, aggregate에서만 사용)
if (this.NODE_ENV === 'prod')
this.$use(async (params, next) => {
if (minimatch(params.action, '+(find*|query*|count|aggregate)')) {
const res = await this.readOnlyInstance[params.model][
params.action
](params.args);
return res;
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
// prod일 경우 읽기전용 DB 사용
if (this.NODE_ENV === 'prod') await this.readOnlyInstance.$disconnect();
}
}
|
cs |
미들웨어를 이용해서 delete 작업도 delete 유무를 판별하는 attribute에 값을 추가해 soft delete를 구현할 수도 있다. 아래의 코드는 delete, deleteMany 작업을 MiddelWare에서 가로채어 실제 delete가 되는 것이 아닌 deletedAt attribute를 new Date() 값으로 채우는 것이다. (모든 테이블에 deletedAt attribute가 있다는 가정)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect();
this.$use(async (params, next) => {
if (params.action === 'delete') {
params.action = 'update';
params.args['data'] = { deleteAt: new Date() };
} else if (params.action === 'deleteMany') {
params.action = 'updateMany';
if (params.args.data !== undefined) {
params.args.data['deletedAt'] = new Date();
} else {
params.args['data'] = { deletedAt: new Date() };
}
}
return next(params);
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
|
cs |
prisma를 사용하면서 느낀 것은, 단일 또는 소규모 애플리케이션에 적합한 ORM이라는것을 사용하면서 계속 느끼고 있다. 애플리케이션이 커질수록 다른 ORM들이 계속 눈에 들어오게 된다.
참고자료
https://www.prisma.io/docs/concepts/components/prisma-client/middleware/soft-delete-middleware
https://github.com/prisma/prisma/issues/172
'프레임워크 > NestJS' 카테고리의 다른 글
[NestJS] 데이터 요청과 응답 사이의 Request lifecycle (0) | 2022.11.06 |
---|---|
[NestJS] Dependency Injection과 NestJS (2) | 2022.11.05 |
[NestJS] Subscription과 Guard 사용하기 (0) | 2022.01.14 |
[NestJS] Prisma db 여러개 연결하기 (0) | 2022.01.08 |
[NestJS] DB 캐시 Redis 사용하기 (0) | 2021.12.16 |