. Net Основные настройки приложения. json лучшие практики - переопределить настройки разработчика (или наоборот)? - PullRequest
12 голосов
/ 29 мая 2020

Ищете разумный подход к структурированию настроек приложений. json файлов в. Net Core.

Если базовый файл appsettings. json должен быть настроен для работы в среде разработки , а затем переопределения на основе среды, такие как appsettings.production. json перезаписать определенные c ключи для производства?

Или appsettings. json должны содержать только конфигурацию, которая является общей для ВСЕХ сред, а затем Speci c appsettings.development / staging. json файл, используемый для явной установки ключей для этих сред?

Меня беспокоит - скажем, приложение развернуто на активном сервере, но ключ хранится в среде переменная (например, для переопределения строки подключения) отсутствует или написана неправильно et c. В этом случае приложение вернется к строке подключения base appsettings. json, которая будет неправильной БД для живой среды. Подобный сценарий звучит довольно катастрофично, особенно потому, что это может быть go незамечено?

Итак, на самом деле вопрос сводится к следующему - должно ли содержимое базового файла appsettings. json быть значениями 'dev' как по умолчанию (например, базы данных разработчиков, API-интерфейсы песочницы), которые заменяются производственными данными или наоборот?

Ответы [ 6 ]

6 голосов
/ 09 июня 2020

Я думаю, это скучный ответ; это зависит. Но мой любимый подход таков:

appsetting.json (base settings)
appsettings.development.json (dev with no secrets)
appsettings.production.json (production with no secrets)

Appsettings, где секретные значения существуют только в базовых настройках, а другие записываются в соответствующих appsettings. [Env]. json. Таким образом, пример ключа подключения к базе данных существует только в базовой настройке с локальной базой данных. Это задача среды, чтобы заменить его

Пример подключения к базе данных и ведения журнала

appsettings. json

{
"ConnectionStrings": {
  “dbConnection: “data source=localhost” <—— only here
},
“environment”: “local”,
"Logging": {
  "LogLevel": {
    "Default": “Verbose”
  }
},
}

appsettings.development. json

{
“environment”: “development”,
"Logging": {
  "LogLevel": {
    "Default": “Warning”
  }
},
}

appsettings.production. json

{
“environment”: “production”,
"Logging": {
  "LogLevel": {
    "Default": “Information”
  }
},
}

Меня беспокоит - скажем, приложение развернуто на активном сервере, но ключ хранится в переменной среды (например, для переопределения строки подключения) отсутствует или написано неправильно et c. В этом случае приложение вернется к строке подключения base appsettings. json, которая будет неправильной БД для живой среды. Подобный сценарий звучит довольно катастрофично, особенно потому, что он может легко go остаться незамеченным?

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

4 голосов
/ 09 июня 2020

Есть несколько способов изменить настройки (в этом вся прелесть. NET Core). Обычно я это делаю следующим образом:

appsetting.json (template)
appsettings.development.json (dev with no secrets)

На самом деле я не устанавливаю никаких настроек в appsettings. json. Я использую его как шаблонную карту настроек, которые должны (могут) быть установлены во время развертывания.

// appsettings.json

{
  "ConnectionStrings": {
    “dbConnection: "************************"
  },
  “environment”: “************************”,
  "Logging": {
    "LogLevel": {
      "Default": “************************”
    }
  },
}

Таким образом, если я пропущу какую-либо настройку, позже станет очевидно, что она была забыта. Мне не нужно беспокоиться о случайном использовании настроек, которые «проскользнули» по иерархии. Поэтому, если вы посмотрите на свои другие json-файлы, они будут полными и в них нет скрытых настроек.

// appsettings.Development.json

{
  "ConnectionStrings": {
    “dbConnection: “data source=localhost”
  },
  “environment”: “local”,
  "Logging": {
     "LogLevel": {
      "Default": “Verbose”
    }
  }
}

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

3 голосов
/ 16 июня 2020

Я привык хранить свою конфигурацию в Azure в AzureAppConfig и / или AzureKeyVault. Это дает мне центральное место для управления настройками разработки, подготовки / тестирования, производства и не требует от меня усложнять развертывание, манипулируя файлами настроек приложений или сохраняя их в каком-то репозитории развертывания. На самом деле он читается только из azure при запуске приложения (мне не нужно было обновлять sh их, пока мое приложение работало). Тем не менее, это сделало его немного интересным для местной истории разработчиков, потому что я лично хотел, чтобы порядок операций был appsettings.json, appsettings.{environment}.json, AzureAppConfig, KeyVault, а затем, наконец, secrets.json. Таким образом, несмотря ни на что, я мог переопределить параметр из azure с помощью моего локального файла секретов (даже если параметр, который я переопределял, технически не был секретом).

В итоге я написал несколько нестандартных код в program.cs для обработки загрузки источников конфигурации из Azure, затем завершите sh поиском JsonConfigurationSource с Path из "secrets.json", затем сделайте это последним элементом в моем IConfigurationBuilder.Sources.

Для меня мои файлы используются следующим образом:

  • appsettings.json - Общие настройки, которые необходимо установить для любой среды, и будут вероятно никогда не меняются в зависимости от окружающей среды. appsettings.{environment}.json - В основном это просто пустые файлы JSON, которые в основном просто называют имена ресурсов AzureAppConfig и AzuerKeyVault для подключения к
  • AzureAppConfig - В основном для всего, что будет отличаться между производством, постановкой / Тестирование или местная разработка, И не является конфиденциальной информацией. Адреса конечных точек API, IP-адреса, различные URL-адреса, информация об ошибках и тому подобное.
  • AzureKeyVault - все, что важно. Имена пользователей, пароли, ключи для внешних API (авторизация, лицензионные ключи, строки подключения и т. Д. c).

Дело в том, что даже если вы поместите параметр в appsettings.json, это не так. t означает, что вы не можете переопределить его с помощью appsettings.{enviroment}.json или где-то еще. Я часто помещаю настройки в файл настроек root со значением NULL, просто чтобы напомнить мне, что это настройка, используемая в приложении. Так что, возможно, лучший вопрос: хотите ли вы запускать свое приложение (без ошибок) только с базовыми appsettings.json и secrets.json? Или содержимое из appsettings.{enviroment}.json всегда будет необходимо для успешного запуска?

Другая вещь, на которую следует обратить внимание на основе вашего вопроса, - это проверка вашей конфигурации. Более поздние версии Microsoft.Extensions.Options предлагают различные способы проверки ваших параметров, чтобы вы могли попытаться поймать случаи, когда что-то оставалось пустым / неопределенным. Я обычно украшаю свои классы параметров POCO атрибутами аннотации данных, а затем использую ValidateDataAnnotations(), чтобы убедиться, что они правильно настроены.

Например,

services.AddOptions<MailOptions>().Bind(configuration.GetSection("MailSettings")).ValidateDataAnnotations();

Стоит отметить, что эта проверка выполняется только тогда, когда вы попробуйте запросить что-то вроде MailOptions, которое я использую в качестве примера выше, из DI (так, не при запуске). По этой причине я также создал свой собственный IStartupFilter, чтобы упреждающе запросить один или несколько моих классов параметров из службы провайдер при запуске приложения, чтобы та же проверка запускалась еще до того, как приложение начнет принимать запросы.

public class EagerOptionsValidationStartupFilter : IStartupFilter
{
    public readonly ICollection<Type> EagerValidateTypes = new List<Type>();
    private readonly IServiceProvider serviceProvider;

    public EagerOptionsValidationStartupFilter(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        foreach (var eagerType in EagerValidateTypes)
        {
            dynamic test = serviceProvider.GetService(typeof(IOptions<>).MakeGenericType(eagerType));
            _ = test.Value;
        }

        return next;
    }
}

startup.cs

public void ConfigureServices(IServiceCollection services)
{

    services.AddTransient<IStartupFilter>(x =>
        new EagerOptionsValidationStartupFilter(x)
        {
            EagerValidateTypes = {
                typeof(MailOptions),
                typeof(OtherOptions),
                typeof(MoreImportantOptions)
            }
        });
}
3 голосов
/ 11 июня 2020

Здесь вступают в игру несколько принципалов:

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

Во-вторых, любой дополнительный развернутый контент является дополнительным риском, поэтому не развертывайте ничего лишнего. Поместите соответствующие значения для каждой среды в файл c, специфичный для среды, и ничего больше. Эти значения должны иметь приоритет над базовым файлом, позволяя развертывать и запускать без ручного вмешательства. Используйте стандартный загрузчик конфигурации, чтобы загрузить (только) правильный файл для текущей среды.

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

Если вы используете централизованный источник конфигурации, можете ли вы разрешить развернутому файлу переопределить его? Это вопрос разработчика c -ops / policy. Ваш ответ определит, где в списке должна находиться централизованная конфигурация. Чем дальше вы поместите его, тем больше вероятность, что вашим разработчикам потребуется запускать экземпляр локально.

Могут быть другие соображения или дополнительные уровни, которые имеют смысл в вашем проекте. Важно иметь «почему» для того выбора, который вы делаете, и уметь логически объяснить и обосновать его в вашем контексте.

0 голосов
/ 15 июня 2020

IMO appsettings.json, который вы передаете в систему управления версиями, должен быть настроен для запуска всего (или насколько это возможно) в локальной среде разработки. Примечание: иногда могут быть сторонние зависимости, которые вы не можете развернуть локально (например, сторонняя служба API, которую использует ваше приложение / служба), и в этом случае я бы зафиксировал значения dev / sandbox для этих настроек c, но для всего остального (например, подключения к базам данных, брокеру сообщений, idp, стеку телеметрии и т. д. c) я бы настроил локальный. Мне также нравится иметь сценарий инициализации, чтобы быстро развернуть все зависимости приложения. В шаблоне микросервиса, который я использую в компании, в которой я работаю, используется PowerShell и docker -compose для быстрого и легкого развертывания локальных контейнерных зависимостей, чтобы члены команды могли как можно быстрее приступить к работе. Вот несколько причин для использования вышеупомянутого подхода:

  • Не делает никаких предположений о существовании постоянной централизованной среды разработки / тестирования или возможности членов команды получить доступ к такой среде.
  • Никаких секретов и паролей в системе контроля версий (или, по крайней мере, никаких производственных секретов и паролей).
  • Позволяет членам команды клонировать репо и как можно быстрее приступить к работе - им не нужно go и получите кучу настроек приложения откуда-то и обновите настройки приложения вручную.

Пара других указателей:

  • Если вы используете docker, вы можете переопределить отдельные настройки приложения, с использованием переменных среды (с использованием синтаксиса двойного подчеркивания, описанного в этот ответ SO ), однако эээ, иногда это может быть немного многословным. Я предпочитаю использовать файл переопределения среды c, как показано ниже. Обратите внимание на переменные среды CONFIG_DIR и ASPNETCORE_ENVIRONMENT:
WebHost.CreateDefaultBuilder(args)
   .ConfigureAppConfiguration((context, builder) =>
   {
      string basePath = Environment.GetEnvironmentVariable("CONFIG_DIR") ?? Directory.GetCurrentDirectory();
      string environmentVariable = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
      Console.WriteLine("Config directory: " + basePath + Environment.NewLine + "Environment: " + environmentVariable);
      builder.SetBasePath(basePath);
      builder.AddJsonFile("appsettings.json", false, true);
      if (!string.IsNullOrEmpty(environmentVariable))
        builder.AddJsonFile("appsettings." + environmentVariable + ".json", true, true);
      builder.AddEnvironmentVariables();
   })
  • В идеале развертывание и управление конфигурацией вашего приложения / службы должно осуществляться в отдельном репозитории git с использованием чего-то вроде Ansible. Если какие-либо параметры конфигурации изменяются, это репозиторий должен go пройти тот же процесс проверки кода, что и репозиторий вашего приложения, все проверяется в истории git, а развертывание происходит автоматически. Короче говоря, это значительно снижает вероятность испортить настройку конфигурации.
  • Если вы выполняете развертывание в Microsoft Azure или используете Azure службы; вам следует проверить Azure Конфигурация приложения - в основном конфигурация приложения как услуга (и совместимая с настройками приложений на основе файлов).
  • Если вы развертываете в Linux, файлы конфигурации, такие как поскольку настройки приложения должны быть скопированы в /etc/opt/[name-of-service] и не должны находиться в том же каталоге, что и двоичные файлы в /opt/[name-of-service]. Это соответствует Linux стандарту иерархии файловой системы . Это то, для чего предназначена переменная среды CONFIG_DIR, описанная ранее.
  • Обычно у меня также есть файл appsettings.docker.json в SCM, когда я хочу запустить свое приложение / службу как локальный контейнер. Пример того, когда я использую это вместо того, чтобы просто запускать приложение из Visual Studio IDE, - это когда я хочу протестировать ведение журнала через docker поставщика журналов.
0 голосов
/ 09 июня 2020
  1. Почему переменные среды должны быть повреждены во время развертывания? Я считаю, что это более вероятно, чем в процессе разработки, в файлы appsettings.*.json будут внесены изменения, которые что-то сломают. Также зачем вам нужны переменные env, если вы думаете о добавлении тех же настроек в свой appsettings.json в качестве запасного варианта?
  2. Можно протестировать не только код. Вы также можете написать тесты для своей конфигурации. Это более надежный подход по сравнению с соглашениями о конфигурации. И если что-то может go неправильно, оно может go не так, независимо от того, в скольких местах вы будете повторять строку подключения. На самом деле ... если вы повторите строку подключения, вы нарушите DRY и будете просить о проблемах. Поскольку эти дубликаты будут расходиться во времени.
  3. Любой из ваших подходов должен дать тот же результат. Если env не работает, то
    1. в первом случае appsettings.json\dbConnection (dev) будет заменено appsettings.production.json\dbConnection.
    2. во втором случае dbConnection будет взято прямо из appsettings.production.json\dbConnection (или из appsettings.development.json\dbConnection на вашем локальном компьютере).
    3. в третьем случае ...? Не совсем понимаю, что вы имеете в виду под словом «наоборот»? Но если вы поместите производственные значения в appsettings.json, они все равно будут переопределены значениями из соответствующих файлов. Или нет (если их там нет). Неважно.

Итак, как я вижу, единственный вопрос: должны ли быть какие-то настройки в appsettings.json, которые отличаются для prod и dev среды, или он должен содержать только общие настройки для обоих?

И разумный ответ: он должен содержать только общие настройки. Потому что это ожидаемо. И еще удобнее - если вам нужно изменить настройки для prod или dev, вам не нужно помнить, где их искать. Очевидно, в appsettings.production.json для prod и в appsettings.development.json для dev. И также это более предсказуемо - однажды, если не вы, то кто-то другой потратит некоторое время, пытаясь выяснить, почему соединение с БД не удается, если строка подключения перед его глазами является правильной (и это потому, что посередине ночи он забывает проверить, не было ли оно отменено).

...