Функционал поиска  —  неотъемлемый компонент современных веб-приложений, с которым у пользователей расширяются возможности быстро находить нужную информацию в огромных массивах данных, вводя запросы и получая соответствующие результаты в условиях усовершенствованных навигации и пользовательского взаимодействия. Маркетплейсы, соцсети, базы знаний, инструменты повышения продуктивности  —  везде эффективный поиск важен для удовлетворенности и вовлеченности пользователей.

Функционал поиска реализуется с помощью структур данных, индексирования, алгоритмов поиска, библиотек 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, запущенный на localhost:7700

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 завершена.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Sadham Hussian M: Go Beyond Basic Search: Enhancing Your Golang App with Meilisearch

Предыдущая статьяКак предотвратить утечки памяти в Android-приложении