Encore Backend Framework 代码片段速查手册:API、数据库、Cron、PubSub、缓存与密钥的实战用法
【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore
导读
本文是 Encore 开源仓库(encor/encore)中面向 Go 后端的速查型技术指南,围绕 docs/go/primitives/code-snippets.md 展开:当你已经熟悉 Encore 的模型后,可以跳过冗长的概念讲解,直接复制粘贴这些经过验证的代码片段来搭建 API、SQL 数据库、定时任务、PubSub 事件流、Redis 缓存集群与密钥管理。文章在完整继承原文档全部示例的基础上,结合runtimes/go下的真实运行时源码(sqldb、cron、pubsub、cache、secrets)与cli/cmd/encore/secrets的 CLI 实现,补充了参数取值范围、默认值与底层调用链,让你既"抄得走"也"读得懂"。
适用前提:以下所有代码片段均以当前仓库
runtimes/go的 Go 运行时 API 为准,需要你的应用基于 Encore 的 Go Backend Framework 构建,并通过encore run启动本地开发环境。
APIs:定义、调用与裸 HTTP 端点
定义 API(//encore:api注解)
在 Encore 中,服务就是一个普通的 Go 包。把任意一个函数加上//encore:api注解,它就会变成一个由 Encore 统一路由、鉴权、追踪和部署的 API 端点:
package hello // service name //encore:api public func Ping(ctx context.Context, params *PingParams) (*PingResponse, error) { msg := fmt.Sprintf("Hello, %s!", params.Name) return &PingResponse{Message: msg}, nil }关键点:
- 包名即服务名:
package hello意味着这个包是一个名为hello的 Encore 服务; public表示公开端点(另有private,只能被其他服务或平台内部调用,外部请求不可直达);- 端点的签名约定为
func(ctx context.Context, params *T) (*R, error),params与返回值都可以是结构体指针或简单类型,返回值中的error会被 Encore 自动映射为 HTTP 状态码与错误响应; - 请求/响应 schema 会自动生成 OpenAPI 文档与类型安全的客户端(见 pkg/clientgen)。
定义请求与响应 Schema
与端点配套的结构体即请求/响应 schema,字段名默认采用 Go 风格自动映射为 JSON 字段:
// PingParams is the request data for the Ping endpoint. type PingParams struct { Name string } // PingResponse is the response data for the Ping endpoint. type PingResponse struct { Message string }调用 API:类型安全的函数调用
跨服务调用在 Encore 里就是一次普通的 Go 函数调用——编译器会为你生成 RPC 封装,无需手动构造 HTTP 请求:
import "encore.app/hello" // import service //encore:api public func MyOtherAPI(ctx context.Context) error { resp, err := hello.Ping(ctx, &hello.PingParams{Name: "World"}) if err == nil { log.Println(resp.Message) // "Hello, World!" } return err }提示:导入服务包,然后像调用普通函数一样调用 API 端点即可。请求会通过 Encore 的服务网格自动完成路由、鉴权、追踪与分布式请求传播。
接收 Webhook(raw 端点)
当需要完全控制 HTTP 请求(例如接收第三方 Webhook、做自定义路由或接入现有net/http中间件)时,使用raw端点:
import "net/http" // Webhook receives incoming webhooks from Some Service That Sends Webhooks. //encore:api public raw func Webhook(w http.ResponseWriter, req *http.Request) { // ... operate on the raw HTTP request ... }提示:与普通端点一样,它也会被公开暴露,URL 形如:
https://<env>-<app-id>.encr.app/service.Webhookraw 端点直接给你http.ResponseWriter与*http.Request,因此可以读取原始 body、请求头,写入任意响应内容,适合做 Stripe/GitHub 这类第三方 Webhook 接收器。
Databases:创建、写入与查询 SQL 数据库
创建 SQL 数据库并定义迁移
导入encore.dev/storage/sqldb,调用sqldb.NewDatabase并把结果赋给包级变量即可声明一个数据库;sqldb.DatabaseConfig.Migrations指定存放迁移 SQL 文件的目录,它就是数据库 schema 的定义方式:
-- todo/db.go -- package todo // Create the todo database and assign it to the "tododb" variable var tododb = sqldb.NewDatabase("todo", sqldb.DatabaseConfig{ Migrations: "./migrations", }) // Then, query the database using db.QueryRow, db.Exec, etc. -- todo/migrations/1_create_table.up.sql -- CREATE TABLE todo_item ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT false -- etc... );补充说明(基于 runtimes/go/storage/sqldb/sqldb.go 与 db.go 的源码):
- 底层连接池由
pgxpool.Pool管理(Database结构体中的pool *pgxpool.Pool),并通过database/sql兼容层暴露Stdlib()方法,可无缝接入期望*sql.DB的第三方库; - 迁移采用版本化 SQL 文件(如
1_create_table.up.sql),Encore 在本地开发环境自动执行迁移,让数据库与代码保持一致; - 每个
Database都会自动注入分布式追踪(DBQueryStart/DBQueryEnd等 trace 事件),你在本地开发仪表盘上可以直接看到每条 SQL 的执行情况。
插入数据
使用包方法sqldb.Exec(底层即Database.Exec)执行带占位符参数的 SQL,避免 SQL 注入:
import "encore.dev/storage/sqldb" // insert inserts a todo item into the database. func insert(ctx context.Context, id, title string, done bool) error { _, err := tododb.Exec(ctx, ` INSERT INTO todo_item (id, title, done) VALUES ($1, $2, $3) `, id, title, done) return err }- 参数占位符使用 PostgreSQL 风格
$1, $2, $3; - 返回的
ExecResult可通过RowsAffected()获取受影响行数(sqldb.go)。
查询单行数据
使用sqldb.QueryRow查询一行,配合Scan将列映射到 Go 变量:
import "encore.dev/storage/sqldb" var item struct { ID int64 Title string Done bool } err := tododb.QueryRow(ctx, ` SELECT id, title, done FROM todo_item LIMIT 1 `).Scan(&item.ID, &item.Title, &item.Done)提示:如果QueryRow没有找到匹配行,会返回一个错误,可通过导入标准库errors包调用errors.Is(err, sqldb.ErrNoRows)判断。ErrNoRows在源码中定义为sql.ErrNoRows(见 sqldb.go),因此语义与database/sql完全一致。另外,数据库服务端报错会被转换为携带Code、Severity、TableName等结构化信息的*sqldb.Error,可用sqldb.ErrCode(err)进一步分类处理(见 errors.go)。
Defining a Cron Job:定义定时任务
使用encore.dev/cron的cron.NewJob定义一个定时任务,并把它赋值给包级变量(var _ =表示仅为了注册,无需持有引用):
import "encore.dev/cron" var _ = cron.NewJob("welcome-email", cron.JobConfig{ Title: "Send welcome emails", Every: 2 * cron.Hour, Endpoint: SendWelcomeEmail, }) //encore:api private func SendWelcomeEmail(ctx context.Context) error { // ... return nil }提示:Cron Jobs 不会在本地开发环境中自动执行,你需要直接调用目标端点来测试实现。
从 runtimes/go/cron/cron.go 可以补充以下重要约束:
id必须是 kebab-case,不超过 63 个字符,以字母开头、以字母或数字结尾;这个 ID 是任务在平台侧的稳定标识,后续重构代码、移动定义位置时,Encore 靠它判断"还是同一个任务";JobConfig中Every与Schedule二选一:Every接受cron.Minute/cron.Hour常量构成的间隔,且间隔必须能整除 24 小时(如10 * cron.Minute、6 * cron.Hour合法,7 * cron.Hour不合法,编译器会在编译期报错);Schedule则接受标准 Cron 表达式;Endpoint的签名必须是func(context.Context) error或func(context.Context) (T, error),且不能带其他参数;JobConfig的字段在编译期被解析并用于平台侧资源供给,因此必须写成常量字面量,运行时并不真正执行这段"配置"。
PubSub:主题、发布与订阅
创建 PubSub Topic
用pubsub.NewTopic创建泛型主题(Topic[T],T是消息载荷类型),主题必须声明为包级变量,不能在函数内部创建:
import "encore.dev/pubsub" type SignupEvent struct { UserID int } var Signups = pubsub.NewTopic*SignupEvent提示:无论主题定义在哪个服务,任何服务都可以向其发布消息或订阅。
配置细节(见 runtimes/go/pubsub/internal/types/public.go):
DeliveryGuarantee是必填字段,可选pubsub.AtLeastOnce(至少一次投递,AWS/GCP 无吞吐限制)或pubsub.ExactlyOnce(尽力恰好一次投递;需注意 ExactlyOnce 只约束"投递"而非"发布",若应用逻辑重试导致同一消息发布两次,仍会投递两次,所以订阅端处理函数最好保持幂等);- 可选
OrderingAttribute指定消息属性作为排序键,保证相同取值消息按发布顺序投递;消息属性通过pubsub-attrstruct tag 声明(UserID string \pubsub-attr:"user-id"``);本地开发环境中排序键暂时不生效。
发布事件(Pub)
调用主题的Publish方法发布消息,返回唯一的消息 ID;Publish会阻塞直到消息被主题成功接收:
if _, err := Signups.Publish(ctx, &SignupEvent{UserID: id}); err != nil { return err } if err := tx.Commit(); err != nil { return err }从 topic.go 可以看到Publish的内部链路:先校验ctx,将消息属性与 JSON body 序列化,自动注入encore_parent_trace_id/encore_ext_correlation_id等关联追踪属性,再经由限流器(publishLimiter.Wait)后调用底层云 Provider 的PublishMessage,同时记录PubsubPublishStart/End追踪事件——这意味着发布是"可观测的",并且会把 trace 上下文自动传递给订阅方。
提示:如果要从其他服务发布,导入该主题的包变量(本例中的Signups)并调用其Publish即可。
订阅事件(Sub)
通过pubsub.NewSubscription以包级变量创建订阅,指定要订阅的主题、订阅名与处理函数:
var _ = pubsub.NewSubscription( user.Signups, "send-welcome-email", pubsub.SubscriptionConfig[*SignupEvent] { Handler: SendWelcomeEmail, }, ) func SendWelcomeEmail(ctx context.Context, event *SignupEvent) error { ... send email ... return nil }SubscriptionConfig中的Handler是必填字段:返回nil表示消息被确认(ack),不再重投;返回非nil错误会触发负确认(nack)与重试,直到达到重试上限。常用可选配置(见 types.go):
MaxConcurrency:单个服务实例并发处理的消息数上限(负值表示不限制;注意该配置对 Encore Cloud 不生效,部分云 Provider 会自适应并发);AckDeadline:处理超时时间,默认 30 秒、至少 1 秒,超时后消息会被放回订阅;MessageRetention:未投递消息的保留时长,默认 7 天;RetryPolicy:MinBackoff(默认 10 秒)/MaxBackoff(默认 10 分钟)/MaxRetries(0时使用默认 100 次重试;pubsub.NoRetries立即进入死信队列;pubsub.InfiniteRetries永不进入死信)。
Defining a Cache cluster:定义 Redis 缓存集群
使用encore.dev/storage/cache的cache.NewCluster声明一个 Redis 缓存集群,同样必须赋给包级变量:
import "encore.dev/storage/cache" var MyCacheCluster = cache.NewCluster("my-cache-cluster", cache.ClusterConfig{ // EvictionPolicy tells Redis how to evict keys when the cache reaches // its memory limit. For typical cache use cases, cache.AllKeysLRU is a good default. EvictionPolicy: cache.AllKeysLRU, })从 runtimes/go/storage/cache/cache.go 可以看到 Encore 支持的全部淘汰策略(EvictionPolicy字符串常量):
| 常量 | Redis 策略 | 行为 |
|---|---|---|
cache.AllKeysLRU | allkeys-lru | 淘汰最久未使用的键,默认值,多数缓存场景的推荐选择 |
cache.AllKeysLFU | allkeys-lfu | 淘汰最不常使用的键 |
cache.AllKeysRandom | allkeys-random | 随机淘汰任意键 |
cache.VolatileLRU | volatile-lru | 仅淘汰设置了过期时间的键中最久未使用的 |
cache.VolatileLFU | volatile-lfu | 仅淘汰设置了过期时间的键中最不常使用的 |
cache.VolatileTTL | volatile-ttl | 淘汰剩余 TTL 最短的键 |
cache.VolatileRandom | volatile-random | 在设置了过期时间的键中随机淘汰 |
cache.NoEviction | noeviction | 不淘汰任何键,内存达到上限时直接返回错误 |
底层客户端基于github.com/go-redis/redis/v8,并围绕"keyspace + KeyPattern + 过期策略"提供类型安全的读写 API(含Miss、KeyExists等可errors.Is判定的哨兵错误);对带默认过期时间的 keyspace,写操作会通过 Redis 事务管道(TxPipeline)把命令与过期时间更新合并为原子操作(见 cache.go)。
Secrets:定义、设置与使用密钥
定义 Secrets
在任意服务包中声明一个未导出的、名为secrets的结构体变量,字段为string类型,字段名即密钥名:
var secrets struct { GitHubAPIToken string // personal access token for deployments SomeOtherSecret string // some other secret }提示:变量必须是未导出的、名为secrets的结构体,且所有字段类型必须是string。该结构体由 Encore 编译器特殊识别,不会被打包进镜像或提交到仓库。
设置 Secret 值
通过 Encore CLI 为密钥设置不同环境的值:
$ encore secret set --type <types...> <secret-name>提示:<types>决定该密钥值作用于哪些环境类型,用逗号分隔的组合包括production、development、preview和local;每个密钥在每个环境类型下只能有一个值。
CLI 实现(cli/cmd/encore/secrets/set.go)提供了更完整的用法:
- 环境类型支持别名:
dev(=development)、prod(=production)、pr/ephemeral(=preview)、local,也可用--dev(等价于 development+preview+local)或--prod快捷标志;还可以用--env <environment-name>只针对某个具体环境设置; - 交互式输入:直接在终端运行命令后,CLI 会以隐藏输入提示
Enter secret value:; - 管道输入:
$ encore secret set --type dev,local,pr MySecret < my-secret.txt,注意会去除值末尾的换行符; - 已存在的密钥/选择器组合会被更新而非重复创建;若选择器与已有密钥值冲突,CLI 会列出冲突明细。
使用 Secrets
在代码中直接通过secrets.<FieldName>读取,运行时由 Encore 从平台注入实际值:
func callGitHub(ctx context.Context) { req, _ := http.NewRequestWithContext(ctx, "GET", "https:///api.github.com/user", nil) req.Header.Add("Authorization", "token " + secrets.GitHubAPIToken) resp, err := http.DefaultClient.Do(req) // ... handle err and resp }提示:密钥名在整个应用中全局唯一;如果多个服务使用相同的密钥名,运行时它们会收到同一个密钥值。密钥不会出现在构建产物或 Git 历史中,CLI 在设置完成后还会触发守护进程的SecretsRefresh以便本地环境立即生效(见 set.go)。
小结与下一步
这份速查手册覆盖了 Encore Go Backend Framework 的全部核心构建块:API(含 raw Webhook 端点)、SQL 数据库(sqldb的建库/迁移/增查)、定时任务(cron.NewJob)、事件驱动(pubsub的 Topic/Subscription)、Redis 缓存集群(cache.NewCluster)与密钥管理(secrets结构体 +encore secret set)。所有片段都可在新项目中直接复制使用,并享受 Encore 自动提供的分布式追踪、类型安全调用与云端资源供给。
需要深入了解某个主题时,可继续查阅仓库内对应文档:定义 API、数据库、Cron Jobs、PubSub、缓存、Secrets,以及对应运行时实现 runtimes/go/storage 与 runtimes/go/pubsub。
【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考