Add initial documentation
This commit is contained in:
169
docs/18_CODING_STANDARDS.md
Normal file
169
docs/18_CODING_STANDARDS.md
Normal file
@ -0,0 +1,169 @@
|
||||
# 18 · Coding Standards & Contributor Guide — **Stella Ops**
|
||||
*(v2.0 — 12 Jul 2025 · supersedes v1.0)*
|
||||
|
||||
> **Audience** — Anyone sending a pull‑request to the open‑source Core.
|
||||
> **Goal** — Keep the code‑base small‑filed, plug‑in‑friendly, DI‑consistent, and instantly readable.
|
||||
|
||||
---
|
||||
|
||||
## 0 Why read this?
|
||||
|
||||
* Cuts review time → quicker merges.
|
||||
* Guarantees code is **hot‑load‑safe** for run‑time plug‑ins.
|
||||
* Prevents style churn and merge conflicts.
|
||||
|
||||
---
|
||||
|
||||
## 1 High‑level principles
|
||||
|
||||
1. **SOLID first** – especially Interface & Dependency Inversion.
|
||||
2. **100‑line rule** – any file > 100 physical lines must be split or refactored.
|
||||
3. **Contract‑level ownership** – public abstractions live in lightweight *Contracts* libraries; impl classes live in runtime projects.
|
||||
4. **Single Composition Root** – all DI wiring happens in **`StellaOps.Web/Program.cs`** and in each plug‑in’s `IoCConfigurator`; nothing else calls `IServiceCollection.BuildServiceProvider`.
|
||||
5. **No Service Locator** – constructor injection only; static `ServiceProvider` is banned.
|
||||
6. **Fail‑fast startup** – configuration validated before the web‑host listens.
|
||||
7. **Hot‑load compatible** – no static singletons that survive plug‑in unload; avoid `Assembly.LoadFrom` outside the built‑in plug‑in loader.
|
||||
|
||||
---
|
||||
|
||||
## 2 Repository layout (flat, July‑2025)**
|
||||
|
||||
```text
|
||||
src/
|
||||
├─ backend/
|
||||
│ ├─ StellaOps.Web/ # ASP.NET host + composition root
|
||||
│ ├─ StellaOps.Common/ # Serilog, Result<T>, helpers
|
||||
│ ├─ StellaOps.Contracts/ # DTO + interface contracts (no impl)
|
||||
│ ├─ StellaOps.Configuration/ # Options + validation
|
||||
│ ├─ StellaOps.Localization/
|
||||
│ ├─ StellaOps.PluginLoader/ # Cosign verify, hot‑load
|
||||
│ ├─ StellaOps.Scanners.Trivy/ # First‑party scanner
|
||||
│ ├─ StellaOps.TlsProviders.OpenSsl/
|
||||
│ └─ … (additional runtime projects)
|
||||
├─ plugins-sdk/ # Templated contracts & abstractions
|
||||
└─ frontend/ # Angular workspace
|
||||
tests/ # Mirrors src structure 1‑to‑1
|
||||
```
|
||||
|
||||
There are no folders named “Module” and no nested solutions.
|
||||
|
||||
## 3 Naming & style conventions
|
||||
|
||||
| Element | Rule | Example |
|
||||
| ------------------------------------------------------------------------------- | --------------------------------------- | ------------------------------- |
|
||||
| Namespaces | File‑scoped, StellaOps.<Area> | namespace StellaOps.Scanners; |
|
||||
| Interfaces | I prefix, PascalCase | IScannerRunner |
|
||||
| Classes / records | PascalCase | ScanRequest, TrivyRunner |
|
||||
| Private fields | camelCase (no leading underscore) | redisCache, httpClient |
|
||||
| Constants | SCREAMING_SNAKE_CASE | const int MAX_RETRIES = 3; |
|
||||
| Async methods | End with Async | Task<ScanResult> ScanAsync() |
|
||||
| File length | ≤ 100 lines incl. using & braces | enforced by dotnet format check |
|
||||
| Using directives | Outside namespace, sorted, no wildcards | — |
|
||||
|
||||
Static analyzers (.editorconfig, StyleCop.Analyzers package) enforce the above.
|
||||
|
||||
## 4 Dependency‑injection policy
|
||||
|
||||
Composition root – exactly one per process:
|
||||
|
||||
```csharp
|
||||
builder.Services
|
||||
.AddStellaCore() // extension methods from each runtime project
|
||||
.AddPluginLoader("/Plugins", cfg); // hot‑load signed DLLs
|
||||
```
|
||||
|
||||
Plug‑ins register additional services via the IoCConfigurator convention described in the Plug‑in SDK Guide, §5.
|
||||
Never resolve services manually (provider.GetService<T>()) outside the composition root; tests may use WebApplicationFactory or ServiceProvider.New() helpers.
|
||||
Scoped lifetime is default; singletons only for stateless, thread‑safe helpers.
|
||||
|
||||
## 5 Project organisation rules
|
||||
|
||||
Contracts vs. Runtime – public DTO & interfaces live in <Area>.Contracts; implementation lives in sibling project.
|
||||
Feature folders – inside each runtime project group classes by use‑case, e.g.
|
||||
|
||||
```text
|
||||
├─ Scan/
|
||||
│ ├─ ScanService.cs
|
||||
│ └─ ScanController.cs
|
||||
├─ Feed/
|
||||
└─ Tls/
|
||||
```
|
||||
|
||||
Tests – mirror the structure under tests/ one‑to‑one; no test code inside production projects.
|
||||
|
||||
## 6 C# language features
|
||||
|
||||
Nullable reference types enabled.
|
||||
record for immutable DTOs.
|
||||
Pattern matching encouraged; avoid long switch‑cascades.
|
||||
Span<T> & Memory<T> OK when perf‑critical, but measure first.
|
||||
Use await foreach over manual paginator loops.
|
||||
|
||||
## 7 Error‑handling template
|
||||
|
||||
```csharp
|
||||
public async Task<IActionResult> PostScan([FromBody] ScanRequest req)
|
||||
{
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
|
||||
try
|
||||
{
|
||||
ScanResult result = await scanService.ScanAsync(req);
|
||||
if (result.Quota != null)
|
||||
{
|
||||
Response.Headers.TryAdd("X-Stella-Quota-Remaining", result.Quota.Remaining.ToString());
|
||||
Response.Headers.TryAdd("X-Stella-Reset", result.Quota.ResetUtc.ToString("o"));
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
RFC 7807 ProblemDetails for all non‑200s.
|
||||
Capture structured logs with Serilog’s message‑template syntax.
|
||||
|
||||
## 8 Async & threading
|
||||
|
||||
* All I/O is async; no .Result / .Wait().
|
||||
* Library code: ConfigureAwait(false).
|
||||
* Limit concurrency via Channel<T> or Parallel.ForEachAsync, never raw Task.Run loops.
|
||||
|
||||
## 9 Testing rules
|
||||
|
||||
| Layer | Framework | Coverage gate |
|
||||
| ------------------------ | ------------------------ | -------------------------- |
|
||||
| Unit | xUnit + FluentAssertions | ≥ 80 % line, ≥ 60 % branch |
|
||||
| Integration | Testcontainers | Real Redis & Trivy |
|
||||
| Mutation (critical libs) | Stryker.NET | ≥ 60 % score |
|
||||
|
||||
One test project per runtime/contract project; naming <Project>.Tests.
|
||||
|
||||
## 10 Static analysis & formatting
|
||||
|
||||
* dotnet format must exit clean (CI gate).
|
||||
* StyleCop.Analyzers + Roslyn‑Security‑Guard run on every PR.
|
||||
* CodeQL workflow runs nightly on main.
|
||||
|
||||
## 11 Commit & PR checklist
|
||||
|
||||
* Conventional Commit prefix (feat:, fix:, etc.).
|
||||
* dotnet format & dotnet test both green.
|
||||
* Added or updated XML‑doc comments for public APIs.
|
||||
* File count & length comply with 100‑line rule.
|
||||
* If new public contract → update relevant markdown doc & JSON‑Schema.
|
||||
|
||||
## 12 Common pitfalls
|
||||
|
||||
|Symptom| Root cause | Fix
|
||||
|-------|-------------|-------------------
|
||||
|InvalidOperationException: Cannot consume scoped service...| Mis‑matched DI lifetimes| Use scoped everywhere unless truly stateless
|
||||
|Hot‑reload plug‑in crash| Static singleton caching plugin types| Store nothing static; rely on DI scopes
|
||||
|
||||
> 100‑line style violation |Large handlers or utils |Split into private helpers or new class
|
||||
|
||||
## 13 Change log
|
||||
|
||||
| Version | Date | Notes |
|
||||
| ------- | ---------- | -------------------------------------------------------------------------------------------------- |
|
||||
| v2.0 | 2025‑07‑12 | Updated DI policy, 100‑line rule, new repo layout, camelCase fields, removed “Module” terminology. |
|
||||
| 1.0 | 2025‑07‑09 | Original standards. |
|
Reference in New Issue
Block a user