본문 바로가기

Backend/NestJS

NestJS - 대충 서비스 만들어보기 (4)

저번엔 Controller 와 Provider 등을 알아본다고 별로 한게 없는데, 이번엔 광고 컨텐츠 등록 API 를 한번 건드려보겠습니다.

DB 는 좀 나중에 연동하기로하고, 컨텐츠를 등록하면 Slack 으로 알람을 보내는 모듈을 만들어보겠습니다.

뭔가 사이드부터 하는 것 같다면 그게 맞습니다..

 

export class CreateAdDto {}

 

일전에 생성했던 리소스대로면 AdsController 의 create, update 메서드는 CreateAdDto 타입의 파라미터를 받게 되어있습니다.

DTO (Data Transfer Object) 는 굳이 적지 않아도 아시다시피 프로세스나 레이어 사이에서 데이터를 전송하는 객체입니다.

직렬화, 역직렬화 로직 또는 단순한 getter setter 정도의 메서드만 가질 뿐 어떠한 비즈니스 로직도 담지 않습니다.

 

NestJS 에선 클라이언트 - 서버 사이에서 요청 / 응답을 전송하는 객체를 DTO 라고 볼 수 있으며, NestJS 에선 DTO 가 구현되어 있어

클라이언트의 요청과 함께 들어오는 데이터인 페이로드를 쉽게 다룰 수 있습니다.

지금은 위 코드처럼 CreateAdDto 가 비어있는데, 일단 이 부분부터 정의를 해보겠습니다.

 

export class CreateAdDto {
  adType: string;
  title: string;
  thumbnail: string;
  content: string;
  country: string;
}

 

뭐 추후에 이것저것 들어갈수도 있겠지만, 일단 광고라는걸 등록하는데는 이정도 정보면 충분할 것 같습니다.

광고타입, 광고제목, 썸네일, 광고내용, 국가 정도의 정보를 받게 되어있습니다. 타입에만 ad 가 붙은건 typescript 에서 type 키워드와
구분하기 위함일 뿐 별 의미는 없습니다. 일부는 나중에 enum 으로 바뀌어야할 필요도 있지만, 일단 string 으로 갑니다.

 

@Controller('ads')
export class AdsController {
  constructor(private readonly adsService: AdsService) {}

  @Post()
  create(@Body() createAdDto: CreateAdDto) {
    console.info(
      createAdDto.adType,
      createAdDto.title,
      createAdDto.thumbnail,
      createAdDto.content,
      createAdDto.country,
    );
    return this.adsService.create(createAdDto);
  }
  ...
}

 

제대로 동작하는지 확인차 console.info 로 확인해볼까 합니다.

 

 

Postman 을 사용해 위처럼 요청을 보내면, 정상적으로 페이로드를 받는걸 확인할 수 있습니다.

DTO 는 여기까지만 일단 확인하고, Slack 서비스를 별도 모듈로 만들어보겠습니다.

 

> nest g mo Slack
> nest g s Slack

 

Slack 서비스는 외부로부터 HTTP 요청을 받는게 아니므로 nest-cli 를 통해 모듈과 서비스만 생성해줍니다.

 

// src/slack/slack.service.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';

@Injectable()
export class SlackService {
  private token: string;
  private channel: string;

  constructor() {
    this.token = 'XXX';
    this.channel = 'XXX';
  }

  async send(adType: string, title: string, country: string) {
    await axios({
      method: 'post',
      url: 'https://slack.com/api/chat.postMessage',
      data: {
        channel: this.channel,
        attachments: [
          {
            color: '#999999',
            title: '광고가 등록되었습니다.',
            text: `${adType} - ${title} - ${country}`,
          },
        ],
      },
      headers: {
        'Content-Type': 'application/json;charset=UTF-8',
        Authorization: `Bearer ${this.token}`,
      },
    });
  }
}

 

생성한 SlackService 를 저는 위와 같이 만들어봤습니다. 위에서 깜빡했는데 HTTP 요청에 사용하려고 axios 모듈을 추가했습니다.

충분히 나중에 확장될 수 있지만 우선 이 서비스는 광고가 등록되었을때만 사용된다라고 가정하고, slack api 를 사용해 채널로

slack message 를 보냅니다.

token, channel, url 이 지금은 하드코딩 되어있는데 이후 ConfigService 를 사용하도록 변경할 예정입니다.

 

// src/slack/slack.module.ts
import { Module } from '@nestjs/common';
import { SlackService } from './slack.service';

@Module({
  providers: [SlackService],
  exports: [SlackService],
})
export class SlackModule {}

 

아까 nest-cli 로 생성한 SlackModule 에는 SlackService 가 providers 에만 등록되어있고 exports 가 없을 것입니다.

우리는 이 SlackService 를 AdsService 에 주입해 사용해야하므로 exports 에 추가해줍니다.

 

// src/ads/ads.module.ts
import { Module } from '@nestjs/common';
import { AdsService } from './ads.service';
import { AdsController } from './ads.controller';
import { SlackModule } from '../slack/slack.module';

@Module({
  imports: [SlackModule],
  controllers: [AdsController],
  providers: [AdsService],
})
export class AdsModule {}

 

SlackModule 에서 exports 해준것만으론 AdsModule context 에서 SlackService 를 사용할 수 없습니다.

SlackModule 을 AdsModule 에 imports 해줍니다.

 

...

@Injectable()
export class AdsService {
  constructor(private readonly slackService: SlackService) {}

  async create(createAdDto: CreateAdDto) {
    const { adType, title, country } = createAdDto;
    await this.slackService.send(adType, title, country);
    return 'This action adds a new ad';
  }
  ...
}

 

이제 SlackService 를 AdsService 에 주입하고, create 시 SlackService 의 send 메서드를 호출해줍니다.

진짜 생성은 건드리지도 않고 이것부터 하고 있다는게 제가 보기에도 이상하긴 하네요. 하고싶은거부터 하다보니..

 

 

위에서 Postman 으로 동일한 요청을 보낸 후 slack 을 확인하면 위와 같은 메세지가 온 것을 볼 수 있습니다.

아래는 현재까지의 디렉토리 구조입니다.

 

 

너무 간단하긴하지만, Slack 부분만 모듈화를 해보았습니다. 일반적으로 모듈이라는 건 작게보면 정의상 하나의 함수도 모듈이라고 할 수 있겠습니다만, 여러 컴퍼넌트를 조합해 그보다는 좀 더 큰 작업을 수행할 수 있는 단위입니다.

애초에 Node.JS 부터 이런 모듈화에 특화되어있고, 하나의 서비스 또는 서버들은 여러 모듈이 모여 만들어지게 되죠.

 

NestJS 앱은 하나의 루트 모듈이 존재하고 (일반적으론 app.module.ts) 이 루트 모듈들은 여러 다른 모듈들로 구성됩니다.

지금 작성하고 있는 서버도 app.module.ts 라는 루트 모듈을 ads.module.ts 와 slack.module.ts 가 구성하고 있죠.

 

어떤 기준으로 모듈을 나누느냐는 정해진 게 없지만 기본적으로 응집도를 높인다는 개념을 베이스로 모듈화를 가져가면 좋을 것입니다.

이렇게 나눈 모듈 자체가 점점 커지면 MSA 관점에서 하나의 마이크로서비스가 될 수도 있겠죠. 작은 모듈이라고 분리안하고 그냥 모듈 하나에 다 때려넣다간 상당한 똥이 되어있는것을 목도하게 될 것 같습니다.