Centralize Postgres connection string policy across all modules

Extract connection string building into PostgresConnectionStringPolicy so all
services use consistent pooling, application_name, and timeout settings.
Adopt the new policy in 20+ module DataSource/ServiceCollectionExtensions classes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-06 08:51:04 +03:00
parent 517fa0a92d
commit ccdfd41e4f
64 changed files with 625 additions and 178 deletions

View File

@@ -23,7 +23,7 @@ namespace StellaOps.Doctor.WebService.Services;
/// </summary>
public sealed class PostgresReportStorageService : IReportStorageService, IDisposable
{
private readonly string _connectionString;
private readonly NpgsqlDataSource _dataSource;
private readonly DoctorServiceOptions _options;
private readonly ILogger<PostgresReportStorageService> _logger;
private readonly Timer? _cleanupTimer;
@@ -37,9 +37,10 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
IOptions<DoctorServiceOptions> options,
ILogger<PostgresReportStorageService> logger)
{
_connectionString = configuration.GetConnectionString("StellaOps")
var connectionString = configuration.GetConnectionString("StellaOps")
?? configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("Database connection string not configured");
_dataSource = CreateDataSource(connectionString);
_options = options.Value;
_logger = logger;
@@ -60,8 +61,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
var json = JsonSerializer.Serialize(report, JsonSerializerOptions.Default);
var compressed = CompressJson(json);
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = """
INSERT INTO doctor_reports (run_id, started_at, completed_at, overall_severity,
@@ -104,8 +104,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
/// <inheritdoc />
public async Task<DoctorReport?> GetReportAsync(string runId, CancellationToken ct)
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = "SELECT report_json_compressed FROM doctor_reports WHERE run_id = @runId";
@@ -126,8 +125,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
/// <inheritdoc />
public async Task<IReadOnlyList<ReportSummaryDto>> ListReportsAsync(int limit, int offset, CancellationToken ct)
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = """
SELECT run_id, started_at, completed_at, overall_severity,
@@ -170,8 +168,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
/// <inheritdoc />
public async Task<bool> DeleteReportAsync(string runId, CancellationToken ct)
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = "DELETE FROM doctor_reports WHERE run_id = @runId";
@@ -185,8 +182,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
/// <inheritdoc />
public async Task<int> GetCountAsync(CancellationToken ct)
{
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = "SELECT COUNT(*) FROM doctor_reports";
@@ -207,8 +203,7 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
var cutoff = DateTimeOffset.UtcNow.AddDays(-_options.ReportRetentionDays);
await using var connection = new NpgsqlConnection(_connectionString);
await connection.OpenAsync(ct);
await using var connection = await _dataSource.OpenConnectionAsync(ct);
const string sql = "DELETE FROM doctor_reports WHERE created_at < @cutoff";
@@ -261,7 +256,20 @@ public sealed class PostgresReportStorageService : IReportStorageService, IDispo
if (!_disposed)
{
_cleanupTimer?.Dispose();
_dataSource.Dispose();
_disposed = true;
}
}
private static NpgsqlDataSource CreateDataSource(string connectionString)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
var connectionStringBuilder = new NpgsqlConnectionStringBuilder(connectionString)
{
ApplicationName = "stellaops-doctor-report-storage"
};
return new NpgsqlDataSourceBuilder(connectionStringBuilder.ConnectionString).Build();
}
}

View File

@@ -3,6 +3,7 @@
## Completed
### 2026-01-12 - Sprint 001_007 Implementation
- [x] SPRINT_20260405_011-XPORT: named the PostgreSQL report-storage data source and aligned the runtime path with the shared transport attribution guardrail.
- [x] Created project structure following Platform WebService pattern
- [x] Implemented DoctorServiceOptions with authority configuration
- [x] Defined DoctorPolicies and DoctorScopes for authorization