feat(domain): add core entities — Club, Member, WorkItem, Shift with state machine
- Create domain entities in WorkClub.Domain/Entities: Club, Member, WorkItem, Shift, ShiftSignup - Implement enums: SportType, ClubRole, WorkItemStatus - Add ITenantEntity interface for multi-tenancy support - Implement state machine validation on WorkItem with C# 14 switch expressions - Valid transitions: Open→Assigned→InProgress→Review→Done, Review→InProgress (rework) - All invalid transitions throw InvalidOperationException - TDD approach: Write tests first, 12/12 passing - Use required properties with explicit Guid/Guid? for foreign keys - DateTimeOffset for timestamps (timezone-aware, multi-tenant friendly) - RowVersion byte[] for optimistic concurrency control - No navigation properties yet (deferred to EF Core task) - No domain events or validation attributes (YAGNI for MVP)
This commit is contained in:
@@ -136,3 +136,99 @@ _Conventions, patterns, and accumulated wisdom from task execution_
|
||||
- Task 3: Populate `realm-export.json` with actual Keycloak realm configuration
|
||||
- Task 7: PostgreSQL migrations for Entity Framework Core
|
||||
- Task 22: Add backend (Api, Application, Infrastructure services) and frontend to compose file
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Domain Entities & State Machine (2026-03-03)
|
||||
|
||||
### Key Learnings
|
||||
|
||||
1. **TDD Approach Workflow**
|
||||
- Write tests FIRST (even when entities don't exist — LSP errors expected)
|
||||
- Create minimal entities to satisfy test requirements
|
||||
- All 12 tests passed on first run after implementation
|
||||
- This validates clean state machine design
|
||||
|
||||
2. **State Machine with C# 14 Switch Expressions**
|
||||
- Pattern matching for tuple of (currentStatus, newStatus) is cleaner than if-else chains
|
||||
- Expressions vs. traditional switch: more functional, concise, easier to verify all transitions
|
||||
- Chosen over Dictionary<(status, status), bool> because:
|
||||
- Easier to read and maintain
|
||||
- Compiler can verify exhaustiveness (with `_ => false` fallback)
|
||||
- No runtime lookup overhead
|
||||
- Clear inline state diagram
|
||||
|
||||
3. **Entity Design Patterns (Domain-Driven Design)**
|
||||
- All entities use `required` properties:
|
||||
- Enforces non-null values at compile time
|
||||
- Forces explicit initialization (no accidental defaults)
|
||||
- Clean validation at instantiation
|
||||
- `TenantId` is `string` (matches Finbuckle.MultiTenant.ITenantInfo.Id type)
|
||||
- Foreign keys use explicit `Guid` or `Guid?` (not navigation properties yet)
|
||||
- `RowVersion: byte[]?` for optimistic concurrency (EF Core `[Timestamp]` attribute in Task 7)
|
||||
|
||||
4. **DateTimeOffset vs DateTime**
|
||||
- Used DateTimeOffset for CreatedAt/UpdatedAt (includes timezone offset)
|
||||
- Better for multi-tenant global apps (know exact UTC moment)
|
||||
- .NET 10 standard for timestamp columns
|
||||
- Avoids timezone confusion across regions
|
||||
|
||||
5. **ITenantEntity Interface Pattern**
|
||||
- Explicit interface property (not EF shadow property) allows:
|
||||
- Easy data seeding in tests
|
||||
- LINQ queries without special knowledge
|
||||
- Clear contract in domain code
|
||||
- Marker interface only (no methods) — true DDD boundary
|
||||
|
||||
6. **Entity Lifecycle Simplicity**
|
||||
- No domain events (YAGNI for MVP)
|
||||
- No navigation properties (deferred to EF configuration in Task 7)
|
||||
- No validation attributes — EF Fluent API handles in Task 7
|
||||
- State machine is only behavior (business rule enforcement)
|
||||
|
||||
7. **Test Structure for TDD**
|
||||
- Helper factory method `CreateWorkItem()` reduces repetition
|
||||
- AAA pattern (Arrange-Act-Assert) clear in test names
|
||||
- Arrange: Create entity with minimal valid state
|
||||
- Act: Call transition method
|
||||
- Assert: Verify state changed or exception thrown
|
||||
- xUnit [Fact] attributes sufficient (no [Theory] needed for now)
|
||||
|
||||
8. **Project Structure Observations**
|
||||
- Projects in `backend/` root, not `backend/src/` (deviation from typical convention but works)
|
||||
- Subdirectories: Entities/, Enums/, Interfaces/ (clean separation)
|
||||
- Test mirrors source structure: Domain tests in dedicated folder
|
||||
- Class1.cs stub removed before implementation
|
||||
|
||||
### Files Created
|
||||
|
||||
- `WorkClub.Domain/Enums/SportType.cs` — 5 values
|
||||
- `WorkClub.Domain/Enums/ClubRole.cs` — 4 values
|
||||
- `WorkClub.Domain/Enums/WorkItemStatus.cs` — 5 values
|
||||
- `WorkClub.Domain/Interfaces/ITenantEntity.cs` — Marker interface
|
||||
- `WorkClub.Domain/Entities/Club.cs` — Basic aggregate root
|
||||
- `WorkClub.Domain/Entities/Member.cs` — User representation
|
||||
- `WorkClub.Domain/Entities/WorkItem.cs` — Task with state machine
|
||||
- `WorkClub.Domain/Entities/Shift.cs` — Volunteer shift
|
||||
- `WorkClub.Domain/Entities/ShiftSignup.cs` — Shift registration
|
||||
- `WorkClub.Tests.Unit/Domain/WorkItemStatusTests.cs` — 12 tests, all passing
|
||||
|
||||
### Build & Test Results
|
||||
|
||||
- **Tests**: 12/12 passed (100%)
|
||||
- 5 valid transition tests ✓
|
||||
- 5 invalid transition tests ✓
|
||||
- 2 CanTransitionTo() method tests ✓
|
||||
- **Build**: Release configuration successful
|
||||
- **Warnings**: Only Finbuckle version resolution (expected, no errors)
|
||||
|
||||
### Next Steps (Tasks 5-7)
|
||||
|
||||
- Task 5: Next.js frontend (parallel)
|
||||
- Task 6: Kustomize deployment (parallel)
|
||||
- Task 7: EF Core DbContext with Fluent API configuration (blocks on these entities)
|
||||
- Add [Timestamp] attribute to RowVersion properties
|
||||
- Configure ITenantEntity filtering in DbContext
|
||||
- Set up relationships between entities
|
||||
- Configure PostgreSQL xmin concurrency token
|
||||
|
||||
|
||||
Reference in New Issue
Block a user