ADR-001: Protocol-Based Interfaces¶
Status: Accepted Date: 2026-02-01 Deciders: adk-secure-sessions maintainers
Context¶
The encryption backend system needs a contract that backend implementations must satisfy. Python offers several ways to define interfaces:
- Abstract Base Classes (
abc.ABC) - Structural subtyping (
typing.Protocol) - Duck typing (no formal contract)
- Zope/interface-style interfaces
We need an approach that is: - Easy for third-party developers to implement (low friction) - Statically verifiable by type checkers - Consistent with modern Python practices and the ADK ecosystem
Decision¶
Use typing.Protocol (PEP 544) for all public interfaces.
Core Protocol¶
from typing import Protocol, runtime_checkable
@runtime_checkable
class EncryptionBackend(Protocol):
"""Contract for all encryption backends."""
async def encrypt(self, plaintext: bytes) -> bytes:
"""Encrypt plaintext bytes."""
...
async def decrypt(self, ciphertext: bytes) -> bytes:
"""Decrypt ciphertext bytes."""
...
Design Principles¶
-
Structural subtyping: Implementors don't need to inherit from or register with our protocol. Any class with matching
encryptanddecryptmethods satisfies the contract. -
@runtime_checkable: Enablesisinstance()checks for validation at service initialization, providing clear error messages when a backend doesn't conform. -
Minimal surface: The protocol defines the absolute minimum —
encryptanddecrypt. Optional capabilities (key rotation, metadata, batch operations) are separate protocols that backends can optionally implement.
Optional Capability Protocols¶
class SupportsKeyRotation(Protocol):
"""Backend that supports key rotation."""
async def rotate_key(self, new_key: bytes) -> None: ...
class SupportsMetadata(Protocol):
"""Backend that provides encryption metadata."""
def get_metadata(self) -> EncryptionMetadata: ...
The session service can check for optional capabilities:
Consequences¶
What becomes easier¶
- Third-party backends: Anyone can implement
encrypt/decryptwithout importing our package - Type checking:
ty,mypy,pyrightall verify protocol conformance statically - Testing: Create a mock with two methods — no base class or registration needed
- Composition: Backends can implement multiple protocols for optional features
What becomes harder¶
- Discovery: No base class to
grepfor — implementors need to read the protocol docs - Runtime validation: Structural typing checks method signatures, not semantics. A backend could implement
encryptthat doesn't actually encrypt. Tests and integration validation cover this gap.
Alternatives Considered¶
Abstract Base Classes (abc.ABC)¶
Rejected. Requires inheritance (class MyBackend(EncryptionBackend)), which couples third-party code to our package at import time. Protocols allow structural ("duck type") conformance without inheritance.
Duck Typing (No Contract)¶
Rejected. No static analysis support, no documentation of the expected interface, runtime AttributeError instead of clear error messages. Protocols give us duck typing's flexibility with static typing's safety.