คู่มือพิมพ์เขียวเชิงลึกและการวิเคราะห์ทางเทคนิค: สถาปัตยกรรมภายใน, 3 บทบาทหลัก, Port Unification, Docker, Kubernetes, Testcontainers และแบบฝึกหัดปฏิบัติการ 6 Drills
ชื่อบท/หัวข้อหลัก: Module 1: Architecture, Core Roles (Mock, Proxy, Verify), Port Unification & Enterprise Deployment (Docker, Testcontainers, K8s)
ตอบ: ในการสร้างระบบซอฟต์แวร์ระดับองค์กร ปัญหาใหญ่ที่สุดคือแอปพลิเคชันของเราต้องพึ่งพาระบบภายนอกที่ไม่เสถียร (Unstable External Dependencies) บทนี้ตั้งชื่อว่า Architecture, Core Roles & Enterprise Deployment เพราะเราจำเป็นต้องเข้าใจสถาปัตยกรรมภายใน รู้จัก 3 บทบาทหลัก (Mock, Proxy, Verify) และเรียนรู้วิธีการติดตั้งรันในองค์กร (Docker/K8s/Testcontainers) เพื่อ "ตัดขาดความเสี่ยง" จากระบบภายนอก ทำให้ทีมพัฒนาสามารถเขียนและทดสอบซอฟต์แวร์ได้เสถียร รวดเร็ว และเป็น Deterministic 100% โดยไม่ต้องง้อใคร
ระบบ E-Commerce ตกเป็นตัวประกันของเซิร์ฟเวอร์ Staging ในคืนวันมหกรรมลดราคา
ทีมพัฒนาแอปพลิเคชันช้อปปิ้งออนไลน์กำลังเตรียมเปิดตัวระบบชำระเงินใหม่ก่อนเทศกาล 11.11 แต่ในสภาพแวดล้อม Staging เซิร์ฟเวอร์ชำระเงินของธนาคารร่วมเปิดทดสอบล่มตลอดเวลา เนื่องจากมีทีมอื่นยิง Load Test ขนานกัน ผลคือ Pipeline CI/CD ของทีมล้มเหลว (Build Failed) ทุกครั้ง และนักพัฒนาไม่สามารถรัน Integration Test บนเครื่องตนเองได้เลย
นอกจากนี้ เมื่อพยายามรัน Docker บนเครื่อง Local พอร์ตการเชื่อมต่อของบริการต่างๆ ก็ชนกันมั่วไปหมด ส่งผลให้งานล่าช้ากว่ากำหนดไป 3 สัปดาห์
💡 การนำ MockServer สถาปัตยกรรมระดับองค์กรเข้ามาแก้ปัญหา: ทีมงานเปลี่ยนวิธีคิด โดยติดตั้ง MockServer ผ่าน Testcontainers ลงในชุดซอร์สโค้ดการทดสอบโดยตรง และรันแบบ Centralized Container บน Kubernetes Cluster สำหรับทีมพัฒนา ทำให้ระบบทดสอบไม่ต้องวิ่งออกไปหาเซิร์ฟเวอร์ธนาคารจริงใน Staging อีกต่อไป แต่ยิงเข้าหา MockServer ที่ทำหน้าที่ตอบกลับข้อมูลจำลองได้อย่างรวดเร็วและแน่นอน (Deterministic)
แปลตรงตัว: การจำลองการตอบกลับ (Response)
อุปมาอุปไมย: เหมือน "ดาราตัวแสดงแทน (Stunt Double)" ที่ยืนรอรับบทแทนตัวจริง เมื่อแอปพลิเคชันถามอะไรมา ตัวแสดงแทนจะพูดบทตามสคริปต์ (Expectation) ที่เตรียมไว้ทันที
แปลตรงตัว: ตัวกลางส่งผ่านข้อมูล
อุปมาอุปไมย: เหมือน "ด่านตรวจศุลกากร" ที่ยืนคั่นกลางระหว่างแอปพลิเคชันของเรากับเซิร์ฟเวอร์จริง คอยส่องดูบันทึกทราฟฟิก หรือส่งต่อ Request ไปยังจุดหมายปลายทาง
แปลตรงตัว: การตรวจสอบการเรียกใช้งาน
อุปมาอุปไมย: เหมือน "ผู้ตรวจการแผ่นดิน" ที่มาเปิดดูป้ายบันทึกหลังจบงาน เพื่อตรวจสอบว่าแอปพลิเคชันของเราได้ส่ง Request ออกไปถูกต้องตรงตามสัญญาหรือไม่
แปลตรงตัว: การรวมพอร์ตการเชื่อมต่อ
อุปมาอุปไมย: เหมือน "พนักงานโอเปอเรเตอร์ต้อนรับ" ที่นั่งอยู่หน้าประตูเดียว (พอร์ต 1080) ไม่ว่าแขกที่เดินเข้ามาจะพูดภาษา HTTP/1.1, HTTP/2, HTTPS หรือ SOCKS พนักงานคนนี้จะฟังออกและจัดการเปลี่ยนโหมดต้อนรับให้อัตโนมัติ
การผูกติดระบบทดสอบเข้ากับ External Shared Environment (Staging) ทำให้เกิดภาวะ Fragile Test คือการทดสอบตกเพราะระบบคนอื่นล่ม ไม่ใช่เพราะโค้ดของเรามี Bug
สูญเสียค่าใช้จ่ายจากการที่พัฒนาซอฟต์แวร์ล่าช้า (Delayed Time-to-Market), ทีมงานเสียเวลาสืบหา Bug เทียม (False Positive Failure) และเสี่ยงต่อการหลุด Bug ไปยัง Production
docker-compose.yml)
version: '3.8'
services:
mockserver:
image: mockserver/mockserver:latest # [Line 5] ดึง Docker Image ล่าสุดของ MockServer
container_name: mockserver-lab # [Line 6] ตั้งชื่อ Container
ports:
- "1080:1080" # [Line 8] Map พอร์ต Host 1080 เข้ากับ Container 1080 (Port Unification)
environment:
MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties # [Line 10] ชี้ไฟล์ Config
MOCKSERVER_LOG_LEVEL: INFO # [Line 11] กำหนดระดับการบันทึก Log
volumes:
- ./config:/config # [Line 13] Mount โฟลเดอร์ตั้งค่าจากเครื่อง Host
PUT /mockserver/expectation)
{
"httpRequest": { // [Section 1] กำหนดเงื่อนไขการตรวจจับ Request
"method": "GET", // ตรวจจับ HTTP Method ต้องเป็น GET เท่านั้น
"path": "/api/v1/health" // ตรวจจับ URL Path ต้องเป็น /api/v1/health
},
"httpResponse": { // [Section 2] กำหนดสิ่งที่จะตอบกลับ (Action)
"statusCode": 200, // ตอบกลับด้วย HTTP Status Code 200 OK
"headers": {
"Content-Type": ["application/json"] // ระบุ Header Response เป็น JSON
},
"body": "{\"status\": \"UP\"}" // เนื้อหาข้อมูล JSON Response Body
}
}
PaymentServiceTest.java)
@Testcontainers // [Line 1] สั่งเปิดการใช้งาน Testcontainers ใน JUnit 5
public class PaymentServiceTest {
@Container // [Line 4] สร้าง Container MockServer อัตโนมัติก่อนเริ่มรัน Test
public static MockServerContainer mockServer = new MockServerContainer(
DockerImageName.parse("mockserver/mockserver:latest")
);
private MockServerClient mockClient;
@BeforeEach
void setUp() {
// [Line 12] เชื่อมต่อ Client เข้ากับ Dynamic Host & Port ของ Testcontainers
mockClient = new MockServerClient(mockServer.getHost(), mockServer.getServerPort());
}
@Test
void testPaymentSuccess() {
// [Line 18] MOCK ROLE: ตั้งสคริปต์ Expectation ตอบกลับ SUCCESS
mockClient.when(
request().withMethod("POST").withPath("/api/v1/charge")
).respond(
response().withStatusCode(200).withBody("{\"status\":\"SUCCESS\"}")
);
// [Line 26] VERIFY ROLE: ยืนยันว่าแอปพลิเคชันยิง Request เข้ามาตรง 1 ครั้งจริง
mockClient.verify(
request().withMethod("POST").withPath("/api/v1/charge"),
VerificationTimes.exactly(1)
);
}
}
เมื่อรัน Container เรียบร้อย สามารถเปิดเว็บเบราว์เซอร์ไปที่: http://localhost:1080/mockserver/dashboard
========================================================================================
MOCKSERVER WEB DASHBOARD (PORT 1080)
========================================================================================
[ Active Expectations ] | [ Recorded Requests / Logs ] | [ Breakpoints ] | [ Reset All ]
----------------------------------------------------------------------------------------
Active Expectations Tab: ใช้ดูรายการ Mocks ที่กำลัง active อยู่ทั้งหมด
- สามารถกดดูรายละเอียด httpRequest และ httpResponse
- มีปุ่ม Delete สำหรับลบ Expectation ออกรายข้อได้ทันที
Recorded Requests & Logs Tab: แสดงประวัติทราฟฟิกจริงทุก Request ที่ยิงเข้าสู่ MockServer
- สามารถคลิกดู Headers, Path, และ Body ของแต่ละ Request ได้
- มีปุ่ม "Create Expectation" เพื่อสกัดดึง Log ทราฟฟิกจริงไปทำเป็น Mocks ได้ทันที
Reset Button (มุมขวาบน): ปุ่มสั่งส่งคำสั่ง PUT /mockserver/reset เพื่อล้าง Mocks และ Logs ทั้งหมดออกในคลิกเดียว
mockserver-module1-enterprise/
├── docker-compose.yml # ไฟล์ Container Orchestration
├── config/
│ └── mockserver.properties # ไฟล์ตั้งค่า Port, Log Level, CORS
├── expectations/
│ └── initializerJson.json # Expectation เริ่มต้นที่กางขึ้นมาทันทีเมื่อบูต
├── scripts/
│ ├── seed_expectations.sh # สคริปต์ยิง cURL โหลด Mocks
│ └── reset_mockserver.sh # สคริปต์สำหรับ Reset ค่า
└── tests/
└── PaymentServiceTest.java # Test Suite ฝั่ง Integration Test
พึ่งพา Staging เซิร์ฟเวอร์จริง ถ้านอกบ้านล่ม Test จะพังทันที:
PaymentResponse res = paymentClient.charge("https://staging-bank.com/api", 100);
assertEquals("SUCCESS", res.getStatus());
รันผ่าน Dynamic Port ของ Testcontainers ได้ผลแน่นอน 100%:
PaymentResponse res = paymentClient.charge(mockServer.getEndpoint() + "/api", 100);
assertEquals("SUCCESS", res.getStatus());
ก่อนเริ่มทำ ตรวจสอบว่าเปิด MockServer ไว้ที่ http://localhost:1080 เรียบร้อยแล้ว
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": "/api/v1/health" }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "{\"status\": \"UP\"}" }}'
Test & Expected: curl -i http://localhost:1080/api/v1/health (ได้ HTTP 200 OK และ body {"status": "UP"})
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": "/api/v1/products", "queryStringParameters": { "category": ["books"], "page": ["1"] } }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "[{\"id\": 101, \"title\": \"Clean Code\"}]" }}'
Test & Expected: curl -i "http://localhost:1080/api/v1/products?category=books&page=1" (ได้ 200 OK)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": "/api/v1/config", "headers": { "X-Client-Id": ["iOS-App"] } }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "{\"theme\": \"dark\"}" }}'
Test & Expected: curl -i http://localhost:1080/api/v1/config -H "X-Client-Id: iOS-App" (ได้ 200 OK)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v1/login", "body": { "type": "JSON", "json": "{\"username\": \"admin\", \"password\": \"secret123\"}", "matchType": "STRICT" } }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "{\"token\": \"session-xyz-999\"}" }}'
Test & Expected: curl -i -X POST http://localhost:1080/api/v1/login -H "Content-Type: application/json" -d '{"username": "admin", "password": "secret123"}' (ได้ HTTP 200 OK)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v1/cart/checkout" }, "httpResponse": { "statusCode": 409, "headers": { "Content-Type": ["application/json"] }, "body": "{\"errorCode\": \"OUT_OF_STOCK\"}" }}'
Test & Expected: curl -i -X POST http://localhost:1080/api/v1/cart/checkout (HTTP 409 Conflict)
Verify Call:
curl -X PUT "http://localhost:1080/mockserver/verify" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": "/api/v1/health" }, "times": { "atLeast": 1 }}'
Expected: HTTP 202 Accepted (พบประวัติการยิงตรงตามเงื่อนไขอย่างน้อย 1 ครั้ง)
คลิกเลือกคำตอบข้อที่ถูกต้อง ระบบจะตรวจให้คะแนนและแสดงผลเฉลยพร้อมคำอธิบายทันที!
คะแนนของคุณคือ ...
เนื้อหา สถาปัตยกรรม คำอธิบายโค้ด คู่มือ Dashboard และแบบฝึกหัด 6 Drills ทั้งหมดในวันนี้ ตอบและสะท้อนชื่อบท Day 1 / Module 1: Architecture, Core Roles & Enterprise Deployment อย่างชัดเจน: การที่เราเข้าใจ 3 บทบาทหลัก (Mock, Proxy, Verify) ร่วมกับ Port Unification ช่วยให้เราเลือกเครื่องมือมาตัดขาดจากระบบที่ไม่เสถียรได้ตรงจุด และการนำไปรันผ่าน Docker, Kubernetes หรือ Testcontainers ก็คือหัวใจของการนำไปปรับใช้จริงในระดับองค์กรนั่นเองครับ!