Una semplice implementazione del tagging con Elasticsearch

Persistenza in alto

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> GUARDA IL CORSO Questo articolo fa parte di una serie: • Una semplice implementazione di tagging con Elasticsearch (articolo corrente) • Una semplice implementazione di tagging con JPA

• Un'implementazione avanzata della codifica con JPA

• Una semplice implementazione del tagging con MongoDB

1. Panoramica

L'etichettatura è un modello di progettazione comune che ci consente di classificare e filtrare gli elementi nel nostro modello di dati.

In questo articolo, implementeremo la codifica utilizzando Spring ed Elasticsearch. Useremo sia Spring Data che l'API Elasticsearch.

Prima di tutto, non tratteremo le basi per ottenere Elasticsearch e Spring Data: puoi esplorarli qui.

2. Aggiunta di tag

L'implementazione più semplice del tagging è un array di stringhe. Possiamo implementarlo aggiungendo un nuovo campo al nostro modello di dati in questo modo:

@Document(indexName = "blog", type = "article") public class Article { // ... @Field(type = Keyword) private String[] tags; // ... }

Notare l'uso del tipo di campo Parola chiave . Vogliamo solo corrispondenze esatte dei nostri tag per filtrare un risultato. Questo ci consente di utilizzare tag simili ma separati come elasticsearchIsAwesome ed elasticsearchIsTerrible .

I campi analizzati restituirebbero risultati parziali, il che in questo caso è un comportamento sbagliato.

3. Creazione di query

I tag ci consentono di manipolare le nostre query in modi interessanti. Possiamo cercare attraverso di loro come qualsiasi altro campo, oppure possiamo usarli per filtrare i nostri risultati sulle query match_all . Possiamo anche usarli con altre query per rafforzare i nostri risultati.

3.1. Ricerca di tag

Il nuovo campo tag che abbiamo creato sul nostro modello è proprio come ogni altro campo nel nostro indice. Possiamo cercare qualsiasi entità che abbia un tag specifico come questo:

@Query("{\"bool\": {\"must\": [{\"match\": {\"tags\": \"?0\"}}]}}") Page findByTagUsingDeclaredQuery(String tag, Pageable pageable);

Questo esempio utilizza uno Spring Data Repository per costruire la nostra query, ma possiamo altrettanto rapidamente utilizzare un Rest Template per interrogare manualmente il cluster Elasticsearch.

Allo stesso modo, possiamo utilizzare l'API Elasticsearch:

boolQuery().must(termQuery("tags", "elasticsearch"));

Supponiamo di utilizzare i seguenti documenti nel nostro indice:

[ { "id": 1, "title": "Spring Data Elasticsearch", "authors": [ { "name": "John Doe" }, { "name": "John Smith" } ], "tags": [ "elasticsearch", "spring data" ] }, { "id": 2, "title": "Search engines", "authors": [ { "name": "John Doe" } ], "tags": [ "search engines", "tutorial" ] }, { "id": 3, "title": "Second Article About Elasticsearch", "authors": [ { "name": "John Smith" } ], "tags": [ "elasticsearch", "spring data" ] }, { "id": 4, "title": "Elasticsearch Tutorial", "authors": [ { "name": "John Doe" } ], "tags": [ "elasticsearch" ] }, ]

Ora possiamo usare questa query:

Page articleByTags = articleService.findByTagUsingDeclaredQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

3.2. Filtraggio di tutti i documenti

Un modello di progettazione comune consiste nel creare una visualizzazione elenco filtrata nell'interfaccia utente che mostra tutte le entità, ma consente anche all'utente di filtrare in base a criteri diversi.

Supponiamo di voler restituire tutti gli articoli filtrati da qualsiasi tag selezionato dall'utente:

@Query("{\"bool\": {\"must\": " + "{\"match_all\": {}}, \"filter\": {\"term\": {\"tags\": \"?0\" }}}}") Page findByFilteredTagQuery(String tag, Pageable pageable);

Ancora una volta, stiamo usando Spring Data per costruire la nostra query dichiarata.

Di conseguenza, la query che stiamo utilizzando è divisa in due parti. La query sul punteggio è il primo termine, in questo caso match_all . La query del filtro è successiva e indica a Elasticsearch quali risultati scartare.

Ecco come utilizziamo questa query:

Page articleByTags = articleService.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

È importante rendersi conto che sebbene ciò restituisca gli stessi risultati del nostro esempio precedente, questa query avrà prestazioni migliori.

3.3. Filtro delle query

A volte una ricerca restituisce troppi risultati per essere utilizzabile. In tal caso, è utile esporre un meccanismo di filtro in grado di rieseguire la stessa ricerca, solo con i risultati ridotti.

Ecco un esempio in cui restringiamo gli articoli che un autore ha scritto, solo quelli con un tag specifico:

@Query("{\"bool\": {\"must\": " + "{\"match\": {\"authors.name\": \"?0\"}}, " + "\"filter\": {\"term\": {\"tags\": \"?1\" }}}}") Page findByAuthorsNameAndFilteredTagQuery( String name, String tag, Pageable pageable);

Ancora una volta, Spring Data sta facendo tutto il lavoro per noi.

Vediamo anche come costruire noi stessi questa query:

QueryBuilder builder = boolQuery().must( nestedQuery("authors", boolQuery().must(termQuery("authors.name", "doe")), ScoreMode.None)) .filter(termQuery("tags", "elasticsearch"));

Possiamo, ovviamente, usare questa stessa tecnica per filtrare su qualsiasi altro campo del documento. Ma i tag si prestano particolarmente bene a questo caso d'uso.

Ecco come utilizzare la query sopra:

SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder) .build(); List articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); // articles contains [ 1, 4 ] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(4))) );

4. Filtra contesto

Quando creiamo una query, dobbiamo distinguere tra il contesto della query e il contesto del filtro. Ogni query in Elasticsearch ha un contesto di query, quindi dovremmo essere abituati a vederle.

Not every query type supports the Filter Context. Therefore if we want to filter on tags, we need to know which query types we can use.

The bool query has two ways to access the Filter Context. The first parameter, filter, is the one we use above. We can also use a must_not parameter to activate the context.

The next query type we can filter is constant_score. This is useful when uu want to replace the Query Context with the results of the Filter and assign each result the same score.

The final query type that we can filter based on tags is the filter aggregation. This allows us to create aggregation groups based on the results of our filter. In other words, we can group all articles by tag in our aggregation result.

5. Advanced Tagging

So far, we have only talked about tagging using the most basic implementation. The next logical step is to create tags that are themselves key-value pairs. This would allow us to get even fancier with our queries and filters.

For example, we could change our tag field into this:

@Field(type = Nested) private List tags;

Then we'd just change our filters to use nestedQuery types.

Una volta capito come utilizzare le coppie chiave-valore , l'utilizzo di oggetti complessi come tag è un piccolo passo. Non molte implementazioni avranno bisogno di un oggetto completo come tag, ma è bene sapere che abbiamo questa opzione se dovessimo richiederla.

6. Conclusione

In questo articolo, abbiamo trattato le basi dell'implementazione del tagging utilizzando Elasticsearch.

Come sempre, è possibile trovare esempi su GitHub.

Avanti » Una semplice implementazione di tagging con JPA Persistence bottom

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO