رفتن به محتوا
cloudhostمستندات
محصولات

فیچر فلگ‌ها

ویژگی‌ها را بدون استقرار مجدد روشن و خاموش کنید، برای درصدی از کاربران باز کنید یا برای گروهی هدف بگیرید. مقدار فلگ‌ها همراه پیکربندی به سرورها می‌رسد، پس خواندنشان تماس شبکه ندارد.

فلگ چیست#

هر فلگ به یک برنامه یا فانکشن تعلق دارد، یک کلید و یک نوع دارد: boolean، string، number یا json. برای هر محیط (پروداکشن، پیش‌نمایش، توسعه) تنظیمات جدا دارد، و هر شاخهٔ پیش‌نمایش می‌تواند تنظیمات خودش را داشته باشد.

تنظیمات یک محیط به ترتیب بررسی می‌شود: اگر فلگ قطع باشد مقدار «حالت قطع» (off_value) داده می‌شود؛ وگرنه اولین قانون هدف‌گیریِ مطابق؛ بعد انتشار تدریجی؛ و در پایان مقدار پیش‌فرض (value).

ایجاد و تغییر#

cloudhost flags add new-checkout --type boolean --description "New checkout flow"
cloudhost flags set new-checkout true --environment preview
cloudhost flags rule add new-checkout email ends_with @acme.ir --value true --environment production
cloudhost flags rollout new-checkout 10 --value true --environment production
cloudhost flags list

فلگ‌ها در داشبورد زیر «فلگ‌ها» در نوار کناری پروژه هستند: فهرست، و برای هر فلگ صفحه‌ای با محیط‌ها، قانون‌ها، لغزندهٔ انتشار، آزمایش و تاریخچه. ابزار MCP هم همین کارها را دارد (list_flags، set_flag، kill_flag و بقیه).

ثبت هر تغییر چند ثانیه بعد به همهٔ سرورها می‌رسد و فانکشن‌ها دوباره راه‌اندازی نمی‌شوند. تغییر محیط توسعه به سرور نمی‌رسد، چون چیزی از آن نمی‌خواند؛ با توکن فقط‌خواندنی همان محیط خوانده می‌شود.

خواندن در کد#

در هر فانکشن و برنامه، env.FLAGS هست. بدون توکن و بدون تماس شبکه از خود سرور می‌خواند. context ویژگی‌هایی است که قانون‌ها می‌سنجند؛ مقدار key (یا ویژگی‌ای که برای انتشار تعیین کرده‌اید) تعیین می‌کند هر کاربر در کدام گروه باشد.

کد
export default {
  async fetch(request, env) {
    const user = { key: "user-42", email: "a@acme.ir", plan: "pro" };
    // the value; false if there is no such flag
    if (await env.FLAGS.get("new-checkout", false, user)) return newCheckout(request);
    // every flag at once, or why a flag gave its answer
    const all = await env.FLAGS.getAll(user);
    const why = await env.FLAGS.evaluate("new-checkout", user); // { value, reason, rule? }
    return oldCheckout(request);
  },
};
روشکار
await env.FLAGS.get(key, default?, context?)مقدار؛ اگر فلگ نباشد default شما
await env.FLAGS.getAll(context?)همهٔ فلگ‌ها: {کلید: مقدار}
await env.FLAGS.evaluate(key, context?, default?){value, reason, rule?, variant?, bucket?}؛ reason یکی از off، rule، rollout، default، missing
خواندن فلگ هیچ‌وقت خطا نمی‌دهد: اگر فایل فلگ‌ها نبود یا فلگی وجود نداشت، مقدار پیش‌فرضِ شما برمی‌گردد.

محیط‌ها و شاخه‌ها#

پروداکشن از تنظیمات production پیروی می‌کند و پیش‌نمایش‌ها از preview. یک شاخه می‌تواند تنظیمات جدا بگیرد: پیش‌نمایش آن شاخه به‌جای تنظیمات preview از آن‌ها پیروی می‌کند (شاخه با همان نامی که در git push می‌دهید).

خط فرمان
cloudhost flags set new-checkout true --environment preview --branch feature/checkout
cloudhost flags eval new-checkout --environment preview --branch feature/checkout

قانون‌ها و انتشار تدریجی#

یک قانون شرط‌هایی دارد که همه باید برقرار باشند و مقداری که در آن صورت داده می‌شود. شرط‌ها:

شرطمعنی
equals, not_equalsویژگی با یک مقدار برابر باشد یا نباشد
in, not_inویژگی در فهرست باشد یا نباشد
contains, not_containsمتن شامل زیرمتن باشد؛ اگر ویژگی فهرست باشد، شامل آن عضو
starts_with, ends_withمتن با زیرمتن شروع یا تمام شود
  • قانون‌ها به ترتیب بررسی می‌شوند و اولین قانونِ مطابق برنده است؛ با percentage فقط همان درصد از مطابق‌ها می‌گیرند.
  • انتشار تدریجی: هر کلید با یک هش ثابت به عددی از ۰ تا ۹۹٫۹۹ درصد می‌رسد، پس هر کاربر همیشه در همان گروه می‌ماند، در همهٔ سرورها و در REST. با بالا بردن درصد فقط کاربران اضافه می‌شوند و کسی که فلگ را داشت از دست نمی‌دهد.
  • ویژگی گروه‌بندی پیش‌فرض key است؛ با --by یا bucket_by ویژگی دیگری (مثلاً orgId) را بگذارید تا همهٔ اعضای یک سازمان با هم باشند. اگر زمینه آن ویژگی را نداشته باشد، انتشار نادیده گرفته می‌شود.
  • با salt گروه‌بندی را عوض کنید تا کاربران دیگری انتخاب شوند.

بخش‌ها#

بخش فهرستی از کاربران است که در چند فلگ به کار می‌رود: کاربرانی که داخل‌اند، کاربرانی که بیرون‌اند (بیرون بر داخل مقدم است)، و قانون‌هایی برای بقیه. در قانون فلگ، شرط in_segment یا not_in_segment را با کلید بخش بنویسید.

cloudhost flags segments add staff --include user-1,user-2
cloudhost flags rule add new-checkout key in_segment staff --environment production --value true
بخشی که قانون فلگی از آن استفاده می‌کند حذف نمی‌شود؛ اول آن قانون را بردارید.

کلید قطع اضطراری#

اگر ویژگی‌ای خراب شد، فلگش را قطع کنید: همان لحظه مقدار «حالت قطع» داده می‌شود و قانون‌ها و انتشار نادیده گرفته می‌شوند. بدون استقرار مجدد و بدون دست‌زدن به قانون‌ها؛ بعد از رفع مشکل روشنش می‌کنید.

خط فرمان
cloudhost flags kill new-checkout                         # every environment
cloudhost flags kill new-checkout --environment production
cloudhost flags revive new-checkout

تاریخچه و بازگشت#

هر تغییر یک نسخه در تاریخچهٔ فلگ ایجاد می‌کند: چه کسی، چه وقت، چه کاری و کدام محیط‌ها را تغییر داد. بازگشت به یک نسخه کل فلگ را (همهٔ محیط‌ها و شاخه‌ها) به همان حالت برمی‌گرداند و خودش هم ثبت می‌شود. تاریخچه بعد از حذف فلگ هم می‌ماند، پس فلگ حذف‌شده را هم می‌شود برگرداند.

خط فرمان
cloudhost flags history new-checkout
cloudhost flags rollback new-checkout 3

خواندن از بیرون پلتفرم#

برای سرور یا ابزار خودتان یک توکن فقط‌خواندنی ایجاد کنید؛ هر توکن یک محیط را می‌بیند و نمی‌تواند چیزی را عوض کند. قانون‌ها نزد ما می‌مانند و فقط پاسخ برمی‌گردد.

// flags.js: a small client, no dependencies
const API = "https://api.ibel.ai";
const cache = new Map();
export function flags(token, { ttl = 5000 } = {}) {
  const ask = async (key, context) => {
    const id = key + JSON.stringify(context ?? {});
    const hit = cache.get(id);
    if (hit && Date.now() - hit.at < ttl) return hit.value;
    const r = await fetch(`${API}/v1/flags/read`, {
      method: "POST",
      headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
      body: JSON.stringify({ keys: [key], context }),
    });
    if (!r.ok) throw new Error(`flags: HTTP ${r.status}`);
    const value = (await r.json()).flags[key];
    cache.set(id, { at: Date.now(), value });
    return value;
  };
  return {
    get: async (key, fallback, context) => (await ask(key, context))?.value ?? fallback,
    evaluate: (key, context) => ask(key, context), // { value, reason, rule? }
  };
}

// const f = flags(process.env.FLAGS_TOKEN);
// if (await f.get("new-checkout", false, { key: user.id })) { … }
هر توکن در ثانیه تعداد محدودی خواندن دارد (بسته به پلن)؛ پاسخ را چند ثانیه در برنامهٔ خود نگه دارید. ابطال توکن تا پنج ثانیه طول می‌کشد.

OpenFeature#

برنامه‌هایی که بیرون از پلتفرم اجرا می‌شوند با SDKهای استاندارد OpenFeature و ارائه‌دهندهٔ OFREP فلگ‌ها را می‌خوانند. یک کلید فلگ ایجاد کنید: کلید سرور همهٔ فلگ‌های یک محیط را می‌بیند؛ کلید مرورگر فقط فلگ‌هایی را که برای مرورگر نمایان کرده‌اید، پس گذاشتنش در صفحهٔ وب امن است.

import { OpenFeature } from "@openfeature/server-sdk";
import { OFREPProvider } from "@openfeature/ofrep-provider";

await OpenFeature.setProviderAndWait(new OFREPProvider({
  baseUrl: "https://api.ibel.ai",
  headers: [["Authorization", `Bearer ${process.env.FLAG_KEY}`]],
}));
const client = OpenFeature.getClient();
const on = await client.getBooleanValue("new-checkout", false, { targetingKey: user.id, plan: user.plan });
targetingKey همان کلید درصد انتشار است، پس هر کاربر همیشه همان پاسخ را می‌گیرد. هر درخواست یک «درخواست فلگ» شمرده می‌شود، هر چند فلگ که بخواند؛ مصرف و قیمت در صفحهٔ قیمت‌ها است.

سقف‌ها#

سقف (برای هر برنامه یا فانکشن)رایگانحرفه‌ای
فلگ201,000
قانون در هر فلگ و محیط550
بخش2100
تغییر در ماه500نامحدود
نگهداری تاریخچه۳۰ روزیک سال
تنظیمات جدای شاخه در هر فلگ5100
توکن فقط‌خواندنی220
خواندن در ثانیه با هر توکن10100
درخواست فلگ در ماه (OpenFeature)100,0001,000,000

هر مقدار تا ۲ کیلوبایت و هر تنظیمات تا ۱۶ کیلوبایت است. GET /v1/pricing همین سقف‌ها را می‌دهد.