1) 각각의 서비스는 자신의 database를 소유(필요 시)
2) 서비스는 다른 서비스의 데이터베이스에 접근 불가
3) 마이크로 서비스 data 관리 시 문제점
장점
단점
장점
단점
// POST Service
const express = require('express');
const bodyParser = require('body-parser');
const { randomBytes } = require('crypto');
const cors = require('cors');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const posts = {};
app.get('/posts', (req, res) => {
res.send(posts);
});
app.post('/posts', (req, res) => {
const id = randomBytes(4).toString('hex');
const { title } = req.body;
posts[id] = {
id,
title
};
res.status(201).send(posts[id]);
});
app.listen(4000, () => {
console.log('Listening on 4000');
});
// Comment Service
const express = require('express');
const bodyParser = require('body-parser');
const { randomBytes } = require('crypto');
const cors = require('cors');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const commentsByPostId = {};
app.get('/posts/:id/comments', (req, res) => {
res.send(commentsByPostId[req.params.id] || []);
});
app.post('/posts/:id/comments', (req, res) => {
const commentId = randomBytes(4).toString('hex');
const { content } = req.body;
const comments = commentsByPostId[req.params.id] || [];
comments.push({ id: commentId, content });
commentsByPostId[req.params.id] = comments;
res.status(201).send(comments);
});
app.listen(4001, () => {
console.log('Listening on 4001');
});
생각해보기
문제점
// POST SERVICE
const express = require("express");
const bodyParser = require("body-parser");
const { randomBytes } = require("crypto");
const cors = require("cors");
const axios = require("axios");
const app = express();
app.use(bodyParser.json());
app.use(cors());
const posts = {};
app.get("/posts", (req, res) => {
res.send(posts);
});
app.post("/posts", async (req, res) => {
const id = randomBytes(4).toString("hex");
const { title } = req.body;
posts[id] = {
id,
title,
};
// POST 생성 후, 이벤트 버스에 이벤트 전달
await axios.post("http://localhost:4005/events", {
type: "PostCreated",
data: {
id,
title,
},
});
res.status(201).send(posts[id]);
});
app.post("/events", (req, res) => {
console.log("Received Event", req.body.type);
res.send({});
});
app.listen(4000, () => {
console.log("Listening on 4000");
});
// Comment Service
const express = require("express");
const bodyParser = require("body-parser");
const { randomBytes } = require("crypto");
const cors = require("cors");
const axios = require("axios");
const app = express();
app.use(bodyParser.json());
app.use(cors());
const commentsByPostId = {};
app.get("/posts/:id/comments", (req, res) => {
res.send(commentsByPostId[req.params.id] || []);
});
app.post("/posts/:id/comments", async (req, res) => {
const commentId = randomBytes(4).toString("hex");
const { content } = req.body;
const comments = commentsByPostId[req.params.id] || [];
comments.push({ id: commentId, content });
commentsByPostId[req.params.id] = comments;
// Comment 생성 후, 이벤트 버스에 이벤트 전달
await axios.post("http://localhost:4005/events", {
type: "CommentCreated",
data: {
id: commentId,
content,
postId: req.params.id,
},
});
res.status(201).send(comments);
});
app.post("/events", (req, res) => {
console.log("Event Received", req.body.type);
res.send({});
});
app.listen(4001, () => {
console.log("Listening on 4001");
});
// Event Bus
const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const app = express();
app.use(bodyParser.json());
// Event 수신 후, 각 Service에 이벤트 전달
app.post("/events", (req, res) => {
const event = req.body;
axios.post("http://localhost:4000/events", event).catch((err) => {
console.log(err.message);
});
axios.post("http://localhost:4001/events", event).catch((err) => {
console.log(err.message);
});
axios.post("http://localhost:4002/events", event).catch((err) => {
console.log(err.message);
});
res.send({ status: "OK" });
});
app.listen(4005, () => {
console.log("Listening on 4005");
});
// Query Service
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const posts = {};
app.get('/posts', (req, res) => {
res.send(posts);
});
app.post('/events', (req, res) => {
const { type, data } = req.body;
// Event 타입에 따라 Post 생성 & Comment 생성 후 클라이언트에게 조합된 정보 전달
if (type === 'PostCreated') {
const { id, title } = data;
posts[id] = { id, title, comments: [] };
}
if (type === 'CommentCreated') {
const { id, content, postId } = data;
const post = posts[postId];
post.comments.push({ id, content });
}
console.log(posts);
res.send({});
});
app.listen(4002, () => {
console.log('Listening on 4002');
});
Comment의 유형에 따라 승인 & 거부 로직을 구축 시, Moderate 서비스는 Comment content 유형에 따른 비즈니스 로직을 실행하고 업데이트된 comment를 이벤트 스토어에 이벤트에 전달 한 후 Comment & Query 서비스에서 이를 수신하여 처리할 수 있다.
하지만, 위와 같이 중재 서비스가 제공하는 타입이 많아질 수록 이벤트 수신 서비스(Comment & Query)는 위 타입에 따른 모든 로직을 구성 해야한다.
이를 해결하기 위해,
결론 : 중재서비스의 이벤트 타입이 많아져도 Comment에서만 이를 처리하고 Query 서비스는 CommentUpdated 이벤트의 업데이트 된 Comment 내역만 확인하면 된다.
클라이언트에게 Post & Comment 정보를 전달하는 Query 서비스가 일시적으로 다운 시, 그동안 요청한 고객의 Post & Comment 정보를 받아오기 위해서 Event Bus 내 Event Bus Store를 만들고, 재가동될때 해당 Event Bus Store에서 그동안의 Post & Comment 요청을 받아 올 수 있다.
// Post Service
const express = require('express');
const bodyParser = require('body-parser');
const { randomBytes } = require('crypto');
const cors = require('cors');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const posts = {};
app.get('/posts', (req, res) => {
res.send(posts);
});
app.post('/posts', async(req, res) => {
const id = randomBytes(4).toString('hex');
const { title } = req.body;
posts[id] = {
id,
title
};
await axios.post('http://localhost:4005/events', {
type: 'PostCreated',
data: {
id, title
}
});
res.status(201).send(posts[id]);
});
app.post('/events', (req, res) => {
console.log('포스트 서비스 이벤트 수신', req.body.type);
res.send({});
});
app.listen(4000, () => {
console.log('Listening on 4000');
});
// Comment Service
const express = require('express');
const bodyParser = require('body-parser');
const { randomBytes } = require('crypto');
const cors = require('cors');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const commentsByPostId = {};
app.get('/posts/:id/comments', (req, res) => {
res.send(commentsByPostId[req.params.id] || []);
});
app.post('/posts/:id/comments', async(req, res) => {
const commentId = randomBytes(4).toString('hex');
const { content } = req.body;
const comments = commentsByPostId[req.params.id] || [];
comments.push({ id: commentId, content, status: 'pending' });
commentsByPostId[req.params.id] = comments;
await axios.post('http://localhost:4005/events', {
type: 'CommentCreated',
data: {
id: commentId,
content,
postId: req.params.id,
status: 'pending'
}
});
res.status(201).send(comments);
});
// CommentModerated 수신 후 이벤트 버스에 CommentUpdated 전송
app.post('/events', async(req, res) => {
console.log('코멘트 서비스 이벤트 수신', req.body.type);
const { type, data } = req.body;
if (type === 'CommentModerated') {
const { id, status, postId, content } = data;
const comments = commentsByPostId[postId];
const comment = comments.find(comment => {
return comment.id === id;
});
comment.status = status;
await axios.post('http://localhost:4005/events', {
type: 'CommentUpdated',
data: {
id,
postId,
status,
content
}
});
}
res.send({});
});
app.listen(4001, () => {
console.log('Listening on 4001');
});
// Moderation Service
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.post('/events', async(req, res) => {
const { type, data } = req.body;
if (type === 'CommentCreated') {
const status = data.content.includes('orange') ? 'rejected' : 'approved';
// CommentCreated 받고 조정 후, CommentModerated를
// 이벤트 버스에 재전송
await axios.post('http://localhost:4005/events', {
type: 'CommentModerated',
data: {
id: data.id,
postId: data.postId,
status,
content: data.content
}
})
}
res.send({});
});
app.listen(4003, () => {
console.log('Listening on 4003');
});
// Event-Bus
const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const app = express();
app.use(bodyParser.json());
// 이벤트 버스 스토어 생성
const events = [];
app.post("/events", (req, res) => {
const event = req.body;
events.push(event);
axios.post("http://localhost:4000/events", event).catch((err) => {
console.log(err.message);
});
axios.post("http://localhost:4001/events", event).catch((err) => {
console.log(err.message);
});
axios.post("http://localhost:4002/events", event).catch((err) => {
console.log(err.message);
});
axios.post("http://localhost:4003/events", event).catch((err) => {
console.log(err.message);
});
res.send({ status: "OK" });
});
app.get('/events', (req, res) => {
res.send(events);
});
app.listen(4005, () => {
console.log("Listening on 4005");
});
// Query Service
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const axios = require('axios');
const app = express();
app.use(bodyParser.json());
app.use(cors());
const posts = {};
const handleEvent = (type, data) => {
if(type === 'PostCreated') {
const { id, title } = data;
posts[id] = { id, title, comments: [] };
}
if(type === 'CommentCreated') {
const { id, content, postId, status } = data;
const post = posts[postId];
post.comments.push({ id, content, status });
}
// 코멘트 상태 업데이트 수신 후 상태 업데이트 진행
if(type === 'CommentUpdated') {
const { id, postId, status, content } = data;
const post = posts[postId];
// console.log('post---', post)
const comment = post.comments.find(comment => {
return comment.id === id;
});
comment.status = status;
comment.content = content;
}
}
app.get('/posts', (req, res) => {
res.send(posts);
});
app.post('/events', (req, res) => {
const { type, data } = req.body;
handleEvent(type, data);
res.send({});
});
// 서비스 다운 후 재실행 시 event 받아오기
app.listen(4002, async () => {
console.log("Listening on 4002");
try {
const res = await axios.get("http://localhost:4005/events");
for (let event of res.data) {
console.log("Processing event:", event.type);
handleEvent(event.type, event.data);
}
} catch (error) {
console.log(error.message);
}
});
참고 : Microservices with Node JS(Stephen Grider)