Skip to content

Commit 734ffaf

Browse files
authored
Merge pull request #34 from szabosteve/add.documentation
[DOCS] Expands client documentation
2 parents 93565ea + 6304b44 commit 734ffaf

File tree

6 files changed

+418
-44
lines changed

6 files changed

+418
-44
lines changed

docs/api-conventions.asciidoc

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
[[api-conventions]]
2+
== API conventions
3+
4+
The Java client uses a very consistent code structure, using modern code
5+
patterns that make complex requests easier to write and complex responses easier
6+
to process. This page explains these so that you quickly feel at home.
7+
8+
[discrete]
9+
=== Package structure and namespace clients
10+
11+
The {es} API is large and is organized into feature groups, as can be seen in
12+
the {ref}/rest-apis.html[{es} API documentation].
13+
14+
The Java client follows this structure: feature groups are called “namespaces”,
15+
and each namespace is located in a subpackage of
16+
`co.elastic.clients.elasticsearch`. The only exceptions are the “search” and
17+
“document” APIs which are located in the `_core` subpackage.
18+
19+
Each of the namespace clients can be accessed from the top level {es} client.
20+
The snippet below shows how to use the indices namespace client to create an
21+
index:
22+
23+
["source","java"]
24+
--------------------------------------------------
25+
ElasticsearchClient esClient = ...
26+
esClient.indices().create(c -> c.index("my-index"));
27+
--------------------------------------------------
28+
29+
Namespace clients are very lightweight objects that can be created on the fly.
30+
31+
32+
[discrete]
33+
=== Method naming conventions
34+
35+
Classes in the Java API Client contain two kinds of methods and properties:
36+
37+
* Methods and properties that are part of the API, such as
38+
`ElasticsearchClient.search()` or `SearchResponse.maxScore()`. They are derived
39+
from their respective names in the {es} JSON API using the standard Java
40+
`camelCaseName` convention.
41+
42+
* Methods and properties that are part of the framework on which the Java API
43+
Client is built, such as `Query._type()`. These methods and properties are
44+
prefixed with an underscore to both avoid any naming conflicts with API names
45+
and ease distinguishing what identifiers belong to the API or to the framework.
46+
47+
48+
[discrete]
49+
=== Immutable objects, builders and builder lambdas
50+
51+
All data types in the Java client are immutable. Object creation uses the
52+
https://www.informit.com/articles/article.aspx?p=1216151&seqNum=2[builder pattern]
53+
that was popularized in *Effective Java* in 2008.
54+
55+
["source","java"]
56+
--------------------------------------------------
57+
CreateResponse createResponse = client.indices().create(
58+
new CreateRequest.Builder()
59+
.index("my-index")
60+
.putAliases("foo",
61+
new Alias.Builder().isWriteIndex(true).build()
62+
)
63+
.build()
64+
);
65+
--------------------------------------------------
66+
67+
Note that a builder should not be reused after its `build()` method has been
68+
called.
69+
70+
Although this works nicely, having to instantiate builder classes and call the
71+
build() method is a bit verbose. So every builder setter in the Java client also
72+
accepts a lambda expression that takes a newly created builder as a parameter
73+
and returns a populated builder. The snippet above can be written also as:
74+
75+
["source","java"]
76+
--------------------------------------------------
77+
CreateResponse createResponse = client.indices()
78+
.create(createBuilder -> createBuilder
79+
.index("my-index")
80+
.putAliases("foo", aliasBuilder -> aliasBuilder
81+
.isWriteIndex(true)
82+
)
83+
);
84+
--------------------------------------------------
85+
86+
This approach allows for much more concise code, and also avoids importing
87+
classes (and even remembering their names) since types are inferred from the
88+
method parameter signature.
89+
90+
It becomes particularly useful with complex nested queries like the one below,
91+
taken from the
92+
{ref}/query-dsl-intervals-query.html[intervals query API documentation].
93+
94+
This example also shows a useful naming convention for builder parameters in
95+
deeply nested structures: since we have to give them a name to comply with the
96+
Java syntax (Kotlin would accept `it` and Scala a simple `_`), we name them with
97+
an underscore followed by the depth of the item, i.e. `_0`, `_1`, and so on.
98+
This removes the need for finding names and makes reading the code easier to
99+
read by reducing the number of identifiers.
100+
101+
["source","java"]
102+
--------------------------------------------------
103+
client.search(_0 -> _0
104+
.query(_1 -> _1
105+
.intervals(_2 -> _2
106+
.field("my_text")
107+
.allOf(_3 -> _3
108+
.ordered(true)
109+
.addIntervals(_4 -> _4
110+
.match(_5 -> _5
111+
.query("my favorite food")
112+
.maxGaps(0)
113+
.ordered(true)
114+
)
115+
)
116+
.addIntervals(_4 -> _4
117+
.anyOf(_5 -> _5
118+
.addIntervals(_6 -> _6
119+
.match(_7 -> _7
120+
.query("hot water")
121+
)
122+
)
123+
.addIntervals(_6 -> _6
124+
.match(_7 -> _7
125+
.query("cold porridge")
126+
)
127+
)
128+
)
129+
)
130+
)
131+
)
132+
),
133+
RequestTest.AppData.class
134+
);
135+
--------------------------------------------------
136+
137+
[discrete]
138+
=== Variant types
139+
140+
The {es} API has a lot of variant types: queries, aggregations, field mappings,
141+
analyzers, and so on. Finding the correct class name in such large collections
142+
can be challenging.
143+
144+
The Java client builders make this easy: the builders for variant types, such as
145+
Query, have methods for each of the available implementations. We’ve seen this
146+
in action above with `intervals` (a kind of query) and `allOf`, `match` and
147+
`anyOf` (various kinds of intervals).
148+
149+
This is because variant objects in the Java client are implementations of a
150+
“tagged union”: they contain the identifier (or tag) of the variant they hold
151+
and the value for that variant. For example, a `Query` object can contain an
152+
`IntervalsQuery` with tag `intervals`, a `TermQuery` with tag `term`, and so on.
153+
This approach allows writing fluent code where you can let the IDE completion
154+
features guide you to build and navigate complex nested structures:
155+
156+
* Variant builders have setter methods for every available implementation. They
157+
use the same conventions as regular properties and accept both a builder lambda
158+
expression and a ready-made object of the actual type of the variant. Here’s an
159+
example to build a term query:
160+
+
161+
--
162+
["source","java"]
163+
--------------------------------------------------
164+
Query query = new Query.Builder()
165+
.term( // <1>
166+
t -> t.field("name").value("foo") // <2>
167+
)
168+
.build(); // <3>
169+
170+
--------------------------------------------------
171+
<1> Choose the `term` variant to build a term query.
172+
<2> Build the terms query with a builder lambda expression.
173+
<3> Build the `Query` that now holds a `TermQuery` object with tag `term`.
174+
--
175+
176+
* Variant objects have getter methods for every available implementation. These
177+
methods check that the object actually holds a variant of that type and return
178+
the value downcasted to the correct type. They throw an `IllegalStateException`
179+
otherwise. This approach allows writing fluent code to traverse variants.
180+
181+
[discrete]
182+
=== Blocking and asynchronous clients
183+
184+
API clients come in two flavors: blocking and asynchronous. All methods on
185+
asynchronous clients return a standard `CompletableFuture`.
186+
187+
Both flavors can be used at the same time depending on your needs, sharing the
188+
same transport object:
189+
190+
["source","java"]
191+
--------------------------------------------------
192+
Transport transport = ...
193+
194+
ElasticsearchClient client = new ElasticsearchClient(transport);
195+
if (client.exists(b -> b.index("products").id("foo")).value()) {
196+
logger.info("product exists");
197+
}
198+
199+
ElasticsearchAsyncClient asyncClient = new ElasticsearchAsyncClient(transport);
200+
asyncClient.exists(b -> b.index("products").id("foo")).thenAccept(response -> {
201+
if (response.value()) {
202+
logger.info("product exists");
203+
}
204+
});
205+
--------------------------------------------------
206+
207+
[discrete]
208+
=== Exceptions
209+
210+
Client methods can throw two kinds of exceptions:
211+
212+
* Requests that were received by the {es} server but that were rejected
213+
(validation error, server internal timeout exceeded, etc) will produce an
214+
`ApiException`. This exception contains details about the error provided by
215+
{es}.
216+
217+
* Requests that fail to reach the server (network error, server unavailable,
218+
etc) will produce a subclass `IOException`. That subclass is specific to the
219+
transport used. In the case of the `RestClientTransport` it will be a
220+
`ResponseException` that contains the low level HTTP response.
221+
222+
223+
[discrete]
224+
=== Object life cycles
225+
226+
There are five kinds of objects in the Java client with different life cycles:
227+
228+
229+
**Object mapper**::
230+
Stateless and thread-safe, but can be costly to create.
231+
It’s usually a singleton that is created at application startup and used to
232+
create the transport.
233+
234+
**Transport**::
235+
Thread-safe, holds network resources through the underlying HTTP client. A
236+
transport object is associated with an {es} cluster and has to be explicitly
237+
closed to release the underlying resources such as network connections.
238+
239+
**Clients**::
240+
Immutable, stateless and thread-safe.
241+
These are very lightweight objects that just wrap a transport and provide API
242+
endpoints as methods.
243+
244+
**Builders**::
245+
Mutable, non thread-safe.
246+
Builders are transient objects that should not be reused after calling
247+
`build()`.
248+
249+
**Requests & other API objects**::
250+
Immutable, thread-safe.
251+
If your application uses the same request or same parts of a request over and
252+
over, these objects can be prepared in advance and reused across multiple calls
253+
over multiple clients with different transports.

docs/connecting.asciidoc

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,61 @@
11
[[connecting]]
22
== Connecting
33

4-
experimental[]
4+
beta[]
55

6-
The code snippet below shows how to initialize a low level REST client and the
7-
Jackson object mapper to configure an ElasticsearchClient:
6+
The Java client is structured around three main components:
87

8+
* **API client classes**. These provide strongly typed data structures and
9+
methods for {es} APIs. Since the {es} API is large, it is structured in feature
10+
groups (also called “namespaces”), each having its own client class. {es} core
11+
features are implemented in the `ElasticsearchClient` class.
12+
* **A JSON object mapper**. This maps your application classes to JSON and
13+
seamlessly integrates them with the API client.
14+
* **A transport layer implementation**. This is where all HTTP request handling
15+
takes place.
16+
17+
This code snippet creates and wires together these three components:
918

1019
["source","java"]
1120
--------------------------------------------------
1221
// Create the low-level client
13-
RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();
22+
RestClient restClient = RestClient.builder(
23+
new HttpHost("localhost", 9200)).build();
1424
15-
// Create the transport that provides JSON and http services to API clients
16-
Transport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
25+
// Create the transport with a Jackson mapper
26+
Transport transport = new RestClientTransport(
27+
restClient, new JacksonJsonpMapper());
1728
18-
// And create our API client
29+
// And create the API client
1930
ElasticsearchClient client = new ElasticsearchClient(transport);
2031
--------------------------------------------------
2132

22-
Authentication is managed by the
23-
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low.html[low-level Rest Client].
24-
For further details on configuring authentication, refer to the
25-
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/_basic_authentication.html[documentation].
33+
Authentication is managed by the <<java-rest-low>>. For further details on
34+
configuring authentication, refer to
35+
{java-api-client}/_basic_authentication.html[its documentation].
36+
37+
[discrete]
38+
=== Your first request
39+
40+
The code snippet below searches all items from a “product” index whose name
41+
matches “bicycle” and return them as instances of a `Product` application class.
42+
43+
It illustrates the use of fluent functional builders to write search queries as
44+
concise DSL-like code. This pattern is explained in more detail in
45+
<<api-conventions>>.
46+
47+
["source","java"]
48+
--------------------------------------------------
49+
SearchResponse<Product> search = client.search(s -> s
50+
.index("products")
51+
.query(q -> q
52+
.term(t -> t
53+
.field("name")
54+
.value("bicycle")
55+
)),
56+
Product.class);
57+
58+
for (Hit<AppData> hit: search.hits().hits()) {
59+
processAppData(hit.source());
60+
}
61+
--------------------------------------------------

docs/index.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ include::{asciidoc-dir}/../../shared/attributes.asciidoc[]
66
include::introduction.asciidoc[]
77
include::installation.asciidoc[]
88
include::connecting.asciidoc[]
9+
include::migrate.asciidoc[]
10+
include::api-conventions.asciidoc[]
911
include::{elasticsearch-root}/docs/java-rest/low-level/index.asciidoc[]

0 commit comments

Comments
 (0)