changed the balancer to a role, created a new class to define the default balancer...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Balancer.pm
index bf47c07..ed7007d 100644 (file)
@@ -1,6 +1,7 @@
 package DBIx::Class::Storage::DBI::Replicated::Balancer;
 
-use Moose;
+use Moose::Role;
+requires 'next_storage';
 
 =head1 NAME
 
@@ -8,8 +9,7 @@ DBIx::Class::Storage::DBI::Replicated::Balancer; A Software Load Balancer
 
 =head1 SYNOPSIS
 
-This class is used internally by L<DBIx::Class::Storage::DBI::Replicated>.  You
-shouldn't need to create instances of this class.
+This role is used internally by L<DBIx::Class::Storage::DBI::Replicated>.
     
 =head1 DESCRIPTION
 
@@ -21,6 +21,34 @@ method by which query load can be spread out across each replicant in the pool.
 
 This class defines the following attributes.
 
+=head2 auto_validate_every ($seconds)
+
+If auto_validate has some sort of value, run the L<validate_replicants> every
+$seconds.  Be careful with this, because if you set it to 0 you will end up
+validating every query.
+
+=cut
+
+has 'auto_validate_every' => (
+    is=>'rw',
+    isa=>'Int',
+    predicate=>'has_auto_validate_every',
+);
+
+=head2 master
+
+The L<DBIx::Class::Storage::DBI> object that is the master database all the
+replicants are trying to follow.  The balancer needs to know it since it's the
+ultimate fallback.
+
+=cut
+
+has 'master' => (
+    is=>'ro',
+    isa=>'DBIx::Class::Storage::DBI',
+    required=>1,
+);
+
 =head2 pool
 
 The L<DBIx::Class::Storage::DBI::Replicated::Pool> object that we are trying to
@@ -70,25 +98,49 @@ Lazy builder for the L</current_replicant_storage> attribute.
 
 sub _build_current_replicant {
     my $self = shift @_;
-    $self->next_storage($self->pool);
+    $self->next_storage;
 }
 
 =head2 next_storage
 
+This method should be defined in the class which consumes this role.
+
 Given a pool object, return the next replicant that will serve queries.  The
 default behavior is to grap the first replicant it finds but you can write 
 your own subclasses of L<DBIx::Class::Storage::DBI::Replicated::Balancer> to 
 support other balance systems.
 
+This returns from the pool of active replicants.  If there are no active
+replicants, then you should have it return the master as an ultimate fallback.
+
+=head2 around: next_storage
+
+Advice on next storage to add the autovalidation.  We have this broken out so
+that it's easier to break out the auto validation into a role.
+
+This also returns the master in the case that none of the replicants are active
+or just just forgot to create them :)
+
 =cut
 
-sub next_storage {
-       my $self = shift @_;
-       return ($self->pool->active_replicants)[0]
-         if $self->pool->active_replicants;
-}
+around 'next_storage' => sub {
+       my ($next_storage, $self, @args) = @_;
+       my $now = time;
+       
+    ## Do we need to validate the replicants?
+    if(
+       $self->has_auto_validate_every && 
+       ($self->auto_validate_every + $self->pool->last_validated) <= $now
+    ) {
+        $self->pool->validate_replicants;
+    }
+    
+    ## Get a replicant, or the master if none
+    my $next = $self->$next_storage(@args);
+    return $next ? $next:$self->master;        
+};
 
-=head2 after: select
+=head2 before: select
 
 Advice on the select attribute.  Each time we use a replicant
 we need to change it via the storage pool algorithm.  That way we are spreading
@@ -96,13 +148,13 @@ the load evenly (hopefully) across existing capacity.
 
 =cut
 
-after 'select' => sub {
+before 'select' => sub {
     my $self = shift @_;
     my $next_replicant = $self->next_storage;
     $self->current_replicant($next_replicant);
 };
 
-=head2 after: select_single
+=head2 before: select_single
 
 Advice on the select_single attribute.  Each time we use a replicant
 we need to change it via the storage pool algorithm.  That way we are spreading
@@ -110,13 +162,13 @@ the load evenly (hopefully) across existing capacity.
 
 =cut
 
-after 'select_single' => sub {
+before 'select_single' => sub {
     my $self = shift @_;
     my $next_replicant = $self->next_storage;
     $self->current_replicant($next_replicant);
 };
 
-=head2 after: columns_info_for
+=head2 before: columns_info_for
 
 Advice on the current_replicant_storage attribute.  Each time we use a replicant
 we need to change it via the storage pool algorithm.  That way we are spreading
@@ -124,7 +176,7 @@ the load evenly (hopefully) across existing capacity.
 
 =cut
 
-after 'columns_info_for' => sub {
+before 'columns_info_for' => sub {
     my $self = shift @_;
     my $next_replicant = $self->next_storage;
     $self->current_replicant($next_replicant);