Julien
Julien

Reputation: 5779

Does DBIx::Class have transparent caching?

In the C#/.Net world, there are ORMs such as NHibernate or ActiveRecord that includes transparent caching: database updates are transparently replicated to the cache, objects are retrieved directly from the cache when available, etc (often with memcached).

It doesn't look like transparent caching is available in Perl with DBIx::Class. Did I miss something? That seems like a common need, I'm surprised I couldn't find anything on it on CPAN or Google.

Upvotes: 9

Views: 2429

Answers (4)

bytepusher
bytepusher

Reputation: 1578

I would like to add that, instead of adding a 'search' method in My::Table,

one can also enhance the ->search method provided by DBIx::Class::ResultSet, like so:

package Schema::ResultSet::My::Table;
use base 'DBIx::Class::ResultSet';

sub search {
    my ( $self, $args ) = ( shift, shift );

    # do what you want here with the args passed to ->search
    return $self->next::method( $args, @_ );
}

Also, you can very likely subclass ResultSet so you can provide this altered ( cached ) search to all ResultSets, thus keeping the caching code in one place for all tables, which would be a lot less messy imho.

This I have not tested yet, though.

To make the above example work, put it in a file with the name of your schema class, in the directory "../Schema/ResultSet/", and make sure your Schema.pm contains "load_namespaces();" which will nicely auto-load all your overloaded classes that you put there ( I think my Catalyst install did that automatically, but I do not recall ).

DBIx::Class::ResultSet

Upvotes: 0

redhotpenguin
redhotpenguin

Reputation: 49

I have come across this same need with my DBIx::Class based model, and after reviewing the answers here I don't really see anything that is the solution I'm looking for. After struggling with this issue, I'm starting to think that my business layer should handle the caching, so that I treat DBIx::Class as a persistence layer that does not implement business logic.

For example, my current code with ideal caching would be something like this:

my $network = SL::Model::App->resultset('Network')->search({ ip => '127.0.0.1' });

And the $network object is served from the $memcached cache that I configured during DBIx::Class schema initialization

The new code would be:

my $network = SL::Network->find_by_ip_or_create({ ip => '127.0.0.1' });

Meanwhile, in a nearby module:

package SL::Network;
...
use SL::Model::App;
use SL::Cache;

our $cache = SL::Cache->new;

sub find_by_ip_or_create {
    my ($class, $args) = @_;

    my $network;
    unless ($network = $cache->get('network|' . $args->{ip}) {
        $network = SL::Model::App->resultset('Network')->find_or_create({ wan_ip => $args->{ip}});
        $cache->set('network|' . $args->{ip} => DBIx::Class::Schema->freeze($network));
    }
    return $network;

}

You get the idea.

Upvotes: 1

friedo
friedo

Reputation: 67048

Here's a simple way that you could add caching with CHI. I haven't actually tried this, so there may be pitfalls I haven't considered, especially with regard to the serialization of DBIC result sets.

package My::Table;
use strict; 
use warnings;

use base 'DBIx::Class';

use Storable 'freeze';
use CHI;

$Storable::canonical = 1;

__PACKAGE__->load_components(qw/Core/);
__PACKAGE__->table('mytable');

# ....

my $CACHE = CHI->new( driver => 'Memory' );

sub search { 
    my $self = shift;

    my $key = freeze( \@_ );      # make cache key from params
    if ( my $rs = $CACHE->get( $key ) ) { 
        return $rs;
    }

    # Note: there are issues with context propagation here
    my $rs = $self->next::method( @_ );
    $CACHE->set( $key => $rs );
    return $rs;
}

sub update { 
    my $self = shift;

    my @keys = $self->find_all_cache_items_affected_by_this_update( @_ );
    $CACHE->remove( $_ ) for @keys;

    $self->next::method( @_ );
}

It's a bit clunky, but I think it's a good starting point. If you do this type of thing in a base class for all your DBIx::Class table classes, you should be able to build in transparent caching pretty easily.

Upvotes: 6

MkV
MkV

Reputation: 3096

Semi-transparently there is DBIx::Class::Cursor::Cached (from mst, like DBIC). You need to provide a Cache object to your connections or schema objects though. Seems very undocumented unfortunately.

The Cookbook does have an example for using Tie::Cache on DBIC, and there are also the (get|set|clear)_cache functions on DBIx::Class::ResultSet, but they are probably not exactly what you need.

Upvotes: 6

Related Questions