Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Nest to Elastic.Clients.Elasticsearch #2244

Merged
merged 13 commits into from
Jun 28, 2024

Conversation

Alirexaa
Copy link
Collaborator

What this PR does / why we need it:

using Elastic.Clients.Elasticsearch package instead of deprecated Nest package

Which issue(s) this PR fixes:

Please reference the issue this PR will close: #2236

Special notes for your reviewer:

this PR brings the following breaking changes:

  • Users that use the Nest package Public API without directly referencing this package.
  • Change UseApiKey Public API
- public ElasticsearchOptions UseApiKey(ApiKeyAuthenticationCredentials apiKey);

+ public ElasticsearchOptions UseApiKey(string apiKey);
  • Remove ApiKeyAuthenticationCredentials Public API, Because this type is part of Nest package
- public ApiKeyAuthenticationCredentials? ApiKeyAuthenticationCredentials { get; private set; }

+public string? ApiKey { get; private set; }

Does this PR introduce a user-facing change?:
Yes.
Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • Code compiles correctly
  • Created/updated tests
  • Unit tests passing
  • End-to-end tests passing
  • Extended the documentation
  • Provided sample for the feature

Copy link
Collaborator

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alirexaa big thanks for your contribution! Please fix the failing test.

@Mpdreamz and @stevejgordon PTAL and let us know if this is the direction we should be heading (this update will be shipped along with .NET 9 and it won't affect older Health Check versions)

{
lowLevelClient = _connections[_options.Uri];
elasticsearchClient = _connections[_options.Uri];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not related to your specific changes, but I wonder: does ElasticsearchClient implement IDisposable? If so, it should be disposed here (we have failed to add it to the cache and decided to reuse the existing instance)

Suggested change
elasticsearchClient = _connections[_options.Uri];
elasticsearchClient.Dispose();
elasticsearchClient = _connections[_options.Uri];

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not implement.
Is there anything else I can do about this?

@@ -21,9 +21,9 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
{
try
{
if (!_connections.TryGetValue(_options.Uri, out var lowLevelClient))
if (!_connections.TryGetValue(_options.Uri, out var elasticsearchClient))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to your changes, but something that we should improve in the future:

From https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/recommendations.html:

Client reuse can be achieved by creating a singleton static instance or by registering the type with a singleton lifetime when using dependency injection containers.

So same as #2113 we should try to steer the users towards best practices and provide and recommend overloads that accept ElasticsearchClient and simply re-use it (to there would be one instance for the whole app, not one for health checks and another for the app itself).

Copy link
Collaborator

@unaizorrilla unaizorrilla Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsitnik what about many health checks for different Elastic instances?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about many health checks for different Elastic instances?

We could use the same approach we have for Azure health checks: by default a singleton instance is resolved from DI, but the users can provide an overload that solves it on their own.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alirexaa I don't expect you to change it in this PR, this is just something to consider as a next improvement. And since we are going to introduce breaking changes now, we could simply introduce one more breaking change ;)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsitnik, is the last change providing all the things we need?
I think we should make all the changes here.

@Mpdreamz
Copy link

Yes @adamsitnik this LGTM.

cc @flobernd who's the maintainer for the new .net client.

@Alirexaa
Copy link
Collaborator Author

Alirexaa commented Jun 24, 2024

The new UseElasticCloud method supports health check for elastic cloud.
it should close: #2242

@Alirexaa Alirexaa requested a review from adamsitnik June 24, 2024 21:05
Copy link
Collaborator

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alirexaa great progress! PTAL at my comment about the public API design.

@@ -32,5 +37,6 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddElasticsearch(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action<HealthChecks.Elasticsearch.ElasticsearchOptions>? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddElasticsearch(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string elasticsearchUri, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddElasticsearch(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func<System.IServiceProvider, Elastic.Clients.Elasticsearch.ElasticsearchClient>? clientFactory = null, System.Action<HealthChecks.Elasticsearch.ElasticsearchOptions>? setup = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve been trying to wrap my head around the shape of public API and got to the following conclusions.
The ElasticsearchOptions option bag is mutable and can be modified by the optional setup action (System.Action<HealthChecks.Elasticsearch.ElasticsearchOptions>? setup = null).
The setup action takes no parameter that would allow the user to read the information from the config (which is how I imagine users provide address, username, key or password).

services.AddHealthChecks().AddElasticsearch(setup =>
{
    // how should the user read these values here?
    setup.UseElasticCloud("cloudId", "cloudApiKey");
});

The CheckHealthAsync method performs a late validation of the option bag and throws if something is missing.

if (_options.AuthenticateWithBasicCredentials)
{
settings = settings.BasicAuthentication(_options.UserName, _options.Password);
}

public ElasticsearchOptions UseBasicAuthentication(string name, string password)
{
UserName = Guard.ThrowIfNull(name);
Password = Guard.ThrowIfNull(password);

That exception won’t be easy to discover, as it will be reported somewhere up in the ASP.NET stack as a health check error.
On the other hand, the ElasticsearchClientSettingsprovided by Elastic has dedicated ctors that expect everything to be provided up front.
settings = _options.AuthenticateWithElasticCloud
? new ElasticsearchClientSettings(_options.CloudId!, new ApiKey(_options.ApiKey!))
: new ElasticsearchClientSettings(new Uri(_options.Uri!));

I believe that this overload should not expose the setup action and just require the users to configure everything before calling the factory method in the DI container and then just resolve the right instance in the setup

Example based on this doc

builder.Services.AddSingleton(s => new ElasticClient(
    Environment.GetEnvironmentVariable("ELASTIC_CLOUD_ID"),
    new ApiKeyAuthenticationCredentials(Environment.GetEnvironmentVariable("ELASTIC_API_KEY"))));

builder.Services..AddHealthChecks().AddElasticsearch();
Suggested change
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddElasticsearch(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func<System.IServiceProvider, Elastic.Clients.Elasticsearch.ElasticsearchClient>? clientFactory = null, System.Action<HealthChecks.Elasticsearch.ElasticsearchOptions>? setup = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddElasticsearch(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func<System.IServiceProvider, Elastic.Clients.Elasticsearch.ElasticsearchClient>? clientFactory = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable<string>? tags = null, System.TimeSpan? timeout = default) { }

@Alirexaa Alirexaa requested a review from adamsitnik June 27, 2024 10:52
Copy link
Collaborator

@adamsitnik adamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it LGTM, thank you for your contribution @Alirexaa !

I have found some nits, I am just going to apply mu suggestions and hit the merge button.

@Alirexaa
Copy link
Collaborator Author

Alirexaa commented Jun 28, 2024

@adamsitnik, we should block this PR until .NET 9 release, Right?

@adamsitnik
Copy link
Collaborator

we should block this PR until .NET 9 release, Right?

AFAIK the package won't be released to nuget.org until @unaizorrilla triggers some automation, so there is no need to wait with the merge.

@adamsitnik adamsitnik merged commit 42c2c73 into Xabaril:master Jun 28, 2024
2 checks passed
@Alirexaa Alirexaa deleted the fix/#2236 branch July 20, 2024 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AspNetCore.HealthChecks.Elasticsearch 8.0.1 using deprecated Nest package
4 participants