Coding Conventions
This page documents the coding standards and patterns used throughout the Tech Strategy Tool codebase. Follow these conventions when contributing changes.
General Rules
Section titled “General Rules”- Warnings-as-errors is enforced globally via
Directory.Build.props— the build fails on any warning - File-scoped namespaces — every
.csfile usesnamespace X.Y.Z;(no braces) sealedon classes that are not designed for inheritance- No
#regionblocks — organize with separate files or partial classes when code grows large - Central package management via
Directory.Packages.props— never specify versions in.csprojfiles
Naming Conventions
Section titled “Naming Conventions”| Pattern | Location | Purpose |
|---|---|---|
*Entity.cs | Infrastructure/Persistence/ | EF Core entity models |
*Endpoint.cs / *Endpoints.cs | Api/Endpoints/ | Minimal API route modules |
*Dto.cs | Shared/ | Read model DTOs |
*Response.cs | Shared/ | API response types |
*Request.cs | Shared/ | API request types |
*Store.cs / *Repository.cs | Infrastructure/Persistence/ | Persistence implementations |
Classes and members
Section titled “Classes and members”- PascalCase for types, methods, properties, and constants
- camelCase for local variables and parameters
_camelCasefor private fields (if any)- Descriptive names that reflect purpose, not implementation
Project Layering
Section titled “Project Layering”Dependency rules
Section titled “Dependency rules”Core → (nothing - zero dependencies)Shared → (nothing)Infrastructure → CoreUI → SharedWeb → UI, SharedApi → Infrastructure, Shared, WebThese 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
Model Classes
Section titled “Model Classes”Domain model classes in Core/Model/ are plain C# classes:
- Mutable
{ get; set; }properties List<T>collections (ordered, supports insert-at-index)- No
sealedmodifier (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. ExposesId,Name, andNameSequence(the sequence number of the lastupdate_nameevent).IDescribedEntity— implemented by Group and Principle. ExposesDescriptionandDescriptionSequence(the sequence number of the lastupdate_descriptionevent).
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).
Interface Records
Section titled “Interface Records”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}Dtofor read models,{Entity}Responsefor API responses,{Entity}Requestfor inputs - Collections use
IReadOnlyList<T> - Positional parameters or
{ get; init; }syntax as appropriate
API Patterns
Section titled “API Patterns”Minimal API endpoints
Section titled “Minimal API endpoints”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(...) { // ... }}Route patterns
Section titled “Route patterns”/api/{resource}— top-level resources/api/{parent}/{parentId}/{child}— nested resources
JSON serialization
Section titled “JSON serialization”System.Text.Json with camelCase property naming (ASP.NET Core default).
Event Processing Patterns
Section titled “Event Processing Patterns”Handler methods
Section titled “Handler methods”Each event handler:
- Extracts data from the
EventEnvelope.Datadictionary usingTryGetValue - Validates entity existence and field values
- Returns a
DispatchResult(never throws exceptions)
Defensive parsing
Section titled “Defensive parsing”Always use TryGetValue/TryParse for data dictionary access. The processor must never throw — malformed data results in rejection, not exception.
Package Management
Section titled “Package Management”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" />Build Properties
Section titled “Build Properties”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.
Primary Constructors
Section titled “Primary Constructors”Use primary constructors where they improve readability, particularly for dependency injection:
public sealed class EventStore(TechStratDbContext context) : IEventStore{ // context is available as a field}Further Reading
Section titled “Further Reading”- Testing — Test patterns and conventions
- Migrations — Database migration workflow