[Nest] [클린코드 4편] Service와 Repository 분리하고 의존성 주입하기(DI)
[Nest] [클린코드 3편] DTO와 전역 pipe로 요청 데이터 깔끔하게 검증하기
[Nest] [클린코드 3편] DTO와 전역 pipe로 요청 데이터 깔끔하게 검증하기
[Nest] [클린코드 2편] Guard와 Decorator로 요청 처리 깔끔하게 구현하기 [Nest] [클린코드 2편] Guard와 Decorator로 요청 처리 깔끔하게 구현하기[Nest] [클린코드 1편] JWT 발급 및 인증 구조 깔끔하게 구현하
blog.juyear.dev
이전 글 읽으러 가기!
👋 소개
안녕하세요! 종강 이후 휴식을 취하다가 돌아온 대학생 개발자 주이어입니다.
오늘은 Nest 클린코드 4편, Service와 Repository를 분리하고 의존성 주입으로 다른 도메인에서 사용하는 방법까지 정리해보려고 합니다.
Nest 클린코드 편은 이전 글에서 작성한 코드가 계속 진행되기 때문에, 순차적으로 보시는 것을 추천드립니다.
그럼 본론으로 들어가도록 하겠습니다.
⚙️ Service와 Repository 분리하기
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private users = [{ id: 1, username: 'juyear', password: '1234' }];
findById(id: number) {
return this.users.find((u) => u.id === id);
}
}
위 코드는 Service와 Repository가 아직 분리되지 않은 기존 코드입니다.
DB를 아직 연결하진 않았지만, Service에서 DB 관리도 같이하는 것을 확인할 수 있습니다.
이렇게 될 경우 아래와 같은 문제들이 발생할 수 있습니다.
- Service의 유지보수성이 떨어집니다.
- Repository 역할을 같이 수행하기 때문에 비즈니스 로직만 테스트하기가 어려워집니다. (책임 분할 x)
- DB가 바뀔 경우 해당 DB를 사용하는 모든 Service의 코드 수정이 필요합니다.
- 똑같은 DB 로직을 여러번 적어야하는 등의 코드 중복 문제가 발생할 수 있습니다.
이외에도 규모가 커짐에 따라 다양한 문제가 발생할 수 있습니다.
그럼 이제 Service와 Repository를 분리해보도록 하겠습니다.
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserRepository {
private users = [{ id: 1, username: 'juyear', password: '1234' }];
findById(id: number) {
return this.users.find((u) => u.id === id);
}
}
먼저 user.repository.ts 파일을 생성한 후 위와 같이 적어주었습니다.
기존 Service에서 생성하여 관리 중이던 Mock 데이터를 Repository로 옮겨 주었고,
id로 user를 찾아 반환해주는 DB 로직을 만들어 주었습니다.
import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
findById(id: number) {
return this.userRepository.findById(id);
}
}
Service는 위와 같이 바꿔주었습니다.
Mock 데이터를 제거하고, User Repository에서 데이터를 받아와 비즈니스 로직을 처리하도록 하였습니다.
지금은 단순한 예제이기 때문에 큰 차이를 못 느낄 수 있지만, 처리해야 하는 비즈니스 로직이 많아질 수록 큰 차이를 느끼실 수 있을 겁니다.
✅ Service와 Repository 분리 후 테스트

분리 후에도 위와 같이 결과가 잘 반환되는 것을 확인할 수 있습니다.
테스트는 Postman으로 진행하였습니다.
⚙️ 의존성 주입으로 Repository 공유하기
@Injectable()
export class AuthService {
private users = [{ id: 1, username: 'juyear', password: '1234' }];
login(username: string, password: string) {
const user = this.users.find(
(u) => u.username === username && u.password === password,
);
if (!user) throw new UnauthorizedException('Invalid credentials');
const token = jwt.sign(
{ id: user.id, username: user.username },
'SECRET_KEY',
{ expiresIn: '1h' },
);
return { access_token: token };
}
현재 Auth Service에서는 위와 같이 user 데이터를 사용하는 것을 확인할 수 있습니다.
Service와 Repository를 분리해야하니 Auth 전용 Repository(auth.repository.ts) 파일을 무조건 새로 만드는 것이 맞을까요?
이 경우 상황에 따라 다르지만, 현재 코드에서는 user 데이터를 "관리"하기보다는 "조회하고 사용하는" 역할에 더 가깝기 때문에 Auth에서 User Repository를 의존성 주입으로 받아와 사용하는 것이 더 적절해 보입니다.
@Injectable()
export class AuthService {
constructor(private readonly userRepository: UserRepository) {}
login(username: string, password: string) {
const user = this.userRepository.findByName(username);
if (!user || user.password !== password)
throw new UnauthorizedException('Invalid credentials');
const token = jwt.sign(
{ id: user.id, username: user.username },
'SECRET_KEY',
{ expiresIn: '1h' },
);
return { access_token: token };
}
Auth Service를 위와 같이 수정해주었습니다.
User Repository에서는 name으로 유저를 찾아 데이터를 반환해주는 로직을 추가해주었고,
Auth Service에서는 이를 받아와 유저를 검증하는 비즈니스 로직을 처리하도록 해주었습니다.

수정한 후 실행을 하면 위와 같이 오류가 발생합니다.
이 오류가 바로 의존성 주입(DI)과 관련된 오류인데요.
Auth Service에서 User Repository를 의존할 수 없다는 오류입니다.
이를 해결하기 위해서 해야하는 것이 바로 의존성 주입 설정입니다.
의존성 주입 설정을 하기 위해서는 module을 사용해야 합니다.
module을 내보낼 user.module과 module을 받아올 auth.module 두 개가 필요합니다.
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
@Module({
providers: [UserService, UserRepository],
exports: [UserRepository],
})
export class UserModule {}
user.module.ts는 위와 같이 작성해주었습니다.
module에는 기본적으로 3가지 속성이 있습니다.
- imports : 다른 Module에서 제공하는 기능을 가져와 사용합니다.
- providers : 해당 Module에 등록할 Provider(Service, Repository 등)를 정의합니다.
- exports : 해당 Module에서 정의한 Provider 중, 다른 Module에서 사용할 수 있도록 공개할 기능을 지정합니다.
이 3가지 속성을 이해한 후 다시 코드를 보면, user.module은 UserService와 UserRepository를 Provider로 정의하고 있고,
이 중 UserRepository만 다른 Module에서 사용할 수 있도록 지정해주었습니다.
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from 'src/user/user.module';
@Module({
imports: [UserModule],
providers: [AuthService],
})
export class AuthModule {}
이제 auth.module.ts 코드입니다.
Auth Service에서 UserRepository 기능을 사용하기 때문에, imports로 가져와줍니다.
이때 주의할 점은 UserRepository를 적어주는 것이 아니라, UserModule을 적어줘야 한다는 점입니다.
의존성 주입은 Module 단위로 한다는 것을 기억해 주셔야합니다.
UserModule을 imports해도 실제로는 exports 설정한 UserRepository만 Auth Service에서 사용할 수 있게 됩니다.
여기까지 해주면 의존성 주입 설정이 끝나게 됩니다.
이제 다시 실행을 해보면 오류 없이 정상적으로 실행이 될 것입니다.
✅ 의존성 주입 테스트

auth/login 으로 요청을 보내보면, 기존과 똑같이 JWT 토큰이 잘 발급되는 것을 확인할 수 있습니다.
기존에는 "login요청 > AuthService 데이터 처리 및 로직 처리 > 토큰 반환" 형식으로 진행되었지만,
수정 후에는 "login요청 > UserRepository 데이터 처리 > AuthService 로직 처리 > 토큰 반환" 형식으로 변경되었습니다.
🛠️ 현재 폴더 구조

😊 마무리
이렇게 오늘 Service와 Repository를 분리하고, 의존성 주입 설정하는 방법까지 정리해보았습니다.
내용은 좀 길었지만 어려운 부분은 아니었기 때문에 쉽게 넘어갈 수 있을 것 같습니다.
의존성 주입은 NestJs에서 가장 핵심적인 부분이기 때문에 꼭 이해하시고 넘어가시길 바랍니다.
또한 이러한 개념은 Spring과 같은 여러 백엔드 프레임워크에서 사용되는 개념이기 때문에 한 번 공부해두시면 계속 사용하는 개념이 될 것이라고 생각합니다.
현재까지 진행된 로직을 확인해보면,
로그인 구현(JWT 발급) -> API 요청 -> Guard로 JWT 검증 -> Decorator로 데이터 추출 -> DTO로 데이터 검증
+ Service와 Repository 분리
+ 의존성 주입
까지 진행이 되었습니다.
다음 글에서는 interceptors를 사용한 응답 속도 측정과 반환 값 통일에 대해서 정리해볼 예정입니다.
분량이 된다면 filters를 사용한 오류 반환 값 통일까지 정리해보도록 하겠습니다.
그럼 지금까지 읽어주셔서 감사드리며, 다음에 더 잘 정리해서 찾아오도록 하겠습니다.
KYT CODING COMMUNITY Discord 서버에 가입하세요!
Discord에서 KYT CODING COMMUNITY 커뮤니티를 확인하세요. 27명과 어울리며 무료 음성 및 텍스트 채팅을 즐기세요.
discord.com
KYT CODING COMMUNITY 가입하기!