Send Search Requests
Update domain/gateway/book_manager.go:
@@ -12,4 +12,5 @@ import (
// BookManager manages all books
type BookManager interface {
IndexBook(ctx context.Context, b *model.Book) (string, error)
+ SearchBooks(ctx context.Context, query string) ([]*model.Book, error)
}
Update infrastructure/search/es.go:
@@ -5,8 +5,11 @@ package search
import (
"context"
+ "encoding/json"
"github.com/elastic/go-elasticsearch/v8"
+ "github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
+ "github.com/elastic/go-elasticsearch/v8/typedapi/types"
"literank.com/fulltext-books/domain/model"
)
@@ -42,3 +45,29 @@ func (s *ElasticSearchEngine) IndexBook(ctx context.Context, b *model.Book) (str
}
return resp.Id_, nil
}
+
+// SearchBooks search from ES and return a list of books
+func (s *ElasticSearchEngine) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+ resp, err := s.client.Search().Index(INDEX_BOOK).
+ Request(&search.Request{
+ Query: &types.Query{
+ MultiMatch: &types.MultiMatchQuery{
+ Query: query,
+ Fields: []string{"title", "author", "content"},
+ },
+ },
+ }).
+ Do(ctx)
+ if err != nil {
+ return nil, err
+ }
+ books := make([]*model.Book, 0)
+ for _, hit := range resp.Hits.Hits {
+ var b model.Book
+ if err := json.Unmarshal(hit.Source_, &b); err != nil {
+ return nil, err
+ }
+ books = append(books, &b)
+ }
+ return books, nil
+}
Here we use MultiMatch
to query on mutilple fields “title“, “author“ and “content“.
Update application/executor/book_operator.go:
@@ -24,3 +24,7 @@ func NewBookOperator(b gateway.BookManager) *BookOperator {
func (o *BookOperator) CreateBook(ctx context.Context, b *model.Book) (string, error) {
return o.bookManager.IndexBook(ctx, b)
}
+
+func (o *BookOperator) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+ return o.bookManager.SearchBooks(ctx, query)
+}
Update adapter/router.go:
@@ -14,6 +14,8 @@ import (
"literank.com/fulltext-books/domain/model"
)
+const fieldQuery = "q"
+
// RestHandler handles all restful requests
type RestHandler struct {
bookOperator *executor.BookOperator
@@ -31,6 +33,7 @@ func MakeRouter(wireHelper *application.WireHelper) (*gin.Engine, error) {
// Create a new Gin router
r := gin.Default()
+ r.GET("/books", rest.searchBooks)
r.POST("/books", rest.createBook)
return r, nil
}
@@ -51,3 +54,13 @@ func (r *RestHandler) createBook(c *gin.Context) {
}
c.JSON(http.StatusCreated, gin.H{"id": bookID})
}
+
+func (r *RestHandler) searchBooks(c *gin.Context) {
+ books, err := r.bookOperator.SearchBooks(c, c.Query(fieldQuery))
+ if err != nil {
+ fmt.Printf("Failed to search books: %v\n", err)
+ c.JSON(http.StatusNotFound, gin.H{"error": "failed to search books"})
+ return
+ }
+ c.JSON(http.StatusOK, books)
+}
Run the server again and try it with curl
:
curl 'http://localhost:8080/books?q=katniss+hunger'
Example response:
[
{
"title": "The Hunger Games",
"author": "Suzanne Collins",
"published_at": "2008-09-14",
"content": "In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."
}
]
Try it in tools like Postman or Insomnia:
http://localhost:8080/books?q=new%20york%20circus%20girl
In URL encoding, both
%20
and+
are used for representing spaces, but they serve slightly different purposes depending on the context.
Great! You just completed a full-text search.
Note:
Full text search requires language analyzers. The default analyzer is the standard analyzer, which may not be the best especially for Chinese, Japanese, or Korean text. With language specific analyzers, you can get better search experience.