[TROUBLESHOOTING] 자동 재고 확보 시스템 MSA 아키텍처 구축

Son_Doobu96·2023년 2월 20일
0

Project

목록 보기
2/3

Step1. Lambda 서버(Sales API) - DB 연결

Lambda를 통해 Sales-api를 구축하고 이를 미리 배포된 RDS DB와 연결하는 작업을 구현한다.

먼저 제공된 코드를 확인 후 작업을 진행했다.

const serverless = require("serverless-http");
const express = require("express");
const app = express();
app.use(express.json())

const AWS = require("aws-sdk") // STEP 2
const sns = new AWS.SNS({ region: "ap-northeast-2" }) // STEP 2

const {
  connectDb,
  queries: { getProduct, setStock }
} = require('./database')

app.get("/product/donut", connectDb, async (req, res, next) => {
  const [ result ] = await req.conn.query(
    getProduct('CP-502101')
  )

  await req.conn.end()
  if (result.length > 0) {
    return res.status(200).json(result[0]);
  } else {
    return res.status(400).json({ message: "상품 없음" });
  }
});

app.post("/checkout", connectDb, async (req, res, next) => {
  const [ result ] = await req.conn.query(
    getProduct('CP-502101')
  )
  if (result.length > 0) {
    const product = result[0]
    if (product.stock > 0) {
      await req.conn.query(setStock(product.product_id, product.stock - 1))
      return res.status(200).json({ message: `구매 완료! 남은 재고: ${product.stock - 1}`});
    }
    else {
      await req.conn.end()
      return res.status(200).json({ message: `구매 실패! 남은 재고: ${product.stock}`});
    }
  } else {
    await req.conn.end()
    return res.status(400).json({ message: "상품 없음" });
  }
});

app.use((req, res, next) => {
  return res.status(404).json({
    error: "Not Found",
  });
});

module.exports.handler = serverless(app);
module.exports.app = app;

이후 적절한 핸들러에 맞게 serverless.yaml을 구성했다.

service: sales-api
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs14.x
  region: ap-northeast-2

functions:
  api:
    handler: handler.handler
    events:
      - httpApi: '*'

사실 Step1에서 가장 힘들었던 부분은 익숙하지 않은 SQL문이었다.
MySQL 엔진을 사용하는 RDS로 구성되어 있어 MySQL 접속 명령어를 통해 RDS에 접속하였다.

$ mysql -h <hostname> -u <uername> -p
이후 패스워드를 입력해준다.

이후 내 데이터베이스를 선택 후 데이터를 입력해주었다.

GET, POST 요청을 확인할 수 있었다.


Step2. “재고없음” 메세지 전달 시스템 구성

해당 파트에서는 재고가 없다는 메시지를 전달 할 수 있도록 함수를 수정하고 SNS를 작동시키고 이를 SQS에 전달하는 것을 목표로 했다.

이 부분에서 가장 고생했던 부분은 Serverless Framework에 대한 미숙함이었다. Serverless 프레임워크를 처음 사용 하다보니 SQS SNS의 구현까지는 했으나 서비스를 분리하고 올바른 구독 관계를 생성하지 못해 꽤나 애를 먹었다.

그래서 먼저 콘솔을 통해 전체 관계를 구성 후 테라폼으로 이를 문서화 하기로 생각한 후 프레임워크로 진행하지 못한 부분을 콘솔로 진행했다.

먼저 Lambda함수에서 구매실패 부분에 아래 함수를 추가해줬다.

const now = new Date().toString()
const message = `도너츠 재고가 없습니다. 제품을 생산해주세요! \n메시지 작성 시각: ${now}`
const params = {
  Message: message,
  Subject: '도너츠 재고 부족',
  MessageAttributes: {
    MessageAttributeProductId: {
      StringValue: product.product_id,
      DataType: "String",
    },
    MessageAttributeFactoryId: {
      StringValue: req.body.MessageAttributeFactoryId,
      DataType: "String",
    },
  },
  TopicArn: process.env.TOPIC_ARN
}

const result = await sns.publish(params).promise()

이후 SNS 서비스와 SQS 서비스를 생성하고 잘 연결까지 진행했는데 SQS에 메시지가 쌓이지 않는 문제가 발생했다.

아직 SQS를 트리거하는 함수가 있지 않은 상황이라 메시지가 삭제되는 것이 이상한데 아예 들어오지 않았다는 것을 금방 알 수 있었다.

함수를 여러번 수정해 보고 SQS 대기열을 삭제했다 고쳤다를 반복했지만 문제는 해결되지 않았다...

문제는 SQS의 엑세스 정책에 있었다.

SQS 엑세스 정책에서 접근 허용이 root 계정으로 제한되어 있는 것을 발견하고 이를 수정하여 문제를 해결할 수 있었다.


Step 3 : 메세지를 Factory API로 전송하는 Lambda 구성 및 DLQ 추가

아직 Factroy API를 구현하지 않았기 때문에 기본 틀 정도만을 구현했다.

먼저 SQS의 DLQ를 생성하여 연결해주었고 새로운 람다 함수를 생성해 기존 SQS를 트리거로 붙여주어 구성을 마쳤다.


오늘 느낀점

사실 내용적으로 별 볼일 없는 포스팅이었다. 솔직히 말하면 쉬운 작업이었지만 문제는 내 수행 능력이 오늘 너무 부족했다...
머리로 이해하지만 구현하지 못하는 답답함을 느끼면서 정신적으로 많이 피폐해진 프로젝트였지만 한편으로는 DevOps라면 다양한 프레임워크에 대한 시각을 넓혀야 한다는 필요성을 느끼게 해준 프로젝트였다.

그리고 제발... 기본을 돌아보자...엑세스 정책에서 3시간은 정말 아니었다 ㅠㅠ

profile
DevOps를 꿈꾸는 엔지니어 지망생!

0개의 댓글