18 KiB
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:
- Production-Ready: Fully tested, multi-tenant secured, frontend integrated
- Clean Architecture: CQRS + DDD, proven architecture pattern
- Zero Migration Risk: Already working in production
- Time-Efficient: No need to rewrite existing functionality
- 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:
// 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:
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 issueDELETE /api/issues/{issueId}/remove-child/{childId}- Remove childGET /api/issues/{issueId}/children- Get all childrenGET /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:
- Mark ProjectManagement module as deprecated
- Add migration path documentation (if needed)
- 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:
- Epic → can have Story children only
- Story → can have Task/Bug children only
- Task → cannot have children (leaf node)
- Bug → can be child of Story, cannot have children
- Max depth: 3 levels (Epic → Story → Task)
- Circular dependency prevention (recursive check)
- 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_idfor 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_idcolumn toissuestable - 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:
IssueHierarchyChangedEventChildIssueAddedEventChildIssueRemovedEvent
- 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-childDELETE /api/issues/{id}/remove-child/{childId}GET /api/issues/{id}/childrenGET /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:
- Single source of truth (no duplication)
- Proven architecture (CQRS + DDD)
- Fully tested (100% multi-tenant isolation)
- Production-ready foundation
- Clean migration path (no breaking changes)
Risks Mitigated:
- No data migration needed
- No breaking changes to frontend
- No need to rewrite tests
- No performance regressions
Technical Specifications
Database Schema Change
-- 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
// 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
// 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)
-- 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
-
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
-
Day 15 (Tomorrow):
- Backend agent starts database migration
- Begin domain layer implementation
-
Day 16-17:
- Complete API implementation
- Integration testing
- Frontend integration
-
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