Функционал поиска — неотъемлемый компонент современных веб-приложений, с которым у пользователей расширяются возможности быстро находить нужную информацию в огромных массивах данных, вводя запросы и получая соответствующие результаты в условиях усовершенствованных навигации и пользовательского взаимодействия. Маркетплейсы, соцсети, базы знаний, инструменты повышения продуктивности — везде эффективный поиск важен для удовлетворенности и вовлеченности пользователей.
Функционал поиска реализуется с помощью структур данных, индексирования, алгоритмов поиска, библиотек JavaScript. Однако с масштабируемыми приложениями эти решения часто не справляются. За разрастанием сайтов и объемов данных легковесные библиотеки поспевают с трудом. Это чревато замедлением поиска, снижением релевантности результатов.
Такие проблемы устраняются масштабируемыми решениями вроде Elasticsearch и Solr. Эти платформы, предназначенные для обработки больших наборов данных, универсальнее и надежнее библиотек JavaScript: в них применяются расширенный функционал и оптимизации, необходимые для получения быстрых и релевантных результатов поиска, даже при увеличении размеров и сложности приложения.
Но у этих продвинутых программ имеются недостатки. Algolia не бесплатна, а для корректной настройки, развертывания и сопровождения платформ вроде Elasticsearch требуются специальные навыки. Эта сложность часто становится непреодолимой для небольших команд или отдельных разработчиков ввиду недостатка опыта.
Meilisearch
Meilisearch — бесплатное, надежное решение с открытым исходным кодом, просто находка для малых и средних проектов. Оно легко настраивается и используется, поэтому без проблем внедряется, а его масштабируемостью обеспечивается бесперебойное функционирование по мере роста проекта.
Особенно впечатляет способность Meilisearch с легкостью выдавать весьма релевантные поисковые результаты. Благодаря сортировке результатов в соответствии с настраиваемыми правилами ранжирования и точной настройке параметров поиска пользователи быстро и безошибочно находят то, что ищут.
Однако важно понимать роль Meilisearch в технологическом стеке: он хорош как поисковый механизм, а не как основное хранилище данных. Идеален для хранения данных, специально предназначенных для поисковых запросов пользователя, чем обеспечивается простота и эффективность поиска.
Попробуем интегрировать Meilisearch с Golang.
Установка
Сначала загружаем и устанавливаем на локальный компьютер последнюю версию Meilisearch:
# Установка Meilisearch
curl -L https://install.meilisearch.com | sh
Затем запускаем Meilisearch в терминале:
# Запуск Meilisearch
./meilisearch --master-key="aSampleMasterKey"
Meilisearch запущен здесь:
Meilisearch + Golang
Официальный пакет.
Применим упрощенный подход интеграции Meilisearch с Golang: обойдя процессы создания БД и вставки данных, получим данные напрямую из внешних источников и вставим их в Meilisearch.
Подключаемся к Meilisearch:
var meilisearchClient = meilisearch.NewClient(meilisearch.ClientConfig{
Host: "http://localhost:7700",
APIKey: "aSampleMasterKey",
})
Затем создаем модели для считывания данных из наборов данных movies.json и web_series.json:
type IMeilisearchIndex interface {
GetIndexName() string
}
type MovieIndex struct {
ID int `json:"id"`
Title string `json:"title"`
Year int `json:"year"`
Cast []string `json:"cast"`
Genres []string `json:"genres"`
}
func (i *MovieIndex) GetIndexName() string {
return "movies"
}
type WebSeriesIndex struct {
ID int `json:"id"`
FirstAirDate string `json:"first_air_date"`
Name string `json:"name"`
OriginalLanguage string `json:"original_language"`
Popularity float32 `json:"popularity"`
VoteAverage float32 `json:"voteAverage"`
VoteCount int `json:"vote_count"`
}
func (i *WebSeriesIndex) GetIndexName() string {
return "web-series"
}
Поле id в MovieIndex и WebSeriesIndex — это первичный ключ в Meilisearch. Согласно официальной документации, первичный ключ может быть любого поля.
Создадим индекс и установим первичный ключ.
Создание индекса
func CreateIndex(indexName string) error {
_, err := meilisearchClient.CreateIndex(&meilisearch.IndexConfig{
Uid: indexName,
PrimaryKey: "id",
})
if err != nil {
return err
}
log.Println("Index created : ", indexName)
return nil
}
// Считывание из json
var movies []MovieIndex
file, err := os.Open("movies.json")
if err != nil {
panic(err)
}
jsonParser := json.NewDecoder(file)
if err = jsonParser.Decode(&movies); err != nil {
panic(err)
}
for i := 0; i < len(movies); i++ {
movies[i].ID = i + 1
}
var webSeries []WebSeriesIndex
file, err = os.Open("web_series.json")
if err != nil {
panic(err)
}
jsonParser = json.NewDecoder(file)
if err = jsonParser.Decode(&webSeries); err != nil {
panic(err)
}
for i := 0; i < len(webSeries); i++ {
webSeries[i].ID = i + 1
}
// Создаем индекс
err = CreateIndex("movies")
if err != nil {
panic(err)
}
err = CreateIndex("web_series")
if err != nil {
panic(err)
}
Создав индексы, вставим в них данные. В Meilisearch каждая запись считается документом.
Вставка документов
func AddDocuments[T any](indexName string, data []T) error {
_, err := meilisearchClient.Index(indexName).AddDocuments(&data, "id")
if err != nil {
return err
}
return nil
}
// Добавляем документы
err = AddDocuments("movies", movies)
if err != nil {
panic(err)
}
err = AddDocuments("web_series", webSeries)
if err != nil {
panic(err)
}
Поиск
Функцией поиска Search как входные данные принимаются строка запроса и объект Meilisearch SearchRequest:
func Search(indexName string) error {
resp, err := meilisearchClient.Index("movies").Search("Water", &meilisearch.SearchRequest{
Limit: 5,
Offset: 0,
})
if err != nil {
panic(err)
}
ToJson("HITS : ", resp.Hits)
}
func ToJson(message string, obj any) {
b, err := json.Marshal(obj)
if err != nil {
log.Println("error converting to json : ", err)
}
log.Println(message, ": ", string(b))
}
Объектом SearchRequest принимается больше параметров вроде rank, filter и т. д. — для настройки поискового запроса.
Чтобы отфильтровать поля в индексе, обновляем поле как фильтруемое с помощью API настроек и применяем для поиска фильтр:
func Search(indexName string) error {
_, err := meilisearchClient.Index("movies").UpdateFilterableAttributes(&[]string{"cast", "genres"})
if err != nil {
panic(err)
}
resp, err := meilisearchClient.Index("movies").Search("", &meilisearch.SearchRequest{
Limit: 10,
Offset: 0,
Filter: `genres = Adventure AND cast = "Robert Downey Jr."`,
})
if err != nil {
panic(err)
}
ToJson("HITS : ", resp.Hits)
return nil
}
На этом реализация функционала поиска в приложениях на Meilisearch и Golang завершена.
Читайте также:
- Загрузка файлов в хранилище Cloudflare R2: простое руководство
- Почему стоит использовать GoFr для разработки Golang-бэкенда?
- Реализация шаблона Saga на Go: практический подход
Читайте нас в Telegram, VK и Дзен
Перевод статьи Sadham Hussian M: Go Beyond Basic Search: Enhancing Your Golang App with Meilisearch