648 lines
18 KiB
Markdown
648 lines
18 KiB
Markdown
# ADR-035: Epic/Story/Task Architecture Decision
|
|
|
|
**Date**: 2025-11-04
|
|
**Status**: Accepted
|
|
**Decision Maker**: Product Manager + Architect
|
|
**Context**: M1 Architecture Clarification
|
|
|
|
---
|
|
|
|
## Context and Problem Statement
|
|
|
|
During Day 14 review, we discovered two different implementations for task management:
|
|
|
|
### Implementation 1: ProjectManagement Module (Exists but Incomplete)
|
|
**Location**: `colaflow-api/src/Modules/ProjectManagement/`
|
|
|
|
**Structure**:
|
|
```
|
|
Project
|
|
└─ Epic
|
|
└─ Story
|
|
└─ WorkTask
|
|
```
|
|
|
|
**Status**:
|
|
- Partial implementation exists
|
|
- Has Epic/Story/Task CRUD commands
|
|
- Has API controllers (EpicsController)
|
|
- No multi-tenant isolation verification
|
|
- No integration tests
|
|
- Not used by frontend
|
|
|
|
### Implementation 2: Issue Management Module (Day 13, Complete & Production-Ready)
|
|
**Location**: `colaflow-api/src/Modules/IssueManagement/`
|
|
|
|
**Structure**:
|
|
```
|
|
Issue (type: Story | Task | Bug | Epic)
|
|
- No parent-child hierarchy (flat structure)
|
|
- Type is just an enum property
|
|
```
|
|
|
|
**Status**:
|
|
- Complete implementation (59 files, 1630 lines)
|
|
- CQRS + DDD architecture
|
|
- Multi-tenant isolation: 100% verified (Day 14 CRITICAL fix)
|
|
- Integration tests: 8/8 passing
|
|
- Frontend integrated (Kanban board working)
|
|
- Production-ready
|
|
|
|
**Problem**: Which architecture should we use? Do we need to migrate or integrate?
|
|
|
|
---
|
|
|
|
## Decision
|
|
|
|
### Core Decision: Use Issue Management Module as Foundation
|
|
|
|
**We choose Implementation 2 (Issue Management Module) as the primary architecture.**
|
|
|
|
**Rationale**:
|
|
1. **Production-Ready**: Fully tested, multi-tenant secured, frontend integrated
|
|
2. **Clean Architecture**: CQRS + DDD, proven architecture pattern
|
|
3. **Zero Migration Risk**: Already working in production
|
|
4. **Time-Efficient**: No need to rewrite existing functionality
|
|
5. **Extensible**: Easy to add parent-child hierarchy as enhancement
|
|
|
|
---
|
|
|
|
## Architecture Decision
|
|
|
|
### Phase 1: Keep Issue Management Module (Current State)
|
|
**Timeline**: Already Complete (Day 13-14)
|
|
|
|
**What We Have**:
|
|
- Issue entity with IssueType enum (Story, Task, Bug, Epic)
|
|
- Full CRUD operations
|
|
- Kanban board integration
|
|
- Multi-tenant isolation
|
|
- Real-time updates (SignalR)
|
|
- Performance optimized (< 5ms queries)
|
|
|
|
**Status**: ✅ KEEP AS-IS
|
|
|
|
### Phase 2: Add Parent-Child Hierarchy (M1 Requirement)
|
|
**Timeline**: Day 15-17 (2-3 days)
|
|
|
|
**What to Add**:
|
|
```csharp
|
|
// Add to Issue entity
|
|
public class Issue : TenantEntity, IAggregateRoot
|
|
{
|
|
// Existing properties...
|
|
|
|
// NEW: Hierarchy support
|
|
public Guid? ParentIssueId { get; private set; }
|
|
public Issue? ParentIssue { get; private set; }
|
|
public List<Issue> ChildIssues { get; private set; } = new();
|
|
|
|
// Hierarchy rules
|
|
public void SetParent(Issue parent)
|
|
{
|
|
// Validation:
|
|
// - Epic can have Story children
|
|
// - Story can have Task children
|
|
// - Task cannot have children
|
|
// - Prevent circular dependencies
|
|
// - Enforce same tenant
|
|
}
|
|
}
|
|
```
|
|
|
|
**Database Migration**:
|
|
```sql
|
|
ALTER TABLE issues
|
|
ADD COLUMN parent_issue_id UUID NULL,
|
|
ADD CONSTRAINT fk_issues_parent
|
|
FOREIGN KEY (parent_issue_id) REFERENCES issues(id);
|
|
|
|
CREATE INDEX ix_issues_parent_issue_id
|
|
ON issues(parent_issue_id);
|
|
```
|
|
|
|
**New API Endpoints**:
|
|
- `POST /api/issues/{issueId}/add-child` - Add child issue
|
|
- `DELETE /api/issues/{issueId}/remove-child/{childId}` - Remove child
|
|
- `GET /api/issues/{issueId}/children` - Get all children
|
|
- `GET /api/issues/{issueId}/hierarchy` - Get full tree (recursive)
|
|
|
|
**Status**: ⏳ TO BE IMPLEMENTED (Day 15-17)
|
|
|
|
### Phase 3: Deprecate ProjectManagement Module (Future)
|
|
**Timeline**: Post-M1 (M2 or later)
|
|
|
|
**Actions**:
|
|
1. Mark ProjectManagement module as deprecated
|
|
2. Add migration path documentation (if needed)
|
|
3. Remove unused code in cleanup phase
|
|
|
|
**Reason**:
|
|
- No need to maintain two implementations
|
|
- Issue Management is more mature and tested
|
|
- Reduces codebase complexity
|
|
|
|
**Status**: 📋 PLANNED FOR M2
|
|
|
|
---
|
|
|
|
## Architecture Principles
|
|
|
|
### 1. Single Source of Truth
|
|
- **Issue Management Module** is the ONLY source for Epic/Story/Task data
|
|
- ProjectManagement module will NOT be used in M1
|
|
|
|
### 2. Hierarchy Rules (DDD Business Logic)
|
|
```
|
|
Epic (IssueType.Epic)
|
|
├─ Story (IssueType.Story)
|
|
│ ├─ Task (IssueType.Task)
|
|
│ └─ Task (IssueType.Task)
|
|
└─ Story (IssueType.Story)
|
|
|
|
Bug (IssueType.Bug)
|
|
- Can be standalone OR child of Story
|
|
- Cannot have children
|
|
```
|
|
|
|
**Validation Rules**:
|
|
1. Epic → can have Story children only
|
|
2. Story → can have Task/Bug children only
|
|
3. Task → cannot have children (leaf node)
|
|
4. Bug → can be child of Story, cannot have children
|
|
5. Max depth: 3 levels (Epic → Story → Task)
|
|
6. Circular dependency prevention (recursive check)
|
|
7. Same tenant enforcement (parent and child must share TenantId)
|
|
|
|
### 3. Performance Optimization
|
|
- Use PostgreSQL recursive CTEs for hierarchy queries
|
|
- Cache frequently accessed hierarchy trees (Redis)
|
|
- Limit depth to 3 levels (prevent infinite recursion)
|
|
- Index on `parent_issue_id` for fast lookups
|
|
|
|
### 4. Multi-Tenant Security
|
|
- All hierarchy queries filtered by TenantId
|
|
- Parent-child links cannot cross tenant boundaries
|
|
- EF Core Global Query Filters automatically applied
|
|
|
|
---
|
|
|
|
## Implementation Plan for Day 15-17
|
|
|
|
### Day 15: Database & Domain Layer (6-8 hours)
|
|
|
|
**Morning (3-4h): Database Design**
|
|
- [ ] Create migration: Add `parent_issue_id` column to `issues` table
|
|
- [ ] Add foreign key constraint: `fk_issues_parent`
|
|
- [ ] Add index: `ix_issues_parent_issue_id`
|
|
- [ ] Run migration on dev environment
|
|
- [ ] Verify backward compatibility (existing data unaffected)
|
|
|
|
**Afternoon (3-4h): Domain Logic**
|
|
- [ ] Update Issue entity: Add `ParentIssueId`, `ParentIssue`, `ChildIssues`
|
|
- [ ] Implement `SetParent(Issue parent)` method with validation
|
|
- [ ] Implement `RemoveParent()` method
|
|
- [ ] Add hierarchy validation rules (see above)
|
|
- [ ] Add domain events:
|
|
- `IssueHierarchyChangedEvent`
|
|
- `ChildIssueAddedEvent`
|
|
- `ChildIssueRemovedEvent`
|
|
- [ ] Unit tests for domain logic (10+ test cases)
|
|
|
|
### Day 16: Application & API Layer (6-8 hours)
|
|
|
|
**Morning (3-4h): Commands & Queries**
|
|
- [ ] Create `AddChildIssueCommand` (CQRS command)
|
|
- [ ] Create `RemoveChildIssueCommand`
|
|
- [ ] Create `GetIssueHierarchyQuery` (recursive query using CTE)
|
|
- [ ] Create `GetChildIssuesQuery`
|
|
- [ ] Implement command handlers with validation
|
|
- [ ] Add authorization checks (same tenant, permissions)
|
|
|
|
**Afternoon (3-4h): API Endpoints**
|
|
- [ ] Add endpoints to `IssuesController`:
|
|
- `POST /api/issues/{id}/add-child`
|
|
- `DELETE /api/issues/{id}/remove-child/{childId}`
|
|
- `GET /api/issues/{id}/children`
|
|
- `GET /api/issues/{id}/hierarchy`
|
|
- [ ] Swagger documentation for new endpoints
|
|
- [ ] SignalR notifications for hierarchy changes
|
|
|
|
### Day 17: Testing & Frontend Integration (4-6 hours)
|
|
|
|
**Morning (2-3h): Integration Tests**
|
|
- [ ] Test: Add child issue (Epic → Story)
|
|
- [ ] Test: Add grandchild (Story → Task)
|
|
- [ ] Test: Prevent invalid hierarchy (Task → Story)
|
|
- [ ] Test: Prevent circular dependency
|
|
- [ ] Test: Multi-tenant isolation (cannot link across tenants)
|
|
- [ ] Test: Cascade delete behavior
|
|
- [ ] Test: Query performance (< 50ms for 100+ issues)
|
|
|
|
**Afternoon (2-3h): Frontend Integration**
|
|
- [ ] Update Kanban board to show child issue count
|
|
- [ ] Add "Create Child Issue" button on Issue detail
|
|
- [ ] Display parent issue breadcrumb
|
|
- [ ] Update issue list to show hierarchy indicators
|
|
- [ ] Test real-time updates (SignalR)
|
|
|
|
---
|
|
|
|
## Answers to Original Questions
|
|
|
|
### Question 1: Architecture Relationship
|
|
**Answer**: **Option A** - Issue Management is the NEW architecture.
|
|
|
|
ProjectManagement module was an earlier incomplete attempt. Issue Management is the production implementation. We will enhance Issue Management with hierarchy support and deprecate ProjectManagement.
|
|
|
|
### Question 2: M1 Task Scope
|
|
**Answer**: **Option A** - Enhance Issue Management Module with hierarchy.
|
|
|
|
"Epic/Story Hierarchy" task in M1_REMAINING_TASKS.md means:
|
|
- Add parent-child relationship to Issue entity
|
|
- Implement hierarchy validation rules
|
|
- Add API endpoints for hierarchy management
|
|
- Update frontend to support hierarchy display
|
|
|
|
**NOT** Option B (create new module) or Option C (merge modules).
|
|
|
|
### Question 3: Multi-Tenant Isolation
|
|
**Answer**: Issue Management Module has 100% multi-tenant isolation.
|
|
|
|
**Verified on Day 14**:
|
|
- CRITICAL security fix implemented
|
|
- TenantContext service working correctly
|
|
- All 8/8 integration tests passing
|
|
- EF Core Global Query Filters verified
|
|
|
|
**For hierarchy feature**:
|
|
- Automatically inherits multi-tenant isolation
|
|
- Parent-child validation includes tenant check
|
|
- No additional work needed (already secured)
|
|
|
|
### Question 4: Frontend Integration
|
|
**Answer**: Frontend currently uses Issue Management API.
|
|
|
|
**Current State**:
|
|
- Kanban board uses: `GET /api/issues`, `PUT /api/issues/{id}/status`
|
|
- Issue creation uses: `POST /api/issues`
|
|
- Issue detail uses: `GET /api/issues/{id}`
|
|
|
|
**After Day 15-17**:
|
|
- Frontend will add hierarchy support using new endpoints
|
|
- No breaking changes to existing API
|
|
- Backward compatible (ParentIssueId is nullable)
|
|
|
|
---
|
|
|
|
## Impact Assessment
|
|
|
|
### On M1 Timeline
|
|
|
|
**Before This Decision**:
|
|
- Ambiguity about which module to use
|
|
- Risk of duplicate work
|
|
- Potential need to migrate data
|
|
- Estimated: 5-7 days of confusion + rework
|
|
|
|
**After This Decision**:
|
|
- Clear direction: Enhance Issue Management
|
|
- No migration needed
|
|
- Estimated: 2-3 days focused work
|
|
- **Time Saved**: 3-4 days
|
|
|
|
**M1 Completion Timeline**:
|
|
- Before: Uncertain (risk of slipping to 4+ weeks)
|
|
- After: **2-3 weeks confirmed** (on track for Nov 20)
|
|
|
|
### On Code Quality
|
|
|
|
**Benefits**:
|
|
1. Single source of truth (no duplication)
|
|
2. Proven architecture (CQRS + DDD)
|
|
3. Fully tested (100% multi-tenant isolation)
|
|
4. Production-ready foundation
|
|
5. Clean migration path (no breaking changes)
|
|
|
|
**Risks Mitigated**:
|
|
1. No data migration needed
|
|
2. No breaking changes to frontend
|
|
3. No need to rewrite tests
|
|
4. No performance regressions
|
|
|
|
---
|
|
|
|
## Technical Specifications
|
|
|
|
### Database Schema Change
|
|
|
|
```sql
|
|
-- Migration: 20251104_AddIssueHierarchy
|
|
|
|
ALTER TABLE issues
|
|
ADD COLUMN parent_issue_id UUID NULL;
|
|
|
|
ALTER TABLE issues
|
|
ADD CONSTRAINT fk_issues_parent
|
|
FOREIGN KEY (parent_issue_id)
|
|
REFERENCES issues(id)
|
|
ON DELETE SET NULL; -- When parent deleted, set child's parent to NULL
|
|
|
|
CREATE INDEX ix_issues_parent_issue_id
|
|
ON issues(parent_issue_id)
|
|
WHERE parent_issue_id IS NOT NULL; -- Partial index (PostgreSQL optimization)
|
|
|
|
-- Add check constraint for hierarchy rules
|
|
ALTER TABLE issues
|
|
ADD CONSTRAINT ck_issues_hierarchy_rules
|
|
CHECK (
|
|
-- Epic can have Story children only
|
|
(type = 'Epic' AND parent_issue_id IS NULL) OR
|
|
-- Story can have Task/Bug children or be child of Epic
|
|
(type = 'Story') OR
|
|
-- Task/Bug must be leaf nodes (no children)
|
|
(type IN ('Task', 'Bug'))
|
|
);
|
|
```
|
|
|
|
### Domain Model Changes
|
|
|
|
```csharp
|
|
// Issue.cs (Updated)
|
|
|
|
public class Issue : TenantEntity, IAggregateRoot
|
|
{
|
|
// Existing properties...
|
|
public IssueType Type { get; private set; }
|
|
public string Title { get; private set; }
|
|
public IssueStatus Status { get; private set; }
|
|
|
|
// NEW: Hierarchy support
|
|
public Guid? ParentIssueId { get; private set; }
|
|
public virtual Issue? ParentIssue { get; private set; }
|
|
public virtual ICollection<Issue> ChildIssues { get; private set; } = new List<Issue>();
|
|
|
|
// NEW: Hierarchy methods
|
|
public Result SetParent(Issue parent)
|
|
{
|
|
if (parent.TenantId != this.TenantId)
|
|
return Result.Failure("Cannot link issues across tenants");
|
|
|
|
if (!IsValidHierarchy(parent))
|
|
return Result.Failure($"{parent.Type} cannot be parent of {this.Type}");
|
|
|
|
if (WouldCreateCircularDependency(parent))
|
|
return Result.Failure("Circular dependency detected");
|
|
|
|
ParentIssueId = parent.Id;
|
|
ParentIssue = parent;
|
|
|
|
AddDomainEvent(new IssueHierarchyChangedEvent(this.Id, parent.Id));
|
|
return Result.Success();
|
|
}
|
|
|
|
public void RemoveParent()
|
|
{
|
|
if (ParentIssueId.HasValue)
|
|
{
|
|
var oldParentId = ParentIssueId.Value;
|
|
ParentIssueId = null;
|
|
ParentIssue = null;
|
|
AddDomainEvent(new IssueHierarchyChangedEvent(this.Id, null, oldParentId));
|
|
}
|
|
}
|
|
|
|
private bool IsValidHierarchy(Issue parent)
|
|
{
|
|
return (parent.Type, this.Type) switch
|
|
{
|
|
(IssueType.Epic, IssueType.Story) => true,
|
|
(IssueType.Story, IssueType.Task) => true,
|
|
(IssueType.Story, IssueType.Bug) => true,
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
private bool WouldCreateCircularDependency(Issue proposedParent)
|
|
{
|
|
var current = proposedParent;
|
|
int depth = 0;
|
|
|
|
while (current != null && depth < 10) // Safety limit
|
|
{
|
|
if (current.Id == this.Id)
|
|
return true; // Circular dependency detected
|
|
|
|
current = current.ParentIssue;
|
|
depth++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public int GetDepth()
|
|
{
|
|
int depth = 0;
|
|
var current = this.ParentIssue;
|
|
|
|
while (current != null && depth < 10)
|
|
{
|
|
depth++;
|
|
current = current.ParentIssue;
|
|
}
|
|
|
|
return depth;
|
|
}
|
|
}
|
|
```
|
|
|
|
### API Contract
|
|
|
|
```csharp
|
|
// POST /api/issues/{id}/add-child
|
|
public class AddChildIssueRequest
|
|
{
|
|
public Guid ChildIssueId { get; set; }
|
|
}
|
|
|
|
public class AddChildIssueResponse
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; }
|
|
public IssueDto Issue { get; set; }
|
|
}
|
|
|
|
// GET /api/issues/{id}/hierarchy
|
|
public class IssueHierarchyDto
|
|
{
|
|
public Guid Id { get; set; }
|
|
public string Title { get; set; }
|
|
public IssueType Type { get; set; }
|
|
public IssueStatus Status { get; set; }
|
|
public List<IssueHierarchyDto> Children { get; set; }
|
|
public int Depth { get; set; }
|
|
}
|
|
```
|
|
|
|
### Query Performance (CTE)
|
|
|
|
```sql
|
|
-- Get complete hierarchy tree
|
|
WITH RECURSIVE hierarchy AS (
|
|
-- Base case: Root issue
|
|
SELECT
|
|
id,
|
|
tenant_id,
|
|
parent_issue_id,
|
|
title,
|
|
type,
|
|
status,
|
|
0 AS depth
|
|
FROM issues
|
|
WHERE id = @rootIssueId
|
|
AND tenant_id = @tenantId
|
|
|
|
UNION ALL
|
|
|
|
-- Recursive case: Children
|
|
SELECT
|
|
i.id,
|
|
i.tenant_id,
|
|
i.parent_issue_id,
|
|
i.title,
|
|
i.type,
|
|
i.status,
|
|
h.depth + 1
|
|
FROM issues i
|
|
INNER JOIN hierarchy h ON i.parent_issue_id = h.id
|
|
WHERE i.tenant_id = @tenantId
|
|
AND h.depth < 3 -- Max depth limit
|
|
)
|
|
SELECT * FROM hierarchy
|
|
ORDER BY depth, title;
|
|
```
|
|
|
|
**Performance Target**: < 50ms for 100+ issues in tree
|
|
|
|
---
|
|
|
|
## Risks and Mitigations
|
|
|
|
### Risk 1: Performance Degradation
|
|
**Impact**: Medium
|
|
**Probability**: Low
|
|
**Mitigation**:
|
|
- Use CTE for recursive queries (PostgreSQL optimized)
|
|
- Add index on `parent_issue_id`
|
|
- Limit depth to 3 levels
|
|
- Cache frequently accessed trees (Redis)
|
|
- Performance test: 100+ issues scenario
|
|
|
|
### Risk 2: Data Integrity Issues
|
|
**Impact**: High
|
|
**Probability**: Low
|
|
**Mitigation**:
|
|
- Database foreign key constraints
|
|
- Domain validation rules (DDD)
|
|
- Transaction isolation
|
|
- Comprehensive integration tests (10+ scenarios)
|
|
- Circular dependency detection
|
|
|
|
### Risk 3: Frontend Breaking Changes
|
|
**Impact**: Low
|
|
**Probability**: Very Low
|
|
**Mitigation**:
|
|
- Backward compatible API (ParentIssueId nullable)
|
|
- Existing endpoints unchanged
|
|
- New endpoints additive only
|
|
- Frontend can adopt gradually
|
|
|
|
### Risk 4: Multi-Tenant Security Breach
|
|
**Impact**: Critical
|
|
**Probability**: Very Low (Already mitigated on Day 14)
|
|
**Mitigation**:
|
|
- Tenant validation in SetParent method
|
|
- EF Core Global Query Filters
|
|
- Integration tests for cross-tenant scenarios
|
|
- Code review by security-focused reviewer
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
### Functional Requirements
|
|
- [ ] Can create Epic → Story → Task hierarchy
|
|
- [ ] Can add/remove parent-child relationships via API
|
|
- [ ] Can query full hierarchy tree
|
|
- [ ] Hierarchy rules enforced (validation)
|
|
- [ ] Circular dependency prevention works
|
|
|
|
### Non-Functional Requirements
|
|
- [ ] Query performance < 50ms (100+ issues)
|
|
- [ ] Multi-tenant isolation 100% verified
|
|
- [ ] Backward compatible (no breaking changes)
|
|
- [ ] Integration tests pass rate ≥ 95%
|
|
- [ ] API response time < 100ms
|
|
|
|
### Documentation Requirements
|
|
- [ ] API documentation updated (Swagger)
|
|
- [ ] Database schema documented
|
|
- [ ] Frontend integration guide
|
|
- [ ] Migration guide (if needed)
|
|
|
|
---
|
|
|
|
## Approval and Sign-off
|
|
|
|
**Proposed By**: Product Manager Agent
|
|
**Date**: 2025-11-04
|
|
|
|
**Approved By**:
|
|
- [ ] Architect Agent - Architecture review
|
|
- [ ] Backend Agent - Implementation feasibility
|
|
- [ ] QA Agent - Testing strategy
|
|
- [ ] Main Coordinator - Project alignment
|
|
|
|
**Status**: AWAITING APPROVAL
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Immediate (Today, Day 14)**:
|
|
- Share this ADR with all agents for review
|
|
- Get approval from Architect and Backend agents
|
|
- Update M1_REMAINING_TASKS.md with clarified scope
|
|
|
|
2. **Day 15 (Tomorrow)**:
|
|
- Backend agent starts database migration
|
|
- Begin domain layer implementation
|
|
|
|
3. **Day 16-17**:
|
|
- Complete API implementation
|
|
- Integration testing
|
|
- Frontend integration
|
|
|
|
4. **Post-Implementation**:
|
|
- Mark ProjectManagement module as deprecated
|
|
- Document migration path (if external users exist)
|
|
- Plan cleanup for M2
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- Issue Management Module Implementation (Day 13)
|
|
- Multi-Tenant Security Fix (Day 14)
|
|
- product.md - Section 5: Core Modules
|
|
- M1_REMAINING_TASKS.md - Section 1.3: Epic/Story Hierarchy
|
|
- CQRS Pattern: https://martinfowler.com/bliki/CQRS.html
|
|
- DDD Aggregates: https://martinfowler.com/bliki/DDD_Aggregate.html
|
|
- PostgreSQL CTE: https://www.postgresql.org/docs/current/queries-with.html
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2025-11-04
|
|
**Next Review**: After Day 17 implementation
|