SiamCafe.net Blog
Technology

Payload CMS Pub Sub Architecture

payload cms pub sub architecture
Payload CMS Pub Sub Architecture | SiamCafe Blog
2026-01-16· อ. บอม — SiamCafe.net· 8,745 คำ

Payload CMS Pub/Sub

Payload CMS Headless TypeScript React Pub/Sub Architecture Event-Driven Kafka RabbitMQ Redis Hooks REST GraphQL MongoDB PostgreSQL Real-time Content

CMSLanguageDatabaseAPIHooks
PayloadTypeScriptMongoDB/PGREST+GraphQLดีมาก
StrapiJavaScriptSQLite/MySQL/PGREST+GraphQLดี
DirectusTypeScriptSQL ทุกตัวREST+GraphQLดี
SanityJavaScriptCloudGROQ+GraphQLWebhook

Payload CMS Setup และ Hooks

# === Payload CMS with Pub/Sub ===

# npx create-payload-app@latest my-cms
# cd my-cms && npm run dev

# payload.config.ts
# import { buildConfig } from 'payload/config'
# import { mongooseAdapter } from '@payloadcms/db-mongodb'
#
# export default buildConfig({
#   db: mongooseAdapter({ url: process.env.MONGODB_URI }),
#   collections: [
#     {
#       slug: 'articles',
#       fields: [
#         { name: 'title', type: 'text', required: true },
#         { name: 'content', type: 'richText' },
#         { name: 'category', type: 'select',
#           options: ['tech', 'finance', 'lifestyle'] },
#         { name: 'status', type: 'select',
#           options: ['draft', 'published', 'archived'] },
#         { name: 'author', type: 'relationship', relationTo: 'users' },
#       ],
#       hooks: {
#         afterChange: [publishEvent],
#         afterDelete: [publishDeleteEvent],
#       },
#     },
#   ],
# })
#
# // Hook: Publish Event
# async function publishEvent({ doc, operation, req }) {
#   const event = {
#     type: `article.`,  // created or updated
#     payload: { id: doc.id, title: doc.title, status: doc.status },
#     timestamp: new Date().toISOString(),
#   }
#   await redis.publish('content-events', JSON.stringify(event))
#   console.log(`Published: `)
# }

from dataclasses import dataclass
from typing import List

@dataclass
class ContentEvent:
    event_type: str
    collection: str
    doc_id: str
    title: str
    timestamp: str

events = [
    ContentEvent("article.created", "articles", "a001", "วิธีใช้ Docker", "10:00:00"),
    ContentEvent("article.updated", "articles", "a002", "Python Tips", "10:05:00"),
    ContentEvent("article.published", "articles", "a001", "วิธีใช้ Docker", "10:10:00"),
    ContentEvent("page.created", "pages", "p001", "About Us", "10:15:00"),
    ContentEvent("article.deleted", "articles", "a003", "Old Post", "10:20:00"),
]

print("=== Content Events ===")
for e in events:
    print(f"  [{e.timestamp}] {e.event_type}")
    print(f"    Collection: {e.collection} | ID: {e.doc_id} | Title: {e.title}")

Pub/Sub Implementation

# === Pub/Sub with Redis ===

# Publisher (Payload Hook)
# import Redis from 'ioredis'
# const redis = new Redis(process.env.REDIS_URL)
#
# async function publishEvent(channel, event) {
#   await redis.publish(channel, JSON.stringify(event))
# }
#
# // Subscriber (Search Indexer)
# const subscriber = new Redis(process.env.REDIS_URL)
# subscriber.subscribe('content-events')
# subscriber.on('message', async (channel, message) => {
#   const event = JSON.parse(message)
#   if (event.type === 'article.created' || event.type === 'article.updated') {
#     await elasticsearch.index({
#       index: 'articles',
#       id: event.payload.id,
#       body: event.payload,
#     })
#   }
# })

# Kafka Alternative
# from kafka import KafkaProducer, KafkaConsumer
# import json
#
# producer = KafkaProducer(
#     bootstrap_servers=['localhost:9092'],
#     value_serializer=lambda v: json.dumps(v).encode('utf-8'),
# )
#
# def publish_content_event(event):
#     producer.send('content-events', value=event)
#     producer.flush()
#
# consumer = KafkaConsumer(
#     'content-events',
#     bootstrap_servers=['localhost:9092'],
#     value_deserializer=lambda m: json.loads(m.decode('utf-8')),
#     group_id='search-indexer',
# )
#
# for message in consumer:
#     event = message.value
#     handle_event(event)

@dataclass
class Subscriber:
    name: str
    topic: str
    action: str
    latency: str

subscribers = [
    Subscriber("Search Indexer", "content-events", "Update Elasticsearch", "< 1s"),
    Subscriber("Cache Invalidator", "content-events", "Clear CDN Cache", "< 2s"),
    Subscriber("SSG Rebuilder", "content-events", "Trigger Next.js ISR", "< 5s"),
    Subscriber("Notification", "content-events", "Send Push Notification", "< 3s"),
    Subscriber("Analytics", "content-events", "Log Content Changes", "< 1s"),
    Subscriber("Backup Service", "content-events", "Backup to S3", "< 10s"),
]

print("\n=== Pub/Sub Subscribers ===")
for s in subscribers:
    print(f"  [{s.name}] Topic: {s.topic}")
    print(f"    Action: {s.action} | Latency: {s.latency}")

Production Architecture

# === Production Setup ===

# docker-compose.yml
# version: '3.8'
# services:
#   payload:
#     build: .
#     ports: ["3000:3000"]
#     environment:
#       - MONGODB_URI=mongodb://mongo:27017/cms
#       - REDIS_URL=redis://redis:6379
#     depends_on: [mongo, redis]
#   mongo:
#     image: mongo:7
#     volumes: [mongo-data:/data/db]
#   redis:
#     image: redis:7-alpine
#     ports: ["6379:6379"]
#   search-indexer:
#     build: ./services/search-indexer
#     environment:
#       - REDIS_URL=redis://redis:6379
#       - ES_URL=http://elasticsearch:9200
#   elasticsearch:
#     image: elasticsearch:8.12.0
#     environment:
#       - discovery.type=single-node
#     ports: ["9200:9200"]

architecture = {
    "Payload CMS": "Content Management + REST/GraphQL API",
    "MongoDB": "Content Storage (Primary DB)",
    "Redis": "Pub/Sub Message Broker + Cache",
    "Elasticsearch": "Full-text Search Index",
    "Next.js": "Frontend SSG/ISR",
    "CDN": "Static Asset Delivery",
    "S3": "Media Upload Storage",
}

print("Architecture Components:")
for component, role in architecture.items():
    print(f"  [{component}]: {role}")

# Event Flow
flow = [
    "1. Editor สร้าง/แก้ Content ใน Payload Admin",
    "2. afterChange Hook Publish Event ไป Redis",
    "3. Search Indexer รับ Event อัพเดท Elasticsearch",
    "4. Cache Invalidator ล้าง CDN Cache",
    "5. SSG Rebuilder Trigger ISR สร้างหน้าใหม่",
    "6. Notification Service ส่ง Push ให้ Subscribers",
    "7. Frontend แสดงเนื้อหาใหม่ภายใน 5 วินาที",
]

print(f"\n\nEvent Flow:")
for step in flow:
    print(f"  {step}")

เคล็ดลับ

การนำความรู้ไปประยุกต์ใช้งานจริง

แหล่งเรียนรู้ที่แนะนำ ได้แก่ Official Documentation ที่อัพเดทล่าสุดเสมอ Online Course จาก Coursera Udemy edX ช่อง YouTube คุณภาพทั้งไทยและอังกฤษ และ Community อย่าง Discord Reddit Stack Overflow ที่ช่วยแลกเปลี่ยนประสบการณ์กับนักพัฒนาทั่วโลก

Payload CMS คืออะไร

Open Source Headless CMS TypeScript React Admin REST GraphQL MongoDB PostgreSQL Hooks Access Control Upload Versioning

Pub/Sub Architecture คืออะไร

Publish Subscribe Messaging Topic Loose Coupling Scale Kafka RabbitMQ Redis Google Pub/Sub Event-Driven Microservices

Payload CMS กับ Pub/Sub ใช้ร่วมกันอย่างไร

Hooks afterChange Publish Event Broker Content เปลี่ยน Rebuild Search Cache Notification Real-time Delivery

Payload CMS กับ Strapi ต่างกันอย่างไร

Payload TypeScript Code-first Type-safe Hooks MongoDB+PG Strapi JS GUI-first ง่ายกว่า Community ใหญ่ SQLite MySQL PG

สรุป

Payload CMS Headless TypeScript Pub/Sub Architecture Redis Kafka Event-Driven Hooks afterChange Search Cache ISR Notification Microservices MongoDB Elasticsearch Production

📖 บทความที่เกี่ยวข้อง

Payload CMS Disaster Recovery Planอ่านบทความ → Payload CMS Hybrid Cloud Setupอ่านบทความ → Radix UI Primitives Pub Sub Architectureอ่านบทความ → Solid.js Signals Pub Sub Architectureอ่านบทความ → Payload CMS CQRS Event Sourcingอ่านบทความ →

📚 ดูบทความทั้งหมด →