반응형

저번에는 SQS 송수신하는 코드를 구현해 보았는데요!
이번에는 이어서 SQS로 전송한 데이터를 Lambda로 받아서 알림톡으로 전송하는 서비스를 구현해 보겠습니다!
 
이전 글 참고 해주세요!
[AWS SQS] Nestjs로 SQS 전송, 수신 구현하기
 

AWS Lambda란?

Lambda는 서버를 따로 두지 않고도 코드를 실행할 수 있는 이벤트 중심의 서비스입니다.
작은 단위의 API를 만들고 싶은데 EC2 인스턴스를 생성하기에는 규모와 비용이 너무 커지기 때문에 간편하게 코드를 작성하고 업로드하면 API 사용이 가능합니다!
비용도 사용한 만큼만 지불하면 되기 때문에 너무 좋은 서비스라고 생각합니다.
 

1. IAM 역할 생성

Lambda를 호출할 수 있는 권한을 가진 IAM을 생성해 줍니다.
SQSFullAccess 권한과 LambdaBasicExecutionRole 권한을 넣어줍니다.

 

2. Lambda 함수 생성

이름과 함수를 어떤 언어로 작성할 것인지 고르고
실행 역할에는 기존 역할 사용을 클릭한 후 방금 생성해 준 역할을 선택해 줍니다.
kakao-messaging이라는 이름으로 생성해 볼게요!
 

3. Lambda 트리거 구성

Lambda에 SQS 트리거를 추가해 줍니다!
트리거에 추가된 SQS에 쌓일 때마다 Lambda가 실행됩니다.

트리거가 생겼습니다!
 

4. Lambda 환경변수 추가

이 부분은 자유이지만 저는 환경변수를 생성해서 코드를 작성해 보도록 하겠습니다!
구성 > 환경 변수 > 편집

 

4. Lambda 로그 스트림 확인

Lambda 로그는 CloudWatch에 쌓이고 있습니다.
로그 확인은 모니터링 > CloudWatch 로그 확인에서 확인할 수 있습니다.

 
카카오 알림톡 전송 코드는 다음 게시글에서 쓰겠습니다~ 

반응형
반응형

AWS SQS가 뭐지?

Amazon Simple Queue Service(SQS)는 쉽게 말해서 Queue입니다.
마이크로서비스, 분산 시스템 등에서 메시지 손실 걱정 없이 어떤 볼륨의 메시지든 전송, 저장 및 수신이 가능한 메시지 대기열입니다.
표준대기열, FIFO 대기열 유형이 있습니다.
 
 
그럼 SQS 송수신 구현을 해보도록 하겠습니다~

1. AWS SQS 대기열 생성

대기열 유형을 선택해야하는데, 각 장단점이 있습니다.
표준 대기열 : 무제한 처리량, 두 번 이상 메시지가 전달될 수 있음, 순서 보장이 안됨
FIFO 대기열 : 정확한 처리, 순서 보장
 
저는 FIFO 방식으로 선택하겠습니다.
FIFO 대기열은 이름 뒤에 반드시 .fifo를 붙여야 합니다.
 

저는 KAKAO_MESSAGE.fifo 라는 이름의 SQS를 만들어보겠습니다.
 

 

2. SQS 메시지 전송 test

SQS가 생성이 됐다면 메시지 송수신 테스트를 해볼 수 있습니다.
 

메시지 전송 테스트

메시지 수신 테스트

메시지 폴링 버튼을 누르면 수신 테스트 시작이고, 그 사이에 메시지가 들어온다면 하단의 메시지란에 새로운 메시지가 생성이 됩니다.
 

3. IAM 사용자 생성

이제 코드에서 SQS 송수신을 하기 위해서 IAM 사용자를 생성해야 합니다.
aws-sqs-group이라는 이름으로 생성해 보겠습니다.
 

생성했다면 생성한 IAM 사용자의 key id와 access key를 가지고 코드를 작성해 보도록 할게요.
 

4. 코드 작성

Nestjs로 작성해 보겠습니다.
저는 @ssut/nestjs-sqs 라이브러리를 사용해서 기능 구현 해봤습니다!
 
라이브러리 install

npm i --save @ssut/nestjs-sqs
yarn add @ssut/nestjs-sqs

 

test-sqs.module.ts

sqs producer 설정을 해주는 부분입니다.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from "@nestjs/config";
import { SqsModule } from "@ssut/nestjs-sqs";
import { SQSClient } from "@aws-sdk/client-sqs";

@Module({
  imports: [
    SqsModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => {
        return {
          producers: [{
            name: configService.get('SQS_NAME'),
            queueUrl: configService.get('SQS_PRODUCER_QUEUE_URL'),
            sqs: new SQSClient({
              region: configService.get('AWS_REGION'),
              credentials: {
                accessKeyId: configService.get<string>('AWS_SQS_ACCESS_KEY_ID'),
                secretAccessKey: configService.get<string>('AWS_SQS_SECRET_ACCESS_KEY') ,
              }
            })
          }]
        };
      },
    })
  ],
  providers: [TestSqsService],
  exports: [TestSqsService]
})
export class TestSqsModule {}

 

test-sqs.service.ts

sqs 메시지 송신 기능 메소드 구현부입니다.

import { Inject, Injectable } from "@nestjs/common";
import { SqsService } from "@ssut/nestjs-sqs";
import { MessageDto } from "../common/dto/biz.msg.dto";
import { ConfigService } from "@nestjs/config";

@Injectable()
export class TestSqsService {
  constructor(
    private configService: ConfigService,
    private readonly sqsService: SqsService,
  ) {}

  /**
   * SQS Message 송신
   * @param message SQS 전송 메시지
   */
  async send(message: MessageDto[]) {
    try {
      const body = JSON.stringify(message);

      const payload = {
        id: uuid, // 중복 방지를 위해 uuid 사용
        body: body,
        groupId: uuid,
        deduplicationId: uuid
      }

      const response = await this.sqsService.send(
        this.configService.get('SQS_NAME'),
        payload
      );

      console.log(`[SQS] send successful : [${JSON.stringify(response)}]`);

      return response;
    } catch (error) {
      console.log(error);
    }
  }
}

 
sqs consumer 부분도 같은 라이브러리를 사용해서 만들 수 있습니다~
 

References

https://www.npmjs.com/package/@ssut/nestjs-sqs

반응형
반응형

NestJS 란?

NestJS는 효율적이고 확장 가능한 Node.js 서버측 애플리케이션을 구축하기 위한 프레임워크입니다.
Express 또는 Fastify 프레임워크를 래핑 하여 동작합니다. (기본으로 설치하면 Express를 사용합니다.) 
OOP (객체 지향 프로그래밍 Object Oriented Programming), FP (함수형 프로그래밍 Functional Programming) 및 FRP (함수형 반응형 프로그래밍 Functional Reactive Programming) 요소를 결합합니다.

NestJS의 특징

  • NestJS는 Angualr로부터 영향을 많이 받았습니다. 모듈/컴포넌트 기반으로 프로그램을 작성함으로써 재사용성을 높여줍니다.
  • IoC(Inversion of Control, 제어 역전), DI(Dependency Injection, 의존성 주입), AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)와 같은 객체지향 개념을 도입하였습니다.
  • 프로그래밍 언어는 타입스크립트를 기본으로 채택하고 있어 타입스크립트가 가진 타입 시스템의 장점을 가집니다.
  • Node.js는 손쉽게 사용할 수 있고 뛰어난 확장성을 가지고 있지만, 높은 자유도로 인해 Architecture 구성이 어렵습니다. 이러한 단점을 보완하기 위해 NestJS가 탄생하게 되었습니다.

NestJS의 장단점

  • NestJS는 데이터베이스, ORM, 설정(Configuration), 유효성 검사 등 수많은 기능을 기본 제공하고 있어 편리합니다.
  • 필요한 라이브러리를 쉽게 설치하여 기능을 확장할 수 있는 Node.js 장점도 그대로 가지고 있습니다.

 

NestJS를 사용하여 개발하면서 느낀 점

러닝 커브가 낮아서 쉽게 접근 가능한 프레임워크인 것 같습니다.
저도 처음에는 영어문서이긴 하지만 문서도 잘 나와있는 편이라서 혼자 문서 보면서 간단한 서비스 만드는 것부터 시작했습니다. (한글문서도 나왔었는데 갑자기 사라짐..)
하지만 아직 사용하는 사람이 많지 않아서 오류 발생 시 찾기가 수월하지는 않습니다.
그래도 점점 Nest를 공부하는 사람들이 많아지는 것 같아서 너무 좋습니다.
 
그리고 Java의 Spring과 Architecture 구성이 유사하기 때문에 Spring 경험이 있는 분들은 금방 익힐 수 있을 것 같습니다.
다음에는 Spring과 NestJS의 차이점을 들고 오도록 하겠습니다.
 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

저번 유닛 테스트에 이어서 e2e 테스트를 해보도록 하겠습니다.

[NestJS] 유닛 테스트(Unit Testing)

 

e2e Test

e2e Test는 end-to-end test로 사용자 입장에서 테스트를 하는 것입니다.

 

 

app.e2e-spec.ts

it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Welecome to my API');
  });
  • request와 url을 받고, get으로 요청해서 200과 Welecome to my API라는 메시지를 받아야 합니다.

 

getAll()

it('/movies (GET)', () => {
    return request(app.getHttpServer())
      .get('/movies')
      .expect(200)
      .expect([]);
  })

 

post

  • 서버에 request 해서 movies에 post 할 때, 이 정보를 보내면 201을 받는지 테스트합니다.
describe('/movies', () => {
    it('GET', () => {
      return request(app.getHttpServer())
        .get('/movies')
        .expect(200)
        .expect([]);
    });
    it('POST', () => {
      return request(app.getHttpServer())
        .post('/movies')
        .send({
          title: "Test",
          year: 2000,
          genres: ['test']
        })
        .expect(201); // 생성
    })
  });

 

delete()

  • 404 notfountexception 이 나오는지 확인합니다.
it('DELETE', () => {
      return request(app.getHttpServer())
        .delete('movies').expect(404);
    })

 

todo

describe('/movies/:id', () => {
    it.todo('GET');
    it.todo('DELETE');
    it.todo('PATCH');
  });

 

beforeEach

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });
  • 테스트가 새로 생길 때마다 새로운 애플리케이션을 만듭니다.
  • 새로 앱을 만들 때마다 데이터베이스가 텅텅 비어있으니까 새로 데이터를 넣어줘야 하는 귀찮음이 있습니다.

→ 그래서 beforeEach를 beforeAll로 바꿔보도록 하겠습니다.

beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
  • beforeAll()로 바꾸고 it.todo() 부분도 변경해줍니다.
describe('/movies/:id', () => {
    it('GET 200', () => {
      return request(app.getHttpServer()).get('/movies/1').expect(200);
    });
    it.todo('DELETE');
    it.todo('PATCH');
  });

→ 위에서 post로 데이터를 만들었으니깐 200이 뜰 줄 알았는데 에러 발생!!

→ 왜? 200이 아니라 404이기 때문에

 

  1. movies.service.ts로 들어가서 getOne()에 넘긴 id 출력
getOne(id: number): Movie {
        console.log(id);
        ...
    }

id 값 잘 넘어옴

 

Insomnia에서 확인

빈 배열이 들어간 것을 확인

 

POST로 데이터를 담고 getOne()으로 테스트합니다.

insomnia 에서는 잘 나옴

 

💡 근데 테스트할 때는 왜 안 나올까요?

 

  • id의 type을 확인해봅니다.
getOne(id: number): Movie {
        console.log(typeof id);
				... }

number가 나옵니다.

반응형
  • npm run test:e2e로 확인해봅니다.

→ 테스트에서는 string으로 나옵니다.

→ movie.id 는 number이고, id는 string이기 때문에 찾을 수 없다고 뜨는 것

 

💡 왜 실서버에서의 id는 number이고, 테스트 서버에서는 string으로 나올까요?

 

 

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist:true, // 아무 decorator 도 없는 어떤 property의 object를 거름
    forbidNonWhitelisted: true, // 잘못된 property의 리퀘스트 자체를 막아버림
    transform: true, // 실제 원하는 타입으로 변경해줌
  }));
  await app.listen(3000);
}
  • transform이라는 것을 넣었습니다.
  • 그래서 getOne()을 호출할 때 id 가 내가 원하는 number 타입으로 변경됩니다.
getOne(id: number): Movie
  • 하지만 문제는 url 은 string입니다.
    • 어떤 이유인지 e2e 테스트에서 transform 이 작동하지 않습니다.
      → 왜냐하면 e2e 테스트할 때 새로운 앱을 생성을 하는데 어떤 pipe 에도 올리지 않음..

 

💡 테스트에도 실제 애플리케이션 환경을 그대로 적용시켜줘야 합니다!!

 

 

app.e2e-spec.ts

...
beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({
      whitelist:true, // 아무 decorator 도 없는 어떤 property의 object를 거름
      forbidNonWhitelisted: true, // 잘못된 property의 리퀘스트 자체를 막아버림
      transform: true, // 실제 원하는 타입으로 변경해줌
    }));
    await app.init();
  });
...
  • 다시 테스트해보면 잘 나오는 것을 확인할 수 있습니다.

 

describe('/movies/:id', () => {
    it('GET 200', () => {
      return request(app.getHttpServer()).get('/movies/1').expect(200);
    });
    it('GET 404', () => {
      return request(app.getHttpServer()).get('/movies/999').expect(404);
    });
    it('PATCH', () => {
      return request(app.getHttpServer()).patch('/movies/1').send({title: 'Update test'}). expect(200);
    });
    it('DELETE', () => {
      return request(app.getHttpServer()).delete('/movies/1').expect(200);
    });
  });

 

  • 잘못된 데이터를 가진 movie를 create 하는지 테스트
it('POST 201', () => {
      return request(app.getHttpServer())
        .post('/movies')
        .send({
          title: "Test",
          year: 2000,
          genres: ['test']
        })
        .expect(201); // 생성
    });

    it('POST 400', () => {
      return request(app.getHttpServer())
        .post('/movies')
        .send({
          title: "Test",
          year: 2000,
          genres: ['test'],
          other: 'thing'
        })
        .expect(400); // 생성
    });

 

 

마치면서

오늘은 저번 유닛 테스트에 이어서 E2E 테스트를 해보았습니다.

매번 프로젝트를 할 때마다 테스트 코드 작성을 습관화하려고 합니다.

모든 유닛 테스트를 할 수 없다면 E2E 테스트만이라도 해서 기능이 잘 작동하는지 테스트해보도록 합시다.

 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] Swagger 생성하기  (0) 2022.02.21
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14
반응형

main.ts 에서 config, document 변수를 SwaggerModule에 넣는다.

// main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  ...

  const config = new DocumentBuilder()
    .setTitle('Unicorn')
    .setDescription('Unicorn API description')
    .setVersion('1.0')
    .addTag('unicorn')
    .build();

  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  const port = process.env.PORT;
  await app.listen(port);
}
bootstrap();

 

Controller의 Tag 부여

@ApiTags('domains')
@Controller('domains')
export class DomainController {
	constructor(private readonly domainService: DomainService) {}
  ...
}

 

api 설명 부여하기

@ApiTags('scenario')
@Controller('domains')
export class ScenarioController {
	constructor(private readonly scenarioService: ScenarioService) {}

	@ApiOperation({ summary: '해당 도메인의 모든 시나리오 검색' })
	@Get('/:domain/scenarios')
	async getAllScenarios(@Param('domain') domain: string): Promise<Scenario> {
		return this.scenarioService.getAllScenarios(domain);
	}

	...
}

 

Schemas 적용

@ApiTags('authorization')
@Controller('authorization')
export class AuthorizationController {
	constructor(private readonly authorizationService: AuthorizationService) {}

	@ApiBody({ type: searchAdminDto })
	@Post()
	async searchAdmin(
		@Body('adminData') adminData: searchAdminDto,
		): Promise<boolean> {
		return this.authorizationService.checkAdmin(adminData);
	}
}

Body 로 들어오는 DTO를 ApiBody 데코레이터로 알려주고, DTO의 필요한 값들을 ApiProperty 데코레이터를 사용하면 된다.

 

export class searchAdminDto {
    @ApiProperty()
    @IsString()
    id: string;

    @ApiProperty()
    @IsString()
    pw: string;    
}

 

📌 ApiBody 데코레이터은 하나의 메서드 당 한 번만 사용 두 개 이상 사용 시 마지막 데코레이터로 적용된다.

 

@ApiProperty 데코레이터

export class searchAdminDto {
    @ApiProperty({
        type: String,
        default: 'default string',
        description: '사원번호'
    })
    @IsString()
    id: string;

    @ApiProperty({
        type: String,
        description: '비밀번호'
    })
    @IsString()
    pw: string;    
}

      •  

 

반응형

'Backend > Nestjs' 카테고리의 다른 글

[NestJS] JWT AuthGuard/Strategy  (0) 2024.01.04
[NestJS] NestJS 란?  (0) 2022.06.10
[NestJS] E2E Testing  (0) 2022.02.27
[NestJS] 유닛 테스트(Unit Testing)  (0) 2022.02.16
[NestJS] Docker 304 undefined 에러  (0) 2022.02.14

+ Recent posts