스프린트 도중 운영 팀에서 재고가 맞지 않아요... 라는 슬랙을 받게 되었다🤯
베트남측 물류 서비스가 변경된다 라고 듣긴 했으나 베트남에서 물류관리 서비스를 계도기 없이 다른 서비스로 바로 전환하게 되어 해당 이슈가 발생하게 되었다.
전환한 서비스에서 API를 제공하기로 하였지만 너무 급작 스럽게 전환하게 되어 API도 준비 되어 있지 않았으며, 당장 재고현황을 제공해야 하는 이슈를 처리하면서 글을 작성합니다.
코어팀에서 전환한 서비스를 일정시간 마다 크롤링하여 재고 정보를 파트너센터 S3에 업로드 하고 S3트리거를 통해 업로드시 자동으로 람다함수를 호출하여 엑셀 컬럼을 검증하고 파트너센터DB 재고 테이블에 upsert하는 방식으로 구성하였다.
그리고 추가로 기존 방식이였던 재고정보가 창고별 지역별로 분리 되어있지 않아 추후에 지역별, 창고별 재고정보를 요청 했을 때 대응할수 있도록 데이터 적재 방법을 하노이,호치민 창고별 입고, 출고 내역을 따로 관리 하는 방법으로 개선하였다.
serverless 프레임워크를 이용
서버리스에서 지원하는 s3에 trigger를 걸수있도록 공식문서에서 안내 하고있다.
공식문서 : https://www.serverless.com/framework/docs/providers/aws/events/s3
//serverless handler를 작성
export const stock: Handler = async (event): Promise<void> => {
//s3트리거가 작동하면 event를 통해 업로드된 파일의 키와 버켓정보를 가져올수있다.
const bucket = event.Records[0].s3.bucket.name as string;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: bucket,
Key: key,
};
try {
//키를 통해 가져온 excel파일을 가져옵니다.
const file = await s3.getObject(params);
const fileToBuffer = await streamToBuffer(file.createReadStream());
const { stockDatas, stockLogDatas, warehouse } = await getStockSummaryExcelData(fileToBuffer, key);
if (stockDatas.length === 0 || stockLogDatas.length === 0) {
await sendAlarm(`[재고 업로드 시스템] ${process.env.NODE_ENV} ${key} STOCK 업로드할 데이터가 없습니다.`);
return;
}
//읽어온 excel파일을 가공되어 만들어진 데이터를 파트너센터 DB에 적재
await Promise.all([insert(stockLogInsertRawQuery, stockLogDatas), insert(stockInsertRawQuery, stockDatas)]);
await sendAlarm(`[재고 업로드 시스템] ${process.env.NODE_ENV} ${warehouse} STOCK 업로드 완료`);
} catch (error) {
await sendAlarm(`[재고 업로드 시스템] ${process.env.NODE_ENV} STOCK 업로드 실패 ERROR: ${error}`);
}
};
//엑셀 파일을 읽어서 DB에 업로드 할수 있도록 가공하는 로직
export const getStockSummaryExcelData = async (
file: Buffer,
key: string,
): Promise<{
stockLogDatas: [string, string, number, number, number, number, StockTypeEnum, Date, WarehouseEnum, LocationEnum][];
stockDatas: [string, string, number, WarehouseEnum, LocationEnum][];
warehouse: WarehouseEnum;
}> => {
const workbook = xlsx.read(file.buffer, {
type: 'buffer',
cellDates: true,
cellText: false,
cellHTML: false,
});
const worksheet = workbook.Sheets['Sheet'];
if (!worksheet) {
throw new Error(`[${process.env.NODE_ENV}] 엑셀 재고 데이터 sheet를 확인할 수 없습니다.`);
}
//key에서 "|" 구분자로 위치, 창고, 날짜를 구분
const [location, warehouse, date] = key.split('|');
const parseLocation = location.slice(location.indexOf('/') + 1) as LocationEnum;
const parseWarehouse = warehouse.toUpperCase().replace(/ /g, '_') as WarehouseEnum;
const orderedAt = new Date(date);
if (!Object.keys(WarehouseEnum).includes(parseWarehouse)) {
throw new Error(`[${process.env.NODE_ENV}] 존재하지 않는 창고 입니다.`);
}
const range = xlsx.utils.decode_range(worksheet['!ref'] as string);
//컬럼 검증 및 데이터를 읽어 오는 로직 (엑셀의 양식이 변경될 수 있기때문에 validation 추가)
const rows = getExcelDataWithHeaderValidationLogic(range, worksheet);
//가져온 데이터를 DB에 적재 하기위해 가공하는 로직
const { stockDatas, stockLogDatas } = transFormExcelData(rows, orderedAt, parseWarehouse, parseLocation);
return {
stockLogDatas,
stockDatas,
warehouse: parseWarehouse,
};
};
엑셀파일을 가져와서 가공하는 로직이 완료 되었고 이제 람다 함수를 생성하는 serverless.yml 파일을 작성합니다.
service: kr-partner-center-batch
frameworkVersion: '2'
plugins:
- serverless-plugin-typescript
- serverless-offline
- serverless-dotenv-plugin
useDotenv: true
provider:
---aws 환경---
functions:
# 재고 입,출고 업로드
stock:
name: kr-partner-center-batch-${opt:stage, 'dev'}
timeout: 30
handler: "./src/handlers/stock/handler.stock"
events:
#서버리스 s3 이벤트 트리거 공식문서 https://www.serverless.com/framework/docs/providers/aws/events/s3
- s3:
//partner-center 버켓에 생성된 cloudify 폴더의 .xlsx파일명으로 생성된 파일의 이벤트를 생성
bucket: partner-center
event: s3:ObjectCreated:*
rules:
- prefix: cloudify/
- suffix: .xlsx
existing: true
yml파일을 작성하고 배포하면 개발 끄읏!