지난번에 작성하지 못했던 Repository 를 연동해 광고 등록시 DB 에 저장하고 DB 로 부터 광고 데이터를 조회하는 API 를
완성해 보도록 하겠습니다.
- TypeORM Repository
NestJS 는 저장소 패턴을 지원하니 먼저 이를 사용해보도록 하겠습니다.
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity('Ad')
export class Ad {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 10, name: 'ad_type' })
adType: string;
@Column({ length: 30 })
title: string;
@Column({ length: 60 })
thumbnail: string;
@Column({ length: 60 })
content: string;
@Column({ length: 2 })
country: string;
}
크게 변경된건 없지만 먼저 entity 인 Ad 를 약간 수정했습니다.
누락했던 content 컬럼 추가와 adType 과 title 의 length 를 잘못 설정해서 약간 늘려주었습니다.
...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Ad } from './entities/ad.entity';
@Module({
imports: [
...,
TypeOrmModule.forFeature([Ad]),
],
...
})
export class AdsModule {}
AdsModule 에서 사용할 저장소를 TypeOrmModule 의 forFeature 메서드로 등록해줍니다.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateAdDto } from './dto/create-ad.dto';
import { UpdateAdDto } from './dto/update-ad.dto';
import { SlackService } from '../slack/slack.service';
import { Ad } from './entities/ad.entity';
@Injectable()
export class AdsService {
constructor(
private readonly slackService: SlackService,
@InjectRepository(Ad) private adRepository: Repository<Ad>,
) {}
async create(createAdDto: CreateAdDto) {
const { adType, title, country, thumbnail, content } = createAdDto;
const ad = new Ad();
ad.adType = adType;
ad.title = title;
ad.country = country;
ad.thumbnail = thumbnail;
ad.content = content;
await this.adRepository.save(ad);
await this.slackService.send(adType, title, country);
return 'This action adds a new ad';
}
...
}
변경된 AdsService 부분입니다. @InjectRepository 데커레이터를 사용해 Ad 저장소를 주입하고,
기존 create 메서드에선 ad entity 객체를 생성한 후에 save 메서드를 통해 DB 에 해당 엔티티를 영구 저장합니다.
Postman 을 통해 POST API 를 호출해보면, 201 응답이 반환되는 것을 확인할 수 있습니다.
실제 DB 에도 정상 저장된 것을 확인할 수 있습니다.
...
@Injectable()
export class AdsService {
...
findAll() {
return this.adRepository.find();
}
findOne(id: number) {
return this.adRepository.findOne({ where: { id } });
}
}
간단하게 모든 광고 리스트를 조회하는 findAll 메서드와 단일 광고를 조회하는 findOne 메서드도 위처럼 작성을 해봤습니다.
두 API 에 대해서도 잘 동작하는 것을 확인했습니다.
응답 결과인 entity 를 그대로 넘기는 것은 지양해야 하지만 당장은 별다른 기능이 있는건 아니니 패스합니다.
- Custom Repository
기존 TypeORM 0.2 버전에선 제공하는 EntityRepository 데커레이터만 사용하면 custom repository 를 구현하기 아주 간단했습니다.
하지만 EntityRepository 데커레이터는 0.3 버전으로 넘어오면서 deprecated 되었습니다.
TypeORM 진영에서 EntityRepository 를 deprecated 해서 custom repository 생성을 비활성화 한 이유는..
일단 0.3 버전에서 custom repository 를 만든 후에 다시 적도록 하겠습니다.
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Ad } from './entities/ad.entity';
import { CreateAdDto } from './dto/create-ad.dto';
export class AdsRepository extends Repository<Ad> {
constructor(
@InjectRepository(Ad) private readonly repository: Repository<Ad>,
) {
super(repository.target, repository.manager, repository.queryRunner);
}
async createUser(adDto: CreateAdDto): Promise<null> {
const { adType, title, country, thumbnail, content } = adDto;
const ad = new Ad();
ad.adType = adType;
ad.title = title;
ad.country = country;
ad.thumbnail = thumbnail;
ad.content = content;
await this.repository.save(ad);
return null;
}
async findAds(): Promise<Array<Ad>> {
return await this.repository.find();
}
async findAdById(id: number): Promise<Ad> {
return await this.repository.findOne({
where: { id },
});
}
}
먼저 src/ads 하위에 ads.repository.ts 파일을 생성하고 위와 같이 작성해주었습니다.
typeorm 의 Repository 를 상속하는 AdsRepository 를 만듦으로써 0.2 때와 유사하게 custom repository 를 구현할 수 있게 되었습니다. 물론 기존의 @EntityRepository 를 사용했던 것 처럼 데코레이터로 만들려면 조금 다른 구현이 필요하지만 여기선 패스하겠습니다.
Repository 는 EntityTarget, EntityManager 필수 그리고 QueryRunner 를 옵셔널하게 생성자 인자로 받습니다.
AdsRepository 에서는 필요한 인자들을 부모 생성자에 넘겨주고 필요한 메서드들을 구현해주면 됩니다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AdsService } from './ads.service';
import { AdsController } from './ads.controller';
import { SlackModule } from '../slack/slack.module';
import { Ad } from './entities/ad.entity';
import { AdsRepository } from './ads.repository';
@Module({
imports: [SlackModule, TypeOrmModule.forFeature([Ad])],
controllers: [AdsController],
providers: [AdsService, AdsRepository],
})
export class AdsModule {}
약간은 변경된 AdsModule 입니다. 사실 추가된거라곤 AdsService 에서 AdsRepository 를 사용할 수 있도록 providers 에 새롭게
생성한 AdsRepository 를 추가한게 전부입니다.
import { Injectable } from '@nestjs/common';
import { AdsRepository } from './ads.repository';
import { CreateAdDto } from './dto/create-ad.dto';
import { SlackService } from '../slack/slack.service';
@Injectable()
export class AdsService {
constructor(
private readonly slackService: SlackService,
private readonly adsRepository: AdsRepository,
) {}
async create(createAdDto: CreateAdDto) {
const { adType, title, country } = createAdDto;
await this.adsRepository.createUser(createAdDto);
await this.slackService.send(adType, title, country);
return 'This action adds a new ad';
}
findAll() {
return this.adsRepository.findAds();
}
findOne(id: number) {
return this.adsRepository.findAdById(id);
}
}
이제 AdsService 는 원래 @InjectRepository 데커레이터를 통해 주입받던 repository 에서 직접 생성한 AdsRepository 를 주입받는
코드로 변경되었습니다. 작성한 코드로 앱을 띄워 위에서 실행했던 API 들을 동일하게 수행해보면, 잘 동작하는 것을 확인할 수 있습니다.
앞에서 얘기하지 못한 부분들을 얘기하기 위해 custom repository 를 사용하기 전 AdsService 와 코드 비교를 좀 해보겠습니다.
// 기존 Repository 사용
findAll() {
return this.adRepository.find();
}
findOne(id: number) {
return this.adRepository.findOne({ where: { id } });
}
// custom repository 사용
findAll() {
return this.adsRepository.findAds();
}
findOne(id: number) {
return this.adsRepository.findAdById(id);
}
전체 광고를 조회하는 findAds 의 경우, custom repository 를 사용하더라도 해당 메서드에선 동일하게 repository 의 find 메서드를 호출해서 결과를 돌려주는게 전부입니다. 이런 경우 메서드를 굳이 만들 필요가 없음에도 @EntityRepository 데코레이터를 통해 custom repository 를 만들게되었고, 이것에 대해 여러 개발자들이 얘기를 나눈 결과 무분별한 사용으로 이어지게 되어 typeorm 측에서 이 데코레이터를 deprecated 했다고 합니다.
하지만 이는 지극히 간단한 예시에 불과하고, findOne 에서도 그렇지만 실제로 Repository 를 이용한 쿼리시에는 더 다양한 DB 연산을 날리게 되는데 이게 고스란히 Service 에 기술되는건 적절한 책임 분리로 보이진 않습니다. 따라서 현업에선 어차피 service layer 와 분리된 custom repository layer 를 작성하게 될거죠. 결국 판단은 자유지만 간단한 프로젝트라면 typeorm 의 repository 를 사용하는 것만으로 충분하고, 앱이 커지면서 필요한 기능들도 다양해진다면 custom repository 를 생성하는게 좋을 것 같습니다.
'Backend > NestJS' 카테고리의 다른 글
NestJS - 대충 서비스 만들어보기 (12) (0) | 2023.05.27 |
---|---|
NestJS - 대충 서비스 만들어보기 (11) (0) | 2023.05.20 |
NestJS - 대충 서비스 만들어보기 (9) (0) | 2023.05.07 |
NestJS - 대충 서비스 만들어보기 (8) (0) | 2023.05.01 |
NestJS - 대충 서비스 만들어보기 (7) (0) | 2023.04.23 |