Skip to content

Coding Conventions

Backend (C#)

Naming Conventions

ElementConventionExample
Classes, interfaces, methods, propertiesPascalCaseFileRecord, IStorageService
Private fields_camelCase_dbContext, _logger
Parameters, local variablescamelCasefileName, userId
InterfacesI-prefixIAuditService, IEmailRenderer
ConstantsPascalCaseMaxFileSize, 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 TypeUse ForPostgreSQL Type
InstantUTC timestampstimestamp with time zone
LocalDateDates without timedate
LocalTimeTimes without datetime

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 assignment

Why 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 order
  • Guid.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 to sites/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-ready
  • feat/phase-N — phase development branches
  • feat/{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/.json files

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 registration

Frontend

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