[Nest] : Scheduler ๋ฅผ ์ฌ์ฉํ SoftDeleteData ์์ ์ญ์
Reference 1
Reference 2
Reference 3
Reference 4
Reference 5
Reference 6
๊ตฌํํ๊ณ ์ ํ๋ ๊ธฐ๋ฅ
nest.js/typeORM ์ softRemove ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ , ํด๋น ๋ฐ์ดํฐ์ deletedAt ์ปฌ๋ผ์ด ํ์ฌ ์๊ฐ ๊ธฐ์ค ํน์ ๊ธฐ๊ฐ์ด ์ง๋ ๋ฐ์ดํฐ๋ผ๋ฉด ์๋ฒ์์ ์๋์ผ๋ก ํด๋น ๋ฐ์ดํฐ๋ฅผ DB์์ ์๊ตฌ์ญ์ ํด์ฃผ๋ ๊ธฐ๋ฅ
ํ์ ํจํค์ง
npm install --save @nestjs/schedule
๋ค์คํธ์ ์ค์ผ์ฅด๋ฌ ํจํค์ง๊ฐ ํ์ํ๋ค.
ํ์ฌ ์ํํธ ๋ฆฌ๋ฌด๋ธ ์ํค๋ ๋ก์ง
// ์ด๋ ฅ์ - ์ญ์
async removeResume(resumeId: number): Promise<object> {
// ์ญ์ ํ ์ด๋ ฅ์ ํ์ธ
const resume = await this.resumeRepository.findOne({
where: { id: resumeId },
});
// ์์ธ์ฒ๋ฆฌ
if (!resume) {
throw new HttpException('Not found resume', HttpStatus.NOT_FOUND);
}
// SOFT_REMOVED ์ด๋ ฅ์
const deletedResume = await this.resumeRepository.softRemove(resume);
// ์์ธ์ฒ๋ฆฌ
if (!deletedResume) {
throw new HttpException('์ญ์ ์ ์คํจํ์์ต๋๋ค.', HttpStatus.BAD_REQUEST);
}
// ๋ฐํ๊ฐ
return { message: `${deletedResume.title} ์ด๋ ฅ์๊ฐ ์ญ์ ๋์์ต๋๋ค.` };
}
์์ธ์ฒ๋ฆฌ๋ฅผ ์ ์ธํ๋ค๋ฉด, ์ญ์ ํ ์ด๋ ฅ์๋ฅผ ๊ธ์ด์์ softRemove ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ null ๊ฐ์ด deletedAt ์ Date ํ์ ์ ๋ฃ์ด์ค๋ค.
import Cron
import { Cron } from '@nestjs/schedule';
๋ฐ๋ก ์ ๋ ฅ์ ํด์ฃผ์ด๋ ๋๊ณ , ์ฝ๋ ์์ ์ ํ๋ฉด์ mac ๊ธฐ์ค cmd+. ์ ๋๋ฅด๋ฉด TS ๊ฒฝ์ฐ ์๋์ผ๋ก ํด๋น ํค์๋๋ฅผ import ํด์ค๋ค.
Cron ์ฌ์ฉํ๊ธฐ
// ์ํํธ๋ฆฌ๋ฌด๋ธ ์ํจ ์ด๋ ฅ์๊ฐ ์๊ฐ์ด ์ง๋๋ฉด ์๋์ผ๋ก ์ญ์ ํ๋ ๋ก์ง
@Cron('0 0 * * *') // ํ๋ก๋์
ํ๊ฒฝ์์๋ ํด๋น ์ฝ๋
// @Cron('*/10 * * * * *') // ๊ฐ๋ฐํ๊ฒฝ์์๋ 10์ด๋ง๋ค ํด๋น ๋ฐ์ฝ๋ ์ดํฐ ์คํ์ผ๋ก ์ค์ ํด์ ์ฝ๋ ์์
ํ์์ฅฌ
async cleanupResumes() {
// Date ํ์
์ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ
const oneDayAgo = new Date();
// ๋ด์ ๋ฐ์ดํฐ์์ 1์ผ์ ๋บ ๋ฐ์ดํฐ๋ฅผ ์ธํ
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
// 1์ผ ์ด์ ์ ์ํํธ๋ฆฌ๋ฌด๋ธ ์ํจ ์ด๋ ฅ์ ์ญ์
const cleanupTarget = await this.resumeRepository.find({
withDeleted: true,
});
// ์์ ์ญ์ ์ํฌ cleanupTarget list์์ ํ๋ํ๋๋ฅผ DB์์ ์ญ์ ํด์ค๋ค.
for (const resume of cleanupTarget) {
// ๋ฝ์์จ ๋ฐ์ดํฐ์์ ์ญ์ ํ ๋ฐ์ดํฐ๋ค ์กฐ๊ฑด ๋ง๋ค๊ธฐ
if (resume.deletedAt <= oneDayAgo && resume.deletedAt !== null) {
// db์์ ์ญ์ ==> delete ์ฌ์ฉ์ ์ญ์ ์๋จ !!
await this.resumeRepository.remove(resume);
}
}
}
์ฒ์ ์ฌ์ฉํด๋ณด๋ ๋ก์ง์ด๋ผ์ ํ๋ํ๋ ์ฃผ์์ ๋ฌ์๋ค.
์ฝ๋ ํด์
Cron์ ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํด์ ๊ฐ์ ธ์ค๊ณ ์ต์ ์ ์ก์์ค๋ค.
์ด 6๊ฐ์ ์ต์ ์๋ฆฌ๋ฅผ ์ค์ ํ ์ ์๊ณ , ์ผ์ชฝ๋ถํฐ "์ด ๋ถ ์ ์ผ ์ ์์ผ" ์ด๋ผ๊ณ ์๊ณ ์๋ค.
2๋ฒ ์ฝ๋์ ์ฒซ๋ฒ์งธ ์ต์ ์๋ฆฌ์ */10์ "์ด"์๋ฆฌ์ ๋ค์ด๊ฐ๋ ์ต์ ์ด๊ณ 10์ด ๊ฐ๊ฒฉ์ผ๋ก ๋ฐ์ฝ๋ ์ดํฐ ์ดํ์ ๋ก์ง์ด ์คํ๋๋ค๋ ๋ป์ด๋ค.
@Cron('0 0 * * *') // ํ๋ก๋์
ํ๊ฒฝ์์๋ ํด๋น ์ฝ๋
// @Cron('*/10 * * * * *') // ๊ฐ๋ฐํ๊ฒฝ์์๋ 10์ด๋ง๋ค ํด๋น ๋ฐ์ฝ๋ ์ดํฐ ์คํ์ผ๋ก ์ค์ ํด์ ์ฝ๋ ์์
ํ์์ฅฌ
์ด ์ฝ๋๋ ํ์ฌ์ ์๊ฐ์ ๋ด์ ์๋ณ์์ธ oneDayAgo์ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ setํด์ฃผ๋ ๊ณผ์ ์ด๊ณ setํ๋ ๋ฐ์ดํฐ๋ ํ์ฌ ๋ ์ง๋ฐ์ดํฐ์์ 1์ผ์ ๋นผ์ฃผ๋ ๋ก์ง์ด๋ค.
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
๊ฒฐ๊ณผ์ ์ผ๋ก ํ์ฌ ๋ ์ง๋ณด๋ค 1์ผ์ด ์ง๋ ๋ ์ง๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง oneDayAgo๋ฅผ ๊ฐ์ง๊ณ DB์์ softRemove๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ํ๋ ๋ฐ๋ณตํ๋ฉด์ deletedAt ๊ฐ์ด oneDayAgo ๋ณด๋ค ์ด์ ์ ๋ ์ง๋ผ๋ฉด ์ญ์ ๋ฅผ ํ๋ ๋ก์ง์ ๊ตฌํํ๋ค.
for (const resume of cleanupTarget) {
// ๋ฝ์์จ ๋ฐ์ดํฐ์์ ์ญ์ ํ ๋ฐ์ดํฐ๋ค ์กฐ๊ฑด ๋ง๋ค๊ธฐ
if (resume.deletedAt <= oneDayAgo && resume.deletedAt !== null) {
// db์์ ์ญ์ ==> delete ์ฌ์ฉ์ ์ญ์ ์๋จ !!
await this.resumeRepository.remove(resume);
}
}
๊ฐ์ฅ ์ ๋จน์ ๋ถ๋ถ
๋ฐ๋ก ์ด ๋ถ๋ถ์ด๋ค.
์ด์ ๋ deletedAt์ null์ด ์๋ Dateํ์ ์ด ๋ด๊ฒจ์๋ ์ ๋ค์ ์ด๋ฏธ ์ญ์ ๊ฐ ๋์๋ค๋ ๋ช ๋ชฉ์๋ ์๋ ์น๊ตฌ๋ค์ด๊ณ , ๋ฐ๋ผ์ ๊ธฐ์กด์ ๋ฐฉ์๋๋ก DB์์ ์ด ์น๊ตฌ๋ค์ ์กฐํํ๋ ค๊ณ ํ๋ฉด ์กฐํ๊ฐ ๋์ง ์๋๋ค.
๋๋ new Date() ์์ฑ์๋ฅผ ์ฌ์ฉํด์ ํ ๋นํ Date๊ฐ(= ISO 8601) ๊ณผ ํ์ฌ deletedAt์ ๋ค์ด์๋ ๋ฐ์ดํฐ ํ์ (GMT)์ด ๋ฌ๋ผ์ ์กฐํ๊ฐ ์๋๋ ์ค ์์๋ค.
๊ทธ๋์ ์ด ๊ธ ์ ์ผ ์์ค์ ์ฐธ๊ณ ๋ฌธ์์ ์ ๋ฐ ์ด์์ด Date ํ์ ๋ณํ์ํค๋ ๋ฌธ์์ธ ์ด์ ๊ฐ ๊ทธ ๋๋ฌธ์ด๋ค.
๊ทธ๋์ LessThan / Not(IsNull()) ์๋ฌด๋ฆฌ ์ด๋ฐ ๋ฉ์๋๋ฅผ ๊ฐ๋ค ๋ถํ๋ ์กฐํ๊ฐ ์๋๋ ์ค ์์์ผ๋,
์ ๋ฉ์๋๋ฅผ createdAt์ ์ ์ฉ์ ํด์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํด๋ณด๋ ํ์ฌ ์ด์์๋ ์ด๋ ฅ์๋ ์กฐํ๊ฐ ๋๋ ๊ฒ์ด๋ค.
const cleanupTarget = await this.resumeRepository.find({
withDeleted: true,
});
๊ทธ๋์ ์ด๋ฏธ ํ ๋ฒ softRemove๊ฐ ๋ ๋ ์๋ค์ ์กฐํํ๋ ค๋ฉด ๋ค๋ฅธ ํค์๋๊ฐ ํ์ํ๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ , ํด๋น ์ต์ ์ ์ฐพ์๋ด๋ฉด์ ์ ์์ ์ผ๋ก ๋ด๊ฐ ์๊ฐํ๋ ๋๋ก ๋ก์ง์ ๊ตฌํํ ์ ์์๋ค.
๋ง์ง๋ง ์์ฑ ์ฝ๋ ์ต์์ ๋ชจ๋ ์ ์ฉํ๊ธฐ
// app.module.ts
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [
ScheduleModule.forRoot(), // ์ ๋
์์ด ๊ผญ ํ์
...
],
controllers: [AppController],
providers: [AppService, ChatGateway],
})
export class AppModule {}
์ด๋ ๊ฒ ๋๋ฉด ์ ์์ ์ผ๋ก ์ค์ผ์ฅด๋ฌ๊ฐ ์๋๋ ๊ฒ์ด๋ค.
#nest #TS #typeORM #schedule #Cron #๋ฐ์ฝ๋ ์ดํฐ