docs: formalize C# formatting, brace discipline, FluentResults vs ternary, grouping, and clean namespace rules in GEMINI.md
This commit is contained in:
@@ -983,15 +983,20 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec
|
||||
|
||||
---
|
||||
|
||||
### 16.3 C# 13 & .NET 10 Coding Style: Functional Results & Syntax Conventions
|
||||
### 16.3 C# 13 & .NET 10 Coding Style: Functional Results, Formatting & Syntax Rules
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Mandatory Assistant Rule (Never Regress or Undo This Approach)**:
|
||||
> Antigravity / Gemini must strictly adhere to the established functional, low-branching C# style. When adding, modifying, or refactoring backend code:
|
||||
> **Authoritative Coding Style Rules (Never Regress or Undo This Approach)**:
|
||||
> Antigravity / Gemini must strictly adhere to the established C# functional idioms, layout, and syntax conventions across all projects. When authoring, refactoring, or extending backend code:
|
||||
> - **DO NOT introduce imperative `try-catch` blocks** for control flow or routine domain logic.
|
||||
> - **DO NOT introduce nested `if-else` return branches**.
|
||||
> - **DO NOT throw exceptions** for predictable domain validation or mutation failures.
|
||||
> - **Always use FluentResults** (`Result.Try`, `Result.OkIf`, `Result.FailIf`) and pattern-matching switch expressions on endpoints.
|
||||
> - **DO NOT use braces `{}` on single-line `if` statements** — braces are reserved exclusively for blocks with more than one line of code.
|
||||
> - **Prefer FluentResults over ternary operators** (`Result.FailIf(...).ToResult(...)` over `condition ? Result.Fail(...) : Result.Ok(...)`).
|
||||
> - **Prefer expression-bodied members (`=>`) and switch expressions** over statement bodies and traditional switch statements.
|
||||
> - **Never split method parameters one-per-line** in method or constructor definitions.
|
||||
> - **Group related variable declarations and function calls** without empty blank lines between them.
|
||||
> - **Never leave unused `using` namespaces** in `.cs` files.
|
||||
> - **Pragmatically avoid magic strings** using constants, enums, or domain errors without obsessive over-engineering.
|
||||
|
||||
#### 1. Extensive Avoidance of `try-catch` Blocks
|
||||
* Imperative `try-catch` blocks are strictly avoided across all application services, endpoints, and domain logic.
|
||||
@@ -1005,37 +1010,51 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec
|
||||
```
|
||||
* Unexpected runtime exceptions are automatically caught by `Result.Try` and converted into a `Result.Fail` with exception metadata, allowing `LoggingInterceptor` to record errors without leaking raw exceptions or crashing threads.
|
||||
|
||||
#### 2. Minimal Code Branching & Elimination of Nested `if-else`
|
||||
* Traditional branching constructs (`if-else-if-else`) on returns are eliminated in favor of clean guard clauses and FluentResults combinators:
|
||||
* **Concise Guard Clauses**: Early validation failure returns:
|
||||
#### 2. Minimal Branching, Guard Clauses & If-Statement Brace Discipline
|
||||
* **No Braces for Single-Line `if`**: Braces `{}` are strictly omitted when the `if` body consists of a single statement/line:
|
||||
```csharp
|
||||
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||
return Result.Fail(NotFoundError.New);
|
||||
```
|
||||
* **`Result.OkIf` & `Result.FailIf`**: Replace conditional return statements:
|
||||
```csharp
|
||||
// Mutation success check:
|
||||
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||
|
||||
// Existence check with mapper transformation:
|
||||
if (decodeResult.IsFailed)
|
||||
return Result.Fail<Contract>(IdDecodeError.Create().CausedBy(decodeResult.Errors));
|
||||
```
|
||||
* **Braces Only for Multi-Line Blocks**: Braces `{}` are used **only** when the `if` block encompasses more than one line of code:
|
||||
```csharp
|
||||
if (string.IsNullOrWhiteSpace(cached))
|
||||
{
|
||||
var result = await service.GetActivitiesAsync(projectIdHash, cancellationToken);
|
||||
if (result.IsFailed) return result;
|
||||
|
||||
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||
return result.Value;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. FluentResults Monadic Chaining over Ternary Operators
|
||||
* Whenever processing or transforming a response, **always choose FluentResults methods over ternary operators (`? :`)**:
|
||||
```csharp
|
||||
// Preferred (Fluent monadic style):
|
||||
return Result.FailIf(project is null, NotFoundError.Create("Project not found"))
|
||||
.ToResult(project?.Map()!);
|
||||
```
|
||||
* **Chained Functional Evaluations**: Guard against invalid decodes or missing foreign keys with immediate failure returns instead of deeply indented blocks.
|
||||
|
||||
#### 3. Primary Constructors & Expression-Bodied Members
|
||||
* Services and endpoints use primary constructors to eliminate constructor assignment boilerplate:
|
||||
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||
|
||||
// Avoid (Ternary branching):
|
||||
// return project is null ? Result.Fail<Project>(...) : Result.Ok(project.Map());
|
||||
```
|
||||
|
||||
#### 4. Expression-Bodied Members & Switch Expressions
|
||||
* Methods, mappers, getters, and endpoint registrations strongly prefer expression bodies (`=>`):
|
||||
```csharp
|
||||
public sealed class ProjectService(
|
||||
IDbContextFactory<ApplicationDbContext> contextFactory,
|
||||
HashService hashService,
|
||||
ITenantContextService tenantContext) : IProjectService
|
||||
public static Activity Map(this Entities.Activity entity) => new()
|
||||
{
|
||||
Id = entity.Id,
|
||||
Title = entity.Title,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
* Methods leverage expression bodies (`=>`) to delegate directly to `await Result.Try(...)` or endpoint mappings.
|
||||
* Nullable reference types (`<Nullable>enable</Nullable>`) are strictly enforced.
|
||||
|
||||
#### 4. Pattern-Matching Endpoint Handlers
|
||||
* Minimal API endpoints map `Result<T>` directly to HTTP status codes via C# switch expressions, completely eliminating endpoint `if-else` branching:
|
||||
* Minimal API endpoints map `Result<T>` directly to HTTP status codes via C# pattern-matching switch expressions:
|
||||
```csharp
|
||||
await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch
|
||||
{
|
||||
@@ -1048,16 +1067,67 @@ The PFM backend strikes a deliberate, pragmatic balance between **Clean Architec
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. High-Concurrency DbContext Management
|
||||
#### 5. Compact Method & Constructor Definitions
|
||||
* Method signatures are kept compact and readable on one line or cleanly wrapped; **parameters are never split into one line per variable**:
|
||||
```csharp
|
||||
// Preferred:
|
||||
public async Task<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||
|
||||
public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService,
|
||||
ITenantContextService tenantContext) : IProjectService
|
||||
|
||||
// Avoid:
|
||||
// public async Task<Result> UpdateProjectStatusAsync(
|
||||
// UpdateProjectStatusRequest request,
|
||||
// Guid userId,
|
||||
// CancellationToken cancellationToken = default)
|
||||
```
|
||||
|
||||
#### 6. Bracket Placement (Allman Style)
|
||||
* Opening braces `{` are placed on a new line for type declarations, multi-line blocks, lambda bodies in `Result.Try`, and object initializers:
|
||||
```csharp
|
||||
public sealed class ProjectService(...) : IProjectService
|
||||
{
|
||||
public async Task<Result> MethodAsync(...) =>
|
||||
await Result.Try((Func<Task<Result>>)(async () =>
|
||||
{
|
||||
// ...
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Dense Grouping of Related Declarations & Calls
|
||||
* Related variable declarations are grouped together without blank lines between them.
|
||||
* Related sequential invocations are kept adjacent without extraneous empty lines:
|
||||
```csharp
|
||||
var name = request.Name.Humanize(LetterCasing.Title).Trim();
|
||||
var description = request.Description.Humanize(LetterCasing.Sentence).Trim();
|
||||
|
||||
var cacheKey = HashService.ToMd5Hash($"project-{id}").Value;
|
||||
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||
```
|
||||
|
||||
#### 8. Clean Namespaces & Pragmatic String Handling
|
||||
* **Zero Unused Namespaces**: Every `.cs` file maintains clean, minimal `using` directives without stale or unused imports. File-scoped namespaces (`namespace PostFundManagement...;`) are standard.
|
||||
* **Pragmatic Magic String Avoidance**: Avoid magic strings for business-critical values (use constants in `Constants.cs`, `EndpointTags`, domain enums, or typed domain errors), but remain pragmatic (e.g. human-readable error messages and route patterns need not be over-abstracted into single-use constants).
|
||||
|
||||
#### 9. Functional Programming Orientation
|
||||
* While C# is multi-paradigm, the codebase leans intentionally into functional programming concepts:
|
||||
* Immutability via positional records (`public sealed record UpdateProjectStatusRequest(...)`).
|
||||
* Pure extension mapping functions (`entity.Map()`).
|
||||
* Declarative LINQ queries and pipeline data transformations.
|
||||
* Monadic error handling (`Result<T>`, `Result.Try`, `CausedBy`).
|
||||
|
||||
#### 10. High-Concurrency DbContext Management
|
||||
* Services inject `IDbContextFactory<ApplicationDbContext>` rather than scoped `ApplicationDbContext`.
|
||||
* Creates short-lived contexts using `await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);`.
|
||||
* Avoids thread concurrency issues in parallel mediator requests and background Quartz jobs.
|
||||
|
||||
#### 6. Query & Mutation Optimizations
|
||||
#### 11. Query & Mutation Optimizations
|
||||
* Queries use `.AsNoTracking()` to eliminate EF Core tracking overhead.
|
||||
* Targeted updates leverage `.ExecuteUpdateAsync(...)` for in-database bulk updates without loading full entities into memory.
|
||||
|
||||
#### 7. Tiered Hybrid Caching
|
||||
#### 12. Tiered Hybrid Caching
|
||||
* Implements `Microsoft.Extensions.Caching.Hybrid` alongside StackExchange Redis.
|
||||
* Local L1 memory cache: 30-minute expiration.
|
||||
* Distributed L2 Redis cache: 2-minute expiration (`InstanceName = "PFM-API_"`).
|
||||
|
||||
Reference in New Issue
Block a user