[ NestJS ] Repository Pattern에 대하여 알아보자
🌈 프로그래밍/Nest JS

[ NestJS ] Repository Pattern에 대하여 알아보자

반응형

 

안녕하세요? 수구리입니다.

이번 포스팅에서는 Nest의 Repository pattern에 대해서 알아보려고 합니다.

디자인 패턴에 대해서 공부하는 것은 굉장히 중요하다고 생각합니다.

어떤 패턴을 쓰냐에 따라서 코드의 유지보수와 관리의 난이도가 결정되기 때문입니다.

 

Repository Design Pattern ?

한 줄로 요약하자면 비즈니스 로직이 있는 Service Layer와 Data Source Layer 사이에서 중재자 역할을 하는

또 다른 하나의 Layer을 말합니다.

 

Service Layer에는 비즈니스 로직 즉, 서비스의 핵심이 되는 로직이 존재합니다.

만약 Repository 패턴을 적용하지 않는다면, 비즈니스 로직에서 데이터베이스에 직접 접근을 하게 됩니다.

이런 경우 문제점이 발생합니다.

 

우선 간단하게 아래의 이미지를 살펴보겠습니다.

위의 그림은 Repository Pattern을 적용하지 않은 흐름입니다.

클라이언트가 Data를 컨트롤러에 보냅니다. (컨트롤러에 도달하기 전까지의 과정은 생략)

클라이언트로부터 DTO 객체를 넘겨받은 컨트롤러는 DTO 객체를 서비스단으로 넘겨주죠.

서비스는 구현된 비즈니스 로직을 통해 컨트롤러에게 받은 DTO 객체를 사용해서 DB에 접근하여 저장합니다.

즉, 서비스에서 Data Source에 직접 접근을 하는 모습입니다.

 

문제점 

1. 비즈니스 로직에만 집중하기가 힘들다.

만약 서비스의 비즈니스 로직에서 DB에 접근할 때 더 복잡한 로직이 필요한다면

결국 서비스는 비즈니스 오롯이 로직에만 집중할 수 없게 됩니다.

또한 비즈니스 로직 자체를 Testing 하는데 어려워지고 코드 중복이 발생하여 가독성이 떨어지게 됩니다.

 

2. 순환 참조 현상

아래의 이미지를 보도록 하겠습니다.

또 발생할 수 있는 다른 문제점으로는

만약 A라는 서비스에서 B라는 서비스의 일부를 참조하고 있고, 

마찬가지로 B라는 서비스에서도 A의 일부를 참조하고 있는 경우 즉, 순환 참조의 경우가 생길 수 있습니다.

해결은 할 수 있지만 이렇게 된다면 마찬가지로 코드의 중복과 가독성이 떨어질 수 있는 문제점이 있습니다.

 

Repository Design Pattern의 장점?

위의 이미지는 Repository Pattern을 적용한 모습입니다.

하나의 Layer가 더 생겼고 위에서 설명한 것처럼 Repository Layer에서만 DB source에 접근이 가능하게 한 모습입니다.

구체적인 코드를 보면서 설명하도록 하겠습니다.

 

cats.service.ts

@Injectable()
export class CatsService {
	// Repository 의존성 주입
	constructor(private readonly catsRepository: CatsRepository) { }

	async signUp(body: CatRequestDto) {
		const { email, name, password } = body;
		const isCatExist = await this.catsRepository.existsByEmail(email);

		if (isCatExist) {
			throw new UnauthorizedException('해당하는 고양이는 이미 존재합니다!');
		}
		const hashedPassword = await bcrypt.hash(password, 10);

		// DB save
		const cat = await this.catsRepository.create({ email, name, password: hashedPassword, });
		return cat.readOnlyData;
	}
}

CatsService의 SignUp 비즈니스 로직입니다.

CatsRepository 안에 있는 DB에 접근하는 함수들을 사용하기 위해 CatsRepository class를 의존성 주입!

signUp 메서드를 보면 컨트롤러에서 DTO 객체를 받아옵니다.

이후 CatsRepository의 existsByEmail이라는 DB에 접근하는 함수를 호출하여 이미 존재하는 이메일인지 아닌지 Check

DB에 저장하는 부분에서도 CatsRepository의 create 메서드를 사용하는 모습입니다.

 

cats.repository.ts

@Injectable()
export class CatsRepository {
	constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) { }

	async existsByEmail(email: string): Promise<boolean> {
		const result = await this.catModel.exists({ email });
		return result;
	}

	async create(cat: CatRequestDto): Promise<Cat> {
		return await this.catModel.create(cat);
	}
}

CatsRepository에서는 Cat 모델을 사용하고, signUp 로직에서 호출한 함수들의 구현입니다.

 

따라서 DB에 접근하는 로직만을 Repository layer로 분리를 시켜준다면

다른 모듈 or 서비스에서 접근할 때 Repository만 접근을 하면 됩니다.

그렇게 한다면 각 서비스에서 비즈니스 로직에 더 집중할 수 있게 될 것이고

모듈 간의 책임과 분리가 명확해지는 장점이 있습니다.

또한 코드의 중복을 최소화하여 가독성을 높일 수 있다는 장점이 있습니다.

 

Repository Pattern의 핵심?

마지막으로 이 패턴의 핵심에 대해서 정리해보겠습니다.

우선 서비스 층에서 데이터의 출처와 관계없이 동일한 방식으로 데이터를 접근할 수 있도록 할 수 있습니다.

이게 어떤 의미냐면 예를 들어 우리 back-end 애플리케이션에서 mongoDB를 사용하고 있다고 가정해봅시다.

그런데, 아래의 그림처럼 여러 개의 DB source를 사용한다고 한다면?

여러 Data Source가 생길 것이고, 만약 Repository layer가 없다면

사용해야 하는 DB의 Source가 달라져 모든 서비스의 쿼리문을 수정해야 하는 엄청난 비용이 드는 작업을 해야 할 것입니다.

하지만 Repository Layer를 적용한다면 DB의 Source가 달라지더라도

서비스 Layer에서 DB를 직접적으로 접근하지 않기 때문에 쿼리를 사용하는 Repository Layer에서만 해당하는 쿼리로 수정하기만 하면 됩니다. 즉, DB의 캡슐화라고 생각하시면 됩니다.

이상으로 Reopsitory Design Pattern에 대해서 알아보았습니다.

감사합니다.

반응형