호랑이한테물릴래

재고관리 시스템 Serverless 구현 본문

DevOps/sprint_project

재고관리 시스템 Serverless 구현

호랑이한테물릴래 2022. 7. 9. 19:19
반응형

Project 3의 목표

  • 메시지 큐의 Pub/Sub패턴과 Producer/Consumer 차이를 파악
  • DB 서버와의 통신하도록 연결
  • 특정 상황에서 SNS, SQS로 메시지 전달 시스템 구성
  • SQS에 들어온 메시지를 레거시 시스템(Factory API)로 전달
  • 레거시 시스템(Factory API)의 콜백 대상 리소스를 생성해서 DB에 접근하도록 구성

  • Lambda_server : 고객의 상품 주문을 담당하는 Sales API로 상품 정보, 수량조회, 주문, 입고 등을 담당한다.
  • SNS_stock_empty : 고객이 주문한 상품의 재고가 없을 경우, 메시지를 전달받는 SNS.
  • SQS_stock_queue : SNS를 통해서 전달받은 메시지를 보관하는 메시지큐. 전달못한 메시지는 SQS_DLQ로 보낸다.
  • SQS_dead_letter_queue : stock_queue의 visibility timeout동안 delete 요청을 받지못한 메시지를 보관하는 메시지 큐
  • Lambda_stock_lambda : 전달받은 재고부족 상품 정보를 Factory API로 전달하는 역할을 한다.

Lambda server 생성

//serverless.yaml - lambda server

service: sales-api
frameworkVersion: '3'

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

functions:
  api:
    handler: handler.handler
    events:
      - httpApi: '*'
//handler.js - lambda server

const serverless = require("serverless-http"); 
const express = require("express"); //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

//./database를 불러와서 연결한다. (import와 비슷한 역할)
const {
  connectDb,
  queries: { getProduct, setStock } //커리 선언하기
} = require('./database')

// product/donut 상품 정보 조회, connectDB를 통해 DB에 연결해서 진행한다.
app.get("/product/donut", connectDb, async (req, res, next) => {
  const [ result ] = await req.conn.query(
    getProduct('CP-502101') //database.js에 SQL 커리가 있다. CP-502101 Product의 정보를 불러온다.
  )

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

// 주문하는 메소드, CP-502101 상품의 재고를 주어진 설정에 맞게 감소시킨다.
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()
      const now = new Date().toString()
      const message = `부산도너츠 재고가 없습니다. 제품을 생산해주세요! \n메시지 작성 시각: ${now}`
      const params = {
      Message: message,
      Subject: '부산도너츠 재고 부족',
      MessageAttributes: {
      ProductId: {
      StringValue: product.product_id,
      DataType: "String",
      },
      FactoryId: {
      StringValue: "2d3a05ed-fdb0-11ec-a242-069c7c67cd32",
      DataType: "String",},
    },  
    TopicArn: process.env.TOPIC_ARN
    }
      const result = await sns.publish(params).promise() //SNS로 부산도너츠의 재고부족 메시지 전달.
      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;
// database.js 데이터베이스 커리 및 환경변수 선언

const mysql = require('mysql2/promise');
require('dotenv').config()

// 환경변수 설정에 따라서 접속한다.
const {
  HOSTNAME: host,
  USERNAME: user,
  PASSWORD: password,
  DATABASE: database
} = process.env;

// DB와 연결하는 역할을 하는 함수
const connectDb = async (req, res, next) => {
  try {//conn 이라는 변수는 connection 정보, pool이 conn안에 들어간다. pool은 접속된 instance를 의미하기도 한다.
    req.conn = await mysql.createConnection({ host, user, password, database })
    next()
  }
  catch(e) {
    console.log(e)
    res.status(500).json({ message: "데이터베이스 연결 오류" })
  }
}


// getStock SQL 커리
const getProduct = (sku) => `
  SELECT BIN_TO_UUID(product_id) as product_id, name, price, stock, BIN_TO_UUID(factory_id), BIN_TO_UUID(ad_id)
  FROM product
  WHERE sku = "${sku}"
`
// setStock SQL 커리
const setStock = (productId, stock) => `
  UPDATE product SET stock = ${stock} WHERE product_id = UUID_TO_BIN('${productId}')
`

module.exports = {
  connectDb,
  queries: {
    getProduct,
    setStock
  }
}

stock-lambda 생성

// serverless.yml - stock_lambda

service: stock-lambda2
frameworkVersion: '3'

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

functions:
  api:
    handler: handler.handler
    events:
      - httpApi: '*'
// handler.js - stock-_lambda

const axios = require('axios').default

exports.handler = async (event) => {
  console.log(event.Records)

  // for (const record of event.Records) {
  //   console.log("Message Body: ", record.body);
  // }


let body = JSON.parse(event.Records[0])
console.log(body)

const payload = {
        "MessageGroupId": body.messageId,
        "MessageAttributeProductId": body.ProductId,
        "MessageAttributeProductCnt": 10,
        "MessageAttributeFactoryId": body.FactoryId,
        "MessageAttributeRequester": "부산시 시장",
        "CallbackUrl": "https://24g120ch84.execute-api.ap-northeast-2.amazonaws.com/product/donut"
  }
  
  axios.post('http://factory.p3.api.codestates-devops.be:8080/api/manufactures', payload)
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

};

 

반응형

'DevOps > sprint_project' 카테고리의 다른 글

Final_project Day 5  (0) 2022.07.29
Final_project Day 4  (0) 2022.07.28
Final_project Day 3  (0) 2022.07.27
Final_project Day 2  (0) 2022.07.26
Terraform Full Stack 어플리케이션 구성 예시  (0) 2022.06.28
0 Comments
댓글쓰기 폼