Controller, Service, Repository 패턴에서의 ORM 사용 방식

김지섭·2024년 3월 1일
0

Controller, Service, Repository 패턴에서 ORM을 어느 레이어로써 활용할 수 있는 두가지 방법에 대해서 설명하고, NestJS와 Prisma를 사용한 예시를 통해 두 접근법을 비교 분석해보려고 합니다.

Repository란?

Repository는 소프트웨어 개발에서 데이터 소스와의 상호작용을 캡슐화하는 디자인 패턴입니다. 이 패턴의 주요 목적은 애플리케이션의 비즈니스 로직과 데이터 액세스 로직을 분리하는 것입니다. 즉, 애플리케이션 코드에서 직접적으로 데이터베이스 쿼리를 호출하는 대신, Repository를 통해 데이터베이스와의 상호작용을 수행합니다. 이로써 애플리케이션의 유지보수성과 확장성을 향상시킬 수 있습니다.

Repository 패턴은 다음과 같은 장점을 제공합니다:

  • 데이터 액세스 로직의 중앙집중화: 모든 데이터 액세스 로직을 Repository 내에 위치시킴으로써 코드의 중복을 줄이고, 데이터 액세스 로직의 변경을 용이하게 만듭니다.
  • 테스트 용이성: 비즈니스 로직을 데이터베이스 로직으로부터 분리함으로써, 데이터베이스에 의존하지 않는 단위 테스트가 가능해집니다.
  • 데이터 소스의 추상화: Repository는 데이터 소스의 추상화를 제공합니다. 이는 애플리케이션 코드가 특정 데이터베이스 기술에 종속되지 않도록 해주며, 필요한 경우 다른 데이터 저장 기술로의 전환을 용이하게 만듭니다.
  • 타입 안전성 및 코드 자동완성: 특히 ORM(Object-Relational Mapping)을 사용할 경우, Repository는 데이터베이스 테이블과 매핑되는 클래스 또는 엔티티의 인스턴스를 반환함으로써 타입 안전성을 보장하고, IDE의 코드 자동완성 기능을 지원합니다.

Repository 패턴은 3계층 아키텍처(표현 계층, 비즈니스 로직 계층, 데이터 액세스 계층)에서 주로 사용되며, 각 계층은 서로 명확하게 분리되어 있어야 합니다. 이러한 구조는 애플리케이션의 규모가 커지고 복잡해짐에 따라 그 중요성이 더욱 증가합니다.

ORM이란?

ORM은 데이터베이스 테이블과 객체 간의 관계를 매핑하는 기술입니다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하는 대신, 객체 지향적인 방식으로 데이터베이스 작업을 할 수 있습니다. ORM은 개발자가 데이터베이스 작업을 수행하기 위해 사용하는 도구나 라이브러리(예: Hibernate, Entity Framework, Sequelize, Prisma 등)를 말합니다.

ORM은 Repository인가?

ORM(Object-Relational Mapping)은 Repository 패턴과는 다른 개념이지만, 데이터 소스와의 상호작용을 캡슐화한다는 관점에서 ORM을 Repository로써 사용을 해도 괜찮다고 생각됩니다.

ORM을 Repository 계층처럼 사용하는 경우의 장점:

  1. 간결성: ORM 자체가 데이터 액세스 로직의 추상화를 제공하기 때문에, 추가적인 Repository 계층을 구현하지 않아도 되므로 코드베이스가 더 간결해질 수 있습니다.
  2. 개발 속도: ORM을 직접 사용하면 복잡한 쿼리를 작성하거나 데이터 변환 로직을 직접 구현할 필요가 줄어들어 개발 속도가 빨라질 수 있습니다.
  3. 기능 활용: 현대의 ORM 도구는 데이터베이스 작업을 위한 강력한 기능들을 제공합니다. 이러한 기능들을 직접 활용함으로써, 개발자는 데이터베이스 작업을 보다 효율적으로 수행할 수 있습니다.

고려해야 할 사항:

  1. 분리의 원칙: 비즈니스 로직과 데이터 액세스 로직을 엄격히 분리하는 것이 좋은 소프트웨어 아키텍처 원칙 중 하나입니다. ORM을 Repository 계층처럼 사용할 때, 이러한 분리가 모호해질 수 있으므로 주의가 필요합니다.
  2. 유연성: 향후 데이터 액세스 로직이나 사용하는 데이터베이스 기술에 변경이 필요할 경우, ORM을 직접 사용하는 방식은 수정이 더 어려울 수 있습니다. Repository 계층을 별도로 두면, 이러한 변경사항을 더 쉽게 관리할 수 있습니다.
  3. 복잡성 관리: 프로젝트의 규모가 커지고 복잡성이 증가할 경우, 데이터 액세스 로직을 분리하여 관리하는 것이 유리할 수 있습니다. 복잡한 쿼리 로직이나 트랜잭션 관리가 필요한 경우, 별도의 Repository 계층을 구현하는 것이 더 나을 수 있습니다.

별도의 Repository에서 ORM 다루기

이 방식에서는 별도의 Repository 계층을 구현하여, 이 계층 내에서 ORM을 사용합니다. 이 접근 방식의 장점은 비즈니스 로직과 데이터 액세스 로직의 엄격한 분리로, 코드의 유지보수성과 확장성이 향상된다는 것입니다. 단점은 추가적인 코드 작성이 필요하다는 점입니다.

NestJS + Prisma 예시

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User as UserModel, Prisma } from '@prisma/client';

@Injectable()
export class UserService {
  constructor(private prisma: PrismaService) {}

  async createUser(data: Prisma.UserCreateInput): Promise<UserModel> {
    return this.prisma.user.create({
      data,
    });
  }

  // 기타 비즈니스 로직 구현
}

@Injectable()
export class UserRepository {
  constructor(private userService: UserService) {}

  async addUser(userData: Prisma.UserCreateInput): Promise<UserModel> {
    return this.userService.createUser(userData);
  }

  // 기타 Repository 로직 구현
}

ORM을 Repository 그 자체로 사용하기

이 접근 방식에서는 ORM 객체를 직접 Repository로 사용하여 데이터베이스와의 상호작용을 관리합니다. 이 방법의 장점은 코드의 양을 줄이고, ORM의 기능을 최대한 활용할 수 있다는 것입니다. 단점은 프로젝트의 복잡성이 증가할 경우, ORM과 비즈니스 로직 간의 경계가 모호해질 수 있다는 점입니다.

NestJS + Prisma 예시

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from '@prisma/client';

@Injectable()
export class UserService {
  constructor(private prisma: PrismaService) {}

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return this.prisma.user.create({
      data,
    });
  }

  // 기타 사용자 관련 메소드 구현
}

결론

ORM을 Repository 그 자체로 사용하는 방법과 별도의 Repository에서 ORM을 다루는 방법은 각각의 장단점을 가지고 있습니다. 프로젝트의 규모, 팀의 선호, 유지보수 및 확장성의 요구 사항을 고려하여 적합한 접근 방식을 선택해야 합니다. NestJS와 Prisma를 사용한 예시는 두 접근 방식을 구현하는 방법을 보여줍니다. 선택한 방식에 따라 애플리케이션의 구조와 유지보수성에 큰 영향을 미칠 수 있으므로, 프로젝트의 요구 사항과 팀의 작업 방식을 충분히 고려하여 결정해야 합니다.

0개의 댓글

관련 채용 정보