DBIX::Class:: DeploymentHandler (aka DBICDH)

Arthur Axel "fRIOUX" Schmidt

What is DBICDH?

DBIx::Class::DeploymentHandler - DBIx::Class::Schema deployment for multiple versions.

  • w/o multiple versions ->deploy is certainly easier

Why not Schema::Versioned?

YUCK.

sub _set_db_version {
  my $self = shift;
  my ($params) = @_;
  $params ||= {};

  my $version = $params->{version} ? $params->{version} : $self->schema_version;
  my $vtable = $self->{vschema}->resultset('Table');

  ##############################################################################
  #                             !!! NOTE !!!
  ##############################################################################
  #
  # The travesty below replaces the old nice timestamp format of %Y-%m-%d %H:%M:%S
  # This is necessary since there are legitimate cases when upgrades can happen
  # back to back within the same second. This breaks things since we relay on the
  # ability to sort by the 'installed' value. The logical choice of an autoinc
  # is not possible, as it will break multiple legacy installations. Also it is
  # not possible to format the string sanely, as the column is a varchar(20).
  # The 'v' character is added to the front of the string, so that any version
  # formatted by this new function will sort _after_ any existing 200... strings.
  my @tm = gettimeofday();
  my @dt = gmtime ($tm[0]);
  my $o = $vtable->create({
    version => $version,
    installed => sprintf("v%04d%02d%02d_%02d%02d%02d.%03.0f",
      $dt[5] + 1900,
      $dt[4] + 1,
      $dt[3],
      $dt[2],
      $dt[1],
      $dt[0],
      $tm[1] / 1000, # convert to millisecs, format as up/down rounded int above
    ),
  });
}

DBICDH is a Transformer

VersionHandler

VersionStorage

DeployMethod

Introduction to the defaults

There are already a lot of different options for building your own DBICDH, we'll talk about the defaults (aka recommended) here.

Monotonic Version Handler

The monotonic version handler uses integer only versions, which follow naturally per revision or release or whatever. So you first version would be 1, the second would be 2, etc.

The main benefit of monotonic is that it is perfectly simple and predictable. No issues of how versions are parsed or ordered or any of that.

SQL::Translator DM

The SQLT deploy method uses SQLT to generate SQL for installs, upgrades, and downgrades. In addition to that it does the actual running of SQL and Perl scripts and defines the directory layout for the scripts.

SQLTDM - Perl Scripts

A Perl script used by SQLTDM is actually very similar to a .psgi script. It should just be an anonymous subref. Scripts for install, upgrade, and downgrade get the schema as their first argument. The initialize scripts are not passed any arguments.

SQLTDM - Serialized Schema

SQLTDM - Directory Layout

Note: this is inspired by DBIx::Migration::Directories

$sql_migration_dir
 |- _source
 |  `- deploy
 |     `- 1
 |        `- 001-auto.yml
 |- SQLite
 |  |- downgrade
 |  |  `- 2-1
 |  |     `- 001-auto.sql
 |  |- initialize
 |  |  `- 1
 |  |     |- 001-create_database.pl
 |  |     `- 002-create_users_and_permissions.pl
 |  |- deploy
 |  |  `- 1
 |  |     `- 001-auto.sql
 |  `- upgrade
 |     |- 1-2
 |     |  `- 001-auto.sql
 |     `- 2-3
 |        `- 001-auto.sql
 |- _common
    |- downgrade
    |  `- 2-1
    |     `- 002-remove-customers.pl
    `- upgrade
       `- 1-2
          `- 002-generate-customers.pl
   

Standard Version Storage

Not a whole lot worth saying here. The main change from ::Versioned is that instead of that crazy date thing I pointed out above it uses a simple autoincrementing id.

General Usage

initialize

The initialize stage, while not required, should vastly reduce the amount of manual intervention required for a deploy. Typical usage will probably be something like having it do CREATE DATABASE FOO as well as adding a few users to the database and whatnot.

Because initialize is such an open-ended stage, it currently only supports running perl scripts, instead of perl and sql like the rest of the stages.

initialize example

# $migrations/MySQL/initialize/1/001_create_db.pl
sub {
   use DBI;
   my $user     = 'root';
   my $password = 'foo';
   my $dsn      = 'bar';

   # you should probably
   # prompt the user for
   # at least the password

   my $dbh      = DBI->connect($dsn, $user, $password);
   $dbh->do('CREATE DATABASE frewmbot')
      or die 'Shucks howdy!  FAIL:' . $@;
};

install

The install stage is where the schema gets deployed, as well as your version storage. In general this will be mostly automatic, unless you want to do something like prepopulate a users table.

install example

# $migrations/_common/install/1/01_create_usr.pl
sub {
   my $schema = shift;
   $schema->resultset('User')->populate([
      [qw( username password )],
      [qw( frew     test     )],
   ]);
};

upgrade

This stage is where your schema gets modified. If you are doing simple changes like adding/removing tables/columns it should be entirely automatic. If you are renaming things or transforming data you'll need to add some scripts yourself.

upgrade example

# $migrations/_common/upgrade/1-2/01_hash_pass.pl
sub {
   my $schema = shift;
   $_->update({
      password => $_->get_column('password')
   }) for $schema->resultset('User')->all;
};

_source

and _preprocess_schema

Workflows

catered to team size

Small Team - People

Small Team - Goals

Small Team

How DBICDH can help

Small Team

How DBICDH can help (2)

Medium Team - People

Medium Team - Goals

Medium Team - Goals (2)

Medium Team

How DBICDH can help

Medium Team

How DBICDH can help (2)

Large Team - People

Large Team - Goals

Large Team - Goals (2)

Large Team - Goals (3)

Large Team

How DBICDH can help

Large Team

How DBICDH can help (2)

DBIx::Class::Fixtures

A side trip

Large Team

How DBICDH can help (3)

PRO TIPS

Implementation Side Trip

Delegation

DO BOTH

Implementation Side Trip

END

Customization

VersionStorage

Changing table name

DBICDH Storage

package MyApp::Schema::DBICDHStorage;
use Moose;
extends
   'DBIx::Class::DeploymentHandler' .
   '::VersionStorage::Standard';

sub _build_version_rs {
  $_[0]->schema->register_class(
    __VERSION =>
      'MyApp::Schema::DBICDHStorageResult'
  );
  $_[0]->schema->resultset('__VERSION')
}

no Moose;
__PACKAGE__->meta->make_immutable;
1;

DBICDH Storage, pt 2

DBIC Result

package MyApp::Schema::DBICDHStorageResult;
use parent
   'DBIx::Class::DeploymentHandler' .
   '::VersionStorage::Standard::VersionResult';
__PACKAGE__->table('fl_bench_journal_versions');
1;

DBIC Result, pt 2

Custom DBICDH

package MyApp::Schema::DeploymentHandler;
use Moose;
extends 'DBIx::Class::DeploymentHandler::Dad';

with 'DBIx::Class::DeploymentHandler::WithApplicatorDumple' => {
    interface_role       => 'DBIx::Class::DeploymentHandler::HandlesDeploy',
    class_name           =>
       'DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator',
    delegate_name        => 'deploy_method',
    attributes_to_assume => ['schema'],
    attributes_to_copy   => [qw( databases script_directory sql_translator_args )],
  },
  'DBIx::Class::DeploymentHandler::WithApplicatorDumple' => {
    interface_role       => 'DBIx::Class::DeploymentHandler::HandlesVersioning',
    class_name           => 'DBIx::Class::DeploymentHandler::VersionHandler::Monotonic',
    delegate_name        => 'version_handler',
    attributes_to_assume => [qw( database_version schema_version to_version )],
  },
  'DBIx::Class::DeploymentHandler::WithApplicatorDumple' => {
    interface_role       => 'DBIx::Class::DeploymentHandler::HandlesVersionStorage',
    class_name           => 'MyApp::Schema::DBICDHStorage',
    delegate_name        => 'version_storage',
    attributes_to_assume => ['schema'],
  };

Custom DBICDH (2)

with 'DBIx::Class::DeploymentHandler::WithReasonableDefaults';

sub prepare_version_storage_install {
  $_[0]->prepare_resultsource_install(
    $_[0]->version_storage->version_rs->result_source
  );
}

sub install_version_storage {
  $_[0]->install_resultsource(
    $_[0]->version_storage->version_rs->result_source
  );
}

sub prepare_install {
   $_[0]->prepare_deploy;
   $_[0]->prepare_version_storage_install;
}

no Moose;
__PACKAGE__->meta->make_immutable;
1;

VersionStorage: etc

VersionHandler

VersionHandler: branches

VersionHandler

git handwaving

VersionHandler

how it might work

Questions?

THE END