20. Microservices Readiness¶
Date: 2026-04-10 Status: Audit complete, principles enforced via subagents
Foundational Principle¶
The codebase must remain extractable into independent services at any point in time. Every PR that adds cross-domain coupling is adding debt that compounds. The subagent review pipeline (architecture-reviewer, code-reviewer, compliance-reviewer, platform-integrity-checker) enforces this on every change.
Current State: 65% Ready¶
What's Clean¶
- Data ownership: each table written by one service domain
- No shared state: zero global singletons, DI via FastAPI
- Configuration: per-domain agnostic, optional services degrade gracefully
- Graceful degradation: Neo4j, Qdrant, Redis, QStash all return empty/None on failure
- Prompt abstraction: all LLM prompts in YAML, no inline constants
3 Critical Blockers¶
- case_orchestrator.py — 96KB god object with 21 cross-domain imports
- Shared DB sessions — one AsyncSession per request spans 8+ services
- Synchronous agent calls — orchestrator calls agents via direct
await
9 Service Boundary Candidates¶
| Domain | Routers | Extractable Now? |
|---|---|---|
| Patient | patients.py | Yes |
| Case | cases.py | After orchestrator refactor |
| Clinical/FHIR | fhir.py | Yes |
| Provider | providers.py, doctors.py | Yes |
| Matching | match.py | After orchestrator refactor |
| Document | documents.py | Yes |
| Agent/Orchestration | agents.py | Last (depends on all others) |
| Consent | consent.py | Yes |
| Notification | (embedded) | Yes |
Rules for New Code¶
MUST (enforced by subagents)¶
- No new cross-domain direct imports
- Own your transaction — don't pass sessions across domains
- Events over function calls for cross-domain side effects
- Typed contracts (dataclass/Pydantic) between domains — no raw dicts
- Config per domain — don't import full settings object
- Prompts via prompt_loader — no inline string constants
MUST NOT (flagged as [RED])¶
- Adding imports from service A into service B
- Passing DB session from one domain's service to another's
- Returning raw dict from a service function
- Adding module-level singletons outside app/integrations/
- Adding inline LLM prompt constants
Known Debt (tracked, don't grow)¶
case_orchestrator.py: 21 cross-domain importsmatch_service.py: reads from patient, FHIR, provider, graphdocument_processing.py: calls clinical_context agent directly
Extraction Sequence (When Ready)¶
| Phase | Services | Prerequisite |
|---|---|---|
| 1 | Consent, Patient, Provider | None |
| 2A | Document, FHIR/Clinical | Phase 1 |
| 2B | Orchestrator refactor (HTTP + events) | Phase 1 |
| 3 | Matching | Phase 2B |
| 4 | Agent Orchestrator | Phase 2B + 3 |
References¶
- Architecture reviewer:
.claude/agents/architecture-reviewer.md - Code reviewer:
.claude/agents/code-reviewer.md - Compliance reviewer:
.claude/agents/compliance-reviewer.md - Platform integrity checker:
.claude/agents/platform-integrity-checker.md - DAO layer spec:
docs/specs/ai-steer/dao-layer-steer.md(pending)