一个应用两家店 — 第二家店要是变成改代码,你就输了
6 分钟第二家店开了。你想把第一家店的应用原样拿来用。
到这里通常会变成这样。
if (shopName == 'Riverside') { ... } else { ... }然后第三家再来一次,第四家再来一次。两年后,文件里还留着一家早就关掉的店的分支,而没人敢删。
应用不该知道店
这个样例只有一条规则。
/// The tempting shape is a `shopName` switch somewhere. Then the second shop
/// is a code change, the third shop is a code change, and eventually the file
/// has a branch for a shop that closed two years ago.
///
/// So nothing here knows any shop's name. The config file is an argument.店是一个配置文件,而配置文件在应用外面。
shop.mbd/ ← 应用。两家店共用
configs/
riverside.json ← 门店 1
hilltop.json ← 门店 2同样的文件,不同的店


shop.mbd — 3 files, sha256:f5f905958eac
[riverside.json] RIVERSIDE · 07:00 - 20:00 · 3 items on the menu
[hilltop.json] HILLTOP · 09:00 - 18:00 · 4 items on the menu
same screen file, two shops: RIVERSIDE due $8.10 vs HILLTOP due $10.45整个包都做了哈希。两家店之间不管有什么不同,都不在那里面。
验证就是一行 grep
这一篇的主张,可以浓缩成一个检查。
for NAME in Riverside Hilltop; do
if grep -RIiq "$NAME" shop.mbd shop_server/bin; then
echo " \"$NAME\" appears inside the app — the difference has leaked in"; exit 1
fi
done neither shop name appears in shop.mbd or shop_server/bin应用里一旦出现店名,就判失败。 将来谁一着急塞进一行 if (shop == 'Hilltop'),这个检查当场就红。这正是它存在的理由 — 那一行永远是在着急的时候进来的。
按了菜单上没有的东西
画面文件是同一个,所以胡萝卜蛋糕的按钮在两家店的画面上都一样存在。
Riverside 没有胡萝卜蛋糕。
[riverside.json] add CK -> "CK is not on this menu"
[hilltop.json] add CK -> "added Carrot cake"拒绝由服务器来做。
// A sku that is on one shop's menu and not the other's must be refused
// here, not hidden by the screen. The screen is the same file in both
// shops and cannot know.
if (item.isEmpty) {
return _state(notice: '$sku is not on this menu');
}这和这个谁能按是同一个故事的另一副面孔。那边是权限在画面之外,这边是商品目录在画面之外。共同点是画面是同一个文件,而同一个文件什么也决定不了。
税的报价方式不同
这是实务中最常分岔的地方。一家店价格含税,另一家在收银台加。
[riverside.json] tax $0.74 · due $8.10 · 10% tax included in the prices above
[hilltop.json] tax $0.95 · due $10.45 · 10% tax added at the counter做两条代码路径,它们必定会走散。所以一个表达式去读那个开关。
// Two shops, two ways of quoting. The arithmetic is one expression that
// reads the flag — not two code paths that drift apart.
final tax = included
? (gross * pct / (100 + pct)).round()
: (gross * pct / 100).round();
final due = included ? gross : gross + tax;$8.10 里的 $0.74。含税额是 8.10 × 10 / 110,而不是 8.10 × 0.1 = 0.81。把这一步算错的生产代码,实在太多了。
第三家店
在这个结构里开第三家店是这样的。
configs/seaside.json一个文件。不用发布、不用构建、不用评审。而这正是选择这个结构的唯一理由。
这个样例的范围
没有「谁来改配置」这件事。 文件是手工改的。现实里老板得在画面上改价格,那就需要一个编辑配置的画面。那在这个样例之外。
没有配置校验。 往 taxPercent 里放个字符串,服务器就直接死。等到有十家店,一份坏配置应该只让那一家开不了,而我们没有做。
两家店跑在同一台机器上。 位于不同门店的两台设备各自领到自己配置的那种发布,没有讲。那会接到一个文件夹就是应用处理过的投递问题上。
共用菜单也没有同步。 要在两家店同时改「美式」的名字,得改两个文件。需要「共享配置 + 门店覆盖」的结构,而我们没有做。
连锁管理工具为什么贵
门店管理 SaaS 贵,不是因为功能多。而是因为把「每家店不一样的东西」保持成不一样这件事本来就难。
这个样例展示的是:那份难处是**「放在哪里」的问题。** 放在应用里,分支随门店数增长;放在外面,文件随门店数增长。分支会互相缠绕,文件不会。
这个差别在第二家店时看不见,到第五家店就看得见了。
练习任务
连续发送两次相同的预订。证明第二次不会再生成一个预订,并说明是哪一方保证了这一点。