Overview
The ALM DSL is the runtime side of the lifecycle. It declares the things that exist while the application is running: deployable services, the feature flags that gate their behaviour, the SLOs they must respect, the health probes that prove they are alive, the observability signals they emit, and the rollout strategies that introduce change without breaking production.
The source generator produces:
- An
AlmRegistryof const strings (Alm.SERVICE_*,Alm.FLAG_*,Alm.SLO_*) - The Aspire AppHost wiring -- a
Builder.AddProject<...>()call per[Service], with health probes and resource references baked in - A strongly-typed
IFeatureFlagsTypedinterface -- one bool property per declared flag, zero magic strings in business code - OpenTelemetry instrumentation classes (
Meter,ActivitySource) per[ObservabilitySignal] - A Grafana dashboard JSON payload per
[Slo] - Roslyn analyzers
ALM001-ALM100enforcing flag usage, SLO presence and layer isolation
The DSL's most important property is what it does not generate: it does not put any runtime code into AcmeStore.Alm itself. The Alm project is a build-time metadata project. The generated Aspire wiring lands in AcmeStore.AppHost. The generated typed flag interface lands in AcmeStore.FeatureToggles. Domain code never references AcmeStore.Alm. This is enforced by analyzer ALM100 -- see Cross-DSL references below.
Service
namespace Cmf.Alm.Lib;
/// <summary>
/// A deployable runtime unit. References an SDLC BuildTarget for
/// the artifact to ship and a Deployment for where to ship it.
/// The compiler refuses to declare a Service without a matching
/// build target.
/// </summary>
[MetaConcept("Service")]
[MetaReference("Build", "BuildTarget", Multiplicity = "1")]
[MetaConstraint("MustHaveBuildTarget",
"Build != null",
Message = "Service must reference a [BuildTarget] from the SDLC DSL")]
public sealed class ServiceAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Build", "string", Required = true)]
public string Build { get; set; }
[MetaProperty("Description", "string")]
public string? Description { get; set; }
public ServiceAttribute(string name, string build)
{
Name = name;
Build = build;
}
}namespace Cmf.Alm.Lib;
/// <summary>
/// A deployable runtime unit. References an SDLC BuildTarget for
/// the artifact to ship and a Deployment for where to ship it.
/// The compiler refuses to declare a Service without a matching
/// build target.
/// </summary>
[MetaConcept("Service")]
[MetaReference("Build", "BuildTarget", Multiplicity = "1")]
[MetaConstraint("MustHaveBuildTarget",
"Build != null",
Message = "Service must reference a [BuildTarget] from the SDLC DSL")]
public sealed class ServiceAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Build", "string", Required = true)]
public string Build { get; set; }
[MetaProperty("Description", "string")]
public string? Description { get; set; }
public ServiceAttribute(string name, string build)
{
Name = name;
Build = build;
}
}Deployment
/// <summary>
/// Where and how a Service is deployed. Kind selects the Aspire
/// resource builder method that will be emitted in AppHost.g.cs.
/// </summary>
[MetaConcept("Deployment")]
public sealed class DeploymentAttribute : Attribute
{
[MetaProperty("Kind", "DeploymentKind", Required = true)]
public DeploymentKind Kind { get; set; }
[MetaProperty("Replicas", "int")]
public int Replicas { get; set; } = 1;
public DeploymentAttribute(DeploymentKind kind) => Kind = kind;
}
public enum DeploymentKind
{
AspireProject, // builder.AddProject<Projects.X>(...)
AspireContainer, // builder.AddContainer(...)
AspireExecutable, // builder.AddExecutable(...)
EdgeFunction // out-of-Aspire edge runtime
}/// <summary>
/// Where and how a Service is deployed. Kind selects the Aspire
/// resource builder method that will be emitted in AppHost.g.cs.
/// </summary>
[MetaConcept("Deployment")]
public sealed class DeploymentAttribute : Attribute
{
[MetaProperty("Kind", "DeploymentKind", Required = true)]
public DeploymentKind Kind { get; set; }
[MetaProperty("Replicas", "int")]
public int Replicas { get; set; } = 1;
public DeploymentAttribute(DeploymentKind kind) => Kind = kind;
}
public enum DeploymentKind
{
AspireProject, // builder.AddProject<Projects.X>(...)
AspireContainer, // builder.AddContainer(...)
AspireExecutable, // builder.AddExecutable(...)
EdgeFunction // out-of-Aspire edge runtime
}Slo
/// <summary>
/// A Service Level Objective. Lambdas are evaluated at runtime against
/// observed metrics; failures emit ObservabilityEvents and -- when
/// configured -- open Incidents. The same lambda is also exported as a
/// Grafana alerting rule (PromQL) in the generated dashboard JSON.
/// </summary>
[MetaConcept("Slo")]
[MetaInherits("MetaConstraint")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SloAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Expression", "lambda<ServiceMetrics, bool>", Required = true)]
public Expression<Func<ServiceMetrics, bool>> Expression { get; }
[MetaProperty("Window", "TimeSpan")]
public string Window { get; set; } = "00:05:00";
public SloAttribute(string name, Expression<Func<ServiceMetrics, bool>> expression)
{
Name = name;
Expression = expression;
}
}/// <summary>
/// A Service Level Objective. Lambdas are evaluated at runtime against
/// observed metrics; failures emit ObservabilityEvents and -- when
/// configured -- open Incidents. The same lambda is also exported as a
/// Grafana alerting rule (PromQL) in the generated dashboard JSON.
/// </summary>
[MetaConcept("Slo")]
[MetaInherits("MetaConstraint")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SloAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Expression", "lambda<ServiceMetrics, bool>", Required = true)]
public Expression<Func<ServiceMetrics, bool>> Expression { get; }
[MetaProperty("Window", "TimeSpan")]
public string Window { get; set; } = "00:05:00";
public SloAttribute(string name, Expression<Func<ServiceMetrics, bool>> expression)
{
Name = name;
Expression = expression;
}
}FeatureFlag
/// <summary>
/// A runtime toggle. Generates one strongly-typed property on
/// IFeatureFlagsTyped. The Owner property links the flag back to
/// a PLM Product so that flag stewardship aligns with product
/// ownership. The Default value is what production reads when the
/// flag store is unreachable.
/// </summary>
[MetaConcept("FeatureFlag")]
[MetaConstraint("FlagNameIsValidIdentifier",
"Regex.IsMatch(Name, '^[A-Z][A-Za-z0-9_]*$')",
Message = "FeatureFlag name must be a valid PascalCase C# identifier")]
public sealed class FeatureFlagAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Default", "bool")]
public bool Default { get; set; } = false;
[MetaProperty("Owner", "string")]
public string? Owner { get; set; }
[MetaProperty("ExpiresOn", "DateOnly")]
public string? ExpiresOn { get; set; }
public FeatureFlagAttribute(string name) => Name = name;
}/// <summary>
/// A runtime toggle. Generates one strongly-typed property on
/// IFeatureFlagsTyped. The Owner property links the flag back to
/// a PLM Product so that flag stewardship aligns with product
/// ownership. The Default value is what production reads when the
/// flag store is unreachable.
/// </summary>
[MetaConcept("FeatureFlag")]
[MetaConstraint("FlagNameIsValidIdentifier",
"Regex.IsMatch(Name, '^[A-Z][A-Za-z0-9_]*$')",
Message = "FeatureFlag name must be a valid PascalCase C# identifier")]
public sealed class FeatureFlagAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Default", "bool")]
public bool Default { get; set; } = false;
[MetaProperty("Owner", "string")]
public string? Owner { get; set; }
[MetaProperty("ExpiresOn", "DateOnly")]
public string? ExpiresOn { get; set; }
public FeatureFlagAttribute(string name) => Name = name;
}Rollout
/// <summary>
/// How a feature flag or a deployment moves from off to on.
/// Strategy controls which runtime selector the typed flag
/// interface uses (canary % bucketing, blue/green pair, dark launch).
/// </summary>
[MetaConcept("Rollout")]
public sealed class RolloutAttribute : Attribute
{
[MetaProperty("Strategy", "RolloutStrategy", Required = true)]
public RolloutStrategy Strategy { get; set; }
[MetaProperty("Percent", "int")]
public int Percent { get; set; } = 100;
public RolloutAttribute(RolloutStrategy strategy) => Strategy = strategy;
}
public enum RolloutStrategy { AllAtOnce, Canary, BlueGreen, Dark }/// <summary>
/// How a feature flag or a deployment moves from off to on.
/// Strategy controls which runtime selector the typed flag
/// interface uses (canary % bucketing, blue/green pair, dark launch).
/// </summary>
[MetaConcept("Rollout")]
public sealed class RolloutAttribute : Attribute
{
[MetaProperty("Strategy", "RolloutStrategy", Required = true)]
public RolloutStrategy Strategy { get; set; }
[MetaProperty("Percent", "int")]
public int Percent { get; set; } = 100;
public RolloutAttribute(RolloutStrategy strategy) => Strategy = strategy;
}
public enum RolloutStrategy { AllAtOnce, Canary, BlueGreen, Dark }HealthProbe
/// <summary>
/// A liveness, readiness or startup probe exposed by a Service.
/// The Aspire AppHost generator wires the probe path into
/// builder.WithHttpHealthCheck(...).
/// </summary>
[MetaConcept("HealthProbe")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class HealthProbeAttribute : Attribute
{
[MetaProperty("Kind", "ProbeKind", Required = true)]
public ProbeKind Kind { get; set; }
[MetaProperty("Path", "string", Required = true)]
public string Path { get; set; }
public HealthProbeAttribute(ProbeKind kind, string path)
{
Kind = kind;
Path = path;
}
}
public enum ProbeKind { Liveness, Readiness, Startup }/// <summary>
/// A liveness, readiness or startup probe exposed by a Service.
/// The Aspire AppHost generator wires the probe path into
/// builder.WithHttpHealthCheck(...).
/// </summary>
[MetaConcept("HealthProbe")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class HealthProbeAttribute : Attribute
{
[MetaProperty("Kind", "ProbeKind", Required = true)]
public ProbeKind Kind { get; set; }
[MetaProperty("Path", "string", Required = true)]
public string Path { get; set; }
public HealthProbeAttribute(ProbeKind kind, string path)
{
Kind = kind;
Path = path;
}
}
public enum ProbeKind { Liveness, Readiness, Startup }ObservabilitySignal
/// <summary>
/// Declares a metric, structured log or distributed trace span
/// that a Service is contractually obliged to emit. The generator
/// produces a Meter or ActivitySource so that consumers (dashboards,
/// alerts, the SLO evaluator) can bind to a stable name.
/// </summary>
[MetaConcept("ObservabilitySignal")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ObservabilitySignalAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Kind", "SignalKind", Required = true)]
public SignalKind Kind { get; set; }
[MetaProperty("Unit", "string")]
public string? Unit { get; set; }
public ObservabilitySignalAttribute(string name, SignalKind kind)
{
Name = name;
Kind = kind;
}
}
public enum SignalKind { Counter, Histogram, Gauge, Trace, StructuredLog }/// <summary>
/// Declares a metric, structured log or distributed trace span
/// that a Service is contractually obliged to emit. The generator
/// produces a Meter or ActivitySource so that consumers (dashboards,
/// alerts, the SLO evaluator) can bind to a stable name.
/// </summary>
[MetaConcept("ObservabilitySignal")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ObservabilitySignalAttribute : Attribute
{
[MetaProperty("Name", "string", Required = true)]
public string Name { get; }
[MetaProperty("Kind", "SignalKind", Required = true)]
public SignalKind Kind { get; set; }
[MetaProperty("Unit", "string")]
public string? Unit { get; set; }
public ObservabilitySignalAttribute(string name, SignalKind kind)
{
Name = name;
Kind = kind;
}
}
public enum SignalKind { Counter, Histogram, Gauge, Trace, StructuredLog }What the developer writes
namespace AcmeStore.Alm;
[Service("Api", build: Sdlc.BUILDTARGET_API,
Description = "Public REST + GraphQL surface")]
[Deployment(DeploymentKind.AspireProject, Replicas = 3)]
[HealthProbe(ProbeKind.Liveness, "/health/live")]
[HealthProbe(ProbeKind.Readiness, "/health/ready")]
[Slo("p99-latency", m => m.LatencyP99Ms <= 250, Window = "00:05:00")]
[Slo("availability", m => m.SuccessRate >= 0.999, Window = "01:00:00")]
[ObservabilitySignal("acme.api.requests", SignalKind.Counter)]
[ObservabilitySignal("acme.api.latency", SignalKind.Histogram, Unit = "ms")]
public partial class ApiService { }
[Service("Worker", build: Sdlc.BUILDTARGET_WORKER)]
[Deployment(DeploymentKind.AspireProject, Replicas = 2)]
[HealthProbe(ProbeKind.Liveness, "/health/live")]
[Slo("queue-drain", m => m.QueueDepth < 1000)]
public partial class WorkerService { }
[Service("Web", build: Sdlc.BUILDTARGET_WEB)]
[Deployment(DeploymentKind.AspireProject, Replicas = 2)]
public partial class WebService { }
// ── Feature flags ──
[FeatureFlag("NewCheckout", Default = false, Owner = Plm.PRODUCT_STORE,
ExpiresOn = "2026-09-30")]
[Rollout(RolloutStrategy.Canary, Percent = 10)]
public partial class NewCheckoutFlag { }
[FeatureFlag("DarkMode", Default = true, Owner = Plm.PRODUCT_STORE)]
[Rollout(RolloutStrategy.AllAtOnce)]
public partial class DarkModeFlag { }
[FeatureFlag("ExperimentalSearch", Default = false, Owner = Plm.PRODUCT_STORE)]
[Rollout(RolloutStrategy.Dark)]
public partial class ExperimentalSearchFlag { }namespace AcmeStore.Alm;
[Service("Api", build: Sdlc.BUILDTARGET_API,
Description = "Public REST + GraphQL surface")]
[Deployment(DeploymentKind.AspireProject, Replicas = 3)]
[HealthProbe(ProbeKind.Liveness, "/health/live")]
[HealthProbe(ProbeKind.Readiness, "/health/ready")]
[Slo("p99-latency", m => m.LatencyP99Ms <= 250, Window = "00:05:00")]
[Slo("availability", m => m.SuccessRate >= 0.999, Window = "01:00:00")]
[ObservabilitySignal("acme.api.requests", SignalKind.Counter)]
[ObservabilitySignal("acme.api.latency", SignalKind.Histogram, Unit = "ms")]
public partial class ApiService { }
[Service("Worker", build: Sdlc.BUILDTARGET_WORKER)]
[Deployment(DeploymentKind.AspireProject, Replicas = 2)]
[HealthProbe(ProbeKind.Liveness, "/health/live")]
[Slo("queue-drain", m => m.QueueDepth < 1000)]
public partial class WorkerService { }
[Service("Web", build: Sdlc.BUILDTARGET_WEB)]
[Deployment(DeploymentKind.AspireProject, Replicas = 2)]
public partial class WebService { }
// ── Feature flags ──
[FeatureFlag("NewCheckout", Default = false, Owner = Plm.PRODUCT_STORE,
ExpiresOn = "2026-09-30")]
[Rollout(RolloutStrategy.Canary, Percent = 10)]
public partial class NewCheckoutFlag { }
[FeatureFlag("DarkMode", Default = true, Owner = Plm.PRODUCT_STORE)]
[Rollout(RolloutStrategy.AllAtOnce)]
public partial class DarkModeFlag { }
[FeatureFlag("ExperimentalSearch", Default = false, Owner = Plm.PRODUCT_STORE)]
[Rollout(RolloutStrategy.Dark)]
public partial class ExperimentalSearchFlag { }Notice three things:
Sdlc.BUILDTARGET_APIis aconst stringfrom the SDLC DSL's generated registry. Renaming the build target inAcmeStore.Sdlcwill break this file at compile time.Plm.PRODUCT_STOREis aconst stringfrom the PLM DSL. Flag ownership is checked transitively.AcmeStore.AlmreferencesAcmeStore.SdlcandAcmeStore.Plm(which both depend on the domain). It does not referenceAcmeStore.Lib,AcmeStore.Api, or any other runtime code. The runtime never depends onAcmeStore.Almeither -- see analyzerALM100.
1. AlmRegistry
// Generated: AlmRegistry.g.cs in AcmeStore.Alm
namespace AcmeStore.Alm;
public static class Alm
{
public const string SERVICE_API = "AcmeStore.Alm.ApiService";
public const string SERVICE_WORKER = "AcmeStore.Alm.WorkerService";
public const string SERVICE_WEB = "AcmeStore.Alm.WebService";
public const string FLAG_NEW_CHECKOUT = "NewCheckout";
public const string FLAG_DARK_MODE = "DarkMode";
public const string FLAG_EXPERIMENTAL_SEARCH = "ExperimentalSearch";
public const string SLO_API_P99_LATENCY = "ApiService::p99-latency";
public const string SLO_API_AVAIL = "ApiService::availability";
public const string SLO_WORKER_QUEUE = "WorkerService::queue-drain";
}// Generated: AlmRegistry.g.cs in AcmeStore.Alm
namespace AcmeStore.Alm;
public static class Alm
{
public const string SERVICE_API = "AcmeStore.Alm.ApiService";
public const string SERVICE_WORKER = "AcmeStore.Alm.WorkerService";
public const string SERVICE_WEB = "AcmeStore.Alm.WebService";
public const string FLAG_NEW_CHECKOUT = "NewCheckout";
public const string FLAG_DARK_MODE = "DarkMode";
public const string FLAG_EXPERIMENTAL_SEARCH = "ExperimentalSearch";
public const string SLO_API_P99_LATENCY = "ApiService::p99-latency";
public const string SLO_API_AVAIL = "ApiService::availability";
public const string SLO_WORKER_QUEUE = "WorkerService::queue-drain";
}2. Aspire AppHost wiring (emitted into the AppHost project, not Alm)
// Generated: AspireAppHost.g.cs in AcmeStore.AppHost
// Source: AcmeStore.Alm/*.cs
// DO NOT EDIT
using Aspire.Hosting;
public static partial class AcmeStoreAppHost
{
public static IDistributedApplicationBuilder WireServices(
this IDistributedApplicationBuilder builder)
{
var api = builder.AddProject<Projects.AcmeStore_Api>("api")
.WithReplicas(3)
.WithHttpHealthCheck("/health/live")
.WithHttpHealthCheck("/health/ready");
var worker = builder.AddProject<Projects.AcmeStore_Worker>("worker")
.WithReplicas(2)
.WithHttpHealthCheck("/health/live");
var web = builder.AddProject<Projects.AcmeStore_Web>("web")
.WithReplicas(2)
.WithReference(api);
return builder;
}
}// Generated: AspireAppHost.g.cs in AcmeStore.AppHost
// Source: AcmeStore.Alm/*.cs
// DO NOT EDIT
using Aspire.Hosting;
public static partial class AcmeStoreAppHost
{
public static IDistributedApplicationBuilder WireServices(
this IDistributedApplicationBuilder builder)
{
var api = builder.AddProject<Projects.AcmeStore_Api>("api")
.WithReplicas(3)
.WithHttpHealthCheck("/health/live")
.WithHttpHealthCheck("/health/ready");
var worker = builder.AddProject<Projects.AcmeStore_Worker>("worker")
.WithReplicas(2)
.WithHttpHealthCheck("/health/live");
var web = builder.AddProject<Projects.AcmeStore_Web>("web")
.WithReplicas(2)
.WithReference(api);
return builder;
}
}The developer's hand-written Program.cs for the AppHost stays minimal:
var builder = DistributedApplication.CreateBuilder(args);
builder.WireServices(); // partial method emitted by ALM generator
builder.Build().Run();var builder = DistributedApplication.CreateBuilder(args);
builder.WireServices(); // partial method emitted by ALM generator
builder.Build().Run();The AppHost project does not reference AcmeStore.Alm. The generator's [Generator] is registered in AcmeStore.Alm.Generators and targets AcmeStore.AppHost via an <AdditionalFiles> directive in the AppHost csproj. This is the linchpin of the dependency-inversion guarantee.
3. Strongly-typed feature flag interface (emitted into FeatureToggles, not Alm)
// Generated: IFeatureFlagsTyped.g.cs in AcmeStore.FeatureToggles
// Source: AcmeStore.Alm/*.cs
// DO NOT EDIT
namespace AcmeStore.FeatureToggles;
public interface IFeatureFlagsTyped : IFeatureFlags
{
bool NewCheckout { get; }
bool DarkMode { get; }
bool ExperimentalSearch { get; }
}
public sealed class FeatureFlagsTyped : IFeatureFlagsTyped
{
private readonly IFeatureFlags _inner;
public FeatureFlagsTyped(IFeatureFlags inner) => _inner = inner;
public bool IsEnabled(string flagName) => _inner.IsEnabled(flagName);
public bool NewCheckout => _inner.IsEnabled("NewCheckout");
public bool DarkMode => _inner.IsEnabled("DarkMode");
public bool ExperimentalSearch => _inner.IsEnabled("ExperimentalSearch");
}// Generated: IFeatureFlagsTyped.g.cs in AcmeStore.FeatureToggles
// Source: AcmeStore.Alm/*.cs
// DO NOT EDIT
namespace AcmeStore.FeatureToggles;
public interface IFeatureFlagsTyped : IFeatureFlags
{
bool NewCheckout { get; }
bool DarkMode { get; }
bool ExperimentalSearch { get; }
}
public sealed class FeatureFlagsTyped : IFeatureFlagsTyped
{
private readonly IFeatureFlags _inner;
public FeatureFlagsTyped(IFeatureFlags inner) => _inner = inner;
public bool IsEnabled(string flagName) => _inner.IsEnabled(flagName);
public bool NewCheckout => _inner.IsEnabled("NewCheckout");
public bool DarkMode => _inner.IsEnabled("DarkMode");
public bool ExperimentalSearch => _inner.IsEnabled("ExperimentalSearch");
}IFeatureFlags is the base contract declared in AcmeStore.Abstractions (zero dependencies). IFeatureFlagsTyped extends it with one property per declared flag. Business code injects IFeatureFlagsTyped and writes:
public sealed class CheckoutCommand
{
private readonly IFeatureFlagsTyped _flags;
public CheckoutCommand(IFeatureFlagsTyped flags) => _flags = flags;
public Task HandleAsync()
=> _flags.NewCheckout
? RunNewCheckoutAsync()
: RunLegacyCheckoutAsync();
}public sealed class CheckoutCommand
{
private readonly IFeatureFlagsTyped _flags;
public CheckoutCommand(IFeatureFlagsTyped flags) => _flags = flags;
public Task HandleAsync()
=> _flags.NewCheckout
? RunNewCheckoutAsync()
: RunLegacyCheckoutAsync();
}No magic string. No reference to AcmeStore.Alm. Renaming the [FeatureFlag] declaration breaks every consuming property at compile time.
4. OpenTelemetry instrumentation
// Generated: ApiServiceTelemetry.g.cs in AcmeStore.FeatureToggles (or a Telemetry project)
public static class ApiServiceTelemetry
{
public static readonly Meter Meter = new("acme.api", version: "1.0.0");
public static readonly Counter<long> Requests =
Meter.CreateCounter<long>("acme.api.requests");
public static readonly Histogram<double> Latency =
Meter.CreateHistogram<double>("acme.api.latency", unit: "ms");
}// Generated: ApiServiceTelemetry.g.cs in AcmeStore.FeatureToggles (or a Telemetry project)
public static class ApiServiceTelemetry
{
public static readonly Meter Meter = new("acme.api", version: "1.0.0");
public static readonly Counter<long> Requests =
Meter.CreateCounter<long>("acme.api.requests");
public static readonly Histogram<double> Latency =
Meter.CreateHistogram<double>("acme.api.latency", unit: "ms");
}5. Grafana SLO dashboard
// Generated: slo-dashboard.json
{
"title": "AcmeStore SLOs",
"panels": [
{
"title": "Api p99 latency (target <= 250ms over 5m)",
"targets": [{
"expr": "histogram_quantile(0.99, rate(acme_api_latency_bucket[5m]))"
}],
"thresholds": [{ "value": 250, "op": "gt", "color": "red" }]
},
{
"title": "Api availability (target >= 99.9% over 1h)",
"targets": [{
"expr": "1 - (sum(rate(acme_api_requests{status=~'5..'}[1h])) / sum(rate(acme_api_requests[1h])))"
}]
}
]
}// Generated: slo-dashboard.json
{
"title": "AcmeStore SLOs",
"panels": [
{
"title": "Api p99 latency (target <= 250ms over 5m)",
"targets": [{
"expr": "histogram_quantile(0.99, rate(acme_api_latency_bucket[5m]))"
}],
"thresholds": [{ "value": 250, "op": "gt", "color": "red" }]
},
{
"title": "Api availability (target >= 99.9% over 1h)",
"targets": [{
"expr": "1 - (sum(rate(acme_api_requests{status=~'5..'}[1h])) / sum(rate(acme_api_requests[1h])))"
}]
}
]
}6. Generation pipeline
Cross-DSL references
The ALM DSL sits between SDLC (below) and PLM (above). Its references are strict:
[Service(Build = Sdlc.BUILDTARGET_*)]-- every service must point at an SDLC build target[FeatureFlag(Owner = Plm.PRODUCT_*)]-- every flag must be owned by a PLM product[Implements(Requirements.FEATURE_*)](optional) -- a service may declare which Requirements DSL features it satisfies
Generation direction is inverted from references: ALM source files live in AcmeStore.Alm, but the bulk of generated runtime code is emitted into other projects. This is what keeps AcmeStore.Alm out of the runtime dependency graph.
Analyzers, including the layer guard
| ID | Severity | Message |
|---|---|---|
ALM001 |
Warning | FeatureFlag {F} is declared but never read in any consuming project |
ALM002 |
Warning | Service {S} has no [Slo] |
ALM003 |
Error | [Service.Build] references unknown BuildTarget {B} |
ALM004 |
Warning | FeatureFlag {F} ExpiresOn {date} is in the past |
ALM005 |
Error | HealthProbe path {P} is not exposed by service {S} |
ALM100 |
Error | Project {X} (layer Domain or Infra) must not reference AcmeStore.Alm. Depend on IFeatureFlags from AcmeStore.Abstractions instead |
ALM100 is the most important rule of the chapter. It is implemented as an IIncrementalGenerator in Cmf.Alm.Generators that inspects every consuming project's Compilation.ReferencedAssemblyNames plus the assembly-level [Layer] attribute. If a project marked [assembly: Layer(Layer.Domain)] or [assembly: Layer(Layer.InfraRuntime)] references AcmeStore.Alm, the build fails with ALM100.
This is what makes the rule of "no leaking ALM into business code" mechanical instead of cultural. A new contributor cannot accidentally using AcmeStore.Alm; from AcmeStore.Lib and slip it past code review.
Why this beats ad-hoc tooling
In a typical project, the same information is spread across:
- An Aspire
AppHost/Program.cs(services, replicas, health checks) - A
appsettings.jsonflag block read by aLaunchDarkly/ConfigCatSDK - A Grafana dashboard cloned from a template
- A Word document defining "the SLOs we should hit"
- A Slack channel where someone occasionally remembers to remove a stale flag
Each artifact has a different syntax, lives in a different system and ages independently. Drift is guaranteed.
The ALM DSL collapses all five into one source of truth. The compiler enforces consistency. The generator emits the artifacts that downstream systems need. Removing a feature flag becomes a one-line PR that the build itself certifies as safe.
The next chapter (Part XVII -- PLM DSL) goes one layer up: Products, Releases, Roadmap items and EOL policies, all referencing the SDLC pipelines and ALM services declared so far.