Appearance
Coding Conventions
Backend (C#)
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Classes, interfaces, methods, properties | PascalCase | FileRecord, IStorageService |
| Private fields | _camelCase | _dbContext, _logger |
| Parameters, local variables | camelCase | fileName, userId |
| Interfaces | I-prefix | IAuditService, IEmailRenderer |
| Constants | PascalCase | MaxFileSize, DefaultTimeout |
File-Scoped Namespaces
Always use file-scoped namespaces:
csharp
namespace Wilo.Api.Features.Auth; // Correct
// NOT:
namespace Wilo.Api.Features.Auth // Wrong
{
}NodaTime (Never DateTime)
| NodaTime Type | Use For | PostgreSQL Type |
|---|---|---|
Instant | UTC timestamps | timestamp with time zone |
LocalDate | Dates without time | date |
LocalTime | Times without date | time |
ID Generation
Use Guid7 (UUIDv7, time-ordered) for all entity IDs:
csharp
using Wilo.Api.Utils;
var id = Guid7.NewGuid(); // Returns Guid7 wrapper
var guidValue = id.Value; // Extract Guid for entity assignmentWhy Guid7 wrapper instead of Guid.CreateVersion7()?
.NET's built-in Guid.CreateVersion7() has incorrect byte ordering (little-endian) that breaks PostgreSQL chronological sorting. The Guid7 wrapper uses the Medo.Uuid7 library internally, which generates RFC 9562-compliant UUIDs with big-endian byte order.
Never use:
Guid.CreateVersion7()— broken byte orderGuid.NewGuid()— UUIDv4 (random), not sequential
Password Hashing
Use PasswordHasher from Utils/PasswordHasher.cs (PBKDF2-SHA512, 250k iterations). Never use BCrypt or Argon2id.
JSON Serialization
Use DefaultSerializer.Options for camelCase properties, null value handling, and NodaTime support.
NuGet Package Management
All versions go in Directory.Packages.props (Central Package Management). Never put Version="x.x.x" in .csproj files:
xml
<!-- Directory.Packages.props -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<!-- .csproj (NO version) -->
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />Testing
- Framework: xUnit v3
- Class naming:
{ClassUnderTest}Tests - Method naming:
{Method}_{Scenario}_{ExpectedResult} - Pattern: AAA (Arrange, Act, Assert)
- Integration tests: Testcontainers for real infrastructure
Frontend (Vue/TypeScript)
Components
- Always
<script setup lang="ts">(never Options API) - File naming: PascalCase (
UserProfile.vue) - SFC order:
<script setup>then<template>then<style scoped> - Path alias:
@/maps tosites/Wilo.App/
Pinia Stores
Setup function style (not options style):
typescript
export const useCounterStore = defineStore('counter', () => {
const count = ref(0) // state
const doubleCount = computed(() => count.value * 2) // getter
function increment() { count.value++ } // action
return { count, doubleCount, increment }
})TypeScript
- Strict mode required
- Prefer interfaces over types for object shapes
- Export types explicitly
UI Components
- Component library: shadcn-vue (accessible, composable)
- Styling: Tailwind CSS v4 (CSS-based config, no
tailwind.config.js) - Icons: lucide-vue-next
- Forms: VeeValidate + Zod
- Dates: date-fns
i18n
- vue-i18n with Composition API (
legacy: false) - Default locale: Spanish (
es), fallback: English (en) - Locale files at
sites/Wilo.App/locales/{en,es}.json - All user-facing text must use i18n keys, no hardcoded strings
Linting and Formatting
- Linter: oxlint (
.oxlintrc.json) - Formatter: oxfmt (
.oxfmtrc.json) - Config:
semi: false,singleQuote: true - Plugins: typescript, unicorn, import, vue, vitest
Git Conventions
Commit Format
type(scope): description- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
- Scopes: app, api, docs, infra, deps, release
- Subject must be lowercase (commitlint enforced)
- Max subject length: 100 characters
Branches
main— production-readyfeat/phase-N— phase development branchesfeat/{scope}-{description}— feature branches
Pre-commit Hooks (lefthook)
Hooks run automatically on commit:
- commit-msg: commitlint (conventional commits)
- pre-commit: oxlint + oxfmt on staged
.ts/.tsx/.vue/.js/.jsx/.css/.jsonfiles
Folder Structure
Backend
src/Wilo.Api/
├── Features/{Module}/{Operation}/ REPR endpoints (2 files each)
├── DependencyInjection/ Modular DI extension methods
├── HealthChecks/ Custom health check implementations
├── Infrastructure/ IEndpoint, ValidationFilter, etc.
├── Utils/ Shared utilities
└── Program.cs App composition root
src/Wilo.Common/
├── Abstractions/ Entity, AggregateRoot, ValueObject, IDomainEvent, Result<T>
├── Endpoints/ IEndpoint, ValidationFilter, EndpointExtensions
├── Modules/ IModuleClient, IModuleEventBus, IEventConsumer<T>
└── Utils/ Guid7, PasswordHasher, DefaultSerializer
src/Wilo.Contracts/
└── {Module}/
├── Events/ IIntegrationEvent records
├── Requests/ IModuleRequest<T> records
└── Responses/ Shared DTO records
src/Wilo.Infrastructure/
├── Email/ IEmailService, EmailService, ReactEmailRenderer
├── Storage/ IStorageService, MinioOptions
├── Caching/ ICacheService, FusionCacheService
├── Messaging/ ModuleClient, ModuleEventBus, DomainEventDispatcher
├── Audit/ IAuditService, ClickHouseAuditService
├── Outbox/ OutboxInterceptor, OutboxProcessor
├── HealthChecks/ ClickHouseHealthCheck, MinioHealthCheck
└── Observability/ SerilogExtensions, OpenTelemetryExtensions
src/Wilo.Modules.{Module}/
├── Entities/ Aggregate roots and domain entities
├── ValueObjects/ Value objects
├── Errors/ Typed Error records
├── Events/ Internal domain events (IDomainEvent)
├── DomainEventHandlers/ IDomainEventHandler<T> implementations
├── Features/{Operation}/ {Op}Endpoint + {Op}Handler + {Op}Validator
├── Consumers/ IEventConsumer<T> implementations
├── RequestHandlers/ IModuleRequestHandler<,> implementations
├── Persistence/ {Module}DbContext, EF configurations, migrations
└── {Module}ModuleRegistration.cs DI + endpoint registrationFrontend
sites/Wilo.App/
├── components/
│ ├── ui/ shadcn-vue components
│ └── AppLayout.vue Main layout
├── views/ Page components
├── stores/ Pinia stores
├── composables/ Vue composables
├── lib/ Utility functions
├── locales/ i18n translation files
├── router/ Vue Router config
├── assets/ Static assets + CSS
└── test/ Vitest tests