Migrations
Sometimes, it is necessary to refactor a data model after it has already been released to
production. The migrations
module provides a mechanism to either apply
and rollback
a
migration. This mechanism is limited in its opinions and feature set, since migrations are highly
contextual by their nature. Migration script authors are responsible for ensuring that their
migrations are either idempotent and safe to re-run, or for checking the migration log to
determine if a script has already been applied.
Migration Component Model
A migration script can be defined by implement the Migration
trait which specifies the following
interface.
#![allow(unused)] fn main() { pub trait Migration { /// The human readable name of the migration. fn name(&self) -> String; /// The set of operations to apply to a database to migrate it to a desired new state. async fn commit(&self, service: &MigrationService) -> Result<LogEntry, Error>; /// The set of operations to undo a previously committed migration. async fn rollback(&self, service: &MigrationService) -> Result<LogEntry, Error>; } }
A Migration
implementation can then be passed to the MigrationService
along with an Effect
enum. The Effect
enum specifies whether to apply or rollback the migration.
#![allow(unused)] fn main() { impl MigrationService { /// Factory method for creating a new `MigrationService` instance. pub async fn new(ctx: &Context) -> Result<MigrationService, Error> { Ok(MigrationService { ctx: ctx.clone() }) } /// Applies a migration with the specified effect. pub async fn apply( &self, migration: &impl Migration, effect: Effect, ) -> Result<LogEntry, Error> { match effect { Effect::Commit => migration.commit(self).await, Effect::Rollback => migration.rollback(self).await, } } } }
User code is required to coordinate the application of migrations, in which order, and to what effect.