Skip to content

Module Structure — Anatomy of a Granit Package

Every Granit package follows a predictable directory structure. A developer opening Granit.Identity should find the same layout as in Granit.Workflow or Granit.DataExchange.

Granit.Example/
├── Domain/ Entities, aggregates, Value Objects, domain enums
├── Events/ Wolverine messages (events, commands)
├── Exceptions/ Business exceptions (inherit BusinessException, etc.)
├── Extensions/ Extension methods (DI, builders, etc.)
├── Handlers/ Wolverine handlers (event/command consumers)
├── Internal/ Internal implementations (services, stores, etc.)
├── Localization/ JSON translation files (18 cultures)
├── Options/ IOptions<T> configuration classes
├── GranitExampleModule.cs Module class (required at root)
├── IExampleReader.cs Public interfaces (at module root)
├── IExampleWriter.cs
├── Granit.Example.csproj
└── README.md
Granit.Example.Endpoints/
├── Dtos/ Request/Response records (API contracts)
├── Endpoints/ Minimal API classes (MapGet, MapPost, etc.)
├── Extensions/ EndpointRouteBuilderExtensions
├── Internal/ Internal implementations
├── Localization/ JSON translation files
├── Options/ Endpoint configuration
├── Permissions/ PermissionDefinitionProvider
├── Validators/ FluentValidation validators
├── GranitExampleEndpointsModule.cs
├── Granit.Example.Endpoints.csproj
└── README.md
Granit.Example.EntityFrameworkCore/
├── Configurations/ Fluent API (IEntityTypeConfiguration<T>)
├── Entities/ EF-only entities (not in the business package)
├── Extensions/ ServiceCollectionExtensions (AddDbContextFactory, etc.)
├── Internal/ DbContext, EfCoreStore implementations
├── GranitExampleEntityFrameworkCoreModule.cs
├── IExampleDbContext.cs Host interface (optional, for shared DbContexts)
├── Granit.Example.EntityFrameworkCore.csproj
└── README.md
DirectoryContentExpected visibility
Extensions/Extension methods (IServiceCollection, IApplicationBuilder, ModelBuilder)public static
Internal/Concrete implementations, services, stores, handlers, DbContextinternal sealed
Options/Configuration classes bound to IOptions<T> / IOptionsMonitor<T>public
Localization/JSON translation files (en.json, fr.json, etc.)
DirectoryContentExpected visibility
Domain/Rich entities, aggregates, Value Objects, domain enumspublic
Events/Wolverine messages (integration events, commands)public sealed record
Exceptions/Business exceptions (BusinessException, NotFoundException, etc.)public sealed
Handlers/Wolverine handlers for events/commandsinternal sealed
DirectoryContentExpected visibility
Dtos/Request (*Request) and response (*Response) recordspublic sealed record
Endpoints/Minimal API classes declaring routesinternal sealed
Permissions/*PermissionDefinitionProviderinternal sealed
Validators/FluentValidation validators (*Validator)internal sealed

Persistence directories (*.EntityFrameworkCore packages)

Section titled “Persistence directories (*.EntityFrameworkCore packages)”
DirectoryContentExpected visibility
Configurations/IEntityTypeConfiguration<T> (Fluent API)internal sealed
Entities/EF-only entities not present in the business packagepublic sealed
NameWhy forbiddenUse instead
Helpers/, Utils/, Common/No semantics — becomes a dumping groundInternal/ or a thematic directory
Services/Too generic — says nothing about responsibilityInternal/ for implementations, root for interfaces
Abstractions/Granit does not separate interfaces into a dedicated packageModule root
Models/Ambiguous between DTO, entity, and ViewModelDomain/ for entities, Dtos/ for DTOs
DbContext/ (directory)Legacy patternInternal/
Stores/ (directory)Stores are internal implementationsInternal/
File typeLocationExample
Public interfaceModule rootIBlobStorage.cs
Public record (service contract)Module rootBlobUploadRequest.cs
Module classModule rootGranitBlobStorageModule.cs
Domain entityDomain/Domain/ExportJob.cs
Business exceptionExceptions/Exceptions/BlobNotFoundException.cs
Wolverine eventEvents/Events/BlobDeletedEvent.cs
Internal implementationInternal/Internal/DefaultBlobStorage.cs
Options classOptions/Options/BlobStorageOptions.cs
DI extensionExtensions/Extensions/BlobStorageServiceCollectionExtensions.cs

2 levels maximum under the module. The IDE handles search — no need for deep nesting.

OK Granit.Foo/Internal/MyStore.cs
OK Granit.Foo/Domain/MyEntity.cs
BAD Granit.Foo/Domain/SubDomain/Values/MyValue.cs (too deep)

Exception: complex multi-feature modules (e.g., Granit.DataExchange) may use feature-level directories at the first level, then standard directories inside.

The namespace must always match the directory structure:

src/Granit.Foo/Internal/MyStore.cs -> namespace Granit.Foo.Internal;
src/Granit.Foo/Domain/MyEntity.cs -> namespace Granit.Foo.Domain;
src/Granit.Foo/IMyService.cs -> namespace Granit.Foo;

This is enforced by the architecture test Namespace_should_match_folder_structure_in_src.

Every *.EntityFrameworkCore package that owns an isolated DbContext must follow this checklist with no exceptions:

  1. <ProjectReference> to Granit.Persistence in the .csproj
  2. Derive from GranitDbContext, passing ICurrentTenant and an optional IDataFilter to the base constructor
  3. Override OnGranitModelCreating for entity configuration — OnModelCreating is sealed on the base class and ApplyGranitConventions is internal
  4. Interceptor wiring in the extension method: use the (sp, options) overload of AddDbContextFactory with ServiceLifetime.Scoped and resolve AuditedEntityInterceptor / SoftDeleteInterceptor from the service provider
  5. [DependsOn(typeof(GranitPersistenceModule))] on the module class
  6. No manual HasQueryFilter in entity configurations — the conventions pass handles all standard filters centrally
  7. IMultiTenant entities use Guid? TenantId (never string)

Modules decorated with [assembly: WolverineHandlerModule] get automatic validator discovery via AddGranitWolverine(). Modules without Wolverine handlers must call AddGranitValidatorsFromAssemblyContaining<TValidator>() manually. Without registration, FluentValidationEndpointFilter<T> silently skips validation.

  1. Create the project: dotnet new classlib -n Granit.Example
  2. Add GranitExampleModule.cs at the root
  3. Create Extensions/ExampleServiceCollectionExtensions.cs with AddGranitExample()
  4. Create README.md
  5. Create the mirror test project: tests/Granit.Example.Tests/
  6. Add directories as needed from the blueprints above
  7. Verify with dotnet test tests/Granit.ArchitectureTests that the structure is compliant