-
Notifications
You must be signed in to change notification settings - Fork 0
03 01 ManagedConfiguration
ManagedConfiguration is FractalDataWorks' pattern for database-persisted, source-generated configuration. It bridges compile-time type safety with runtime database-driven configuration.
The [ManagedConfiguration] attribute marks a class for:
- DDL Generation - SQL table definitions with parent-child relationships
- ConfigurationType Generation - Metadata class with SQL queries
- Validator Generation - FluentValidation validators
- UI Generation - Form models for configuration editing
flowchart TB
subgraph "Compile Time"
A[Configuration Class] --> B[ManagedConfiguration Attribute]
B --> C[Configuration.SourceGenerators]
C --> D[DDL Definition]
C --> E[ConfigurationType Class]
C --> F[Validator]
C --> G[ConfigurationTypes Collection]
end
subgraph "Bootstrap Time"
H[appsettings.json] --> I[ConfigurationDb Bootstrap]
I --> J[MsSqlConnection to ConfigDb]
D --> K[Schema Applied to Database]
end
subgraph "Runtime"
J --> L[ConfigurationLoader]
L --> M[QueryCommand via DataCommands]
M --> N[cfg.* Tables]
N --> O[Configuration Instances]
O --> P[IOptionsSnapshot via ServiceType.Configure]
end
flowchart LR
subgraph "Source Files"
A["[ManagedConfiguration]<br/>MsSqlConnectionConfiguration.cs"]
end
subgraph "Source Generator Output"
B["MsSqlConnectionConfiguration.Ddl.g.cs<br/>(DDL definition)"]
C["MsSqlConnectionConfigurationConfigurationType.g.cs<br/>(Metadata + SQL queries)"]
D["ConfigurationTypes.g.cs<br/>(Static collection)"]
E["MsSqlConnectionConfiguration.Validator.g.cs<br/>(FluentValidation)"]
end
A --> B
A --> C
A --> D
A --> E
Configuration types often have inheritance hierarchies. The parent holds common properties, children hold type-specific properties.
classDiagram
class IConnectionConfiguration {
<<interface>>
+Guid Id
+string Name
+string ConnectionType
+bool IsEnabled
}
class ConnectionConfigurationBase~T~ {
<<abstract>>
+Guid Id
+string Name
+abstract string ConnectionType
+bool IsEnabled = true
+string? SecretManagerName
}
class MsSqlConnectionConfiguration {
+string ConnectionType = "MsSql"
+string Server
+string Database
+int Port = 1433
+string? Authentication
}
class RestConnectionConfiguration {
+string ConnectionType = "Rest"
+string BaseUrl
+int TimeoutSeconds = 30
+Dictionary~string,string~ DefaultHeaders
}
IConnectionConfiguration <|.. ConnectionConfigurationBase
ConnectionConfigurationBase <|-- MsSqlConnectionConfiguration
ConnectionConfigurationBase <|-- RestConnectionConfiguration
Parent-child relationships create separate tables with foreign keys:
erDiagram
Connection {
uniqueidentifier Id PK
nvarchar(256) Name UK
nvarchar(50) ServiceOptionType
bit IsEnabled
}
MsSqlConnection {
uniqueidentifier Id PK
uniqueidentifier ConnectionId FK
nvarchar(256) Server
nvarchar(256) Database
int Port
}
RestConnection {
uniqueidentifier Id PK
uniqueidentifier ConnectionId FK
nvarchar(2048) BaseUrl
int TimeoutSeconds
nvarchar(max) DefaultHeaders
}
Connection ||--o| MsSqlConnection : "has"
Connection ||--o| RestConnection : "has"
Table Relationship Patterns:
| Pattern | FK Column | Use Case |
|---|---|---|
| Parent-Child |
{ParentName}Id (e.g., ConnectionId) |
Type-specific properties (MsSqlConnection, RestConnection) |
The MsSqlConfigurationProvider automatically loads parent-child configurations and child key-value tables declared via [ChildTable] attributes.
ConfigurationTypes follows the TypeCollection pattern for cross-assembly discovery and runtime registration. Each [ManagedConfiguration] class generates a ConfigurationType that is automatically registered.
classDiagram
class IConfigurationType {
<<interface>>
+string Schema
+string TableName
+string? ParentName
+string? ServiceCategory
+string? ServiceType
+Type ConfigurationClrType
}
class ConfigurationTypeBase {
<<abstract>>
+string Schema
+string TableName
+string? ParentName
+string? ServiceCategory
+Type ConfigurationClrType
}
class MsSqlConnectionConfigurationType {
+Schema = "cfg"
+TableName = "MsSqlConnection"
+ParentName = "ConnectionConfigurationBase"
+ServiceCategory = "Connection"
}
IConfigurationType <|.. ConfigurationTypeBase
ConfigurationTypeBase <|-- MsSqlConnectionConfigurationType
ConfigurationTypes uses module initializers for cross-assembly discovery:
-
Source Generator -
ConfigurationSourceGeneratorgenerates ConfigurationType classes marked with[ConfigurationTypeOption] -
Module Initializer Generator -
ConfigurationTypeModuleInitializerGenerator(in Registration.SourceGenerators) scans referenced assemblies and generates module initializers in entry points -
Runtime - Module initializers register types with
ConfigurationTypes.Register()
// Generated in entry point (e.g., Reference.Api)
[ModuleInitializer]
public static void RegisterConfigurationTypes()
{
ConfigurationTypes.Register(new MsSqlConnectionConfigurationType());
ConfigurationTypes.Register(new RestConnectionConfigurationType());
// ... all discovered types from referenced assemblies
}// Lookup by name
var mssqlType = ConfigurationTypes.ByName("MsSqlConnection");
// Lookup by service category
var connectionTypes = ConfigurationTypes.All()
.Where(t => t.ServiceCategory == "Connection");
// Lookup by service type
var mssqlType = ConfigurationTypes.All()
.FirstOrDefault(t => t.ServiceType == "MsSql");Each [ManagedConfiguration] class generates a ConfigurationType with:
| Property | Description |
|---|---|
Schema |
Database schema (e.g., "cfg") |
TableName |
Table name (e.g., "MsSqlConnection") |
ParentName |
Parent configuration name for joins |
ParentForeignKeyProperty |
FK column name |
ServiceCategory |
Grouping (e.g., "Connection", "SecretManager") |
ServiceType |
Discriminator (e.g., "MsSql", "EnvironmentVariable") |
SelectAllQuery |
Generated SQL to load all rows |
SelectByNameQuery |
Generated SQL to load by name |
ConfigurationClrType |
The CLR Type for deserialization |
sequenceDiagram
participant App as Application Startup
participant Boot as ConfigurationBootstrap
participant Conn as MsSqlConnection
participant Loader as ConfigurationLoader
participant Types as ConfigurationTypes
participant DB as ConfigurationDb
App->>Boot: Initialize with bootstrap connection string
Boot->>Conn: Create connection to ConfigDb
App->>Loader: LoadAll()
loop For each ServiceCategory
Loader->>Types: GetByServiceCategory("Connection")
Types-->>Loader: [MsSqlConnectionConfigurationType, RestConnectionConfigurationType, ...]
loop For each ConfigurationType
Loader->>DB: Execute SelectAllQuery
DB-->>Loader: DataReader rows
Loader->>Loader: Deserialize to Configuration instances
end
end
Loader->>App: Register with IOptionsSnapshot
[ManagedConfiguration(
Schema = "cfg",
TableName = "MsSqlConnection",
ParentTableName = "Connection",
ServiceCategory = "Connection",
ServiceType = "MsSql")]
public partial class MsSqlConnectionConfiguration
: ConnectionConfigurationBase<MsSqlConnectionConfiguration>
{
public override string ConnectionType => "MsSql";
public string Server { get; set; } = "localhost";
public string Database { get; set; } = string.Empty;
public int Port { get; set; } = 1433;
[ConfigurationOption(typeof(AuthenticationMethods))]
public string? Authentication { get; set; }
}-- Parent table (if not exists)
CREATE TABLE cfg.ConnectionConfigurationBase (
Id uniqueidentifier NOT NULL PRIMARY KEY,
Name nvarchar(256) NOT NULL UNIQUE,
ConnectionType nvarchar(50) NOT NULL,
IsEnabled bit NOT NULL DEFAULT 1,
SecretManagerName nvarchar(256) NULL
);
-- Child table
CREATE TABLE cfg.MsSqlConnection (
Id uniqueidentifier NOT NULL PRIMARY KEY,
Name nvarchar(256) NOT NULL UNIQUE,
ConnectionId uniqueidentifier NOT NULL,
Server nvarchar(256) NOT NULL,
[Database] nvarchar(256) NOT NULL,
Port int NOT NULL DEFAULT 1433,
Authentication nvarchar(50) NULL
-- Note: No hard FK constraint - parent Id isn't unique due to versioning
);// Via ConfigurationTypes collection (generated)
var mssqlType = ConfigurationTypes.ByName("MsSqlConnectionConfiguration");
Console.WriteLine($"Table: {mssqlType.Schema}.{mssqlType.TableName}");
Console.WriteLine($"Parent: {mssqlType.ParentName}");
// Via IOptionsSnapshot (standard .NET)
public class MyService
{
private readonly IOptionsSnapshot<List<MsSqlConnectionConfiguration>> _configs;
public MyService(IOptionsSnapshot<List<MsSqlConnectionConfiguration>> configs)
{
_configs = configs;
}
public MsSqlConnectionConfiguration? GetByName(string name)
=> _configs.Value.FirstOrDefault(c => c.Name == name);
}| Aspect | Bootstrap (appsettings.json) | Runtime (ConfigurationDb) |
|---|---|---|
| Purpose | Connect to ConfigDb | All service configuration |
| Storage | JSON file | Database tables |
| When Loaded | Application startup | After ConfigDb connection |
| Examples | ConfigDb connection, Serilog | Connections, DataSets, SecretManagers |
| Reloadable | Restart required | Hot-reload supported |
| Component | Package | Purpose |
|---|---|---|
[ManagedConfiguration] |
Configuration.SourceGenerators | Attribute to mark config classes |
ConfigurationTypes |
Generated in each assembly | Static lookup collection |
ConfigurationLoader |
Configuration.MsSql | Loads config from database |
FdwConfigurationProvider |
Configuration | IConfiguration provider |
ManagedConfigurationProvider<T> |
Configuration | Caching provider for hot-reload |
- Service Domains Overview - How configs integrate with services
- Creating a Service Domain - Step-by-step with configuration
-
TypeCollections - TypeCollections referenced by
[ConfigurationOption] - Configuration.SourceGenerators README - Detailed attribute reference