Поиск является неотъемлемой частью любого приложения и возможность найти что-либо через строку поиска. Есть много способов сделать это, я видел как это сделано на стороне клиента с помощью цикла, или же с полнотекстовой индексацией в Postgres или MySQL. Но наступает ситуация, где нам нужен отдельный поисковой сервис, и для этого часто выбирают Elasticsearch.
В этой статье я покажу как сделать простой поисковой микросервис, используя язык программирования Golang. Мы будем искать пользователей по электронной почте, никнейму и настоящему имени. Все исходники вы можете найти на GitHub.
Архитектура
Прежде чем начать писать код давайте разберемся как это должно работать.
Важно понимать, что Elasticsearch не должен подвергаться прямому воздействию клиента, поэтому создание промежуточного микросервиса имеет важное значение.
В этом примере для микросервиса потребуется один endpoint для поиска. Также нам надо заполнить кеш Elasticsearch’а, поэтому нам понадобится второй endpoint. В реальном приложении лучше использовать какую-то очередь для набора Producer и Consumer (подробнее об этом описано здесь), чтобы заполнить систему. Однако мы не будем к этому прибегать в этой статье.
Мы будем использовать в качестве двух endpoint’ов следующее:
/search
/populate
Эти endpoint’ы будут получать параметры.
Поиск
Поисковой endpoint должен получать два параметра, во-первых, нам нужно указать строку, которую будем искать. Также нам нужна простейшая пагинация, чтобы указать сколько результатов поиска вернуть и сколько нужно результатов проигнорировать в начале поиска.
- q - строка для поискового запроса
- from - начальный индекс в списке результатов
- size - количество результатов для возвращения
Заполнение
Заполняющий endpoint получает только один параметр, который используется для определения количества результатов для генерирования.
- number - количество результатов для вставки в Elasticsearch
Endpoint’ы
Теперь, когда мы знаем структуру нашего микросервиса и какие endpoint’ы и параметры нам нужны, давайте начнем с писать код!
Во-первых, сначала создадим главный файл main.go
с endpoint’ами и базовым HTTP-сервером.
1 | package main |
Поскольку это достаточно маленький микросервис, мы создадим endpoint’ы непосредственно в файле main.go
. Но в продакшн или если у вас больше логики, лучше перенести эти endpoint’ы в отдельный файлы.
Теперь, давайте напишем поисковой endpoint.
Нам нужно будет получить get
параметры, что относительно неуклюже и долго, когда вы используете стандартную библиотеку Golang, поэтому я не буду описывать это в статье, если вам интересно, то метод описан на GitHub.
После получения параметров мы проверяем, правильны ли они, если нет, то отправляем ответ, что это недопустимый запрос. После этого мы передаем параметры term
, from
и size
в нашу функцию поиска в Elasticsearch, которую мы создадим в следующем разделе. Функция будет опрашивать Elasticsearch и возвращать результаты, а также ошибку, если таковая случится. Оттуда мы будем формировать json ответ и отправлять его клиенту. Довольно просто.
1 | mux.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) { |
Следующая задача - создать endpoint для заполнения Elasticsearch. Наличие endpoint’а поиска не имеет смысла, если у нас нет данных для поиска!
Первое, что мы делаем, это получить get параметр number
и переобразовать его в целое число. Если он неправильный, мы возвращаем отрицательный ответ клиенту. Следующий шаг - заполнить Elasticsearch, это делается с помощью вспомогательной функции, которую мы сделаем позже в этой статье. Наконец, мы возвращаем ошибку, если функция заполнения отдает ошибку.
1 | mux.HandleFunc("/populate", func(w http.ResponseWriter, req *http.Request) { |
Теперь, когда мы создали endpoint’ы, нам нужно связать все это вместе с функциями Elasticsearch.
Elasticsearch Helpers
Последняя часть создания микросервиса это подключение к Elasticsearch. Давайте создадим helper’ы сейчас, мы сделаем это в новом файле elastic.go
.
Давайте посмотрим, как должен выглядеть файл, сначала нам нужна json-модель для Elasticsearch. Вам нужно создать структуру пользователя User
со следующими полями:
Username
с json-декораторомusername
Email
с json-декораторомemail
RealName
с json-декораторомreal_name
Нам также необходимо создать функции Populate
и Search
.
1 | package main |
Теперь, когда мы написали макет, следующий шаг - создать методы. Мы используем github.com/olivere/elastic для работы с Elasticsearch.
Теперь создадим логику для функции поиска. Первый шаг это соединиться к поиску Elastic и мы сделаем это создав новый клиент. Если соединение прошло успешно мы формируем запрос. Мы используем многопоточный запрос, если вам интересны другие параметры вы можете найти их в документации Elasticsearch.
Наконец, мы вызываем метод Search
и передаём корректные параметры, используем индекс пользователя, и передаём ранее созданный многопоточный запрос и добавляем пагинацию.
В ответ мы получим много довольно интересной информации, но для нашего микросервиса мы не занимаемся журналированием или аналитикой, поэтому нас интересуют только результаты поиска. Мы проходимся циклом по результатам и используем json.Unmarshal
, чтобы добавить пользователей в нашу структуру и затем добавляем структуру в массив, чтобы передать клиенту.
1 | func Search(term string, from, size int) ([]*User, error) { |
Функция Populate
начинается почти также, нам нужно подключится к клиенту. Но мы проверяем, существует ли индекс, и если нет, то создаем его. Затем мы используем библиотеку для генерации фальшивых пользователей, генерируем и передаем их в функцию, и вставляем в Elasticsearch.
1 | func Populate(number int) error { |
Настраиваем Docker
Для этой системы я выбираю docker-compose, чтобы связать поисковый микросервис с Elasticsearch. В документации Elasticsearch есть отличная статья по этому поводу. Я последовал за этой статьей с несколькими небольшими изменениями. Единственная часть, на которой нам нужно сосредоточиться, - это Dockerfile, а также привязка его к Elasticsearch через docker-compose.
Dockerfile довольно простой, мы загружаем docker образ Golang 1.10 alpine
, добавляем dep
для нашего менеджера зависимостей, добавляем путь к коду в GOPATH
, получаем зависимости с dep
, компилируем программу и запускаем ее.
1 | FROM golang:1.10-alpine |
Файл docker-compose.yml
стандартный, он основан на этом с добавлением службы поиска и добавлением в esnet
.
1 | version: '2.2' |
Чтобы проверить и убедиться, что файлы работают вместе, как и планировалось, выполните docker-compose build
для построения поискового микросервиса, а затем запустите docker-compose up
для запуска кода. Elasticsearch будет собран в docker-compose up
, поэтому не беспокойтесь, если вы заметите это.
Запуск!
Теперь, когда микросервис написан и взаимодействует с Elasticsearch, пришло время проверить его. Давайте запустим наши endpoint’ы и посмотрим, что произойдет! Во-первых, мы должны заполнить Elasticsearch результатами, поэтому давайте запустим endpoint /populate
. Вы можете запустить его с помощью ссылки ниже:1
http://localhost:8080/populate?number=100
После заполнения следующий шаг - поиск результатов. Поскольку мы используем Faker, имена не известны заранее, поэтому вам, возможно, придется попробовать пару имен, прежде чем вы увидите результаты. Вы можете запустить поисковой endpoint с помощью ссылки ниже:1
http://localhost:8080/search?q=ryan&from=0&size=20
Заключение
Эта статья показывает как можно легко сделать микросервис для таких сервисов, как поиск, и подключить его для работы с Elasticsearch. В реальной системе вы не будете генерировать случайные данные, вы, вероятно, будете использовать webhooks или системы очередей для заполнения поиска.
Источник: ryanmccue