Packages 包@downcity/city
Hook 与服务挂载
hook 三层、service 挂载方式,以及它们为什么是 City 的共享扩展点。
Downcity 很多共享能力不是靠“把代码复制到每个 service 里”完成的,而是通过 hook 和 service 进入 City。
这两个概念分别在解决什么
可以先这样分工:
- hook:给已有调用链插入规则
- service:给 City 增加一整组长期复用能力
所以你看到的 usage、账号、支付,并不是“额外开一套系统”,而是作为扩展点进入同一套 City runtime。
什么时候该先想 hook
- 你想在某类调用前后加规则
- 逻辑是横切的,不想写进每个 action
- 能力只在当前项目里使用,不一定要独立成 package
典型例子:
- 额度判断
- 日志记录
- 风控校验
- 调用后扣减余额
什么时候更适合做 service
- 能力需要跨多个产品复用
- 不只是前后置逻辑,而是一整组 service / 表 / 管理入口
- 你希望产品侧能长期通过统一 service 调它
典型例子:
@downcity/services@downcity/services@downcity/services
先理解 hook 的三层位置
global
-> service
-> action含义分别是:
- global:所有 service / action 共享
- service:某个 service 下所有 action 共享
- action:只对一个具体 action 生效
这三层不是为了增加复杂度,而是为了让你把规则放在最合适的位置,而不是一股脑写死到业务函数里。
最常见的 service 挂载示例
import { CityBase } from "@downcity/city";
import { accountsService } from "@downcity/services";
import { balanceService } from "@downcity/services";
import { paymentService } from "@downcity/services";
import { stripePaymentMethod } from "@downcity/services";
import { usageService } from "@downcity/services";
import { stripePaymentService } from "@downcity/services";
const base = new CityBase({ db });
const balance = balanceService();
base.use(accountsService({
token_ttl: "7d",
}));
base.use(balance);
base.use(paymentService({
methods: [
stripePaymentMethod(),
],
}));
base.use(usageService({
record_errors: true,
}));
base.use(stripePaymentService({
balance: {
readTopup: async (topup_id) => await balance.readTopup(topup_id),
finishTopup: async (topup_id, extra) => await balance.finishTopup(topup_id, extra),
},
secret_key: process.env.STRIPE_SECRET_KEY,
webhook_secret: process.env.STRIPE_WEBHOOK_SECRET,
}));这段代码背后的意思是:
accountsService()给 City 增加accountsservicepaymentService()给 City 增加统一支付入口,用来暴露当前可用的支付方式usageService()给 City 增加 usage 记录能力和相关查询入口stripePaymentService()给 City 增加 Stripe Checkout、webhook 和支付结果同步
service 挂进来后产品端怎么调
从产品视角看,service 最终会变成统一 service:
const session = await guest.service("accounts").action("login").invoke({
email: "user@example.com",
password: "password123",
town_id: "town_demo",
});
const usage = await admin.service("usage").get("summary");
const methods = await guest.service("payment").get("methods");
const checkout = await user.service("payment.stripe").action("checkout/create").invoke({
topup_id: "topup_demo",
});也就是说,service 并不会让产品端学第二套调用协议。
hook、service 的关系
可以把它们想成一条链:
service / action
-> hook 在调用前后加规则
-> service 提供可复用 service、表和管理能力
-> client 统一通过 /v1/* 调用这条链的好处是:
- 业务能力能复用
- 规则能集中治理
- 前端和后端的调用模型保持统一
常用 API / 入口
这一页最常用的公共入口其实很简单:
base.use(service):把 service 挂进 Cityguest.service("accounts")...guest.service("payment").get("methods")user.service("usage")...admin.service("payment.stripe")...
如果你是从扩展角度思考,最重要的不是记很多 API,而是先判断“这是 hook 问题还是 service 问题”。
常见误解
service 不是单纯的 npm 包
它代表的是一整组进入 City 的能力边界,通常还伴随:
- service
- 数据表
- 管理接口
- 可选 hook 逻辑
hook 不等于随便加中间件
hook 适合放横切治理逻辑,不适合把整个业务动作都塞进去。
service 不替代 City
service 负责把能力挂进 City,真正的产品调用入口仍然是 @downcity/city。
继续阅读
- 想看 town 端怎么统一调用 service,读 @downcity/city
- 想看账号能力,读 @downcity/services
- 想看 usage 记录,读 @downcity/services
- 想看 Stripe 一次性充值,读 @downcity/services