Skip to content

Commit 2a123d4

Browse files
committed
chore: add docs
1 parent 49b3afe commit 2a123d4

File tree

16 files changed

+1616
-9
lines changed

16 files changed

+1616
-9
lines changed

guides/explanation/architecture.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Architecture and Core Concepts
2+
3+
This document explains the key architectural concepts behind ElixirCache and how its components work together.
4+
5+
## Core Architecture
6+
7+
ElixirCache is designed around a simple principle: provide a consistent interface to different caching backends. The architecture consists of:
8+
9+
1. **Core Interface**: Defined by the `Cache` module
10+
2. **Adapters**: Backend-specific implementations
11+
3. **Term Encoder**: Handles serialization and compression
12+
4. **Telemetry Integration**: For observability and metrics
13+
5. **Sandbox System**: For isolated testing
14+
15+
## The Cache Behaviour
16+
17+
At the heart of ElixirCache is the `Cache` behaviour, which defines the contract that all cache adapters must implement:
18+
19+
- `child_spec/1`: Defines how the cache is started and supervised
20+
- `opts_definition/0`: Defines adapter-specific options
21+
- `put/5`: Stores a value in the cache
22+
- `get/3`: Retrieves a value from the cache
23+
- `delete/3`: Removes a value from the cache
24+
25+
Each adapter implements these functions, allowing your application code to remain the same regardless of which cache backend you use.
26+
27+
## The `use Cache` Macro
28+
29+
When you `use Cache` in your module, the macro:
30+
31+
1. Sets up the configuration for your cache
32+
2. Creates the required module functions that delegate to the appropriate adapter
33+
3. Wraps operations in telemetry spans for metrics and observability
34+
4. Implements the `get_or_create/2` convenience function
35+
5. Adds sandboxing capabilities for testing if enabled
36+
37+
## Term Encoding and Compression
38+
39+
ElixirCache includes internal term encoding functionality that handles serialization and deserialization of Elixir terms. This allows you to store complex Elixir data structures in any cache backend. The encoding system:
40+
41+
1. Uses Erlang's term_to_binary for efficient serialization
42+
2. Applies configurable compression to reduce memory usage
43+
3. Automatically handles decoding when retrieving values
44+
45+
## Sandboxing for Tests
46+
47+
A unique feature of ElixirCache is its sandboxing capability for tests. When you enable sandboxing:
48+
49+
1. Each test has its own isolated cache namespace
50+
2. Cache operations are automatically prefixed with a test-specific ID
51+
3. Tests can run concurrently without cache interference or global overlap
52+
53+
This is achieved through the `Cache.SandboxRegistry` which maintains a registry of cache contexts.
54+
55+
## Adapters Design
56+
57+
Each adapter is designed to be a thin wrapper around the underlying storage mechanism:
58+
59+
### ETS Adapter
60+
61+
The ETS adapter uses Erlang Term Storage for high-performance in-memory caching. It:
62+
- Starts an ETS table with the configured settings
63+
- Provides direct access to ETS-specific functions
64+
- Handles conversion between the Cache interface and ETS operations
65+
66+
### DETS Adapter
67+
68+
Similar to the ETS adapter but uses disk-based storage for persistence across restarts.
69+
70+
### Redis Adapter
71+
72+
The Redis adapter provides a more feature-rich distributed caching solution:
73+
- Manages a connection pool to Redis
74+
- Handles serialization of Elixir terms for Redis storage
75+
- Provides access to Redis-specific operations like hash and JSON commands
76+
77+
### Agent Adapter
78+
79+
A simple implementation using Elixir's Agent for lightweight in-memory storage.
80+
81+
### ConCache Adapter
82+
83+
Wraps the ConCache library to provide its expiration and callback capabilities.
84+
85+
## Telemetry Integration
86+
87+
ElixirCache provides telemetry events for all cache operations:
88+
- `[:elixir_cache, :cache, :put]` - When storing values
89+
- `[:elixir_cache, :cache, :get]` - When retrieving values
90+
- `[:elixir_cache, :cache, :get, :miss]` - When a key is not found
91+
- `[:elixir_cache, :cache, :delete]` - When deleting values
92+
- Error events when operations fail
93+
94+
This allows you to monitor cache performance, hit/miss ratios, and error rates.

guides/how-to/choosing_adapter.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# How to Choose the Right Cache Adapter
2+
3+
ElixirCache supports multiple cache adapters, each with its own strengths and use cases. This guide will help you choose the most appropriate adapter for your specific needs.
4+
5+
## Available Adapters
6+
7+
ElixirCache provides the following adapters:
8+
9+
1. `Cache.ETS` - Erlang Term Storage
10+
2. `Cache.DETS` - Disk-based ETS
11+
3. `Cache.Redis` - Redis-backed distributed cache
12+
4. `Cache.Agent` - Simple Agent-based in-memory cache
13+
5. `Cache.ConCache` - ConCache wrapper
14+
6. `Cache.Sandbox` - Isolated cache for testing
15+
16+
## Choosing an Adapter
17+
18+
### Cache.ETS
19+
20+
**Best for:**
21+
- High-performance in-memory caching
22+
- Single node applications
23+
- Low-latency requirements
24+
25+
**Configuration example:**
26+
27+
```elixir
28+
defmodule MyApp.Cache do
29+
use Cache,
30+
adapter: Cache.ETS,
31+
name: :my_app_cache,
32+
opts: [
33+
read_concurrency: true,
34+
write_concurrency: true
35+
]
36+
end
37+
```
38+
39+
### Cache.DETS
40+
41+
**Best for:**
42+
- Persistent caching across application restarts
43+
- Larger datasets that shouldn't be lost on restart
44+
- Less frequent access patterns
45+
46+
**Configuration example:**
47+
48+
```elixir
49+
defmodule MyApp.PersistentCache do
50+
use Cache,
51+
adapter: Cache.DETS,
52+
name: :my_app_persistent_cache,
53+
opts: [
54+
file: "cache_data.dets"
55+
]
56+
end
57+
```
58+
59+
### Cache.Redis
60+
61+
**Best for:**
62+
- Distributed applications running on multiple nodes
63+
- Systems requiring shared cache across services
64+
- Applications needing advanced features like expiration, pub/sub, etc.
65+
66+
**Configuration example:**
67+
68+
```elixir
69+
defmodule MyApp.DistributedCache do
70+
use Cache,
71+
adapter: Cache.Redis,
72+
name: :my_app_redis_cache,
73+
opts: [
74+
host: "localhost",
75+
port: 6379,
76+
pool_size: 5
77+
]
78+
end
79+
```
80+
81+
### Cache.Agent
82+
83+
**Best for:**
84+
- Simple use cases
85+
- Small applications
86+
- Development environments
87+
88+
**Configuration example:**
89+
90+
```elixir
91+
defmodule MyApp.SimpleCache do
92+
use Cache,
93+
adapter: Cache.Agent,
94+
name: :my_app_simple_cache
95+
end
96+
```
97+
98+
### Cache.ConCache
99+
100+
**Best for:**
101+
- Applications already using ConCache
102+
- Needs for automatic key expiration and callback execution
103+
104+
**Configuration example:**
105+
106+
```elixir
107+
defmodule MyApp.ConCache do
108+
use Cache,
109+
adapter: Cache.ConCache,
110+
name: :my_app_con_cache,
111+
opts: [
112+
ttl_check_interval: :timer.seconds(1),
113+
global_ttl: :timer.minutes(10)
114+
]
115+
end
116+
```
117+
118+
### Cache.Sandbox
119+
120+
**Best for:**
121+
- Testing environments
122+
- Isolated tests that shouldn't interfere with each other
123+
124+
**Configuration example:**
125+
126+
```elixir
127+
defmodule MyApp.TestCache do
128+
use Cache,
129+
adapter: Cache.ETS,
130+
name: :my_app_test_cache,
131+
sandbox?: true
132+
end
133+
```
134+
135+
## Switching Between Adapters
136+
137+
One of the main benefits of ElixirCache is the ability to easily switch between adapters without changing your application code. You can use different adapters in different environments:
138+
139+
```elixir
140+
defmodule MyApp.Cache do
141+
use Cache,
142+
adapter: get_adapter(),
143+
name: :my_app_cache,
144+
opts: get_opts()
145+
146+
defp get_adapter do
147+
case Mix.env() do
148+
:test -> Cache.Sandbox
149+
:dev -> Cache.ETS
150+
:prod -> Cache.Redis
151+
end
152+
end
153+
154+
defp get_opts do
155+
case Mix.env() do
156+
:test -> []
157+
:dev -> [read_concurrency: true]
158+
:prod -> [
159+
host: System.get_env("REDIS_HOST", "localhost"),
160+
port: String.to_integer(System.get_env("REDIS_PORT", "6379")),
161+
pool_size: 10
162+
]
163+
end
164+
end
165+
end
166+
```
167+
168+
## Performance Considerations
169+
170+
When choosing an adapter, consider:
171+
172+
1. **Access patterns** - How frequently are you reading vs writing?
173+
2. **Data volume** - How much data will be stored?
174+
3. **Persistence requirements** - Does the data need to survive restarts?
175+
4. **Distribution needs** - Will multiple nodes/services need access?
176+
5. **Complexity** - Do you need advanced features or simple key-value storage?
177+
178+
Always benchmark different options with your specific workload to determine the best fit.

0 commit comments

Comments
 (0)