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 逃生口。只需根據「查詢穩定性 & 模板復用程度」做權衡,就能兼顧 可靠性 與 靈活性,寫出更易維護、更易拓展的搜索服務。