개발/프로젝트

[prisma] 서비스 레이어를 거치지 않고 aop개념으로 로직 구현하기

prpn97 2024. 10. 9. 18:45

계기

개인프로젝트를 진행하면서 이메일 암/복호화를 구현하던 중, 문득 무언가 불편함을 느꼈다. 

결국 이메일을 암호화해서 저장하게 되면, 이메일을 조회할 때 이메일을 암호화해서 조회, 그리고 조회한 뒤에 클라이언트에게 보여질 땐 복호화되어야 하기 때문에 이메일을 클라이언트에게 보여주기 위해서는 동일 로직을 여러번 거쳐야 했다. 

 

인증 / 인가를 별도로 매번 구현하는 것이 아니라 UseGuards나 UseInterceptors 데코레이터를 통해 관심사를 분리하는 것처럼 db에 접근할 때 항상 prisma를 통해 접근하기 때문에 prisma 에서 email 필드에 대해 암/복호화 과정을 거치는 부분을 세팅한다면 암/복호화를 놓치는 휴먼이슈나 가독성 등 여러 측면에서 괜찮을 것 같다는 생각이 들었다. 

 

 

과정

처음에는 prisma를 호출하는 함수를 구현하는 방법을 떠올렸다. 그 전에 prisma는 너무 친절하게 공식문서가 잘 정리되어 있기 때문에.. 더 좋은 방법을 캐치할 수 있을지 찾아보다가 어떻게 구현할지 명확히 확인할 수 있었다. 

공식문서 예시)

const prisma = new PrismaClient().$extends({
  model: {
    user: {
      async signUp(email: string) {
        await prisma.user.create({ data: { email } })
      },
    },
  },
})

const user = await prisma.user.signUp('john@prisma.io')

prisma 공식문서: https://www.prisma.io/docs/orm/prisma-client/client-extensions/model

 

Prisma Client extensions: model component | Prisma Documentation

Extend the functionality of Prisma Client, model component

www.prisma.io

 

위 예시는 $extends 메서드를 통해 prisma의 모델들 안에 signUp이라는 커스텀 메서드를 구성한 것이다. 다음은 내가 구현한 코드의 일부다. 

 

  this._prisma = new PrismaClient({
      datasources: {
        db: { url: appConfig().DATABASE_URL },
      },
    });

    const encryption = this.encryptionService;
    const prismaInstance = this._prisma;

    this.prisma = this._prisma.$extends({
      model: {
        user: {
          async create(params) {
            const { data } = params;
            if (data.email) {
              data.email = await encryption.encrypt(
                data.email,
                appConfig().ENCRYPTION_KEY,
              );
            }
            if (data.password) {
              data.password = await encryption.hash(data.password);
            }
            return prismaInstance.user.create(params);
          },
          async findUnique(params) {
            if (params.where.email) {
              params.where.email = await encryption.encrypt(
                params.where.email,
                appConfig().ENCRYPTION_KEY,
              );
            }
            const result = await prismaInstance.user.findUnique(params);
            if (result && result.email) {
              result.email = await encryption.decrypt(
                result.email,
                appConfig().ENCRYPTION_KEY,
              );
            }
            return result;
          },
        },
      },
    });
  }

 

기존에 사용하고 있는 create, findUnique에 들어오는 params를 확인, email일 경우 특정 로직을 거치는 것이다. 현재 예시 이외에도 findFirst, findMany, count 등의 메서드에 이메일을 확인할 경우 로직들을 적용했다. 

 

이렇게 적용하고 나면, 별도로 서비스레이어에서 암/복호화를 거치지 않고 자연스럽게 기존 이메일을 통해 조회해도 prisma를 통해 이메일이 암호화된 상태에서 조회되고, 조회된 값을 복호화해서 반환하게 된다. 

728x90