Lambda 로 데일리 알람 받아보기

본 글에서는 AWS Lambda를 활용하여 매일 원하는 뉴스 기사를 이메일로 받아보는 방법에 대해 자세히 설명합니다.

 

예전에 재미로 6시간마다 날씨 정보를 [카카오톡 나와의 메세지] API 를 통해 보내는 코드를 작성한 적이 있었다. (https://github.com/iceCreamParlor/DailyAlarm)
JSON 라이브러리를 제외하고는 순수 자바 코드로 작성했고, 공공데이터포털의 [기상청 동네예보 정보조회 API] 와 카카오톡의 [나에게 보내기 API] 를 사용해서 구현했었다. 운영하는 서버는 가지고 있던 EC2 인스턴스에 직접 Java 프로세스를 돌려서 사용했었다. 이 때의 문제점은 크게 두 가지가 있었다.

1.하루에 몇 번 호출되지 않음에도, EC2 인스턴스 자원을 계속 사용해야 했던 점
   - 사실 낭비가 된 것은 아니고, 그 기회로 다른 웹서비스, DB 도 호스팅했었다.

2.DB 를 사용하고 있지 않아서, 카카오톡 관련 Access Token, Refresh Token 을 메모리에 저장시켜서 사용해야 했다. 
   - 이러한 이유료, 한 번 Refresh Token 이 만료되어 Token 값이 꼬여버리자, 문제가 발생하였고 서비스 자체가 죽어버렸다.

3. 날씨 데이터가 생각보다 궁금하지 않았다 (?)
  - 재미삼아 만들어 보긴 했지만, 생각보다 실용적이지 않다는 생각이 들었다.

 

이러한 문제점들 때문에, 새로운 방식, 더 생산성있는 컨텐츠, 더 효율성있는 개발 도구 를 이용해서 비슷한 역할을 하는 시스템을 만들었다. 사용한 스택은 다음과 같다.

- AWS CloudWatch (Lambda 를 하루에 한 번 Trigger 한다.)
- AWS Lambda (실질적인 로직을 수행함)
- AWS S3
- Typescript (자바에 비해 높은 생산성을 지니는 Javascript / 타입 안정성을 위한 Typescript)
- Serverless (Infrastructure As Code 를 도와주는 라이브러리)

제공하는 컨텐츠도 날씨 데이터에서, 크롤링한 뉴스 기사를 이메일로 보내주는 것으로 바꾸었다. (더 높은 흥미 + 쉬운 유지보수성)

Lambda Function 수행 코드는 다음과 같다. (실질적으로 메일 보내는 함수, 크롤링하는 함수는 생략)

https://github.com/iceCreamParlor/daily

// handler.ts

import { sendMail } from "./gmail";
import { Handler, Context, Callback } from "aws-lambda";
import AWS from "aws-sdk";
import "dotenv/config";
import { getNaverNews } from "./newsCrawler";

interface Response {
  statusCode: number;
  body: string;
}

const retry = 10;

const handler: Handler = async (
  event: any,
  context: Context,
  callback: Callback<any>
) => {
  // 실패할 경우를 대비해서 10회 시도한다.
  for (var i = 1; i <= retry; i++) {
    try {
      const s3 = new AWS.S3({
        accessKeyId: process.env.AWS_IAM_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_IAM_SECRET_ACCESS_KEY,
      });

      /**
       * S3 로부터 정보를 얻을 JSON 파일을 읽어온다.
       */
      const s3Response: any = await s3
        .getObject({
          Bucket: "heejae-bucket",
          Key: "daily.json",
        })
        .promise();
      const s3Json: any = JSON.parse(s3Response["Body"].toString("utf-8"));
      const s3Result: any = {
        keyword: s3Json.crawler.keyword as string[],
        email: s3Json.crawler.email as string[],
      };

      console.log(`s3Result ${s3Result}`);

      /**
       * 네이버에서 키워드들을 하나씩 크롤링해서,
       * 메일 컨텐츠를 채워 나간다.
       */
      let mailContent = (
        await Promise.all(
          s3Result.keyword.map(async (k: string) => await getNaverNews(k))
        )
      ).join("\n");

      /**
       * 전송할 이메일 리스트를 보고, 이메일을 발송한다.
       */
      await Promise.all(
        s3Result.email.map(
          async (e: string) =>
            await sendMail(
              e,
              `${new Date().toISOString().slice(0, 10)} 기사`,
              mailContent
            )
        )
      );

      break;
    } catch (err) {
      console.log(`${i} 번째 시도 실패`);
      console.error(err);

      if (i >= retry) {
        // 10 회 이상 시도했는데도 실패하면 실패코드 반환
        return {
          statusCode: 500,
          body: JSON.stringify({
            message: "Fail",
          }),
        };
      }
      // 10회보다 적게 시도되었으면 다시 시도
      continue;
    }
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Success",
    }),
  };
};

export { handler };

 

이를 배포할 serverless 설정은 다음과 같다.

service: daily

frameworkVersion: '2'

plugins:
  - serverless-plugin-typescript
  - serverless-offline
  - serverless-dotenv-plugin


provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-2
  environment:
    NODE_ENV: production

functions:
  dailyInfo:
    handler: src/handler.handler
    events:
      - schedule:
          name: daily-info-cloudwatch
          description: 'daily info cloudwatch'
          # 오전 9시에 Trigger
          rate: cron(0 0 * * ? *)

 


이것도 읽어보세요