name: add-domain-entity description: | Create domain layer components: models, repository interfaces, marshallers, and implementations. Use when: (1) adding domain model in internal/domain/model/, (2) creating repository interface in internal/domain/repository/, (3) implementing repository with marshaller in internal/infrastructure/{db}/. This is Step 2 of CRUD workflow (after add-database-table, before add-api-endpoint).
Add Domain Entity
Create domain layer components for a new entity following DDD patterns.
Prerequisites
- Database table created (use add-database-table skill first)
- SQLBoiler model generated via
make migrate.up
Quick Workflow
1. Domain Model → internal/domain/model/{entity}.go
2. Domain Error → internal/domain/errors/errors.go
3. Repository Interface → internal/domain/repository/{entity}.go
4. Marshaller → internal/infrastructure/{db}/internal/marshaller/{entity}.go
5. Repository Impl → internal/infrastructure/{db}/repository/{entity}.go
6. Generate Mocks → make generate.mock
Step 1: Create Domain Model
Location: internal/domain/model/{entity}.go
Create the entity struct, constructor, update methods, and type aliases.
Key requirements:
- Use
id.New()for ID generation in constructor - Set both
CreatedAtandUpdatedAtto the same time parameter - Define
ReadonlyReferencefor relations (always nil in constructor) - Create slice type alias:
type Examples []*Example
See: references/domain-model-patterns.md
Step 2: Add Domain Error
Location: internal/domain/errors/errors.go
Add a not-found error for the entity:
ExampleNotFoundErr = NewNotFoundError("E2xxxxx", "Example not found")
Follow error code conventions from .claude/rules/domain-errors.md.
Step 3: Create Repository Interface
Location: internal/domain/repository/{entity}.go
Define the repository interface with standard CRUD operations and query structs.
Key requirements:
- Add
//go:generatedirective for mock generation - Use
nullable.Type[T]for optional enum/custom type filter fields - Embed
BaseGetOptions/BaseListOptionsin query structs
See: references/repository-patterns.md
Step 4: Create Marshaller
Location: internal/infrastructure/{mysql|postgresql|spanner}/internal/marshaller/{entity}.go
Convert between DB models and domain models.
Key requirements:
- Related entity's
ReadonlyReferencemust remain nil (no recursive loading) - Use var declaration pattern for nullable timestamp fields
- Include both
ToModelandToDBModelfunctions
See: references/marshaller-patterns.md
Step 5: Create Repository Implementation
Location: internal/infrastructure/{mysql|postgresql|spanner}/repository/{entity}.go
Implement the repository interface using SQLBoiler.
Key requirements:
- Use
transactable.GetContextExecutor(ctx)for all DB operations - Implement
buildListQueryhelper for reusable filter logic - Implement
buildPreloadhelper for relation loading - Use base helper functions:
addForUpdateFromBaseGetOptions,addForUpdateFromBaseListOptions
See: references/repository-patterns.md
Step 6: Generate Mocks
make generate.mock
This generates mock implementations in internal/domain/repository/mock/.
Checklist
Domain Model
- Entity struct with all fields
-
ReadonlyReferencefor relations (if any) - Constructor with
id.New()andReadonlyReference: nil - Update method using
null.*types for optional fields - Slice type alias (
Examples []*Example) - Helper methods on slice (
IDs(),MapByID())
Status/Enum Types (if needed)
- Type definition with
Unknownas first constant -
String()andValid()methods -
New{Type}(str string)constructor
Repository
- Interface with
//go:generatedirective - Query structs with
nullable.Type[T]for optional enums - Domain error added for not-found case
Implementation
- Marshaller with
ToModel,ToDBModel,ToModels,ToDBModels - Marshaller handles
ReadonlyReferencecorrectly - Repository implementation with all CRUD methods
-
buildListQueryandbuildPreloadhelpers - Mocks generated
Next Steps
After creating domain entity, use add-api-endpoint skill to create:
- Usecase input/output structs
- Interactor interface and implementation
- Protocol Buffers definition
- gRPC handler