1 为什么需要两种写法?
在 Golang 项目中访问 Elasticsearch,一般会遇到两类需求:
需求场景 | 特点 | 最佳写法 |
---|---|---|
后台服务 / 业务逻辑 | 查询固定、字段清晰,需要编译期保障 | Request 结构体 |
仪表盘 / 高级搜索 / 模板 DSL | 查询片段由前端或脚本动态生成,或要沿用历史 JSON 文件 | Raw JSON |
Typed Client 同时提供两种 API:.Request(&req)
与 .Raw([]byte)
,互不冲突,互有侧重。
2 使用强类型 Request —— 类型安全 + IDE 补全
2.1 代码示例:搜索 name="Foo"
package mainimport ("context""encoding/json""fmt""log"typedapi "github.com/elastic/go-elasticsearch/v8/typedapi""github.com/elastic/go-elasticsearch/v8/typedapi/search""github.com/elastic/go-elasticsearch/v8/typedapi/types"
)type Product struct {Name string `json:"name"`Price float64 `json:"price"`
}func main() {// 1) 创建 Typed Clientes, _ := typedapi.NewTypedClient(typedapi.Config{Addresses: []string{"http://localhost:9200"},})// 2) 构造强类型请求体req := search.Request{Query: &types.Query{Term: map[string]types.TermQuery{"name": {Value: "Foo"},},},}// 3) 发送查询res, err := es.Search().Index("products").Size(10).Request(&req). // ← 传入 Request 结构体Do(context.Background())if err != nil {log.Fatalf("search: %v", err)}defer res.Body.Close()// 4) 解析响应(Raw → 业务结构体)var body struct {Hits struct {Hits []struct {Source Product `json:"_source"`} `json:"hits"`} `json:"hits"`}_ = json.NewDecoder(res.Body).Decode(&body)for _, hit := range body.Hits.Hits {fmt.Printf("%+v\n", hit.Source)}
}
2.2 优缺点
优点 | 说明 |
---|---|
编译期校验 | DSL 字段、类型、枚举均受 Go 类型系统约束 |
IDE 智能提示 | 自动补全复杂结构(Query , Sort , Aggregation 等) |
易于重构 | 字段改动立即触发编译错误,避免运行期踩坑 |
注意点 | 说明 |
---|---|
依赖 spec 版本 | 新 API 字段需等待官方更新 elasticsearch-specification 生成代码 |
编码速度 | 初学者需花时间熟悉类型层级 |
3 使用 Raw JSON —— 复用模板 / 自定义编码
3.1 代码示例:用户 ID 精确匹配
package mainimport ("context""fmt""log"typedapi "github.com/elastic/go-elasticsearch/v8/typedapi"
)func main() {es, _ := typedapi.NewTypedClient(typedapi.Config{Addresses: []string{"http://localhost:9200"},})// Mustache / Kibana 导出的查询片段rawQuery := []byte(`{"query": {"term": {"user.id": {"value": "kimchy","boost": 1.0}}}}`)res, err := es.Search().Index("twitter").Raw(rawQuery). // ← 直接塞入 JSONDo(context.Background())if err != nil {log.Fatalf("search: %v", err)}defer res.Body.Close()fmt.Println(res.Status()) // 200 OK
}
3.2 优缺点
优点 | 说明 |
---|---|
模板友好 | 可与前端、Dashboard 共用一份纯 JSON |
零等待 | 新 API 字段、Beta 特性不依赖生成器 |
可替换 Encoder | 想要 jsoniter 、easyjson ?直接先序列化再 .Raw() |
注意点 | 说明 |
---|---|
无校验 | DSL 拼写 / 字段错位不会在编译期发现 |
最高优先级 | .Raw() 覆盖一切;之后再 .Query() 、.Request() 都被忽略 |
4 优雅切换策略
经验法则
- 80 % 固定业务查询 ➜ Request 结构体(静态安全)
- 20 % 动态或实验性查询 ➜ Raw JSON(灵活兜底)
在实际工程里,可将两套方案封装成 Repository / DSL Builder:
type ProductRepo struct {es *typedapi.TypedClient
}func (r *ProductRepo) ByName(ctx context.Context, name string) { /* Request 结构体 */ }
func (r *ProductRepo) ByTemplate(ctx context.Context, tpl []byte) { /* Raw JSON */ }
这样调用层永远只见到 强类型方法签名,底层细节由仓库层决定,是不是很优雅?🤘
5 小结
维度 | Request 结构体 | Raw JSON |
---|---|---|
类型安全 | ✔✔✔ | ✘ |
IDE 补全 | ✔ | ✘ |
学习成本 | 中 | 低(已有模板) |
新字段适配 | 需等生成器 | 立即可用 |
性能自定义 | 默认 encoding/json | 自选 Encoder |
Typed Client 让 Go + Elasticsearch 在保持类型安全的同时,又给出了面向未来的 Raw JSON 逃生口。只需根据「查询稳定性 & 模板复用程度」做权衡,就能兼顾 可靠性 与 灵活性,写出更易维护、更易拓展的搜索服务。