How Macro Manages Database Migrations Across Its SQLx Database Clients
Macro centralizes all PostgreSQL schema changes in a single macro_db_client crate, exposing migrations through a shared macro_db_migrator library that every downstream service consumes.
The Macro codebase follows a crate-based migration strategy to keep database schema changes consistent across multiple microservices. Rather than duplicating migration logic, the engineering team maintains one authoritative source in crates/macro_db_client and distributes changes via Cargo dependencies and the sqlx::migrate! macro.
Centralized Migration Storage
All .sql migration files live in crates/macro_db_client/migrations/. The naming convention follows chronological ordering:
0001_baseline.sql— initial schema20260715154137_add_entity_access_source_type_entity_plain_index.sql— incremental changes
This directory serves as the single source of truth for the entire Macro platform. Any schema modification made here automatically propagates to every service that depends on macro_db_client.
The Migration Distribution Architecture
Macro separates migration storage from migration execution across two crates:
| Component | Purpose | Key File |
|---|---|---|
macro_db_client |
Stores .sql files; shared dependency for all services |
crates/macro_db_client/migrations/ |
macro_db_migrator |
Exposes sqlx::migrate! macro to run migrations |
crates/macro_db_migrator/src/lib.rs |
In crates/macro_db_migrator/src/lib.rs, the migrations are exposed using SQLx's compile-time checked macro:
// Re-exports migrations from the canonical location
pub fn run(pool: &sqlx::PgPool) -> Result<()> {
sqlx::migrate!("../macro_db_client/migrations")
.run(pool)
.await
}
This indirection allows services to execute migrations without hardcoding paths to the migration directory.
How Services Consume Shared Migrations
Every service that touches PostgreSQL adds macro_db_client to its Cargo.toml:
[dependencies]
macro_db_client = { path = "../../crates/macro_db_client" }
To apply migrations, services invoke the migrator at startup or during CI:
use macro_db_migrator::run;
async fn initialize_database(pool: &sqlx::PgPool) -> anyhow::Result<()> {
run(pool).await?;
Ok(())
}
Test Integration with SQLx
Integration tests use the #[sqlx::test] attribute with an explicit migrations path. This ensures each test runs against a fresh database with the latest schema:
// Example from crates/stream/src/outbound/redis_pg/queries/test.rs
#[sqlx::test(migrations = "../macro_db_client/migrations")]
async fn test_document_queries(pool: PgPool) -> sqlx::Result<()> {
// Test logic executes against migrated schema
}
Local Development and CI Workflows
The xtask tooling standardizes migration commands across environments. In crates/macro_db_client/justfile:
# Create a fresh database for local development
create_db:
# Database creation logic
# Run pending SQLx migrations
migrate_db:
sqlx migrate run --source migrations/
These commands are invoked through the workspace-level justfile:
just crates/macro_db_client/create_db # fresh database
just crates/macro_db_client/migrate_db # apply migrations
Production and CI Safety
The same just commands wrap additional safety checks. In tooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs, migrations are referenced when previewing Fly.io deployments, with prompts that warn before applying changes to production databases.
Key Implementation Files
Understanding these files clarifies how database migrations are managed across Macro's SQLx clients:
crates/macro_db_client/migrations/— Canonical.sqlmigration seriescrates/macro_db_client/justfile— Local development commands (create_db,migrate_db)crates/macro_db_migrator/src/lib.rs—sqlx::migrate!wrapper exposing the migration setservices/*/Cargo.toml— Demonstratesmacro_db_clientdependency patterncrates/stream/src/outbound/redis_pg/queries/test.rs—#[sqlx::test(migrations = "...")]usagetooling/xtask/crates/xtask_local/src/local/db.rs— Local snapshot migration runnertooling/xtask/crates/xtask_workflows/src/workflows/preview_fly.rs— Production deployment migration handling
Summary
- Single crate ownership:
macro_db_clienthouses all.sqlmigrations, preventing drift across services - Macro-based distribution:
sqlx::migrate!("../macro_db_client/migrations")inmacro_db_migrator/src/lib.rscompiles migrations into consumable code - Dependency sharing: Services import
macro_db_clientvia path dependencies inCargo.toml - Test synchronization:
#[sqlx::test(migrations = "...")]guarantees consistent schema in integration tests - Unified tooling:
justcommands incrates/macro_db_client/justfilestandardize local and CI workflows
Frequently Asked Questions
How do Macro's microservices stay synchronized with database schema changes?
Services depend on macro_db_client via Cargo path dependencies. When the migration files in crates/macro_db_client/migrations/ change, developers update their lockfiles and receive the new schema on next build. The shared macro_db_migrator::run() function ensures all services apply identical migrations.
Can individual services override or skip migrations?
No. The architecture intentionally prevents this. Services call macro_db_migrator::run() which hardcodes the path to ../macro_db_client/migrations. There is no mechanism to inject alternative migration sources without forking the migrator crate.
How are migrations tested before production deployment?
Integration tests use #[sqlx::test(migrations = "../macro_db_client/migrations")] to spin up isolated PostgreSQL instances with the full schema. CI pipelines run these tests across all dependent crates. The xtask_workflows crate additionally validates migrations during Fly.io preview deployments.
What prevents accidental production database modifications?
The xtask tooling layers interactive prompts onto migration commands for production environments. While Local development uses just crates/macro_db_client/migrate_db directly, production workflows in tooling/xtask/crates/xtask_workflows/ require explicit confirmation before executing schema changes against live databases.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →