Skip to content

Naming Conventions

This document is a concise reference for naming conventions that apply across all layers of a backend project following these standards. When a layer-specific convention file defines a more specific rule, that rule takes precedence.


C# Type and Member Naming

ConceptConventionExample
ClassPascalCasePostRepository, CreatePostCommandHandler
InterfaceIPascalCaseIPostRepository, IDatabaseContext
RecordPascalCasePostResult, CreatePostCommand
Abstract classPascalCaseDomainException, AggregateNotFoundException
EnumPascalCase (type and values)OrderStatus.Pending, OrderStatus.Placed
MethodPascalCaseGetByIdAsync, HandleAsync, Publish
PropertyPascalCaseTitle, PublishedAt, AuthorId
Private backing field_camelCase_lines, _postRepository
Constructor parameter (DI)camelCasepostRepository, messageBus
Local variablecamelCasepost, result, command
ConstantPascalCaseMaxTitleLength, DefaultPageSize
Static readonly fieldPascalCaseEmpty (e.g., PostId.Empty)

File Naming

One class per file. File name matches the primary type name exactly, including capitalization.

RuleExample
One public type per filePost.cs contains sealed class Post
File name matches type namePostNotFoundException.cs contains sealed class PostNotFoundException
No type suffixes in file names beyond what the type name already containsIPostRepository.cs not IPostRepositoryInterface.cs

Layer-Specific Suffixes

SuffixProjectMeaning
CommandApplication.Write.ContractsInput record for a write operation
CommandResultApplication.Write.ContractsOutput record from a write operation
CommandHandlerApplication.WriteHandler implementation for a command
CommandValidatorApplication.WriteValidator for a command. Throws CommandValidationException subclasses.
QueryApplication.Read.ContractsInput record for a read operation
ResultApplication.Read.ContractsFull output record returned by a query handler
SummaryApplication.Read.ContractsAbbreviated projection for list queries
IDatabaseContextApplication.Read.ContractsSingle read-side database context interface. One property per aggregate.
QueryHandlerApplication.ReadHandler implementation for a query
QueryValidatorApplication.ReadValidator for a query. Throws QueryValidationException subclasses.
EventHandlerApplication.ReactionsHandler that reacts to a domain event
RepositoryInfrastructureImplementation of an IXxxRepository interface
ConfigurationInfrastructureEF Core IEntityTypeConfiguration<T> class
EndpointWebApiIEndpoint implementation class
RequestWebApiHTTP request body model
ResponseWebApiHTTP response body model
ApiMappingsWebApiinternal static class with mapping extension methods
ServiceRegistrationInfrastructure, WebApiDI extension method class

Exception Naming Convention

Exception CategoryNaming PatternExample
DomainException subclass{AggregateName}{Reason}ExceptionPostAlreadyPublishedException
AggregateNotFoundException subclass{AggregateName}NotFoundExceptionPostNotFoundException
CommandValidationException subclass{FieldOrConcept}RequiredException or {FieldOrConcept}{Reason}ExceptionPostTitleRequiredException, PostTitleTooLongException
QueryValidationException subclass{FieldOrConcept}RequiredExceptionPostIdRequiredException

Boolean Properties and Methods

Properties and methods that return a boolean MUST use the Is, Has, or Can prefix where the semantics fit.

// GOOD:
public bool IsPublished => State is PublishedPostState;
public bool HasTags => _tags.Count > 0;
// BAD:
public bool Published => State is PublishedPostState;
public bool Tags => _tags.Count > 0;

Async Method Naming

Every method that returns Task or Task<T> (or ValueTask / ValueTask<T>) MUST have the Async suffix. Every such method MUST accept a CancellationToken parameter as the last parameter named exactly cancellationToken.

// GOOD:
Task<Post> GetByIdAsync(PostId id, CancellationToken cancellationToken);
Task AddAsync(Post post, CancellationToken cancellationToken);
// BAD:
Task<Post> GetById(PostId id);
Task<Post> GetByIdAsync(PostId id, CancellationToken ct); // BAD: parameter named 'ct', not 'cancellationToken'
Task Add(Post post);

Event Handler Naming

Event handler class names follow the pattern {Action}On{EventName}EventHandler. The action describes what the handler does. The event name is the domain event class name without the word “Event”.

// GOOD: name describes the action and the triggering event
SendConfirmationOnOrderPlacedEventHandler
NotifySubscribersOnPostPublishedEventHandler
LogOnCustomerRegisteredEventHandler
// BAD: name only describes the event, not the action
PostPublishedEventHandler // BAD: what does it do when the post is published?
OnPostPublished // BAD: missing "EventHandler" suffix

Narrow Interface Naming

Narrow interfaces defined in Application.Reactions are named after their single purpose. They follow the pattern I{Verb}{Context} where the verb describes the action and the context names the subject.

// GOOD: name describes the single capability the interface provides
internal interface IPostPublishedNotifier
{
Task NotifySubscribersAsync(PostId postId, string postTitle, CancellationToken cancellationToken);
}
internal interface IOrderConfirmationSender
{
Task SendAsync(OrderId orderId, string recipientEmail, CancellationToken cancellationToken);
}
// BAD: broad service interface that exposes more than the handler needs
internal interface IEmailService // BAD: exposes 20 methods; the handler needs one
{
Task SendAsync(...);
Task SendBulkAsync(...);
Task GetDeliveryStatusAsync(...);
// ...
}