Update documentation.

Original Pull Request #2361
Closes #2360
This commit is contained in:
Peter-Josef Meisch 2022-11-10 21:41:10 +01:00 committed by GitHub
parent f8ddf16c0c
commit d7e42fcb76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 150 deletions

View File

@ -37,7 +37,7 @@ built and tested.
[cols="^,^,^,^,^",options="header"] [cols="^,^,^,^,^",options="header"]
|=== |===
| Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot
| 2022.0 (Turing) | 5.0.x | 8.5.0 | 6.0.x | 3.0.x? | 2022.0 (Turing) | 5.0.x | 8.5.0 | 6.0.x | 3.0.x
| 2021.2 (Raj) | 4.4.x | 7.17.3 | 5.3.x | 2.7.x | 2021.2 (Raj) | 4.4.x | 7.17.3 | 5.3.x | 2.7.x
| 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x | 2021.1 (Q) | 4.3.x | 7.15.2 | 5.3.x | 2.6.x
| 2021.0 (Pascal) | 4.2.xfootnote:oom[Out of maintenance] | 7.12.0 | 5.3.x | 2.5.x | 2021.0 (Pascal) | 4.2.xfootnote:oom[Out of maintenance] | 7.12.0 | 5.3.x | 2.5.x

View File

@ -3,8 +3,10 @@
This chapter illustrates configuration and usage of supported Elasticsearch client implementations. This chapter illustrates configuration and usage of supported Elasticsearch client implementations.
Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Spring Data Elasticsearch operates upon an Elasticsearch client (provided by Elasticsearch client libraries) that is
Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>. connected to a single Elasticsearch node or a cluster.
Although the Elasticsearch Client can be used directly to work with the cluster, applications using Spring Data
Elasticsearch normally use the higher level abstractions of <<elasticsearch.operations>> and <<elasticsearch.repositories>>.
[[elasticsearch.clients.restclient]] [[elasticsearch.clients.restclient]]
== Imperative Rest Client == Imperative Rest Client
@ -21,14 +23,20 @@ public class MyClientConfig extends ElasticsearchConfiguration {
@Override @Override
public ClientConfiguration clientConfiguration() { public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() // return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200") // .connectedTo("localhost:9200")
.build(); .build();
} }
} }
----
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
// ... The following beans can then be injected in other Spring components:
====
[source,java]
----
@Autowired @Autowired
ElasticsearchOperations operations; <.> ElasticsearchOperations operations; <.>
@ -39,11 +47,8 @@ ElasticsearchClient elasticsearchClient; <.>
RestClient restClient; <.> RestClient restClient; <.>
---- ----
the following can be injected:
<.> an implementation of `ElasticsearchOperations` <.> an implementation of `ElasticsearchOperations`
<.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used. <.> the `co.elastic.clients.elasticsearch.ElasticsearchClient` that is used.
This is new Elasticsearch client implementation.
<.> the low level `RestClient` from the Elasticsearch libraries <.> the low level `RestClient` from the Elasticsearch libraries
==== ====
@ -61,18 +66,24 @@ When working with the reactive stack, the configuration must be derived from a d
import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchConfiguration;
@Configuration @Configuration
public class MyClientConfig extends ElasticsearchConfiguration { public class MyClientConfig extends ReactiveElasticsearchConfiguration {
@Override @Override
public ClientConfiguration clientConfiguration() { public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() // return ClientConfiguration.builder() <.>
.connectedTo("localhost:9200") // .connectedTo("localhost:9200")
.build(); .build();
} }
} }
----
<.> for a detailed description of the builder methods see <<elasticsearch.clients.configuration>>
====
// ... The following beans can then be injected in other Spring components:
====
[source,java]
----
@Autowired @Autowired
ReactiveElasticsearchOperations operations; <.> ReactiveElasticsearchOperations operations; <.>
@ -85,9 +96,9 @@ RestClient restClient; <.>
the following can be injected: the following can be injected:
<.> an implementation of `ElasticsearchOperations` <.> an implementation of `ReactiveElasticsearchOperations`
<.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used. <.> the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` that is used.
This is based on the new Elasticsearch client implementation. This is a reactive implementation based on the Elasticsearch client implementation.
<.> the low level `RestClient` from the Elasticsearch libraries <.> the low level `RestClient` from the Elasticsearch libraries
==== ====
@ -97,8 +108,14 @@ When using repositories, this instance is used under the hood as well.
[[elasticsearch.clients.resthighlevelclient]] [[elasticsearch.clients.resthighlevelclient]]
== High Level REST Client (deprecated) == High Level REST Client (deprecated)
The Java RestHighLevelClient is deprecated, but still can be configured like shown (read [CAUTION]
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well): ====
The Elasticsearch Java RestHighLevelClient is deprecated, but still can be configured like shown (make sure to read
<<elasticsearch-migration-guide-4.4-5.0.old-client>> as well).
It should only be used to access an Elasticsearch cluster running version 7, even with the compatibility headers set
there are cases where the `RestHighLevelClient` cannot read the responses sent from a version 8 cluster.
====
.RestHighLevelClient .RestHighLevelClient
==== ====
@ -127,15 +144,6 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
RestHighLevelClient highLevelClient; RestHighLevelClient highLevelClient;
RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3> RestClient lowLevelClient = highLevelClient.lowLevelClient(); <3>
// ...
IndexRequest request = new IndexRequest("spring-data")
.id(randomID())
.source(singletonMap("feature", "high-level-rest-client"))
.setRefreshPolicy(IMMEDIATE);
IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT);
---- ----
<1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL. <1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
@ -150,8 +158,12 @@ The `org.springframework.data.elasticsearch.client.erhlc.ReactiveElasticsearchCl
It uses the request/response objects provided by the Elasticsearch core project. It uses the request/response objects provided by the Elasticsearch core project.
Calls are directly operated on the reactive stack, **not** wrapping async (thread pool bound) responses into reactive types. Calls are directly operated on the reactive stack, **not** wrapping async (thread pool bound) responses into reactive types.
This was the first reactive implementation Spring Data Elasticsearch provided, but now is deprecated in favour of the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient` [CAUTION]
====
This was the first reactive implementation Spring Data Elasticsearch provided, but now is deprecated in favour
of the `org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient`
which uses the functionality offered by the new Elasticsearch client libraries. which uses the functionality offered by the new Elasticsearch client libraries.
====
.Reactive REST Client (deprecated) .Reactive REST Client (deprecated)
==== ====
@ -194,6 +206,11 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
==== ====
[source,java] [source,java]
---- ----
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import static org.springframework.data.elasticsearch.client.elc.ElasticsearchClients.*;
HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("some-header", "on every request") <.> httpHeaders.add("some-header", "on every request") <.>
@ -212,7 +229,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
return headers; return headers;
}) })
.withClientConfigurer( <.> .withClientConfigurer( <.>
ElasticsearchClients.ElasticsearchClientConfigurationCallback.from(clientBuilder -> { ElasticsearchClientConfigurationCallback.from(clientBuilder -> {
// ... // ...
return clientBuilder; return clientBuilder;
})) }))
@ -227,12 +244,10 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> Optionally set a proxy. <.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy. <.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout. <.> Set the connection timeout.
Default is 10 sec.
<.> Set the socket timeout. <.> Set the socket timeout.
Default is 5 sec.
<.> Optionally set headers. <.> Optionally set headers.
<.> Add basic authentication. <.> Add basic authentication.
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header. <.> A `Supplier<HttpHeaders>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
<.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added <.> a function to configure the created client (see <<elasticsearch.clients.configuration.callbacks>>), can be added
multiple times. multiple times.
==== ====
@ -251,6 +266,8 @@ The following callbacks are provided:
[[elasticsearch.clients.configuration.callbacks.rest]] [[elasticsearch.clients.configuration.callbacks.rest]]
==== Configuration of the low level Elasticsearch `RestClient`: ==== Configuration of the low level Elasticsearch `RestClient`:
This callback provides a `org.elasticsearch.client.RestClientBuilder` that can be used to configure the Elasticsearch
`RestClient`:
==== ====
[source,java] [source,java]
---- ----
@ -266,6 +283,9 @@ ClientConfiguration.builder()
[[elasticsearch.clients.configurationcallbacks.httpasync]] [[elasticsearch.clients.configurationcallbacks.httpasync]]
==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`: ==== Configuration of the HttpAsyncClient used by the low level Elasticsearch `RestClient`:
This callback provides a `org.apache.http.impl.nio.client.HttpAsyncClientBuilder` to configure the HttpCLient that is
used by the `RestClient`.
==== ====
[source,java] [source,java]
---- ----
@ -287,6 +307,9 @@ documentation].
For the imperative client this must be done by setting the default headers, for the reactive code this must be done using a header supplier: For the imperative client this must be done by setting the default headers, for the reactive code this must be done using a header supplier:
CAUTION: Even when these headers are set, there are cases where the response returned from the cluster cannot be
parsed with the client. This is not an error in Spring Data Elasticsearch.
==== ====
[source,java] [source,java]
---- ----
@ -310,12 +333,13 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
[[elasticsearch.clients.logging]] [[elasticsearch.clients.logging]]
== Client Logging == Client Logging
To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below. To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level
needs to be turned on as outlined in the snippet below. This can be enabled in the Elasticsearch client by setting
the level of the `tracer` package to "trace" (see
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/java-rest-low-usage-logging.html)
.Enable transport layer logging .Enable transport layer logging
[source,xml] [source,xml]
---- ----
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace"/> <logger name="tracer" level="trace"/>
---- ----
NOTE: The above applies to both the `RestHighLevelClient` and `ReactiveElasticsearchClient` when obtained via `RestClients` respectively `ReactiveRestClients`.

View File

@ -6,6 +6,7 @@
* Upgrade to Java 17 baseline * Upgrade to Java 17 baseline
* Upgrade to Spring Framework 6 * Upgrade to Spring Framework 6
* Upograde to Elasticsearch 8.5.0
* Use the new Elasticsearch client library * Use the new Elasticsearch client library
[[new-features.4-4-0]] [[new-features.4-4-0]]

View File

@ -1,20 +1,9 @@
[[elasticsearch.mapping]] [[elasticsearch.mapping]]
= Elasticsearch Object Mapping = Elasticsearch Object Mapping
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back. Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON
representation that is stored in Elasticsearch and back. The class that is internally used for this mapping is the
Earlier versions of Spring Data Elasticsearch used a Jackson based conversion, Spring Data Elasticsearch 3.2.x introduced the <<elasticsearch.mapping.meta-model>>. `MappingElasticsearcvhConverter`.
As of version 4.0 only the Meta Object Mapping is used, the Jackson based mapper is not available anymore and the `MappingElasticsearchConverter` is used.
The main reasons for the removal of the Jackson based mapper are:
* Custom mappings of fields needed to be done with annotations like `@JsonFormat` or `@JsonInclude`.
This often caused problems when the same object was used in different JSON based datastores or sent over a JSON based API.
* Custom field types and formats also need to be stored into the Elasticsearch index mappings.
The Jackson based annotations did not fully provide all the information that is necessary to represent the types of Elasticsearch.
* Fields must be mapped not only when converting from and to entities, but also in query argument, returned data and on other places.
Using the `MappingElasticsearchConverter` now covers all these cases.
[[elasticsearch.mapping.meta-model]] [[elasticsearch.mapping.meta-model]]
== Meta Model Object Mapping == Meta Model Object Mapping
@ -31,14 +20,13 @@ The metadata is taken from the entity's properties which can be annotated.
The following annotations are available: The following annotations are available:
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. * `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database.
The most important attributes are: The most important attributes are (check the API documentation for the complete list of attributes):
** `indexName`: the name of the index to store this entity in. ** `indexName`: the name of the index to store this entity in.
This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"` This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"`
** `createIndex`: flag whether to create an index on repository bootstrapping. ** `createIndex`: flag whether to create an index on repository bootstrapping.
Default value is _true_. Default value is _true_.
See <<elasticsearch.repositories.autocreation>> See <<elasticsearch.repositories.autocreation>>
** `versionType`: Configuration of version management.
Default value is _EXTERNAL_.
* `@Id`: Applied at the field level to mark the field used for identity purpose. * `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field. * `@Transient`: By default all fields are mapped to the document when it is stored or retrieved, this annotation excludes the field.
@ -103,6 +91,9 @@ The following table shows the different attributes and the mapping created from
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of
predefined values and their patterns.
[[elasticsearch.mapping.meta-model.annotations.range]] [[elasticsearch.mapping.meta-model.annotations.range]]
==== Range types ==== Range types
@ -194,7 +185,6 @@ Those type hints are represented as `_class` attributes within the document and
[source,java] [source,java]
---- ----
public class Person { <1> public class Person { <1>
@Id String id; @Id String id;
String firstname; String firstname;
String lastname; String lastname;
@ -274,7 +264,6 @@ Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs.
[source,java] [source,java]
---- ----
public class Address { public class Address {
String city, street; String city, street;
Point location; Point location;
} }

View File

@ -34,7 +34,7 @@ There is support for automatic creation of indices and writing the mappings when
== Usage examples == Usage examples
The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
The example assumes that `Person` is a class that is annotated with `@Document`, `@Id` etc (see <<elasticsearch.mapping.meta-model.annotations>>).
.ElasticsearchOperations usage .ElasticsearchOperations usage
==== ====
[source,java] [source,java]
@ -45,34 +45,29 @@ public class TestController {
private ElasticsearchOperations elasticsearchOperations; private ElasticsearchOperations elasticsearchOperations;
public TestController(ElasticsearchOperations elasticsearchOperations) { <1> public TestController(ElasticsearchOperations elasticsearchOperations) { <.>
this.elasticsearchOperations = elasticsearchOperations; this.elasticsearchOperations = elasticsearchOperations;
} }
@PostMapping("/person") @PostMapping("/person")
public String save(@RequestBody Person person) { <2> public String save(@RequestBody Person person) { <.>
Person savedEntity = elasticsearchOperations.save(person);
IndexQuery indexQuery = new IndexQueryBuilder() return savedEntity.getId();
.withId(person.getId().toString())
.withObject(person)
.build();
String documentId = elasticsearchOperations.index(indexQuery);
return documentId;
} }
@GetMapping("/person/{id}") @GetMapping("/person/{id}")
public Person findById(@PathVariable("id") Long id) { <3> public Person findById(@PathVariable("id") Long id) { <.>
Person person = elasticsearchOperations Person person = elasticsearchOperations.get(id.toString(), Person.class);
.queryForObject(GetQuery.getById(id.toString()), Person.class);
return person; return person;
} }
} }
---- ----
<1> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor. <.> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<2> Store some entity in the Elasticsearch cluster. <.> Store some entity in the Elasticsearch cluster. The id is read from the returned entity, as it might have been
<3> Retrieve the entity with a query by id. null in the `person` object and been created by Elasticsearch.
<.> Retrieve the entity with a get by id.
==== ====
To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation. To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.

View File

@ -5,80 +5,22 @@
The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`. The `ReactiveElasticsearchTemplate` is the default implementation of `ReactiveElasticsearchOperations`.
[[elasticsearch.reactive.template]] [[elasticsearch.reactive.operations]]
== Reactive Elasticsearch Template == Reactive Elasticsearch Operations
To get started the `ReactiveElasticsearchTemplate` needs to know about the actual client to work with. To get started the `ReactiveElasticsearchOperations` needs to know about the actual client to work with.
Please see <<elasticsearch.clients.reactive>> for details on the client. Please see <<elasticsearch.clients.reactiverestclient>> for details on the client and how to configure it.
[[elasticsearch.reactive.template.configuration]] [[elasticsearch.reactive.operations.usage]]
=== Reactive Template Configuration === Reactive Operations Usage
The easiest way of setting up the `ReactiveElasticsearchTemplate` is via `AbstractReactiveElasticsearchConfiguration` providing `ReactiveElasticsearchOperations` lets you save, find and delete your domain objects and map those objects to documents
dedicated configuration method hooks for `base package`, the `initial entity set` etc. stored
in Elasticsearch.
.The AbstractReactiveElasticsearchConfiguration
====
[source,java]
----
@Configuration
public class Config extends AbstractReactiveElasticsearchConfiguration {
@Bean <1>
@Override
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
====
NOTE: If applicable set default `HttpHeaders` via the `ClientConfiguration` of the `ReactiveElasticsearchClient`. See <<elasticsearch.clients.configuration>>.
TIP: If needed the `ReactiveElasticsearchTemplate` can be configured with default `RefreshPolicy` and `IndicesOptions` that get applied to the related requests by overriding the defaults of `refreshPolicy()` and `indicesOptions()`.
However one might want to be more in control over the actual components and use a more verbose approach.
.Configure the ReactiveElasticsearchTemplate
====
[source,java]
----
@Configuration
public class Config {
@Bean <1>
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
// ...
}
@Bean <2>
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
}
@Bean <3>
public SimpleElasticsearchMappingContext elasticsearchMappingContext() {
return new SimpleElasticsearchMappingContext();
}
@Bean <4>
public ReactiveElasticsearchOperations reactiveElasticsearchOperations() {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), elasticsearchConverter());
}
}
----
<1> Configure the client to use. This can be done by `ReactiveRestClients` or directly via `DefaultReactiveElasticsearchClient`.
<2> Set up the `ElasticsearchConverter` used for domain type mapping utilizing metadata provided by the mapping context.
<3> The Elasticsearch specific mapping context for domain type metadata.
<4> The actual template based on the client and conversion infrastructure.
====
[[elasticsearch.reactive.template.usage]]
=== Reactive Template Usage
`ReactiveElasticsearchTemplate` lets you save, find and delete your domain objects and map those objects to documents stored in Elasticsearch.
Consider the following: Consider the following:
.Use the ReactiveElasticsearchTemplate .Use the ReactiveElasticsearchOperations
==== ====
[source,java] [source,java]
---- ----
@ -94,15 +36,20 @@ public class Person {
[source,java] [source,java]
---- ----
template.save(new Person("Bruce Banner", 42)) <1>
ReactiveELasticsearchOperations operations; <.>
// ...
operations.save(new Person("Bruce Banner", 42)) <.>
.doOnNext(System.out::println) .doOnNext(System.out::println)
.flatMap(person -> template.findById(person.id, Person.class)) <2> .flatMap(person -> operations.get(person.id, Person.class)) <.>
.doOnNext(System.out::println) .doOnNext(System.out::println)
.flatMap(person -> template.delete(person)) <3> .flatMap(person -> operations.delete(person)) <.>
.doOnNext(System.out::println) .doOnNext(System.out::println)
.flatMap(id -> template.count(Person.class)) <4> .flatMap(id -> operations.count(Person.class)) <.>
.doOnNext(System.out::println) .doOnNext(System.out::println)
.subscribe(); <5> .subscribe(); <.>
---- ----
The above outputs the following sequence on the console. The above outputs the following sequence on the console.
@ -114,11 +61,10 @@ The above outputs the following sequence on the console.
> QjWCWWcBXiLAnp77ksfR > QjWCWWcBXiLAnp77ksfR
> 0 > 0
---- ----
<1> Insert a new `Person` document into the _marvel_ index under type _characters_. The `id` is generated on server side and set into the instance returned. <.> Insert a new `Person` document into the _marvel_ index . The `id` is generated on server
<2> Lookup the `Person` with matching `id` in the _marvel_ index under type _characters_. side and set into the instance returned.
<3> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index under type _characters_. <.> Lookup the `Person` with matching `id` in the _marvel_ index.
<4> Count the total number of documents in the _marvel_ index under type _characters_. <.> Delete the `Person` with matching `id`, extracted from the given instance, in the _marvel_ index.
<5> Don't forget to _subscribe()_. <.> Count the total number of documents in the _marvel_ index.
<.> Don't forget to _subscribe()_.
==== ====