First Iteration
The first iteration revealed several design decisions that needed refinement. The initial architecture was solid, but practical implementation exposed areas for improvement.
Interface Alignment
The biggest challenge was aligning interfaces across the codebase. The DocumentRepository interface in the core
use cases didn’t match the persistence layer. The ChangeLog interface needed to live in a shared package to avoid
import cycles between the root package and use cases.
The solution: put all interfaces in core/usecases/interfaces, with implementations in the root package and
persistence layer.
Encryption Scope
The initial architecture described encryption at rest. But as the design matured, it became clear that local storage doesn’t need encryption—the user already has access to their data. Encryption applies only to remote storage (backends), happening at the transport layer: encrypt on push, decrypt on pull.
Naming: Squash to Snapshot
The term “squash” for compacting change history was replaced with “snapshot”—the standard event sourcing term. Snapshots capture the current state at a point in time, enabling efficient bootstrapping without replaying all events.
What Worked Well
The multi-backend sync design proved robust. Pull from all, merge with LWW, push to all—this simple pattern handles new backends, offline backends, and multiple accounts. The nested field diffs minimize conflict surface area while keeping the change log compact.
The first iteration demonstrates the value of iterative design. By building a working version and refining based on practical experience, the architecture evolved from theoretical elegance to practical robustness. The interface alignment challenge is a common pain point in polyglot codebases—solved here by establishing a single source of truth for interfaces.





