Developer: s4gor
Github: https://github.com/s4gor
schema-sync/
├── Cargo.toml # Project configuration
├── README.md # User-facing documentation
├── DESIGN.md # Detailed design rationale
├── ARCHITECTURE.md # This file - architecture overview
├── .gitignore # Git ignore rules
├── src/
│ ├── lib.rs # Main library entry point with architecture diagram
│ ├── adapters.rs # Database adapter traits (DatabaseAdapter, SchemaInspector, MigrationRunner)
│ ├── diff.rs # Schema diff calculation and representation
│ ├── engine.rs # Main engine orchestration
│ ├── errors.rs # Error types
│ ├── planner.rs # Migration planning
│ ├── executor.rs # Migration execution
│ ├── snapshot.rs # Schema snapshot system
│ └── cli.rs # CLI types and context
└── examples/
├── basic_usage.rs # Basic sync example
├── dry_run_mode.rs # Dry-run mode example
└── ci_validation.rs # CI validation example
Purpose: Main entry point for database operations.
Key Methods:
inspector()→Box<dyn SchemaInspector>migration_runner()→Box<dyn MigrationRunner>database_type()→&strtest_connection()→Result<()>
Why it exists: Factory pattern for creating inspectors and runners. Enables multi-database support.
Purpose: Read-only schema introspection.
Key Methods:
inspect_schema(tenant)→Result<SchemaSnapshot>schema_exists(tenant)→Result<bool>list_tenants()→Result<Vec<TenantContext>>
Why it exists:
- Enables audit mode without write permissions
- Allows dry-run mode to calculate diffs without locks
- Supports testing with mock inspectors
Purpose: Execute schema changes.
Key Methods:
execute_migration(tenant, plan)→Result<MigrationResult>acquire_lock(tenant, timeout)→Result<Box<dyn LockGuard>>validate_migration(tenant, plan)→Result<()>
Why it exists:
- Pluggable migration engines (SQL files, Rust code, external tools)
- Different strategies per database type
- Testing with mock runners
Purpose: Create executable migration plans from schema diffs.
Key Methods:
create_plan(current, target, diff)→Result<MigrationPlan>validate_plan(plan)→Result<()>
Why it exists:
- Dry-run mode can show what would happen
- Validation of plans before execution
- Different planning strategies (safe ordering, dependency resolution)
Purpose: Orchestrate the execution of migration plans.
Key Methods:
execute(tenant, plan, runner)→Result<ExecutionResult>dry_run(tenant, plan, runner)→Result<ExecutionResult>
Why it exists:
- Different execution strategies (transactional, non-transactional)
- Progress reporting
- Retry logic
Purpose: Calculate differences between schema snapshots.
Key Methods:
calculate_diff(from, to)→SchemaDiff
Why it exists:
- Different diff algorithms
- Three-way merge support
- Conflict detection
Purpose: Store and retrieve schema snapshots.
Key Methods:
store(tenant, snapshot)→Result<()>get_latest(tenant)→Result<Option<SchemaSnapshot>>get_by_hash(tenant, hash)→Result<Option<SchemaSnapshot>>list(tenant)→Result<Vec<SchemaSnapshot>>compare(tenant, hash_a, hash_b)→Result<SchemaDiff>
Why it exists:
- Multiple storage backends (filesystem, database, version control)
- Version history
- Deterministic versioning
Normalized, database-agnostic representation of a schema.
Properties:
- Deterministic: Same schema always produces same snapshot
- Order-independent (uses HashMaps)
- Database-agnostic
Contains:
- Tables (with columns, constraints, indexes)
- Views
- Functions
- Types
Represents differences between two snapshots.
Structure: Hierarchical (schema → table → column → constraint)
Contains:
- Tables: added, removed, modified
- Views: added, removed, modified
- Functions: added, removed, modified
- Types: added, removed, modified
Executable sequence of operations to transform schema.
Structure: Ordered steps with dependencies
Contains:
- Steps (ordered operations)
- Estimated duration
- Downtime requirements
- Warnings
Explicit tenant scoping for all operations.
Properties:
- Single field:
tenant_id: String - Required for all operations
- Prevents cross-tenant leakage
- Implement
DatabaseAdapter - Implement
SchemaInspector(convert DB schema →SchemaSnapshot) - Implement
MigrationRunner(convertMigrationPlan→ DB SQL) - Use with engine
No changes needed to: Engine, planner, executor, diff calculator.
- Implement
MigrationRunner - Convert
MigrationPlanto your format - Execute using your tool
Example: SQL file migrations, diesel migrations, sqlx migrations.
- Implement
SnapshotStore - Store/retrieve
SchemaSnapshotin your backend - Use with engine
Example: Filesystem, database, S3, version control.
- Implement
Planner - Create
MigrationPlanfromSchemaDiff - Use with engine
Example: Safe planner for zero-downtime migrations.
- Implement
DiffCalculator - Calculate
SchemaDifffrom twoSchemaSnapshots - Use with engine
Example: Three-way merge calculator.
Implementation: Engine::sync_tenant(tenant, target, execute=true)
Behavior: Calculate diff, create plan, execute plan.
Implementation: Engine::sync_tenant(tenant, target, execute=false)
Behavior: Calculate diff, create plan, validate plan, return diff without executing.
Implementation:
Engine::sync_tenant(tenant, target, execute=false)for all tenants- Check
already_in_syncflag - Exit non-zero if any tenant has
already_in_sync=false
Behavior: Verify all tenants match expected schema.
Implementation: Use SchemaInspector directly, no MigrationRunner.
Behavior: Read-only inspection, no changes allowed.
Traits allow:
- Multiple implementations to coexist
- Pluggable components
- Testing with mocks
- Extension without modification
- Inspector can be used without runner (audit mode)
- Different migration strategies can be implemented
- Testing is easier with mock implementations
- Dry-run mode can plan without executing
- Validation of plans before execution
- Different execution strategies
- Enables diffing schema version A vs B
- Supports version control integration
- Allows deterministic versioning
- Type safety: Can't accidentally operate on wrong tenant
- Makes tenant isolation explicit
- Supports batch operations
- Enables per-tenant locking
- Default
Plannerimplementation - Default
Executorimplementation - Default
DiffCalculatorimplementation - File-based
SnapshotStoreimplementation
-
PostgresAdapterimplementation -
PostgresInspectorimplementation -
PostgresMigrationRunnerimplementation
- CLI argument parsing
- Mode handling (sync, diff, validate, audit)
- Output formatting (text, JSON)
- Exit codes for CI
- MySQL support
- SQLite support
- Three-way merge support
- Conflict detection
- Zero-downtime migration strategies
- Progress reporting
- Audit trail
- Mock implementations of all traits
- Test each component in isolation
- Test database (testcontainers or in-memory)
- Test full sync flow
- Test error handling and rollback
- Deterministic snapshots
- Reversibility (plan + execution = target)
This architecture provides a solid, extensible foundation for schema synchronization. The trait-based design enables growth without breaking changes, and the separation of concerns makes the codebase maintainable.
The key insight: design for extension first. Every abstraction exists to enable a future feature, not just to solve the current problem.