ЀункцияAPI SearchkitAPI ReactiveSearch
Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ запросаGraphQL APIREST API (ΠΏΠΎΡ…ΠΎΠΆΠΈΠ΅ Π½Π° Elasticsearch DSL)
ΠšΠ°ΡΡ‚ΠΎΠΌΠΈΠ·Π°Ρ†ΠΈΡΠ›ΠΎΠ³ΠΈΠΊΠ° запросов опрСдСляСтся Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Π½Π° сСрвСрСЛогика запросов настраиваСтся Ρ‡Π΅Ρ€Π΅Π· REST API ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ Π½Π° Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π΅
Π‘Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒΠ Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½Ρ‹ΠΉ слой (GraphQL-эндпоинт Π·Π°Ρ‰ΠΈΡ‰Π°Π΅Ρ‚ Elasticsearch). ВмСсто ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ запроса Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ Π² Elasticsearch API, Π²Ρ‹ отправляСтС GraphQL запрос (с ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ поиска Π² Π²ΠΈΠ΄Π΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…) Π² Searchkit API. Searchkit Π·Π°Ρ‚Π΅ΠΌ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΈΡ‚ этот GraphQL запрос Π² Elasticsearch DSLΠ Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΎΡ‡Π½Ρ‹ΠΉ слой (REST-эндпоинт Π·Π°Ρ‰ΠΈΡ‰Π°Π΅Ρ‚ Elasticsearch). Запрос DSL Π±Π΅Π· Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… настроСк, Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π΅Π½ Ρ‚ΠΎΠΌΡƒ, Ρ‡Ρ‚ΠΎ использовался Π±Ρ‹ для Elasticsearch
ΠŸΡ€ΠΎΡΡ‚ΠΎΡ‚Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΡΠ£ΠΏΡ€ΠΎΡ‰Π°Π΅Ρ‚ использованиС Elasticsearch с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ GraphQL (трСбуСтся Π·Π½Π°Π½ΠΈΠ΅ Π² ΠΊΠΎΠΌΠ°Π½Π΄Π΅)Запросы Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π½Ρ‹ стандартным для Elasticsearch API, Π½ΠΎ позволяСт ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Π΅/ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ Π΄Π°Π½Π½Ρ‹Ρ… Π² Elastic Π²ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ
Π‘Π»ΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ настройкиБрСдняя (трСбуСтся настройка сСрвСра для GraphQL)БрСдняя (трСбуСтся настройка сСрвСра для API)
ДокумСнтацияДокумСнтация SearchkitДокумСнтация ReactiveSearch

АрхитСктура Searchkit

Как Π΄Π΅Π»Π°Ρ‚ΡŒ запросы ΠΊ API Searchkit ΠΈ ReactiveSearch


1. ΠŸΡ€ΠΈΠΌΠ΅Ρ€ запроса Searchkit (GraphQL)

ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ backend для Searchkit

import { createSearchkitConfig, SearchkitSchema } from "@searchkit/schema";
import { ApolloServer } from "apollo-server";
 
const searchkitConfig = createSearchkitConfig({
  host: "http://localhost:9200", // Π’Π°Ρˆ Elasticsearch-эндпоинт
  index: "ecommerce",            // Имя индСкса Elasticsearch
  hits: {
    fields: ["name", "brand.name", "categories.name", "picture"],
  },
  query: {
    fields: ["name", "full_name", "brand.name"],
  },
  facets: [
    { field: "brand.name.keyword", label: "Brand", type: "terms" },
    { field: "categories.name.keyword", label: "Category", type: "terms" },
  ],
});
 
const server = new ApolloServer({
  schema: SearchkitSchema(searchkitConfig),
});
 
server.listen().then(({ url }) => {
  console.log(`πŸš€ Π‘Π΅Ρ€Π²Π΅Ρ€ Π³ΠΎΡ‚ΠΎΠ² Π½Π° ${url}`);
});
 
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ GraphQL запроса

query SearchProducts($query: String, $filters: [SearchkitFilterSet]) {
  results(query: $query, filters: $filters) {
    hits {
      items {
        id
        fields {
          name
          brand {
            name
          }
          categories {
            name
          }
          picture
        }
      }
    }
    facets {
      id
      label
      entries {
        label
        count
        id
        isSelected
      }
    }
  }
}
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… запроса

{
  "query": "Electrolux",
  "filters": [
    {
      "id": "brand.name.keyword",
      "value": "Electrolux"
    }
  ]
}
 
{
  "query": "query SearchProducts($query: String, $filters: [SearchkitFilterSet]) { ... }",
  "variables": {
    "query": "Electrolux",
    "filters": [{ "id": "brand.name.keyword", "value": "Electrolux" }]
  }
}
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚Π²Π΅Ρ‚Π°

{
  "data": {
    "results": {
      "hits": {
        "items": [
          {
            "id": "32c03957-3738-45a0-b5d5-34cba1cedd6c",
            "fields": {
              "name": "ВСпловСнтилятор Electrolux EFH/C-5115",
              "brand": {
                "name": "Electrolux"
              },
              "categories": [
                {
                  "name": "ВСпловСнтиляторы"
                }
              ],
              "picture": "https://rkcdn.ru/products/ba10f5f4-7c4a-11e7-9ead-ac162d7b6f40/thumb_big.jpg"
            }
          }
        ]
      },
      "facets": [
        {
          "id": "brand.name.keyword",
          "label": "Brand",
          "entries": [
            {
              "label": "Electrolux",
              "count": 100,
              "id": "Electrolux",
              "isSelected": true
            }
          ]
        }
      ]
    }
  }
}
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π° (React с Apollo Client)

НиТС прСдставлСн простой ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ React, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСт запрос ΠΊ GraphQL API Searchkit с использованиСм Apollo Client:

import { ApolloClient, InMemoryCache, gql, useQuery } from "@apollo/client";
 
const client = new ApolloClient({
  uri: "http://localhost:4000/graphql", // Π’Π°Ρˆ GraphQL эндпоинт Searchkit
  cache: new InMemoryCache(),
});
 
const SEARCH_QUERY = gql`
  query SearchProducts($query: String) {
    results(query: $query) {
      hits {
        items {
          id
          fields {
            name
            brand {
              name
            }
            picture
          }
        }
      }
    }
  }
`;
 
function ProductSearch({ searchQuery }) {
  const { loading, error, data } = useQuery(SEARCH_QUERY, {
    variables: { query: searchQuery },
    client,
  });
 
  if (loading) return <p>Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°...</p>;
  if (error) return <p>Ошибка: {error.message}</p>;
 
  return (
    <div>
      {data.results.hits.items.map((item) => (
        <div key={item.id}>
          <h2>{item.fields.name}</h2>
          <p>{item.fields.brand.name}</p>
          <img src={item.fields.picture} alt={item.fields.name} />
        </div>
      ))}
    </div>
  );
}
 
export default ProductSearch;
 

2. ΠŸΡ€ΠΈΠΌΠ΅Ρ€ запроса API ReactiveSearch (REST)

Настройка API ReactiveSearch

настройка для сСрвСра Π½Π° Node.js с использованиСм ReactiveSearch API:

const express = require("express");
const { ReactiveSearchApi } = require("@appbaseio/reactivesearch-api");
 
const app = express();
const api = new ReactiveSearchApi({
  host: "http://localhost:9200",    // Π’Π°Ρˆ Elasticsearch эндпоинт
  credentials: "username:password", // ΠžΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ: ΡƒΡ‡Π΅Ρ‚Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ для Elasticsearch
  enableTelemetry: false,
});
 
// ΠœΠΎΠ½Ρ‚ΠΈΡ€ΡƒΠ΅ΠΌ API ReactiveSearch Π½Π° ΠΌΠ°Ρ€ΡˆΡ€ΡƒΡ‚, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `/api`
app.use("/api", api.init());
 
app.listen(4001, () => {
  console.log("ReactiveSearch API Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π½Π° http://localhost:4001/api");
});
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ REST запроса

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ POST-запросы Π½Π° http://localhost:4001/api/ecommerce/_search. НапримСр:

{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "Electrolux",
            "fields": ["name", "full_name", "brand.name"]
          }
        }
      ],
      "filter": [
        {
          "term": { "brand.name.keyword": "Electrolux" }
        }
      ]
    }
  }
}
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚Π²Π΅Ρ‚Π°

{
  "took": 12,
  "timed_out": false,
  "hits": {
    "total": 1,
    "max_score": 1.0,
    "hits": [
      {
        "_index": "ecommerce",
        "_id": "32c03957-3738-45a0-b5d5-34cba1cedd6c",
        "_score": 1.0,
        "_source": {
          "name": "ВСпловСнтилятор Electrolux EFH/C-5115",
          "brand": { "name": "Electrolux" },
          "categories": [{ "name": "ВСпловСнтиляторы" }],
          "picture": "https://rkcdn.ru/products/ba10f5f4-thumb_big.jpg"
        }
      }
    ]
  }
}
 

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π°

НиТС прСдставлСн простой ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ React, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСт запрос ΠΊ API ReactiveSearch с использованиСм Axios:

import React, { useState, useEffect } from "react";
import axios from "axios";
 
function ProductSearch({ searchQuery }) {
  const [results, setResults] = useState([]);
 
  useEffect(() => {
    axios
      .post("http://localhost:4001/api/ecommerce/_search", {
        query: {
          bool: {
            must: [
              {
                multi_match: {
                  query: searchQuery,
                  fields: ["name", "full_name", "brand.name"],
                },
              },
            ],
          },
        },
      })
      .then((response) => {
        setResults(response.data.hits.hits);
      })
      .catch((error) => {
        console.error("Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ Π΄Π°Π½Π½Ρ‹Ρ…", error);
      });
  }, [searchQuery]);
 
  return (
    <div>
      {results.map((result) => (
        <div key={result._id}>
          <h2>{result._source.name}</h2>
          <p>{result._source.brand.name}</p>
          <img src={result._source.picture} alt={result._source.name} />
        </div>
      ))}
    </div>
  );
}
 
export default ProductSearch;
 

3. РСзюмС

  1. GraphQL vs. DSL

    • ReactiveSearch ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ сырой Elasticsearch DSL ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒ Π΅Π³ΠΎ Π² Elasticsearch—фактичСски дСйствуя ΠΊΠ°ΠΊ простой прокси.
    • Searchkit, с Π΄Ρ€ΡƒΠ³ΠΎΠΉ стороны, прСдоставляСт GraphQL-эндпоинт. ВмСсто ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ Elasticsearch DSL Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ Π² ES, Π²Ρ‹ отправляСтС GraphQL запрос (с вашими ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ поиска ΠΊΠ°ΠΊ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌΠΈ). Searchkit Π·Π°Ρ‚Π΅ΠΌ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΈΡ‚ этот GraphQL запрос Π² Elasticsearch DSL.
  2. БСрвСрная конфигурация

    • Π’ Searchkit Π²Ρ‹ опрСдСляСтС, ΠΊΠ°ΠΊ запросы ΠΈ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Ρ‹ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ маппится Π½Π° поля Elasticsearch Π² вашСй ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ Searchkit.
    • Когда ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ запрос GraphQL, Searchkit ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ эту ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ для построСния Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ DSL запроса. ΠžΠ±Ρ‹Ρ‡Π½ΠΎ Π²Ρ‹ Π½Π΅ ΠΏΠ΅Ρ€Π΅Π΄Π°Π΅Ρ‚Π΅ сырой DSL с Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π°; ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ ΠΈΠ· GraphQL β†’ DSL происходит Π²Π½ΡƒΡ‚Ρ€ΠΈ.
  3. Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ

    • Searchkit ΠΌΠΎΠΆΠ΅Ρ‚ автоматичСски ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ фасСты, ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ, ΠΌΠ½ΠΎΠ³ΠΎΡƒΡ€ΠΎΠ²Π½Π΅Π²Ρ‹Π΅ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Ρ‹ ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠ΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ, ΡƒΠ΄ΠΎΠ±Π½Ρ‹Π΅ для элСктронной ΠΊΠΎΠΌΠΌΠ΅Ρ€Ρ†ΠΈΠΈ. Π­Ρ‚ΠΈ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π½Π°ΡΡ‚Ρ€Π°ΠΈΠ²Π°ΡŽΡ‚ΡΡ Π½Π° сСрвСрС Searchkit.
    • GraphQL API ΠΌΠΎΠΆΠ΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ ΡƒΠΆΠ΅ структурированныС Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ поиска ΠΈ Π΄Π°Π½Π½Ρ‹Π΅ фасСтов Π½Π° ваш Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄.
  4. ΠžΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΠΈΠ΅ прямого эндпоинта DSL

    • Searchkit Π½Π΅ прСдоставляСт эндпоинт для запросов DSL. Если Π²Ρ‹ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒ DSL Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ, Π²Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ Π±ΡƒΠ΄Π΅Ρ‚ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ кастомный сСрвСрный слой, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΡ‚ ΠΈΠ»ΠΈ Ρ€Π°ΡΡˆΠΈΡ€ΡΠ΅Ρ‚ ΡΡ‚Π°Π½Π΄Π°Ρ€Ρ‚Π½ΡƒΡŽ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ GraphQL Searchkit.

  • ОбС систСмы, ReactiveSearch ΠΈ Searchkit, находятся ΠΌΠ΅ΠΆΠ΄Ρƒ вашим Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄ΠΎΠΌ ΠΈ Elasticsearch.
  • API ReactiveSearch ΠΌΠΎΠΆΠ΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ прокси для ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Elasticsearch DSL (с Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ функциями).
  • Searchkit прСдставляСт собой GraphQL Π°Π±ΡΡ‚Ρ€Π°ΠΊΡ†ΠΈΡŽ. Π’Ρ‹ отправляСтС GraphQL запросы, ΠΈ ΠΎΠ½ автоматичСски ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΈΡ‚ ΠΈΡ… Π² DSL β€” Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, Π²Ρ‹ Π½Π΅ Π²ΠΈΠ΄ΠΈΡ‚Π΅ ΠΈ Π½Π΅ отправляСтС Elasticsearch DSL с Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π°.