Skip to content

Backend ADRs — Architecture Decision Records

Architecture Decision Records (ADRs) document significant technical decisions made during the development of Granit.

Each ADR follows a consistent template: Context, Decision, Evaluated Alternatives, Justification, and Consequences.

#TitleStatusDateScope
001Observability Stack — Serilog + OpenTelemetryAccepted2026-02-21Granit.Observability
002Redis via StackExchange.Redis — Distributed CacheAccepted2026-02-21Granit.Caching
003Testing Stack — xUnit v3, NSubstitute and BogusAccepted2026-02-21granit-dotnet
004Asp.Versioning — REST API VersioningAccepted2026-02-22Granit.Http.ApiVersioning
005Wolverine + Cronos — Messaging, CQRS and SchedulingAccepted2026-02-22Granit.Wolverine
006FluentValidation — Business Validation FrameworkAccepted2026-02-24Granit.Validation
007Testcontainers — Containerized Integration TestsAccepted2026-02-24Integration Tests
008SmartFormat.NET — CLDR PluralizationAccepted2026-02-26Granit.Localization
009Scalar.AspNetCore — Interactive API DocumentationAccepted2026-02-26Granit.Http.ApiDocumentation
010Scriban — Text Template EngineAccepted2026-02-27Granit.Templating.Scriban
011ClosedXML — Excel Spreadsheet GenerationAccepted2026-02-27Granit.DocumentGeneration.Excel
012PuppeteerSharp — HTML to PDF RenderingAccepted2026-02-28Granit.DocumentGeneration.Pdf
013Magick.NET — Image ProcessingAccepted2026-02-28Granit.Imaging.MagickNet
014Migrate FluentAssertions to ShouldlyAccepted2026-02-28granit-dotnet
015Sep — High-Performance CSV ParsingAccepted2026-03-01Granit.DataExchange.Csv
016Sylvan.Data.Excel — Streaming Excel File ReadingAccepted2026-03-01Granit.DataExchange.Excel
017DDD Aggregate Root & Value Object StrategyAccepted2026-03-19granit-dotnet (all modules)
018FusionCache — Caching ProviderAcceptedGranit.Caching
019User Lookup — Dual ModeAcceptedGranit.Identity
020Declarative Definitions Placement (Query & Export)Accepted2026-04-18Granit.QueryEngine, Granit.DataExchange, all modules
021Privacy Data Export — Framework DefaultsAccepted2026-04-19Granit.Privacy, Granit.Privacy.BlobStorage, Granit.Privacy.EntityFrameworkCore
022ICommandSender + Module Naming — No Technology Suffix on Domain ModulesAccepted2026-04-20All business modules, Granit.Commands, Granit.Wolverine
023Tenant-aware role lookupAccepted2026-04-22Granit.Identity.Local, Granit.Identity.Local.AspNetIdentity
024Shared-connection EF Core transaction for role orchestrationAccepted2026-04-22Granit.Identity.Local.AspNetIdentity, Granit.Authorization.EntityFrameworkCore, Granit.OpenIddict.EntityFrameworkCore, Granit.Persistence.EntityFrameworkCore
025Keycloak client-role distinction and boot-time syncAccepted2026-04-22Granit.Identity, Granit.Identity.Federated.Keycloak
026Entra ID App Role distinction and boot-time syncAccepted2026-04-22Granit.Identity.Federated.EntraId
027Cognito app-client group sync via naming-prefix conventionAccepted2026-04-23Granit.Identity.Federated.Cognito
028Unified Data Lookup for QueryEngine Filters and Form DropdownsAccepted2026-04-23Granit.DataLookup.*, Granit.QueryEngine.*, Granit.ReferenceData.*, @granit/data-lookup, @granit/react-data-lookup
029Client-role sync — opt-in orphan cleanup policyAccepted2026-04-23Granit.Authorization.*, Granit.Identity.Federated.*
030Client-role sync — scheduled re-sync via Granit.BackgroundJobsAccepted2026-04-23Granit.Identity.Federated.*.BackgroundJobs
031Client-role write operations on IIdentityClientRoleManagerAccepted2026-04-23Granit.Identity, Granit.Identity.Federated.*
032Granit.Catalog with Product as the shared billing aggregateAccepted2026-04-24Granit.Catalog.*, Granit.Metering, Granit.Subscriptions, Granit.Invoicing
033Metering hybrid — lifecycle, CountDistinct, recompute, backfill, deprecateAccepted2026-04-25Granit.Metering.*
034Subscriptions — pricing tiers, phases, discounts, price overridesAccepted2026-04-25Granit.Subscriptions, Granit.Subscriptions.EntityFrameworkCore, Granit.Subscriptions.Endpoints
035Granit.CustomerBalance ↔ ORB Credits term-by-term mappingAccepted2026-04-25Granit.CustomerBalance.*
036Invoicing line item source + product conventionAccepted2026-04-25Granit.Invoicing.*, Granit.Subscriptions, Granit.Metering
037Party Merge Framework — Mergeable primitive + cross-module rewritersAccepted2026-04-27Granit.EntityMerge, Granit.EntityMerge.EntityFrameworkCore, Granit.Parties, Granit.Parties.Mergeable, Granit.Parties.Endpoints
038DashboardDefinition vs Dashboard boundaryAccepted2026-04-28Granit.Analytics, Granit.Analytics.EntityFrameworkCore, Granit.Analytics.Endpoints
039Widget renderer architecture — typed IWidgetSource<TSnapshot> + non-generic adapterAccepted2026-04-29Granit.Dashboards, Granit.Dashboards.Endpoints, Granit.Analytics
040Three-tier metadata architecture (compiled / tenant customization / Niveau B)Accepted2026-05-01Granit.Entities, Granit.Entities.Customization
041Field component catalog + closed naming conventionAccepted2026-05-01Granit.Entities (form/detail builders); @granit/entities-react
042View catalog + per-kind config schemaAccepted2026-05-01Granit.Entities (collections facet); @granit/entities-react
043Dashboard push transport (SSE / WebSocket) is framework-ownedAccepted2026-04-30Granit.Analytics, Granit.Dashboards, Granit.Dashboards.Push
044Workspace navigation — hierarchical + additive presets + agnostic detail routesAccepted2026-04-30Granit.Workspaces.*, @granit/workspaces-react
045Inversion-of-control contributor pattern (workspaces, relations, activities)Accepted2026-04-30Granit.Workspaces.Abstractions, Granit.Entities.Abstractions, Granit.Activities.Abstractions
046Activities vs Timeline split — past vs future, narrative vs action-requiredAccepted2026-04-30Granit.Timeline, Granit.Activities
047EntityView supersedes Granit.QueryEngine.SavedViewsAccepted2026-04-30Granit.Entities.Views, Granit.Entities.Views.Endpoints; @granit/entities-react
048Cross-module entity relations (smart buttons + sidebar + tab + inline-chips)Accepted2026-05-01Granit.Entities.Abstractions, Granit.Entities, Granit.Entities.Endpoints
049Default landing route — 5-tier precedence with URL/route outputAccepted2026-04-30Granit.Workspaces, Granit.Workspaces.Endpoints
050OData EDM whitelist via EntityDefinition (gate) + ExportDefinition (field source)Accepted2026-05-01Granit.Http.ODataExposure
051User aggregate in Granit.Identity + optional Granit.Parties.Identity bridgeAccepted2026-05-02Granit.Identity, Granit.Identity.Local, Granit.Identity.Federated, Granit.Authorization, Granit.Parties.Identity
052Granit.Documents — user-managed file & asset moduleAccepted2026-05-10Granit.Documents.* (+ Renditions, AssetMetadata, PublicLinks, Indexing, Workflow, Collections)
053Granit.Entities.Customization — Layer 1 tenant overrides (reorder / regroup / hide)Accepted2026-05-02Granit.Entities.Customization.* (+ touches Granit.Entities.Endpoints)
054Granit.Taxonomy — cross-cutting tags and hierarchical categoriesAccepted2026-05-02Granit.Taxonomy.* (replaces per-module *Tag join tables)
055Extract URL safety + temp-file primitives into shared packagesAccepted2026-05-11Granit.Http.Security, Granit.IO; consumers refactored: Browsing, Webhooks, Privacy.BlobStorage
056Server-side entity-action execution + bulk endpointAccepted2026-05-13Granit.Entities.Abstractions (Actions/Execution/), Granit.Entities.Endpoints (BulkActionEndpoint)
057Workspace composition belongs to the application, not the moduleAccepted2026-05-15Granit.Workspaces.*, every *.Endpoints package shipping a *WorkspaceContribution
058JSON Persistence PolicyAccepted2026-05-21Granit.Persistence
059Enum persistence strategyAccepted2026-05-24Granit.Persistence
060OIDC sub-as-Guid policy for domain ownershipAccepted2026-05-26Granit.Users, Granit.Documents
061Optimistic concurrency strategyAccepted2026-05-27Granit.Persistence
062Framework-pure core + transport bindings (.Http / .Wolverine)Accepted2026-05-27Granit.RateLimiting.*, Granit.Features.*
063Tenant/Host data storage modes — reverted to single-DbContext baselineSuperseded2026-05-31Granit.Persistence, all *.EntityFrameworkCore packages
064Structured AI output as a first-class Granit.AI primitiveAccepted2026-05-30Granit.AI, Granit.AI.Extraction, all .AI consumer modules
065Privacy capability modularization — extract Data Export as an opt-in moduleAccepted2026-06-03Granit.Privacy, Granit.Privacy.DataExport, Granit.Parties.Privacy, Granit.Documents.Privacy
066Account creation — master gate, default-role provisioning, external profile completionAccepted2026-06-11Granit.Identity.Local, Granit.Identity.Local.Endpoints, Granit.OpenIddict
067Agentic AI chat, tool registry, and prompt catalogAccepted2026-06-12Granit.AI.Tools, Granit.AI.Chat, Granit.AI.Prompts (+ QueryEngine.AI / VectorData / Localization.AI adapters)
068Validation message key naming convention — Validation:{Category}:{Rule} (framework) / {Module}:Validation:{Rule} (modules), enforced by an architecture testAccepted2026-06-11Granit.Validation + every module owning validation messages (framework, granit-business, granit-iot)
069Bank accounts as the single source of truth — centralized referential, masked snapshots, immutable signed mandate snapshotAccepted2026-06-14Granit.BankAccounts.*, Granit.Payments.SepaDirectDebit.*, Granit.Payments.SepaTransfer.*
070Value-object persistence & query capabilities — converter (default) vs ComplexProperty opt-in vs read-model projection; fail-loud QueryEngine contractAccepted2026-06-15Granit.Domain.SingleValueObject, ApplyGranitConventions, Granit.QueryEngine.*
071Agentic chat streaming via FunctionInvokingChatClient — early-flush SSE + live tool-status framesAccepted2026-06-18Granit.AI.Tools, Granit.AI.Chat, Granit.AI.Chat.Endpoints (+ Microsoft.Extensions.AI)
072Address platform — three orthogonal axes, flat-column persistence, capability model, tiered verificationAccepted2026-06-29Granit.Domain.ValueObjects (Address*), Granit.Geocoding.*, Granit.AddressEnrichment, Granit.AddressDeliverability.Abstractions; consumed by granit-business Parties