dotnet-domain-primitives
Skill4 starsChanged 10 months agoMITView on GitHub
Use when modelling domain types or designing API/persistence boundaries in .NET - prevent primitive obsession by enforcing strongly-typed IDs and value objects.
In this repository67 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- agents-onboardingSkill
- agent-workitem-automationSkill
- architecture-testingSkill
- aspire-integration-testingSkill
- automated-standards-enforcementSkill
- best-practice-introductionSkill
- branching-strategy-and-conventionsSkill
- broken-windowSkill
- change-risk-rollbackSkill
- ci-cd-conformanceSkill
- component-boundary-ownershipSkill
- contract-consistency-validationSkill
- csharp-best-practicesSkill
- deployment-provenanceSkill
- documentation-as-codeSkill
- documentation-scaffoldSkill
- dotnet-bespoke-code-minimisationSkill
- dotnet-best-practicesSkill
- dotnet-efcore-practicesSkill
- dotnet-healthchecksSkill
- dotnet-logging-serilogSkill
- dotnet-mapping-standardSkill
- dotnet-open-source-first-governanceSkill
- dotnet-source-generation-firstSkill
- dotnet-specification-patternSkill
- dotnet-testing-assertionsSkill
- environment-traceabilitySkill
- finishing-a-development-branch-iddSkill
- greenfield-baselineSkill
- impacted-scope-enforcementSkill
- incremental-change-impactSkill
- incremental-deploymentSkill
- innersource-governance-bootstrapSkill
- issue-driven-deliverySkill
- library-extraction-stabilisationSkill
- local-dev-experienceSkill
- markdown-authorSkill
- modern-csharp-coding-standardsSkill
- monorepo-orchestration-setupSkill
- observability-logging-baselineSkill
- pair-programmingSkill
- persona-switchingSkill
- process-skill-routerSkill
- quality-gate-enforcementSkill
- release-tagging-planSkill
- repo-best-practices-bootstrapSkill
- repository-foundation-bootstrapSkill
- requirements-gatheringSkill
- runtime-tooling-validationSkill
- safe-brownfield-refactorSkill
- scoped-colocationSkill
- security-processesSkill
- selective-build-testSkill
- semantic-version-impactSkill
- session-awareSkill
- skills-first-workflowSkill
- static-analysis-securitySkill
- structured-review-workflowSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 9 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈9
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 27
Define strongly typed IDs: Create typed ID types using source-generation libraries for entity identifiers
- Distinctive: line 28
Define value objects: Create value objects for meaningful primitives with validation (EmailAddress, Money)
- Distinctive: line 30
Configure serialization: Add JSON converters, EF Core value converters, and type handlers as needed
- Distinctive: line 32
Add boundary tests: Test that boundary conversion works and invalid primitives are rejected
- Very specific: line 40
Use value objects for meaningful primitives (e.g., EmailAddress, Money, Percentage, TenantId, CorrelationId).
- Distinctive: line 44
Prefer open-source libraries that use source generation for typed IDs.
- Distinctive: line 77
Ensure typed ID types are stable for logging/telemetry (string representation).
- Distinctive: line 78
Avoid implicit conversions that obscure boundary crossings.
- Distinctive: line 127
No type safety between layers
- Distinctive: line 218
No domain-layer primitive IDs added.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Overview
Prevent primitive obsession by enforcing strongly typed identifiers and value objects in domain models. Conversions to/from primitives are permitted only at explicit boundaries (persistence, transport, serialization), ensuring type safety and validation throughout the domain layer.
When to Use
- Designing or reviewing domain models (entities, aggregates, commands/events)
- Introducing new entity identifiers (CustomerId, OrderId, TenantId, etc.)
- Working with meaningful primitives that carry validation (EmailAddress, Money, Percentage)
- Reviewing code where primitive types (Guid, string, int) are used for domain concepts
- Implementing API boundaries, persistence layers, or serialization
Core Workflow
- Identify primitive obsession: Locate places where Guid, string, int, or long represent domain concepts
- Define strongly typed IDs: Create typed ID types using source-generation libraries for entity identifiers
- Define value objects: Create value objects for meaningful primitives with validation (EmailAddress, Money)
- Establish boundary conversions: Map to/from primitives only at explicit boundaries (API, persistence, transport)
- Configure serialization: Add JSON converters, EF Core value converters, and type handlers as needed
- Update service signatures: Change service methods to accept domain primitives, not raw types
- Add boundary tests: Test that boundary conversion works and invalid primitives are rejected
Core
Defaults (non-negotiable)
- StronglyTypedIds by default for all entity identifiers in domain/application code.
- No primitive IDs (
Guid,int,long,string) in the domain layer. - Use value objects for meaningful primitives (e.g.,
EmailAddress,Money,Percentage,TenantId,CorrelationId).
Preferred approach
- Prefer open-source libraries that use source generation for typed IDs.
- Conversions to/from primitives are permitted only at explicit boundaries:
- persistence adapters,
- transport adapters (HTTP, messaging),
- serialization/deserialization.
Review rules
- New domain types must not introduce primitive ID properties/fields.
- Mapping layers must map typed IDs explicitly; no "magic" conversions hidden in core domain types.
Load: examples
Strongly typed ID (source generator style)
- Define a
CustomerIdtype and use it on entities/commands. - Map to primitive
Guidat the persistence boundary and transport boundary.
Value object boundaries
- Allow
stringin DTOs if required by external contracts. - Convert to
EmailAddress(value object) inside the application layer.
Load: advanced
Integration guidance
- EF Core: value converters for typed IDs and value objects.
- System.Text.Json: custom converters where needed for typed IDs.
- Dapper: type handlers if Dapper is used for read models.
Operational concerns
- Ensure typed ID types are stable for logging/telemetry (string representation).
- Avoid implicit conversions that obscure boundary crossings.
API Boundary Mapping & Validation
Before: Primitive Obsession at Boundaries
// API Controller - accepts primitives
[ApiController]
[Route("api/[controller]")]
public class CustomersController
{
private readonly ICustomerService _service;
[HttpPost]
public async Task<IActionResult> CreateCustomer(CreateCustomerRequest request)
{
// No type safety - primitives passed directly to service
var result = await _service.CreateCustomer(request.Id, request.Email);
return Ok(result);
}
}
// Service layer - accepts primitives, loses domain context
public class CustomerService
{
public async Task<CustomerDto> CreateCustomer(string id, string email)
{
// Validation scattered across layers
if (string.IsNullOrWhiteSpace(email))
throw new ArgumentException("Email required");
// No connection to domain types
var customer = new Customer { Id = Guid.Parse(id), Email = email };
await _repository.AddAsync(customer);
return new CustomerDto { Id = customer.Id.ToString(), Email = email };
}
}
// DTO - exposes internal structure
public class CreateCustomerRequest
{
public string Id { get; set; }
public string Email { get; set; }
}Problems:
- No type safety between layers
- Validation scattered across concerns
- Easy to pass invalid primitives
After: Domain Primitives at Boundaries
// Domain types
public partial class CustomerId : IStronglyTypedId<Guid> { }
public partial class EmailAddress : IValueObject<string> { }
// API Controller - explicit boundary conversion
[ApiController]
[Route("api/[controller]")]
public class CustomersController
{
private readonly ICustomerService _service;
private readonly ICustomerMapper _mapper;
[HttpPost]
public async Task<IActionResult> CreateCustomer(CreateCustomerRequest request)
{
// Explicit conversion at boundary
var customerId = new CustomerId(Guid.Parse(request.Id));
var email = EmailAddress.Create(request.Email).ThrowIfFailure();
var result = await _service.CreateCustomer(customerId, email);
return Ok(_mapper.ToResponse(result));
}
}
// Service layer - type-safe, domain-focused
public class CustomerService
{
public async Task<Customer> CreateCustomer(CustomerId id, EmailAddress email)
{
// Domain types ensure validity before service runs
var customer = Customer.Create(id, email).ThrowIfFailure();
await _repository.AddAsync(customer);
return customer;
}
}
// Mapper - explicit conversion layer
public class CustomerMapper
{
public CustomerResponse ToResponse(Customer customer)
{
return new CustomerResponse
{
Id = customer.Id.Value.ToString(), // Explicit back to primitive
Email = customer.Email.Value // Explicit back to primitive
};
}
}Benefits:
- Type safety enforced across layers
- Validation centralized in domain types
- Clear boundary crossing
- Compiler prevents invalid combinations
Validation Steps for Domain Primitive Implementation
- API Controllers:
- DTO properties remain primitives
- Convert DTOs to domain types immediately upon entry
- Use mapper/converter class for boundary crossing
- Service Layer:
- Accept domain primitives, not raw types
- Never accept
GuidwhenCustomerIdexists - Ensure validation runs before service logic
- Mapping & Serialization:
- JSON serialization handles conversion via custom converters
- EF Core value converters map domain types ↔ database columns
- No implicit conversions in constructors
- Testing:
- Unit test boundary conversion in mapper
- Integration test proves invalid primitives rejected at API
- Verify domain type validation runs before service
Load: enforcement
Acceptance criteria for PRs
- New entities/aggregates use typed IDs.
- No domain-layer primitive IDs added.
- Boundary conversion is explicit and covered by unit tests.
Red Flags - STOP
These statements indicate primitive obsession patterns:
| Thought | Reality |
| "Guid is fine for identifiers" | Primitive IDs lose type safety; use strongly typed IDs |
| "String is good enough for email" | Value objects centralise validation; prevent invalid data |
| "Implicit conversions are convenient" | Implicit conversions obscure boundaries; be explicit |
| "Domain types add too much ceremony" | Source generators eliminate boilerplate; use them |
| "We'll add types later" | Retrofitting types is expensive; start with them |
| "Validation can happen anywhere" | Centralise validation in domain types; single source of truth |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

