ADR 0019: Package Structure, Import Rules, and Source Root Policy¶
Status¶
Accepted
Date¶
2026-03-16
Context¶
IRIS is an analytical platform built around:
- domain-oriented modules
- bounded contexts
- event-driven orchestration
- layered architecture
- strict separation of concerns
As the project grows, the following risks appear:
- chaotic package structure
- implicit dependencies between domains
- the spread of generic "utility" files
- deep relative imports
- mixing infrastructure and domain logic
In addition, product code must not couple itself to a repository-layout namespace.
A standard is therefore needed to:
- make project structure predictable
- make domain dependencies explicit
- remove the infrastructure namespace
srcfrom product code - establish one product namespace:
iris
Decision¶
IRIS uses a single product namespace: iris.
All product imports must begin with:
iris.*
src/ is a repository-internal folder for migration artifacts and is not part of the product namespace.
Source Root Policy¶
Repository layout:
backend/
iris/
src/
migrations/
Example:
backend/iris/apps/signals
Import:
from iris.apps.signals.application.services.build_signal_snapshot import ...
Forbidden:
from src.apps.signals ...
src is not part of the runtime namespace and must not appear in product imports.
This is a binding engineering rule. Compatibility entrypoints may remain while files are reorganized, but product imports and active tooling must use iris.* rather than src.*.
Project Package Layout¶
Backend package structure:
iris/
api/
apps/
core/
runtime/
main.py
Package Responsibilities¶
iris.api
HTTP and transport layer.
Contains:
- routers
- dependencies
- request mapping
- response mapping
- error translation
Business logic is forbidden here.
iris.apps
Bounded contexts.
Each product domain is implemented as a dedicated package.
Examples:
iris.apps.signalsiris.apps.market_datairis.apps.control_planeiris.apps.settings
iris.core
Shared platform kernel.
Contains:
- configuration
- i18n
- logging
- base error classes
- telemetry
- shared utilities
core must remain minimal and stable.
iris.runtime
Infrastructure runtime:
- workers
- stream processors
- schedulers
- event-loop orchestration
Domain Package Structure¶
Each new or actively refactored domain in iris.apps must follow the same structure.
apps/<domain>/
api/
application/
domain/
infrastructure/
contracts/
Layer Responsibilities¶
api
Transport adapters.
Examples:
routes.pyread_routes.pywrite_routes.pydependencies.pyerror_mapping.py
application
Use cases and orchestration.
Contains:
commands/queries/services/
Example files:
create_signal.pyactivate_strategy.pylist_signals.pyrefresh_market_data.py
domain
Pure domain model.
Contains:
entities.pyvalue_objects.pyevents.pyexceptions.pyenums.pypolicies/
The domain layer does not depend on infrastructure.
infrastructure
Persistence and integrations.
Contains:
models.pyrepositories/queries/cache/integrations/
contracts
Typed contracts between layers.
Contains:
commands.pyresponses.pyread_models.pyevents.py
File Naming Rules¶
A file must describe a concrete responsibility.
Allowed:
refresh_market_data.pybuild_signal_snapshot.pyactivate_strategy.pysignal_history_query.py
Forbidden:
utils.pyhelpers.pycommon.pymisc.pymanager.pyprocessor.pyservice.py
Such names are treated as architectural smells.
Avoid Tautological Naming¶
The path already contains the architectural layer.
Bad:
application/services/signal_service.py
domain/models/domain_models.py
Good:
application/services/build_signal_snapshot.py
domain/entities.py
Aggregator Files¶
Files such as:
repositories.pyschemas.pymodels.py
are allowed only if:
- they are small
- they contain a logically related object group
As they grow, they must be split.
Import Rules¶
Within the Same Domain¶
Relative imports are allowed.
Example:
from .exceptions import SignalError
from ..contracts.read_models import SignalSummary
Cross-Domain Imports¶
Cross-domain imports must be absolute.
Example:
from iris.apps.market_data.contracts.read_models import MarketSnapshot
Forbidden:
from ...market_data.contracts import MarketSnapshot
Relative Import Depth¶
Relative imports deeper than .. are forbidden.
Allowed:
...
Forbidden:
.......
Cross-Domain Dependency Rules¶
A domain may import another domain only through public modules.
Allowed:
- contracts
- public facades
Forbidden:
- API
- infrastructure
- repositories
- ORM models
- private services
Example of a bad import:
from iris.apps.market_data.infrastructure.models import MarketModel
Core Imports¶
iris.core is imported only through absolute imports.
from iris.core.config import settings
Test Imports¶
Tests may use additional import-path configuration.
src may be used as a source root in the test environment.
However, product code must not use the src namespace.
Architectural Goal¶
Project structure must provide:
- readable paths
- explicit domain boundaries
- minimal coupling
- easy refactoring
- architectural scalability
Consequences¶
Positive¶
IRIS gains:
- one stable namespace:
iris - a strict domain-package structure
- clear file names
- controlled cross-domain dependencies
- a predictable import system
That makes the architecture more resilient as the system grows.
Negative¶
- a phased migration from the existing
src.*namespace will be required - until migration is complete, the repository will live in a mixed state
- some CI constraints cannot be enabled as immediate hard failures
See also¶
- ADR 0020: Dependency Direction Rules and Import Boundaries — dependency-direction rules
- ADR 0002: Persistence Architecture — infrastructure layer