0.0
No release in over 3 years
A practical approach to configuration management with type safety, team isolation, environment inheritance, encryption, and remote sources. Designed for teams dealing with configuration complexity at scale.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

 Project Readme

HatiConfig

Gem Version License: MIT

A Ruby approach to configuration management, inspired by real-world challenges in distributed systems. This gem explores practical solutions for teams dealing with configuration complexity at scale.

Features

  • Simple Configuration Management: Easily define, set, and retrieve configuration options.
  • Type Validation: Ensure configurations are correct with built-in type validation.
  • Multiple Formats: Import and export configurations in JSON, YAML, and Hash formats.
  • Nested Configurations: Support for infinite nested configurations for complex applications.
  • Classy Access: Access configurations in a 'classy' manner for better organization and readability.
  • Built-in Types: Utilize various built-in types including basic types, data structures, numeric types, and time types.
  • Extensible: Easily extendable to accommodate custom configuration needs.

Table of Contents

  • Overview & Configuration Patterns
  • Installation
  • Basic Usage
  • Configuration Container Usage
  • Define with DSL Syntax
  • Distributed Features
    • Remote Configuration
    • Environment Management
    • Team Isolation
    • Schema Versioning
    • Caching and Refresh
    • Encryption
  • Typing
    • Configure Type Validation
    • Define Configuration Type Validation
    • Type Schema
    • Built-in Types
  • Import/Export:
    • Loading Configurations
    • Loading from Remote Sources
    • Loading from a JSON String
    • Loading from a YAML File
    • Exporting Configurations
    • to_h
    • to_json
    • to_yaml
  • Security
    • Encryption
    • Encryption Key Providers
    • Security Features
  • OSS
    • Development
    • Contributing
    • License
    • Code of Conduct

Recent Updates

Version 0.1.0 (Latest)

  • Fixed: Encryption functionality now works correctly with the config(key, value: "secret", encrypted: true) syntax
  • Enhanced: Improved encryption handling for both inline and hash-based configuration syntax
  • Improved: Better error handling and type validation for encrypted values
  • Updated: Comprehensive encryption documentation with practical examples

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add hati-config

If bundler is not being used to manage dependencies, install the gem by executing:

gem install hati-config

Basic Usage

Use Case: You're building a Ruby application that needs clean, type-safe configuration management. You want to avoid scattered constants, magic strings, and configuration bugs that crash production. You need nested configs, type validation, and the ability to export configs for debugging.

Why existing tools fall short:

  • Plain Ruby constants are global and can't be nested cleanly
  • YAML files lack type safety and runtime validation
  • Environment variables become unwieldy with complex nested configs
  • No built-in export/import capabilities for debugging and testing

HatiConfig solution: Clean DSL for defining configs with automatic type validation, nested namespaces, and built-in serialization.

require 'hati_config'

module MyApp
  extend HatiConfig
end

MyApp.configure :settings do
  config option: 42
  config.int typed_opt_one: 42
  config typed_opt_two: 4.2, type: :float
end

MyApp.settings.option                 # => 42
MyApp.settings.typed_opt_one          # => 42
MyApp.settings.typed_opt_two          # => 4.2

With Encryption

require 'hati_config'

# Set up encryption key
ENV['HATI_CONFIG_ENCRYPTION_KEY'] = '0' * 32  # 256-bit key

# Create a setting instance with encryption
settings = HatiConfig::Setting.new

# Configure encryption
settings.class.encryption do
  key_provider :env
end

# Configure encrypted and plain values
settings.config :api_key, value: "secret-key", encrypted: true
settings.config :public_url, value: "https://api.example.com"

settings[:api_key]     # => "secret-key" (automatically decrypted)
settings[:public_url]  # => "https://api.example.com" (plain text)

Basic Syntax

MyApp.configure :settings do
  config option: 42
end

Namespacing

MyApp.configure :app do
  configure :lvl_one do
    config opt: 100
    configure :lvl_two do
      config opt: 200
      configure :lvl_three do
        config opt: 300
        configure :lvl_four do
          config opt: 400
          configure :lvl_five do
            config opt: 500
            # NOTE: as deep as you want
          end
        end
      end
    end
  end
end

MyApp.app.lvl_one.opt                                     # => 100
MyApp.app.lvl_one.lvl_two.opt                             # => 200
MyApp.app.lvl_one.lvl_two.lvl_three.opt                   # => 300
MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.opt          # => 400
MyApp.app.lvl_one.lvl_two.lvl_three.lvl_four.lvl_five.opt # => 500

Configure Type Validation

MyApp.configure :settings do
  config custom_typed_opt_one: '42', type: :float
end
# => HatiConfig::SettingTypeError

Distributed Features

Remote Configuration

Use Case: You're running a microservices architecture with 50+ services across multiple regions. Each service needs to know database endpoints, API keys, and feature flags that change frequently. Traditional config files mean redeploying every service when anything changes, causing downtime and deployment bottlenecks.

Why existing tools fall short:

  • Environment variables become unmanageable with hundreds of configs
  • Config files require application restarts and deployments
  • Tools like Consul require additional infrastructure and learning curve
  • Most solutions don't handle automatic refresh or fallback gracefully

HatiConfig solution: Load configurations from HTTP endpoints, S3, or Redis with automatic refresh, caching, and fallback. Update configs without touching code or deployments.

HatiConfig supports loading configurations from various remote sources:

require 'hati_config'

module MyApp
  extend HatiConfig

  # Load from HTTP endpoint
  configure :api_settings, http: {
    url: 'https://config-server/api-config.json',
    headers: { 'Authorization' => 'Bearer token' },
    refresh_interval: 300  # refresh every 5 minutes
  }

  # Load from S3
  configure :database_settings, s3: {
    bucket: 'my-configs',
    key: 'database.yml',
    region: 'us-west-2',
    refresh_interval: 600
  }

  # Load from Redis
  configure :feature_flags, redis: {
    host: 'redis.example.com',
    key: 'feature_flags',
    refresh_interval: 60
  }
end

Environment Management

Use Case: Your application runs across development, staging, production, and multiple regional production environments. Each environment needs different database URLs, API endpoints, timeout values, and feature flags. Developers accidentally use production configs in development, or staging configs leak into production, causing outages.

Why existing tools fall short:

  • Rails environments are limited and don't handle complex multi-region setups
  • Multiple config files lead to duplication and inconsistency
  • No validation that the right configs are loaded in the right environment
  • Switching between environments requires manual file changes or complex deployment scripts

HatiConfig solution: Define base configurations with environment-specific overrides. Built-in environment detection with validation ensures the right configs are always loaded.

Easily manage configurations across different environments:

module MyApp
  extend HatiConfig

  configure :settings do
    # Base configuration
    config :timeout, default: 30
    config :retries, default: 3

    # Environment-specific overrides
    environment :development do
      config :api_url, value: 'http://localhost:3000'
      config :debug, value: true
    end

    environment :staging do
      config :api_url, value: 'https://staging-api.example.com'
      config :timeout, value: 60
    end

    environment :production do
      config :api_url, value: 'https://api.example.com'
      config :timeout, value: 15
      config :retries, value: 5
    end
  end
end

Team Isolation

Use Case: You work at a large tech company with 10+ engineering teams (Frontend, Backend, Mobile, DevOps, ML, etc.). Each team has their own configurations, but they all deploy to shared infrastructure. Teams accidentally override each other's configs, causing mysterious production issues that take hours to debug.

Why existing tools fall short:

  • Shared config files create merge conflicts and accidental overwrites
  • Namespace collisions are common (multiple teams using "database_url")
  • No clear ownership or boundaries for configuration sections
  • Changes by one team can break another team's services

HatiConfig solution: Create isolated namespaces for each team. Teams can safely manage their own configs without affecting others, while still sharing common infrastructure settings.

Prevent configuration conflicts between teams:

module MyApp
  extend HatiConfig

  # Team-specific configuration namespace
  team :frontend do
    configure :settings do
      config :api_endpoint, value: '/api/v1'
      config :cache_ttl, value: 300
    end
  end

  team :backend do
    configure :settings do
      config :database_pool, value: 5
      config :worker_threads, value: 10
    end
  end

  team :mobile do
    configure :settings do
      config :push_notifications, value: true
      config :offline_mode, value: true
    end
  end
end

# Access team configurations
MyApp.frontend.settings.api_endpoint  # => '/api/v1'
MyApp.backend.settings.database_pool  # => 5
MyApp.mobile.settings.offline_mode    # => true

Schema Versioning

Use Case: Your application has evolved over 2 years. The original config had simple database settings, but now includes complex microservice endpoints, ML model parameters, and feature flags. Old configs are incompatible with new code, but you need to support gradual rollouts and rollbacks without breaking existing deployments.

Why existing tools fall short:

  • No versioning means breaking changes crash old application versions
  • Manual migration scripts are error-prone and forgotten
  • No way to validate that configs match the expected schema
  • Rolling back code requires manually reverting config changes

HatiConfig solution: Version your configuration schemas with automatic migrations. Validate configs against expected schemas and handle version mismatches gracefully.

Track and validate configuration schema changes:

module MyApp
  extend HatiConfig

  configure :settings, version: '2.0' do
    # Schema definition with version constraints
    schema do
      required :database_url, type: :string, since: '1.0'
      required :pool_size, type: :integer, since: '1.0'
      optional :replica_urls, type: [:string], since: '2.0'
      deprecated :old_setting, since: '2.0', remove_in: '3.0'
    end

    # Migrations for automatic updates
    migration '1.0' => '2.0' do |config|
      config.replica_urls = [config.delete(:backup_url)].compact
    end
  end
end

Caching and Refresh

Use Case: Your application makes 1000+ requests per second and needs to check feature flags and rate limits on each request. Fetching configs from remote sources every time would crush your config server and add 50ms latency to every request. But configs can change and you need updates within 1 minute for critical flags.

Why existing tools fall short:

  • No caching means every request hits the config server
  • Simple TTL caching means stale data during config server outages
  • No intelligent refresh strategies lead to thundering herd problems
  • Manual cache invalidation is complex and error-prone

HatiConfig solution: Intelligent caching with stale-while-revalidate, background refresh, exponential backoff, and jitter to prevent thundering herds.

Configure caching behavior and automatic refresh:

module MyApp
  extend HatiConfig

  configure :settings do
    # Cache configuration
    cache do
      adapter :redis, url: 'redis://cache.example.com:6379/0'
      ttl 300  # 5 minutes
      stale_while_revalidate true
    end

    # Refresh strategy
    refresh do
      interval 60  # check every minute
      jitter 10    # add random delay (0-10 seconds)
      backoff do
        initial 1
        multiplier 2
        max 300
      end
    end
  end
end

Encryption

Use Case: Your application handles API keys, database passwords, OAuth secrets, and encryption keys that are worth millions if compromised. These secrets are scattered across config files, environment variables, and deployment scripts. A single leaked config file or compromised CI/CD pipeline exposes everything. Compliance requires encryption at rest and audit trails.

Why existing tools fall short:

  • Environment variables are visible in process lists and logs
  • Config files with secrets get committed to Git accidentally
  • Kubernetes secrets are base64 encoded, not encrypted
  • External secret managers add complexity and network dependencies
  • No transparent encryption/decryption in application code

HatiConfig solution: Automatic encryption of sensitive values with multiple key providers (env, files, AWS KMS). Values are encrypted at rest and decrypted transparently when accessed.

Secure sensitive configuration values with built-in encryption support:

require 'hati_config'

# Set the encryption key via environment variable
ENV['HATI_CONFIG_ENCRYPTION_KEY'] = '0123456789abcdef' * 2  # 32-character key

# Create a settings instance
settings = HatiConfig::Setting.new

# Configure encryption with environment variable key provider
settings.class.encryption do
  key_provider :env  # Uses HATI_CONFIG_ENCRYPTION_KEY environment variable
  algorithm 'aes'    # AES encryption (default)
  key_size 256       # 256-bit keys (default)
  mode 'gcm'         # GCM mode (default)
end

# Configure settings with encrypted and plain values
settings.config :api_key, value: 'secret-api-key', encrypted: true
settings.config :database_password, value: 'super-secret-password', encrypted: true

# Regular unencrypted values
settings.config :api_url, value: 'https://api.example.com'

# Nested configurations with encryption
settings.configure :database do
  config :host, value: 'db.example.com'
  config :password, value: 'db-secret', encrypted: true
  config :username, value: 'app_user'
end

# Access values - encrypted values are automatically decrypted
settings[:api_key]              # => 'secret-api-key' (decrypted)
settings.database[:password]    # => 'db-secret' (decrypted)
settings[:api_url]              # => 'https://api.example.com' (plain)

Encryption Key Providers

The gem supports multiple key providers for encryption keys:

# Environment variable (default)
settings.class.encryption do
  key_provider :env, env_var: 'MY_ENCRYPTION_KEY'  # Custom env var name
end

# File-based key
settings.class.encryption do
  key_provider :file, file_path: '/secure/path/to/key.txt'
end

# AWS KMS (requires aws-sdk-kms gem)
settings.class.encryption do
  key_provider :aws_kms, key_id: 'alias/config-key', region: 'us-west-2'
end

Security Features

  • AES-256-GCM encryption: Industry-standard encryption with authentication
  • Automatic encryption/decryption: Values are encrypted when stored and decrypted when accessed
  • Type safety: Only string values can be encrypted (enforced at runtime)
  • Multiple key providers: Support for environment variables, files, and AWS KMS
  • Secure storage: Encrypted values are stored as Base64-encoded strings

Configuration Container Usage

require 'hati_config'

module MyGem
  extend HatiConfig
end

Declare configurations

MyGem.configure :settings do
  config :option
  config.int :typed_opt_one
  config :typed_opt_two, type: Integer
  # NOTE: declare nested namespace with configure <symbol arg>
  configure :nested do
    config :option
  end
end

Define configurations

MyGem.settings do
  config option: 1
  config typed_opt_one: 2
  config typed_opt_two: 3
  # NOTE: access namespace via <.dot_access>
  config.nested do
    config option: 4
  end
end

Define with DSL Syntax

MyGem.settings do
  option 'one'
  typed_opt_one 1
  typed_opt_two 2
  # NOTE: access namespace via <block>
  nested do
    option 'nested'
  end
end

Get configurations

MyGem.settings.option        # => 'one'
MyGem.settings.typed_opt_one # => 1
MyGem.settings.typed_opt_two # => 2
MyGem.settings.nested.option # => 'nested'

Define Configuration Type Validation

MyGem.settings do
  config.typed_opt_two: '1'
end
# => HatiConfig::SettingTypeError

MyGem.settings do
  typed_opt_two '1'
end
# => HatiConfig::SettingTypeError

Union

# List one of entries as built-in :symbols or classes

MyApp.configure :settings do
  config transaction_fee: 42, type: [Integer, :float, BigDecimal]
  config vendor_code: 42, type: [String, :int]
end

Custom

MyApp.configure :settings do
  config option: CustomClass.new, type: CustomClass
end

Callable

acc_proc = Proc.new { |val| val.respond_to?(:accounts) }
holder_lam = ->(name) { name.length > 5 }

MyApp.configure :settings do
  config acc_data: User.new, type: acc_proc
  config holder_name: 'John Doe', type: holder_lam
end

Loading Configuration Data

Use Case: Your application needs to load configuration from existing YAML files, JSON APIs, or hash data from databases. You want to validate the loaded data against expected schemas and handle format errors gracefully. Different environments might use different config sources (files in development, APIs in production).

Why existing tools fall short:

  • Manual YAML/JSON parsing is error-prone and lacks validation
  • No schema validation means runtime errors from bad config data
  • Mixing different config sources requires complex custom code
  • No unified interface for different data formats

HatiConfig solution: Unified interface for loading from YAML, JSON, and Hash sources with optional schema validation and clear error handling.

The HatiConfig module allows you to load configuration data from various sources, including YAML and JSON. Below are the details for each option.

  • json (String)
  • yaml (String)
  • hash (Hash)
  • schema (Hash) (Optional) See: Type Schema and Built-in Types

Loading from a JSON String

You can load configuration data from a JSON string by passing the json option to the configure method.

Parameters

  • json (String): A JSON string containing the configuration data.
  • schema (Hash) (Optional): A hash representing the type schema for the configuration data.

Error Handling

  • If the JSON format is invalid, a LoadDataError will be raised with the message "Invalid JSON format".

Example 1

MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}').settings
# => #<MyGem::Setting:0x00007f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>

Example 2

MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :str })
# => HatiConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.

Example 3

MyGem.configure(:settings, json: '{"opt_one":1,"opt_two":2}', schema: { opt_one: :int, opt_two: :int })

MyGem.settings do
  opt_one 1
  opt_two "2"
end
# => HatiConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.

Loading from a YAML File

You can also load configuration data from a YAML file by passing the yaml option to the configure method.

Parameters

  • yaml (String): A file path to a YAML file containing the configuration data.
  • schema (Hash) (Optional): A hash representing the type schema for the configuration data.

Error Handling

  • If the specified YAML file is not found, a LoadDataError will be raised with the message "YAML file not found".
YAML File
# settings.yml

opt_one: 1
opt_two: 2

Example 1

MyGem.configure :settings, yaml: 'settings.yml'
# => #<MyGem::Setting:0x00006f8c1c0b2a80 @options={:opt_one=>1, :opt_two=>2}>

Example 2

MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :str }
# => HatiConfig::SettingTypeError: Expected: <str>. Given: 2 which is <Integer> class.

Example 3

MyGem.configure :settings, yaml: 'settings.yml', schema: { opt_one: :int, opt_two: :int }

MyGem.settings do
  opt_one 1
  opt_two "2"
end
# => HatiConfig::SettingTypeError: Expected: <intstr>. Given: \"2\" which is <String> class.

Exporting Configuration Data

You can dump the configuration data in various formats using the following methods:

to_h

MyGem.configure :settings do
  config opt_one: 1
  config opt_two: 2
end

MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'

to_json

MyGem.configure :settings do
  config opt_one: 1
  config opt_two: 2
end

MyGem.settings.to_json # => '{"opt_one":1,"opt_two":2}'

to_yaml

MyGem.configure :settings do
  config opt_one: 1
  config opt_two: 2
end

MyGem.settings.to_yaml # => "---\nopt_one: 1\nopt_two: 2\n"

type_schema

MyGem.configure :settings do
  config.int opt_one: 1
  config.str opt_two: "2"
end

MyGem.settings.type_schema # => {:opt_one=>:int, :opt_two=>:str}

Built-in Types Features

Use Case: Your application crashes in production because someone set a timeout to "30 seconds" instead of 30, or a database pool size to "many" instead of 10. You need runtime type validation that catches these errors early and provides clear error messages. Different config values need different types (strings, integers, arrays, custom objects).

Why existing tools fall short:

  • No runtime type checking means silent failures or crashes
  • Custom validation code is scattered throughout the application
  • Error messages are unclear ("expected Integer, got String")
  • No support for complex types like arrays of specific types or custom classes

HatiConfig solution: Comprehensive type system with built-in types, composite types, custom validators, and clear error messages.

Base Types

:int  => Integer
:str  => String
:sym  => Symbol
:null => NilClass
:any  => Object
:true_class  => TrueClass
:false_class => FalseClass

Data Structures

:hash  => Hash
:array => Array

Numeric

:big_decimal => BigDecimal,
:float       => Float,
:complex     => Complex,
:rational    => Rational,

Time

:date      => Date,
:date_time => DateTime,
:time      => Time,

Composite

:bool       => [TrueClass, FalseClass],
:numeric    => [Integer, Float, BigDecimal],
:kernel_num => [Integer, Float, BigDecimal, Complex, Rational],
:chrono     => [Date, DateTime, Time]

Authors

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release to publish the .gem file to rubygems.org.

Contributing

Bug reports and feature requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the HatiConfig project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.