App-Config-Chronicle
view release on metacpan or search on metacpan
chronicle_reader
The chronicle store that configurations can be fetch from it. It should
be an instance of Data::Chronicle::Reader. But user is free to
implement any storage backend he wants if it is implemented with a
'get' method.
chronicle_writer
The chronicle store that updated configurations can be stored into it.
It should be an instance of Data::Chronicle::Writer. But user is free
to implement any storage backend he wants if it is implemented with a
'set' method.
chronicle_subscriber
The chronicle connection that can notify via callbacks when particular
configuration items have a new value set. It should be an instance of
Data::Chronicle::Subscriber.
refresh_interval
How much time (in seconds) should pass between check_for_update
invocations until it actually will do (a bit heavy) lookup for settings
in redis.
Default value is 10 seconds
check_for_update
check and load updated settings from chronicle db
Checks at most every refresh_interval unless forced with a truthy first
argument
save_dynamic
Save dynamic settings into chronicle db
current_revision
lib/App/Config/Chronicle.pm view on Meta::CPAN
=cut
has chronicle_reader => (
is => 'ro',
isa => 'Data::Chronicle::Reader',
required => 1,
);
=head2 chronicle_writer
The chronicle store that updated configurations can be stored into it. It should be an instance of L<Data::Chronicle::Writer>.
But user is free to implement any storage backend he wants if it is implemented with a 'set' method.
=cut
has chronicle_writer => (
is => 'rw',
isa => 'Data::Chronicle::Writer',
);
=head2 chronicle_subscriber
lib/App/Config/Chronicle.pm view on Meta::CPAN
=cut
has refresh_interval => (
is => 'ro',
isa => 'Num',
required => 1,
default => 10,
);
has _updated_at => (
is => 'rw',
isa => 'Num',
required => 1,
default => 0,
);
# definitions database
has _defdb => (
is => 'rw',
lazy => 1,
lib/App/Config/Chronicle.pm view on Meta::CPAN
die "Variable with name $key found under "
. $section->path
. ".\n$key is an internally used variable and cannot be reused, please use a different name";
}
return;
}
=head2 check_for_update
check and load updated settings from chronicle db
Checks at most every C<refresh_interval> unless forced with
a truthy first argument
=cut
sub check_for_update {
my ($self, $force) = @_;
return unless $force or $self->_has_refresh_interval_passed();
$self->_updated_at(Time::HiRes::time());
# do check in Redis
my $data_set = $self->data_set;
my $app_settings = $self->chronicle_reader->get($self->setting_namespace, $self->setting_name);
my $db_version;
if ($app_settings and $data_set) {
$db_version = $app_settings->{_rev};
unless ($data_set->{version} and $db_version and $db_version eq $data_set->{version}) {
# refresh all
lib/App/Config/Chronicle.pm view on Meta::CPAN
}
}
$settings->{global} = $global->data;
$settings->{_rev} = Time::HiRes::time();
$self->chronicle_writer->set($self->setting_namespace, $self->setting_name, $settings, Date::Utility->new);
# since we now have the most recent data, we better set the
# local version as well.
$self->data_set->{version} = $settings->{_rev};
$self->_updated_at($settings->{_rev});
return 1;
}
=head2 current_revision
Loads setting from chronicle reader and returns the last revision
It is more likely that you want L</loaded_revision> in regular use
lib/App/Config/Chronicle.pm view on Meta::CPAN
Loads latest values from data chronicle into local cache.
Calls to this method are rate-limited by C<refresh_interval>.
=cut
sub update_cache {
my $self = shift;
die 'Local caching not enabled' unless $self->local_caching;
return unless $self->_has_refresh_interval_passed();
$self->_updated_at(Time::HiRes::time());
return unless $self->_is_cache_stale();
my $keys = [$self->dynamic_keys(), '_global_rev'];
my @all_entries = $self->_retrieve_objects_from_chron($keys);
$self->_store_objects_in_cache({map { $keys->[$_] => $all_entries[$_] } (0 .. $#$keys)});
return 1;
}
sub _has_refresh_interval_passed {
my $self = shift;
my $now = Time::HiRes::time();
my $prev_update = $self->_updated_at;
my $time_since_prev_update = $now - $prev_update;
return ($time_since_prev_update >= $self->refresh_interval);
}
sub _is_cache_stale {
my $self = shift;
my @rev_cache = $self->_retrieve_objects_from_cache(['_global_rev']);
my @rev_chron = $self->_retrieve_objects_from_chron(['_global_rev']);
return !($rev_cache[0] && $rev_chron[0] && $rev_cache[0]->{data} eq $rev_chron[0]->{data});
}
lib/App/Config/Chronicle.pod view on Meta::CPAN
The YAML file that store the configuration
=head2 chronicle_reader
The chronicle store that configurations can be fetch from it. It should be an instance of L<Data::Chronicle::Reader>.
But user is free to implement any storage backend he wants if it is implemented with a 'get' method.
=head2 chronicle_writer
The chronicle store that updated configurations can be stored into it. It should be an instance of L<Data::Chronicle::Writer>.
But user is free to implement any storage backend he wants if it is implemented with a 'set' method.
=head2 chronicle_subscriber
The chronicle connection that can notify via callbacks when particular configuration items have a new value set. It should be an instance of L<Data::Chronicle::Subscriber>.
=head2 refresh_interval
How much time (in seconds) should pass between L<check_for_update> invocations until
it actually will do (a bit heavy) lookup for settings in redis.
Default value is 10 seconds
=head2 check_for_update
check and load updated settings from chronicle db
Checks at most every C<refresh_interval> unless forced with
a truthy first argument
=head2 save_dynamic
Save dynamic settings into chronicle db
=head2 current_revision
t/07_full_build.t view on Meta::CPAN
chronicle_writer => $chronicle_w,
);
}
'We are living';
ok($app_config->system->isa('App::Config::Chronicle::Attribute::Section'), 'system is a Section');
is_deeply($app_config->system->admins, [], "admins is empty by default");
my $old_revision = $app_config->current_revision;
$app_config->system->email('test@abc.com');
$app_config->save_dynamic;
is_deeply($app_config->system->email, 'test@abc.com', "email is updated");
my $new_revision = $app_config->current_revision;
isnt($new_revision, $old_revision, "revision updated");
is($app_config->loaded_revision, $new_revision, "Loaded revision matches current revision");
my $app_config2 = App::Config::Chronicle->new(
definition_yml => "$Bin/test.yml",
chronicle_reader => $chronicle_r,
chronicle_writer => $chronicle_w,
refresh_interval => 1,
);
is($app_config2->current_revision, $new_revision, "revision is correct even if we create a new instance");
is($app_config2->system->email, 'test@abc.com', "email is updated");
# force check & trigger internal timer
$app_config2->check_for_update;
$app_config->system->email('test2@abc.com');
$app_config->save_dynamic;
# will not refresh as not enough time has passed
$app_config2->check_for_update;
is($app_config2->system->email, 'test@abc.com', "still have old value");
cmp_ok($app_config2->loaded_revision, '<=', $app_config2->current_revision, "Loaded revision is older than current revision");
{
no warnings 'redefine';
t/08_new_api.t view on Meta::CPAN
local_caching => 1,
refresh_interval => 0
);
my $direct_config = _new_app_config(local_caching => 0);
ok $direct_config->set({EMAIL_KEY() => FIRST_EMAIL}), 'Set email succeeds';
is $direct_config->get(EMAIL_KEY), FIRST_EMAIL, 'Email is retrieved successfully';
is $cached_config1->get(EMAIL_KEY), DEFAULT_EMAIL, 'Cache1 contains default before first update call';
is $cached_config2->get(EMAIL_KEY), DEFAULT_EMAIL, 'Cache2 contains default before first update call';
ok $cached_config1->update_cache(), 'Cache 1 is updated';
ok $cached_config2->update_cache(), 'Cache 2 is updated';
is $cached_config1->get(EMAIL_KEY), FIRST_EMAIL, 'Cache1 is updated with email';
is $cached_config2->get(EMAIL_KEY), FIRST_EMAIL, 'Cache2 is updated with email';
set_fixed_time(++$tick); #Ensure new value is recorded at a different time
ok $cached_config1->set({EMAIL_KEY() => SECOND_EMAIL}), 'Set email via cache 1 succeeds';
is $direct_config->get(EMAIL_KEY), SECOND_EMAIL, 'Email is retrieved directly';
is $cached_config1->get(EMAIL_KEY), SECOND_EMAIL, 'Cache1 has updated email';
is $cached_config2->get(EMAIL_KEY), FIRST_EMAIL, 'Cache2 still has old email';
ok $cached_config2->update_cache(), 'Cache 2 is updated';
is $cached_config2->get(EMAIL_KEY), SECOND_EMAIL, 'Cache2 has updated email';
};
subtest 'Cache refresh_interval' => sub {
my $cached_config = _new_app_config(
local_caching => 1,
refresh_interval => 2
);
my $direct_config = _new_app_config(local_caching => 0);
set_fixed_time(++$tick); #Ensure new value is recorded at a different time
ok $direct_config->set({EMAIL_KEY() => FIRST_EMAIL}), 'Set email succeeds';
is $direct_config->get(EMAIL_KEY), FIRST_EMAIL, 'Email is retrieved successfully';
ok $cached_config->update_cache(), 'Cache is updated';
is $cached_config->get(EMAIL_KEY), FIRST_EMAIL, 'Email is retrieved successfully';
set_fixed_time(++$tick); #Ensure new value is recorded at a different time
ok $direct_config->set({EMAIL_KEY() => SECOND_EMAIL}), 'Set email succeeds';
is $direct_config->get(EMAIL_KEY), SECOND_EMAIL, 'Email is retrieved successfully';
ok !$cached_config->update_cache(), 'update not done due to refresh_interval';
is $cached_config->get(EMAIL_KEY), FIRST_EMAIL, "Cache still has old value since interval hasn't passed";
set_fixed_time($tick + $cached_config->refresh_interval);
ok $cached_config->update_cache(), 'Cache is updated';
is $cached_config->get(EMAIL_KEY), SECOND_EMAIL, 'Email is retrieved successfully from updated cache';
};
sub _new_app_config {
my $app_config;
my %options = @_;
subtest 'Setup' => sub {
my ($chronicle_r, $chronicle_w) = Data::Chronicle::Mock::get_mocked_chronicle();
lives_ok {
$app_config = App::Config::Chronicle->new(
( run in 0.340 second using v1.01-cache-2.11-cpan-05444aca049 )