Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/AndreGoepel.AppFoundation.Hosting/AppFoundationOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using JasperFx;
using Microsoft.AspNetCore.DataProtection;
using Wolverine;

Expand Down Expand Up @@ -54,4 +55,20 @@ public sealed class AppFoundationOptions
/// <c>options.ConfigureWolverine = w =&gt; w.Discovery.IncludeAssembly(typeof(SomeHandler).Assembly)</c>.
/// </summary>
public Action<WolverineOptions>? ConfigureWolverine { get; set; }

/// <summary>
/// How Marten manages the database schema. When <c>null</c> (the default) the
/// foundation selects a safe mode by environment: <see cref="AutoCreate.All"/> in
/// Development (fast inner loop; permits destructive rebuilds) and
/// <see cref="AutoCreate.CreateOrUpdate"/> everywhere else (additive only — never
/// drops or rewrites existing objects, so a code/database mismatch cannot destroy
/// data at runtime).
/// <para>
/// Set explicitly to take control — most notably <see cref="AutoCreate.None"/> for a
/// least-privilege deployment where the schema is provisioned out-of-band (a
/// migration job / <c>db-apply</c>) and the application runs with a database role
/// that has no DDL rights.
/// </para>
/// </summary>
public AutoCreate? SchemaCreation { get; set; }
}
14 changes: 13 additions & 1 deletion src/AndreGoepel.AppFoundation.Hosting/Initialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,19 @@ public static WebApplicationBuilder AddAppFoundation(
marten.Connection(connectionString);

marten.InitializeIdentity();
marten.AutoCreateSchemaObjects = AutoCreate.All;

// Never let the running app drop/rewrite schema to match code: default
// to additive-only (CreateOrUpdate) outside Development, keeping the
// permissive All only for the local inner loop. A host can override —
// e.g. AutoCreate.None for a least-privilege role with schema applied
// out-of-band (#53).
marten.AutoCreateSchemaObjects =
options.SchemaCreation
?? (
builder.Environment.IsDevelopment()
? AutoCreate.All
: AutoCreate.CreateOrUpdate
);

// The alias (and thus the table name) is part of the storage
// contract — hosts that persisted key ring entries with an
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using AndreGoepel.AppFoundation.Hosting;
using JasperFx;
using Marten;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AndreGoepel.AppFoundation.Tests.Hosting;

public class AddAppFoundationSchemaCreationTests
{
[Fact]
public void AddAppFoundation_NonDevelopmentEnvironment_UsesCreateOrUpdate()
{
// Arrange
var builder = CreateBuilder("Production");

// Act
builder.AddAppFoundation();

// Assert
Assert.Equal(AutoCreate.CreateOrUpdate, ResolveAutoCreate(builder));
}

[Fact]
public void AddAppFoundation_DevelopmentEnvironment_UsesAll()
{
// Arrange
var builder = CreateBuilder("Development");

// Act
builder.AddAppFoundation();

// Assert
Assert.Equal(AutoCreate.All, ResolveAutoCreate(builder));
}

[Fact]
public void AddAppFoundation_ExplicitSchemaCreation_OverridesEnvironmentDefault()
{
// Arrange — Development would otherwise select All.
var builder = CreateBuilder("Development");

// Act
builder.AddAppFoundation(options => options.SchemaCreation = AutoCreate.None);

// Assert
Assert.Equal(AutoCreate.None, ResolveAutoCreate(builder));
}

private static AutoCreate ResolveAutoCreate(WebApplicationBuilder builder)
{
using var provider = builder.Services.BuildServiceProvider();
var store = provider.GetRequiredService<IDocumentStore>();
return ((StoreOptions)store.Options).AutoCreateSchemaObjects;
}

private static WebApplicationBuilder CreateBuilder(string environmentName)
{
var builder = WebApplication.CreateBuilder(
new WebApplicationOptions { EnvironmentName = environmentName }
);
builder.Configuration.AddInMemoryCollection(
new Dictionary<string, string?>
{
["ConnectionStrings:appfoundation-database"] =
"Host=localhost;Port=5432;Database=test;Username=u;Password=p",
}
);
return builder;
}
}
Loading