一句话:远程配置让你无需发布新版本就能改变应用的行为——功能开关、可调参数,以及在运行时从后端下发的 A/B 实验变体。你在应用里用 lingcode.config 读取这些值;在服务端翻转它们。永远给 get() 传一个默认值,这样配置加载之前应用也能正常工作。
发布版本是一种缓慢、要么全有要么全无的决策方式。把 new_checkout = true 写死,就等于对所有用户一锤定音,唯一的退路是再走一次应用商店发版。远程配置把这件事倒过来:决策放在服务器上,应用在启动时读取它,于是你可以把一个开关往前推进、回拨一档,或者把流量拆进实验变体——这一切都不必动用户手里已经装好的那个二进制包。
三种活儿,一套机制。功能开关把某条代码路径打开或关闭(先暗发上线、之后再启用、出问题时立刻干掉一处回归)。可调参数暴露一个数字或一段文案,让你不用发版就能调整——一个分页大小、一个价格展示字符串、一个重试次数。实验把不同的变体发给不同的用户,让你测出哪一个胜出。这三者都不过是你的应用在运行时读取的键值对。
要记在脑子里的那条分界:你在客户端读取值,在后端设置值。客户端从不决定一个开关该是什么——它只是发问、拿到答案、然后分支。改变那个答案是一次服务端操作,会在下一次配置加载时生效,完全不涉及重新构建。
一切都在 client.config 之下(在 /try 和 Mac 预览里,客户端已经被预先注入为 lingcode):
config.ready → Promise<ConfigApi>。当这个客户端的远程配置加载完成后 resolve。首屏读取前先 await 它。config.get(key, def?) → 值。返回已配置的值,如果该键未设置则返回 def。实验变体也是通过 get() 下发的。config.all() → Record<string, any>。所有已解析的配置值。等配置加载完成,用一个合理的默认值读取开关,然后分支:
await lingcode.config.ready;
const showNewCheckout = lingcode.config.get('new_checkout', false); // 默认 false
if (showNewCheckout) {
renderNewCheckout();
} else {
renderOldCheckout();
}
默认值不是摆设——它是配置还没加载、或者请求失败时应用所采用的那个值。挑一个本身就正确的默认值(这里是已有的结账流程),那么无论网络发生什么,你的应用都能优雅降级。
实验就是一个有两个以上结果的开关:后端为每个客户端分配一个变体,你用 get() 读取它并据此分支。把它和数据分析配对——在你关心的那个结果上调用 logEvent——这样你才能测出哪个变体真正胜出:
await lingcode.config.ready;
const variant = lingcode.config.get('checkout_button', 'control'); // 'control' | 'green' | 'urgent'
renderCheckoutButton(variant);
// 之后,当用户完成转化时——把这个结果归因到该变体
async function onPurchase(amount) {
await lingcode.telemetry.logEvent('purchase', { variant, amount });
}
进来的是同一个客户端分配,出去的是结果事件——整个闭环就是这样。时间一长,数据分析会告诉你 green 或 urgent 是否击败了 control,你只需在服务端翻转配置,就能把胜者推全量。
当你想要完整的已解析集合时——用来填充一个设置界面,或记录某次会话跑在哪份配置下——就用 all():
await lingcode.config.ready;
const cfg = lingcode.config.all(); // { new_checkout: true, checkout_button: 'green', page_size: 50, ... }
把远程配置当作功能开关、灰度放量、文案微调和可调数值的归宿。它不是放密钥的地方。私有密钥(Stripe secret、第三方 API key)应当放进密钥保险库,并从函数里读取——函数运行在服务端。另外,要靠你的默认值:应用在每个开关都取默认值时都应表现正确,这样一次缓慢或失败的配置加载永远不会变成一个坏掉的应用。
SDK 从单个端点拉取配置,并按客户端作用域,这样每个客户端都能拿到属于自己的实验分配:
GET https://lingcode.dev/api/cloud/be/<backend-id>/config?client_id=<id>
响应是一个扁平的键值对对象:
{
"new_checkout": true,
"checkout_button": "green",
"page_size": 50
}
如果请求失败,SDK 会抛出一个 config_failed 错误——这正是为什么每次 get() 调用都该带一个默认值。应用会继续跑在它的默认值上;你不必在调用处内联处理这个错误。