1 为什么需要两种写法?
在 Golang 项目中访问 Elasticsearch,一般会遇到两类需求:
需求场景 | 特点 | 最佳写法 |
---|---|---|
后台服务 / 业务逻辑 | 查询固定、字段清晰,需要编译期保障 | Request 结构体 |
仪表盘 / 高级搜索 / 模板 DSL | 查询片段由前端或脚本动态生成,或要沿用历史 JSON 文件 | Raw JSON |
Typed Client 同时提供两种 API:.Request(&req)
与 .Raw([]byte)
,互不冲突,互有侧重。
2 使用强类型 Request —— 类型安全 + IDE 补全
2.1 代码示例:搜索 name="Foo"
package main
import (
"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 Client
es, _ := 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 main
import (
"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). // ← 直接塞入 JSON
Do(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 逃生口。只需根据「查询稳定性 & 模板复用程度」做权衡,就能兼顾 可靠性 与 灵活性,写出更易维护、更易拓展的搜索服务。