golang

Почему JSON и YAML мешают вам писать нормальные конфиги (и чем их заменить)

  • вторник, 11 ноября 2025 г. в 00:00:22
https://habr.com/ru/articles/964972/

JSON, YAML, TOML, HCL - за последние годы человечество успело изобрести десяток языков для конфигурации.

Каждый обещал быть "простым", "удобным" и "читаемым человеком".

Но по факту - все они страдают от одних и тех же проблем: шумный синтаксис, хрупкие отступы, бесконечные кавычки и отсутствие элементарных возможностей вроде модульности и слияния конфигов.

Пора перестать с этим мириться и сделать конфигурации наконец человеческими.

🛑 Перестаньте

  • утомлять глаза, пытаясь разобраться в тонне бесполезных кавычек

  • утомлять глаза, пытаясь распознать в каком месте есть проблема с отступом

  • писать велосипед, когда в очередной раз нужно мержить конфиги для разных окружений

  • писать велосипед, когда нужно парсить env переменные в структуру конфига

  • забивать мозг, пытаясь разобраться в огромной конфигурации из-за отсутствия модульности

⁉️ Как перестать?

Использовать адекватный язык для конфигурации ATMC
ATMC - минималистичный, но мощный язык конфигурации, созданный для удобного описания системных настроек.
Он сочетает простоту синтаксиса, поддержку импортов, переопределений, гибкого слияния конфигов и переменные окружения.

💎 Почему ATMC?

  • топовый синтаксис

    • супер минималистичный

    • интуитивно понятный и простой

    • не перегружен конструкциями

    • не зависит от отступов, переносов, кавычек, запятых и прочей чепухи

    • очень простой и в то же время достаточно мощный

  • модульность

    • можно импортировать разные кусочки (модули) конфига

    • очень минималистичный синтаксис импорта

    • можно обращаться к вложенным полям импортированного конфига

  • слияние конфигов (киллер фича)

    • супер легко мерджить несколько конфигов (например, common + stg или prod)

    • есть возможность переопределять поля

    • умное слияние - рекурсивное - не перетирает поле полностью

  • встраивание

    • можно встраивать объекты и массивы с помощью spread (...) оператора

    • с помощью него же и происходит слияние

  • доступ к env переменным

    • $YOUR_ENV_VARIABLE

  • поддержка всех необходимых типов

    • int

    • float

    • string

    • bool

    • object

    • array

  • компиляция в структуру и мапу из коробки

  • поддержка комментариев

  • расширяемость:

    • можно получить итоговый AST со всеми резовленными значениями

    • можно сделать на этой основе компилятор во что угодно

    • в перспективе можно поддержать LSP

💡 Примеры использования

Простой пример

📄File: config.atmc

{
    logging: {
        level: ["warn", "error"]
    }
    database: {
        clickhouse: {
            username: "username"
            password: "password"
            port: 9000
        }
    }
}

Пример с импортом

📄File: config.atmc

db ./database.atmc // Супер минималистичный импорт

{
    logging: {
        level: ["warn", "error"]
    }
    database: db // Тут появится объект из импортированного файла
}

📄File: database.atmc

{
    clickhouse: {
        username: "username"
        password: "password"
        port: 9000
    }
}

Пример с spread + env

📄File: config.atmc

db ./database.atmc

{
    logging: {
        level: ["warn", "error"]
    }
    database: {
        postgres: {
            username: $POSTGRES_USERNAME // Будет подставленно значение из env
            password: $POSTGRES_PASSWORD
        }
        db... // Сюда встроится импортированный конфиг
    }
}

📄File: database.atmc

{
    clickhouse: {
        username: "username"
        password: "password"
        port: 9000
    }
}

Пример с доступом к вложенному полю

📄File: config.atmc

db ./database.atmc

{
    logging: {
        level: ["warn", "error"]
    }
    database: {
        postgres: {
            username: $POSTGRES_USERNAME // Будет подставленно значение из env
            password: $POSTGRES_PASSWORD
        }
        clickhouse: db.clickhouse // Сюда будет встроен вложенный в db объект
    }
}

📄File: database.atmc

{
    clickhouse: {
      username: "username"
      password: "password"
      port: 9000
    }
}

Пример со слиянием

📄File: common.atmc

{
    logging: {
        level: ["error"]
    }
    database: {
        postgres: {
            username: $POSTGRES_USERNAME
            password: $POSTGRES_PASSWORD
            host: "localhost"
            port: 5432
        }
    }
}

📄File: prod.atmc - конфиг для prod окружения

common ./common.atmc

{
    common... // Встраивается общий конфиг
    // Далее добавляются новые параметры и переопределяются параметры из общего конфига
    logging: {
        level: ["warn", "error"] // Переопределяется
        enable_tracing: true // Добавляется новое поле
    }
    database: {
        postgres: {
            port: 6432 // Переопределяется только порт, остальные поля не будут затронуты      
        }
    }
}

📄File: stage.atmc - конфиг для stage окружения

common ./common.atmc

{
    common... // Встраивается общий конфиг
    // Далее добавляются новые параметры и переопределяются параметры из общего конфига
    logging: {
        // Переопределяется - можно без запятых
        level: [
            "info"
            "warn"
            "error"
        ]
    }
}

⚙️ Что под капотом?

Язык конфигурации работает благодаря множеству компонентов:

  • lexer

    • преобразует код в токены

  • parser

    • преобразует токены в AST

  • analyzer

    • проверяет семантику в рамках одного AST

    • проверяет наличие неиспользованных переменных

    • проверяет использование неопределенных переменных

  • linker

    • резолвит значения переменных из всех связанных AST

    • резолвит значения переменных среды

    • выдает один итоговый AST

  • processor

    • получает на вход путь до файла с конфигом

    • запускает все необходимые компоненты для обработки всех связанных файлов

    • отдает полученный итоговый AST после линковки

  • compiler

    • компилирует итоговый AST

    • компиляторы могут быть разными (в map, в struct и т.д.)

🧩 Поддержка и интеграция

Язык написан пока только на Go, соответственно работает только для этого языка программирования.
В дальнейшем планируется поддержка других платформ

🔮 В планах

  • компиляция в самого себя

    • будет удобно смотреть итоговый конфиг

    • можно будет выводить итоговый конфиг, как это делает, например, nginx -T

  • кодогенерация структур на базе конфига

    • тогда описывать конфиг придется только в одном месте - в atmc файле

  • автоматическая загрузка env переменных из .env файла

    • сейчас такого нет - нужно будет самим загружать, чтобы они стали доступны в os.Getenv

    • но будет удобно, если добавить

  • подсветка синтаксиса

  • имплементация LSP

    • умная подсветка синтаксиса

    • подсказки

    • поиск определений и помощь в импортах

P.S. Знаю, что уже есть похожие языки, но у всех есть свои недостатки. Где-то приятный синтаксис, но нет модульности; где-то есть модульность, но нет слияния; а где-то есть всё, но язык излишне усложнён. В ATMC есть всё нужное - и ничего лишнего.

Моя цель - сделать конфигурации такими же простыми, как Go-код: предсказуемыми, читаемыми и расширяемыми.


👉 Репозиторий и примеры: https://github.com/atmxlab/atmc