SiamCafe.net Blog
Technology

Payload CMS Distributed System

payload cms distributed system
Payload CMS Distributed System | SiamCafe Blog
2025-11-11· อ. บอม — SiamCafe.net· 11,450 คำ

Payload CMS

Payload CMS เป็น Headless CMS สร้างด้วย TypeScript Node.js Open Source MongoDB PostgreSQL Admin Panel REST API GraphQL API Authentication Access Control

Distributed System ใช้ Payload เป็น Content API หลาย Frontend เรียก API CDN Cache Redis Webhooks Load Balancer

Payload CMS Setup

# === Payload CMS Setup ===

# 1. สร้างโปรเจค
# npx create-payload-app@latest my-cms
# cd my-cms

# 2. payload.config.ts
# import { buildConfig } from 'payload/config'
# import { mongooseAdapter } from '@payloadcms/db-mongodb'
# import { slateEditor } from '@payloadcms/richtext-slate'
# import { webpackBundler } from '@payloadcms/bundler-webpack'
# import { Articles } from './collections/Articles'
# import { Users } from './collections/Users'
# import { Media } from './collections/Media'
#
# export default buildConfig({
#   admin: { user: Users.slug, bundler: webpackBundler() },
#   editor: slateEditor({}),
#   db: mongooseAdapter({ url: process.env.MONGODB_URI }),
#   collections: [Users, Articles, Media],
#   typescript: { outputFile: './types.ts' },
#   graphQL: { schemaOutputFile: './schema.graphql' },
# })

# 3. Collections — Articles
# // collections/Articles.ts
# import { CollectionConfig } from 'payload/types'
#
# export const Articles: CollectionConfig = {
#   slug: 'articles',
#   admin: {
#     useAsTitle: 'title',
#     defaultColumns: ['title', 'status', 'publishedAt'],
#   },
#   access: {
#     read: () => true,  // Public read
#     create: ({ req: { user } }) => !!user,
#     update: ({ req: { user } }) => !!user,
#     delete: ({ req: { user } }) => user?.role === 'admin',
#   },
#   hooks: {
#     afterChange: [
#       async ({ doc, operation }) => {
#         // Webhook: แจ้ง Services อื่นเมื่อ Content เปลี่ยน
#         if (operation === 'create' || operation === 'update') {
#           await fetch(process.env.WEBHOOK_URL, {
#             method: 'POST',
#             headers: { 'Content-Type': 'application/json' },
#             body: JSON.stringify({
#               event: `article.`,
#               data: { id: doc.id, title: doc.title, slug: doc.slug },
#             }),
#           }).catch(console.error)
#         }
#       },
#     ],
#   },
#   fields: [
#     { name: 'title', type: 'text', required: true },
#     { name: 'slug', type: 'text', unique: true, admin: { position: 'sidebar' } },
#     { name: 'content', type: 'richText' },
#     { name: 'excerpt', type: 'textarea' },
#     { name: 'coverImage', type: 'upload', relationTo: 'media' },
#     { name: 'author', type: 'relationship', relationTo: 'users' },
#     { name: 'category', type: 'select', options: [
#       { label: 'Technology', value: 'tech' },
#       { label: 'Business', value: 'business' },
#       { label: 'Lifestyle', value: 'lifestyle' },
#     ]},
#     { name: 'tags', type: 'array', fields: [
#       { name: 'tag', type: 'text' },
#     ]},
#     { name: 'status', type: 'select', defaultValue: 'draft', options: [
#       { label: 'Draft', value: 'draft' },
#       { label: 'Published', value: 'published' },
#     ]},
#     { name: 'publishedAt', type: 'date', admin: { position: 'sidebar' } },
#   ],
# }

import json

config = {
    "collections": ["Users", "Articles", "Media", "Categories"],
    "database": "MongoDB or PostgreSQL",
    "api": ["REST API", "GraphQL API", "Local API"],
    "auth": "Built-in Authentication + Access Control",
    "hooks": "beforeChange, afterChange, beforeRead, afterRead",
    "plugins": "SEO, Form Builder, Nested Docs, Redirects",
}

print("Payload CMS Configuration:")
for key, value in config.items():
    if isinstance(value, list):
        print(f"  {key}: {', '.join(value)}")
    else:
        print(f"  {key}: {value}")

Distributed Architecture

# distributed_payload.py — Payload CMS Distributed Architecture
from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class ServiceNode:
    name: str
    role: str
    technology: str
    port: int
    replicas: int

class PayloadDistributed:
    """Payload CMS Distributed Architecture"""

    def __init__(self):
        self.services: List[ServiceNode] = []

    def add_service(self, svc: ServiceNode):
        self.services.append(svc)

    def show_architecture(self):
        print(f"\n{'='*55}")
        print(f"Payload CMS — Distributed Architecture")
        print(f"{'='*55}")

        by_role = {}
        for svc in self.services:
            if svc.role not in by_role:
                by_role[svc.role] = []
            by_role[svc.role].append(svc)

        for role, services in by_role.items():
            print(f"\n  [{role}]")
            for svc in services:
                print(f"    {svc.name} ({svc.technology})")
                print(f"      Port: {svc.port} | Replicas: {svc.replicas}")

    def caching_strategy(self):
        """Caching Strategy"""
        strategies = {
            "CDN Cache": {
                "what": "Cache API Response ที่ CDN Edge",
                "ttl": "5-60 นาที",
                "invalidation": "Webhook จาก Payload afterChange hook",
                "tools": "Cloudflare, Fastly, CloudFront",
            },
            "Redis Cache": {
                "what": "Cache Database Queries ใน Redis",
                "ttl": "1-5 นาที",
                "invalidation": "afterChange hook ลบ Cache Key",
                "tools": "Redis, Upstash, ElastiCache",
            },
            "ISR (Next.js)": {
                "what": "Incremental Static Regeneration",
                "ttl": "60 วินาที revalidate",
                "invalidation": "On-demand Revalidation API",
                "tools": "Next.js, Vercel",
            },
        }

        print(f"\n  Caching Strategies:")
        for name, info in strategies.items():
            print(f"\n    [{name}]")
            for key, value in info.items():
                print(f"      {key}: {value}")

# ตัวอย่าง
arch = PayloadDistributed()

services = [
    ServiceNode("Payload CMS", "Backend", "Node.js + TypeScript", 3000, 3),
    ServiceNode("MongoDB Replica Set", "Database", "MongoDB 7.0", 27017, 3),
    ServiceNode("Redis Cache", "Cache", "Redis 7.2", 6379, 2),
    ServiceNode("Next.js Frontend", "Frontend", "Next.js 14", 3001, 3),
    ServiceNode("Nginx Load Balancer", "Edge", "Nginx", 80, 2),
    ServiceNode("MinIO/S3", "Storage", "MinIO", 9000, 1),
    ServiceNode("Webhook Worker", "Worker", "BullMQ", 0, 2),
]

for svc in services:
    arch.add_service(svc)

arch.show_architecture()
arch.caching_strategy()

# Docker Compose
docker_services = {
    "payload": "Node.js CMS Server (Port 3000)",
    "mongodb": "MongoDB Replica Set (Port 27017)",
    "redis": "Redis Cache (Port 6379)",
    "nginx": "Reverse Proxy + Load Balancer (Port 80)",
    "minio": "S3-compatible Object Storage (Port 9000)",
    "frontend": "Next.js SSR Frontend (Port 3001)",
}

print(f"\n\nDocker Compose Services:")
for svc, desc in docker_services.items():
    print(f"  {svc}: {desc}")

Multi-tenancy และ Scaling

# multi_tenant.py — Multi-tenancy Pattern
# Payload CMS Multi-tenancy approaches

approaches = {
    "Database per Tenant": {
        "description": "แต่ละ Tenant มี Database แยก",
        "pros": ["Data Isolation สูงสุด", "Performance แยก", "Backup แยก"],
        "cons": ["จัดการ Database หลายตัว", "Cost สูง", "Migration ซับซ้อน"],
        "code": """
# Payload Config per Tenant
# const tenantConfig = (tenantId) => buildConfig({
#   db: mongooseAdapter({
#     url: `mongodb://localhost/_db`
#   }),
#   serverURL: `https://.myapp.com`,
# })
""",
    },
    "Shared Database + Tenant Field": {
        "description": "Database เดียว เพิ่ม tenant field ทุก Collection",
        "pros": ["จัดการง่าย", "Cost ต่ำ", "Migration ง่าย"],
        "cons": ["Data Leak Risk", "Performance ร่วม", "Query ซับซ้อน"],
        "code": """
# Collection with Tenant Field
# {
#   slug: 'articles',
#   fields: [
#     { name: 'tenant', type: 'relationship', relationTo: 'tenants' },
#     // ... other fields
#   ],
#   access: {
#     read: ({ req }) => ({
#       tenant: { equals: req.user?.tenant }
#     }),
#   },
# }
""",
    },
    "Collection per Tenant": {
        "description": "Dynamic Collections ตาม Tenant",
        "pros": ["แยก Collection ชัดเจน", "Flexible Schema"],
        "cons": ["ซับซ้อน", "Payload ไม่รองรับ Dynamic Collections"],
        "code": "# ไม่แนะนำ — ใช้ Shared DB + Tenant Field แทน",
    },
}

print("Multi-tenancy Approaches:")
for name, info in approaches.items():
    print(f"\n  [{name}]")
    print(f"    {info['description']}")
    print(f"    Pros: {', '.join(info['pros'])}")
    print(f"    Cons: {', '.join(info['cons'])}")

# Scaling Strategies
scaling = {
    "Horizontal Scaling": {
        "what": "เพิ่มจำนวน Payload Instances",
        "how": "Kubernetes Deployment replicas: 3-10",
        "requirement": "Shared Database + Redis Session Store",
    },
    "Database Scaling": {
        "what": "MongoDB Replica Set + Sharding",
        "how": "Primary-Secondary-Secondary + Shard Key",
        "requirement": "MongoDB Atlas หรือ Self-hosted Replica Set",
    },
    "CDN + Edge Caching": {
        "what": "Cache API Response ที่ Edge",
        "how": "Cloudflare Workers + KV Store",
        "requirement": "Cache-Control Headers + Webhook Invalidation",
    },
    "Read Replicas": {
        "what": "แยก Read/Write Database",
        "how": "Read จาก Secondary, Write จาก Primary",
        "requirement": "MongoDB Read Preference: secondaryPreferred",
    },
}

print(f"\n\nScaling Strategies:")
for name, info in scaling.items():
    print(f"\n  [{name}]")
    for key, value in info.items():
        print(f"    {key}: {value}")

Best Practices

Payload CMS คืออะไร

Headless CMS TypeScript Node.js Open Source MongoDB PostgreSQL Admin Panel REST API GraphQL API Authentication Access Control Hooks Plugins

Headless CMS ต่างจาก Traditional CMS อย่างไร

Headless แยก Backend Frontend ส่ง API ใช้ Frontend อะไรก็ได้ Traditional รวมไว้ด้วยกัน WordPress Headless ยืดหยุ่น Scale ง่ายกว่า

Payload CMS ใช้กับ Distributed System อย่างไร

Content API Server หลาย Frontend เรียก API CDN Cache Redis Cache Webhooks แจ้ง Services Load Balancer กระจาย Traffic Multi-instance

Payload CMS เทียบกับ Strapi อย่างไร

Payload TypeScript Type-safe Local API Code-first Strapi Community ใหญ่ Plugin มาก Payload ยืดหยุ่นกว่า ไม่ต้อง GUI สร้าง Schema

สรุป

Payload CMS Headless CMS TypeScript Node.js Distributed System Content API CDN Cache Redis Webhooks Load Balancer Multi-tenancy Shared DB Tenant Field Horizontal Scaling MongoDB Replica Set Access Control Hooks TypeScript Type-safe

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

Python Pydantic Distributed Systemอ่านบทความ → Payload CMS Serverless Architectureอ่านบทความ → Elixir Ecto Distributed Systemอ่านบทความ → XDR Platform Distributed Systemอ่านบทความ → HTTP/3 QUIC Distributed Systemอ่านบทความ →

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