File Coverage

blib/lib/Starch/Store/Layered.pm
Criterion Covered Total %
statement 39 39 100.0
branch 7 10 70.0
condition n/a
subroutine 10 10 100.0
pod 3 3 100.0
total 59 62 95.1


line stmt bran cond sub pod time code
1             package Starch::Store::Layered;
2             our $VERSION = '0.14';
3              
4             =encoding utf8
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   567 use Scalar::Util qw( blessed );
  1         3  
  1         66  
56 1     1   7 use Types::Standard -types;
  1         2  
  1         15  
57              
58 1     1   4626 use Moo;
  1         3  
  1         8  
59 1     1   469 use strictures 2;
  1         11  
  1         44  
60 1     1   218 use namespace::clean;
  1         2  
  1         12  
61              
62             with 'Starch::Store';
63              
64             after BUILD => sub{
65             my ($self) = @_;
66              
67             # Load these up as early as possible.
68             $self->outer();
69             $self->inner();
70              
71             return;
72             };
73              
74             =head1 REQUIRED ARGUMENTS
75              
76             =head2 outer
77              
78             This is the outer store, the one that tries to handle read requests
79             first before falling back to the L store.
80              
81             Accepts the same value as L.
82              
83             =cut
84              
85             has _outer_arg => (
86             is => 'ro',
87             isa => HashRef,
88             required => 1,
89             init_arg => 'outer',
90             );
91              
92             has outer => (
93             is => 'lazy',
94             isa => ConsumerOf[ 'Starch::Store' ],
95             init_arg => undef,
96             );
97             sub _build_outer {
98 5     5   52 my ($self) = @_;
99 5         15 my $store = $self->_outer_arg();
100 5         25 return $self->new_sub_store( %$store );
101             }
102              
103             =head2 inner
104              
105             This is the inner store, the one that only handles read requests
106             if the L store was unable to.
107              
108             Accepts the same value as L.
109              
110             =cut
111              
112             has _inner_arg => (
113             is => 'ro',
114             isa => HashRef,
115             required => 1,
116             init_arg => 'inner',
117             );
118              
119             has inner => (
120             is => 'lazy',
121             isa => ConsumerOf[ 'Starch::Store' ],
122             init_arg => undef,
123             );
124             sub _build_inner {
125 5     5   51 my ($self) = @_;
126 5         15 my $store = $self->_inner_arg();
127 5         24 return $self->new_sub_store( %$store );
128             }
129              
130             =head1 ATTRIBUTES
131              
132             =head2 can_reap_expired
133              
134             Return true if either the L or L stores support the
135             L method.
136              
137             =cut
138              
139             sub can_reap_expired {
140 1     1 1 3 my ($self) = @_;
141 1 50       23 return 1 if $self->outer->can_reap_expired();
142 1 50       18 return 1 if $self->inner->can_reap_expired();
143 1         7 return 0;
144             }
145              
146             =head1 METHODS
147              
148             =head2 reap_expired
149              
150             Calls L on the L and L
151             stores, if they support expired state reaping.
152              
153             =cut
154              
155             around reap_expired => sub{
156             my ($orig, $self) = @_;
157              
158             # Go ahead and throw the exception provided by Starch::Store::reap_expired.
159             return $self->$orig() if !$self->can_reap_expired();
160              
161             $self->outer->reap_expired() if $self->outer->can_reap_expired();
162             $self->inner->reap_expired() if $self->inner->can_reap_expired();
163              
164             return;
165             };
166              
167             =head2 set
168              
169             Set L.
170              
171             =head2 get
172              
173             Set L.
174              
175             =head2 remove
176              
177             Set L.
178              
179             =cut
180              
181             sub set {
182             my $self = shift;
183             $self->outer->set( @_ );
184             $self->inner->set( @_ );
185             return;
186             }
187              
188             sub get {
189 6     6 1 20 my ($self, $key, $namespace) = @_;
190              
191 6         101 my $data = $self->outer->get( $key, $namespace );
192 6 100       35 return $data if $data;
193              
194 3         49 $data = $self->inner->get( $key, $namespace );
195 3 100       19 return undef if !$data;
196              
197             # Now we got the data from the inner store but not the outer store.
198             # Let's set it on the outer store so that we can retrieve it from
199             # there next time.
200              
201 1         8 my $expires = $data->{ $self->manager->expires_state_key() };
202 1 50       8 $expires = $self->manager->expires() if !defined $expires;
203              
204             # Make sure we take into account max_expires.
205 1         3 $expires = $self->calculate_expires( $expires );
206              
207 1         17 $self->outer->set( $key, $namespace, $data, $expires );
208              
209 1         5 return $data;
210             }
211              
212             sub remove {
213 2     2 1 5 my $self = shift;
214 2         52 $self->outer->remove( @_ );
215 2         35 $self->inner->remove( @_ );
216 2         5 return;
217             }
218              
219             1;
220             __END__