สคีมาฐานข้อมูลแผนและสิทธิ์สำหรับการอัปเกรดและแอดออน
สคีมาฐานข้อมูลสำหรับแผนและสิทธิ์ที่รองรับอัปเกรด แอดออน ทดลอง และการเพิกถอนโดยไม่ต้องฝังกฎในโค้ด โดยใช้ตารางและการตรวจสอบที่ชัดเจน

ทำไมแผนและฟีเจอร์ถึงยุ่งเหยิงได้อย่างรวดเร็ว
แผนดูเรียบง่ายในหน้าราคา: Basic, Pro, Enterprise แต่ความยุ่งเริ่มต้นตั้งแต่คุณพยายามเปลี่ยนชื่อนั้นให้เป็นกฎการเข้าถึงจริงในแอปของคุณ
การฝังเงื่อนไขเช็คฟีเจอร์ (เช่น if plan = Pro then allow X) ใช้ได้สำหรับเวอร์ชันแรก แต่แล้วราคาก็เปลี่ยน ฟีเจอร์ย้ายจาก Pro ไป Basic มีแอดออนใหม่ หรือดีลกับฝ่ายขายรวมแพ็กพิเศษ ทันใดนั้นคุณมีเงื่อนไขเดียวกันซ้ำใน API, UI, แอปมือถือ และงานพื้นหลัง คุณแก้ที่หนึ่งแล้วลืมอีกที่ ผู้ใช้สังเกตเห็น
ปัญหาอีกอย่างคือเวลา การสมัครไม่ได้เป็นป้ายคงที่; มันเปลี่ยนกลางรอบ ใครบางคนอัปเกรดวันนี้ ดาวน์เกรดเดือนหน้า หยุดพัก หรือยกเลิกทั้งที่ยังมีเวลาเสียเงินเหลืออยู่ ถ ั้หากฐานข้อมูลของคุณเก็บแค่ “แผนปัจจุบัน” คุณจะสูญเสียไทม์ไลน์และไม่สามารถตอบคำถามพื้นฐานได้ในภายหลัง: เมื่อวันอังคารที่แล้วพวกเขาเข้าถึงอะไรได้บ้าง? ทำไมฝ่ายสนับสนุนถึงอนุมัติการคืนเงิน?
แอดออนทำให้สถานการณ์แย่ขึ้นเพราะมันข้ามแผน แอดออนอาจปลดล็อกที่นั่งเพิ่ม ยกเลิกข้อจำกัด หรือเปิดฟีเจอร์เฉพาะ ผู้คนสามารถซื้อแอดออนได้บนแผนใดก็ได้ เอาออกทีหลัง หรือเก็บไว้หลังดาวน์เกรด ถ้าเงื่อนไขฝังในโค้ด คุณจะจบด้วยกองกรณีพิเศษที่เพิ่มขึ้นเรื่อยๆ
ต่อไปนี้คือสถานการณ์ที่มักจะทำให้การออกแบบแบบง่ายๆ พัง:
- อัปเกรดกลางรอบ: การเข้าถึงควรเปลี่ยนทันที แต่การคำนวณปรอทาอาจมีเงื่อนไขต่างกัน
- การดาวน์เกรดที่ตั้งเวลา: การเข้าถึงอาจยังคงสูงกว่าไปจนกว่าจะสิ้นสุดรอบที่ชำระแล้ว
- Grandfathering: ลูกค้าเก่าบางคนรักษาฟีเจอร์ที่ลูกค้าใหม่ไม่ได้รับ
- ดีลเฉพาะ: บัญชีหนึ่งได้ Feature A แต่ไม่ได้ Feature B ทั้งที่มีชื่อแผนเดียวกัน
- ความต้องการตรวจสอบ: ฝ่ายสนับสนุน การเงิน หรือคอมไพลแอนซ์ถามว่า “จริงๆ แล้วเปิดอะไรไว้และเมื่อไหร่?”
เป้าหมายชัดเจน: โมเดลการควบคุมการเข้าถึงที่ยืดหยุ่นซึ่งเปลี่ยนตามราคา โดยไม่ต้องเขียนตรรกะธุรกิจใหม่ทุกครั้ง คุณต้องการที่เดียวที่จะถามว่า 'พวกเขาทำได้ไหม?' และมีบันทึกในฐานข้อมูลที่อธิบายคำตอบ
เมื่อจบบทความนี้ คุณจะได้รูปแบบสคีมาที่คัดลอกได้: แผนและแอดออนกลายเป็นข้อมูลนำเข้า และ entitlements กลายเป็นแหล่งเดียวของความจริงสำหรับการเข้าถึงฟีเจอร์ แนวทางเดียวกันนี้ยังเหมาะกับตัวสร้างแบบ no-code อย่าง AppMaster เพราะคุณเก็บกฎไว้ในข้อมูลและสามารถคิวรีจาก backend, เว็บ และแอปมือถือได้อย่างสม่ำเสมอ
คำสำคัญ: แผน, แอดออน, สิทธิ์, และการเข้าถึง
ปัญหาการสมัครใช้งานหลายอย่างเริ่มจากปัญหาคำศัพท์ ถ้าทุกคนใช้คำเดียวกันหมายถึงสิ่งต่างกัน สคีมาของคุณจะกลายเป็นกรณีพิเศษ
ต่อไปนี้เป็นคำที่ควรแยกกันชัดในสคีมาฐานข้อมูลแผนและสิทธิ์:
- Plan: แพ็กพื้นฐานที่ใครสักคนได้รับเมื่อสมัคร (เช่น Basic หรือ Pro) โดยปกติแผนจะกำหนดขีดจำกัดพื้นฐานและฟีเจอร์ที่รวมอยู่
- Add-on: การซื้อเลือกเสริมที่เปลี่ยนพื้นฐาน (เช่น 'ที่นั่งเพิ่ม' หรือ 'รายงานขั้นสูง') แอดออนควรแนบและเอาออกได้โดยไม่ต้องเปลี่ยนแผน
- Entitlement: ผลลัพธ์สุดท้ายที่คำนวณแล้วว่า 'สิ่งที่พวกเขามีตอนนี้' หลังจากรวมแผน + แอดออน + การยกเว้น นี่คือสิ่งที่แอปของคุณควรถาม
- Permission (หรือ capability): การกระทำเฉพาะที่คนทำได้ (เช่น 'export data' หรือ 'manage billing') สิทธิ์มักขึ้นกับบทบาทบวกกับ entitlements
- Access: ผลลัพธ์ในโลกจริงเมื่อแอปบังคับใช้กฎ (หน้าจอแสดงหรือซ่อนฟีเจอร์, เรียก API ได้หรือถูกบล็อก, ข้อจำกัดถูกนำไปใช้)
Feature flags เกี่ยวข้องแต่ต่างกัน โดยปกติ 'feature flag' เป็นสวิตช์ผลิตภัณฑ์ที่คุณควบคุม (rollouts, experiments, ปิดฟีเจอร์ระหว่างเหตุการณ์) ส่วน 'entitlement' เป็นการเข้าถึงเฉพาะลูกค้าตามที่เขาจ่ายหรือได้รับ ใช้ flags เมื่อต้องการเปลี่ยนพฤติกรรมสำหรับกลุ่มโดยไม่แตะการเรียกเก็บเงิน ใช้ entitlements เมื่อต้องการให้การเข้าถึงตรงกับการสมัคร ใบแจ้งหนี้ หรือสัญญา
ขอบเขตเป็นแหล่งความสับสนอีกประการหนึ่ง แยกแนวคิดเหล่านี้ให้ชัด:
- User: คนหนึ่งคน เหมาะกับบทบาท (admin vs member) และข้อจำกัดส่วนบุคคล
- Account (customer): หน่วยที่จ่ายเงิน เหมาะกับข้อมูลการเรียกเก็บเงินและความเป็นเจ้าของการสมัคร
- Workspace (project/team): ที่ที่งานเกิดขึ้น หลายผลิตภัณฑ์ใช้ entitlements ที่นี่ (ที่นั่ง, พื้นที่จัดเก็บ, โมดูลที่เปิดใช้)
เวลาเป็นสิ่งสำคัญเพราะการเข้าถึงเปลี่ยนแปลง ควรทำแบบจำลองโดยตรง:
- Start and end: Entitlement อาจมีผลเฉพาะในช่วงเวลาหนึ่ง (ทดลอง, โปรโมชั่น, สัญญารายปี)
- Scheduled change: การอัปเกรดอาจเริ่มตอนนี้; การดาวน์เกรดมักเริ่มที่การต่ออายุครั้งถัดไป
- Grace and cancelation: อาจอนุญาตการเข้าถึงจำกัดหลังการชำระเงินล้มเหลว แต่ต้องมีวันสิ้นสุดชัดเจน
ตัวอย่าง: บริษัทอยู่ในแผน Pro, เพิ่ม 'Advanced Reporting' กลางเดือน แล้วตั้งเวลาจะดาวน์เกรดเป็น Basic รอบถัดไป แผนเปลี่ยนทีหลัง แอดออนเริ่มตอนนี้ ชั้นสิทธิ์ (entitlement layer) ยังคงเป็นที่เดียวที่ถามว่า: 'วันนี้ workspace นี้ใช้รายงานขั้นสูงได้ไหม?'
สคีมาพื้นฐานสำหรับแผนและฟีเจอร์
สคีมาที่ดีเริ่มจากเล็ก: แยกสิ่งที่คุณขาย (แผนและแอดออน) ออกจากสิ่งที่คนทำได้ (ฟีเจอร์) ถ้ารักษาสองแนวคิดนี้ชัด การอัปเกรดและแอดออนใหม่กลายเป็นการเปลี่ยนข้อมูล ไม่ใช่การเขียนโค้ดใหม่
ชุดตารางหลักที่ใช้งานได้กับผลิตภัณฑ์สมัครส่วนใหญ่มีดังนี้:
- products: สิ่งที่ขายได้ (Base plan, Team plan, Extra seats add-on, Priority support add-on)
- plans: ทางเลือก ถ้าต้องการให้แผนเป็นชนิดพิเศษของ product ที่มีฟิลด์เฉพาะ (billing interval, public display order) หลายทีมเก็บแผนไว้ใน
productsแล้วใช้คอลัมน์product_type - features: แคตาล็อกความสามารถ (API access, max projects, export, SSO, SMS credits)
- product_features (หรือ
plan_featuresถ้าจะแยกแผน): ตารางเชื่อมที่ระบุว่าฟีเจอร์ใดมาพร้อมกับผลิตภัณฑ์ไหน โดยปกติจะมีค่าแนบด้วย
ตารางเชื่อมนี้คือที่ที่พลังส่วนใหญ่เกิดขึ้น ฟีเจอร์ไม่ค่อยเป็นแค่เปิด/ปิด แผนอาจประกอบด้วย max_projects = 10 ในขณะที่แอดออนอาจเพิ่ม +5 ดังนั้น product_features ควรรองรับอย่างน้อย:
feature_value(ตัวเลข ข้อความ JSON หรือคอลัมน์แยก)value_type(boolean, integer, enum, json)grant_mode(replace vs add) เพื่อให้แอดออนสามารถ 'เพิ่ม 5 ที่นั่ง' แทนการเขียนทับขีดจำกัดพื้นฐาน
จำลองแอดออนเป็น products ด้วย ความแตกต่างเพียงวิธีการซื้อ ผลิตภัณฑ์แผนพื้นฐานเป็น 'หนึ่งครั้งเท่านั้น' ขณะที่แอดออนอาจอนุญาตจำนวน (quantity) แต่ทั้งคู่แม็ปไปยังฟีเจอร์แบบเดียวกัน วิธีนี้หลีกเลี่ยงกรณีพิเศษเช่น 'ถ้าแอดออน X ให้เปิด Y' กระจัดกระจายอยู่ในโค้ด
ฟีเจอร์ควรเป็นข้อมูล ไม่ใช่ค่าคงที่ในโค้ด หากคุณฝังการเช็คฟีเจอร์ไว้ในบริการหลายที่ คุณจะเจอความไม่สอดคล้อง (เว็บบอกว่าใช่ มือถือบอกว่าไม่ Backend ต่างออกไป) เมื่อฟีเจอร์อยู่ในฐานข้อมูล แอปสามารถถามคำถามเดียวและคุณแก้ไขด้วยการแก้แถวข้อมูล
การตั้งชื่อสำคัญกว่าที่คิด ใช้ตัวระบุที่คงที่ซึ่งไม่เปลี่ยน แม้ชื่อการตลาดจะเปลี่ยน:
feature_keyเช่นmax_projects,sso,priority_supportproduct_codeเช่นplan_starter_monthly,addon_extra_seats
เก็บป้ายแสดงผลแยกไว้ (feature_name, product_name) ถ้าใช้ AppMaster Data Designer กับ PostgreSQL การปฏิบัติให้ feature_key และ product_code เป็นค่าเอกลักษณ์จะช่วยได้ทันที: คุณสามารถปรับโครงสร้างใหม่ได้อย่างปลอดภัยโดยยังคงความเสถียรของการผสานและการรายงาน
ชั้นสิทธิ์ (entitlement layer): ที่เดียวที่จะถามว่า 'พวกเขาทำได้ไหม?'
ระบบสมัครหลายระบบพังเมื่อ 'สิ่งที่พวกเขาซื้อ' ถูกเก็บในที่หนึ่ง แต่ 'สิ่งที่พวกเขาทำได้' ถูกคำนวณในหลายเส้นทางโค้ด การแก้คือชั้นสิทธิ์: ตารางเดียว (หรือ view) ที่แสดงการเข้าถึงที่มีผลสำหรับ subject ในช่วงเวลาใดช่วงเวลาหนึ่ง
ถ้าคุณต้องการสคีมาที่ทนต่ออัปเกรด ดาวน์เกรด ทดลอง และการให้สิทธิ์ครั้งเดียว ชั้นนี้คือส่วนที่ทำให้ทุกอย่างคาดเดาได้
ตาราง entitlements ที่ใช้งานได้จริง
คิดว่าทุกแถวเป็นคำกล่าวอ้างหนึ่งข้อ: 'subject นี้มีสิทธิ์ฟีเจอร์นี้ด้วยค่ นี้ ตั้งแต่เวลานี้ถึงเวลานั้น มาจากแหล่งนี้' รูปร่างทั่วไปมีดังนี้:
- subject_type (เช่น ‘account’, ‘user’, ‘org’) และ subject_id
- feature_id
- value (ค่าที่มีผลสำหรับฟีเจอร์นั้น)
- source (มาจากที่ไหน: ‘direct’, ‘plan’, ‘addon’, ‘default’)
- starts_at และ ends_at (ends_at เป็นค่า null ได้สำหรับการเข้าถึงต่อเนื่อง)
คุณสามารถเก็บค่า value ได้หลายแบบ: คอลัมน์ text/JSON เดียวบวก value_type หรือคอลัมน์แยกเช่น value_bool, value_int, value_text เก็บให้เรียบและคิวรีง่าย
ประเภทค่าที่ครอบคลุมผลิตภัณฑ์ส่วนใหญ่
ฟีเจอร์ไม่ได้เป็นแค่เปิด/ปิด ประเภทค่าต่อไปนี้ครอบคลุมความต้องการการเรียกเก็บเงินและการควบคุมการเข้าถึงส่วนใหญ่:
- Boolean: เปิด/ปิด (เช่น
can_export= true) - จำนวนแบบโควตา: ขีดจำกัด (เช่น
seats= 10,api_calls= 100000) - ระดับชั้น: ลำดับชั้น (เช่น
support_tier= 2) - สตริง: โหมดหรือรูปแบบ (เช่น
data_retention= '90_days')
ลำดับความสำคัญ: วิธีแก้ข้อขัดแย้ง
ข้อขัดแย้งเป็นเรื่องปกติ ผู้ใช้คนหนึ่งอาจอยู่ในแผนที่ให้ 5 ที่นั่ง ซื้อแอดออนเพิ่ม 10 ที่นั่ง และได้รับการมอบสิทธิ์ด้วยมือจากฝ่ายสนับสนุน
ตั้งกฎชัดเจนและยึดไว้ทุกที่:
- การให้สิทธิ์โดยตรงมีน้ำหนักมากกว่าแผน
- ถัดมาเป็นแอดออน
- แล้วค่าเริ่มต้น
วิธีง่ายคือเก็บแถวผู้สมัครทั้งหมด (ได้จากแผน, ได้จากแอดออน, ได้โดยตรง) แล้วคำนวณ 'ผู้ชนะ' สุดท้ายต่อ subject_id + feature_id โดยการเรียงตามลำดับแหล่งที่มา แล้วเรียงโดย starts_at ล่าสุด
นี่คือสถานการณ์ชัดเจน: ลูกค้าดาวน์เกรดวันนี้ แต่จ่ายแอดออนที่มีผลจนสิ้นเดือน ด้วย starts_at/ends_at บน entitlements การดาวน์เกรดมีผลทันทีสำหรับฟีเจอร์จากแผน ในขณะที่แอดออนยังคงมีผลจนถึง ends_at แอปของคุณสามารถตอบว่า 'พวกเขาทำได้ไหม?' ด้วยคิวรีเดียวแทนการเขียนกรณีพิเศษ
การสมัคร, รายการ และการเข้าถึงที่มีขอบเขตเวลา
แคตาล็อกแผนของคุณ (แผน แอดออน ฟีเจอร์) คือ 'สิ่งที่มีให้' การสมัครเป็น 'ใครมีอะไร และเมื่อไหร่' ถ้าคุณเก็บแยกกัน การอัปเกรดและการยกเลิกจะไม่เป็นเรื่องน่ากลัว
รูปแบบปฏิบัติ: หนึ่ง subscription ต่อ account และหลาย subscription_items ภายใต้มัน (หนึ่งสำหรับแผนพื้นฐาน บวกแอดออนหลายรายการ) ในสคีมานี้ คุณจะมีที่ที่สะอาดเพื่อบันทึกการเปลี่ยนแปลงตามเวลาโดยไม่ต้องเขียนทับกฎการเข้าถึง
ตารางหลักสำหรับการสร้างไทม์ไลน์การซื้อ
เก็บให้เรียบด้วยสองตารางที่คิวรีง่าย:
- subscriptions: id, account_id, status (active, trialing, canceled, past_due), started_at, current_period_start, current_period_end, canceled_at (nullable)
- subscription_items: id, subscription_id, item_type (plan, addon), plan_id/addon_id, quantity, started_at, ends_at (nullable), source (stripe, manual, promo)
รายละเอียดทั่วไป: เก็บแต่ละ item พร้อมวันที่ของมัน วิธีนี้คุณสามารถให้แอดออนแค่ 30 วัน หรือให้แผนดำเนินจนสิ้นรอบที่ชำระแล้วแม้ว่าลูกค้าจะตั้งค่าหยุดต่ออายุ
แยกการคำนวณปรอทาและการเรียกเก็บเงินออกจากตรรกะการเข้าถึง
ปรอทา ใบแจ้งหนี้ และการลองชำระเป็นปัญหาบัญชี การเข้าถึงเป็นปัญหา entitlements อย่าใช้ความพยายามคิดว่า 'คำนวณการเข้าถึง' จากบรรทัดใบแจ้งหนี้
ให้เหตุการณ์การเรียกเก็บเงินอัปเดตระเบียนการสมัคร (เช่น ขยาย current_period_end, สร้าง subscription_item ใหม่, หรือตั้ง ends_at) แล้วแอปของคุณตอบคำถามการเข้าถึงจากไทม์ไลน์การสมัคร (และต่อมา จากชั้น entitlement) แทนการคำนวณบิล
การตั้งการเปลี่ยนแปลงตามเวลาโดยไม่เกิดความประหลาดใจ
อัปเกรดและดาวน์เกรดมักมีผลในช่วงเวลาที่ชัดเจน:
- เพิ่ม
pending_plan_idและchange_atใน subscriptions สำหรับการเปลี่ยนแปลงแผนที่ตั้งค่าได้หนึ่งรายการ - หรือใช้ตาราง
subscription_changes(subscription_id, effective_at, from_plan_id, to_plan_id, reason) ถ้าต้องการเก็บประวัติและหลายการเปลี่ยนในอนาคต
วิธีนี้ป้องกันการฝังกฎแบบ 'ดาวน์เกรดเกิดขึ้นที่สิ้นรอบ' ในส่วนต่างๆ ของโค้ด ตารางข้อมูลทำหน้าที่เป็นแหล่งความจริง
การทดลอง (trials) อยู่ตรงไหน
Trial เป็นเพียงการเข้าถึงที่มีขอบเขตเวลาโดยมีแหล่งที่มาแตกต่างกัน สองทางเลือกที่ชัด:
- ถือ trial เป็นสถานะของ subscription (trialing) พร้อมกับ trial_start/trial_end
- หรือสร้าง subscription_items/entitlements สำหรับทดสอบโดยมี started_at/ends_at และ source = trial
ถ้าสร้างใน AppMaster ตารางเหล่านี้จับคู่ได้ดีใน Data Designer (PostgreSQL) และวันที่ทำให้คิวรี 'อะไรที่ active ตอนนี้' ง่ายโดยไม่ต้องกรณีพิเศษ
ขั้นตอนทีละขั้น: นำรูปแบบไปใช้งาน
สัญญาหนึ่งข้อของสคีมาที่ดีคือ: ตรรกะฟีเจอร์อยู่ในข้อมูล ไม่กระจัดกระจายทั่วโค้ด แอปของคุณควรถามคำถามเดียว — 'สิทธิ์ที่มีผลตอนนี้คืออะไร?' — แล้วได้คำตอบชัดเจน
1) กำหนดฟีเจอร์ด้วยคีย์ที่คงที่
สร้างตาราง feature ด้วยคีย์ที่อ่านง่ายและจะไม่เปลี่ยนชื่อ (แม้ป้าย UI จะเปลี่ยน) คีย์ดีๆ เช่น export_csv, api_calls_per_month, หรือ seats
เพิ่มชนิดเพื่อให้ระบบรู้ว่าจะจัดการค่าอย่างไร: boolean (เปิด/ปิด) vs numeric (ขีดจำกัด) เก็บให้เรียบและสอดคล้อง
2) แม็ปแผนและแอดออนไปยังสิทธิ์
ตอนนี้คุณต้องมีแหล่งความจริงสองแห่ง: สิ่งที่แผนรวม และสิ่งที่แอดออนให้
ลำดับปฏิบัติที่เรียบง่าย:
- ใส่ฟีเจอร์ทั้งหมดในตาราง
featureเดียวด้วยคีย์คงที่และชนิดค่าของมัน - สร้าง
planและplan_entitlementที่แต่ละแถวให้ค่าฟีเจอร์ (เช่นseats = 5,export_csv = true) - สร้าง
addonและaddon_entitlementที่ให้ค่าเพิ่ม (เช่นseats + 10,api_calls_per_month + 50000, หรือpriority_support = true) - ตัดสินใจวิธีรวมค่า: boolean มักใช้ OR, ขีดจำกัดเชิงตัวเลขมักใช้ MAX (ค่าสูงสุดชนะ), และจำนวนที่นั่งมักใช้ SUM
- บันทึกเมื่อ entitlements เริ่มและหมดอายุ เพื่อให้อัปเกรด ยกเลิก และปรอทาไม่ทำให้การเช็คการเข้าถึงพัง
ถ้าสร้างใน AppMaster คุณสามารถโมเดลตารางเหล่านี้ใน Data Designer และเก็บกฎการรวมเป็นตารางนโยบายเล็กๆ หรือ enum ที่ Business Process ใช้
3) ผลิต 'สิทธิ์ที่มีผล'
คุณมีสองตัวเลือก: คำนวณเมื่ออ่าน (query และรวมทุกครั้ง) หรือสร้าง snapshot เก็บแคชเมื่อมีการเปลี่ยนแปลง (แผนเปลี่ยน, ซื้อแอดออน, ต่ออายุ, ยกเลิก) สำหรับแอปส่วนใหญ่ snapshot ง่ายต่อการเข้าใจและเร็วภายใต้โหลด
แนวทางทั่วไปคือมีตาราง account_entitlement ที่เก็บผลลัพธ์สุดท้ายต่อฟีเจอร์ พร้อม valid_from และ valid_to
4) บังคับใช้การเข้าถึงด้วยการเช็คเดียว
อย่ากระจายกฎในหน้าจอ จุดสิ้นสุด และงานพื้นหลัง ใส่ฟังก์ชันเดียวในโค้ดแอปที่อ่านสิทธิ์ที่มีผลและตัดสินใจ
can(account_id, feature_key, needed_value=1):
ent = get_effective_entitlement(account_id, feature_key, now)
if ent.type == "bool": return ent.value == true
if ent.type == "number": return ent.value >= needed_value
เมื่อทุกส่วนเรียก can(...) การอัปเกรดและแอดออนกลายเป็นการอัปเดตข้อมูล ไม่ใช่การเขียนโค้ดใหม่
ตัวอย่างสถานการณ์: อัปเกรดบวกแอดออนโดยไม่มีความประหลาดใจ
ทีมซัพพอร์ต 6 คนอยู่ในแผน Starter ซึ่งรวมที่นั่ง 3 คนและ 1,000 SMS ต่อเดือน กลางเดือนพวกเขาขยายเป็น 6 คนและต้องการแพ็ก SMS เพิ่ม 5,000 ข้อความ คุณต้องการให้ระบบทำงานโดยไม่ต้องโค้ดกรณีพิเศษ เช่น 'ถ้าแผน = Pro แล้ว...'
วันที่ 1: เริ่มใน Starter
สร้าง subscription ให้บัญชีโดยมีรอบการเรียกเก็บ (เช่น 1 ม.ค. ถึง 31 ม.ค.) แล้วเพิ่ม subscription_item สำหรับแผนนั้น
ที่เช็คเอาต์ (หรือผ่านงานกลางคืน) เขียนการให้สิทธิ์สำหรับรอบนั้น:
entitlement_grant:agent_seats, ค่า3, เริ่ม1 ม.ค., สิ้นสุด31 ม.ค.entitlement_grant:sms_messages, ค่า1000, เริ่ม1 ม.ค., สิ้นสุด31 ม.ค.
แอปของคุณไม่ถามว่า 'พวกเขาอยู่แผนอะไร?' มันถามว่า 'สิทธิ์ที่มีผลตอนนี้คืออะไร?' และได้ผลลัพธ์ seats = 3, SMS = 1000
วันที่ 15: อัปเกรดเป็น Pro และเพิ่มแพ็ก SMS
วันที่ 15 ม.ค. พวกเขาอัปเกรดเป็น Pro (มีที่นั่ง 10 คนและ 2,000 SMS) คุณไม่แก้ไขการให้สิทธิ์เก่า แต่เพิ่มระเบียนใหม่:
- ปิดรายการแผนเก่า: ตั้ง
subscription_item(Starter) ให้สิ้นสุดที่15 ม.ค. - สร้างรายการแผนใหม่:
subscription_item(Pro) เริ่ม15 ม.ค., สิ้นสุด31 ม.ค. - เพิ่มรายการแอดออน:
subscription_item(SMS Pack 5000) เริ่ม15 ม.ค., สิ้นสุด31 ม.ค.
แล้วการให้สิทธิ์ในรอบเดียวกันจะถูกต่อท้าย:
entitlement_grant:agent_seats, ค่า10, เริ่ม15 ม.ค., สิ้นสุด31 ม.ค.entitlement_grant:sms_messages, ค่า2000, เริ่ม15 ม.ค., สิ้นสุด31 ม.ค.entitlement_grant:sms_messages, ค่า5000, เริ่ม15 ม.ค., สิ้นสุด31 ม.ค.
ผลทันทีในวันที่ 15:
- ที่นั่ง: ที่นั่งที่มีผลเป็น 10 (ถ้ากฎคือ 'เอาค่าสูงสุดสำหรับที่นั่ง') พวกเขาสามารถเพิ่มอีก 3 ตัวในวันนั้น
- SMS: ข้อความที่มีผลเป็น 7,000 สำหรับที่เหลือของรอบ (ถ้ากฎคือ 'รวมแพ็กข้อความแบบเพิ่ม')
ไม่ต้องย้ายการใช้งานเดิม ตารางการใช้งานยังคงนับข้อความที่ส่ง; การเช็คสิทธิ์เพียงเทียบการใช้งานในรอบกับขีดจำกัดที่มีผลตอนนี้
วันที่ 25: ตั้งเวลาดาวน์เกรด ให้เข้าถึงต่อจนสิ้นรอบ
วันที่ 25 ม.ค. พวกเขาตั้งเวลาจะดาวน์เกรดกลับเป็น Starter เริ่ม 1 ก.พ. คุณไม่แตะการให้สิทธิ์ของเดือนมกราคม คุณสร้างรายการในอนาคตสำหรับรอบถัดไป:
subscription_item(Starter) เริ่ม1 ก.พ., สิ้นสุด28 ก.พ.- ไม่มีรายการแพ็ก SMS เริ่ม
1 ก.พ.
ผลลัพธ์: พวกเขายังคงใช้ที่นั่งแบบ Pro และแพ็ก SMS ถึง 31 ม.ค. ในวันที่ 1 ก.พ. ที่นั่งจะลดเป็น 3 และ SMS จะกลับสู่ขีดจำกัดของ Starter สำหรับรอบใหม่ นี่เป็นสิ่งที่คาดเดาได้และจับคู่กับเวิร์กโฟลว์ no-code ใน AppMaster ได้อย่างสะอาด: การเปลี่ยนวันที่สร้างแถวใหม่ และคิวรี entitlement ยังคงเหมือนเดิม
ข้อผิดพลาดและกับดักทั่วไป
บั๊กการสมัครส่วนใหญ่ไม่ใช่บั๊กบิล แต่เป็นบั๊กการเข้าถึงที่เกิดจากตรรกะกระจัดกระจาย จุดที่ทำให้สคีมาพังเร็วที่สุดคือการตอบ 'พวกเขาทำได้ไหม?' ในหลายที่
ความล้มเหลวคลาสสิกคือการฝังกฎใน UI, API, และงานพื้นหลังแยกกัน UI ซ่อนปุ่ม API ลืมบล็อก endpoint และงานกลางคืนยังทำงานเพราะมันเช็คสิ่งอื่น คุณจะได้รายงาน 'มันทำงานบางครั้ง' ที่แก้ไขยาก
กับดักอีกอย่างคือการใช้ plan_id เช็คแทนการเช็คฟีเจอร์ มันดูเรียบง่าย แต่จะพังเมื่อคุณเพิ่มแอดออน ลูกค้าที่ได้รับสิทธิ์ก่อนหน้านี้ (grandfathered), ทดลองฟรี, หรือข้อยกเว้นขององค์กร ถ้าคุณเคยพูดว่า 'if plan is Pro then allow...' คุณกำลังก่อเขาวงกตที่จะต้องดูแลตลอดไป
ขอบเขตเวลาและการยกเลิกที่ยุ่ง
การเข้าถึงติดอยู่เมื่อคุณเก็บแค่ boolean เช่น has_export = true โดยไม่แนบวันที่ การยกเลิก การคืนเงิน และการดาวน์เกรดกลางรอบต้องการช่วงเวลา หากไม่มี starts_at และ ends_at คุณอาจให้การเข้าถึงถาวรโดยไม่ได้ตั้งใจ หรือเอาการเข้าถึงออกเร็วเกินไป
ข้อควรเช็คง่ายๆ เพื่อหลีกเลี่ยง:
- ทุกการให้สิทธิ์ต้องมีแหล่งที่มา (plan, add-on, manual override) และช่วงเวลา
- ทุกการตัดสินใจการเข้าถึงควรใช้ 'ตอนนี้อยู่ระหว่าง start และ end' (มีข้อกำหนดชัดเจนสำหรับ end ที่เป็น null)
- งานพื้นหลังควรตรวจสอบ entitlements ตอนรันไทม์ ไม่สมมติสถานะของเมื่อวาน
อย่าปนการเรียกเก็บเงินกับการเข้าถึง
ทีมมักมีปัญหาเมื่อผสมเรคคอร์ดการเรียกเก็บเงินกับกฎการเข้าถึงในตารางเดียว การเรียกเก็บเงินต้องการใบแจ้งหนี้ ภาษี การคำนวณปรอทา ไอดีผู้ให้บริการ และสถานะการลองชำระ ขณะที่การเข้าถึงต้องการคีย์ฟีเจอร์ชัดเจนและช่วงเวลา เมื่อทั้งสองพันกัน การย้ายนโยบายบัญชีอาจกลายเป็นการล่มของผลิตภัณฑ์
สุดท้าย หลายระบบข้ามประวัติการตรวจสอบ เมื่อผู้ใช้ถามว่า 'ทำไมฉันส่งออกได้?' คุณต้องตอบได้ว่า: 'เปิดโดย Add-on X ตั้งแต่ 2026-01-01 ถึง 2026-02-01' หรือ 'ให้โดยฝ่ายสนับสนุน ตั๋ว 1842' หากไม่มีสิ่งนั้น ฝ่ายสนับสนุนและวิศวกรจะต้องเดา
ถ้าสร้างใน AppMaster เก็บฟิลด์ตรวจสอบใน Data Designer ของคุณ และทำให้การเช็ค 'พวกเขาทำได้ไหม?' เป็น Business Process เดียวที่เว็บ มือถือ และงานที่ตั้งเวลาทุกตัวใช้
เช็คลิสต์ด่วนก่อนปล่อย
ก่อนปล่อยสคีมาฐานข้อมูลแผนและสิทธิ์ ให้ตรวจทานด้วยคำถามจริง ไม่ใช่ทฤษฎี เป้าหมายคือการเข้าถึงต้องอธิบายได้ ทดสอบได้ และเปลี่ยนแปลงง่าย
คำถามตรวจสอบความจริง
เลือกผู้ใช้หนึ่งคนและฟีเจอร์หนึ่งตัว แล้วลองอธิบายผลลัพธ์เหมือนที่อธิบายให้ฝ่ายสนับสนุนหรือการเงิน ถ้าคุณตอบได้แค่ 'พวกเขาอยู่ใน Pro' (หรือแย่กว่านั้น 'โค้ดบอกอย่างนั้น') คุณจะเจอปัญหาเมื่อลูกค้าอัปเกรดกลางรอบหรือได้ดีลพิเศษ
ใช้เช็คลิสต์นี้:
- คุณตอบได้ไหมว่า 'ทำไมผู้ใช้คนนี้ถึงมีการเข้าถึง?' โดยดูจากข้อมูลเพียงอย่างเดียว (subscription items, add-ons, overrides, และช่วงเวลา) โดยไม่ต้องอ่านโค้ด
- การเช็คการเข้าถึงทั้งหมดอิงคีย์ฟีเจอร์ที่คงที่ (เช่น
feature.export_csv) แทนชื่อแผน (เช่น 'Starter' หรือ 'Business') ชื่อแผนอาจเปลี่ยน; คีย์ฟีเจอร์ไม่ควรเปลี่ยน - Entitlements มีเวลาเริ่มและสิ้นสุดชัดเจน รวมทดลอง ช่วงเวลาผ่อนปรน และการยกเลิกที่ตั้งเวลา ถ้าขาดเวลา การดาวน์เกรดจะกลายเป็นข้อถกเถียง
- คุณให้หรือยกเลิกการเข้าถึงสำหรับลูกค้าหนึ่งรายด้วยระเบียน override ได้ไหม โดยไม่ต้องเปลี่ยนตรรกะ นี่คือวิธีจัดการ 'ให้ที่นั่งเพิ่ม 10 ตัวในเดือนนี้' โดยไม่ต้องโค้ดเฉพาะ
- คุณทดสอบการอัปเกรดและดาวน์เกรดด้วยตัวอย่างเพียงไม่กี่แถวแล้วได้ผลลัพธ์ที่คาดเดาได้หรือไม่ ถ้าต้องใช้สคริปต์ซับซ้อนเพื่อจำลอง แสดงว่าสมรรถภาพของโมเดลคุณคลุมเครือเกินไป
การทดสอบปฏิบัติ: สร้างผู้ใช้สามคน (ใหม่, อัปเกรดกลางเดือน, ยกเลิก) และแอดออนหนึ่งตัว (เช่น 'ที่นั่งเพิ่ม' หรือ 'รายงานขั้นสูง') แล้วรันคิวรีการเข้าถึงสำหรับแต่ละคน ถ้าผลลัพธ์ชัดเจนและอธิบายได้ คุณพร้อมแล้ว
ถ้าคุณใช้เครื่องมืออย่าง AppMaster ให้ยึดกฎเดียวกัน: ทำให้มีที่เดียว (คิวรีหรือ Business Process เดียว) รับผิดชอบคำถาม 'พวกเขาทำได้ไหม?' ให้ทุกหน้าจอเว็บและมือถือใช้คำตอบเดียวกัน
ขั้นตอนถัดไป: ทำให้อัปเกรดดูแลได้ง่าย
วิธีที่ดีที่สุดในการทำให้อัปเกรดไม่ยุ่งคือเริ่มเล็กกว่าที่คิด เลือกฟีเจอร์ที่ขับเคลื่อนมูลค่าจริงไม่กี่ตัว (5-10 ก็พอ) แล้วสร้างการเช็คสิทธิ์หนึ่งตัวที่ตอบคำถามเดียว: 'บัญชีนี้ทำ X ได้ตอนนี้ไหม?' ถ้าคุณตอบไม่ได้ในที่เดียว การอัปเกรดจะเป็นเรื่องเสี่ยงเสมอ
เมื่อการเช็คเดียวทำงาน ให้มองเส้นทางอัปเกรดเป็นพฤติกรรมผลิตภัณฑ์ ไม่ใช่แค่พฤติกรรมการเรียกเก็บเงิน วิธีที่เร็วที่สุดจะจับกรณีขอบได้คือเขียนชุดทดสอบการเข้าถึงเล็กๆ ตามการเคลื่อนไหวของลูกค้าจริง
ขั้นตอนปฏิบัติที่ให้ผลทันที:
- กำหนดแคตาล็อกฟีเจอร์ขั้นต่ำและแม็ปแต่ละแผนให้เป็นชุด entitlement ชัดเจน
- เพิ่มแอดออนเป็น 'รายการ' แยกต่างหากที่ให้หรือขยาย entitlements แทนการฝังไว้ในกฎแผน
- เขียน 5-10 การทดสอบการเข้าถึงสำหรับเส้นทางทั่วไป (อัปเกรดกลางรอบ, ดาวน์เกรดเมื่อต่ออายุ, เพิ่มแล้วเอาแอดออนออก, ทดลองไปจ่ายเงิน, ช่วงผ่อนปรน)
- ทำให้การเปลี่ยนแปลงราคาเป็นเรื่องข้อมูล: อัปเดตแถวแผน การแม็ปฟีเจอร์ และการให้สิทธิ์ ไม่ใช่โค้ดแอป
- ตั้งนิสัย: ทุกแผนหรือแอดออนใหม่ต้องมาพร้อมอย่างน้อยหนึ่งการทดสอบที่พิสูจน์ว่าการเข้าถึงทำงานตามคาด
ถ้าคุณใช้ backend แบบ no-code คุณยังสามารถโมเดลรูปแบบนี้ได้สะอาด ใน AppMaster Data Designer เหมาะสำหรับสร้างตารางหลัก (plans, features, subscriptions, subscription items, entitlements) แล้ว Business Process Editor เก็บโฟลว์การตัดสินใจการเข้าถึง (โหลด entitlements ที่ active, ใช้ช่วงเวลา, คืนค่า allow/deny) ทำให้คุณไม่ต้องเขียนตรรกะกระจัดกระจายใน endpoint
ผลตอบแทนจะปรากฏเมื่อมีการเปลี่ยนแปลงราคา แทนที่จะเขียนกฎใหม่ คุณแก้ข้อมูล: ฟีเจอร์ย้ายจาก 'Pro' เป็นแอดออน ระยะเวลาสิทธิ์เปลี่ยน หรือแผนเก่ารักษาสิทธิ์เดิม ตรรกะการเข้าถึงของคุณคงที่ และการอัปเกรดเป็นการอัปเดตที่ควบคุมได้ ไม่ใช่ sprint โค้ด
ถ้าต้องการตรวจสอบสคีมาอย่างรวดเร็ว ลองโมเดลการอัปเกรดหนึ่งครั้งบวกแอดออนหนึ่งชิ้นตั้งแต่ต้นจนจบ แล้วรันการทดสอบการเข้าถึงก่อนเพิ่มอย่างอื่น


