Skip to content

Commit

Permalink
add migration section for the new ES client (#71604) (#72887)
Browse files Browse the repository at this point in the history
* add migration guide for the new client

* add missing breaking changes

* add paragraph on header override
  • Loading branch information
pgayvallet authored Jul 22, 2020
1 parent 389ee0e commit 78c6ef6
Showing 1 changed file with 260 additions and 1 deletion.
261 changes: 260 additions & 1 deletion src/core/MIGRATION_EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ APIs to their New Platform equivalents.
- [Changes in structure compared to legacy](#changes-in-structure-compared-to-legacy)
- [Remarks](#remarks)
- [UiSettings](#uisettings)
- [Elasticsearch client](#elasticsearch-client)
- [Client API Changes](#client-api-changes)
- [Accessing the client from a route handler](#accessing-the-client-from-a-route-handler)
- [Creating a custom client](#creating-a-custom-client)

## Configuration

Expand Down Expand Up @@ -1003,4 +1007,259 @@ setup(core: CoreSetup){
},
})
}
```
```
## Elasticsearch client
The new elasticsearch client is a thin wrapper around `@elastic/elasticsearch`'s `Client` class. Even if the API
is quite close to the legacy client Kibana was previously using, there are some subtle changes to take into account
during migration.
[Official documentation](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html)
### Client API Changes
The most significant changes for the consumers are the following:
- internal / current user client accessors has been renamed and are now properties instead of functions
- `callAsInternalUser('ping')` -> `asInternalUser.ping()`
- `callAsCurrentUser('ping')` -> `asCurrentUser.ping()`

- the API now reflects the `Client`'s instead of leveraging the string-based endpoint names the `LegacyAPICaller` was using

before:

```ts
const body = await client.callAsInternalUser('indices.get', { index: 'id' });
```

after:

```ts
const { body } = await client.asInternalUser.indices.get({ index: 'id' });
```

- calling any ES endpoint now returns the whole response object instead of only the body payload

before:

```ts
const body = await legacyClient.callAsInternalUser('get', { id: 'id' });
```

after:

```ts
const { body } = await client.asInternalUser.get({ id: 'id' });
```

Note that more information from the ES response is available:

```ts
const {
body, // response payload
statusCode, // http status code of the response
headers, // response headers
warnings, // warnings returned from ES
meta // meta information about the request, such as request parameters, number of attempts and so on
} = await client.asInternalUser.get({ id: 'id' });
```

- all API methods are now generic to allow specifying the response body type

before:

```ts
const body: GetResponse = await legacyClient.callAsInternalUser('get', { id: 'id' });
```

after:

```ts
// body is of type `GetResponse`
const { body } = await client.asInternalUser.get<GetResponse>({ id: 'id' });
// fallback to `Record<string, any>` if unspecified
const { body } = await client.asInternalUser.get({ id: 'id' });
```

- the returned error types changed

There are no longer specific errors for every HTTP status code (such as `BadRequest` or `NotFound`). A generic
`ResponseError` with the specific `statusCode` is thrown instead.

before:

```ts
import { errors } from 'elasticsearch';
try {
await legacyClient.callAsInternalUser('ping');
} catch(e) {
if(e instanceof errors.NotFound) {
// do something
}
}
```

after:

```ts
import { errors } from '@elastic/elasticsearch';
try {
await client.asInternalUser.ping();
} catch(e) {
if(e instanceof errors.ResponseError && e.statusCode === 404) {
// do something
}
// also possible, as all errors got a name property with the name of the class,
// so this slightly better in term of performances
if(e.name === 'ResponseError' && e.statusCode === 404) {
// do something
}
}
```

- the parameter property names changed from camelCase to snake_case

Even if technically, the javascript client accepts both formats, the typescript definitions are only defining the snake_case
properties.

before:

```ts
legacyClient.callAsCurrentUser('get', {
id: 'id',
storedFields: ['some', 'fields'],
})
```

after:

```ts
client.asCurrentUser.get({
id: 'id',
stored_fields: ['some', 'fields'],
})
```

- the request abortion API changed

All promises returned from the client API calls now have an `abort` method that can be used to cancel the request.

before:

```ts
const controller = new AbortController();
legacyClient.callAsCurrentUser('ping', {}, {
signal: controller.signal,
})
// later
controller.abort();
```

after:

```ts
const request = client.asCurrentUser.ping();
// later
request.abort();
```

- it is now possible to override headers when performing specific API calls.

Note that doing so is strongly discouraged due to potential side effects with the ES service internal
behavior when scoping as the internal or as the current user.

```ts
const request = client.asCurrentUser.ping({}, {
headers: {
authorization: 'foo',
custom: 'bar',
}
});
```

Please refer to the [Breaking changes list](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/breaking-changes.html)
for more information about the changes between the legacy and new client.

### Accessing the client from a route handler

Apart from the API format change, accessing the client from within a route handler
did not change. As it was done for the legacy client, a preconfigured scoped client
bound to the request is accessible using `core` context provider:

before:

```ts
router.get(
{
path: '/my-route',
},
async (context, req, res) => {
const { client } = context.core.elasticsearch.legacy;
// call as current user
const res = await client.callAsCurrentUser('ping');
// call as internal user
const res2 = await client.callAsInternalUser('search', options);
return res.ok({ body: 'ok' });
}
);
```

after:

```ts
router.get(
{
path: '/my-route',
},
async (context, req, res) => {
const { client } = context.core.elasticsearch;
// call as current user
const res = await client.asCurrentUser.ping();
// call as internal user
const res2 = await client.asInternalUser.search(options);
return res.ok({ body: 'ok' });
}
);
```

### Creating a custom client

Note that the `plugins` option is now longer available on the new client. As the API is now exhaustive, adding custom
endpoints using plugins should no longer be necessary.

The API to create custom clients did not change much:

before:

```ts
const customClient = coreStart.elasticsearch.legacy.createClient('my-custom-client', customConfig);
// do something with the client, such as
await customClient.callAsInternalUser('ping');
// custom client are closable
customClient.close();
```

after:

```ts
const customClient = coreStart.elasticsearch.createClient('my-custom-client', customConfig);
// do something with the client, such as
await customClient.asInternalUser.ping();
// custom client are closable
customClient.close();
```

If, for any reasons, one still needs to reach an endpoint not listed on the client API, using `request.transport`
is still possible:

```ts
const { body } = await client.asCurrentUser.transport.request({
method: 'get',
path: '/my-custom-endpoint',
body: { my: 'payload'},
querystring: { param: 'foo' }
})
```

Remark: the new client creation API is now only available from the `start` contract of the elasticsearch service.

0 comments on commit 78c6ef6

Please sign in to comment.