Running our database in Aspire
One of the coolest things about .NET Aspire is that we can run our entire system with it. The first step for us is to migrate our Postgres database to Aspire.
Adding the integration
One of the core pillars of .NET Aspire is called an "Integration". Integrations are just Nuget packages with a fancy name. Fundamentally they just wrap existing packages with a log of orchestration and pre-configuration by Microsoft to give a great developer experience.
Let's add Postgres as a hosted integration.
First let's add the Nuget package for the integration to the AppHost project:
dotnet add package Aspire.Hosting.PostgreSQL
Then we need to configure some parameters for the project such as the database username and password:
"Parameters" :
{
"postgres-username": "workshop",
"postgres-password": "changeme"
}
Then we can configure the database integration in the Program.cs:
var mainDbUsername = builder.AddParameter("postgres-username");
var mainDbPassword = builder.AddParameter("postgres-password");
var mainDb = builder.AddPostgres("main-db", mainDbUsername, mainDbPassword, port: 5432)
.WithDataVolume()
.AddDatabase("dometrain");
builder.AddProject<Projects.Dometrain_Monolith_Api>("dometrain-api")
.WithReference(mainDb);
At this point, nothing needs to change on our API level. Simply run the AppHost project and see the magic happen. We now have a new database that's being run and managed by Aspire.
There are however benefits in migrating our current code to use the Aspire Postgres client, mainly around resilience and telemetry. Thankfully it's very simple to do.
First install the Postgres Aspire package in the API project.
dotnet add package Aspire.Npgsql
Then simply call the dependency injection AddNpgsqlDataSource
method in the API's Program.cs and replace
the multiplexer registration code.
builder.Services.AddSingleton<IDbConnectionFactory, NpgsqlConnectionFactory>();
builder.AddNpgsqlDataSource("dometrain");
Lastly we need to update the NpgsqlConnectionFactory
to use the new NpgsqlDataSource
.
public class NpgsqlConnectionFactory : IDbConnectionFactory
{
private readonly NpgsqlDataSource _dataSource;
public NpgsqlConnectionFactory(NpgsqlDataSource dataSource)
{
_dataSource = dataSource;
}
public async Task<IDbConnection> CreateConnectionAsync(CancellationToken token = default)
{
var connection = await _dataSource.OpenConnectionAsync(token);
return connection;
}
}
Hack time
Now, because we have predefined a database through Aspire, we have to make sure it exists.
This is something that ideally, should be part of the App.Host project but we will add it in our API via the DbInitializer
class.
All we need to do is update the class with the following code:
private readonly NpgsqlDataSource _dataSource;
public DbInitializer(NpgsqlDataSource dataSource)
{
_dataSource = dataSource;
}
public async Task InitializeAsync()
{
await using var connection = _dataSource.CreateConnection();
#region MEGAHACKDONTLOOK
var topConnection = new NpgsqlConnection(string.Join(";", connection.ConnectionString.Split(";").Concat(["password=changeme"]).Where(x => !x.StartsWith("database=", StringComparison.OrdinalIgnoreCase))));
try
{
await topConnection.ExecuteAsync("CREATE DATABASE dometrain");
}
catch
{
}
await topConnection.CloseAsync();
#endregion
var script = """
Now that we have our entire system running through .NET Aspire, let's take a look at how we can expand our tech choices even further.