Skip to content

feat(xtm-oaev-sdk): PoC architecture comparison, flat vs ddd #301

Description

@Kakudou

TL;DR


Context

This issue is the anchor for the three-SDK architecture comparison. The satellites (collectors-sdk and injectors-sdk) reference this issue for shared context and decision framing.

Work was done exploration-first: both architectures are fully implemented and tested before these issues were created.
The goal is to validate a structural decision as a team at the squad meeting, not to merge a PR today.

Origin analysis: #199.


Why this SDK exists

pyoaev is currently a 62-file monolithic package that mixes:

  • API client framework
  • Shared data types
  • Security domain logic
  • Signature verification
  • Daemon lifecycle management
  • Configuration

xtm-oaev-sdk extracts the shared data-layer: types, config, contracts, signatures, exceptions, helpers.
So that pyoaev becomes a pure framework client.
Extension SDKs (collectors-sdk, injectors-sdk) and future squads depend on this extracted layer, not on the monolith.

SDK hierarchy:

xtm-core-sdk (planned)
    └── xtm-oaev-sdk          ← this issue
            ├── pyoaev (post-extraction)
            ├── collectors-sdk
            └── injectors-sdk

Dependency direction is strictly one-way.
Extension SDKs never depend on each other.
Scope promotion rules:
if both extension SDKs need a symbol → promote to xtm-oaev-sdk;
if both squads need it → promote to xtm-core-sdk.


Two branches, two architectures

Branch Location
exploration/sdks-flat https://github.com/OpenAEV-Platform/client-python/tree/exploration/sdks-flat
exploration/sdks-ddd https://github.com/OpenAEV-Platform/client-python/tree/exploration/sdks-ddd

Both branches expose identical public symbols. Both produce the same __all__. Same test suite, same assertions. Both are green.


Architecture A: Flat-First (exploration/sdks-flat)

Structure: 2 layers, ~15 files.

xtm-oaev-sdk/
├── _core/          # private implementation
└── __init__.py     # re-exports only
  • All symbols live in _core/ as flat modules.
  • No feature folders, everything at the same depth.
  • Fast to navigate, minimal ceremony, obvious entry point.
  • Lower file count = lower cognitive overhead for a new contributor.

Trade-off: As the symbol count grows, _core/ becomes a flat dumping ground.
No enforced boundary between features.
A breaking change in one area is indistinguishable from a change in another.


Architecture B: DDD + Light Hex (exploration/sdks-ddd)

Structure: 3 layers, ~30 files.

xtm-second-oaev-sdk/
├── _core/          # private domain, feature folders
│   ├── configuration/
│   ├── contracts/
│   ├── errors/
│   ├── signatures/
│   └── ...
├── contracts/      # stable boundary, re-exports only, one file per feature
└── public/         # flat user-facing, re-exports only

Three named layers:

  • _core/: private domain logic, never imported directly by consumers.
  • contracts/ : stable boundary; re-exports only; this is what other SDKs depend on. A change here is a deliberate, visible contract change.
  • public/: flat, user-facing; re-exports from contracts/.

Feature folder convention: features are first-class structural units.
Each feature gets its own folder in all three layers.
Sub-components live inside their parent feature folder unless genuinely standalone (no micro-feature split).

Template-aligned role decomposition inside features:

_core/<feature>/
├── engines/
├── internals/
├── models/
├── protocols/
├── types/
└── utils/

Trade-off: ~2× the file count. contracts/ layer is ceremony, it adds a stable boundary but requires every contributor to understand the three-layer contract.
Onboarding takes longer.


Decision matrix

Both architectures are evaluated on 8 criteria. Neither is universally superior.

Criterion Weight Flat-First DDD + Light Hex
Simplicity High
Feature isolation Medium
Breaking change control High
Onboarding speed Medium
Scalability Medium
Test boundaries Low
File count / navigation Low
Self-documenting structure Medium

File count comparison across all three SDKs:

SDK Flat-First DDD + Light Hex
xtm-oaev-sdk ~15 files ~30 files
collectors-sdk ~10 files ~20 files
injectors-sdk ~8 files (~17 .py) ~16 files (~29 .py)

Hybrid option: Start flat, promote to DDD on a concrete trigger. This is viable but the trigger must be named explicitly, "when it gets complex" is not a trigger.


Migration strategy (architecture-agnostic)

Three-phase deprecation shims in pyoaev for all moved symbols:

Phase Warning type Visibility
Phase 1 (now) DeprecationWarning Test output only
Phase 2 FutureWarning Production visible
Phase 3 Remove shim Old import path stops working

xtm-oaev-sdk deprecation shims:

Area Shim count
signatures 22
exceptions 13
configuration 6
utils 6
SSL 4
daemon protocol 1

Four open questions for the squad meeting

These must be answered before a structural decision can be made:

  1. Growth rate: How many features per SDK in the next year? (current counts: injectors=2, collectors=1, xtm-oaev-sdk=7 groups)
  2. Ceremony tolerance: Is the team OK enforcing the contracts/ layer as a strict boundary for all contributors?
  3. __init__.py discipline: Do we commit to __init__.py = re-exports only, regardless of architecture?
  4. Hybrid trigger: If we go hybrid (flat → DDD on trigger), what is the concrete, named condition for promotion?

Validation facts

  • 513 tests pass on both architectures. Same suite, same assertions, both green.
  • 180 total public symbols across all 3 SDKs (107 + 43 + 30).
  • Both produce the same __all__, consumers cannot tell the architectures apart from the outside.

This is a PoC

These branches exist for architecture validation at the squad meeting. There is no merge target. The goal is a team decision, not a shipping artifact.

Related satellite issues:
OpenAEV-Platform/collectors#450
OpenAEV-Platform/injectors#286

Metadata

Metadata

Assignees

Labels

architecture improvementArchitecture refactor or improvement is needed.filigran teamItem from the Filigran team.multirepositoryThere is more repositories related to this prtech foundationLinked to tech foundation.

Type

No fields configured for Task.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions