File Coverage

blib/lib/Starch/Store/Layered.pm
Criterion Covered Total %
statement 41 41 100.0
branch 7 10 70.0
condition n/a
subroutine 11 11 100.0
pod 3 3 100.0
total 62 65 95.3


line stmt bran cond sub pod time code
1             package Starch::Store::Layered;
2 1     1   514 use 5.008001;
  1         3  
3 1     1   6 use strictures 2;
  1         8  
  1         40  
4             our $VERSION = '0.11';
5              
6             =head1 NAME
7              
8             Starch::Store::Layered - Layer multiple Starch stores.
9              
10             =head1 SYNOPSIS
11              
12             my $starch = Starch->new(
13             expires => 2 * 60 * 60, # 2 hours
14             store => {
15             class => '::Layered',
16             outer => {
17             class=>'::CHI',
18             max_expires => 10 * 60, # 10 minutes
19             ...,
20             },
21             inner => {
22             class=>'::MongoDB',
23             ...,
24             },
25             },
26             );
27              
28             =head1 DESCRIPTION
29              
30             This store provides the ability to declare two stores that act
31             in a layered fashion where all writes (C and C) are
32             applied to both stores but all reads (C) are attempted, first,
33             on the L store, and if that fails the read is attempted in
34             the L store.
35              
36             When C is called, if the outer store did not have the data,
37             but the inner store did, then the data will be automatically
38             written to the outer store.
39              
40             The most common use-case for this store is for placing a cache in
41             front of a persistent store. Typically caches are much faster than
42             persistent storage engines.
43              
44             Another use case is for migrating from one store to another. Your
45             new store would be set as the inner store, and your old store
46             would be set as the outer store. Once sufficient time has passed,
47             and the new store has been populated, you could switch to using
48             just the new store.
49              
50             If you'd like to layer more than two stores you can use layered
51             stores within layered stores.
52              
53             =cut
54              
55 1     1   246 use Types::Standard -types;
  1         2  
  1         17  
56 1     1   4800 use Scalar::Util qw( blessed );
  1         3  
  1         55  
57              
58 1     1   6 use Moo;
  1         3  
  1         7  
59 1     1   398 use namespace::clean;
  1         2  
  1         8  
60              
61             with qw(
62             Starch::Store
63             );
64              
65             after BUILD => sub{
66             my ($self) = @_;
67              
68             # Load these up as early as possible.
69             $self->outer();
70             $self->inner();
71              
72             return;
73             };
74              
75             =head1 REQUIRED ARGUMENTS
76              
77             =head2 outer
78              
79             This is the outer store, the one that tries to handle read requests
80             first before falling back to the L store.
81              
82             Accepts the same value as L.
83              
84             =cut
85              
86             has _outer_arg => (
87             is => 'ro',
88             isa => HashRef,
89             required => 1,
90             init_arg => 'outer',
91             );
92              
93             has outer => (
94             is => 'lazy',
95             isa => ConsumerOf[ 'Starch::Store' ],
96             init_arg => undef,
97             );
98             sub _build_outer {
99 5     5   47 my ($self) = @_;
100 5         13 my $store = $self->_outer_arg();
101 5         20 return $self->new_sub_store( %$store );
102             }
103              
104             =head2 inner
105              
106             This is the inner store, the one that only handles read requests
107             if the L store was unable to.
108              
109             Accepts the same value as L.
110              
111             =cut
112              
113             has _inner_arg => (
114             is => 'ro',
115             isa => HashRef,
116             required => 1,
117             init_arg => 'inner',
118             );
119              
120             has inner => (
121             is => 'lazy',
122             isa => ConsumerOf[ 'Starch::Store' ],
123             init_arg => undef,
124             );
125             sub _build_inner {
126 5     5   49 my ($self) = @_;
127 5         13 my $store = $self->_inner_arg();
128 5         60 return $self->new_sub_store( %$store );
129             }
130              
131             =head1 ATTRIBUTES
132              
133             =head2 can_reap_expired
134              
135             Return true if either the L or L stores support the
136             L method.
137              
138             =cut
139              
140             sub can_reap_expired {
141 1     1 1 3 my ($self) = @_;
142 1 50       23 return 1 if $self->outer->can_reap_expired();
143 1 50       18 return 1 if $self->inner->can_reap_expired();
144 1         5 return 0;
145             }
146              
147             =head1 METHODS
148              
149             =head2 reap_expired
150              
151             Calls L on the L and L
152             stores, if they support expired state reaping.
153              
154             =cut
155              
156             around reap_expired => sub{
157             my ($orig, $self) = @_;
158              
159             # Go ahead and throw the exception provided by Starch::Store::reap_expired.
160             return $self->$orig() if !$self->can_reap_expired();
161              
162             $self->outer->reap_expired() if $self->outer->can_reap_expired();
163             $self->inner->reap_expired() if $self->inner->can_reap_expired();
164              
165             return;
166             };
167              
168             =head2 set
169              
170             Set L.
171              
172             =head2 get
173              
174             Set L.
175              
176             =head2 remove
177              
178             Set L.
179              
180             =cut
181              
182             sub set {
183             my $self = shift;
184             $self->outer->set( @_ );
185             $self->inner->set( @_ );
186             return;
187             }
188              
189             sub get {
190 6     6 1 17 my ($self, $key, $namespace) = @_;
191              
192 6         104 my $data = $self->outer->get( $key, $namespace );
193 6 100       27 return $data if $data;
194              
195 3         48 $data = $self->inner->get( $key, $namespace );
196 3 100       18 return undef if !$data;
197              
198             # Now we got the data from the inner store but not the outer store.
199             # Let's set it on the outer store so that we can retrieve it from
200             # there next time.
201              
202 1         7 my $expires = $data->{ $self->manager->expires_state_key() };
203 1 50       7 $expires = $self->manager->expires() if !defined $expires;
204              
205             # Make sure we take into account max_expires.
206 1         4 $expires = $self->calculate_expires( $expires );
207              
208 1         17 $self->outer->set( $key, $namespace, $data, $expires );
209              
210 1         6 return $data;
211             }
212              
213             sub remove {
214 2     2 1 4 my $self = shift;
215 2         50 $self->outer->remove( @_ );
216 2         37 $self->inner->remove( @_ );
217 2         5 return;
218             }
219              
220             1;
221             __END__