App-Config-Chronicle

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN


 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 )