@Injectable()
export class CounselingRepository {
constructor(
// DB 주입
// User DB
@InjectRepository(PetEntity)
private PetDB: Repository<PetEntity>,
@InjectRepository(DoctorEntity)
private DoctorDB: Repository<DoctorEntity>,
@InjectRepository(CounselingEntity)
private CounselingDB: Repository<CounselingEntity>,
) {
this.mapper = new CounselingMapper()
}
counseling.repository.ts
@Injectable()
export class CounselingService {
constructor(
@Inject()
private readonly repository: CounselingRepository,
) {}
conunseling.service.ts
서비스에서 쓰기 위한 레포지토리를 그냥 class로 구현해버리고 이렇게 service의 생성자에 해당 클래스를 박아 넣으면 이 서비스는 이 레포지토리밖에 사용하지 못한다.
이 서비스가 배포용, 테스트용, 혹은 다른 orm이나 dbms에 맞는 등 여러가지 레포지토리를 사용하게 하려면 기본 레포지토리를 interface로 구현한 후 이를 implements한 여러 레포지토리 클레스들을 만들면 좋다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forFeature([PetEntity, CounselingEntity, DoctorEntity]),
],
providers: [
CounselingRepository,
CounselingService,
],
controllers: [CounselingController],
exports: [
CounselingRepository
],
})
export class CounselingModule {}
counseling.module.ts
그리고 서비스와 레포지토리가 이런 경우의 모듈은 이렇게 되있을 것이다. providers에 그냥 CounselingRepository 클래스만 명시되어 있다. 이부분이 바뀔 것이다.
export const COUNSELING_REPOSITORY = "Counseling Repository"
export interface CounselingRepository {
//스케쥴을 반환
getSchedules(): Promise<Schedule[]>;
// 진료내역을 저장하고, 성공했는지 반환함
registerCounselingHistory(info: CounselingInfo): Promise<Counseling>;
// start ~ end 사이의 히스토리를 반환함
getConselingHistories(startDate: Date, endDate: Date): Promise<Counseling[]>;
getOneCounseling(counselingId: string): Promise<Counseling>;
deleteOneCounseling(counselingId: string): Promise<boolean>;
}
counseling.repository.ts
이와 같이 interface로 CounselingRepository 를 만들고, 필요한 메소드의 원형들만 선언해 놓는다.
코드 위쪽에 COUNSELING_REPOSITORY라는 상수값이 있는데 이건 인젝션에 사용되는데 뒤에 나온다.
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { CounselingRepository } from "../domain/counseling.repository";
import { CounselingEntity } from "./counseling.entity";
@Injectable()
export class CounselingRepositoryImpl implements CounselingRepository {
constructor(
// DB 주입
// User DB
@InjectRepository(PetEntity)
private PetDB: Repository<PetEntity>,
@InjectRepository(DoctorEntity)
private DoctorDB: Repository<DoctorEntity>,
@InjectRepository(CounselingEntity)
private CounselingDB: Repository<CounselingEntity>,
) {
this.mapper = new CounselingMapper()
}
counseling.db.ts
해당 인터페이스를 implements하여 CounselingRepositoryImpl 클래스를 구현하고 해당 함수들의 내용을 실제로 구현하기.
class TestCounselingRepository implements CounselingRepository {
//생략
}
describe('CounselingService', () => {
let service: CounselingService;
beforeEach(async () => {
const repository: CounselingRepository = new TestCounselingRepository()
service = new CounselingService(repository)
});
//생략
}
counseling.service.test.ts
서비스를 테스트하는 파일인 서비스테스트 파일에 테스트레포지토리 클래스를 구현한다.
테스트레포지토리는 '레포지토리를 사용하는 곳'을 테스트하기 위한 클래스이고 레포지토리를 테스트하기 위한 클래스가 아니다.
서비스가 바로 주로 '레포지토리를 사용하는 곳'이므로 여기다 구현한 것.
서비스를 테스트할 때에는 레포지토리가 실제로 구현된 레포지토리가 아니어도 상관 없기 때문에 가짜 레포지토리를 만드는 것.
import { Inject, Injectable } from '@nestjs/common';
import { COUNSELING_REPOSITORY, CounselingRepository } from './counseling.repository';
@Injectable()
export class CounselingService {
constructor(
@Inject(COUNSELING_REPOSITORY)
private readonly repository: CounselingRepository,
) {}
conunseling.service.ts
이 코드는 맨위에 넣은 서비스코드와 동일하지만 지금 적어놓은 CounselingRepository는 클래스가 아니라 인터페이스이다.
근데 이 서비스에서 배포용으로 써야할 메소드들은 CounselingRepositoryImpl 클래스에 있을텐데 그것을 쓰겠다는 코드가 어디에도 보이지 않는다.
이는 서비스가 아니라 모듈에 명시되어 있다.
일단 이 코드에서 @Inject(COUNSELING_REPOSITORY) 라고 Inject안에 어떤 변수가 들어가 있음에 주목해야 한다. 이 변수는 아까 인터페이스를 구현할 때 있던 상수값이다.
import { Module } from '@nestjs/common';
import { CounselingController } from './api/counseling.controller';
import { CounselingService } from './domain/counseling.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CounselingRepositoryImpl } from './data/counseling.db';
import { CounselingEntity } from './data/counseling.entity';
import { COUNSELING_REPOSITORY } from './domain/counseling.repository';
import { PetEntity } from '../value-data/pet.db';
import { DoctorEntity } from '../value-data/doctor.db';
@Module({
imports: [
TypeOrmModule.forFeature([PetEntity, CounselingEntity, DoctorEntity]),
],
providers: [
{
provide: COUNSELING_REPOSITORY,
useClass: CounselingRepositoryImpl,
},
CounselingService,
],
controllers: [CounselingController],
exports: [
{
provide: COUNSELING_REPOSITORY,
useClass: CounselingRepositoryImpl,
},
],
})
export class CounselingModule {}
counseling.module.ts
프로바이더에 레포지토리 쪽을 보면 객체로써
provide : '문자열'
useClass : 배포용레포지토리 클래스
이런 구조로 되어 있는 것을 볼 수 있는데,
이 말은, 해당 문자열을 넣은 채로 inject를 하면 그 경우에 useClass에 명시된 클래스를 사용하겠다는 말이다.
자세히는 모르지만 nest는 프로바이더에 interface 자체를 명시하지 못한다고 하며 이런 식으로 해당 interface를 주입할 때 사용할 클래스를 명시할 수 있다고 한다.
이렇게 모듈에 써있으므로 이제
서비스에서 '@Inject(COUNSELING_REPOSITORY)' 데코레이터를 사용해서 주입했던 인터페이스는 배포용 레포지토리로써 생성자가 호출되게 된다.
이제 여기까지는 실제 api 호출 시에 서비스에서 CounselingRepositoryImpl를 사용하게 한 방법이고.
class TestCounselingRepository implements CounselingRepository {
//테스트용 메소드들 구현하기...
}
describe('CounselingService', () => {
let service: CounselingService;
beforeEach(async () => {
const repository: CounselingRepository = new TestCounselingRepository()
service = new CounselingService(repository)
});
//생략
}
counseling.service.test.ts
아까 작성해놓은 서비스테스트 파일에 이미 다 나와있었는데
여기에 저 테스트레포지토리 클래스를 구현해놓고
describe 내에서
beforeAll이든 beforeEach든 안에서
const repository: CounselingRepository = new TestCounselingRepository()
service = new CounselingService(repository)
이 두줄을 추가하면 된다.
테스트레포지토리 타입의 객체를 만들고 이를 서비스의 생성자에 인자로 집어넣는다.
아까 서비스의 생성자의 매개변수로 특정 레포지토리클래스를 정해놓은게 아닌 인터페이스를 지정했으므로 그 인터페이스를 implements한 이 테스트레포지토리 객체도 인자로 들어갈 수 있는 것이다.