data.mdx.frontmatter.hero_image

Azure Search – Wykorzystanie REST API

2018-11-29 | Azure | bd90

Czasami zdarzają się scenariusze, w których:

  • nie potrzebujemy przechowywać kopii danych w chmurze Azure
  • mamy własne data center on-premise, tylko brak nam usługi wyszukiwarki w systemie.

Szukamy wtedy specjalisty od Elastic Search-a, by postawić klaster we własnej infrastrukturze. On nam krzyczy: "Wezmę zlecenie, ale daj mnie worek złota, cztery krowy i rząd dusz". Co zrobić, gdy akurat nie mamy worka złota? Azure ma Search as a Service. Wszystko ładnie pięknie się zgrywa, jednak aby podpiąć Azure Search do bazy Cosmos DB czy Azure SQL musielibyśmy najpierw stworzyć system do synchronizacji naszej bazy on-premise z bazą cloud. Rozwiązanie jak kakałko na wodzie - takie średnie. Szczególnie, jeżeli weźmiemy pod uwagę fakt, że za storage płacimy trzykrotnie:

  • Zakupione środowisko on-premise
  • Baza Cosmos DB / Azure SQL
  • Storage samej usługi Azure Search

I tutaj właśnie przychodzi nam na pomoc Azure Search REST API umożliwiające całkowite zarządzać usługą za pomocą request-ów. Pozwala to wysyłać dokumenty do zindeksowania bezpośrednio do usługi.

Przeszukanie kolekcji

Zacznijmy od rzeczy podstawowej. Za pomocą Azure Search REST API przeszukajmy kolekcję dokumentów. Będziemy potrzebowali:

  • Adres usługi znajdującej się w polu URL na dashboard-zie usługi: https://nasza-nazwa-uslugi.search.windows.net
  • Nazwę naszego indeksu. Na potrzeby tutoriala użyjemy indeksu stworzonego wcześniej, przy okazji pobierania danych z CosmosDB, documentdb-index
  • Wersja API
  • Klucz API

Zalecana wersja API jest zapisana w dokumentacji: api-version=2017-11-11. Natomiast, jeżeli chodzi o klucz API, musimy wyłuskać go z panelu. Jedna uwaga z mojej strony - klucze dzielą się na dwa rodzaje:

  • Admin - Jeden (a w zasadzie to dwa) klucz, by rządzić wszystkimi. Jak się przekonacie w późniejszej części tego artykułu, za jego pomocą można dokonać dowolnych zmian, dlatego ważne, aby korzystać z niego ostrożnie i z rozwagą. Jeżeli niewłaściwa osoba wejdzie w jego posiadanie, to nie ma zmiłuj... Staną się wtedy złe rzeczy. Na szczęście, usługa Azure Search nie pozwoli nam korzystać z niego w celu przeszukiwania kolekcji, ten klucz jest przekazywany jako nagłówek HTTP a nie jako parametr w query string-u.
  • Query key -Posiada on możliwość przeszukiwania kolekcji, natomiast nie można dzięki niemu niczego zmodyfikować. Może być podawany jako parametr api-key w query string-u.

Uzyskanie Query Key

W pierwszej kolejności zajmiemy się uzyskaniem klucza query-key. Nie jest to trudne zadanie, ale na wszelki wypadek zrobiłem screen-y.

W panelu zarządzania usługa Azure Search musimy przejść do zakładki Keys. Znajduje się ona w sekcji Settings po lewej stronie.

Po przejściu do zakładki Keys pokażą się nam dwa klucze Administracyjne. Dlaczego dwa? To wyjaśnię w późniejszej części tego artykułu ;). Na chwilę obecną nas nie interesują. Musimy przejść do podstrony Manage query keys.

W przeglądarce dostaniemy listę wygenerowanych kluczy. Już na starcie Azure Search wygenerował nam jeden klucz. Jeżeli nie chcecie z niego korzystać, wystarczy kliknąć w niebieskiego plusa z napisem Add.

Tak oto weszliśmy w posiadanie query key.

Wykorzystanie Azure Search REST API do przeszukiwania kolekcji

Mając wszystkie potrzebne informacje możemy uruchomić Postman-a i po raz pierwszy użyć Azure Search REST API do otrzymania wyników wyszukiwania.

Składnia samego zapytania HTTP wygląda następująco:

https://{nazwa-usługi}.windows.net/indexes/{nazwa-indeksu}/docs?search={zapytanie}&api-version=2017-11-11&api-key={query-key}

Podstawiając wszystkie wspomniane wartości możemy wysłać żądanie GET na adres:

https://blog-search-post.search.windows.net/indexes/documentdb-index/docs?search=*&api-version=2017-11-11&api-key=CCE1230E85B7328CB058D57F6C77E7C9

Jako że wykorzystałem użyty wcześniej indeks to postman wyświetlił mi przyjemne wyniki wyszukiwania:

{
    "@odata.context": "https://blog-search-post.search.windows.net/indexes('documentdb-index')/$metadata#docs",
    "value": [
        {
            "@search.score": 1,
            "id": "62301688-c98b-8b7b-eaf8-71a1cd65143e",
            "title": "ad dolor est",
            "description": "Enim laboris occaecat duis Lorem veniam consequat dolor cillum. Eiusmod veniam deserunt aliqua culpa tempor officia et cillum id consectetur nostrud consectetur. Et id sit adipisicing esse sit duis magna aliquip veniam proident anim duis.",
            "author_first": "Mcclure",
            "author_last": "Lindsay",
            "tags": [
                "aute",
                "aliqua",
                "nostrud",
                "aliquip",
                "culpa"
            ]
        },
        {
            "@search.score": 1,
            "id": "9580f044-e88b-7592-57f9-e9ebbefb573e",
            "title": "tempor exercitation aute",
            "description": "Adipisicing dolore sint quis elit tempor officia id aliqua pariatur sint officia. Deserunt consectetur reprehenderit nostrud cillum enim ea occaecat ad quis anim consequat sunt. Quis deserunt ea enim nisi nulla nostrud aliqua laborum eiusmod voluptate. Veniam minim dolore excepteur dolore culpa ad mollit amet aute do dolor sunt nostrud. Esse adipisicing amet qui deserunt sunt. Minim qui deserunt esse ut pariatur cupidatat irure occaecat reprehenderit minim veniam incididunt ex sint.",
            "author_first": "Hatfield",
            "author_last": "Kinney",
            "tags": [
                "ipsum",
                "excepteur",
                "eiusmod",
                "id",
                "qui"
            ]
        },
        {
            "@search.score": 1,
            "id": "dbf3667d-a64e-8077-7d50-e107a99a2c3f",
            "title": "ipsum non nisi",
            "description": "Reprehenderit ullamco est qui do commodo fugiat. Veniam in eiusmod velit adipisicing. Velit et elit eiusmod dolor adipisicing sint id ea aliquip. In adipisicing amet excepteur dolor exercitation culpa voluptate laboris pariatur Lorem nostrud.",
            "author_first": "Glenda",
            "author_last": "Hatfield",
            "tags": [
                "officia",
                "sunt",
                "deserunt",
                "occaecat",
                "aute"
            ]
        },
        {
            "@search.score": 1,
            "id": "2b3534a8-5c7c-d37a-3acd-d9b7e282f772",
            "title": "anim sit qui",
            "description": "Excepteur consectetur proident id laborum adipisicing id. Exercitation magna do eiusmod culpa quis laborum consectetur elit aliqua. Aute mollit in nostrud minim elit quis et. Magna aliquip anim incididunt magna mollit quis cupidatat officia adipisicing deserunt. Magna ad adipisicing adipisicing excepteur fugiat enim esse dolor laborum sint. Id duis enim excepteur laboris consectetur aute deserunt consequat commodo elit dolor. Tempor voluptate irure proident incididunt aliqua.",
            "author_first": "Whitaker",
            "author_last": "Vasquez",
            "tags": [
                "laborum",
                "anim",
                "Lorem",
                "non",
                "exercitation"
            ]
        }
    ]
}

Yay! Udało się! Wykorzystaliśmy Azure Search REST API aby otrzymać wyniki wyszukiwania. I w sumie na tym kończą się możliwości wykorzystania query key. Dlatego ruszajmy dalej, aby zobaczyć jak możemy wykorzystać Admin Key.

Z wielką mocą przychodzi wielka odpowiedzialność

Dlaczego mamy aż dwa klucze? Z prostej przyczyny - w przypadku konieczności wygenerowania nowego klucza przepinamy naszą aplikację na zapasowy, generujemy nowy główny i ponownie przepinamy . Zabieg taki pozwala osiągnąć wyższe SLA naszej usługi.

To teraz zobaczymy co możemy zdziałać z tak potężnym kluczem.

Tworzenie indeksu

Za pomocą Azure Search Rest API możemy utworzyć indeks, do którego trafią zdefiniowane przez nas dokumenty. Zapytanie musimy wysłać na adres:

https://{nazwa-usługi}.search.windows.net/indexes/?api-version=2017-11-11

Uważajcie na znak ? , bez niego nie otrzymamy odpowiedzi w stylu 404 Not Found tylko otrzymamy 400 Bad Request i informację, że indeks musi się składać z małych liter, cyfr, myślników etc. W przypadku żądania o stworzenie index-u może to być mylące, ponieważ w ciele żądania znajduje się zdefiniowana przez nas nazwa nowego indeksu. Dzieje się tak, gdyż Azure Search REST API, w przypadku braku ? po /indexes/, traktuję nazwę api-version=2017-11-11 jako nazwę poszukiwanego przez nas indeksu, na którym chcemy wykonywać operację.

Sama struktura zapytania, służąca do stworzenia indeksu, nie jest jakoś skomplikowana. Pamiętając o metodzie tworzenia indeksu, opisanej w poprzednim artykule na temat Azure Search, pewnie kojarzycie formularz, gdzie zaznaczaliśmy checkbox-y informujące po jakich polach możemy przeszukiwać, filtrować, jakie będą zwracane. Teraz musimy to odtworzyć w notacji JSON, pod kluczem fields. Indeks musimy zawrzeć w polu Name.

Najlepiej będzie to wyjaśnić na przykładzie. Weźmy pod lupę stworzenie takiego samego indeksu jak w poprzednim poście:

POST: https://blog-search-post.search.windows.net/indexes/?api-version=2017-11-11

Content-Type: application/json
api-key: 254F21A3A67489308EF8D0251394075E
{
    "name": "books",
    "fields": [
    	{"name": "id", "type": "Edm.String", "key": true, "searchable": true, "sortable": true, "retrievable": true},
    	{"name": "title", "type": "Edm.String", "searchable": true, "sortable": true, "retrievable": true},
    	{"name": "description", "type": "Edm.String", "searchable": true, "retrievable": true},
    	{"name": "author_first", "type": "Edm.String", "searchable": true, "retrievable": true},
    	{"name": "author_last", "type": "Edm.String", "searchable": true, "retrievable": true},
        {"name": "tags", "type": "Collection(Edm.String)", "searchable": true, "retrievable": true, "filterable": true, "facetable": true}
    ]
}

Ważnym element w tym zapytaniu jest dodanie naszego api-key w nagłówkach żądania, już nie musimy dodawać klucza w query string-u. Bez tego request nie przejdzie autoryzacji. Jak widać nasz indeks składa się z sześciu pól, które mają zdefiniowane typy, zdefiniowane odpowiednie flagi. Co ważne nasz indeks musi zawierać klucz główny, czyli jakaś unikalną wartość dla każdego obiektu, inaczej nie będziemy mogli utworzyć indeksu.

Jako odpowiedź otrzymamy oczywiście JSON-a, z potwierdzeniem zdefiniowania indeksu. Możemy w nim znaleźć sporo ciekawych rzeczy o których jeszcze nie pisałem na tym blogu takich jak indexAnalyzer czy też mapy synonimów. W przyszłości napewno wszystkie te propertis-y omówimy. Przykładową odpowiedź widzicie poniżej.

{
    "@odata.context": "https://blog-search-post.search.windows.net/$metadata#indexes/$entity",
    "@odata.etag": "0x8D6557636782EA4",
    "name": "books",
    "fields": [
        {
            "name": "id",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": true,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        },
        {
            "name": "title",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        },
        {
            "name": "description",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        },
        {
            "name": "author_first",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        },
        {
            "name": "author_last",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        },
        {
            "name": "tags",
            "type": "Collection(Edm.String)",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": false,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "synonymMaps": []
        }
    ],
    "scoringProfiles": [],
    "defaultScoringProfile": null,
    "corsOptions": null,
    "suggesters": [],
    "analyzers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": []
}

Dodawanie treści do wyszukiwarki

Po stworzeniu indeksu możemy przejść do jego uzupełniania. Jak to zwykle bywa przy korzystaniu z jakiegoś REST API musimy po prostu skonstruować odpowiedni request, który wypuścimy do odpowiedniego endpoint-a.

Jeżeli właśnie kiwasz głową myśląc: "tak, to faktycznie częste/standardowe/tak bywa" to pamiętaj by czasem wyjść na słońce i poćwiczyć. Ja zapomniałem i teraz przeraża mnie ilość x - ów na metkach moich ubrań :P

Wracając: aby dodać treści do wyszukiwarki musimy uderzyć pod adres https://{nazwa-usługi}.search.windwos.net/indexes/{nazwa-indeksu}/docs/index?api-version=2017-11-11. Za pomocą jednego żądania dodajemy / modyfikujemy wiele obiektów naraz. Jest to przemyślana usługa , bardzo user friendly.

W ciele żądania musimy przesłać obiekt koniecznie zawierający klucz value. Powinna w nim znajdować się tablica z obiektami, które chcemy zmienić, dodać lub usunąć. Dlatego każdy obiekt powinien się składać z minimalnie dwóch elementów: deklaracji rodzaju akcji jaką chcemy wykonać (za pomocą klucza @search.action) i definicji klucza głównego, do którego chcemy się odwołać.

Są 4 rodzaje akcji jakie możemy wykonać:

  • upload - dodawany do kolekcji. Jeżeli już istnieje dokument o takim kluczu głównym to zostanie on zastąpiony.
  • merge - dokument o podanym kluczu głównym zostanie zmodyfikowany o przesłane pola. Jeżeli dokument o podanym kluczu głównym nie istnieje to wtedy otrzymamy błąd.
  • mergeOrUpload - zachowuje się podobnie do merge, z tą różnicą, że nie znalezienie dokumentu o podanym kluczu głównym powoduje zmianę zachowania na tą przypisaną akcji upload.
  • delete - usunie dokument o podanym kluczu głównym z kolekcji

Teraz możemy zdefiniować żądanie tak jak poniżej:

https://blog-search-post.search.windows.net/indexes/books/docs/index?api-version=2017-11-11

{  
  "value": [  
  {
    "@search.action": "upload",
    "id": "5bff209a087bdc4f3dd1a627",
    "title": "culpa est deserunt",
    "author_first": "John",
    "author_last": "Smith",
    "description": "Do nostrud ex exercitation voluptate eiusmod laboris laborum tempor ullamco duis. Duis est Lorem elit duis exercitation sit deserunt pariatur proident minim mollit dolore. Labore qui sint aliquip cillum et consequat sit ex ipsum culpa velit do consectetur amet. Elit irure deserunt excepteur in do commodo aute. Ad elit Lorem consequat sunt do deserunt eu non cupidatat. Elit excepteur occaecat adipisicing enim consectetur sit mollit labore. Qui elit nostrud enim veniam enim aliquip deserunt do elit ea.",
    "tags": [
      "irure",
      "et",
      "est",
      "non",
      "cupidatat"
    ]
  },
  {
    "@search.action": "upload",
    "id": "5bff209a831f7d5dd3b32708",
    "title": "sint deserunt nulla",
    "author_first": "Molina",
    "author_last": "Ramirez",
    "description": "Esse irure excepteur voluptate Lorem cillum ipsum qui tempor consequat aliqua sunt. Nulla irure aliqua tempor exercitation excepteur aute eiusmod labore deserunt ad sit. Culpa quis exercitation et esse ullamco do nostrud deserunt.",
    "tags": [
      "occaecat",
      "reprehenderit",
      "minim",
      "id",
      "amet"
    ]
  }
  ]  
}

W odpowiedzi otrzymamy informacje o poprawnym (lub nie) zapisaniu dokumentu. Dla każdego elementu powstanie osobny wpis w tablicy.

{
    "@odata.context": "https://blog-search-post.search.windows.net/indexes('books')/$metadata#Collection(Microsoft.Azure.Search.V2017_11_11.IndexResult)",
    "value": [
        {
            "key": "5bff209a087bdc4f3dd1a627",
            "status": true,
            "errorMessage": null,
            "statusCode": 201
        },
        {
            "key": "5bff209a831f7d5dd3b32708",
            "status": true,
            "errorMessage": null,
            "statusCode": 201
        }
    ]
}

Dla przykładu, chcąc usunąć dany dokument z kolekcji, wystarczy wykonać poniższe zapytanie pod ten sam adres:

{  
  "value": [  
    {  
      "@search.action": "delete",  
      "id": "5bff209a087bdc4f3dd1a627"  
    }  
  ]  
}

Podsumowanie

Wow, to jest chyba najdłuższy artykuł w historii mojego bloga. Za tydzień omówimy .NET SDK dla usługi Azure Search i jak nas to wspomoże aby pisać aplikację, która będzie komunikować się z tą usługą.

Jeżeli nie chcecie przegapić nowych wpisów: zapiszcie się na newsletter (Jest w stopce). Zero spamu, maksymalnie jeden, treściwy mail w tygodniu. Serdecznie zapraszam :)

Dzięki za przeczytanie tego artykułu i jak zwykle do następnego!

Cześć

By Bd90 | 29-11-2018 | Azure