Modular Architecture¶
Domain-driven module structure for the platform server, with import rules, ownership conventions, and inter-service communication patterns.
Domain Modules¶
The server codebase is organized into 7 domain modules. Each domain owns its models, services, API routes, and schemas. Work on any feature must be placed in the correct domain.
| Domain | Directory | UI Section | Owns | Routes |
|---|---|---|---|---|
| iam | server/src/orchestrator/domains/iam/ |
IAM + Organization | User, Org, Team, Membership, AuthProvider, ServiceToken, Session | /api/v1/auth/*, /api/v1/organizations/*, /api/v1/teams/*, /api/v1/iam/*, /api/v1/profile/* |
| workflow | server/src/orchestrator/domains/workflow/ |
Workflows + Dashboard | Workflow, WorkflowRun, Step, Task, RunTask, OrchestratorConfig | /api/v1/workflows/*, /api/v1/tasks/*, /api/v1/dashboard/* |
| catalogue | server/src/orchestrator/domains/catalogue/ |
AI | Tool, ToolFile, AIModel, AgentPreset, Prompt, ToolChatMessage | /api/v1/tools/*, /api/v1/models/*, /api/v1/agents/*, /api/v1/prompts/*, /api/v1/orchestrators/*, /api/v1/catalogue/* |
| audit | server/src/orchestrator/domains/audit/ |
Security | AuditLog, ViolationLog | /api/v1/audit/* |
| monitoring | server/src/orchestrator/domains/monitoring/ |
Monitoring + Billing | ExecutionLog, UsageLog, ObservabilityConfig, Balance | /api/v1/logs/*, /api/v1/observability/* |
| notification | server/src/orchestrator/domains/notification/ |
(cross-cutting) | Notification | /api/v1/notifications/* |
| assistant | server/src/orchestrator/domains/assistant/ |
AI Assistant | Conversation, Message | /api/v1/assistant/* |
Domain Directory Structure¶
Each domain follows this layout:
domains/{domain}/
__init__.py
models/ # SQLAlchemy models (one file per model or logical group)
services/ # Business logic (one file per service class)
api/ # FastAPI routers (one file per route group)
schemas/ # Pydantic request/response schemas
events.py # Domain event definitions (optional)
Module Boundary Rules¶
These rules are enforced in every code review
Violations block merge. They exist to keep domains independently extractable as microservices.
Rule 1: Allowed imports¶
A domain module may ONLY import from:
- Its own package:
from orchestrator.domains.catalogue.models import Tool - The
core/package:from orchestrator.core.id_generator import make_id_factory - The
cwiq-commonshared library:from cwiq_common.auth import validate_jwt - The
contracts/package for cross-domain DTOs:from orchestrator.contracts.iam import UserDTO
Rule 2: No direct cross-domain imports¶
# BAD — imports directly from another domain
from orchestrator.domains.iam.models import User
# GOOD — imports a DTO from the contracts package
from orchestrator.contracts.iam import UserDTO
Rule 3: No cross-domain ORM joins¶
# BAD — joins across domain boundaries
db.query(Workflow).join(Tool)
# GOOD — calls a service that returns DTOs
catalogue_service.get_tools_by_ids(tool_ids)
Rule 4: Ownership columns stay denormalized¶
owner_org_id, owner_team_id, and created_by_user_id are plain string columns in every domain table. There are no foreign key constraints from these columns to IAM tables. JWT claims provide the values; filtering uses the column directly.
Where to Put New Code¶
| If the feature involves... | Domain | Reason |
|---|---|---|
| User accounts, org settings, team management | iam |
Owns identity and access |
| Workflow creation, execution, runs, tasks | workflow |
Owns execution lifecycle |
| Tool/model/agent/prompt CRUD | catalogue |
Owns AI resource registry |
| Audit trail, security events, compliance | audit |
Owns audit data |
| Execution logs, usage tracking, billing | monitoring |
Owns observability data |
| Push notifications, alerts to users | notification |
Owns notification delivery |
| AI chat, context building | assistant |
Owns conversation state |
| Cross-cutting utilities (auth, config, DB) | core |
Shared infrastructure |
| Cross-domain data shapes | contracts |
DTOs and Protocol types |
Prefixed IDs¶
All resource IDs use the format {prefix}-{16 hex chars}:
Use make_id_factory for all model primary key defaults:
from orchestrator.core.id_generator import make_id_factory
class Workflow(Base):
id: Mapped[str] = mapped_column(
String(26), primary_key=True, default=make_id_factory("wf")
)
Never use uuid.uuid4() directly for resource IDs
Always use make_id_factory("prefix"). The prefix registry is in platform/plans/multi-tenancy/PREFIXED_IDS.md.
make_id_factory is also available in cwiq_common.models (cwiq-common v0.1.1+).
Inter-Service Communication¶
| Pattern | Use For | Technology |
|---|---|---|
| Synchronous HTTP | CRUD reads, validation on write | nginx proxy between services |
| Kafka events | Audit trail, usage tracking, notifications | aiokafka + Redpanda |
| Redis pub/sub | Real-time WebSocket events (1–5ms) | Redis channels |
| In-process function call | Same-domain operations | Direct Python call |
| JWT validation | Auth on every request | cwiq-common (in-process, no network hop) |
| Aggregated health fan-out | Liveness across all microservices (?detailed=true) |
server fans out via httpx to each /health |
Microservices Extraction Roadmap¶
Each domain is designed for independent extraction. When a domain is extracted:
- Internal function calls become HTTP or Kafka calls
- contracts/ DTOs define the API boundary
See platform/plans/micro-services/ for the full decomposition roadmap.
Related Documentation¶
- Artifact Naming — Docker image names per service
- CI/CD Overview — Per-service pipeline structure