이번 글에서는 Firebase Functions 환경에서 사용하던 Storage Trigger 기반 워크플로우를 Cloudflare Workers + R2 + Inngest 조합으로 마이그레이션한 과정을 공유한다.
특히 Inngest의 이벤트 기반 워크플로우 시스템을 활용해, Cloudflare R2에 파일이 업로드되었을 때 후처리 작업을 자동으로 실행하는 구조를 구현하였다.
Inngest
: 이벤트 기반 서버리스 워크플로우 플랫폼
간단히 설명하자면, 트리거가 발생했을때 특정 로직을 실행시켜주는 도구이다.
주요 기능은 아래와 같다.
file.uploaded 이벤트 전송/api/inngest에 정의된 워크플로우가 실행됨/storage/post-upload) 호출/app/api/inngest/client.tsimport { Inngest } from "inngest";
export const inngest = new Inngest({
id: "wedeo-inngest",
baseUrl: process.env.INNGEST_BASE_URL,
eventKey: process.env.INNGEST_EVENT_KEY,
});
/app/api/inngest/route.tsimport { serve } from "inngest/next";
import { inngest } from "./client";
import { onFileInBucketCreate } from "./workflow";
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [onFileInBucketCreate],
});
/app/api/inngest/workflow.tsimport { inngest } from "./client";
export const onFileInBucketCreate = inngest.createFunction(
{ id: "on-file-in-bucket-create" },
{ event: "file.uploaded" },
async ({ event, step }) => {
const { userId, fileId } = event.data;
await step.run("Call worker's post-upload handler", async () => {
const res = await fetch("http://localhost:8787/storage/post-upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId, fileId }),
});
if (!res.ok) {
throw new Error(`Worker 처리 실패: ${await res.text()}`);
}
});
return { status: "ok" };
}
);
uploadFileToStorageexport const uploadFileToStorage = async (
bucket: R2Bucket,
inngestBaseUrl: string,
inngestEventKey: string,
filePath: string,
userId: string,
fileId: string,
file: Buffer,
) => {
await bucket.put(filePath, file);
const response = await sendInngestEvent(inngestBaseUrl, inngestEventKey, "file.uploaded", {
userId,
fileId,
});
return filePath;
};
sendInngestEventexport const sendInngestEvent = async (
inngestBaseUrl: string,
inngestEventKey: string,
name: string,
data: any
) => {
return await fetch(`${inngestBaseUrl}/e/${inngestEventKey}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, data }),
});
};