1
1
[[api-conventions]]
2
2
== API conventions
3
3
4
- The Java client uses a very consistent code structure, using modern code
4
+ The {java- client} uses a very consistent code structure, using modern code
5
5
patterns that make complex requests easier to write and complex responses easier
6
6
to process. This page explains these so that you quickly feel at home.
7
7
@@ -11,19 +11,22 @@ to process. This page explains these so that you quickly feel at home.
11
11
The {es} API is large and is organized into feature groups, as can be seen in
12
12
the {ref}/rest-apis.html[{es} API documentation].
13
13
14
- The Java client follows this structure: feature groups are called “namespaces”,
14
+ The {java- client} follows this structure: feature groups are called “namespaces”,
15
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.
16
+ `co.elastic.clients.elasticsearch`.
17
+
18
+ Each of the namespace clients can be accessed from the top level {es} client. The
19
+ only exceptions are the “search” and “document” APIs which are located in the `core`
20
+ subpackage and can be accessed on the main {es} client object.
18
21
19
- Each of the namespace clients can be accessed from the top level {es} client.
20
22
The snippet below shows how to use the indices namespace client to create an
21
- index:
23
+ index (the lambda syntax is <<builder-lambdas, explained below>>) :
22
24
23
25
["source","java"]
24
26
--------------------------------------------------
25
- ElasticsearchClient esClient = ...
26
- esClient.indices().create(c -> c.index("my-index"));
27
+ // Create the "products" index
28
+ ElasticsearchClient client = ...
29
+ client.indices().create(c -> c.index("products"));
27
30
--------------------------------------------------
28
31
29
32
Namespace clients are very lightweight objects that can be created on the fly.
@@ -32,106 +35,98 @@ Namespace clients are very lightweight objects that can be created on the fly.
32
35
[discrete]
33
36
=== Method naming conventions
34
37
35
- Classes in the Java API Client contain two kinds of methods and properties:
38
+ Classes in the {java-client} contain two kinds of methods and properties:
36
39
37
40
* Methods and properties that are part of the API, such as
38
41
`ElasticsearchClient.search()` or `SearchResponse.maxScore()`. They are derived
39
42
from their respective names in the {es} JSON API using the standard Java
40
- `camelCaseName ` convention.
43
+ `camelCaseNaming ` convention.
41
44
42
45
* 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
+ Client is built, such as `Query._kind()`. These methods and properties are
47
+ prefixed with an underscore to both avoid any naming conflicts with API names,
48
+ and allow to easily distinguish what belongs to the API from what belongs to the
49
+ framework.
46
50
47
51
48
52
[discrete]
53
+ [[builder-lambdas]]
49
54
=== Immutable objects, builders and builder lambdas
50
55
51
- All data types in the Java client are immutable. Object creation uses the
56
+ All data types in the {java- client} are immutable. Object creation uses the
52
57
https://www.informit.com/articles/article.aspx?p=1216151&seqNum=2[builder pattern]
53
58
that was popularized in *Effective Java* in 2008.
54
59
55
60
["source","java"]
56
61
--------------------------------------------------
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
- );
62
+ ElasticsearchClient client = ...
63
+ include-tagged::{doc-tests}/ApiConventionsTest.java[builders]
65
64
--------------------------------------------------
66
65
67
66
Note that a builder should not be reused after its `build()` method has been
68
67
called.
69
68
70
69
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
70
+ ` build()` method is a bit verbose. So every property setter in the {java- client} also
72
71
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:
72
+ and returns a populated builder. The snippet above can also be written as:
74
73
75
74
["source","java"]
76
75
--------------------------------------------------
77
- CreateResponse createResponse = client.indices()
78
- .create(createBuilder -> createBuilder
79
- .index("my-index")
80
- .putAliases("foo", aliasBuilder -> aliasBuilder
81
- .isWriteIndex(true)
82
- )
83
- );
76
+ ElasticsearchClient client = ...
77
+ include-tagged::{doc-tests}/ApiConventionsTest.java[builder-lambdas]
84
78
--------------------------------------------------
85
79
86
80
This approach allows for much more concise code, and also avoids importing
87
81
classes (and even remembering their names) since types are inferred from the
88
82
method parameter signature.
89
83
90
- It becomes particularly useful with complex nested queries like the one below,
91
- taken from the
84
+ Note in the above example that builder variables are only used to start a chain
85
+ of property setters. Their name therefore doesn't matter much and can be shortened
86
+ to improve readability:
87
+
88
+ ["source","java"]
89
+ --------------------------------------------------
90
+ ElasticsearchClient client = ...
91
+ include-tagged::{doc-tests}/ApiConventionsTest.java[builder-lambdas-short]
92
+ --------------------------------------------------
93
+
94
+
95
+ Builder lambdas become particularly useful with complex nested queries like the
96
+ one below, taken from the
92
97
{ref}/query-dsl-intervals-query.html[intervals query API documentation].
93
98
94
99
This example also shows a useful naming convention for builder parameters in
95
100
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.
101
+ Java syntax, we name them with an underscore followed by the depth of the item,
102
+ i.e. `_0`, `_1`, and so on. This removes the need for finding names and makes
103
+ the code easier to read by reducing the number of identifiers. Correct indentation
104
+ also allows the structure of the query to stand out.
105
+
106
+ ["source","java"]
107
+ --------------------------------------------------
108
+ ElasticsearchClient client = ...
109
+ include-tagged::{doc-tests}/ApiConventionsTest.java[builder-intervals]
110
+ --------------------------------------------------
111
+ <1> Search results will be mapped to `SomeApplicationData` instances to
112
+ be readily available to the application.
113
+
114
+ When using the {java-client} with Kotlin, all lambda functions can use the implicit
115
+ `it` parameter. Similarly, in Scala they can use the implicit `_` parameter.
116
+
117
+ [discrete]
118
+ === Lists and maps
119
+
120
+ Properties of type `List` and `Map` are exposed by object builders as a set of overloaded
121
+ methods that _update_ the property value, by appending to lists and adding new entries to maps
122
+ (or replacing existing ones).
123
+
124
+ Object builders create immutable objects, and this also applies to list and map properties
125
+ that are made immutable at object construction time.
100
126
101
127
["source","java"]
102
128
--------------------------------------------------
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
- );
129
+ include-tagged::{doc-tests}/ApiConventionsTest.java[collections]
135
130
--------------------------------------------------
136
131
137
132
[discrete]
@@ -141,42 +136,59 @@ The {es} API has a lot of variant types: queries, aggregations, field mappings,
141
136
analyzers, and so on. Finding the correct class name in such large collections
142
137
can be challenging.
143
138
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
139
+ The {java- client} builders make this easy: the builders for variant types, such as
140
+ ` Query` , have methods for each of the available implementations. We’ve seen this
146
141
in action above with `intervals` (a kind of query) and `allOf`, `match` and
147
142
`anyOf` (various kinds of intervals).
148
143
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
144
+ This is because variant objects in the {java- client} are implementations of a
145
+ “tagged union”: they contain the identifier (or tag) of the variant they hold
151
146
and the value for that variant. For example, a `Query` object can contain an
152
147
`IntervalsQuery` with tag `intervals`, a `TermQuery` with tag `term`, and so on.
153
148
This approach allows writing fluent code where you can let the IDE completion
154
149
features guide you to build and navigate complex nested structures:
155
150
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
- --
151
+ Variant builders have setter methods for every available implementation. They
152
+ use the same conventions as regular properties and accept both a builder lambda
153
+ expression and a ready-made object of the actual type of the variant. Here’s an
154
+ example to build a term query:
155
+
162
156
["source","java"]
163
157
--------------------------------------------------
164
- Query query = new Query.Builder()
165
- .term( // <1>
166
- t -> t.field("name").value("foo") // <2>
167
- )
168
- .build(); // <3>
169
-
158
+ include-tagged::{doc-tests}/ApiConventionsTest.java[variant-creation]
170
159
--------------------------------------------------
171
160
<1> Choose the `term` variant to build a term query.
172
161
<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
- --
162
+ <3> Build the `Query` that now holds a `TermQuery` object of kind `term`.
163
+
164
+ Variant objects have getter methods for every available implementation. These
165
+ methods check that the object actually holds a variant of that kind and return
166
+ the value downcasted to the correct type. They throw an `IllegalStateException`
167
+ otherwise. This approach allows writing fluent code to traverse variants.
168
+
169
+ ["source","java"]
170
+ --------------------------------------------------
171
+ include-tagged::{doc-tests}/ApiConventionsTest.java[variant-navigation]
172
+ --------------------------------------------------
173
+
174
+ Variant objects also provide information on the variant kind they currently hold:
175
+
176
+ * with `is` methods for each of the variant kinds: `isTerm()`, `isIntervals()`, `isFuzzy()`, etc.
175
177
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.
178
+ * with a nested `Kind` enumeration that defines all variant kinds.
179
+
180
+ This information can then be used to navigate down into specific variants after checking
181
+ their actual kind:
182
+
183
+ --
184
+ ["source","java"]
185
+ --------------------------------------------------
186
+ include-tagged::{doc-tests}/ApiConventionsTest.java[variant-kind]
187
+ --------------------------------------------------
188
+ <1> Test if the variant is of a specific kind.
189
+ <2> Test a larger set of variant kinds.
190
+ <3> Get the kind and value held by the variant object.
191
+ --
180
192
181
193
[discrete]
182
194
=== Blocking and asynchronous clients
@@ -189,19 +201,9 @@ same transport object:
189
201
190
202
["source","java"]
191
203
--------------------------------------------------
192
- Transport transport = ...
204
+ ElasticsearchTransport transport = ...
193
205
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
- });
206
+ include-tagged::{doc-tests}/ApiConventionsTest.java[blocking-and-async]
205
207
--------------------------------------------------
206
208
207
209
[discrete]
@@ -211,22 +213,21 @@ Client methods can throw two kinds of exceptions:
211
213
212
214
* Requests that were received by the {es} server but that were rejected
213
215
(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.
216
+ `ElasticsearchException`. This exception contains details about the error,
217
+ provided by {es}.
221
218
219
+ * Requests that failed to reach the server (network error, server unavailable,
220
+ etc) will produce a `TransportException`. That exception's cause is the exception
221
+ thrown by the lower-level implementation. In the case of the `RestClientTransport`
222
+ it will be a `ResponseException` that contains the low level HTTP response.
222
223
223
224
[discrete]
224
225
=== Object life cycles
225
226
226
- There are five kinds of objects in the Java client with different life cycles:
227
+ There are five kinds of objects in the {java- client} with different life cycles:
227
228
228
229
229
- **Object mapper**::
230
+ **Object mapper**::
230
231
Stateless and thread-safe, but can be costly to create.
231
232
It’s usually a singleton that is created at application startup and used to
232
233
create the transport.
0 commit comments