Skip to content

Coding Conventions

This page documents the coding standards and patterns used throughout the Tech Strategy Tool codebase. Follow these conventions when contributing changes.

  • Warnings-as-errors is enforced globally via Directory.Build.props — the build fails on any warning
  • File-scoped namespaces — every .cs file uses namespace X.Y.Z; (no braces)
  • sealed on classes that are not designed for inheritance
  • No #region blocks — organize with separate files or partial classes when code grows large
  • Central package management via Directory.Packages.props — never specify versions in .csproj files
PatternLocationPurpose
*Entity.csInfrastructure/Persistence/EF Core entity models
*Endpoint.cs / *Endpoints.csApi/Endpoints/Minimal API route modules
*Dto.csShared/Read model DTOs
*Response.csShared/API response types
*Request.csShared/API request types
*Store.cs / *Repository.csInfrastructure/Persistence/Persistence implementations
  • PascalCase for types, methods, properties, and constants
  • camelCase for local variables and parameters
  • _camelCase for private fields (if any)
  • Descriptive names that reflect purpose, not implementation
Core → (nothing - zero dependencies)
Shared → (nothing)
Infrastructure → Core
UI → Shared
Web → UI, Shared
Api → Infrastructure, Shared, Web

These are strict rules:

  • Core has zero NuGet dependencies and zero project references — pure domain logic only
  • Shared contains only DTOs — no business logic, no infrastructure
  • Infrastructure depends only on Core (plus EF Core NuGet packages)
  • UI depends only on Shared (plus Blazor component NuGet packages)
  • Api is the composition root that wires everything together

Domain model classes in Core/Model/ are plain C# classes:

  • Mutable { get; set; } properties
  • List<T> collections (ordered, supports insert-at-index)
  • No sealed modifier (these are mutable document nodes)
  • No behavior — all logic lives in EventProcessor

Two interfaces in Core/Model/ provide shared contracts for generic event handlers:

  • INamedEntity — implemented by Team, Group, Principle, Objective, Initiative. Exposes Id, Name, and NameSequence (the sequence number of the last update_name event).
  • IDescribedEntity — implemented by Group and Principle. Exposes Description and DescriptionSequence (the sequence number of the last update_description event).

These interfaces allow EventProcessor.GenericHandlers.cs to implement update_name and update_description once, with entity-type-specific field limits dispatched via FieldLimits.NameLimitFor(entityType) and FieldLimits.DescriptionLimitFor(entityType).

Records in Core/Interfaces/ (e.g., StoredEvent, StoredCheckpoint, UserRecord) are immutable:

  • { get; init; } properties
  • Value-equality semantics
  • Used for cross-layer data transfer

All DTOs live in TechStrat.Shared/:

  • Records — immutable, value-equality semantics
  • Naming: {Entity}Dto for read models, {Entity}Response for API responses, {Entity}Request for inputs
  • Collections use IReadOnlyList<T>
  • Positional parameters or { get; init; } syntax as appropriate

Endpoints are organized as static classes with MapXxx extension methods on IEndpointRouteBuilder:

public static class TeamEndpoints
{
public static IEndpointRouteBuilder MapTeamEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/api/teams", GetTeams).RequireAuthorization("ViewerOrAbove");
return app;
}
private static async Task<IResult> GetTeams(...)
{
// ...
}
}
  • /api/{resource} — top-level resources
  • /api/{parent}/{parentId}/{child} — nested resources

System.Text.Json with camelCase property naming (ASP.NET Core default).

Each event handler:

  1. Extracts data from the EventEnvelope.Data dictionary using TryGetValue
  2. Validates entity existence and field values
  3. Returns a DispatchResult (never throws exceptions)

Always use TryGetValue/TryParse for data dictionary access. The processor must never throw — malformed data results in rejection, not exception.

All NuGet package versions are centralized in Directory.Packages.props at the solution root:

<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />

Project files reference packages without versions:

<PackageReference Include="Microsoft.EntityFrameworkCore" />

Directory.Build.props at the solution root defines shared properties:

  • Target framework: net10.0
  • Nullable reference types: enabled
  • Implicit usings: enabled
  • Warnings-as-errors: enabled

These apply to all projects in the solution automatically.

Use primary constructors where they improve readability, particularly for dependency injection:

public sealed class EventStore(TechStratDbContext context) : IEventStore
{
// context is available as a field
}
  • Testing — Test patterns and conventions
  • Migrations — Database migration workflow