Skip to content

Golden Path

Standardized patterns and best practices for building applications on the Forge platform.


📋 Overview

The Golden Path represents the recommended way to build applications using Forge. Following these patterns ensures consistency, maintainability, and takes advantage of platform capabilities.


🎯 Golden Path Philosophy

The Golden Path represents:

  • Standardized approaches that work for most cases
  • Best practices vetted by the platform team
  • Reduced cognitive load through conventions
  • Flexibility when needed - you can deviate, but know why

🏗️ Core Patterns

API Layer (Experience APIs)

// ✅ Golden Path: Use minimal APIs with service pattern
app.MapGet("/users/{id}", async (int id, IUserService service) =>
    await service.GetAsync(id) is User user
        ? Results.Ok(user)
        : Results.NotFound());

// ✅ Golden Path: Keep controllers thin
[ApiController]
public class UsersController(IUserService service) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<User>> Get(int id) =>
        await service.GetAsync(id) is User user ? Ok(user) : NotFound();
}

// ❌ Anti-pattern: Business logic in controller
[HttpGet("{id}")]
public async Task<ActionResult<User>> Get(int id)
{
    // Don't do database calls, complex logic here
    var user = await _context.Users
        .Include(u => u.Roles)
        .Where(u => u.Id == id)
        .FirstOrDefaultAsync();
    // ... complex processing
}

Service Layer

// ✅ Golden Path: Interface-based services
public interface IUserService
{
    Task<User?> GetAsync(int id);
    Task<User> CreateAsync(CreateUserRequest request);
}

public class UserService(IUserRepository repository) : IUserService
{
    public async Task<User?> GetAsync(int id) =>
        await repository.FindByIdAsync(id);
}

// ✅ Register in DI
builder.Services.AddScoped<IUserService, UserService>();

Data Access

// ✅ Golden Path: Repository pattern for CosmosDB
public class UserRepository(CosmosClient client) : IUserRepository
{
    private Container Container => client.GetContainer("db", "users");

    public async Task<User?> FindByIdAsync(string id)
    {
        try
        {
            var response = await Container.ReadItemAsync<User>(id, new PartitionKey(id));
            return response.Resource;
        }
        catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            return null;
        }
    }
}

Authentication

// ✅ Golden Path: Use Forge auth extensions
builder.AddOpenIdConnectServices();

// In middleware
app.UseOpenIdConnectServices();

// ❌ Anti-pattern: Custom JWT validation
builder.Services.AddAuthentication()
    .AddJwtBearer(options => { /* Custom config */ });

Configuration

// ✅ Golden Path: Use appsettings.{env}.json for API app settings
// appsettings.json
{
  "OrderProcessing": {
    "MaxRetryAttempts": 3,
    "BatchSize": 50
  }
}

Settings like this are for application-owned values you define yourself. Connection info for provisioned resources (Cosmos DB, Service Bus, and similar) is injected separately via Aspire service discovery / connection strings, not hand-written in appsettings.json — see the resource's own integration docs for how to consume it.

Frontend/static web apps have no ASP.NET Core runtime to load appsettings.json, so they use infra/web/settings.yml instead. See Settings and Secrets for the full picture.

Feature flags are a separate case with their own configuration path, not part of this appsettings.json migration: deployed apps with Azure App Configuration configured source flags from there. appsettings.json still plays two roles: it's the local-development/no-App-Configuration fallback, and (for saif-feature-api golden-path projects, where deployment sync is enabled automatically) it's also the deployment-time source that the sync pipeline reads to populate Azure App Configuration. See Feature Flags for the full pattern.

// ✅ Access via configuration
var maxRetries = configuration.GetValue<int>("OrderProcessing:MaxRetryAttempts");

// ❌ Anti-pattern: Hardcoded values
var maxRetries = 3;

Service Discovery

// ✅ Golden Path: Use .NET Service Discovery for service-to-service calls
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
    http.AddServiceDiscovery();
});

// Reference services by name (resolved via Aspire)
builder.Services.AddHttpClient<IPaymentClient>(client =>
{
    client.BaseAddress = new Uri("https+http://payment-api");
});

// ❌ Anti-pattern: Hardcoded service URLs
builder.Services.AddHttpClient<IPaymentClient>(client =>
{
    client.BaseAddress = new Uri("https://payment-api.internal.com");
});

In Aspire AppHost, services are automatically discoverable:

var paymentApi = builder.AddProject<Projects.PaymentApi>("payment-api");

builder.AddProject<Projects.MyApi>("my-api")
    .WithReference(paymentApi);  // Enables service discovery

Infrastructure

# ✅ Golden Path: Use Forge modules
module "saif-appservices" {
  source  = "app.terraform.io/SAIFCorp/saif-apiservice/azure"
  version = ">= 3.0.0, < 4.0.0"

  # Standard variables
  owner       = local.variables.owner
  project_id  = local.variables.project_id
  environment = var.environment
}

# ❌ Anti-pattern: Raw Azure resources
resource "azurerm_app_service" "api" {
  # Custom configuration
}

🤔 Decision Framework

When to Follow the Golden Path

  • Starting new projects
  • Standard use cases (CRUD APIs, web apps)
  • Team is new to the technology
  • Long-term maintainability is priority

When to Deviate

  • Unique performance requirements
  • Legacy system integration
  • Specialized domain needs
  • After discussing with platform team

📊 Pattern Recommendations

By Use Case

Need Golden Path
REST API Minimal APIs or Controllers with Services
Database Cosmos DB with repository pattern
Auth Forge OIDC extensions
Secrets Azure Key Vault via Azure DevOps Library
Messaging Azure Service Bus with Aspire
Frontend React with Vite

By File Type

File Pattern
Program.cs Builder → Configure → Run
*.csproj Central package management
infra/*.tf Forge modules + generated files
appsettings.{env}.json Configuration over hardcoding (API apps)
settings.yml Configuration over hardcoding (frontend/static web apps)

✅ Code Review Checklist

When reviewing for Golden Path adherence:

  • Uses Forge packages (not raw Azure SDKs)
  • Configuration via appsettings.{env}.json (or settings.yml for frontend/static web apps)
  • No hardcoded secrets or URLs
  • Service/repository pattern for logic
  • Minimal business logic in controllers
  • Uses Aspire for orchestration
  • Infrastructure uses Forge modules
  • Tests follow Forge patterns

💬 Guidance Examples

Gentle Guidance

"I see you're accessing the database directly from the controller. The Golden Path pattern would be to create a repository service. This makes testing easier and keeps controllers focused on HTTP concerns."

Explaining Trade-offs

"The Golden Path uses Cosmos DB for most data storage. If you need relational data with complex joins, SQL might be better - but let's discuss with the platform team first to understand the trade-offs."