เอกสารอ้างอิงสำหรับแอปพลิเคชัน นี่เป็นสถาปัตยกรรมเชิงตัวอย่างและรายการตรวจสอบขอบเขตงาน ไม่ใช่โครงการของลูกค้าที่ผ่านการตรวจสอบหรือการติดตั้งใช้งานที่มีการวัดผลจริง เทคโนโลยี ขอบเขตการผสานระบบ และรายการส่งมอบจะตกลงกันตามโจทย์จริง
สถานการณ์และข้อจำกัด
ผลิตภัณฑ์บริการที่ iOS และ Android ใช้เวิร์กโฟลว์เดียวกัน และทีมขนาดเล็กต้องดูแลทั้งสองแพลตฟอร์ม ข้อจำกัดสามประการเป็นตัวกำหนดการออกแบบ ได้แก่ เครือข่ายมือถือที่ไม่เสถียร รอบการตรวจสอบของแอปสโตร์ที่ทำให้การออกแพตช์เร่งด่วนล่าช้า และความจำเป็นในการเปลี่ยนกฎทางธุรกิจโดยไม่ต้องส่งบิลด์ใหม่
สถาปัตยกรรม
เลเยอร์และความรับผิดชอบ
| เลเยอร์ | เทคโนโลยี | ความรับผิดชอบ |
|---|---|---|
| แอป | Flutter (Dart) แบ่งเป็น UI / state / repository | หน้าจอและการโต้ตอบ เลเยอร์ repository เป็นส่วนเดียวที่รู้ว่าข้อมูลมาจากเครือข่ายหรือแคชภายในเครื่อง ซึ่งช่วยแยกการรองรับการทำงานออฟไลน์ออกจากโค้ด UI |
| พื้นที่จัดเก็บภายในเครื่อง | SQLite (drift) + secure storage ของแพลตฟอร์ม | เอนทิตีที่แคชไว้ outbox สำหรับการเขียนข้อมูลที่รอดำเนินการ และโทเค็นใน secure storage แทน shared preferences |
| API | Go + Gin พร้อมเส้นทางแบบมีเวอร์ชัน | กฎทางธุรกิจ การตรวจสอบความถูกต้อง และการอนุญาต กำหนดเวอร์ชันตั้งแต่วันแรก เพราะบิลด์แอปเก่ายังคงอยู่ในอุปกรณ์ของผู้ใช้เป็นเวลานานหลังการเผยแพร่ |
| พื้นที่จัดเก็บข้อมูล | PostgreSQL | ข้อมูลธุรกรรม ใช้ JSONB สำหรับแอตทริบิวต์ที่เปลี่ยนแปลงได้จริง แทนการเพิ่มคอลัมน์ในตารางอย่างต่อเนื่อง |
| แคช / คิว | Redis | เซสชัน การจำกัดอัตราการเรียกใช้ คีย์ idempotency และคิวงานเบื้องหลัง |
| การแจ้งเตือนแบบพุช | FCM และ APNs ผ่านบริการแจ้งเตือนเดียว | API ภายในชุดเดียวสำหรับการแจ้งเตือน โดยจัดการความแตกต่างระหว่างแพลตฟอร์มที่ฝั่งเซิร์ฟเวอร์ ไม่ใช่ในแอป |
| ผู้ดูแลระบบ | แผงควบคุมผู้ดูแลระบบ Vue | การจัดการผู้ใช้ เนื้อหา และคำสั่งซื้อ รวมถึงค่าการกำหนดค่าจากระยะไกลที่ใช้เปลี่ยนพฤติกรรมของแอปโดยไม่ต้องออกเวอร์ชันใหม่ |
โมเดลข้อมูล
ตารางด้านล่างรองรับกฎที่ทุกส่วนอื่นต้องพึ่งพา การออกแบบส่วนนี้ผิดพลาดคือสิ่งที่เปลี่ยนเดโมที่ทำงานได้ให้กลายเป็นระบบที่ไม่สามารถต่อยอดได้
เอนทิตีหลัก
| เอนทิตี | ฟิลด์หลัก ความสัมพันธ์ และกฎ |
|---|---|
| user / auth_identity | ผู้ใช้หนึ่งรายสามารถมีข้อมูลระบุตัวตนได้หลายรายการ (โทรศัพท์, Apple, Google, WeChat) การแยกข้อมูลระบุตัวตนออกจากผู้ใช้ทำให้การเพิ่มวิธีเข้าสู่ระบบในภายหลังไม่ต้องย้ายข้อมูล |
| device | โทเค็นพุช แพลตฟอร์ม เวอร์ชันแอป และภาษาท้องถิ่นของแต่ละอุปกรณ์ การกำหนดเป้าหมายการแจ้งเตือนและการทยอยเปิดใช้งานจะอ่านข้อมูลจากส่วนนี้ |
| order / booking | เอนทิตีของโดเมนที่มีคอลัมน์สถานะอย่างชัดเจนและบันทึกการเปลี่ยนสถานะ เพื่อให้สามารถตอบได้ว่า “สถานะนี้เกิดขึ้นได้อย่างไร” เมื่อฝ่ายสนับสนุนต้องตรวจสอบ |
| outbox (ฝั่งไคลเอ็นต์) | รายการเขียนข้อมูลภายในเครื่องที่เข้าคิวไว้พร้อม UUID ที่ไคลเอ็นต์สร้างขึ้น UUID ดังกล่าวจะกลายเป็นคีย์ idempotency บนเซิร์ฟเวอร์ ทำให้การลองใหม่หลังหมดเวลาปลอดภัย |
| sync_cursor | จุดอ้างอิง updated_at แยกตามผู้ใช้สำหรับการซิงก์แบบเพิ่มทีละส่วน ทำให้แอปดึงเฉพาะข้อมูลที่เปลี่ยนแปลงแทนการดึงคอลเลกชันทั้งหมดซ้ำ |
| remote_config | การกำหนดค่าแบบคีย์-ค่าแยกตามเวอร์ชันแอปและแพลตฟอร์ม ช่วยให้เปลี่ยนกฎและแฟล็กฟีเจอร์ได้โดยไม่ต้องส่งแอปเข้าสู่แอปสโตร์ |
อินเทอร์เฟซหลัก
เอนด์พอยต์ที่รองรับกฎทางธุรกิจ
| เอนด์พอยต์ | วัตถุประสงค์และข้อสังเกตด้านการทำงานพร้อมกัน |
|---|---|
| POST /api/v1/auth/token | ออก access token อายุสั้นพร้อม refresh token แบบหมุนเวียน การหมุนเวียน refresh token ช่วยจำกัดความเสียหายจากโทเค็นที่ถูกขโมยบนอุปกรณ์ที่ไม่สามารถเข้าถึงได้ |
| GET /api/v1/sync?since= | ดึงข้อมูลแบบเพิ่มทีละส่วนของระเบียนที่เปลี่ยนแปลงหลังเคอร์เซอร์ โดยแบ่งหน้าและจำกัดขนาด เพื่อไม่ให้ผู้ใช้ที่กลับมาใช้งานหลังออฟไลน์หลายสัปดาห์ดึงการตอบกลับที่ไม่มีขอบเขต |
| POST /api/v1/orders | รับ UUID ของไคลเอ็นต์เป็นคีย์ idempotency การลองใหม่หลังเครือข่ายหมดเวลาจะส่งคืนคำสั่งซื้อเดิมแทนการสร้างรายการซ้ำ |
| POST /api/v1/devices | ลงทะเบียนหรือรีเฟรชโทเค็นพุช โดยผูกกับผู้ใช้และเวอร์ชันแอปเพื่อรองรับการแจ้งเตือนแบบกำหนดเป้าหมายและทยอยเปิดใช้งาน |
| GET /api/v1/config | ส่งคืนการกำหนดค่าจากระยะไกลสำหรับเวอร์ชันแอปที่ร้องขอ ซึ่งเป็นกลไกที่ช่วยหลีกเลี่ยงการออกบิลด์ใหม่สำหรับการเปลี่ยนกฎ |
การตัดสินใจทางเทคนิค
- เลือก Flutter แทนโค้ดเนทีฟสองชุด เวิร์กโฟลว์เหมือนกันและทีมมีขนาดเล็ก ดังนั้นโค้ดแอปที่ใช้ร่วมกันอาจช่วยลดงานบำรุงรักษาที่ซ้ำซ้อนในส่วนที่เวิร์กโฟลว์ทับซ้อนกัน อย่างไรก็ตาม การผสานรวมกับแพลตฟอร์ม การตรวจสอบอุปกรณ์ และการเผยแพร่ยังคงต้องทำแยกกัน ในส่วนที่แพลตฟอร์มแตกต่างกันจริง เช่น การชำระเงิน พุช และไบโอเมตริก ความแตกต่างจะถูกแยกไว้หลัง platform channel แทนที่จะปล่อยให้รั่วไหลเข้าสู่โค้ดที่ใช้ร่วมกัน
- รองรับการทำงานเมื่อออฟไลน์ ไม่ใช่ออฟไลน์เป็นหลัก การอ่านข้อมูลจะใช้แคชและการเขียนข้อมูลจะเข้าคิวใน outbox แต่เซิร์ฟเวอร์ยังคงเป็นแหล่งข้อมูลหลัก การทำออฟไลน์เป็นหลักเต็มรูปแบบพร้อมการแก้ไขข้อขัดแย้งมีต้นทุนสูงกว่ามาก และคุ้มค่าเฉพาะเมื่อผู้ใช้ต้องทำงานโดยไม่มีการเชื่อมต่อเป็นเวลานานจริง ๆ
- ใช้ UUID ที่ไคลเอ็นต์สร้างเป็นคีย์ idempotency เครือข่ายมือถือมักหมดเวลาระหว่างคำขอ หากไม่มีสิ่งนี้ การลองใหม่หลังหมดเวลาอาจทำให้เกิดความเสี่ยงในการสร้างคำสั่งซื้อซ้ำ ซึ่งต้องผ่านการทดสอบ
- กำหนดเวอร์ชัน API ตั้งแต่การเผยแพร่ครั้งแรก ผู้ใช้มักเลื่อนการอัปเดต ดังนั้นเซิร์ฟเวอร์ต้องรองรับบิลด์เก่าต่อไป การเพิ่มการกำหนดเวอร์ชันย้อนหลังหลังเปิดใช้งานมีค่าใช้จ่ายสูงกว่าการเริ่มต้นด้วยแนวทางนี้มาก
- ใช้การกำหนดค่าจากระยะไกลสำหรับกฎทางธุรกิจ การเผยแพร่ไคลเอ็นต์ขึ้นอยู่กับการตรวจสอบของแพลตฟอร์มและการยอมรับของผู้ใช้ ค่าเกณฑ์ ข้อความ และแฟล็กฟีเจอร์ที่รองรับอาจจัดการจากฝั่งเซิร์ฟเวอร์ได้ในกรณีที่กฎของแพลตฟอร์มอนุญาต ส่วนการเปลี่ยนแปลงที่ต้องใช้บิลด์แอปใหม่ยังคงขึ้นอยู่กับกระบวนการดังกล่าว
เป้าหมายการออกแบบเชิงสมมติฐาน
ตัวเลขด้านล่างเป็นตัวอย่างการวางแผนความจุเชิงสมมติฐาน ไม่ใช่ผลลัพธ์ของลูกค้าที่วัดจริง คำแนะนำด้านการกำหนดขนาดที่ผ่านการตรวจสอบแล้ว หรือ SLA ควรกำหนดเป้าหมายการยอมรับเฉพาะโครงการหลังจากระบุเวิร์กโหลด อุปกรณ์ ข้อมูล ฮาร์ดแวร์ และการพึ่งพาบุคคลที่สามแล้ว และควรตรวจสอบผ่านโครงการนำร่องและการทดสอบโหลดก่อนผูกมัดกับเป้าหมายสำหรับการใช้งานจริง
เป้าหมายด้านความจุและประสิทธิภาพ
| เป้าหมาย | ค่าและสมมติฐาน |
|---|---|
| การเริ่มต้นแบบเย็นจนถึงหน้าจอแรกที่มีความหมาย | ต่ำกว่า 2 วินาทีบนอุปกรณ์ Android ระดับกลาง โดยสมมติว่าข้อมูลที่แคชไว้แสดงผลขณะที่การรีเฟรชเครือข่ายทำงานอยู่เบื้องหลัง |
| การซิงก์แบบเพิ่มทีละส่วน | เพย์โหลดส่วนต่างต่ำกว่า 200 KB สำหรับผู้ใช้ที่กลับมาใช้งานโดยทั่วไป โดยจำกัดขนาดหน้าเพื่อให้ผู้ใช้ที่หายไปเป็นเวลานานยังทำงานได้อย่างค่อยเป็นค่อยไปแทนที่จะหมดเวลา |
| เวลาแฝงของ API | p95 ต่ำกว่า 300 ms สำหรับการอ่านมาตรฐาน ไม่รวมการเรียกบุคคลที่สามซึ่งเราไม่สามารถควบคุมเวลาแฝงได้ |
| การส่งพุช | กำหนดและวัดเวลาตั้งแต่การเข้าคิวในแอปจนถึงการตอบรับจากผู้ให้บริการ เวลาส่งถึงปลายทางสุดท้ายขึ้นอยู่กับแพลตฟอร์มและไม่ได้รับประกันโดยเอกสารอ้างอิงนี้ |
สิ่งที่รวมอยู่ในการส่งมอบ
- ซอร์สโค้ดแอป Flutter ซอร์สโค้ดแบ็กเอนด์ Go และซอร์สโค้ดผู้ดูแลระบบ Vue ใน repository ของคุณ
- การกำหนดค่าการบิลด์และการลงนามสำหรับทั้งสองแพลตฟอร์ม รวมถึงการเตรียมข้อมูลสำหรับหน้ารายการในสโตร์
- เอกสาร API สคีมาฐานข้อมูล และประวัติการย้ายข้อมูล
- บันทึกการติดตั้งใช้งาน การมอนิเตอร์ และการเข้าถึงล็อก รวมถึงขั้นตอนการสำรองและกู้คืนข้อมูล
- การสนับสนุนการส่งแอปเข้าสู่ App Store และ Google Play ผลลัพธ์และระยะเวลาการตรวจสอบยังคงขึ้นอยู่กับ Apple และ Google
การวางแผนให้เหมาะกับธุรกิจของคุณ
โมดูลด้านบนเป็นจุดเริ่มต้น ไม่ใช่แพ็กเกจตายตัว โดยปกติเราจะลดขอบเขตการเผยแพร่ครั้งแรกให้เหลือเวิร์กโฟลว์หลักหนึ่งรายการและเวิร์กโฟลว์ผู้ดูแลระบบหนึ่งรายการ จากนั้นจึงขยายเมื่อการใช้งานจริงแสดงให้เห็นว่าภาระงานและกรณีขอบเขตอยู่ที่ใด ส่งรายละเอียดขอบเขตงาน แล้วเราจะนำไปเทียบกับสถาปัตยกรรมนี้และแจ้งว่าส่วนใดที่คุณยังไม่จำเป็นต้องใช้ในตอนนี้

