Platform ServicesML (Python)
Architecture Rules
Clean Architecture guidelines for WordLoop Python Core
Architecture Rule: Clean Architecture for WordLoop ML
1. Context & Scope
This rule physically applies our Service Architecture Principles to all Python code within src/wordloop.
We divide the code structurally based on its behavior: the core business logic remains isolated in src/wordloop/core/, inbound traffic is handled by src/wordloop/entrypoints/, and all external integrations belong in src/wordloop/providers/.
2. Architectural Layers (Inward Dependency Flow)
Dependencies must only point INWARD. Inner layers must never import from outer layers.
Domain (src/wordloop/core/domain)
- Purpose: Business entities and core logic using
dataclassesorPydantic. - Zero-Dependency Core: Standard library and Pydantic/Dataclasses only. No I/O.
- Framework Agnostic Definitions: Models must remain free from database-specific decorators or library-specific types (e.g., avoid
SQLAlchemyORM models here). - Universal Vocabulary: Define core application exceptions (
src/wordloop/core/exceptions.py) and constants here. - Testing: Pure Unit Tests. Verify state transitions and business rules with zero mocks.
Gateways (src/wordloop/core/gateways)
- Purpose:
typing.Protocolorabc.ABCdefinitions that define capabilities. - Contractual Masters: Gateways define what (e.g.,
store), never how. - No Leaky Abstractions: Signatures must use Domain entities. Never reference SDK types (e.g.,
openai.ChatCompletion) or transport types. - The Golden Rule: Use generic names.
publish(msg: Message), notsend_to_sqs(msg: Message).
Services (src/wordloop/core/services)
- Purpose: Use-case orchestration. This is where the application "decides" what happens.
- Dependency Injection: Services depend on Gateways (Protocols/ABCs), not concrete Providers.
- Protocol Consumer Rule: Services should return concrete Domain objects. If a Service is used by an Entrypoint, the Entrypoint defines the Protocol/Interface it requires from the Service.
- Transaction Boundaries: Coordinate workflows by fetching data, applying domain logic, and persisting results.
Providers (src/wordloop/providers/)
- Purpose: Concrete implementations of Gateway interfaces (The "Adapter").
- Mapping (Domain Alignment): Translates external SDK responses into Domain Entities.
- Error Wrapping: Catch library-specific errors (e.g.,
botocore.exceptions.ClientError) and raise a corresponding Core Exception defined in the Domain layer. - Testing: Integration Tests only. Use
testcontainers-pythonto verify actual I/O against real instances.
Entrypoints (src/wordloop/entrypoints/)
- Purpose: The interaction layer (FastAPI, CLI).
- Boundary Validation: Entrypoints validate inputs, map request schemas to Domain objects, and call a Service. They delegate all business decisions to the Core.
- Testing: Use
fastapi.testclient.TestClientwith mocked Services to verify routing and status codes.
3. Dependency Injection & State
- Constructor Injection: Use
__init__for all dependencies. - Explicit Lifecycles: Initialize database clients solely at the entrypoint startup (e.g.,
lifespanin FastAPI) and inject them rather than relying on global singletons. - Wiring: All concrete Provider-to-Service wiring happens at the outermost edge (the Entrypoint or a dedicated
container.py).
4. Integrity & System Testing
Bootstrap Verification (The "Smoke" Test)
To ensure the application is wired correctly:
- Wiring Test: A test in
tests/system/test_bootstrap.pythat attempts to initialize the full dependency tree. - Validation: Ensures that all required environment variables are present and that the DI container (or manual wiring) doesn't fail on startup.
Golden Path System Tests
- Location:
tests/system/. - Strategy: Run a live instance of the app (e.g., using
uvicornin a subprocess orTestClientwith real providers) against real infrastructure viatestcontainers. - Zero Mocks: These tests verify the "Golden Thread" from the API route all the way to the database/third-party SDK.
- Scope: Focus strictly on high-value success paths.
5. Core Engineering Standards
- Pydantic Everywhere: Use Pydantic for all data boundaries (Request/Response and Domain).
- Structured Logging: Utilize structural logging libraries rather than basic print statements to ensure trace fidelity.
- Acyclic Dependencies: Prevent import cycles, as they act as an immediate signal of leaked layer responsibilities.
- Strict Boundaries: Maintain clean layers by preventing FastAPI
Depends,Request, or SQLSessionobjects from entering the Service or Domain layers. - Clean Containers: Always ensure
container.stop()or similar cleanup is called inpytestfixtures to prevent resource leaks in CI.