안녕하세요! 친구 웨딩 축하 이벤트 웹사이트에서 메시지 시스템의 내부 동작 원리에 대해 자세히 알아보겠습니다. 사용자가 신랑신부에게 보내는 축하 메시지가 어떻게 처리되고 저장되는지, 그리고 나중에 어떻게 조회되는지 전체 흐름을 살펴보겠습니다.
시스템 아키텍처 개요
우리 웨딩 앱은 다음과 같은 기술 스택을 사용합니다:
- 프론트엔드: Vue.js, Vuetify
- 백엔드: Node.js, Express
- 데이터베이스: MongoDB
- 이미지 저장소: Cloudinary
- 메시지 큐: AWS SQS (Simple Queue Service)
1. 사용자 경험: 편지 작성 및 제출
사용자가 웹사이트에 접속하면 다음과 같은 편지 작성 폼을 볼 수 있습니다:
이미지 표시
사용자는 편지 제목, 작성자 이름, 테마 선택, 이미지 첨부(선택 사항), 그리고 편지 내용을 입력합니다. 모든 필수 필드를 작성한 후 "편지 보내기" 버튼을 클릭하면 다음과 같은 과정이 시작됩니다:
async submitLetter() {
if (!this.$refs.form.validate()) return;
const confirmed = confirm('편지를 보내시겠습니까?\n\n※ 한 번 보낸 편지는 삭제할 수 없습니다.');
if (!confirmed) return;
this.isLoading = true;
// FormData 생성
const formData = new FormData();
formData.append('title', this.letter.title);
formData.append('writer', this.letter.writer);
formData.append('theme', this.letter.theme);
formData.append('content', this.letter.content);
// 이미지가 있으면 추가
if (this.letter.image) {
formData.append('image', this.letter.image);
}
// API 요청
const response = await fetch('https://wedding-junghwan.onrender.com/api/letters', {
method: 'POST',
body: formData
});
// 결과 처리...
}
2. API 요청 처리: 서버 측 로직
사용자가 편지 폼을 제출하면, 백엔드 서버의 /api/letters 엔드포인트가 요청을 받습니다. 이 요청은 letterRoutes.js 파일의 POST 라우트 핸들러에서 처리됩니다.
2.1 이미지 업로드 처리
먼저, 요청에 이미지가 포함되어 있다면 Multer와 Cloudinary를 통해 이미지가 처리됩니다. upload.single('image') 미들웨어가 이 작업을 담당합니다
// 이미지 정보 추출
const imageData = req.file ? {
imageUrl: req.file.path,
imagePublicId: req.file.filename
} : { imageUrl: null, imagePublicId: null };
Cloudinary에 이미지가 업로드되면, 해당 이미지의 URL과 공개 ID가 반환됩니다. 이 정보는 나중에 메시지와 함께 저장됩니다.
2.2 SQS 메시지 큐에 메시지 전송
이미지 처리가 완료되면, 편지 데이터는 AWS SQS 메시지 큐로 전송됩니다:
// 메시지 큐에 메시지 전송
await sqs.sendMessage({
QueueUrl: process.env.SQS_QUEUE_URL,
MessageBody: JSON.stringify({
title: req.body.title,
writer: req.body.writer,
content: req.body.content,
theme: req.body.theme || 'default',
...imageData,
createdAt: new Date(),
isPublic: true
})
}).promise();
메시지 큐를 사용하는 이유는 다음과 같습니다:
- 비동기 처리: 사용자에게 빠르게 응답을 반환하고, 데이터 저장은 백그라운드에서 처리
- 부하 분산: 트래픽이 많을 때 시스템 부하 분산
- 내구성: 메시지는 처리될 때까지 큐에 안전하게 보관됨
- 확장성: 필요에 따라 여러 소비자를 추가하여 처리 속도 향상 가능
2.3 사용자에게 성공 응답 반환
메시지가 큐에 성공적으로 전송되면, 서버는 사용자에게 성공 응답을 반환합니다:
res.status(201).json({
success: true,
message: '편지가 성공적으로 전송되었습니다!',
});
이때 사용자는 편지가 성공적으로 전송되었다는 메시지를 보게 됩니다:
중요한 점은 이 시점에서 편지가 아직 데이터베이스에 저장되지 않았다는 것입니다. 편지 데이터는 메시지 큐에 있으며, 백그라운드에서 처리될 예정입니다.
3. 메시지 소비자 서비스: 큐에서 메시지 처리
서버가 시작되면 letterConsumer.js 모듈의 startConsumer() 함수가 호출되어 메시지 소비자 서비스가 시작됩니다:
// index.js
app.listen(PORT, () => {
console.log(`서버가 ${PORT} 포트에서 실행 중입니다.`);
// Letter 소비자 서비스 시작
try {
const letterConsumer = require('./services/letterConsumer');
letterConsumer.startConsumer();
console.log('Letter 소비자 서비스가 시작되었습니다.');
} catch (error) {
console.error('Letter 소비자 서비스 시작 실패 : ', error);
}
});
메시지 소비자 서비스는 지속적으로 SQS 큐를 폴링하여 새 메시지를 확인합니다:
async function pollMessages() {
try {
const params = {
QueueUrl: process.env.SQS_QUEUE_URL,
MaxNumberOfMessages: 10,
WaitTimeSeconds: 20
};
const data = await sqs.receiveMessage(params).promise();
if (data.Messages && data.Messages.length > 0) {
console.log(`${data.Messages.length}개의 메시지를 수신했습니다.`);
for (const message of data.Messages) {
await processMessage(message);
}
}
} catch (error) {
console.error('메시지 풀링 오류 : ', error);
} finally {
// 연속적인 풀링을 위해 재귀 호출
setTimeout(pollMessages, 1000)
}
}
4. 데이터베이스 저장: MongoDB에 편지 저장
메시지 소비자가 큐에서 메시지를 받으면, processMessage() 함수가 호출되어 편지 데이터를 MongoDB에 저장합니다:
async function processMessage(message) {
try {
const messageBody = JSON.parse(message.Body);
// MongoDB에 저장
const newLetter = new Letter({
title: messageBody.title,
writer: messageBody.writer,
content: messageBody.content,
theme: messageBody.theme,
imageUrl: messageBody.imageUrl,
imagePublicId: messageBody.imagePublicId,
createdAt: messageBody.createdAt,
isPublic: messageBody.isPublic
});
await newLetter.save();
console.log(`편지 저장 완료: ${newLetter._id}`);
// 메시지 삭제 (처리 완료)
await sqs.deleteMessage({
QueueUrl: process.env.SQS_QUEUE_URL,
ReceiptHandle: message.ReceiptHandle
}).promise();
return true;
} catch (error) {
console.error('메시지 처리 오류:', error);
return false;
}
}
편지가 성공적으로 저장되면, 해당 메시지는 큐에서 삭제됩니다. 이로써 중복 처리를 방지합니다.
5. 편지 조회: 저장된 편지 불러오기
사용자가 저장된 편지를 조회하려면, 프론트엔드에서 /api/letters 엔드포인트로 GET 요청을 보냅니다. 이 요청은 letterRoutes.js 파일의 GET 라우트 핸들러에서 처리됩니다:
router.get('/', async (req, res) => {
try {
const { page = 1, limit = 300, theme, search } = req.query;
const query = { isPublic: true };
if (theme && theme !== 'all') {
query.theme = theme;
}
if (search) {
query.$text = { $search: search };
}
const letters = await Letter.find(query)
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await Letter.countDocuments(query);
res.json({
success: true,
letters,
pagination: {
total,
page: parseInt(page),
pages: Math.ceil(total / limit)
}
});
} catch (error) {
console.error('Letter fetch error : ', error);
res.status(500).json({
success: false,
message: '편지 조회 중 오류가 발생했습니다.',
error: error.message
});
}
});
이 핸들러는 MongoDB에서 저장된 편지를 조회하고, 페이지네이션, 테마 필터링, 검색 기능을 지원합니다.
조회된 편지는 프론트엔드에서 다음과 같이 표시됩니다:

6. 전체 프로세스 요약
전체 편지 저장 및 조회 프로세스를 요약하면 다음과 같습니다:
- 사용자 입력: 사용자가 편지 폼을 작성하고 제출
- 이미지 업로드: 이미지가 Cloudinary에 업로드됨 (있는 경우)
- 메시지 큐 전송: 편지 데이터가 AWS SQS 큐로 전송됨
- 성공 응답: 사용자에게 성공 메시지 표시
- 메시지 소비: 백그라운드에서 메시지 소비자가 큐에서 메시지를 가져옴
- 데이터베이스 저장: 메시지 소비자가 편지 데이터를 MongoDB에 저장
- 편지 조회: 저장된 편지를 API를 통해 조회하고 표시
결론
이 아키텍처는 다음과 같은 이점을 제공합니다:
- 높은 확장성: 트래픽이 많아도 시스템이 안정적으로 작동
- 빠른 응답 시간: 사용자는 즉시 응답을 받을 수 있음
- 내결함성: 시스템 일부가 실패해도 데이터 손실 없음
- 분리된 책임: 각 구성 요소가 자신의 역할에만 집중
이러한 접근 방식은 특히 결혼식과 같은 특별한 날에 트래픽이 급증할 수 있는 이벤트 기반 애플리케이션에 적합합니다. AWS SQS와 같은 메시지 큐 서비스를 활용하면 시스템 안정성을 높이고 사용자 경험을 향상시킬 수 있습니다.
'MyStory > Deployment and management' 카테고리의 다른 글
| 레거시 코드 리팩토링기: 매장 정보 관리 시스템 개선 사례 (0) | 2025.06.09 |
|---|---|
| iOS 앱 푸시 알림 설정하기: 오류 해결부터 Firebase 연동까지 (0) | 2025.04.21 |
| 안전한 인증 시스템 구현하기: JWT, CSRF 보호 및 보안 구현 (0) | 2025.04.11 |
| GitLab CI/CD 파이프라인을 활용한 GUARDIANSVIEW 프로젝트 배포 (0) | 2025.04.09 |