README.md

# CacheWorker

Defines a behavior to be implemented for managing data that should be held in the VM and periodically refreshed.

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `cache_worker` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:cache_worker, "~> 0.1.0"}
  ]
end
```

## Guide

The data is organized as a key/val data store called a bucket where the
keys and vals can be any data type.

Any errors generated by the callback functions will be caught and logged
by the callback functions, thus allowing for an insulated, reliable
experience for consumers, trying to access the data.

If you wanted to keep track of some widgets, you might create your module
thusly:

```elixir
defmodule WidgetStore do
  use CacheWorker, bucket: :widgets, file: "/tmp/widget-dump"

  def load(key) do
    {:ok, Somewhere.get_widget(key)}
  end
end
```

Add the `WidgetStore` module to your supervision tree. This will add the
data refresher agent to handle updating the keys on the refresh interval.

```elixir
children = [
  WidgetStore,
  ...,
]

Supervisor.start_link(children, opts)
```

When WidgetStore is started, it will load the dump file if one was
configured. If a `:refresh_interval` was configured, a full refresh of all
the data will be triggered at that interval, forever.

The `&load/1` function on your `WidgetStore` module defines how the value
for a given key is found and is called on whenever (1) a value is being
requested and isn't yet in the bucket, or (2) is being refreshed by the
cache worker process.

To get a widget, all a consumer needs to do is call `&WidgetStore.get(key)`
and it will be returned. (`&fetch/1` does similarly, but provides more
information.) The value from the cache will be used unless it's the first
time for the particular key. In this case, `&WidgetStore.load/1` will be
dispatched for the value, which will then be cached and returned.

Steps Taken on a `&fetch(key)` Request:

    `&fetch("key")`
    --> [Cache] => *from cache* `{:ok, "value from cache"}`
        -or-> *no cache* `&load("key")` => `{:error, "Something went wrong!"}`
            -or-> *Saved to the cache* => `{:ok, "Hot & fresh value!"}`

### Options for `use CacheWorker`

The `:bucket` option must be defined directly in the `use` options.

The cascading logic for finding the final list of options is as follows,
with each step having the opportunity to override the last:

* `%CacheWorker.Config{}` struct defaults
* Any options provided to `use`
* Any options provided to `&child_spec/1`. (eg: `{WidgetStore, file: "foo"}`)

In the `use` options, you may pass in any of the values described for the
struct fields in `CacheWorker.Config`.

### The `&load/1` Callback

This is the only callback you *must* implement. It should fetch and return
the data for the given key. This function should return one of the
following:

* `{:ok, value}` - the value for the given key is being returned (and should
be added to the bucket)
* `{:ok, value, map}` - the value for the given key is being returned, but
should not be automatically cached. Instead, all key/val pairs provided by
the map in the third tuple element should be added to the bucket
* `{:error, message}` - an error should be logged and nothing should be added
to the bucket. Note that `&get/1` will return `nil` and `&fetch/1` will
return the same error tuple.

### The `&init/1` Callback

You might like to override the default implementation for the `&init/1`
callback in order to take certain steps when your cache worker starts up,
the most obvious of which would be seeding your bucket with certain data.

Simply override the `&init/1` callback and return one of the following:

* `:ok` - simply indicates that we've started up and are good to go
* `{:ok, map}` - indicates that the bucket should be seeded with key/val
pairs returned as a map
* `{:error, message}` - indicates that an error should be logged, but the
worker should start up normally, otherwise
* `{:stop, message}` - indicates that our GenServer startup should fail,
fatally.

### Error Insulation

Cache workers will catch and log any errors that arise from `init()` or
`load()`. Callers will not need to be overly cautious (catching errors) when
reaching for data if they prefer to gracefully continue on failures.

### Options for `&fetch/1` and `&get/2`

These functions allow access to the data in the cache worker's bucket. The
following options are available:

* `:skip_save` - When set to `true` and `&load/1` is invoked to load a value,
that value will not be saved to the cache as usual. Instead, it's expected
that the calling code will manually call `&direct_set(key, value)`. This is
useful if the new data needs to be tested in a pipeline before committed to
the cache.