2026-03-03 14:02:37 +01:00
|
|
|
# Learnings — Club Work Manager
|
|
|
|
|
|
|
|
|
|
_Conventions, patterns, and accumulated wisdom from task execution_
|
|
|
|
|
|
|
|
|
|
---
|
2026-03-03 14:07:29 +01:00
|
|
|
|
|
|
|
|
## Task 1: Monorepo Scaffolding (2026-03-03)
|
|
|
|
|
|
|
|
|
|
### Key Learnings
|
|
|
|
|
|
|
|
|
|
1. **.NET 10 Solution Format Change**
|
|
|
|
|
- .NET 10 uses `.slnx` format (not `.sln`)
|
|
|
|
|
- Solution files are still named `WorkClub.slnx`, compatible with `dotnet sln add`
|
|
|
|
|
- Both formats work seamlessly with build system
|
|
|
|
|
|
|
|
|
|
2. **Clean Architecture Implementation**
|
|
|
|
|
- Successfully established layered architecture with proper dependencies
|
|
|
|
|
- Api → (Application + Infrastructure) → Domain
|
|
|
|
|
- Tests reference all layers for comprehensive coverage
|
|
|
|
|
- Project references added via `dotnet add reference`
|
|
|
|
|
|
|
|
|
|
3. **NuGet Package Versioning**
|
|
|
|
|
- Finbuckle.MultiTenant: Specified 8.2.0 but .NET 10 SDK resolved to 9.0.0
|
|
|
|
|
- This is expected behavior with `rollForward: latestFeature` in global.json
|
|
|
|
|
- No build failures - warnings only about version resolution
|
|
|
|
|
- Testcontainers brings in BouncyCastle which has known security advisories (expected in test dependencies)
|
|
|
|
|
|
|
|
|
|
4. **Git Configuration for Automation**
|
|
|
|
|
- Set `user.email` and `user.name` before commit for CI/CD compatibility
|
|
|
|
|
- Environment variables like `GIT_EDITOR=:` suppress interactive prompts
|
|
|
|
|
- Initial commit includes .sisyphus directory (plans, notepads, etc.)
|
|
|
|
|
|
|
|
|
|
5. **Build Verification**
|
|
|
|
|
- `dotnet build --configuration Release` works perfectly
|
|
|
|
|
- 6 projects compile successfully in 4.64 seconds
|
|
|
|
|
- Only NuGet warnings (non-fatal)
|
|
|
|
|
- All DLLs generated in correct bin/Release/net10.0 directories
|
|
|
|
|
|
|
|
|
|
### Configuration Files Created
|
|
|
|
|
|
|
|
|
|
- **.gitignore**: Comprehensive coverage for:
|
|
|
|
|
- .NET: bin/, obj/, *.user, .vs/
|
|
|
|
|
- Node: node_modules/, .next/, .cache/
|
|
|
|
|
- IDE: .idea/, .vscode/, *.swp
|
|
|
|
|
|
|
|
|
|
- **.editorconfig**: C# conventions with:
|
|
|
|
|
- 4-space indentation for .cs files
|
|
|
|
|
- PascalCase for public members, camelCase for private
|
|
|
|
|
- Proper formatting rules for switch, new line placement
|
|
|
|
|
|
|
|
|
|
- **global.json**: SDK pinning with latestFeature rollForward for flexibility
|
|
|
|
|
|
|
|
|
|
### Project Template Choices
|
|
|
|
|
|
|
|
|
|
- Api: `dotnet new webapi` (includes Program.cs, appsettings.json, Controllers template)
|
|
|
|
|
- Application/Domain/Infrastructure: `dotnet new classlib` (clean base)
|
|
|
|
|
- Tests: `dotnet new xunit` (modern testing framework, includes base dependencies)
|
|
|
|
|
|
|
|
|
|
### Next Phase Considerations
|
|
|
|
|
|
|
|
|
|
- Generated Program.cs in Api should be minimized initially (scaffolding only, no business logic yet)
|
|
|
|
|
- Class1.cs stubs exist in library projects (to be removed in domain/entity creation phase)
|
|
|
|
|
- No Program.cs modifications yet - pure scaffolding as required
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Task 2: Docker Compose with PostgreSQL 16 & Keycloak 26.x (2026-03-03)
|
|
|
|
|
|
|
|
|
|
### Key Learnings
|
|
|
|
|
|
|
|
|
|
1. **Docker Compose v3.9 for Development**
|
|
|
|
|
- Uses explicit `app-network` bridge for service discovery
|
|
|
|
|
- Keycloak service depends on postgres with `condition: service_healthy` for ordered startup
|
|
|
|
|
- Health checks critical: PostgreSQL uses `pg_isready`, Keycloak uses `/health/ready` endpoint
|
|
|
|
|
|
|
|
|
|
2. **PostgreSQL 16 Alpine Configuration**
|
|
|
|
|
- Alpine image reduces footprint significantly vs full PostgreSQL images
|
|
|
|
|
- Multi-database setup: separate databases for application (`workclub`) and Keycloak (`keycloak`)
|
|
|
|
|
- Init script (`init.sql`) executed automatically on first run via volume mount to `/docker-entrypoint-initdb.d`
|
|
|
|
|
- Default PostgreSQL connection isolation: `read_committed` with max 200 connections configured
|
|
|
|
|
|
|
|
|
|
3. **Keycloak 26.x Setup**
|
|
|
|
|
- Image: `quay.io/keycloak/keycloak:26.1` from Red Hat's container registry
|
|
|
|
|
- Command: `start-dev --import-realm` (development mode with automatic realm import)
|
|
|
|
|
- Realm import directory: `/opt/keycloak/data/import` mounted from `./infra/keycloak`
|
|
|
|
|
- Database credentials: separate `keycloak` user with `keycloakpass` (not production-safe, dev only)
|
|
|
|
|
- Health check uses curl to `/health/ready` endpoint (startup probe: 30s initial wait, 30 retries)
|
|
|
|
|
|
|
|
|
|
4. **Volume Management**
|
|
|
|
|
- Named volume `postgres-data` for persistent PostgreSQL storage
|
|
|
|
|
- Bind mount `./infra/keycloak` to `/opt/keycloak/data/import` for realm configuration
|
|
|
|
|
- Bind mount `./infra/postgres` to `/docker-entrypoint-initdb.d` for database initialization
|
|
|
|
|
|
|
|
|
|
5. **Service Discovery & Networking**
|
|
|
|
|
- All services on `app-network` bridge network
|
|
|
|
|
- Service names act as hostnames: `postgres:5432` for PostgreSQL, `localhost:8080` for Keycloak UI
|
|
|
|
|
- JDBC connection string in Keycloak: `jdbc:postgresql://postgres:5432/keycloak`
|
|
|
|
|
|
|
|
|
|
6. **Development vs Production**
|
|
|
|
|
- This configuration is dev-only: hardcoded credentials, start-dev mode, default admin user
|
|
|
|
|
- Security note: Keycloak admin credentials (admin/admin) and PostgreSQL passwords visible in plain text
|
|
|
|
|
- No TLS/HTTPS, no resource limits, no restart policies beyond defaults
|
|
|
|
|
- Future: Task 22 will add backend/frontend services to this compose file
|
|
|
|
|
|
|
|
|
|
### Configuration Files Created
|
|
|
|
|
|
|
|
|
|
- **docker-compose.yml**: 68 lines, v3.9 format with postgres + keycloak services
|
|
|
|
|
- **infra/postgres/init.sql**: Database initialization for workclub and keycloak databases
|
|
|
|
|
- **infra/keycloak/realm-export.json**: Placeholder realm (will be populated by Task 3)
|
|
|
|
|
|
|
|
|
|
### Environment Constraints
|
|
|
|
|
|
|
|
|
|
- Docker Compose CLI plugin not available in development environment
|
|
|
|
|
- Configuration validated against v3.9 spec structure
|
|
|
|
|
- YAML syntax verified via grep pattern matching
|
|
|
|
|
- Full integration testing deferred to actual Docker deployment
|
|
|
|
|
|
|
|
|
|
### Patterns & Conventions
|
|
|
|
|
|
|
|
|
|
- Use Alpine Linux images for smaller container footprints
|
|
|
|
|
- Health checks with appropriate startup periods and retry counts
|
|
|
|
|
- Ordered service startup via `depends_on` with health conditions
|
|
|
|
|
- Named volumes for persistent state, bind mounts for configuration
|
|
|
|
|
- Separate database users and passwords even in development (easier to migrate to secure configs)
|
|
|
|
|
|
|
|
|
|
### Gotchas to Avoid
|
|
|
|
|
|
|
|
|
|
- Keycloak startup takes 20-30 seconds even in dev mode (don't reduce retries)
|
|
|
|
|
- `/health/ready` is not the same as `/health/live` (use ready for startup confirmation)
|
|
|
|
|
- PostgreSQL in Alpine doesn't include common extensions by default (not needed yet)
|
|
|
|
|
- Keycloak password encoding: stored hashed in PostgreSQL, admin creds only in environment
|
|
|
|
|
- Missing realm-export.json or empty directory causes Keycloak to start but import silently fails
|
|
|
|
|
|
|
|
|
|
### Next Dependencies
|
|
|
|
|
|
|
|
|
|
- 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
|
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)
2026-03-03 14:09:25 +01:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|