2.3 · Architectural patterns
การอ่าน Volume 3-10 และ Volume 11-15 ต่อเนื่องกันทำให้เห็น pattern สถาปัตยกรรมที่เกิดซ้ำข้ามหลาย platform group ไม่ใช่เรื่องบังเอิญของ repo เดียว — บทนี้ตั้งชื่อ pattern เหล่านั้นให้ชัดเจน เพื่อให้เวลาพูดถึงปัญหาระดับสถาปัตยกรรม ทีมมีคำศัพท์กลางใช้ร่วมกัน
Pattern 1 — Distributed monolith (การต่อ database ข้าม service ตรง)
คำนิยาม — service ที่ deploy แยกกันเปิด connection (TypeORM/Sequelize/raw driver) ไปที่ database ที่อีก service เป็นเจ้าของ/เขียนหลัก แทนที่จะเรียกผ่าน API ของเจ้าของข้อมูล
หลักฐาน — Volume 11 § 11.2 พบ 14 เส้น coupling แบบนี้ ตัวอย่างที่รุนแรงที่สุดคือ chat-center-api เปิด TypeORM connection ไปที่ database 2way ของ 2way-api ด้วย synchronize: true — แปลว่าทุกครั้งที่ deploy chat-center-api และมี entity เปลี่ยน TypeORM จะรัน ALTER TABLE/CREATE TABLE บน database ที่ตัวเองไม่ได้เป็นเจ้าของ โดย 2way-api ไม่มีทางรู้หรือ approve การเปลี่ยนแปลงล่วงหน้า
เหตุผลที่นับเป็น pattern ไม่ใช่ข้อยกเว้น — Volume 11 เทียบไว้ชัดว่ามีทั้ง pattern ที่ "สุขภาพดี" (เรียกผ่าน API จริง เช่น meeting-vtrc-api → vtrc-api, benefit-api → vtrc-common) และ pattern นี้ปนกันอยู่ในระบบเดียว — แสดงว่าทีมงานรู้จัก API-mediated call ที่ถูกต้องอยู่แล้ว การเลือกต่อ DB ตรงในหลายจุดน่าจะมาจาก deadline pressure หรือ perf shortcut มากกว่าไม่รู้ pattern ที่ถูกต้อง
ผลกระทบเชิงสถาปัตยกรรม — ไม่มี schema migration ใดที่ปลอดภัยฝ่ายเดียวอีกต่อไป, access control กระจุกอยู่ที่ layer เดียวที่ bypass ได้ง่าย, cascade delete/update ไม่มีทางทำงานข้าม engine (ต้อง manual cascade เอง) — รายละเอียดเต็มที่ Volume 11 § 11.2
Pattern 2 — Branch-per-environment (โค้ดจริงไม่อยู่ที่ default branch)
คำนิยาม — repo ที่ default branch (main/master) ว่างหรือมีโค้ดน้อยกว่า branch ที่ deploy จริงต่อ environment (prod, uat, ฯลฯ)
หลักฐาน — 15+ จาก 31 repo ใช้ pattern นี้ (2way-api→prod, pms-api→uat, workflow-api→uat, ai-recruitment-api→prod ในขณะที่ main ว่างเปล่า ฯลฯ) — ดูตารางเต็มที่ Canonical Truths § Branch topology และตัวเลขสรุปที่ Volume 15 § ตัวเลขที่ควรจำ
ความเสี่ยง — engineer ที่ git clone แล้วเปิด branch default โดยไม่รู้กฎนี้จะเห็นโค้ดที่ไม่ตรงกับ production จริง ทุก citation ในคู่มือนี้จึงต้องระบุ branch annotation เมื่ออ้างถึง repo กลุ่มนี้ (ดู Volume 0 § Citation convention) — ไม่มี CI ที่ตรวจสอบให้ default branch sync กับ branch ที่ deploy จริงโดยอัตโนมัติ
Pattern 3 — Shared secret ไม่มี central management
คำนิยาม — service หลายตัวใช้ค่า secret (JWT secret, database password, encryption key) ตัวเดียวกันตัวต่อตัวอักษร โดย copy เข้า .env/source ของตัวเอง แทนการเรียก central secret manager หรือ central auth service
หลักฐาน — Volume 11 § 11.4 ยืนยัน 5 cluster ที่ครอบคลุม 14 repo ที่ไม่ซ้ำกัน:
- Cluster 1 — JWT secret เดียวกันข้าม
vtrc-api+benefit-api+recruitment-api+ai-recruitment-api+pms-api(5 service) — token ที่vtrc-apiออกให้ verify ผ่านrecruitment-apiได้ตรง - Cluster 2 — MSSQL
sapassword เดียวกันข้ามchat-center-api,job-scheduler-api,vtrc-common,workflow-api - Cluster 3 — JWT secret รูปแบบ RSA-key เดียวกันข้าม
vtrc-common,chat-center-api,job-scheduler-api(ซ้อนกับ Cluster 2 บางส่วน) - Cluster 4 —
SECRET_JWT_TWOWAYเดียวกันข้าม2way-api,2way-batch,2way-line-service - Cluster 5 — RSA private key เดียวกันข้าม
vtrc-mobile,new-vtrc-mobile(compile เข้า JS bundle ที่แจกจ่ายจริง)
เหตุผลที่เป็น pattern สถาปัตยกรรมไม่ใช่แค่ bug — ไม่มี repo ใดในทั้งแพลตฟอร์มใช้ secret manager (Vault/AWS Secrets Manager/k8s Secret) — ทุก secret อยู่ใน .env แบบ plaintext เป็นทางเลือกทางสถาปัตยกรรมที่ทำซ้ำกันตั้งแต่ legacy core จนถึง microservices ที่สร้างล่าสุด ไม่ใช่ความผิดพลาดของทีมใดทีมหนึ่ง — ดู Volume 12 § 12.1 Auth mechanisms inventory สำหรับผลกระทบด้าน trust boundary
Pattern 4 — Fork-not-rewrite (คู่ repo ที่ดูเหมือนเวอร์ชันใหม่แต่เป็น fork)
คำนิยาม — เมื่อทีมต้องการพัฒนาต่อจาก codebase เดิมโดยไม่แน่ใจว่าจะ deprecate ตัวเก่าหรือไม่ วิธีที่เลือกซ้ำ ๆ คือ copy ทั้ง repository แล้วตั้งชื่อ -new แทนการสร้าง branch ใหม่ในบันทึกด้วย git history เดียวกัน
หลักฐานที่ 1 — vtrc-backoffice vs vtrc-backoffice-new — Volume 4 พบว่าเป็น fork/copy โครงสร้างทั้งชุด ไม่ใช่ rewrite: package.json ทั้งสองระบุชื่อ "vtrc-backoffice" เหมือนกัน, dependency ทุกตัวตรงกัน, src/ ตรงกัน 99%+ (ต่างกันแค่ 3 จาก 700+ ไฟล์) ทั้งสองมีแนวโน้มยัง live คู่กันในบทบาทที่ทับซ้อนกัน (deploy status จริง — UNVERIFIED)
หลักฐานที่ 2 — meeting-backoffice vs meeting-backoffice-new — Volume 7 พบรูปแบบต่างออกไปเล็กน้อย — ไม่ใช่ fork เพื่อพัฒนาต่อ แต่เป็นการ port framework (Next.js → CRA) เพื่อวัตถุประสงค์การฝัง (embed) เป็น static sub-app เข้าไปใน vtrc-backoffice(-new) — หลักฐานชี้ขาดคือ shasum ของไฟล์ build ตรงกันเป๊ะระหว่าง 3 repo และ commit timestamp ห่างกันเพียง 4 นาที
เหตุผลที่เป็น pattern สถาปัตยกรรม — ทั้งสองกรณีทำให้ทีมดูแลระบบต้องเก็บ 2 codebase ที่ทำหน้าที่เดียวกันไว้พร้อมกันโดยไม่มีเอกสารยืนยันชัดว่าตัวไหนคือ "ของจริง" ในปัจจุบัน — เพิ่ม maintenance cost และความเสี่ยงที่ patch หนึ่งตัวจะไม่ถูก apply เข้าอีกตัว (เช่น vtrc-backoffice-new อัปเดต token-refresh flow เป็น refreshTokenBO แต่ vtrc-backoffice เดิมยังใช้ query เก่า)
Pattern 5 — Hub-and-spoke รอบ vtrc-api/cu-central-api
คำนิยาม — แม้แพลตฟอร์มมี microservice ใหม่เกิดขึ้น 20+ ตัวหลังจาก legacy core แต่ทุกกลุ่มยังพึ่งพา vtrc-api/cu-central-api เป็นแหล่งข้อมูลพนักงาน (employee directory) กลาง
หลักฐาน — Volume 13 § 13.3.6 พบว่า vtrc-api มี fan-in 9 caller และ cu-central-api มี fan-in 4 caller ข้าม platform group — query fetchListEmployeeForTwoWay ถูกเรียกจาก 3 caller ต่างกลุ่ม (2way-api, 2way-vtrc-api, meeting-vtrc-api) ผ่าน URL ที่ hardcode ต่างกัน 3 แบบ แม้ชี้ไป schema เดียวกันของ cu-central-api
ความเสี่ยงเชิงโครงสร้าง — ไม่มี circuit breaker/timeout ระหว่าง vtrc-api → cu-central-api (ดู Volume 3 · vtrc-api/04-centralize-bridge) — ถ้า cu-central-api ล่ม ผลกระทบไหลขึ้นไปกระทบ vtrc-api และทุก platform group ที่พึ่งพาโดยตรงพร้อมกัน แม้จะมี service ใหม่แยกออกไปมากแล้วก็ตาม — นี่คือเหตุผลที่ Volume 2 § 2.2.2 เรียก 2 service นี้ว่า "hub" ไม่ใช่ "legacy ที่กำลังถูกทดแทน"
Pattern 6 — สองยุคสถาปัตยกรรมซ้อนกันในกลุ่มเดียว (ไม่จำกัดแค่ 2way Platform)
คำนิยาม — บาง platform group มี service คนละยุค (legacy Express/Sequelize ปนกับ NestJS ใหม่) ทำงานคู่กันจริง โดยชื่อ/เอกสารเดิมทำให้ดูเหมือนเป็น stack เดียว
หลักฐาน — 2way Platform คือตัวอย่างชัดสุด: 2way-vtrc-api (Express legacy) เป็น backend หลักที่ frontend ฝั่งพนักงานเรียกจริง ส่วน 2way-api (NestJS) จัดการเฉพาะบาง feature — Volume 5 ระบุว่าแพลตฟอร์มนี้ "ไม่ได้เป็น NestJS ล้วนตามที่ตารางเดิมระบุ" Meeting Platform ก็มี pattern คล้ายกันในระดับเบาลง — meeting-vtrc-api เป็น Express + Sequelize (ไม่ใช่ NestJS) ในขณะที่ frontend ทั้งสองตัวเป็นคนละ framework กัน (Next.js vs CRA)
นัยเชิงสถาปัตยกรรม — เมื่ออ่าน stack summary ระดับ platform-group แบบผิวเผิน (เช่นตารางเดิมใน docs/index.md) มีความเสี่ยงสูงที่จะสรุปผิดว่าทั้งกลุ่มเป็น stack เดียว engineer ที่จะแก้ไข/migrate ต้องเปิดดู stack จริงของแต่ละ repo ก่อนวางแผนเสมอ — Volume 4-9 ของคู่มือนี้ระบุ "Stack จริง" แยกไว้ต่อ repo ด้วยเหตุผลนี้โดยเฉพาะ
ย้อนกลับไป Volume 2 · Architecture & Topology