# Public API Documentation
## Table of Contents
1. [Overview](#overview)
2. [Getting Started](#getting-started)
3. [Core Foundation API (`Foundation`)](#core-foundation-api-foundation)
4. [Configuration API (`Foundation.Config`)](#configuration-api-foundation-config)
5. [Events API (`Foundation.Events`)](#events-api-foundation-events)
6. [Telemetry API (`Foundation.Telemetry`)](#telemetry-api-foundation-telemetry)
7. [Utilities API (`Foundation.Utils`)](#utilities-api-foundation-utils)
8. [Service Registry API (`Foundation.ServiceRegistry`)](#service-registry-api-foundation-serviceregistry)
9. [Process Registry API (`Foundation.ProcessRegistry`)](#process-registry-api-foundation-processregistry)
10. [Infrastructure Protection API (`Foundation.Infrastructure`)](#infrastructure-protection-api-foundation-infrastructure)
11. [Error Context API (`Foundation.ErrorContext`)](#error-context-api-foundation-errorcontext)
12. [Error Handling & Types (`Foundation.Error`, `Foundation.Types.Error`)](#error-handling-types-foundation-error-foundation-types-error)
13. [Performance Considerations](#performance-considerations)
14. [Best Practices](#best-practices)
15. [Complete Examples](#complete-examples)
16. [Migration Guide](#migration-guide)
17. [Support and Resources](#support-and-resources)
## Overview
The Foundation Library provides core utilities, configuration management, event handling, telemetry, service registration, infrastructure protection patterns, and robust error handling for Elixir applications. It offers a clean, well-documented API designed for both ease of use and enterprise-grade performance.
### Key Features
- **🔧 Configuration Management** - Runtime configuration with validation and hot-reloading
- **📦 Event System** - Structured event creation, storage, and querying
- **📊 Telemetry Integration** - Comprehensive metrics collection and monitoring
- **🔗 Service & Process Registry** - Namespaced service discovery and management
- **🛡️ Infrastructure Protection** - Circuit breakers, rate limiters, connection pooling
- **✨ Error Context** - Enhanced error reporting with operational context
- **🛠️ Core Utilities** - Essential helper functions for common tasks
- **⚡ High Performance** - Optimized for low latency and high throughput
- **🧪 Test-Friendly** - Built-in support for isolated testing and namespacing
### System Requirements
- Elixir 1.15+ and OTP 26+
- Memory: Variable, base usage ~5MB + resources per service/event
- CPU: Optimized for multi-core systems
### Performance Characteristics
| Operation | Time Complexity | Typical Latency | Memory Usage |
|-----------|----------------|-----------------|--------------|
| Config Get | O(1) | < 1μs | Minimal |
| Config Update | O(1) + validation | < 100μs | Minimal |
| Event Creation | O(1) | < 10μs | ~200 bytes |
| Event Storage | O(1) | < 50μs | ~500 bytes |
| Event Query | O(log n) to O(n) | < 1ms | Variable |
| Service Lookup | O(1) | < 1μs | Minimal |
| Telemetry Emit | O(1) | < 5μs | ~100 bytes |
| Registry Operations | O(1) | < 1μs | ~100 bytes |
---
## Getting Started
### Installation
Add Foundation to your `mix.exs`:
```elixir
def deps do
[
{:foundation, "~> 0.1.0"} # Or the latest version
]
end
```
### Basic Setup (in your Application's `start/2` callback)
```elixir
def start(_type, _args) do
# Start Foundation's supervision tree
children = [
Foundation.Application # Starts all Foundation services
]
# Alternatively, if you need to initialize Foundation manually:
# {:ok, _} = Foundation.initialize()
# ... your application's children
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
```
### Quick Example
```elixir
# Ensure Foundation is initialized (typically done by Foundation.Application)
# Foundation.initialize()
# Configure a setting
:ok = Foundation.Config.update([:ai, :planning, :sampling_rate], 0.8)
# Create and store an event
correlation_id = Foundation.Utils.generate_correlation_id()
{:ok, event} = Foundation.Events.new_event(:user_action, %{action: "login"}, correlation_id: correlation_id)
{:ok, event_id} = Foundation.Events.store(event)
# Emit telemetry
:ok = Foundation.Telemetry.emit_counter([:myapp, :user, :login_attempts], %{user_id: 123})
# Use a utility
unique_op_id = Foundation.Utils.generate_id()
# Register a service
:ok = Foundation.ServiceRegistry.register(:production, :my_service, self())
# Use infrastructure protection
result = Foundation.Infrastructure.execute_protected(
:external_api_call,
[circuit_breaker: :api_fuse, rate_limiter: {:api_user_rate, "user_123"}],
fn -> ExternalAPI.call() end
)
```
---
## Core Foundation API (`Foundation`)
The `Foundation` module is the main entry point for managing the Foundation layer itself.
### initialize/1
Initialize the entire Foundation layer. This typically ensures `Config`, `Events`, and `Telemetry` services are started. Often called by `Foundation.Application`.
```elixir
@spec initialize(opts :: keyword()) :: :ok | {:error, Error.t()}
```
**Parameters:**
- `opts` (optional) - Initialization options for each service (e.g., `config: [debug_mode: true]`).
**Example:**
```elixir
:ok = Foundation.initialize(config: [debug_mode: true])
# To initialize with defaults:
:ok = Foundation.initialize()
```
### status/0
Get comprehensive status of all core Foundation services (`Config`, `Events`, `Telemetry`).
```elixir
@spec status() :: {:ok, map()} | {:error, Error.t()}
```
**Returns:**
- `{:ok, status_map}` where `status_map` contains individual statuses.
**Example:**
```elixir
{:ok, status} = Foundation.status()
# status might be:
# %{
# config: %{status: :running, uptime_ms: 3600000},
# events: %{status: :running, event_count: 15000},
# telemetry: %{status: :running, metrics_count: 50}
# }
```
### available?/0
Check if all core Foundation services (`Config`, `Events`, `Telemetry`) are available.
```elixir
@spec available?() :: boolean()
```
**Example:**
```elixir
if Foundation.available?() do
IO.puts("Foundation layer is ready.")
end
```
### health/0
Get detailed health information for monitoring, including service status, Elixir/OTP versions.
```elixir
@spec health() :: {:ok, map()} | {:error, Error.t()}
```
**Returns:**
- `{:ok, health_map}` with keys like `:status` (`:healthy`, `:degraded`), `:timestamp`, `:services`, etc.
**Example:**
```elixir
{:ok, health_info} = Foundation.health()
IO.inspect(health_info.status) # :healthy
```
### version/0
Get the version string of the Foundation library.
```elixir
@spec version() :: String.t()
```
**Example:**
```elixir
version_string = Foundation.version()
# "0.1.0"
```
### shutdown/0
Gracefully shut down the Foundation layer and its services. This is typically managed by the application's supervision tree.
```elixir
@spec shutdown() :: :ok
```
---
## Configuration API (`Foundation.Config`)
The Configuration API provides centralized configuration management with runtime updates, validation, and subscriber notifications.
### initialize/0, initialize/1
Initialize the configuration service. Usually called by `Foundation.initialize/1`.
```elixir
@spec initialize() :: :ok | {:error, Error.t()}
@spec initialize(opts :: keyword()) :: :ok | {:error, Error.t()}
```
**Parameters:**
- `opts` (optional) - Configuration options
**Returns:**
- `:ok` on success
- `{:error, Error.t()}` on failure
**Example:**
```elixir
# Initialize with defaults
:ok = Foundation.Config.initialize()
# Initialize with custom options
:ok = Foundation.Config.initialize(cache_size: 1000)
```
### status/0
Get the status of the configuration service.
```elixir
@spec status() :: {:ok, map()} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, status} = Foundation.Config.status()
# Returns: {:ok, %{
# status: :running,
# uptime_ms: 3600000,
# updates_count: 15,
# subscribers_count: 3
# }}
```
### get/0, get/1
Retrieve the entire configuration or a specific value by path.
```elixir
@spec get() :: {:ok, Foundation.Types.Config.t()} | {:error, Error.t()}
@spec get(path :: [atom()]) :: {:ok, term()} | {:error, Error.t()}
```
**Parameters:**
- `path` (optional) - Configuration path as list of atoms
**Returns:**
- `{:ok, value}` - Configuration value
- `{:error, Error.t()}` - Error if path not found or service unavailable
**Examples:**
```elixir
# Get complete configuration
{:ok, config} = Foundation.Config.get()
# Get specific value
{:ok, provider} = Foundation.Config.get([:ai, :provider])
# Returns: {:ok, :mock}
# Get nested value
{:ok, timeout} = Foundation.Config.get([:interface, :query_timeout])
# Returns: {:ok, 10000}
# Handle missing path
{:error, error} = Foundation.Config.get([:nonexistent, :path])
# Returns: {:error, %Error{error_type: :config_path_not_found}}
```
### get_with_default/2
Retrieve a configuration value, returning a default if the path is not found.
```elixir
@spec get_with_default(path :: [atom()], default :: term()) :: term()
```
**Example:**
```elixir
timeout = Foundation.Config.get_with_default([:my_feature, :timeout_ms], 5000)
```
### update/2
Update a configuration value at runtime. Only affects paths listed by `updatable_paths/0`.
```elixir
@spec update(path :: [atom()], value :: term()) :: :ok | {:error, Error.t()}
```
**Parameters:**
- `path` - Configuration path (must be in updatable paths)
- `value` - New value
**Returns:**
- `:ok` on successful update
- `{:error, Error.t()}` on validation failure or forbidden path
**Examples:**
```elixir
# Update sampling rate
:ok = Foundation.Config.update([:ai, :planning, :sampling_rate], 0.8)
# Update debug mode
:ok = Foundation.Config.update([:dev, :debug_mode], true)
# Try to update forbidden path
{:error, error} = Foundation.Config.update([:ai, :api_key], "secret")
# Returns: {:error, %Error{error_type: :config_update_forbidden}}
```
### safe_update/2
Update configuration if the path is updatable and not forbidden, otherwise return an error.
```elixir
@spec safe_update(path :: [atom()], value :: term()) :: :ok | {:error, Error.t()}
```
### updatable_paths/0
Get a list of configuration paths that can be updated at runtime.
```elixir
@spec updatable_paths() :: [[atom(), ...], ...]
```
**Returns:**
- List of updatable configuration paths
**Example:**
```elixir
paths = Foundation.Config.updatable_paths()
# Returns: [
# [:ai, :planning, :sampling_rate],
# [:ai, :planning, :performance_target],
# [:capture, :processing, :batch_size],
# [:dev, :debug_mode],
# ...
# ]
```
### subscribe/0, unsubscribe/0
Subscribe/unsubscribe the calling process to/from configuration change notifications.
```elixir
@spec subscribe() :: :ok | {:error, Error.t()}
@spec unsubscribe() :: :ok | {:error, Error.t()}
```
**Returns:**
- `:ok` on success
- `{:error, Error.t()}` on failure
Messages are received as `{:config_notification, {:config_updated, path, new_value}}`.
**Example:**
```elixir
# Subscribe to config changes
:ok = Foundation.Config.subscribe()
# You'll receive messages like:
# {:config_notification, {:config_updated, [:dev, :debug_mode], true}}
# Unsubscribe
:ok = Foundation.Config.unsubscribe()
```
### available?/0
Check if the configuration service is available.
```elixir
@spec available?() :: boolean()
```
**Example:**
```elixir
true = Foundation.Config.available?()
```
### reset/0
Reset configuration to its default values.
```elixir
@spec reset() :: :ok | {:error, Error.t()}
```
---
## Events API (`Foundation.Events`)
The Events API provides structured event creation, storage, querying, and serialization capabilities.
### initialize/0, status/0
Initialize/get status of the event store service.
```elixir
@spec initialize() :: :ok | {:error, Error.t()}
@spec status() :: {:ok, map()} | {:error, Error.t()}
```
**Examples:**
```elixir
# Initialize events system
:ok = Foundation.Events.initialize()
# Get status
{:ok, status} = Foundation.Events.status()
# Returns: {:ok, %{status: :running, event_count: 1500, next_id: 1501}}
```
### new_event/2, new_event/3
Create a new structured event. The 2-arity version uses default options. The 3-arity version accepts `opts` like `:correlation_id`, `:parent_id`.
```elixir
@spec new_event(event_type :: atom(), data :: term()) :: {:ok, Event.t()} | {:error, Error.t()}
@spec new_event(event_type :: atom(), data :: term(), opts :: keyword()) :: {:ok, Event.t()} | {:error, Error.t()}
```
**Parameters:**
- `event_type` - Event type atom
- `data` - Event data (any term)
- `opts` - Optional parameters (correlation_id, parent_id, etc.)
**Returns:**
- `{:ok, Event.t()}` - Created event
- `{:error, Error.t()}` - Validation error
**Examples:**
```elixir
# Simple event
{:ok, event} = Foundation.Events.new_event(:user_login, %{user_id: 123})
# Event with correlation ID
{:ok, event} = Foundation.Events.new_event(
:payment_processed,
%{amount: 100.0, currency: "USD"},
correlation_id: "req-abc-123"
)
# Event with parent relationship
{:ok, parent_event} = Foundation.Events.new_event(
:request_start,
%{path: "/api/users"},
correlation_id: "req-abc-123"
)
{:ok, child_event} = Foundation.Events.new_event(
:validation_step,
%{step: "auth"},
correlation_id: "req-abc-123",
parent_id: parent_event.event_id
)
```
### store/1, store_batch/1
Store events in the event store.
```elixir
@spec store(event :: Event.t()) :: {:ok, event_id :: Event.event_id()} | {:error, Error.t()}
@spec store_batch(events :: [Event.t()]) :: {:ok, [event_id :: Event.event_id()]} | {:error, Error.t()}
```
**Parameters:**
- `event` - Event to store
- `events` - List of events for batch storage
**Returns:**
- `{:ok, event_id}` or `{:ok, [event_ids]}` - Assigned event IDs
- `{:error, Error.t()}` - Storage error
**Examples:**
```elixir
# Store single event
{:ok, event} = Foundation.Events.new_event(:user_action, %{action: "click"})
{:ok, event_id} = Foundation.Events.store(event)
# Store multiple events atomically
{:ok, events} = create_multiple_events()
{:ok, event_ids} = Foundation.Events.store_batch(events)
```
### get/1
Retrieve a stored event by its ID.
```elixir
@spec get(event_id :: Event.event_id()) :: {:ok, Event.t()} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, event} = Foundation.Events.get(12345)
```
### query/1
Query stored events.
```elixir
@spec query(query_params :: map() | keyword()) :: {:ok, [Event.t()]} | {:error, Error.t()}
```
**Query Parameters (as map keys or keyword list):**
- `:event_type` - Filter by event type
- `:time_range` - `{start_time, end_time}` tuple
- `:limit` - Maximum number of results
- `:offset` - Number of results to skip
- `:order_by` - `:event_id` or `:timestamp`
**Examples:**
```elixir
# Query by event type
{:ok, events} = Foundation.Events.query(%{
event_type: :user_login,
limit: 100
})
# Query with time range
start_time = System.monotonic_time() - 3600_000_000_000 # 1 hour ago in nanoseconds
end_time = System.monotonic_time()
{:ok, events} = Foundation.Events.query(%{
time_range: {start_time, end_time},
limit: 500
})
# Query recent events
{:ok, recent_events} = Foundation.Events.query(%{
limit: 50,
order_by: :timestamp
})
```
### get_by_correlation/1
Retrieve all events matching a correlation ID, sorted by timestamp.
```elixir
@spec get_by_correlation(correlation_id :: String.t()) :: {:ok, [Event.t()]} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, related_events} = Foundation.Events.get_by_correlation("req-abc-123")
# Returns all events that share the correlation ID, sorted by timestamp
```
### Convenience Event Creators
#### function_entry/5
Creates a `:function_entry` event.
```elixir
@spec function_entry(module :: module(), function :: atom(), arity :: arity(), args :: [term()], opts :: keyword()) :: {:ok, Event.t()} | {:error, Error.t()}
```
#### function_exit/7
Creates a `:function_exit` event.
```elixir
@spec function_exit(module :: module(), function :: atom(), arity :: arity(), call_id :: Event.event_id(), result :: term(), duration_ns :: non_neg_integer(), exit_reason :: atom()) :: {:ok, Event.t()} | {:error, Error.t()}
```
#### state_change/5
Creates a `:state_change` event.
```elixir
@spec state_change(server_pid :: pid(), callback :: atom(), old_state :: term(), new_state :: term(), opts :: keyword()) :: {:ok, Event.t()} | {:error, Error.t()}
```
**Examples:**
```elixir
# Create function entry event
{:ok, entry_event} = Foundation.Events.function_entry(
MyModule, :my_function, 2, [arg1, arg2]
)
# Create function exit event
{:ok, exit_event} = Foundation.Events.function_exit(
MyModule, :my_function, 2, call_id, result, duration_ns, :normal
)
# Create state change event
{:ok, state_event} = Foundation.Events.state_change(
server_pid, :handle_call, old_state, new_state
)
```
### Other Convenience Queries
#### get_correlation_chain/1
Retrieves events for a correlation ID, sorted.
```elixir
@spec get_correlation_chain(correlation_id :: String.t()) :: {:ok, [Event.t()]} | {:error, Error.t()}
```
#### get_time_range/2
Retrieves events in a monotonic time range.
```elixir
@spec get_time_range(start_time :: integer(), end_time :: integer()) :: {:ok, [Event.t()]} | {:error, Error.t()}
```
#### get_recent/1
Retrieves the most recent N events.
```elixir
@spec get_recent(limit :: non_neg_integer()) :: {:ok, [Event.t()]} | {:error, Error.t()}
```
**Examples:**
```elixir
# Get correlation chain
{:ok, chain} = Foundation.Events.get_correlation_chain("req-abc-123")
# Get events in time range
{:ok, events} = Foundation.Events.get_time_range(start_time, end_time)
# Get recent events
{:ok, recent} = Foundation.Events.get_recent(100)
```
### Serialization
#### serialize/1
Serializes an event to binary.
```elixir
@spec serialize(event :: Event.t()) :: {:ok, binary()} | {:error, Error.t()}
```
#### deserialize/1
Deserializes binary to an event.
```elixir
@spec deserialize(binary :: binary()) :: {:ok, Event.t()} | {:error, Error.t()}
```
#### serialized_size/1
Calculates the size of a serialized event.
```elixir
@spec serialized_size(event :: Event.t()) :: {:ok, non_neg_integer()} | {:error, Error.t()}
```
**Examples:**
```elixir
# Serialize event to binary
{:ok, event} = Foundation.Events.new_event(:test, %{data: "example"})
{:ok, binary} = Foundation.Events.serialize(event)
# Deserialize binary to event
{:ok, restored_event} = Foundation.Events.deserialize(binary)
# Calculate serialized size
{:ok, size_bytes} = Foundation.Events.serialized_size(event)
```
### Storage Management
#### stats/0
Get storage statistics.
```elixir
@spec stats() :: {:ok, map()} | {:error, Error.t()}
```
#### prune_before/1
Prune events older than a monotonic timestamp.
```elixir
@spec prune_before(cutoff_timestamp :: integer()) :: {:ok, non_neg_integer()} | {:error, Error.t()}
```
**Examples:**
```elixir
# Get storage statistics
{:ok, stats} = Foundation.Events.stats()
# Returns: {:ok, %{
# current_event_count: 15000,
# events_stored: 50000,
# events_pruned: 1000,
# memory_usage_estimate: 15728640,
# uptime_ms: 3600000
# }}
# Prune old events
cutoff_time = System.monotonic_time() - 86400_000_000_000 # 24 hours ago in nanoseconds
{:ok, pruned_count} = Foundation.Events.prune_before(cutoff_time)
```
### available?/0
Check if the event store service is available.
```elixir
@spec available?() :: boolean()
```
---
## Telemetry API (`Foundation.Telemetry`)
Provides metrics collection, event measurement, and monitoring integration.
### initialize/0, status/0
Initialize/get status of the telemetry service.
```elixir
@spec initialize() :: :ok | {:error, Error.t()}
@spec status() :: {:ok, map()} | {:error, Error.t()}
```
**Examples:**
```elixir
# Initialize telemetry system
:ok = Foundation.Telemetry.initialize()
# Get status
{:ok, status} = Foundation.Telemetry.status()
# Returns: {:ok, %{status: :running, metrics_count: 50, handlers_count: 5}}
```
### execute/3
Execute a telemetry event with measurements and metadata. This is the core emission function.
```elixir
@spec execute(event_name :: [atom()], measurements :: map(), metadata :: map()) :: :ok
```
**Parameters:**
- `event_name` - List of atoms defining the event name hierarchy
- `measurements` - Map of measurement values
- `metadata` - Map of additional context
**Example:**
```elixir
Foundation.Telemetry.execute(
[:myapp, :request, :duration],
%{value: 120, unit: :milliseconds},
%{path: "/users", method: "GET", status: 200}
)
```
### measure/3
Measure the execution time of a function and emit a telemetry event.
```elixir
@spec measure(event_name :: [atom()], metadata :: map(), fun :: (-> result)) :: result when result: var
```
**Example:**
```elixir
result = Foundation.Telemetry.measure(
[:myapp, :db_query],
%{table: "users", operation: "select"},
fn -> Repo.all(User) end
)
# Automatically emits timing telemetry for the function execution
```
### emit_counter/2
Emit a counter metric (increments by 1).
```elixir
@spec emit_counter(event_name :: [atom()], metadata :: map()) :: :ok
```
**Example:**
```elixir
:ok = Foundation.Telemetry.emit_counter(
[:myapp, :user, :login_attempts],
%{user_id: 123, ip: "192.168.1.1"}
)
```
### emit_gauge/3
Emit a gauge metric (absolute value).
```elixir
@spec emit_gauge(event_name :: [atom()], value :: number(), metadata :: map()) :: :ok
```
**Example:**
```elixir
:ok = Foundation.Telemetry.emit_gauge(
[:myapp, :database, :connection_pool_size],
25,
%{pool: "main"}
)
```
### get_metrics/0
Retrieve all collected metrics.
```elixir
@spec get_metrics() :: {:ok, map()} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, metrics} = Foundation.Telemetry.get_metrics()
# Returns a map of all collected metrics with their values and metadata
```
### attach_handlers/1, detach_handlers/1
Attach/detach custom handlers for specific telemetry event names. Primarily for internal use or advanced scenarios.
```elixir
@spec attach_handlers(event_names :: [[atom()]]) :: :ok | {:error, Error.t()}
@spec detach_handlers(event_names :: [[atom()]]) :: :ok
```
**Example:**
```elixir
# Attach custom handlers
:ok = Foundation.Telemetry.attach_handlers([
[:myapp, :request, :duration],
[:myapp, :database, :query]
])
# Detach handlers
:ok = Foundation.Telemetry.detach_handlers([
[:myapp, :request, :duration]
])
```
### Convenience Telemetry Emitters
#### time_function/3
Measures execution of `fun`, names event based on `module`/`function`.
```elixir
@spec time_function(module :: module(), function :: atom(), fun :: (-> result)) :: result when result: var
```
**Example:**
```elixir
result = Foundation.Telemetry.time_function(
MyModule,
:expensive_operation,
fn -> MyModule.expensive_operation() end
)
# Emits timing under [:foundation, :function_timing, MyModule, :expensive_operation]
```
#### emit_performance/3
Emits a gauge under `[:foundation, :performance, metric_name]`.
```elixir
@spec emit_performance(metric_name :: atom(), value :: number(), metadata :: map()) :: :ok
```
**Example:**
```elixir
:ok = Foundation.Telemetry.emit_performance(
:memory_usage,
1024 * 1024 * 50, # 50MB
%{component: "event_store"}
)
```
#### emit_system_event/2
Emits a counter under `[:foundation, :system, event_type]`.
```elixir
@spec emit_system_event(event_type :: atom(), metadata :: map()) :: :ok
```
**Example:**
```elixir
:ok = Foundation.Telemetry.emit_system_event(
:service_started,
%{service: "config_server", pid: self()}
)
```
#### get_metrics_for/1
Retrieves metrics matching a specific prefix pattern.
```elixir
@spec get_metrics_for(event_pattern :: [atom()]) :: {:ok, map()} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, app_metrics} = Foundation.Telemetry.get_metrics_for([:myapp])
# Returns only metrics that start with [:myapp]
```
### available?/0
Check if the telemetry service is available.
```elixir
@spec available?() :: boolean()
```
---
## Utilities API (`Foundation.Utils`)
Provides general-purpose helper functions used within the Foundation layer and potentially useful for applications integrating with Foundation.
### generate_id/0
Generates a unique positive integer ID, typically for events or operations.
```elixir
@spec generate_id() :: pos_integer()
```
**Example:**
```elixir
unique_id = Foundation.Utils.generate_id()
# Returns: 123456789
```
### monotonic_timestamp/0
Returns the current monotonic time in nanoseconds. Suitable for duration calculations.
```elixir
@spec monotonic_timestamp() :: integer()
```
**Example:**
```elixir
start_time = Foundation.Utils.monotonic_timestamp()
# ... do work ...
end_time = Foundation.Utils.monotonic_timestamp()
duration_ns = end_time - start_time
```
### wall_timestamp/0
Returns the current system (wall clock) time in nanoseconds.
```elixir
@spec wall_timestamp() :: integer()
```
### generate_correlation_id/0
Generates a UUID v4 string, suitable for correlating events across operations or systems.
```elixir
@spec generate_correlation_id() :: String.t()
```
**Example:**
```elixir
correlation_id = Foundation.Utils.generate_correlation_id()
# Returns: "550e8400-e29b-41d4-a716-446655440000"
```
### truncate_if_large/1, truncate_if_large/2
Truncates a term if its serialized size exceeds a limit (default 10KB). Returns a map with truncation info if truncated.
```elixir
@spec truncate_if_large(term :: term()) :: term()
@spec truncate_if_large(term :: term(), max_size :: pos_integer()) :: term()
```
**Examples:**
```elixir
# Use default limit (10KB)
result = Foundation.Utils.truncate_if_large(large_data)
# Use custom limit
result = Foundation.Utils.truncate_if_large(large_data, 5000)
# If truncated, returns: %{truncated: true, original_size: 15000, truncated_size: 5000}
```
### safe_inspect/1
Inspects a term, limiting recursion and printable length to prevent overly long strings. Returns `<uninspectable>` on error.
```elixir
@spec safe_inspect(term :: term()) :: String.t()
```
**Example:**
```elixir
safe_string = Foundation.Utils.safe_inspect(complex_data)
# Safely converts any term to a readable string
```
### deep_merge/2
Recursively merges two maps.
```elixir
@spec deep_merge(left :: map(), right :: map()) :: map()
```
**Example:**
```elixir
merged = Foundation.Utils.deep_merge(
%{a: %{b: 1, c: 2}},
%{a: %{c: 3, d: 4}}
)
# Returns: %{a: %{b: 1, c: 3, d: 4}}
```
### format_duration/1
Formats a duration (in nanoseconds) into a human-readable string.
```elixir
@spec format_duration(nanoseconds :: non_neg_integer()) :: String.t()
```
**Example:**
```elixir
formatted = Foundation.Utils.format_duration(1_500_000_000)
# Returns: "1.5s"
```
### format_bytes/1
Formats a byte size into a human-readable string.
```elixir
@spec format_bytes(bytes :: non_neg_integer()) :: String.t()
```
**Example:**
```elixir
formatted = Foundation.Utils.format_bytes(1536)
# Returns: "1.5 KB"
```
### measure/1
Measures the execution time of a function.
```elixir
@spec measure(func :: (-> result)) :: {result, duration_microseconds :: non_neg_integer()} when result: any()
```
**Example:**
```elixir
{result, duration_us} = Foundation.Utils.measure(fn ->
:timer.sleep(100)
:completed
end)
# Returns: {:completed, 100000} (approximately)
```
### measure_memory/1
Measures memory consumption change due to a function's execution.
```elixir
@spec measure_memory(func :: (-> result)) :: {result, {before_bytes :: non_neg_integer(), after_bytes :: non_neg_integer(), diff_bytes :: integer()}} when result: any()
```
### system_stats/0
Returns a map of current system statistics (process count, memory, schedulers).
```elixir
@spec system_stats() :: map()
```
**Example:**
```elixir
stats = Foundation.Utils.system_stats()
# Returns: %{
# process_count: 1234,
# memory_total: 104857600,
# memory_processes: 52428800,
# schedulers_online: 8
# }
```
---
## Service Registry API (`Foundation.ServiceRegistry`)
High-level API for service registration and discovery, built upon `ProcessRegistry`.
### register/3
Registers a service PID under a specific name within a namespace.
```elixir
@spec register(namespace :: namespace(), service_name :: service_name(), pid :: pid()) :: :ok | {:error, {:already_registered, pid()}}
```
**Parameters:**
- `namespace` - `:production` or `{:test, reference()}`
- `service_name` - An atom like `:config_server`, `:event_store`
- `pid` - Process ID to register
**Example:**
```elixir
:ok = Foundation.ServiceRegistry.register(:production, :my_service, self())
```
### lookup/2
Looks up a registered service PID.
```elixir
@spec lookup(namespace :: namespace(), service_name :: service_name()) :: {:ok, pid()} | {:error, Error.t()}
```
**Example:**
```elixir
{:ok, pid} = Foundation.ServiceRegistry.lookup(:production, :my_service)
```
### unregister/2
Unregisters a service.
```elixir
@spec unregister(namespace :: namespace(), service_name :: service_name()) :: :ok
```
### list_services/1
Lists all service names registered in a namespace.
```elixir
@spec list_services(namespace :: namespace()) :: [service_name()]
```
**Example:**
```elixir
services = Foundation.ServiceRegistry.list_services(:production)
# Returns: [:config_server, :event_store, :telemetry_service]
```
### health_check/3
Checks if a service is registered, alive, and optionally passes a custom health check function.
```elixir
@spec health_check(namespace :: namespace(), service_name :: service_name(), opts :: keyword()) :: {:ok, pid()} | {:error, term()}
```
**Options:**
- `:health_check` - Custom health check function
- `:timeout` - Timeout in milliseconds
**Example:**
```elixir
{:ok, pid} = Foundation.ServiceRegistry.health_check(
:production,
:my_service,
health_check: fn pid -> GenServer.call(pid, :health) end,
timeout: 5000
)
```
### wait_for_service/3
Waits for a service to become available, up to a timeout.
```elixir
@spec wait_for_service(namespace :: namespace(), service_name :: service_name(), timeout_ms :: pos_integer()) :: {:ok, pid()} | {:error, :timeout}
```
### via_tuple/2
Generates a `{:via, Registry, ...}` tuple for GenServer registration with the underlying ProcessRegistry.
```elixir
@spec via_tuple(namespace :: namespace(), service_name :: service_name()) :: {:via, Registry, {module(), {namespace(), service_name()}}}
```
**Example:**
```elixir
via_tuple = Foundation.ServiceRegistry.via_tuple(:production, :my_service)
GenServer.start_link(MyService, [], name: via_tuple)
```
### get_service_info/1
Get detailed information about all services in a namespace.
```elixir
@spec get_service_info(namespace :: namespace()) :: map()
```
### cleanup_test_namespace/1
Specifically for testing: terminates and unregisters all services within a `{:test, ref}` namespace.
```elixir
@spec cleanup_test_namespace(test_ref :: reference()) :: :ok
```
---
## Process Registry API (`Foundation.ProcessRegistry`)
Lower-level, ETS-based process registry providing namespaced registration. Generally, `ServiceRegistry` is preferred for direct use.
### register/3
Registers a PID with a name in a namespace directly in the registry.
```elixir
@spec register(namespace :: namespace(), service_name :: service_name(), pid :: pid()) :: :ok | {:error, {:already_registered, pid()}}
```
### lookup/2
Looks up a PID directly from the registry.
```elixir
@spec lookup(namespace :: namespace(), service_name :: service_name()) :: {:ok, pid()} | :error
```
### via_tuple/2
Generates a `{:via, Registry, ...}` tuple for `GenServer.start_link` using this registry.
```elixir
@spec via_tuple(namespace :: namespace(), service_name :: service_name()) :: {:via, Registry, {module(), {namespace(), service_name()}}}
```
---
## Infrastructure Protection API (`Foundation.Infrastructure`)
Unified facade for applying protection patterns like circuit breakers, rate limiting, and connection pooling.
### initialize_all_infra_components/0
Initializes all underlying infrastructure components (Fuse for circuit breakers, Hammer for rate limiting). Typically called during application startup.
```elixir
@spec initialize_all_infra_components() :: {:ok, []} | {:error, term()}
```
### execute_protected/3
Executes a function with specified protection layers.
```elixir
@spec execute_protected(protection_key :: atom(), options :: keyword(), fun :: (-> term())) :: {:ok, term()} | {:error, term()}
```
**Options (examples):**
- `circuit_breaker: :my_api_breaker`
- `rate_limiter: {:api_calls_per_user, "user_id_123"}`
- `connection_pool: :http_client_pool` (if ConnectionManager is used for this)
**Example:**
```elixir
result = Foundation.Infrastructure.execute_protected(
:external_api_call,
[circuit_breaker: :api_fuse, rate_limiter: {:api_user_rate, "user_123"}],
fn -> HTTPoison.get("https://api.example.com/data") end
)
case result do
{:ok, response} -> handle_success(response)
{:error, :circuit_open} -> handle_circuit_breaker()
{:error, :rate_limited} -> handle_rate_limit()
{:error, reason} -> handle_other_error(reason)
end
```
### configure_protection/2
Configures protection rules for a given key (e.g., circuit breaker thresholds, rate limits).
```elixir
@spec configure_protection(protection_key :: atom(), config :: map()) :: :ok | {:error, term()}
```
**Config Example:**
```elixir
:ok = Foundation.Infrastructure.configure_protection(
:external_api_call,
%{
circuit_breaker: %{failure_threshold: 3, recovery_time: 15_000},
rate_limiter: %{scale: 60_000, limit: 50}
}
)
```
### get_protection_config/1
Retrieves the current protection configuration for a key.
```elixir
@spec get_protection_config(protection_key :: atom()) :: {:ok, map()} | {:error, :not_found | term()}
```
---
## Error Context API (`Foundation.ErrorContext`)
Provides a way to build up contextual information for operations, which can then be used to enrich errors.
### new/3
Creates a new error context for an operation.
```elixir
@spec new(module :: module(), function :: atom(), opts :: keyword()) :: ErrorContext.t()
```
**Options:** `:correlation_id`, `:metadata`, `:parent_context`.
**Example:**
```elixir
context = Foundation.ErrorContext.new(
MyModule,
:complex_operation,
correlation_id: "req-123",
metadata: %{user_id: 456}
)
```
### child_context/4
Creates a new error context that inherits from a parent context.
```elixir
@spec child_context(parent_context :: ErrorContext.t(), module :: module(), function :: atom(), metadata :: map()) :: ErrorContext.t()
```
### add_breadcrumb/4
Adds a breadcrumb (a step in an operation) to the context.
```elixir
@spec add_breadcrumb(context :: ErrorContext.t(), module :: module(), function :: atom(), metadata :: map()) :: ErrorContext.t()
```
### add_metadata/2
Adds or merges metadata into an existing context.
```elixir
@spec add_metadata(context :: ErrorContext.t(), new_metadata :: map()) :: ErrorContext.t()
```
### with_context/2
Executes a function, automatically capturing exceptions and enhancing them with the provided error context.
```elixir
@spec with_context(context :: ErrorContext.t(), fun :: (-> term())) :: term() | {:error, Error.t()}
```
**Example:**
```elixir
context = Foundation.ErrorContext.new(MyModule, :complex_op)
result = Foundation.ErrorContext.with_context(context, fn ->
# ... operations that might fail ...
context = Foundation.ErrorContext.add_breadcrumb(
context, MyModule, :validation_step, %{step: 1}
)
if some_condition_fails do
raise "Specific failure"
end
:ok
end)
case result do
{:error, %Error{context: err_ctx}} ->
IO.inspect(err_ctx.operation_context) # Will include breadcrumbs, etc.
result ->
# success
result
end
```
### enhance_error/2
Adds the operational context from `ErrorContext.t()` to an existing `Error.t()`.
```elixir
@spec enhance_error(error :: Error.t(), context :: ErrorContext.t()) :: Error.t()
@spec enhance_error({:error, Error.t()}, context :: ErrorContext.t()) :: {:error, Error.t()}
@spec enhance_error({:error, term()}, context :: ErrorContext.t()) :: {:error, Error.t()}
```
---
## Error Handling & Types (`Foundation.Error`, `Foundation.Types.Error`)
The Foundation layer uses a structured error system for consistent error handling across all components.
### `Foundation.Types.Error` (Struct)
This is the data structure for all errors in the Foundation layer.
```elixir
%Foundation.Types.Error{
code: pos_integer(), # Hierarchical error code
error_type: atom(), # Specific error identifier (e.g., :config_path_not_found)
message: String.t(),
severity: :low | :medium | :high | :critical,
context: map() | nil, # Additional error context
correlation_id: String.t() | nil,
timestamp: DateTime.t() | nil,
stacktrace: list() | nil, # Formatted stacktrace
category: atom() | nil, # E.g., :config, :system, :data
subcategory: atom() | nil, # E.g., :validation, :access
retry_strategy: atom() | nil, # E.g., :no_retry, :fixed_delay
recovery_actions: [String.t()] | nil # Suggested actions
}
```
### Error Categories and Codes
| Category | Code Range | Examples |
|----------|------------|----------|
| System | 1000-1999 | Service unavailable, initialization failures |
| Configuration | 2000-2999 | Invalid paths, update failures |
| Data/Events | 3000-3999 | Event validation, storage errors |
| Network/External | 4000-4999 | API failures, timeouts |
| Security | 5000-5999 | Authentication, authorization |
| Validation | 6000-6999 | Input validation, type errors |
### `Foundation.Error` (Module)
Provides functions for working with `Types.Error` structs.
#### new/3
Creates a new `Error.t()` struct based on a predefined error type or a custom definition.
```elixir
@spec new(error_type :: atom(), message :: String.t() | nil, opts :: keyword()) :: Error.t()
```
**Options:** `:context`, `:correlation_id`, `:stacktrace`, etc. (to populate `Types.Error` fields).
**Example:**
```elixir
error = Foundation.Error.new(
:config_path_not_found,
"Configuration path [:ai, :provider] not found",
context: %{path: [:ai, :provider]},
correlation_id: "req-123"
)
```
#### error_result/3
Convenience function to create an `{:error, Error.t()}` tuple.
```elixir
@spec error_result(error_type :: atom(), message :: String.t() | nil, opts :: keyword()) :: {:error, Error.t()}
```
#### wrap_error/4
Wraps an existing error result (either `{:error, Error.t()}` or `{:error, reason}`) with a new `Error.t()`, preserving context.
```elixir
@spec wrap_error(result :: term(), error_type :: atom(), message :: String.t() | nil, opts :: keyword()) :: term()
```
**Example:**
```elixir
result = some_operation()
wrapped = Foundation.Error.wrap_error(
result,
:operation_failed,
"Failed to complete operation",
context: %{operation: "data_processing"}
)
```
#### to_string/1
Converts an `Error.t()` struct to a human-readable string.
```elixir
@spec to_string(error :: Error.t()) :: String.t()
```
#### is_retryable?/1
Checks if an error suggests a retry strategy.
```elixir
@spec is_retryable?(error :: Error.t()) :: boolean()
```
#### collect_error_metrics/1
Emits telemetry for an error. This is often called internally by error-aware functions.
```elixir
@spec collect_error_metrics(error :: Error.t()) :: :ok
```
---
## Performance Considerations
- Prefer `:ok` / `:error` tuples for return values in critical paths.
- Use `:telemetry` for high-frequency events; batch low-frequency events.
- Configure `:logger` for asynchronous logging in production.
- Use `:circuit_breaker` and `:rate_limiter` judiciously to protect resources.
- Monitor `:memory` and `:cpu` usage; optimize NIFs and ports as needed.
- Leverage `:poolboy` or similar for managing external connections.
## Best Practices
### Configuration Management Guidelines
**Configuration Structure:**
- Use nested configuration structures with clear namespaces
- Validate configuration at startup and runtime updates
- Implement configuration migrations for schema changes
- Use environment-specific configurations for development, staging, and production
```elixir
# Good: Well-structured configuration
config = %{
parsing: %{
batch_size: 1000,
timeout_ms: 5000,
retry_attempts: 3
},
storage: %{
max_events: 100_000,
retention_days: 30
}
}
# Subscribe to configuration changes for dynamic updates
:ok = Foundation.Config.subscribe()
```
**Configuration Best Practices:**
- Always validate configuration values before applying them
- Use descriptive configuration keys and document their purpose
- Implement configuration rollback for critical settings
- Monitor configuration changes with telemetry events
- Use type specifications for configuration schemas
### Event System Guidelines
**Event Design Patterns:**
- Use consistent event types and structured data schemas
- Include correlation IDs for request tracking and debugging
- Implement event relationships for complex workflows
- Design events for both immediate processing and historical analysis
```elixir
# Good: Well-structured event
{:ok, event} = Foundation.Events.new_event(
:user_authentication,
%{
user_id: 12345,
authentication_method: "oauth2",
ip_address: "192.168.1.100",
user_agent: "Mozilla/5.0...",
success: true
},
correlation_id: correlation_id,
metadata: %{
service: "auth_service",
version: "1.2.3"
}
)
```
**Event Storage and Querying:**
- Use appropriate query filters for performance
- Implement event pruning strategies for storage management
- Design event schemas that support future querying needs
- Use event relationships to track complex workflows
### Telemetry and Monitoring Guidelines
**Metrics Collection Strategy:**
- Emit telemetry for all critical operations and state changes
- Use appropriate metric types (counters, gauges, histograms)
- Include relevant metadata for filtering and aggregation
- Monitor both business and technical metrics
```elixir
# Business metrics
:ok = Foundation.Telemetry.emit_counter(
[:myapp, :orders, :completed],
%{payment_method: "credit_card", amount_usd: 99.99}
)
# Technical metrics
:ok = Foundation.Telemetry.emit_gauge(
[:myapp, :database, :connection_pool_size],
pool_size,
%{database: "primary", environment: "production"}
)
```
**Performance Monitoring:**
- Set up telemetry handlers for automatic metrics collection
- Monitor service health and availability continuously
- Track performance trends and set up regression detection
- Use distributed tracing for complex request flows
### Infrastructure Protection Patterns
**Circuit Breaker Usage:**
- Implement circuit breakers for external service calls
- Configure appropriate failure thresholds and timeouts
- Monitor circuit breaker state changes
- Design fallback strategies for when circuits are open
```elixir
# Protected external API call
result = Foundation.Infrastructure.execute_protected(
:payment_api_call,
[
circuit_breaker: :payment_service_breaker,
rate_limiter: {:payment_api_rate, user_id}
],
fn -> PaymentAPI.process_payment(payment_data) end
)
```
**Rate Limiting Strategy:**
- Implement rate limiting for resource-intensive operations
- Use hierarchical rate limiting (global, per-user, per-IP)
- Monitor rate limit violations and adjust limits dynamically
- Provide meaningful error messages when limits are exceeded
### Testing Best Practices
**Test Structure and Organization:**
- Follow the testing pyramid: many unit tests, fewer integration tests
- Use descriptive test names that explain the scenario being tested
- Group related tests in `describe` blocks for better organization
- Isolate tests to avoid dependencies between test cases
**Property-Based Testing:**
- Use property-based testing for complex business logic
- Test invariants that should always hold true
- Generate realistic test data that covers edge cases
- Implement custom generators for domain-specific data types
```elixir
# Property-based test example
property "configuration roundtrip serialization" do
check all config <- ConfigGenerator.valid_config() do
serialized = :erlang.term_to_binary(config)
deserialized = :erlang.binary_to_term(serialized)
assert config == deserialized
end
end
```
**Integration Testing:**
- Test service interactions and data flow between components
- Verify error propagation and recovery scenarios
- Test configuration changes and their effects on running services
- Validate telemetry events and metrics collection
**Performance Testing:**
- Establish performance baselines for regression detection
- Test with realistic data volumes and concurrent load
- Monitor resource usage (CPU, memory, I/O) during tests
- Implement automated performance regression detection
### Error Handling Strategies
**Structured Error Management:**
- Use the Foundation Error module for consistent error handling
- Attach operational context to errors for better debugging
- Implement error recovery strategies appropriate to the failure mode
- Log errors with sufficient context for troubleshooting
```elixir
# Good error handling with context
case Foundation.Error.try(
fn -> ExternalService.risky_operation(data) end,
log: true
) do
{:ok, result} ->
process_result(result)
{:error, error} ->
# Add operational context
enhanced_error = Foundation.ErrorContext.add_context(error, %{
operation: "external_service_call",
user_id: user_id,
attempt_number: retry_count
})
handle_error(enhanced_error)
end
```
### Service Registry and Process Management
**Service Registration:**
- Use meaningful service names and appropriate namespaces
- Register services after they are fully initialized
- Implement health checks for registered services
- Clean up registrations during graceful shutdown
**Process Lifecycle Management:**
- Follow OTP principles for supervision and fault tolerance
- Implement graceful shutdown procedures for all services
- Use appropriate restart strategies for different failure modes
- Monitor process memory usage and implement cleanup procedures
### Security and Compliance
**Data Protection:**
- Validate all inputs at system boundaries
- Sanitize data before logging or storing events
- Implement appropriate access controls for sensitive operations
- Use secure defaults for all configuration settings
**Operational Security:**
- Monitor for suspicious patterns in telemetry data
- Implement rate limiting to prevent abuse
- Log security-relevant events for audit purposes
- Regularly review and rotate credentials
### Development and Maintenance
**Code Quality:**
- Use type specifications (`@spec`) for all public functions
- Follow Elixir naming conventions and style guidelines
- Document public APIs with comprehensive examples
- Implement comprehensive unit tests for all modules
**Dependency Management:**
- Keep dependencies up to date with security patches
- Use precise version specifications in `mix.exs`
- Monitor for deprecated functions and upgrade paths
- Test applications with updated dependencies before deployment
**Operational Excellence:**
- Implement comprehensive logging for troubleshooting
- Set up monitoring and alerting for critical metrics
- Document runbooks for common operational procedures
- Practice disaster recovery procedures regularly
## Complete Examples
### Example 1: Basic Configuration and Event Handling
```elixir
# Ensure Foundation is initialized
:ok = Foundation.initialize()
# Configure the application
:ok = Foundation.Config.update([:dev, :debug_mode], true)
# Create and store an event
correlation_id = Foundation.Utils.generate_correlation_id()
{:ok, event} = Foundation.Events.new_event(:user_action, %{action: "login"}, correlation_id: correlation_id)
{:ok, event_id} = Foundation.Events.store(event)
# Emit a telemetry metric
:ok = Foundation.Telemetry.emit_counter([:myapp, :user, :login_attempts], %{user_id: 123})
# Register a service
:ok = Foundation.ServiceRegistry.register(:production, :my_service, self())
# Use infrastructure protection to call an external API
result = Foundation.Infrastructure.execute_protected(
:external_api_call,
[circuit_breaker: :api_fuse, rate_limiter: {:api_user_rate, "user_123"}],
fn -> ExternalAPI.call() end
)
```
### Example 2: Advanced Configuration with Subscribers
```elixir
# Ensure Foundation is initialized
:ok = Foundation.initialize()
# Subscribe to configuration changes
:ok = Foundation.Config.subscribe()
# Update a configuration value
:ok = Foundation.Config.update([:ai, :planning, :sampling_rate], 0.8)
# The subscriber process will receive a notification:
# {:config_notification, {:config_updated, [:ai, :planning, :sampling_rate], 0.8}}
# Unsubscribe from configuration changes
:ok = Foundation.Config.unsubscribe()
```
### Example 3: Error Handling with Context
```elixir
# Ensure Foundation is initialized
:ok = Foundation.initialize()
# Set the user context for error reporting
:ok = Foundation.ErrorContext.set_user_context(123)
# Try a risky operation
result = Foundation.Error.try(
fn -> ExternalAPI.call() end,
log: true
)
# Handle the result
case result do
{:ok, data} ->
# Process the data
{:error, error} ->
# Log and handle the error
:ok = Foundation.ErrorContext.log_error(error)
end
# Clear the user context
:ok = Foundation.ErrorContext.clear_user_context()
```
---
## Migration Guide
### Using Foundation Library
Foundation is a standalone library that can be added to any Elixir application:
1. Add `{:foundation, "~> 0.1.0"}` to your `mix.exs` dependencies
2. Start `Foundation.Application` in your supervision tree
3. Use Foundation modules directly (e.g., `Foundation.Config`, `Foundation.Events`)
### Configuration Structure
The Foundation library uses a structured configuration with the following top-level sections:
- `:ai` - AI provider and analysis settings (for applications that use AI features)
- `:capture` - Event capture and buffering configuration
- `:storage` - Storage and retention policies for events and data
- `:interface` - Query and API interface settings
- `:dev` - Development and debugging options
- `:infrastructure` - Rate limiting, circuit breakers, and connection pooling
**Note:** Foundation provides a complete configuration structure that applications can use as needed. Not all sections are required - applications can use only the parts relevant to their use case.
### Service Architecture
Foundation provides three core services:
- `Foundation.Services.ConfigServer` - Configuration management
- `Foundation.Services.EventStore` - Event storage and querying
- `Foundation.Services.TelemetryService` - Metrics collection
These services are automatically started by `Foundation.Application` and can be accessed through the high-level APIs.
---
## Support and Resources
- **GitHub Repository**: [https://github.com/nshkrdotcom/foundation](https://github.com/nshkrdotcom/foundation)
- **Issue Tracker**: [https://github.com/nshkrdotcom/foundation/issues](https://github.com/nshkrdotcom/foundation/issues)
- **Documentation**: [https://hexdocs.pm/foundation](https://hexdocs.pm/foundation)
- **Local Documentation**: Run `mix docs` to generate local documentation