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

Use MySqlDataSource in AddMySql by default #2096

Merged
merged 6 commits into from
Dec 11, 2023

Conversation

bgrainger
Copy link
Contributor

@bgrainger bgrainger commented Nov 18, 2023

What this PR does / why we need it:

This pushes users towards best practices (similar to what @adamsitnik described in #2040). The user should register a MySqlDataSource with DI, then call .AddHealthChecks().AddMySql() to use that same MySqlDataSource by default.

This PR also builds on top of #2093. They could be separated if needed, but there would be a merge conflict if they were opened separately, and both changes are useful.

Which issue(s) this PR fixes:

Special notes for your reviewer:

Does this PR introduce a user-facing change?:

Yes, the .AddMySql method now has an overload with all default parameters that "does the right thing".

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

I didn't see a place to update documentation or samples for MySql, but I'd be happy to do this to show how to add this health check properly.

Users can opt in to setting a command that will be executed on the server, but the default is now a more efficient ping.
This pushes users towards good defaults: firstly registering a MySqlDataSource as a singleton in DI, then just calling .AddHealthChecks().AddMySql() with no other arguments to retrieve and use that same data source for health checks.
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.

@bgrainger thank you for your contribution! I believe it's the step in the right direction, it needs only some polishing before getting merged.

/// </summary>
public string ConnectionString { get; set; } = null!;
public MySqlDataSource? DataSource { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Exposing a DataSource can lead to issues similar to the ones I've fixed in #2045 for PostgreSQL. IMO we should make it internal.

Suggested change
public MySqlDataSource? DataSource { get; set; }
internal MySqlDataSource? DataSource { get; set; }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you worried that this could lead to API misuse because someone could create a new MySqlDataSource just to set this property?

I'm more worried that without this property, someone would be forced to set the ConnectionString property, which would be a worse outcome. Making this internal would also make it impossible for an external client to initialize this object properly (i.e., with a data source), making the AddMySql overload that takes it a "pit of failure".

I'm happy to make this internal if we also make AddMySql(this IHealthChecksBuilder builder, MySqlHealthCheckOptions options, ...) [Obsolete] because it doesn't allow one to set to set the data source. Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

After thinking about it for a while and experimenting with Npgsql in #2116 I got to the following conclusions:

Let's make the parameterless MySqlHealthCheckOptions ctor internal and add two pubic ctors: one that accepts ConnectionString and one that accepts DataSource.
Moreover, let's make the setters for these properties internal.

This allows us to ensure that every instance of this type is valid: it has either the connection string or the data source. Never both.

This is the only change I would like to make before merging this PR.

@bgrainger thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good to me; I'll update this PR with that pattern soon.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's make the parameterless MySqlHealthCheckOptions ctor internal ... Moreover, let's make the setters for these properties internal.

When developing this, I found that I didn't need a parameterless constructor nor to set the properties after construction. Thus, the MySqlHealthCheckOptions type has been updated to have just two public constructors and private setters for all properties.

Copy link
Collaborator

Choose a reason for hiding this comment

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

When developing this, I found that I didn't need a parameterless constructor nor to set the properties after construction. Thus, the MySqlHealthCheckOptions type has been updated to have just two public constructors and private setters for all properties.

Great! 👍

Conflicts:
	src/HealthChecks.MySql/HealthChecks.MySql.csproj
@adamsitnik
Copy link
Collaborator

@bgrainger I've created #2113, PTAL and let me know what do you think

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.

It's almost ready, PTAL at my comments. Thank you!

src/HealthChecks.MySql/README.md Show resolved Hide resolved
/// </summary>
public string ConnectionString { get; set; } = null!;
public MySqlDataSource? DataSource { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

After thinking about it for a while and experimenting with Npgsql in #2116 I got to the following conclusions:

Let's make the parameterless MySqlHealthCheckOptions ctor internal and add two pubic ctors: one that accepts ConnectionString and one that accepts DataSource.
Moreover, let's make the setters for these properties internal.

This allows us to ensure that every instance of this type is valid: it has either the connection string or the data source. Never both.

This is the only change I would like to make before merging this PR.

@bgrainger thoughts?

There are two constructors: one to initialise with a MySqlDataSource, and one with a connection string. This helps enforce correct uasge of the API by ensuring that exactly one is set.
Conflicts:
	src/HealthChecks.MySql/HealthChecks.MySql.csproj
@adamsitnik adamsitnik added this to the V 8.0 milestone Dec 11, 2023
@codecov-commenter
Copy link

Codecov Report

Attention: 18 lines in your changes are missing coverage. Please review.

Comparison is base (fb04fe8) 0.00% compared to head (c58614c) 64.23%.

Files Patch % Lines
src/HealthChecks.MySql/MySqlHealthCheck.cs 38.09% 9 Missing and 4 partials ⚠️
...encyInjection/MySqlHealthCheckBuilderExtensions.cs 77.27% 4 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #2096       +/-   ##
===========================================
+ Coverage    0.00%   64.23%   +64.23%     
===========================================
  Files           7      248      +241     
  Lines         356     8396     +8040     
  Branches       28      592      +564     
===========================================
+ Hits            0     5393     +5393     
- Misses        356     2852     +2496     
- Partials        0      151      +151     
Flag Coverage Δ
MySql 31.34% <66.66%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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.

LGTM, thank you very much @bgrainger !

Comment on lines +15 to 20
Guard.ThrowIfNull(options);
if (options.DataSource is null && options.ConnectionString is null)
throw new InvalidOperationException("One of options.DataSource or options.ConnectionString must be specified.");
if (options.DataSource is not null && options.ConnectionString is not null)
throw new InvalidOperationException("Only one of options.DataSource or options.ConnectionString must be specified.");
_options = options;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need these checks anymore, as the public ctors ensure that the state is always valid.

Suggested change
Guard.ThrowIfNull(options);
if (options.DataSource is null && options.ConnectionString is null)
throw new InvalidOperationException("One of options.DataSource or options.ConnectionString must be specified.");
if (options.DataSource is not null && options.ConnectionString is not null)
throw new InvalidOperationException("Only one of options.DataSource or options.ConnectionString must be specified.");
_options = options;
_options = Guard.ThrowIfNull(options);

Copy link
Collaborator

Choose a reason for hiding this comment

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

For some reason I can't apply this suggestion. I am going to merge it now since it's not blocking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@adamsitnik adamsitnik merged commit 5be2f55 into Xabaril:master Dec 11, 2023
2 checks passed
eerhardt added a commit to eerhardt/AspNetCore.Diagnostics.HealthChecks that referenced this pull request Dec 12, 2024
RabbitMQHealthCheck is maintaining it's private static cache of instances. It's wrong, as it can cause a memory leak (it won't ever be freed).

Moreover, it's can create instances of IConnection. This is wrong, as it can lead into a situation when we have multiple instances of IConnection that are connected to the same server: one used by the app and another created and used by the health check. And we should have only one.

We should do the same as in Xabaril#2040, Xabaril#2116 and Xabaril#2096

I left RabbitMQ.v6 as-is to limit the breaking change. Only when an existing user moves from RabbitMQ.Client v6 to v7 will they need to update their health check (along with other breaking changes in RabbitMQ.Client).
adamsitnik pushed a commit that referenced this pull request Dec 13, 2024
* Remove private static cache from RabbitMQHealthCheck

RabbitMQHealthCheck is maintaining it's private static cache of instances. It's wrong, as it can cause a memory leak (it won't ever be freed).

Moreover, it's can create instances of IConnection. This is wrong, as it can lead into a situation when we have multiple instances of IConnection that are connected to the same server: one used by the app and another created and used by the health check. And we should have only one.

We should do the same as in #2040, #2116 and #2096

I left RabbitMQ.v6 as-is to limit the breaking change. Only when an existing user moves from RabbitMQ.Client v6 to v7 will they need to update their health check (along with other breaking changes in RabbitMQ.Client).

* Add test when no IConnection is registered in DI.
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.

3 participants