Packages

Consistent Hash Ring implementation in Elixir with Clean Architecture

Current section

Files

Jump to
ex_ring_ring guides architecture.md
Raw

guides/architecture.md

# Architecture
ExRingRing follows **Clean Architecture** principles with a strict separation of concerns across three layers:
## Layer Overview
```
lib/ex_ring_ring/
├── domain/ # Core business logic
│ ├── entities/ Ring, Node
│ ├── value_objects/ HashConfig, VirtualNode
│ └── services/ HashRing (behaviour), HashAlgorithm (behaviour)
├── application/ # Use cases
│ ├── services/ HashRingService
│ └── use_cases/ CreateRing, AddNodes, RemoveNodes, FindNode, CollectNodes
└── infrastructure/ # Concrete implementations
├── hash_algorithms/ MD5, SHA, SHA256, CRC32, PHash2
└── hash_ring/ Static, Dynamic, Base
```
## Domain Layer
The domain layer contains pure business logic with no external dependencies.
### Entities
- **`ExRingRing.Domain.Entities.Ring`** — The hash ring. A struct containing:
- `impl_module` — The behaviour module implementing ring logic (Static or Dynamic)
- `impl_state` — The internal state of that implementation
- `config` — A `HashConfig` value object
- **`ExRingRing.Domain.Entities.Node`** — A physical node on the ring:
- `key` — Used for hashing and identification
- `data` — Arbitrary user payload (server address, connection info, etc.)
- `weight` — Controls virtual node count (0 = phantom/standby)
### Value Objects
- **`HashConfig`** — Immutable configuration holding algorithm, virtual node count, max hash byte size, and computed hash mask.
- **`VirtualNode`** — A point on the ring circle linking a hash value to a physical node.
### Domain Services (Behaviours)
- **`ExRingRing.Domain.Services.HashRing`** — Behaviour defining the contract for ring implementations: `make/2`, `add_nodes/2`, `remove_nodes/2`, `get_nodes/1`, `fold/4`, `get_non_phantom_node_count/1`, `hash/2`.
- **`ExRingRing.Domain.Services.HashAlgorithm`** — Behaviour defining hash algorithm contract: `hash/1`, `hash_byte_size/0`, `algorithm_name/0`.
## Application Layer
Orchestrates domain logic for specific use cases. Each use case is a module with a single `execute` or `call` function.
- **`CreateRing`** — Builds a ring from nodes or keys with configuration options
- **`AddNodes`** — Adds one or more nodes to an existing ring
- **`RemoveNodes`** — Removes one or more nodes by key
- **`FindNode`** — Finds the node responsible for an item
- **`CollectNodes`** — Collects N distinct nodes for replication
**`HashRingService`** is the application service that delegates to use cases. The main `ExRingRing` module delegates to this service.
## Infrastructure Layer
Concrete implementations of domain behaviours.
### Hash Ring Implementations
All implementations live under `ExRingRing.Infrastructure.HashRing` and share a common **`Base`** module for state management (node tracking, hash calculation, phantom score).
- **`Static`** — Uses a sorted tuple of virtual nodes. Fast O(log N) lookups, O(N log N) modifications.
- **`Dynamic`** — Uses a `:gb_trees` balanced tree. O(log N) modifications, slightly slower lookups.
### Hash Algorithms
| Module | Algorithm | Bytes |
|--------|-----------|-------|
| `MD5` | MD5 | 16 |
| `SHA` | SHA-1 | 20 |
| `SHA256` | SHA-256 | 32 |
| `CRC32` | CRC32 | 4 |
| `PHash2` | Erlang `phash2` | 4 |
The `HashAlgorithms` registry module resolves algorithm names to their implementations.
## Data Flow
```
User Code
ExRingRing (public API)
HashRingService (application service)
├── CreateRing ──────────► HashConfig (config)
│ │
│ ▼
│ Static | Dynamic (impl)
│ │
│ ▼
│ Ring entity
├── AddNodes ────────────► Ring.update_state
├── RemoveNodes ─────────► Ring.update_state
├── FindNode ────────────► impl.fold
└── CollectNodes ────────► impl.fold
```