ADR-000: Strategy + Direct Implementation Architecture¶
Status: Accepted Date: 2026-02-01 Deciders: adk-secure-sessions maintainers
Context¶
adk-secure-sessions is a library that adds encryption to Google ADK's session storage. The architecture must:
- Be transparent — users should interact with ADK's session interface, not ours
- Support pluggable encryption backends (Fernet, and later KMS providers)
- Stay focused — this is not an application with complex domain logic
- Be easy to extend without modifying existing code
We investigated ADK's session internals to determine the right integration approach.
Decision¶
Adopt Strategy + Direct Implementation as the core architectural pattern.
Direct Implementation of BaseSessionService (Integration)¶
The EncryptedSessionService directly implements ADK's BaseSessionService ABC. It owns its own database connection, schema, and serialization — with encryption at the serialization boundary.
This is the same approach used by every community session service (adk-extra-services MongoDB/Redis, google-adk-extras, etc.). Nobody wraps DatabaseSessionService; they all implement BaseSessionService from scratch.
User Code
│
▼
EncryptedSessionService (implements BaseSessionService)
│ json.dumps → encrypt → write
│ read → decrypt → json.loads
▼
SQLite (aiosqlite) / PostgreSQL
We model our implementation on ADK's own SqliteSessionService, which uses aiosqlite + JSON serialization. Our version is structurally identical but adds an encrypt/decrypt step around the JSON serialization boundary.
Why not wrap DatabaseSessionService (Decorator pattern)?¶
We initially planned a decorator approach. Reading the actual ADK source revealed this doesn't work:
-
append_eventhas no interception point. It reads state from the DB, merges deltas viajson_patchSQL operations, writes the event, and commits — all in one internal transaction. There's no clean boundary to inject encryption. -
State is split across 3 tables (
app_states,user_states,sessions). A decorator would need to understand this internal split to encrypt/decrypt correctly, coupling us to ADK internals. -
No community precedent. Every third-party session service (
adk-extra-services,google-adk-redis) subclassesBaseSessionServicedirectly. Nobody wraps the built-in services.
Revision Note (2026-03-05): The rejection reasoning above was correct for ADK V0. ADK V1 changed state merging from SQL-side
json_patchoperations to Python-sidedict | delta, removing the interception barrier described in point 1.DatabaseSessionServicewrapping is now viable viaTypeDecorator-based column encryption. ADR-007 formalizes the decision to migrate to this architecture — see ADR-007 and Issue #118. This ADR's "Direct Implementation" approach is superseded by ADR-007; the "Strategy" portion (pluggable backends) remains valid.
Strategy Pattern (Extensibility)¶
Encryption backends are interchangeable strategies. Each implements the EncryptionBackend protocol (see ADR-001). The session service delegates all encrypt/decrypt operations to the configured backend.
EncryptedSessionService
│
├── FernetBackend (symmetric, simple — v1)
└── CustomBackend (user-provided, implement 2 methods)
Project Structure¶
src/adk_secure_sessions/
├── __init__.py # Public API surface
├── protocols.py # EncryptionBackend protocol
├── exceptions.py # Exception hierarchy
├── services/
│ ├── __init__.py
│ └── encrypted_session.py # Implements BaseSessionService
├── backends/
│ ├── __init__.py
│ └── fernet.py # Fernet symmetric encryption (v1)
└── serialization.py # Encrypt/decrypt at JSON boundary
Layer Rules¶
services/depends onprotocols.py(the contract), never on concrete backendsbackends/implementsprotocols.py— each backend is self-containedprotocols.pyhas zero dependencies (stdlibtypingonly)serialization.pyhandles the encrypt-on-write / decrypt-on-read boundary aroundjson.dumps/json.loads
What We Inherit from BaseSessionService¶
ADK's BaseSessionService provides two concrete helper methods we reuse:
_trim_temp_delta_state(event)— removes temporary state keys before persistence_update_session_state(session, event)— applies state deltas to in-memory session
We implement the four abstract methods and append_event:
create_session()— serialize state → encrypt → write to DBget_session()— read from DB → decrypt → deserialize statelist_sessions()— read metadata (unencrypted) + decrypt state per sessiondelete_session()— delete rowsappend_event()— encrypt state delta + event data → write, then callsuper().append_event()for in-memory update
Consequences¶
What becomes easier¶
- Adding backends: New backend = one file, implement 2 methods, done
- Testing: Mock the protocol for unit tests, use real backends for integration
- Adoption: Same interface as ADK's built-in services — one-line swap
- Maintenance: We own our schema, no fragile coupling to ADK internals
What becomes harder¶
- Feature parity: We reimplement session storage rather than delegating. If ADK adds new session service features (e.g., new query filters), we need to add them too.
- State splitting logic: We must replicate ADK's
app_state/user_state/session_stateprefix-based splitting (via_session_util.extract_state_delta). This is a public utility in ADK, so it's stable.
Trade-offs¶
- More code than a decorator, but it actually works. A decorator that can't intercept
append_eventis useless. - We depend on
BaseSessionService's interface stability, not internal implementation details. This is the correct dependency direction for a plugin.
Alternatives Considered¶
Decorator Wrapping DatabaseSessionService¶
Rejected. Investigated in detail — append_event does state reads, delta merges, event writes, and commits in a single internal transaction with no interception points. State split across 3 tables requires knowledge of internals. No community implementations use this approach.
Hexagonal Architecture (Ports & Adapters)¶
Rejected. Hexagonal is designed for applications with rich domain logic and multiple I/O boundaries. adk-secure-sessions has one job (encrypt/decrypt session data) and one boundary (ADK's session service interface). Three layers of indirection would add ceremony without value.
Simple Inheritance of DatabaseSessionService¶
Rejected. Subclassing DatabaseSessionService couples us to its internal SQLAlchemy schema, _SchemaClasses version logic, and StorageSession / StorageEvent models. When ADK refactors internals (as they did in v1.22.0 with the V0→V1 schema migration), our subclass breaks. (See ADR-007 for how TypeDecorator-based subclassing differs from this rejected approach — it intercepts at the ORM column boundary rather than overriding CRUD methods.)
Middleware/Pipeline Pattern¶
Considered for future. If we need composable transformations (encrypt → compress → sign), a middleware chain would fit. For now, single-backend encryption doesn't warrant the complexity.