DEV Community

somaz
somaz

Posted on

Refactoring Configuration Management in Go: From Inline to Structured Constants

Introduction

In this post, we will review a refactoring process for loading application configuration in Go.
The original implementation (referred to as Version 2) directly used hard-coded environment variable names inside the Load function.
The refactored version (Version 1) introduced constant definitions for environment variable keys and default values, improving both readability and maintainability.

Original Code (Version 2)

In the initial implementation, environment variable keys were inline literals within the Load function:

func Load() *Config {
    return &Config{
        EnvKeys:          os.Getenv("INPUT_ENV_KEY"),
        EnvValues:        os.Getenv("INPUT_ENV_VALUE"),
        OutputKeys:       os.Getenv("INPUT_OUTPUT_KEY"),
        OutputValues:     os.Getenv("INPUT_OUTPUT_VALUE"),
        GithubEnv:        os.Getenv("GITHUB_ENV"),
        GithubOutput:     os.Getenv("GITHUB_OUTPUT"),
        Delimiter:        getEnvWithDefault("INPUT_DELIMITER", ","),
        FailOnEmpty:      getBoolEnv("INPUT_FAIL_ON_EMPTY", true),
        TrimWhitespace:   getBoolEnv("INPUT_TRIM_WHITESPACE", true),
        CaseSensitive:    getBoolEnv("INPUT_CASE_SENSITIVE", true),
        ErrorOnDuplicate: getBoolEnv("INPUT_ERROR_ON_DUPLICATE", true),
        MaskSecrets:      getBoolEnv("INPUT_MASK_SECRETS", false),
        MaskPattern:      getEnvWithDefault("INPUT_MASK_PATTERN", ""),
        ToUpper:          getBoolEnv("INPUT_TO_UPPER", false),
        ToLower:          getBoolEnv("INPUT_TO_LOWER", false),
        EncodeURL:        getBoolEnv("INPUT_ENCODE_URL", false),
        EscapeNewlines:   getBoolEnv("INPUT_ESCAPE_NEWLINES", true),
        MaxLength:        getIntEnv("INPUT_MAX_LENGTH", 0),
        AllowEmpty:       getBoolEnv("INPUT_ALLOW_EMPTY", false),
        DebugMode:        getBoolEnv("DEBUG_MODE", false),
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Issues with This Approach
    • Magic Strings: Each environment variable name appears as a literal string, which increases the risk of typos.
    • Maintainability: If the environment variable name changes, it needs to be updated in multiple places.
    • Lack of Central Documentation: No clear central reference point for all supported environment variables.

Refactored Code (Version 1)

The refactored version introduces central constants for environment variable names and default values:

// Environment Variable Names Constants
const (
    // Input Environment Variables
    EnvKeyInput           = "INPUT_ENV_KEY"
    EnvValueInput         = "INPUT_ENV_VALUE"
    OutputKeyInput        = "INPUT_OUTPUT_KEY"
    OutputValueInput      = "INPUT_OUTPUT_VALUE"
    DelimiterInput        = "INPUT_DELIMITER"
    FailOnEmptyInput      = "INPUT_FAIL_ON_EMPTY"
    TrimWhitespaceInput   = "INPUT_TRIM_WHITESPACE"
    CaseSensitiveInput    = "INPUT_CASE_SENSITIVE"
    ErrorOnDuplicateInput = "INPUT_ERROR_ON_DUPLICATE"
    MaskSecretsInput      = "INPUT_MASK_SECRETS"
    MaskPatternInput      = "INPUT_MASK_PATTERN"
    ToUpperInput          = "INPUT_TO_UPPER"
    ToLowerInput          = "INPUT_TO_LOWER"
    EncodeURLInput        = "INPUT_ENCODE_URL"
    EscapeNewlinesInput   = "INPUT_ESCAPE_NEWLINES"
    MaxLengthInput        = "INPUT_MAX_LENGTH"
    AllowEmptyInput       = "INPUT_ALLOW_EMPTY"
    DebugModeInput        = "DEBUG_MODE"

    // GitHub Environment Variables
    GithubEnvVar    = "GITHUB_ENV"
    GithubOutputVar = "GITHUB_OUTPUT"
)

// Default Values Constants
const (
    DefaultDelimiter        = ","
    DefaultFailOnEmpty      = true
    DefaultTrimWhitespace   = true
    DefaultCaseSensitive    = true
    DefaultErrorOnDuplicate = true
    DefaultMaskSecrets      = false
    DefaultMaskPattern      = ""
    DefaultToUpper          = false
    DefaultToLower          = false
    DefaultEncodeURL        = false
    DefaultEscapeNewlines   = true
    DefaultMaxLength        = 0
    DefaultAllowEmpty       = false
    DefaultDebugMode        = false
)

// Config holds the application configuration
type Config struct {
    // Input/Output Keys and Values
    EnvKeys      string
    EnvValues    string
    OutputKeys   string
    OutputValues string

    // GitHub File Paths
    GithubEnv    string
    GithubOutput string

    // Input Processing Options
    Delimiter        string
    FailOnEmpty      bool
    TrimWhitespace   bool
    CaseSensitive    bool
    ErrorOnDuplicate bool
    AllowEmpty       bool

    // Value Transformation Options
    ToUpper        bool
    ToLower        bool
    EncodeURL      bool
    EscapeNewlines bool
    MaxLength      int

    // Security Options
    MaskSecrets bool
    MaskPattern string

    // Debug Options
    DebugMode bool
}

// Load loads configuration from environment variables
func Load() *Config {
    return &Config{
        // Input/Output Keys and Values
        EnvKeys:      os.Getenv(EnvKeyInput),
        EnvValues:    os.Getenv(EnvValueInput),
        OutputKeys:   os.Getenv(OutputKeyInput),
        OutputValues: os.Getenv(OutputValueInput),

        // GitHub File Paths
        GithubEnv:    os.Getenv(GithubEnvVar),
        GithubOutput: os.Getenv(GithubOutputVar),

        // Input Processing Options
        Delimiter:        getEnvWithDefault(DelimiterInput, DefaultDelimiter),
        FailOnEmpty:      getBoolEnv(FailOnEmptyInput, DefaultFailOnEmpty),
        TrimWhitespace:   getBoolEnv(TrimWhitespaceInput, DefaultTrimWhitespace),
        CaseSensitive:    getBoolEnv(CaseSensitiveInput, DefaultCaseSensitive),
        ErrorOnDuplicate: getBoolEnv(ErrorOnDuplicateInput, DefaultErrorOnDuplicate),
        AllowEmpty:       getBoolEnv(AllowEmptyInput, DefaultAllowEmpty),

        // Value Transformation Options
        ToUpper:        getBoolEnv(ToUpperInput, DefaultToUpper),
        ToLower:        getBoolEnv(ToLowerInput, DefaultToLower),
        EncodeURL:      getBoolEnv(EncodeURLInput, DefaultEncodeURL),
        EscapeNewlines: getBoolEnv(EscapeNewlinesInput, DefaultEscapeNewlines),
        MaxLength:      getIntEnv(MaxLengthInput, DefaultMaxLength),

        // Security Options
        MaskSecrets: getBoolEnv(MaskSecretsInput, DefaultMaskSecrets),
        MaskPattern: getEnvWithDefault(MaskPatternInput, DefaultMaskPattern),

        // Debug Options
        DebugMode: getBoolEnv(DebugModeInput, DefaultDebugMode),
    }
}


Enter fullscreen mode Exit fullscreen mode

Question

I modified the code as above, but I think it got too long? Is it a lot? I'm a DevOps engineer and I'm also studying development.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

๐Ÿ‘‹ Kindness is contagious

Please leave a โค๏ธ or a friendly comment on this post if you found it helpful!

Okay