Skip to content

Granit Architecture — ADRs, patterns and design principles

Pick any senior .NET codebase and ask why a specific call uses IReader instead of IRepository, why authentication never reaches a shared library, or why the framework ships eight modules just to expose OData to Power BI. The honest answer is almost always “someone decided this two years ago and we never wrote it down”. Granit does the opposite: every architectural decision that constrains future code is captured in writing — with context, evaluated alternatives, and explicit consequences — the moment it’s made.

This section is the trail of those decisions, plus the pattern library that maps each design choice to its concrete implementation in the framework.

  • Pattern Library — 60 design patterns with their concrete implementation in Granit, organized by category (architecture, cloud/SaaS, GoF, data, concurrency, .NET idioms, security, AI).
  • ADRs — 56 Architecture Decision Records documenting library selections, design pivots, and the trade-offs behind each. Read these when a convention surprises you — the why is in the record.
  • Architecture Styles — DDD ≠ architecture style. Granit supports both Clean Architecture and Vertical Slice Architecture; pick what fits your team.
  • Dependency Graph — visual map of every Granit package and how it depends on the rest. Read before adding a reference.
  • HTTP Conventions — status codes, RFC 7807 Problem Details, DTO naming, pagination — the contract every Granit endpoint honours.
  • Tech Stack — every direct production dependency, organized by domain, with the ADR that justifies it.

Six rules that explain most of Granit’s apparent peculiarities. Each appears in multiple ADRs — they’re not retrofits, they’re the framework’s spine.

PrincipleWhat it meansWhy it matters
Convention over configurationSensible defaults; explicit overridesNew modules don’t need to declare what hasn’t changed — every line of config is a line you can get wrong
Module isolationEach module owns its DbContext, DI registrations, and public API surfaceModules can be replaced, removed, or upgraded without ripple effects
CQRS everywhereIReader and IWriter interfaces stay separate; never merged into a single IRepositoryRead paths can scale and cache independently; write paths can enforce invariants without leaking through reads
Soft dependenciesModules access cross-cutting concerns (tenancy, time, user context, AI) via Granit interfaces with null-object defaultsRemoving an optional package is a one-line config change, not a refactor
Compliance by designGDPR (right to erasure, data minimization) and ISO 27001 (audit trail, immutability) are architectural decisionsCompliance reviews don’t trigger code rewrites
No silent defaults for risky surfacesOData EntitySets must declare RequirePermission + expand policy explicitly; webhook target URLs are SSRF-validated by default; MapGranitGroup enforces FluentValidationConvention drift toward “unprotected by default” is the top cause of data leaks

Three reading paths depending on what you’re trying to do:

  • Onboarding a Granit module for the first time → start with the relevant pattern page. It’s the shortest description of how a concept is implemented, with file paths to the actual source.
  • A convention is blocking you and you want to know if you should fight it → search the ADRs. If the constraint isn’t justified there, it’s probably accidental and open for change. If it is, the Consequences section tells you what breaks when you bend it.
  • You’re picking a library or pattern for a new module → check Tech Stack first (libraries Granit already uses) and the Pattern Library (idioms the rest of the framework follows). Aligning saves you from reinventing what’s already tested.