File Coverage

blib/lib/EventStore/Tiny.pm
Criterion Covered Total %
statement 70 70 100.0
branch 10 10 100.0
condition 11 11 100.0
subroutine 19 19 100.0
pod 8 8 100.0
total 118 118 100.0


line stmt bran cond sub pod time code
1             package EventStore::Tiny;
2              
3 6     6   143559 use strict;
  6         36  
  6         158  
4 6     6   27 use warnings;
  6         9  
  6         130  
5              
6 6     6   2226 use EventStore::Tiny::Logger;
  6         16  
  6         177  
7 6     6   2432 use EventStore::Tiny::Event;
  6         24  
  6         160  
8 6     6   2456 use EventStore::Tiny::DataEvent;
  6         16  
  6         157  
9 6     6   2307 use EventStore::Tiny::EventStream;
  6         19  
  6         147  
10 6     6   2137 use EventStore::Tiny::Snapshot;
  6         16  
  6         173  
11              
12 6     6   2343 use Clone qw(clone);
  6         12692  
  6         278  
13 6     6   3381 use Storable;
  6         16438  
  6         314  
14 6     6   2839 use Data::Compare; # Exports Compare()
  6         57224  
  6         36  
15              
16             # Enable handling of CODE refs (as event actions are code refs)
17             $Storable::Deparse = 1;
18             $Storable::Eval = 1;
19              
20             our $VERSION = '0.41';
21              
22             use Class::Tiny {
23 7         201 registry => sub {{}},
24 6         728 events => sub {EventStore::Tiny::EventStream->new(
25             logger => shift->logger)},
26 5         127 init_data => sub {{}},
27 4         82 logger => sub {EventStore::Tiny::Logger->log_cb},
28 6         84 cache_distance => 0, # Default: store snapshot each time. no caching: undef
29 6     6   19593 }, '_cached_snapshot';
  6         29  
30              
31             # Class method to construct
32             sub new_from_file {
33 1     1 1 4939 my (undef, $fn) = @_;
34 1         6 return retrieve($fn);
35             }
36              
37             sub store_to_file {
38 1     1 1 2214 my ($self, $fn) = @_;
39 1         30 return store($self, $fn);
40             }
41              
42             sub register_event {
43 15     15 1 8047 my ($self, $name, $transformation) = @_;
44              
45 15         249 return $self->registry->{$name} = EventStore::Tiny::Event->new(
46             name => $name,
47             transformation => $transformation,
48             logger => $self->logger,
49             );
50             }
51              
52             sub event_names {
53 2     2 1 1528 my $self = shift;
54 2         3 return [sort keys %{$self->registry}];
  2         42  
55             }
56              
57             sub store_event {
58 247     247 1 9175 my ($self, $name, $data) = @_;
59              
60             # Lookup template event
61 247         3150 my $template = $self->registry->{$name};
62 247 100       1128 die "Unknown event: $name!\n" unless defined $template;
63              
64             # Specialize event with new data
65 246         511 my $event = EventStore::Tiny::DataEvent->new_from_template(
66             $template, $data
67             );
68              
69             # Done
70 246         4176 return $self->events->add_event($event);
71             }
72              
73             sub init_state {
74 34     34 1 48 my $self = shift;
75              
76             # Clone init data
77 34         502 return clone($self->init_data);
78             }
79              
80             sub snapshot {
81 34     34 1 52331 my ($self, $timestamp) = @_;
82 34         79 my $state = $self->init_state;
83              
84             # Work on latest timestamp if not specified
85 34   100     680 $timestamp //= $self->events->last_timestamp;
86 34         962 my $es = $self->events->before($timestamp);
87              
88             # Check if the cached snapshot can be used
89 34         1318 my $cached_sn = $self->_cached_snapshot;
90 34 100 100     522 if (defined $cached_sn and $cached_sn->timestamp <= $timestamp) {
91 23         413 $state = clone $cached_sn->state;
92 23         632 $es = $es->after($cached_sn->timestamp);
93             }
94              
95             # Calculate snapshot
96 34   100     1205 my $snapshot = EventStore::Tiny::Snapshot->new(
97             state => $es->apply_to($state, $self->logger),
98             timestamp => $es->last_timestamp // 0,
99             );
100              
101             # Caching disabled: done
102 34 100       592 return $snapshot unless defined $self->cache_distance;
103              
104             # Cache snapshot if no cache present yet, but neccessary
105 32 100 100     570 $self->_cached_snapshot($snapshot)
106             if not defined $self->_cached_snapshot and $es->size > 0;
107              
108             # Cache snapshot if new event count > cache size
109             $self->_cached_snapshot($snapshot)
110 32 100       235 if @{$es->events} > $self->cache_distance;
  32         472  
111              
112             # Done
113 32         896 return $snapshot;
114             }
115              
116             sub is_correct_snapshot {
117 4     4 1 62 my ($self, $snapshot) = @_;
118              
119             # Replay events before snapshot time
120 4         63 my $our_sn = $self->snapshot($snapshot->timestamp);
121              
122             # True iff the generated state looks the same
123 4         116 return Compare($snapshot->state, $our_sn->state);
124             }
125              
126             1;
127              
128             =pod
129              
130             =encoding utf-8
131              
132             =head1 NAME
133              
134             EventStore::Tiny - A minimal event sourcing framework.
135              
136             =begin html
137              
138            

139              
140            
141             CPAN version
142            
143             Travis CI tests
144            
145             Codecov test coverage
146            
147             Coveralls test coverage
148            
149             CPANTS kwalitee score
150              
151            
152              
153            
154             CPAN testers reports
155            
156             CPAN testers matrix
157            
158             GitHub repository
159            
160             GitHub issue tracker
161              
162            

163              
164             =end html
165              
166             =head1 SYNOPSIS
167              
168             use EventStore::Tiny;
169              
170             my $store = EventStore::Tiny->new;
171              
172             # Register event type
173             $store->register_event(UserAdded => sub {
174             my ($state, $data) = @_;
175              
176             # Use $data to inject the new user into the given $state
177             $state->{users}{$data->{id}} = {
178             name => $data->{name},
179             };
180             });
181              
182             # ...
183              
184             # Store an event instance represented by type and data
185             $store->store_event(UserAdded => {id => 17, name => 'Bob'});
186              
187             # ...
188              
189             # Work with the current state snapshot generated by event application
190             say 'His name is ' . $store->snapshot->state->{users}{17}{name}; # Bob
191              
192             =head1 DESCRIPTION
193              
194             In Event Sourcing, the state of a system is calculated as the application of a stream of events representing each change of the system. This framework is a minimal approach to use these mechanics in simple perl systems and offers these features:
195              
196             =over 2
197              
198             =item *
199              
200             Flexible snapshots (high-resolution timestamps) and event substreams.
201              
202             =item *
203              
204             Customizable event logging.
205              
206             =item *
207              
208             Simple storage solution for events in the file system.
209              
210             =item *
211              
212             Transparent snapshot caching mechanism to improve performance.
213              
214             =back
215              
216             The internal state of the system needs to be represented by a simple (nested) hash and all events need to operate on this hash only (by side-effect).
217              
218             =head1 REFERENCE
219              
220             EventStore::Tiny implements the following attributes and methods, grouped by topic.
221              
222             =head2 CONSTRUCTION AND PERSISTENCE
223              
224             =head3 new
225              
226             my $store = EventStore::Tiny->new(init_data => {answer = 42});
227              
228             Standard constructor. Understands all attributes as arguments. For most use cases, these are the sensible arguments:
229              
230             =over 4
231              
232             =item init_data
233              
234             A hashref representing the initial state. B>
235              
236             =item cache_distance
237              
238             The number of events after a new snapshot is cached for accellerated access. 0 means the cache is updated after each event. undef means the system does not use any caching. B
239              
240             =item logger
241              
242             A subref (callback) which will be called each time an event is applied to the state. The callback gets this event as its only argument. B>
243              
244             =back
245              
246             =head3 new_from_file
247              
248             my $store = EventStore::Tiny->new_from_file($filename);
249              
250             Deserializes an existing store object which was Ld before.
251              
252             =head3 store_to_file
253              
254             $store->store_to_file($filename);
255              
256             Serializes the store object to the file system. It can be deserialized via L later.
257              
258             =head2 EVENT SOURCING WORKFLOW
259              
260             =head3 register_event
261              
262             $store->register_event(ConnectionRemoved => sub {
263             my ($state, $data) = @_;
264             # Change $state depending on $data (by side-effect)
265             });
266              
267             Stores an event type in the system by name and action on the C<$state>. Events of this type can be added later to the event store by setting concrete C<$data> with L.
268              
269             =head3 store_event
270              
271             $store->store_event(ConnectionRemoved => {id => 42});
272              
273             Stores a concrete instance of an event type in the event store. The instance is defined by its event type name and a hash of data used by the subref the event uses to manipulate the state.
274              
275             =head3 snapshot
276              
277             my $state1 = $store->snapshot->state;
278              
279             my $snapshot = $store->snapshot(1234217421);
280             my $state2 = $snapshot->state;
281             my $timestamp = $snapshot->timestamp; # 1234217421
282              
283             Returns a L object which basically consists of the corresponding state of the system (represented by a hashref) and the timestamp of the last used event. Snapshots are selected by the given argument timestamp, which returns the current snapshot at the given time. If no timestamp is given, the snapshot represents the last state of the system.
284              
285             =head2 INTROSPECTION
286              
287             =head3 event_names
288              
289             my $types = $store->event_names;
290              
291             Returns an arrayref containing all event type names of registered events, sorted by name. These names are the values of L.
292              
293             =head3 registry
294              
295             my $user_added = $store->registry->{UserAdded};
296              
297             Returns a hashref with event type names as keys and event types as values, which are L instances. Should be manipulated by L only.
298              
299             =head3 events
300              
301             my $event_stream = $store->events;
302              
303             Returns the internal L object that stores all concrete events (L instances). Should be manipulated by L only. Events should never be changed or removed.
304              
305             =head3 init_state
306              
307             my $state = $store->init_state;
308              
309             Returns a cloned copy of the ininitial state all events are applied on, which was defined by L as a hashref.
310              
311             =head2 OTHER
312              
313             =head3 is_correct_snapshot
314              
315             if ($store->is_correct_snapshot($snapshot)) {
316             # ...
317             }
318              
319             Checks if a given L instance is a valid snapshot of our L event store. Mostly used for testing.
320              
321             =head1 REPOSITORY AND ISSUE TRACKER
322              
323             EventStore::Tiny's source repository is hosted on L together with an issue tracker.
324              
325             =head1 COPYRIGHT AND LICENSE
326              
327             Copyright (c) 2018 L (L<@memowe|https://github.com/memowe>, L)
328              
329             Released under the MIT License (see LICENSE.txt for details).
330              
331             =head2 CONTRIBUTORS
332              
333             =over 2
334              
335             =item *
336              
337             Mohammad S Anwar (L<@manwar|https://github.com/manwar>)
338              
339             =item *
340              
341             Toby Inkster (L<@tobyink|https://github.com/tobyink>)
342              
343             =back
344              
345             =cut