달력에서 연속 15일간의 데이터만 뽑아서 보여주는 API를 만들어보자 (Ver. 2)
🤔 For What?
달력에서 연속 15일간의 데이터만 뽑아서 보여주는 API 만들기에서 구현한 API에서 몇가지 문제점을 발견해서 리팩토링을 진행했다.
🗑 원래 코드 👉🏻
public async findDailyTrend({
date,
}: FindDailyTrendRequestDto): Promise<SvcResponse> {
const requestDate = new Date(
`${date.split('.')[0]}/${Number(date.split('.')[1])}/${Number(
date.split('.')[2]
)}`
);
const today = dayjs(requestDate).format();
let after15days;
await this.db
.collection(`[COLLECTION NAME]`)
.aggregate([])
.toArray()
.then((allDailyDatasArr: any) => {
allDailyDatasArr.forEach((dailyDatas: any) => {
const date = new Date(dailyDatas.datas.timestamp).toLocaleString();
if (date === requestDate.toLocaleString()) {
after15days = dayjs(requestDate).add(15, 'day').format();
}
});
});
let resultArr = [];
await this.db
.collection(`[COLLECTION NAME]`)
.find() // 💡
.toArray()
.then((allDatas) => {
allDatas.forEach((data: any) => {
if (
today <= dayjs(new Date(data.datas.timestamp)).format() &&
after15days > dayjs(new Date(data.datas.timestamp)).format()
) {
resultArr.push(data.datas);
}
});
});
return {
data: JSON.stringify(resultArr),
statusCode: HttpStatus.OK,
};
}
🧐 문제점
☑️ 💡 를 보면, mongoDB의 db.collection.find()
메서드 자체에 조건을 주는 것이 아닌 .then()
으로 모든 데이터를 받아와서 그 안에서 if 조건문을 통해 데이터를 걸러내는 방식을 사용했다.
👉🏻 해당 방식은 현재처럼 많지 않은 데이터로만 코드를 돌릴 때는 별 문제 없이 돌아가겠지만, 추후에 development 단계나 production 단계에서는 이보다 훨씬 많은 데이터가 들어가기 때문에 클린 코드라고 말하기 힘들다.
🧐 find()
를 통해 많은 데이터를 모두 불러온 후 조건을 주는 방식이기 때문이다.
☑️ 위에 코드를 보면 중복되는 코드가 없어 보이지만, 현재 프로젝트에서 같은 방식의 daily 데이터 추출 API가 3개 필요하다. 따라서 겹치는 코드가 많다.
💡 해결책
✅ find()
에서 { }
내부에 옵션 조건을 줘서 처음부터 모든 데이터를 가져오지 않도록 구현한다.
👉🏻 조건에 맞는 데이터만 가져온다.
✅ 따로 private한 메서드를 선언해서 중복된 코드를 최소화한다.
♻️ 리팩토링한 코드
private 메서드 선언 - 중복된 코드 최소화
private async find15daysData(
date: string,
subtractedDate: string,
collectionName: string
) {
const data = await this.db
.collection(collectionName)
.find({
$expr: {
$and: [
{
$gt: [
{
$dateFromString: {
dateString: '$datas.timestamp',
},
},
new Date(subtractedDate),
],
},
{
$lt: [
{
$dateFromString: {
dateString: '$datas.timestamp',
},
},
new Date(date),
],
},
],
},
})
.toArray();
data.sort((a: any, b: any) => {
if (new Date(a.datas.timestamp) > new Date(b.datas.timestamp)) {
return 1;
} else {
return -1;
}
});
return data;
}
private convertStartDate(startDate: string): string {
let sMonth =
startDate.slice(4, 5) === '0' ? startDate.slice(5, 6) : startDate.slice(4, 6);
let sDate =
startDate.slice(6, 7) === '0' ? startDate.slice(7, 8) : startDate.slice(6, 8);
let sYear = startDate.slice(0, 4);
const date: string = `${sMonth}/${Number(sDate) + 1}/${sYear}`;
return date;
}
메인 API - 코드 효율성 up (조건에 맞는 데이터만 find)
public async findEC2DailyTrend({
date,
}: FindDailyTrendRequestDto): Promise<SvcResponse> {
const pickedDate = this.convertStartDate(date);
const before15daysDate = dayjs(pickedDate).subtract(15, 'day').format();
console.log(pickedDate, before15daysDate);
const data = await this.find15daysData(
pickedDate,
before15daysDate,
`[collection name]`
);
if (data.length < 15) {
return {
data: JSON.stringify(data),
message: 'under 15',
statusCode: HttpStatus.OK,
};
}
return {
data: JSON.stringify(data),
statusCode: HttpStatus.OK,
};
}
💡 코드 설명
find15daysData와 convertStartDate를 통해 중복된 코드를 메서드로 선언해주었다.
✔️ find15daysData는 find() 내부의 옵션을 통해 조건을 줘서 필요한 데이터만 가져온 data를 날짜의 내림차순으로 재정렬하여 리턴하는 메서드이다.
✔️ convertStartDate는 Query로 받아온 데이터를 MongoDB에서 인식할 수 있는 String 타입으로 변환하는 메서드이다.
👉🏻 find15daysData를 보면 알겠지만, 해당 String 타입은 new Date()를 통해 Date 타입으로 변환해주고, MongoDB의 String 타입으로 저장된 날짜를 Date 타입으로 변환해주는 $dateFromString을 통해 convertStartDate를 통해 변환한 날짜와 $gt, $lt를 통해 비교하여 조건에 해당하는 데이터들만 추출했다.
데이터 추출 결과
{
"data": [
{
"datas": {
"timestamp": "2/19/2022, 12:00:00 AM",
"dailyEC2Stats": [
{
"region": "ap-northeast-2",
"isAllowed": true,
"countTotalEC2": 62,
"countUnmanagedEC2": 45,
"countUnallowedSpec": 9
},
{
"region": "us-east-1",
"isAllowed": true,
"countTotalEC2": 96,
"countUnmanagedEC2": 14,
"countUnallowedSpec": 1
},
{
"region": "ap-northeast-3",
"isAllowed": true,
"countTotalEC2": 109,
"countUnmanagedEC2": 61,
"countUnallowedSpec": 6
},
{
"region": "ap-northeast-1",
"isAllowed": true,
"countTotalEC2": 111,
"countUnmanagedEC2": 98,
"countUnallowedSpec": 8
},
{
"region": "sa-east-1",
"isAllowed": true,
"countTotalEC2": 120,
"countUnmanagedEC2": 54,
"countUnallowedSpec": 0
}
]
}
},
.
.
.
{
"datas": {
"timestamp": "3/5/2022, 12:00:00 AM",
"dailyEC2Stats": [
{
"region": "ap-northeast-2",
"isAllowed": true,
"countTotalEC2": 71,
"countUnmanagedEC2": 50,
"countUnallowedSpec": 6
},
{
"region": "us-east-1",
"isAllowed": true,
"countTotalEC2": 96,
"countUnmanagedEC2": 75,
"countUnallowedSpec": 4
},
{
"region": "ap-northeast-3",
"isAllowed": true,
"countTotalEC2": 101,
"countUnmanagedEC2": 36,
"countUnallowedSpec": 8
},
{
"region": "ap-northeast-1",
"isAllowed": true,
"countTotalEC2": 110,
"countUnmanagedEC2": 83,
"countUnallowedSpec": 5
},
{
"region": "sa-east-1",
"isAllowed": true,
"countTotalEC2": 118,
"countUnmanagedEC2": 75,
"countUnallowedSpec": 2
}
]
}
}
]
}