-
Notifications
You must be signed in to change notification settings - Fork 803
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
DB health checks: design #2113
Comments
For clarity, could you provide the method signatures so we're sure we're talking about the same thing? For example, in #2096 I took the existing method:
and added:
Does that implement the new API you're suggesting? If so, I have no objections because that's already what I came up with (assuming that the new API should be additive and we shouldn't make a needless breaking change by removing existing methods). |
This seems like a very strong point to me. Even when users update to .NET 7.0+, they still have to be educated about |
Sure, please excuse me for not doing that. Here are the APIs for PostgreSQL from #2116: namespace HealthChecks.NpgSql
{
public class NpgSqlHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
{
public NpgSqlHealthCheck(NpgSqlHealthCheckOptions options) { }
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { }
}
public class NpgSqlHealthCheckOptions
{
public NpgSqlHealthCheckOptions(string connectionString) { }
public string CommandText { get; set; }
public Action<NpgsqlConnection>? Configure { get; set; }
public string? ConnectionString { get; set; }
public Func<object?, HealthCheckResult>? HealthCheckResultBuilder { get; set; }
}
}
namespace Microsoft.Extensions.DependencyInjection
{
public static class NpgSqlHealthCheckBuilderExtensions
{
public static IHealthChecksBuilder AddNpgSql(this IHealthChecksBuilder builder, NpgSqlHealthCheckOptions options, string? name = null, HealthStatus? failureStatus = default, IEnumerable<string>? tags = null, TimeSpan? timeout = default) { }
public static IHealthChecksBuilder AddNpgSql(this IHealthChecksBuilder builder, Func<IServiceProvider, string> connectionStringFactory, string healthQuery = "SELECT 1;", Action<NpgsqlConnection>? configure = null, string? name = null, HealthStatus? failureStatus = default, IEnumerable<string>? tags = null, TimeSpan? timeout = default) { }
public static IHealthChecksBuilder AddNpgSql(this IHealthChecksBuilder builder, Func<IServiceProvider, NpgsqlDataSource>? dbDataSourceFactory = null, string healthQuery = "SELECT 1;", Action<NpgsqlConnection>? configure = null, string? name = null, HealthStatus? failureStatus = default, IEnumerable<string>? tags = null, TimeSpan? timeout = default) { }
public static IHealthChecksBuilder AddNpgSql(this IHealthChecksBuilder builder, string connectionString, string healthQuery = "SELECT 1;", Action<NpgsqlConnection>? configure = null, string? name = null, HealthStatus? failureStatus = default, IEnumerable<string>? tags = null, TimeSpan? timeout = default) { }
}
} My reasoning:
In case of Npgsql, we should point to Npgsql.DependencyInjection package because it configures the registered components, for examply it configures the logger factory: and registers not only the db data source, but also db connection: This is the first overload: public static IHealthChecksBuilder AddAbc(this IHealthChecksBuilder builder, Func<IServiceProvider, AbcDataSource>? dbDataSourceFactory = null,
This can be done by providing just the connection string. Users may need to access public static IHealthChecksBuilder AddAbc(this IHealthChecksBuilder builder, string connectionString, /* optional args */);
public static IHealthChecksBuilder AddAbc(this IHealthChecksBuilder builder, Func<IServiceProvider, string> connectionStringFactory, /* optional args */);
We need connection string and all optional arguments specific to DBs:
I am not sure whether we should allow the users to specify an instance of services.AddAbc(sp => new AbcOptions()
{
DataSource = new DbDataSource($ConnectionString) // it should be sp.GetRequiredService<AbcDataSource>()
}); I am not totally against it. From my experience working on the .NET Team it's easy to add new things when needed, but it's very hard to remove existing ones. |
That basically aligns with what I did on #2096. (There is also a MySqlConnector.DependencyInjection package that does similar work of registering My one piece of feedback there (#2096 (comment)) was that I was less sure about not exposing However, I agree that it's easier to add things than take them away, and ultimately I don't have a strong view on the matter. (If changing |
.NET 7 has introduced the concept of
DbDataSource
, from dotnet/runtime#64812 we can read it's benefits:#1993 has proved, that our current design can lead into failures: when the users don't provide an existing
DbDataSource
when registering a health check, but just aConnectionString
, then an instance ofDbDataSource
is created each time the health check is invoked.This is bad for performance, because we end up having multiple connections open. The fix that I've provided in #2045 was just a workaround, we need a proper solution that can be applied to all DB-based health checks (see #2096 as an example).
Similarly to #2040, we should steer the users towards best practices and provide an API that makes it very hard to fall into such perf traps.
We are soon about to release 8.0 and it's great opportunity to introduce the breaking change. Having README.md should help us to ease the pain.
Possible solutions:
Provide a health check that by default resolves an instance of given
DbDataSource
from DI container.Pros:
DbDataSource
in the DICons:
DbDataSource
in the DI, they get no compiler error and the health check fails at runtimeDbDataSource
)DbConnection
and not willing to learn and useDbDataSource
Provide a health check that does not accept
DbDataSource
Pros:
ConnectionString
)Cons:
DbDataSource
DbDataSource
? cc @rojiIn theory, we could implement the check in a way that it would try to resolve
DbDataSource
first and re-use it when possible:This could be a performance hit, but we could cache the result similarly to what I did in #2045.
My current best idea is to provide both :
@sungam3r @bgrainger what is your opinion on that?
The text was updated successfully, but these errors were encountered: