Thực hành tạo ra mạng xã hội Facebook với Microservices cùng hàng trịu user ☠
Ờm mình chém đấy, làm quái gì có chuyện mới đọc xong một bài viết của mình mà nhảy sang làm cả một hệ thống to đùng như vậy được 🤣🤣
Moshi Moshi, xin chào anh em, lại mình là NekoArcoder đây ạ!
Sau bài đầu tiên mình cũng đã nhận được kha khá sự chú ý của các bạn nên mình quyết định sẽ đến với bài tiếp theo trong chuỗi series "Bạn mù tịt về Microservices, tôi cũng thế" (Flop là series chỉ có duy nhất 1 tập hehe)
Ở bài này chúng ta sẽ thực hành để triển khai những lý thuyết chúng ta đã học, nhưng sẽ thay đổi ví dụ một chút cho dễ và đơn giản hơn.
Nào nào không lòng vòng như Hải Phòng nữa, chúng ta đến với bài tiếp theo ngay thôi nhé!
I. Tổng quan về dự án
Dự án lần này chỉ là một dự án nhỏ để giúp bạn thực hành những lý thuyết của bài trước, chúng ta sẽ cố gắng thực hiện thủ công hết mọi thứ để thực sự hiểu rõ chuyện gì đang diễn ra bên trong một Microservices nhé.
1. Yêu cầu của dự án:
Năm nay là năm 2004 và Mark Zuckerberg đang muốn tạo một dự án mạng xã hội dùng để tạo Post và Comment trên post đó.
Rất đơn giản phải không, để dễ hình dung, mình sẽ minh họa về giao diện dự án nhé:

2. Các tính năng của dự án
- Tạo bài đăng (Post).
- Hiển thị danh sách bài đăng.
- Tạo bình luận (Comment) trên bài đăng.
- Hiện thị danh sách bình luận trên bài đăng.
3. Chọn chiến lược
🐓Synchronous Communication
Ở chiến lược này chúng ta sẽ chia thành 2 service đó là Post và Comment.
Thiết kế bảng dữ liệu:
- Bảng Post gồm 2 cột: id và title.
- Bảng Comment gồm 3 cột: ID, content và postId.
Ý tưởng: Chúng ta sẽ gọi đến Post service sau đó để cho Post serivce cầm ID qua bên Comment Service để lấy những comment thuộc bài viết đó. (Service này chờ kết quả của Service khác).

🦅 Asynchronous Communication
Ở chiến lược này chúng ta sẽ chia thành 3 serivice đó là Post và Comment, Query và một Event Bus.
Thiết kế bảng dữ liệu:
- Bảng Post ở Post service gồm 2 cột: id và title.
- Bảng Comment ở Comment service gồm 3 cột: ID, content và postId.
- Bảng ở Service Query sẽ bao gồm các cột: ID, title và content.
Ý tưởng:
Query service sẽ đảm nhiệm việc lưu thông tin sự kiện khi có một Post mới được tạo.

Sau đó một Comment được thêm vào.

Từ đó khi người dùng muốn hiển thị các bài viết kèm bình luận thì chỉ cần gọi đến Query Service.
4. Lựa chọn công nghệ
Giả sử team của bạn một nửa là thành thạo Python, một nửa là thành thạo Javascript/Typescript và cả 2 bên đều biết đến Go, và khách hàng cho bạn lựa chọn thoải mái công nghệ.
Để thể hiện sự độc lập trong công nghệ giống với thực tế thì ở mỗi feature thì mình sẽ chọn một framwork, cụ thể như sau: Gin cho Post service, FastAPI cho Comment service và Express cho Query service (Khi chọn chiến lược Async) .
Trong thực tế bạn phải lựa chọn theo yêu cầu đặc thù của từng service, ví dụ User service mục tiêu chỉ cần CRUD user, preferences, roles chỉ cần ít CPU thì ta dùng Node.js để dev nhanh. Product service mục tiêu là hiển thị sản phẩm, filter, phân trang với nhu cầu nhiều đọc, ít ghi, scale tốt thì ta chọn Go.
Mình sẽ không dùng database để quản lý dữ liệu, mà thay vào đó lưu trực tiếp thẳng vào Memory để cho tiện ví dụ và bớt lằng nhằng.
II. Triển khai dự án theo hướng Sync Comunication
Mình sẽ bỏ qua bước cài đặt các thứ, các bạn có thể tự tìm hiểu trên mạng hoặc clone dự án của mình ở nhánh Main ở đây nha: https://gitlab.com/neko.arcoder/facebook
1. Comment service
Chúng ta sẽ khởi tạo dự án FastAPI chạy ở port 8081 với 2 endpoint ở service này đó là GET và POST với path là "/comments/post/{post_id}" dùng để lấy danh sách comment theo postId và tạo comment của Post.
Lưu ý: Nhớ thêm CORS middleware để tránh bị lỗi CORS nhé
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
app = FastAPI(title="Comment Service", version="1.0.0")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Data models
class Comment(BaseModel):
id: int
content: str
postId: int
class CommentCreate(BaseModel):
content: str
comments_db = []
next_comment_id = 1
@app.get("/")
async def root():
return {"message": "Comment Service is running"}
@app.get("/comments/post/{post_id}")
async def get_comments_by_post_id(post_id: int):
"""Lấy comments theo post ID"""
post_comments = [comment for comment in comments_db if comment["postId"] == post_id]
return post_comments
@app.post("/comments/post/{post_id}")
async def create_comment(post_id: int, comment_data: CommentCreate):
"""Tạo comment mới cho post"""
global next_comment_id
new_comment = {
"id": next_comment_id,
"content": comment_data.content,
"postId": post_id
}
comments_db.append(new_comment)
next_comment_id += 1
return new_comment
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8081)
2. Post service
Ở đây ta sẽ tạo một dự án Gin framework lắng nghe ở port 8080 với endpoint đó là GET: /posts với query parameter là comments, nếu comments=true thì chúng ta lấy danh sách các Post kèm Comment của nó, nếu là false thì ta chỉ cần trả danh sách Post thôi.
Một endpoint lữa là POST: /posts là dùng để tạo Post mới nhé!
Lưu ý: Nhớ thêm CORS middleware để tránh bị lỗi CORS nhé
package main
import (
"encoding/json"
"io"
"net/http"
"strconv"
"sync"
"github.com/gin-gonic/gin"
)
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Comments []Comment `json:"comments,omitempty"`
}
type Comment struct {
ID int `json:"id"`
Content string `json:"content"`
PostID int `json:"postId"`
}
var (
posts = []Post{}
nextID = 1
postMux sync.Mutex
)
func main() {
r := gin.Default()
// CORS middleware
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
// Post endpoints
r.GET("/posts", getPosts)
r.POST("/posts", createPost)
r.Run(":8080")
}
// GET /posts
func getPosts(c *gin.Context) {
postMux.Lock()
defer postMux.Unlock()
// Kiểm tra query parameter comments
includeComments := c.Query("comments") == "true"
if includeComments {
postsWithComments := make([]Post, len(posts))
for i, post := range posts {
postsWithComments[i] = Post{
ID: post.ID,
Title: post.Title,
}
// Gọi đến Comment service để lấy comments
comments, err := fetchCommentsFromCommentService(post.ID)
if err != nil {
// Nếu không lấy được comments, để trống
postsWithComments[i].Comments = []Comment{}
} else {
postsWithComments[i].Comments = comments
}
}
c.JSON(http.StatusOK, postsWithComments)
} else {
// Không include comments, chỉ trả về posts đơn giản
simplePosts := make([]Post, len(posts))
for i, post := range posts {
simplePosts[i] = Post{
ID: post.ID,
Title: post.Title,
}
}
c.JSON(http.StatusOK, simplePosts)
}
}
// POST /posts
func createPost(c *gin.Context) {
var newPost Post
if err := c.ShouldBindJSON(&newPost); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
postMux.Lock()
newPost.ID = nextID
nextID++
posts = append(posts, newPost)
postMux.Unlock()
c.JSON(http.StatusCreated, newPost)
}
// Hàm gọi đến Comment service để lấy comments theo post ID
func fetchCommentsFromCommentService(postID int) ([]Comment, error) {
url := "http://localhost:8081/comments/post/" + strconv.Itoa(postID)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var comments []Comment
err = json.Unmarshal(body, &comments)
if err != nil {
return nil, err
}
return comments, nil
}
3. Kết quả
Kết quả tuyệt cà là vời khi chúng ta đã tạo ra được trang web facebook của riêng chúng ta với Sync comunication:

4. Quả bom hẹn giờ
Khi dự án đến tay hàng nghìn người sử dụng thì dự án đã để lộ những điểm yếu chết người như sau:
1. Lỗi Lan Truyền và Tight Coupling
Trong hệ thống microservice, Tight Coupling xảy ra khi các service phụ thuộc trực tiếp và đồng thời vào nhau để hoàn thành một tác vụ.
Khi một service phụ như Comment service bị lỗi (ở đây là database down), service chính (Post service) không thể nhận đủ dữ liệu cần thiết và buộc phải trả lỗi cho client. Điều này tạo ra hiệu ứng dây chuyền chỉ một điểm lỗi ở tầng dưới cũng có thể khiến toàn bộ luồng request thất bại, dù các service khác vẫn hoạt động bình thường.

2. Nghẽn Cổ Chai
Khi một service phải chờ kết quả từ nhiều service con hoặc nhiều truy vấn song song, tổng thời gian phản hồi sẽ bị giới hạn bởi thành phần chậm nhất trong chuỗi đó.
Trong ví dụ dưới đây, Post service gọi 3 Comment service song song, nhưng phải đợi tất cả cùng hoàn tất trước khi trả kết quả. Nếu một truy vấn mất 500 ms trong khi các truy vấn khác nhanh hơn, toàn bộ request vẫn phải chờ 500 ms, từ đó tạo ra một điểm nghẽn cổ chai.

III. Triển khai dự án theo hướng Async Comunication
Okay, giờ chúng ta sẽ tiến hành triển khai dự án theo hướng Async Comunication, đây mới là trọng tâm cần chúng ta chú ý trong toàn bộ bài viết này, các bạn có thể clone lại dự án với nhánh asynchronous https://gitlab.com/neko.arcoder/facebook để dễ dàng follow nhé!

Chúng ta sẽ cho Post service và Comment service chỉ có nhiệm vụ là tạo các Post và Comment, và sau khi tạo thì nhớ la lên báo cho Event bus nhận được thông tin, sau đó Event bus sẽ gửi thông tin cho Query service lưu lại. Khi này muốn truy vấn Post và Comment ta sẽ lấy ra từ Query service... Well, cơ bản ý tưởng của chúng ta là vậy ~~
Ở đây chúng ta đã quên mất sự hiện diện của Event bus, chúng ta chưa thực sự biết Event bus là gì và hoạt động như thế nào, vì vậy nên chúng ta sẽ nói về nó trước:
1. Lý thuyết
Điều đầu tiên bạn cần hiểu đó là Event Bus có rất nhiều loại khác nhau. Có thể bạn đã nghe qua mấy cái tên như RabbitMQ, Kafka, hay NATS... Đúng vậy, đi phỏng vấn intern 10 năm kinh nghiệm gặp mấy keyword này suốt luôn 😅. Thì đó đều là các dạng Event bus khác nhau, chúng là các dự án mã nguồn mở, bạn có thể tải về và tự chạy, hoặc dùng dịch vụ host sẵn trên mạng. Điểm chung của chúng là chúng đều nhận Event và sau đó gửi sự kiện đó đi cho các Listener.
Khi nói đến Event, bạn cần hiểu là ta đang nói về một gói thông tin nào đó, thực ra không có quy tắc cố định nào về việc một Event phải trông như thế nào cả, nó có thể là JSON, chuỗi bytes, string, hay bất kỳ định dạng gì bạn muốn chia sẻ giữa các service. Còn khi ta nói đến Listener, là ta đang nói về các service khác muốn nhận thông báo mỗi khi có Event được phát ra.
Giữa các loại event bus khác nhau như RabbitMQ, Kafka, NATS... chúng có những tính năng nhỏ khác nhau, có cái giúp mọi thứ dễ hơn, có cái thì phức tạp hơn. Bạn không thể chọn bừa một loại Event Bus mà phải đánh giá xem nó phù hợp với nhu cầu của mình hay không. Vì chúng ta mới học nên chúng ta sẽ tự triển khai một Event bus bằng Express để hiểu được một Event bus hoạt động ra sao, còn để triển khai RabbitMQ, Kafka hay NATS thì cần có một bài viết riêng để triển khai mấy cái này.
Giờ hãy nói về Event Bus viết bằng ExpressJS mà ta sắp làm. Phiên bản này rất đơn giản, chủ yếu chỉ để bạn thấy cách dữ liệu di chuyển trong hệ thống. Nó sẽ giúp bạn dễ hình dung quy trình Event chạy qua các service ra sao. Giờ mình sẽ mô tả cách nó hoạt động nhé:
Bước 1: Thêm một route mới vào trong Post Service và Comment Service và Query Service:
Mỗi route này sẽ nhận request POST đến đường dẫn /events

Tại một thời điểm nào đó, Post Service hoặc Comment Service sẽ cần phát ra một event. Khi service cần phát event, nó sẽ gửi POST request đến Event Bus mà chúng ta tạo ra. Trong POST đó, ta sẽ đính kèm dữ liệu của event là một object mô tả nội dung của event đó.
Bước 2: Tạo Event Bus bằng ExpressJS:
Ta tạo một app Express bình thường có route là POST với đường dẫn là /events. Event Bus sẽ nhận event, và sau đó gửi lại event đó đến tất cả các service khác bởi route /events mà ta đã định nghĩa ở trên.

Ví dụ, Event bus sẽ gửi event đến:
localhost:8080/events → Post Service,
localhost:8081/events → Comment Service,
localhost:8082/events → Query Service.
Tức là nó gửi cùng một event đến tất cả các service (Hàng nhà làm thì cứ tạm thế thôi hẹ hẹ).

Nói ngắn gọn, bất cứ khi nào một service gửi event đến Event Bus, Event Bus sẽ phát tán event đó đến toàn bộ hệ thống, kể cả service ban đầu phát ra nó. Vậy là xong, Event Bus của chúng ta chỉ đơn giản như vậy thôi.
Khi sau này dùng Event Bus thật, bạn sẽ thấy nó có thêm nhiều tính năng phức tạp hơn, nhưng bản chất vẫn chỉ là như vầy:
Service phát event → Event Bus nhận → gửi lại cho các service khác.
2. Thực hành
Bước 1: Triển khai Event bus:
Mình sẽ bỏ qua bước cài đặt và handle lỗi nhé:
const express = require('express')
const axios = require('axios')
const app = express()
app.use(express.json())
const port = 4000
app.post('/events', (req, res) => {
const event = req.body;
// Kiểm tra thử xem event có đúng không
console.log('Received event:', event);
const targets = [
'http://127.0.0.1:8080/events',
'http://127.0.0.1:8081/events',
'http://127.0.0.1:8082/events'
];
Promise.allSettled(
targets.map((url) => axios.post(url, event))
);
res.send({ status: 'OK' })
})
app.listen(port, '0.0.0.0', () => {
console.log(`Event Bus listening on port ${port}`)
})
Bước 2: Tạo loa phát thanh cho Post Service và Comment Service để báo cho Event bus:
1. Post Service: Ở đây chúng ta tạo hàm sendEvent để gửi event tạo Post tới Event bus.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"github.com/gin-gonic/gin"
)
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
}
type SendEvent struct {
Type string `json:"type"`
Data Post `json:"data"`
}
var (
posts = []Post{}
nextID = 1
postMux sync.Mutex
)
func main() {
r := gin.Default()
// CORS middleware
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
r.POST("/posts", createPost)
r.POST("/events", receiveEvent)
r.Run(":8080")
}
// POST /posts
func createPost(c *gin.Context) {
var newPost Post
if err := c.ShouldBindJSON(&newPost); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
postMux.Lock()
newPost.ID = nextID
nextID++
posts = append(posts, newPost)
postMux.Unlock()
// Tạo event và gửi đến cho event bus
event := SendEvent{
Type: "PostCreated",
Data: newPost,
}
if err := sendEvent(event); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, newPost)
}
func sendEvent(event SendEvent) error {
url := "http://localhost:4000/events"
jsonData, err := json.Marshal(event)
if err != nil {
return err
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("event bus error: %s", string(body))
}
return nil
}
2. Comment Service: Ở đây chúng ta tạo hàm sendEvent để gửi event tạo Comment tới Event bus.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import httpx
app = FastAPI(title="Comment Service", version="1.0.0")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Data models
class Comment(BaseModel):
id: int
content: str
postId: int
class CommentCreate(BaseModel):
content: str
class SendEvent(BaseModel):
type: str
data: Comment
comments_db = []
next_comment_id = 1
@app.get("/")
async def root():
return {"message": "Comment Service is running"}
@app.post("/comments/post/{post_id}")
async def create_comment(post_id: int, comment_data: CommentCreate):
"""Tạo comment mới cho post"""
global next_comment_id
new_comment = {
"id": next_comment_id,
"content": comment_data.content,
"postId": post_id
}
comments_db.append(new_comment)
next_comment_id += 1
# Gửi event tới Event Bus
await send_event({
"type": "CommentCreated",
"data": new_comment
})
return new_comment
async def send_event(event: SendEvent):
url = "http://localhost:4000/events"
async with httpx.AsyncClient() as client:
await client.post(url, json=event, timeout=5)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8081)
3. Kiểm tra thử Event bus có hoạt động không?: Bạn hãy thử tạo 1 Post xem console.log ở terminal Event bus đã hoạt động chưa nhé
Event Bus listening on port 4000
Received event: { type: 'PostCreated', data: { id: 1, title: 'Test1' } }
Bước 3: Tạo route /events cho Post Service và Comment Service để nhận event từ Event bus:
1. Post Service:
// Existing code
type ReceiveEvent struct {
Type string `json:"type"`
Data any `json:"data"`
}
func receiveEvent(c *gin.Context) {
var event ReceiveEvent
if err := c.ShouldBindJSON(&event); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("Received event:", event)
c.JSON(http.StatusOK, gin.H{"message": "Event received"})
}
// ...
2. Comment Service:
# Existing code
from typing import Any
class ReceiveEvent(BaseModel):
type: str
data: Any
@app.post("/events")
async def receive_event(event: ReceiveEvent):
print("Received event:", event)
return {"message": "Event received"}
# ...
3. Kiểm tra thử Event bus có gửi event đến các Service chưa?: Giờ bạn hãy thử tạo một Post xem, ở cả Post và Comment service đã nhận được event do Event bus gửi hay chưa
Received event: {PostCreated map[id:2 title:test]}
Received event: type='PostCreated' data={'id': 2, 'title': 'test'}
Bước cuối: Tạo Query service:
Giờ chúng ta sẽ tạo 2 endpoint, 1 endpoint để nhận event từ event bus rồi lưu data của event, 1 endpoint là để front-end truy vấn.

const express = require('express')
const axios = require('axios')
const cors = require('cors')
const app = express()
app.use(express.json())
app.use(cors(
{
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
}
))
const port = 8082
const posts = []
app.post('/events', (req, res) => {
const event = req.body;
if(event?.type === 'PostCreated') {
posts.push(event.data)
}
if(event?.type === 'CommentCreated') {
const post = posts.find(post => post.id === event.data.postId)
if(post) {
post.comments = post.comments || []
post.comments.push(event.data)
}
}
res.send({ status: 'OK' })
})
app.get('/posts', (req, res) => {
res.send(posts)
})
app.listen(port, '0.0.0.0', () => {
console.log(`Query Service listening on port ${port}`)
})
Vậy là thành công rồi!!!
Sau bao nhiêu nỗ lực cuối cùng chúng ta đã triển khai hệ thống facebook cho Mark Zuckerberg theo hướng Async Communication. Giờ thì lụm lúa thôi.

IV. Những thắc mắc còn bỏ ngỏ
Khi nào thì thêm một service?
Hẳn các bạn rất thắc mắc khi nào thì chúng ta phải thêm một service mới ví dụ như posts và comments mà muốn hiển thị chúng cùng nhau thì ở đây ta đã tạo một Query service thứ ba qua ví dụ vừa rồi.
Thực ra chúng ta không nhất thiết phải tạo ra thêm service mới chỉ để ghép dữ liệu lại. Trong thực tế, nếu đây là một ứng dụng blog thật, thì phần Post và Comment sẽ cùng nằm trong một service. Khi đó ta có thể join dữ liệu ngay trong code, thay vì phải tạo thêm một service khác chỉ để join dữ liệu ở cấp hệ thống.
Những gì ta đang làm bây giờ chỉ là để ví dụ, để bạn hiểu cách dữ liệu có thể được tách ra nhiều service khác nhau và rồi tái sử dụng hoặc đồng bộ chúng ở một nơi khác.
Microservices quá phức tạp so với lợi ích mang lại!
Mình hiểu, và đúng là bây giờ trông nó phức tạp thật. Kiểu kiến trúc dựa trên Event-driven này hiện giờ có vẻ rườm rà và khó hiểu, nhưng bạn sẽ sớm thấy lý do vì sao nó đáng giá khi thêm mới một tính năng, và về lâu về dài những lợi ích nó mang lại trong việc mở rộng lớn như thế nào, so với việc ta đang dùng monolith.
Nói cách khác, dù lúc này nó có vẻ quá sức cần thiết, nhưng sau này bạn sẽ thấy nó giúp mọi thứ mở rộng dễ hơn.
Nếu event này xảy ra cùng lúc với event kia thì sao? Nếu bị lệch dữ liệu thì sao?
Đúng vậy, khi dùng kiến trúc event-driven, sẽ có một số trường hợp đặc biệt mà ta phải lưu ý. Ví dụ:
- Hai event cùng lúc gửi đến, có thể khiến thứ tự xử lý không đúng.
- Một event đến chậm hơn hoặc bị mất, dẫn đến lệch dữ liệu giữa các service.
Đây thực chất không phải lỗi của event-driven, mà là đặc điểm tự nhiên của hệ thống phân tán. Khi các service hoạt động độc lập và giao tiếp qua event, sẽ luôn có khả năng một service nhận được event sớm hoặc muộn hơn mong đợi.
Về cách xử lý chúng ta sẽ có một số như sau:
- Thiết kế event rõ ràng, có version hoặc timestamp → để biết event nào là mới nhất.
- Lưu trữ event history (Event log / outbox pattern) → đảm bảo có thể khôi phục nếu mất dữ liệu.
- Đảm bảo idempotent (xử lý lặp lại không gây lỗi) → nếu event gửi lại, hệ thống vẫn đúng.
- Dùng saga / transaction pattern để đảm bảo dữ liệu nhất quán giữa nhiều service.
V. Tổng kết
Vậy là chúng ta đã hoàn thành xong thực hành chi tiết thực chiến cách triển khai một dự án Microservices với cả 2 chiến lược comunication.
Ở bài viết sau, chúng ta sẽ tìm hiểu một chủ đề rất thú vị đó chính là docker. Có lẽ bạn đã nghe đến Docker và tìm hiểu nó, nhưng vẫn sẽ có chút mơ hồ đúng chứ, đừng lo, bài viết tiếp theo mình sẽ diễn đạt Docker một cách dễ hiểu nhất tới cho anh em thông qua kiến thức mình học của anh Sờ Ti Phờn.
Mình xin nhắc lại là bài viết của mình không thể thay thế được những kiến thức khóa học từ anh Sờ Te Phờn, các bạn hãy đăng ký và trải nghiệm thử.
Nếu bạn thấy hay thì cho mình xin 1 upvote và để lại comment những chỗ mình đã sai, hoặc muốn thảo luận thêm nhé.
Mình là NekoArcoder, xin hẹn gặp lại các bạn ở bài viết tiếp theo!!!

All rights reserved