Problem
Do you have a lot of “side-projects”? I do. Did you ever find yourself in situation where you open one of those “side-projects” thinking everything will work fine because you are an awesome developer and you are not making mistakes?
And then reality kicks in.
I must admit, when I was setting up my template, which I use for most (if not all) blog posts, I was too lazy to set up docker and automatic migration. I though it wouldn’t be a problem…
So, this is how it usually goes…
I get a burst of energy to write something I started a while ago. I open that “side-project” and I try to run it. It fails mostly because I forgot that I’m using some database and that appropriate docker container isn’t running.
So, now I have to start a container. Any normal person would go to the documentation and use it as a reference, but not me. I did it a milion times, what can go wrong? Then I spend next 10 minutes writing random commands in bash hoping one of them will start that container correctly. Then I give up and do what any normal person would do.
So, I have a database running and I have to update the database with my migrations. Any normal person would go to the documentation and use it as a reference, but not me. I think that you know the rest of the story…
It’s time to fix this.
Solution
- Adding docker-compose file
- Running migration automatically on project startup
Adding docker-compose.yml file
- At the root of our project, let’s add docker-compose.yml
- For this project, we are going to use Postgres
version: "3.3"
services:
company-shorts-postgres:
image: "postgres:15.1-alpine"
container_name: company-shorts-postgres
volumes:
- ./var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
restart: unless-stopped
ports:
- "5432:5432"
Now when we run docker-compose up, it will run our database. Nice!
Running migration automatically
- Inside our DependencyInjection.cs lets add another method
namespace Company.Shorts.Infrastructure.Db.Postgres
{
using Company.Shorts.Infrastructure.Db.Postgres.Internal;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
public static class DependencyInjection
{
public static IServiceCollection AddPostgresDatabaseLayer(this IServiceCollection services, PostgresAdapterSettings settings)
{
services.AddDbContext<PostgresDbContext>(options =>
{
options.UseNpgsql(settings.Url);
}, ServiceLifetime.Transient);
DatabaseDependencyInjection.AddRepositories<PostgresDbContext>(services);
return services;
}
public static IApplicationBuilder MigratePostgresDb(this IApplicationBuilder builder)
{
using var scope = builder.ApplicationServices.CreateScope();
using var dbContext = scope.ServiceProvider.GetService<PostgresDbContext>();
if (dbContext is null)
{
throw new ArgumentNullException(nameof(dbContext));
}
if (dbContext.Database.GetPendingMigrations().Count() > 0)
{
dbContext.Database.Migrate();
}
return builder;
}
}
public class PostgresAdapterSettings
{
public const string Key = nameof(PostgresAdapterSettings);
public string Url { get; set; } = default!;
}
}
- Let’s call that method in our Startup.cs class
namespace Company.Shorts
{
using Company.Shorts.Application;
using Company.Shorts.Blocks.Common.Mapping.Configuration;
using Company.Shorts.Blocks.Common.Serilog.Configuration;
using Company.Shorts.Blocks.Common.Swagger.Configuration;
using Company.Shorts.Blocks.Presentation.Api.Configuration;
using Company.Shorts.Infrastructure.Db.Postgres;
using Company.Shorts.Infrastructure.ExampleAdapter;
using Company.Shorts.Presentation.Api;
using Hellang.Middleware.ProblemDetails;
public sealed class Startup
{
public Startup(
IConfiguration configuration,
IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; }
public ExampleAdapterSettings ExampleAdapterSettings =>
Configuration
.GetSection(ExampleAdapterSettings.Key)
.Get<ExampleAdapterSettings>();
public PostgresAdapterSettings PostgresAdapterSettings =>
Configuration
.GetSection(PostgresAdapterSettings.Key)
.Get<PostgresAdapterSettings>();
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddHealthChecks();
services.AddPostgresDatabaseLayer(PostgresAdapterSettings);
services.AddInfrastructureExampleAdapter(ExampleAdapterSettings);
services.AddApplicationLayer();
services.AddPresentationLayer(Environment);
services.AddAutoMapperConfiguration(AppDomain.CurrentDomain);
}
public void Configure(IApplicationBuilder app)
{
app.UseProblemDetails();
if (!Environment.IsDevelopment())
{
app.UseHsts();
}
// Automatic Migration
app.MigratePostgresDb();
app.UseSwaggerConfiguration();
app.UseHttpsRedirection();
app.UseCors(options => options
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseSerilogConfiguration();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapDefaultHealthCheckRoute();
});
}
}
}
Benefits?
- You can just run your project and everything will sync.
Branch: steps/automatic-migration