QualCoder: Functional Domain-Driven Design Architecture¶
Executive Summary¶
This document presents a comprehensive Functional Domain-Driven Design (fDDD) architecture for QualCoder, a qualitative data analysis application. The design emphasizes:
- Pure functions over stateful objects
- Immutable data structures for predictable behavior
- Explicit effects through a Functional Core / Imperative Shell architecture
- Type-driven development using Python's type system with runtime validation
- Event-driven communication between bounded contexts
Table of Contents¶
- Design Philosophy
- Strategic Design: Bounded Contexts
- Context Map
- Tactical Patterns: Functional Approach
- Bounded Context Deep Dives
- Event Catalog
- Cross-Cutting Concerns
- Data Flow Architecture
- Migration Strategy
1. Design Philosophy¶
1.1 Functional DDD Principles¶
graph TB
subgraph "Traditional OOP DDD"
A[Entity with Behavior] --> B[Mutable State]
B --> C[Side Effects Hidden]
end
subgraph "Functional DDD"
D[Entity as Data Type] --> E[Immutable Values]
E --> F[Pure Functions]
F --> G[Explicit Effects]
end
style D fill:#90EE90
style E fill:#90EE90
style F fill:#90EE90
style G fill:#90EE90
1.2 Core Tenets¶
| Principle | Description | QualCoder Application |
|---|---|---|
| Data as Types | Entities are type definitions with validation | Code, Segment, Case as frozen dataclasses |
| Behavior as Functions | Operations are pure functions over data | apply_code(), merge_codes() as standalone functions |
| Invariants as Predicates | Business rules as boolean-returning functions | is_code_name_unique(), is_hierarchy_acyclic() |
| Derivers as Decision Makers | Complex logic returns discriminated unions | derive_coding_event() returns Success | Failure |
| Controllers as Coordinators | Async/IO operations in imperative shell | CodingController handles DB and UI coordination |
1.3 Architecture Layers¶
graph TB
subgraph "Imperative Shell"
UI[PyQt6 UI Layer]
CTRL[Controllers]
REPO[Repositories]
DB[(SQLite)]
end
subgraph "Functional Core"
ENT[Entities / Value Objects]
INV[Invariants]
DER[Derivers]
EVT[Domain Events]
end
UI --> CTRL
CTRL --> DER
CTRL --> REPO
DER --> INV
DER --> ENT
DER --> EVT
REPO --> DB
style ENT fill:#E6F3FF
style INV fill:#E6F3FF
style DER fill:#E6F3FF
style EVT fill:#E6F3FF
Functional Core: Pure, testable, no side effects Imperative Shell: Handles I/O, database, UI interactions
2. Strategic Design: Bounded Contexts¶
2.1 Context Overview¶
graph TB
subgraph "Core Domain"
CODING[Coding Context]
ANALYSIS[Analysis Context]
end
subgraph "Supporting Domain"
PROJECT[Project Context]
SOURCE[Source Management Context]
CASE[Case Context]
COLLAB[Collaboration Context]
end
subgraph "Generic Domain"
AI[AI Services Context]
EXPORT[Export Context]
end
PROJECT --> CODING
SOURCE --> CODING
CODING --> ANALYSIS
CODING --> CASE
COLLAB --> PROJECT
AI --> CODING
AI --> ANALYSIS
ANALYSIS --> EXPORT
style CODING fill:#FFD700
style ANALYSIS fill:#FFD700
2.2 Context Responsibilities¶
| Context | Type | Responsibility | Key Aggregates |
|---|---|---|---|
| Coding | Core | Apply semantic codes to data segments | Code, Segment |
| Analysis | Core | Generate insights from coded data | Report, Matrix |
| Project | Supporting | Lifecycle management of research projects | Project |
| Source Management | Supporting | Import, store, and manage research data | Source |
| Case | Supporting | Group segments into research units | Case |
| Collaboration | Supporting | Multi-coder workflows and merging | Coder, MergeOperation |
| AI Services | Generic | LLM integration and semantic search | AISession, VectorStore |
| Export | Generic | Output generation (REFI-QDA, XLSX) | ExportJob |
3. Context Map¶
3.1 Integration Patterns¶
graph LR
subgraph "Upstream"
PROJECT[Project Context]
SOURCE[Source Context]
end
subgraph "Core"
CODING[Coding Context]
end
subgraph "Downstream"
ANALYSIS[Analysis Context]
CASE[Case Context]
EXPORT[Export Context]
end
PROJECT -->|"Conformist"| CODING
SOURCE -->|"Open Host Service"| CODING
CODING -->|"Published Language"| ANALYSIS
CODING -->|"Published Language"| CASE
CODING -->|"Customer/Supplier"| EXPORT
AI[AI Context] -.->|"Partnership"| CODING
AI -.->|"Partnership"| ANALYSIS
COLLAB[Collaboration] -->|"Anti-Corruption Layer"| PROJECT
3.2 Integration Relationships¶
| Upstream | Downstream | Pattern | Description |
|---|---|---|---|
| Project | Coding | Conformist | Coding accepts Project's model |
| Source | Coding | Open Host Service | Source exposes standard API for segment references |
| Coding | Analysis | Published Language | Events define the contract |
| Coding | Case | Published Language | Segment references are immutable |
| Collaboration | Project | Anti-Corruption Layer | Merge logic translates external formats |
| AI | Coding/Analysis | Partnership | Bidirectional collaboration on embeddings |
4. Tactical Patterns: Functional Approach¶
4.1 Entity Pattern¶
Entities are immutable data containers with identity, defined using frozen dataclasses with runtime validation via Pydantic.
classDiagram
class Entity {
<<abstract>>
+id: EntityId
+created_at: datetime
+updated_at: datetime
}
class Code {
+code_id: CodeId
+name: str
+color: Color
+memo: Optional~str~
+category_id: Optional~CategoryId~
+owner: CoderId
}
class TextSegment {
+segment_id: SegmentId
+source_id: SourceId
+code_id: CodeId
+start_pos: int
+end_pos: int
+selected_text: str
+importance: int
+memo: Optional~str~
+owner: CoderId
}
Entity <|-- Code
Entity <|-- TextSegment
4.2 Value Object Pattern¶
Value Objects are immutable and compared by value, not identity.
classDiagram
class Color {
+red: int
+green: int
+blue: int
+to_hex() str
+contrast_color() Color
}
class TextPosition {
+start: int
+end: int
+length() int
+overlaps(other) bool
+contains(other) bool
}
class ImageRegion {
+x: int
+y: int
+width: int
+height: int
+area() int
+intersects(other) bool
}
class TimeRange {
+start_ms: int
+end_ms: int
+duration_ms() int
+overlaps(other) bool
}
4.3 Invariant Pattern¶
Invariants are pure predicate functions that validate business rules.
flowchart LR
subgraph "Invariant Function"
INPUT[Entity or Value] --> CHECK{Business Rule}
CHECK -->|Valid| TRUE[True]
CHECK -->|Invalid| FALSE[False]
end
Invariant Catalog for Coding Context:
| Invariant | Input | Rule |
|---|---|---|
is_code_name_unique |
Code, existing_codes | No duplicate names in project |
is_hierarchy_acyclic |
Category, all_categories | No circular parent references |
is_segment_within_bounds |
TextSegment, Source | Position within source length |
is_valid_overlap |
Segment, existing_segments | Overlaps are tracked, not prevented |
can_code_be_deleted |
Code, segments | No segments reference this code |
is_color_valid |
Color | RGB values 0-255 |
4.4 Deriver Pattern¶
Derivers are pure functions that: 1. Accept entities and context as input 2. Compose multiple invariants 3. Return a discriminated union (Result type) of success or failure
flowchart TB
subgraph "Deriver: derive_apply_code_event"
INPUT[Command Data + State]
INV1{is_code_exists?}
INV2{is_source_exists?}
INV3{is_segment_valid?}
INV4{is_user_authorized?}
INPUT --> INV1
INV1 -->|No| FAIL1[CodeNotFound]
INV1 -->|Yes| INV2
INV2 -->|No| FAIL2[SourceNotFound]
INV2 -->|Yes| INV3
INV3 -->|No| FAIL3[InvalidSegment]
INV3 -->|Yes| INV4
INV4 -->|No| FAIL4[Unauthorized]
INV4 -->|Yes| SUCCESS[SegmentCoded Event]
end
style SUCCESS fill:#90EE90
style FAIL1 fill:#FFB6C1
style FAIL2 fill:#FFB6C1
style FAIL3 fill:#FFB6C1
style FAIL4 fill:#FFB6C1
Result Type Pattern:
Result[T, E] = Success[T] | Failure[E]
where:
T = Domain Event (success case)
E = Failure Reason (discriminated union of error types)
4.5 Controller Pattern¶
Controllers live in the Imperative Shell and coordinate: - Reading state from repositories - Calling derivers with state - Persisting changes based on events - Publishing events to the event bus
sequenceDiagram
participant UI as PyQt6 UI
participant CTRL as Controller
participant REPO as Repository
participant DER as Deriver
participant BUS as Event Bus
participant DB as SQLite
UI->>CTRL: apply_code(command_data)
CTRL->>REPO: get_code(code_id)
REPO->>DB: SELECT * FROM code_name
DB-->>REPO: code_row
REPO-->>CTRL: Code entity
CTRL->>REPO: get_source(source_id)
REPO-->>CTRL: Source entity
CTRL->>DER: derive_apply_code_event(data, state)
DER-->>CTRL: Result[SegmentCoded, Failure]
alt Success
CTRL->>REPO: save_segment(segment)
REPO->>DB: INSERT INTO code_text
CTRL->>BUS: publish(SegmentCoded)
CTRL-->>UI: Success response
else Failure
CTRL-->>UI: Error response
end
4.6 Policy Pattern¶
Policies are reactive event handlers that trigger cross-aggregate or cross-context actions.
flowchart LR
subgraph "Event Source"
EVT1[CodeDeleted Event]
end
subgraph "Policy: on_code_deleted"
LISTEN[Listen for CodeDeleted]
ACTION1[Remove orphaned segments]
ACTION2[Update category counts]
ACTION3[Invalidate AI embeddings]
end
EVT1 --> LISTEN
LISTEN --> ACTION1
LISTEN --> ACTION2
LISTEN --> ACTION3
5. Bounded Context Deep Dives¶
5.1 Coding Context (Core Domain)¶
The heart of QualCoder - applying semantic codes to research data.
5.1.1 Aggregate Structure¶
classDiagram
class Code {
<<Aggregate Root>>
+code_id: CodeId
+name: str
+color: Color
+memo: Optional~str~
+category_id: Optional~CategoryId~
+owner: CoderId
+created_at: datetime
}
class Category {
<<Aggregate Root>>
+category_id: CategoryId
+name: str
+parent_id: Optional~CategoryId~
+memo: Optional~str~
+owner: CoderId
}
class Segment {
<<Aggregate Root>>
+segment_id: SegmentId
+code_id: CodeId
+source_id: SourceId
+owner: CoderId
+memo: Optional~str~
+importance: int
}
class TextSegment {
+position: TextPosition
+selected_text: str
}
class ImageSegment {
+region: ImageRegion
}
class AVSegment {
+time_range: TimeRange
+transcript: Optional~str~
}
Segment <|-- TextSegment
Segment <|-- ImageSegment
Segment <|-- AVSegment
Code "1" --> "*" Segment : coded by
Category "1" --> "*" Code : contains
Category "0..1" --> "*" Category : parent of
5.1.2 Commands and Events¶
flowchart TB
subgraph "Commands"
C1[CreateCode]
C2[RenameCode]
C3[ChangeCodeColor]
C4[DeleteCode]
C5[MergeCodes]
C6[ApplyCodeToSegment]
C7[RemoveCodeFromSegment]
C8[MoveCodeToCategory]
C9[CreateCategory]
C10[DeleteCategory]
end
subgraph "Success Events"
E1[CodeCreated]
E2[CodeRenamed]
E3[CodeColorChanged]
E4[CodeDeleted]
E5[CodesMerged]
E6[SegmentCoded]
E7[SegmentUncoded]
E8[CodeMovedToCategory]
E9[CategoryCreated]
E10[CategoryDeleted]
end
subgraph "Failure Events"
F1[CodeNotCreated/NameExists]
F2[CodeNotRenamed/NameExists]
F3[CodeNotDeleted/HasSegments]
F4[SegmentNotCoded/InvalidPosition]
F5[CategoryNotDeleted/HasChildren]
end
C1 --> E1
C1 --> F1
C2 --> E2
C2 --> F2
C4 --> E4
C4 --> F3
C6 --> E6
C6 --> F4
C10 --> E10
C10 --> F5
5.1.3 Invariants¶
| Invariant | Parameters | Business Rule |
|---|---|---|
is_code_name_unique |
name, project_codes | No two codes share the same name |
is_category_name_unique |
name, project_categories | No two categories share the same name |
is_hierarchy_acyclic |
category, parent_id, all_categories | Moving category doesn't create cycle |
is_segment_position_valid |
position, source | Start < end, both within source bounds |
is_code_deletable |
code_id, segments | No segments reference this code |
is_category_deletable |
category_id, codes, subcategories | No codes or children reference category |
are_codes_mergeable |
source_code, target_code | Both exist, different codes |
5.1.4 Deriver Flows¶
Apply Code to Text Segment:
stateDiagram-v2
[*] --> ValidateCode
ValidateCode --> CodeNotFound: Code doesn't exist
ValidateCode --> ValidateSource: Code exists
ValidateSource --> SourceNotFound: Source doesn't exist
ValidateSource --> ValidatePosition: Source exists
ValidatePosition --> InvalidPosition: Out of bounds
ValidatePosition --> CheckOverlaps: Valid position
CheckOverlaps --> CreateSegment: No conflicts
CreateSegment --> SegmentCoded: Success
CodeNotFound --> [*]
SourceNotFound --> [*]
InvalidPosition --> [*]
SegmentCoded --> [*]
Merge Codes:
stateDiagram-v2
[*] --> ValidateSourceCode
ValidateSourceCode --> SourceCodeNotFound: Doesn't exist
ValidateSourceCode --> ValidateTargetCode: Exists
ValidateTargetCode --> TargetCodeNotFound: Doesn't exist
ValidateTargetCode --> CheckSameCode: Exists
CheckSameCode --> CannotMergeSame: Same code
CheckSameCode --> LoadSegments: Different codes
LoadSegments --> ReassignSegments
ReassignSegments --> DeleteSourceCode
DeleteSourceCode --> CodesMerged: Success
SourceCodeNotFound --> [*]
TargetCodeNotFound --> [*]
CannotMergeSame --> [*]
CodesMerged --> [*]
5.2 Source Management Context¶
Manages research data files and their content.
5.2.1 Aggregate Structure¶
classDiagram
class Source {
<<Aggregate Root>>
+source_id: SourceId
+name: str
+memo: Optional~str~
+owner: CoderId
+created_at: datetime
}
class TextSource {
+fulltext: str
+char_count: int
}
class ImageSource {
+media_path: str
+width: int
+height: int
}
class AudioSource {
+media_path: str
+duration_ms: int
+transcript: Optional~str~
}
class VideoSource {
+media_path: str
+duration_ms: int
+transcript: Optional~str~
+width: int
+height: int
}
class PDFSource {
+media_path: str
+page_count: int
+extracted_text: str
}
Source <|-- TextSource
Source <|-- ImageSource
Source <|-- AudioSource
Source <|-- VideoSource
Source <|-- PDFSource
5.2.2 Commands and Events¶
| Command | Success Event | Failure Events |
|---|---|---|
| ImportSource | SourceImported | ImportFailed/InvalidFormat, ImportFailed/FileTooLarge |
| RenameSource | SourceRenamed | SourceNotRenamed/NameExists |
| DeleteSource | SourceDeleted | SourceNotDeleted/HasSegments |
| ExtractText | TextExtracted | ExtractionFailed |
| UpdateTranscript | TranscriptUpdated | - |
5.3 Case Context¶
Groups coded segments into research units (participants, events, etc.).
5.3.1 Aggregate Structure¶
classDiagram
class Case {
<<Aggregate Root>>
+case_id: CaseId
+name: str
+memo: Optional~str~
+owner: CoderId
+created_at: datetime
}
class CaseMembership {
<<Value Object>>
+case_id: CaseId
+source_id: SourceId
+position: Optional~TextPosition~
+owner: CoderId
}
class CaseAttribute {
<<Value Object>>
+case_id: CaseId
+attribute_type: AttributeType
+value: AttributeValue
}
class AttributeType {
<<Value Object>>
+name: str
+value_type: character | numeric | date
}
Case "1" --> "*" CaseMembership : has members
Case "1" --> "*" CaseAttribute : has attributes
5.3.2 Commands and Events¶
flowchart LR
subgraph Commands
C1[CreateCase]
C2[RenameCase]
C3[DeleteCase]
C4[AddSourceToCase]
C5[AddSegmentToCase]
C6[RemoveMemberFromCase]
C7[SetCaseAttribute]
end
subgraph Events
E1[CaseCreated]
E2[CaseRenamed]
E3[CaseDeleted]
E4[SourceAddedToCase]
E5[SegmentAddedToCase]
E6[MemberRemovedFromCase]
E7[CaseAttributeSet]
end
C1 --> E1
C2 --> E2
C3 --> E3
C4 --> E4
C5 --> E5
C6 --> E6
C7 --> E7
5.4 Analysis Context¶
Generates insights and reports from coded data.
5.4.1 Aggregate Structure¶
classDiagram
class Report {
<<Aggregate Root>>
+report_id: ReportId
+report_type: ReportType
+filters: ReportFilters
+generated_at: datetime
+owner: CoderId
}
class ReportFilters {
<<Value Object>>
+code_ids: Optional~List~CodeId~~
+source_ids: Optional~List~SourceId~~
+case_ids: Optional~List~CaseId~~
+coder_ids: Optional~List~CoderId~~
+date_range: Optional~DateRange~
}
class FrequencyReport {
+results: List~CodeFrequency~
}
class CooccurrenceReport {
+matrix: CooccurrenceMatrix
}
class CoderComparisonReport {
+agreements: List~Agreement~
+disagreements: List~Disagreement~
+kappa_score: float
}
class CodeFrequency {
<<Value Object>>
+code_id: CodeId
+count: int
+percentage: float
+sources: List~SourceId~
}
Report <|-- FrequencyReport
Report <|-- CooccurrenceReport
Report <|-- CoderComparisonReport
Report --> ReportFilters
FrequencyReport --> CodeFrequency
5.4.2 Report Generation Flow¶
sequenceDiagram
participant UI as Analysis UI
participant CTRL as AnalysisController
participant DER as ReportDeriver
participant REPO as CodingRepository
participant GEN as ReportGenerator
UI->>CTRL: generate_frequency_report(filters)
CTRL->>REPO: get_segments_by_filter(filters)
REPO-->>CTRL: List[Segment]
CTRL->>REPO: get_codes()
REPO-->>CTRL: List[Code]
CTRL->>DER: derive_frequency_report(segments, codes, filters)
Note over DER: Pure calculation<br/>No side effects
DER-->>CTRL: FrequencyReport
CTRL->>GEN: render_report(report, format)
GEN-->>CTRL: rendered_output
CTRL-->>UI: ReportGenerated event + output
5.5 Project Context¶
Manages research project lifecycle.
5.5.1 Aggregate Structure¶
classDiagram
class Project {
<<Aggregate Root>>
+project_id: ProjectId
+name: str
+database_path: Path
+version: int
+memo: Optional~str~
+about: Optional~str~
+created_at: datetime
+owner: CoderId
}
class ProjectSettings {
<<Value Object>>
+language: str
+font_size: int
+backup_enabled: bool
+ai_model: Optional~str~
+recent_files: List~Path~
}
class ProjectLock {
<<Value Object>>
+locked_by: str
+locked_at: datetime
+heartbeat: datetime
}
Project --> ProjectSettings
Project --> ProjectLock
5.5.2 Project Lifecycle¶
stateDiagram-v2
[*] --> Closed
Closed --> Creating: create_project
Creating --> Open: ProjectCreated
Creating --> Closed: CreationFailed
Closed --> Opening: open_project
Opening --> CheckLock: Load database
CheckLock --> Locked: Another user has lock
CheckLock --> AcquireLock: No lock
AcquireLock --> Open: Lock acquired
Locked --> Closed: Cannot open
Open --> Saving: save_project
Saving --> Open: ProjectSaved
Open --> Closing: close_project
Closing --> ReleaseLock: Release lock
ReleaseLock --> Closed: ProjectClosed
Open --> Backing: backup_project
Backing --> Open: BackupCreated
5.6 Collaboration Context¶
Handles multi-coder workflows and project merging.
5.6.1 Aggregate Structure¶
classDiagram
class Coder {
<<Aggregate Root>>
+coder_id: CoderId
+name: str
+visibility: bool
}
class MergeOperation {
<<Aggregate Root>>
+merge_id: MergeId
+source_project: Path
+target_project: Path
+status: MergeStatus
+conflicts: List~Conflict~
+resolutions: List~Resolution~
+started_at: datetime
+completed_at: Optional~datetime~
}
class Conflict {
<<Value Object>>
+conflict_type: code | category | attribute
+source_item: str
+target_item: str
+description: str
}
class Resolution {
<<Value Object>>
+conflict: Conflict
+strategy: keep_source | keep_target | rename | merge
+new_name: Optional~str~
}
MergeOperation --> "*" Conflict
MergeOperation --> "*" Resolution
5.6.2 Merge Workflow¶
flowchart TB
START[Start Merge] --> BACKUP[Create Target Backup]
BACKUP --> LOAD[Load Source Project]
LOAD --> DETECT[Detect Conflicts]
DETECT --> CODES{Code Conflicts?}
CODES -->|Yes| RESOLVE_CODES[Queue Code Conflicts]
CODES -->|No| CATS
RESOLVE_CODES --> CATS
CATS{Category Conflicts?}
CATS -->|Yes| RESOLVE_CATS[Queue Category Conflicts]
CATS -->|No| ATTRS
RESOLVE_CATS --> ATTRS
ATTRS{Attribute Conflicts?}
ATTRS -->|Yes| RESOLVE_ATTRS[Queue Attribute Conflicts]
ATTRS -->|No| USER_REVIEW
RESOLVE_ATTRS --> USER_REVIEW
USER_REVIEW[Present Conflicts to User]
USER_REVIEW --> APPLY[Apply Resolutions]
APPLY --> IMPORT[Import Non-Conflicting Items]
IMPORT --> VERIFY[Verify Integrity]
VERIFY --> COMPLETE[MergeCompleted]
VERIFY -->|Failed| ROLLBACK[Restore Backup]
ROLLBACK --> FAILED[MergeFailed]
5.7 AI Services Context¶
Provides LLM-powered analysis and semantic search.
5.7.1 Aggregate Structure¶
classDiagram
class AISession {
<<Aggregate Root>>
+session_id: SessionId
+model_config: AIModelConfig
+messages: List~ChatMessage~
+created_at: datetime
}
class AIModelConfig {
<<Value Object>>
+provider: openai | blablador | local
+model_name: str
+api_key: Optional~str~
+temperature: float
+max_tokens: int
}
class ChatMessage {
<<Value Object>>
+role: user | assistant | system
+content: str
+timestamp: datetime
}
class VectorStore {
<<Aggregate Root>>
+store_id: StoreId
+project_id: ProjectId
+embedding_model: str
+indexed_segments: int
+last_updated: datetime
}
class SegmentEmbedding {
<<Value Object>>
+segment_id: SegmentId
+embedding: List~float~
+text_content: str
}
AISession --> AIModelConfig
AISession --> "*" ChatMessage
VectorStore --> "*" SegmentEmbedding
5.7.2 AI Query Flow¶
sequenceDiagram
participant UI as AI Chat UI
participant CTRL as AIController
participant VS as VectorStore
participant LLM as LLM Provider
participant REPO as CodingRepository
UI->>CTRL: ask_question(query)
CTRL->>VS: semantic_search(query, k=10)
VS-->>CTRL: List[SegmentEmbedding]
CTRL->>REPO: get_segments(segment_ids)
REPO-->>CTRL: List[Segment] with context
Note over CTRL: Build prompt with<br/>retrieved context
CTRL->>LLM: complete(prompt)
LLM-->>CTRL: response
CTRL->>CTRL: save_message(query, response)
CTRL-->>UI: AIResponseGenerated
6. Event Catalog¶
6.1 Event Categories¶
graph TB
subgraph "Project Events"
PE1[ProjectCreated]
PE2[ProjectOpened]
PE3[ProjectClosed]
PE4[ProjectBackedUp]
end
subgraph "Source Events"
SE1[SourceImported]
SE2[SourceRenamed]
SE3[SourceDeleted]
SE4[TextExtracted]
end
subgraph "Coding Events"
CE1[CodeCreated]
CE2[CodeRenamed]
CE3[CodeDeleted]
CE4[CodesMerged]
CE5[SegmentCoded]
CE6[SegmentUncoded]
CE7[CategoryCreated]
CE8[CategoryDeleted]
end
subgraph "Case Events"
CSE1[CaseCreated]
CSE2[CaseDeleted]
CSE3[MemberAddedToCase]
CSE4[AttributeSet]
end
subgraph "Analysis Events"
AE1[ReportGenerated]
AE2[ReportExported]
end
subgraph "Collaboration Events"
COE1[MergeStarted]
COE2[ConflictDetected]
COE3[ConflictResolved]
COE4[MergeCompleted]
end
6.2 Event Structure¶
All events follow a consistent structure:
DomainEvent:
+event_id: UUID
+event_type: str
+aggregate_type: str
+aggregate_id: str
+occurred_at: datetime
+caused_by: Optional[CoderId]
+payload: Dict[str, Any]
+metadata: Dict[str, Any]
6.3 Event Flow Between Contexts¶
flowchart LR
subgraph "Coding Context"
CE[CodeDeleted]
end
subgraph "Event Bus"
BUS((Event Bus))
end
subgraph "Consumers"
CASE[Case Context]
AI[AI Context]
ANALYSIS[Analysis Context]
end
CE --> BUS
BUS --> CASE
BUS --> AI
BUS --> ANALYSIS
CASE --> |"Remove orphaned<br/>case memberships"| CASE
AI --> |"Remove embeddings<br/>for deleted code"| AI
ANALYSIS --> |"Invalidate cached<br/>reports"| ANALYSIS
7. Cross-Cutting Concerns¶
7.1 Error Handling Strategy¶
flowchart TB
subgraph "Functional Core"
DERIVER[Deriver Function]
DERIVER --> RESULT{Result Type}
RESULT --> SUCCESS[Success + Event]
RESULT --> FAILURE[Failure + Reason]
end
subgraph "Imperative Shell"
CTRL[Controller]
SUCCESS --> PERSIST[Persist Changes]
PERSIST --> PUBLISH[Publish Event]
PUBLISH --> UI_SUCCESS[Update UI - Success]
FAILURE --> LOG[Log Failure]
LOG --> UI_FAILURE[Update UI - Error]
end
Failure Reason Types:
| Context | Failure Reasons |
|---|---|
| Coding | CodeNotFound, SourceNotFound, InvalidPosition, DuplicateName, CyclicHierarchy |
| Source | InvalidFormat, FileTooLarge, FileNotFound, ExtractionFailed |
| Case | CaseNotFound, DuplicateName, InvalidAttribute |
| Project | DatabaseCorrupted, VersionMismatch, LockConflict |
| Collaboration | MergeConflict, IncompatibleVersions |
7.2 Audit Trail¶
Every state change is tracked through events, enabling:
flowchart LR
subgraph "Event Store"
E1[Event 1] --> E2[Event 2] --> E3[Event 3] --> E4[Event N]
end
subgraph "Capabilities"
AUDIT[Audit Log]
UNDO[Undo/Redo]
REPLAY[State Replay]
DEBUG[Debugging]
end
E4 --> AUDIT
E4 --> UNDO
E4 --> REPLAY
E4 --> DEBUG
7.3 Validation Layers¶
flowchart TB
INPUT[User Input]
subgraph "Layer 1: Structural Validation"
PYDANTIC[Pydantic Models]
INPUT --> PYDANTIC
PYDANTIC -->|Invalid| REJECT1[Reject: Invalid Format]
PYDANTIC -->|Valid| LAYER2
end
subgraph "Layer 2: Domain Invariants"
LAYER2[Invariant Functions]
LAYER2 -->|Violated| REJECT2[Reject: Business Rule]
LAYER2 -->|Passed| LAYER3
end
subgraph "Layer 3: Cross-Aggregate Rules"
LAYER3[Deriver Logic]
LAYER3 -->|Failed| REJECT3[Reject: Consistency]
LAYER3 -->|Passed| ACCEPT[Accept: Create Event]
end
8. Data Flow Architecture¶
8.1 Complete Request Flow¶
flowchart TB
subgraph "Presentation Layer"
UI[PyQt6 Dialog]
UI --> |User Action| CMD[Command DTO]
end
subgraph "Application Layer"
CTRL[Controller]
CMD --> CTRL
CTRL --> |Fetch State| REPO
CTRL --> |Call| DERIVER
end
subgraph "Domain Layer - Functional Core"
DERIVER[Deriver]
INV[Invariants]
ENT[Entities]
EVT[Domain Event]
DERIVER --> INV
DERIVER --> ENT
DERIVER --> |Return| EVT
end
subgraph "Infrastructure Layer"
REPO[Repository]
DB[(SQLite)]
BUS[Event Bus]
REPO --> DB
CTRL --> |Persist| REPO
CTRL --> |Publish| BUS
end
BUS --> |Notify| POLICY[Policies]
POLICY --> |Trigger| CTRL
CTRL --> |Response| UI
8.2 Read vs Write Paths (CQRS-lite)¶
flowchart LR
subgraph "Write Path"
W_CMD[Command] --> W_CTRL[Controller]
W_CTRL --> W_DER[Deriver]
W_DER --> W_EVT[Event]
W_EVT --> W_REPO[Repository]
W_REPO --> W_DB[(Write DB)]
end
subgraph "Read Path"
R_QUERY[Query] --> R_REPO[Query Repository]
R_REPO --> R_DB[(Read Views)]
R_DB --> R_DTO[Read DTO]
end
W_DB -.-> |Sync| R_DB
9. Migration Strategy¶
9.1 Phased Approach¶
gantt
title Functional DDD Migration Phases
dateFormat YYYY-MM-DD
section Phase 1
Define Entities & Value Objects :p1, 2024-01-01, 30d
Implement Invariants :p2, after p1, 20d
section Phase 2
Build Derivers :p3, after p2, 40d
Create Event Types :p4, after p2, 20d
section Phase 3
Refactor Controllers :p5, after p3, 30d
Implement Event Bus :p6, after p4, 20d
section Phase 4
Add Policies :p7, after p5, 20d
Integration Testing :p8, after p7, 30d
section Phase 5
UI Integration :p9, after p8, 40d
Performance Optimization :p10, after p9, 20d
9.2 Migration by Context Priority¶
| Priority | Context | Rationale |
|---|---|---|
| 1 | Coding | Core domain, highest complexity, most business rules |
| 2 | Source Management | Foundation for coding, relatively isolated |
| 3 | Case | Depends on coding, moderate complexity |
| 4 | Project | Cross-cutting but stable |
| 5 | Analysis | Read-heavy, can use existing data layer |
| 6 | Collaboration | Complex but infrequent usage |
| 7 | AI Services | Already somewhat isolated |
9.3 Strangler Fig Pattern¶
flowchart TB
subgraph "Current System"
OLD_UI[Existing PyQt6 UI]
OLD_LOGIC[Monolithic Logic]
OLD_DB[(SQLite)]
end
subgraph "New fDDD System"
NEW_CTRL[New Controllers]
NEW_CORE[Functional Core]
NEW_REPO[New Repositories]
end
subgraph "Facade"
ROUTER{Feature Router}
end
OLD_UI --> ROUTER
ROUTER -->|Legacy Features| OLD_LOGIC
ROUTER -->|Migrated Features| NEW_CTRL
OLD_LOGIC --> OLD_DB
NEW_CTRL --> NEW_CORE
NEW_CTRL --> NEW_REPO
NEW_REPO --> OLD_DB
style NEW_CTRL fill:#90EE90
style NEW_CORE fill:#90EE90
style NEW_REPO fill:#90EE90
Appendix A: Glossary¶
| Term | Definition |
|---|---|
| Aggregate | Cluster of entities treated as a unit for data changes |
| Bounded Context | Explicit boundary within which a domain model applies |
| Command | Request to change state (imperative: "CreateCode") |
| Controller | Imperative shell component coordinating I/O and derivers |
| Deriver | Pure function that derives an event from command + state |
| Domain Event | Record of something significant that happened in the domain |
| Entity | Object with identity that persists over time |
| Invariant | Business rule that must always be true |
| Policy | Reactive handler triggered by domain events |
| Repository | Abstraction for retrieving and storing aggregates |
| Value Object | Immutable object defined by its attributes, no identity |
Appendix B: Technology Choices¶
| Concern | Technology | Rationale |
|---|---|---|
| Type Definitions | Pydantic v2 | Runtime validation, frozen models, JSON schema |
| Immutability | @dataclass(frozen=True) |
Native Python, Pydantic compatible |
| Result Types | returns library or custom |
Explicit success/failure handling |
| Event Bus | blinker or custom |
Lightweight, synchronous for desktop app |
| Database | SQLite (existing) | Maintain compatibility |
| UI | PyQt6 (existing) | Maintain compatibility |
Appendix C: File Structure¶
qualcoder/
├── domain/
│ ├── coding/
│ │ ├── entities.py # Code, Segment, Category
│ │ ├── value_objects.py # Color, TextPosition, etc.
│ │ ├── invariants.py # is_code_name_unique, etc.
│ │ ├── derivers.py # derive_create_code_event, etc.
│ │ ├── events.py # CodeCreated, SegmentCoded, etc.
│ │ └── __init__.py
│ ├── sources/
│ │ ├── entities.py
│ │ ├── ...
│ ├── cases/
│ ├── projects/
│ ├── analysis/
│ ├── collaboration/
│ ├── ai_services/
│ └── shared/
│ ├── result.py # Result[T, E] type
│ ├── events.py # Base event types
│ └── identifiers.py # Typed IDs
├── application/
│ ├── controllers/
│ │ ├── coding_controller.py
│ │ ├── source_controller.py
│ │ └── ...
│ ├── policies/
│ │ ├── coding_policies.py
│ │ └── ...
│ └── event_bus.py
├── infrastructure/
│ ├── repositories/
│ │ ├── code_repository.py
│ │ ├── source_repository.py
│ │ └── ...
│ ├── database/
│ │ ├── connection.py
│ │ └── migrations/
│ └── external/
│ ├── openai_client.py
│ └── ...
├── presentation/
│ ├── dialogs/
│ │ ├── code_text.py # Existing UI, calls controllers
│ │ └── ...
│ └── view_models/
└── main.py
Document Version: 1.0 Last Updated: 2026-01-29 Author: Generated for QualCoder fDDD Architecture