API объявления живёт в
Veriqa.Core.Configuration. Это часть MPL-2.0-ядра, которое ваш хост и так
запускает (пакет приезжает с Veriqa.Core.AuthServer и с пакетами канальных адаптеров), а не MIT-пакета
контрактов — поэтому ссылайтесь на него из сборки, которая объявляет, либо объявляйте в хосте.
Всё, что описано на этой странице, — публичный API.Уровни приложения и ui_config открыты расширениям
Читатели записей уровней — и тот, который достаёт запись OIDC-клиента, и тот, который достаёт запись
ui_config, — internal, но расширению они и не нужны. Владельцу ключа нужен адрес внутри
записи, а не сама запись: вы указываете, где в записи лежит ваше значение, а читатель отдаёт запись
резолюции, не зная, что из неё прочитали.
Обе записи принимают члены, которых заранее никто не объявлял:
- Уровень приложения — элемент массива
Veriqa:OpenIddict:Clientsв конфигурации хоста. Он читается как узел, по пути: любой член, который вы впишете в запись клиента, читается по адресу, объявленному вашим ключом, независимо от того, есть ли уOidcClientOptionsсвойство с таким именем. - Уровень
ui_config— динамическая запись. Неизвестные ей поля сохраняются при чтении и записи через[JsonExtensionData], поэтому ваше поле переживает каждый цикл чтения-записи записи.
Veriqa.Core.Configuration), а не библиотека, объявляющая ключи.
1. Свой каталог
Каталог держит объявления одного владельца — общего каталога на все расширения нет. Заполняйте его из статического инициализатора своего класса ключей и выставляйте наружу:AcmeConfigKeys.cs
Of<T>(name) открывает цепочку для значения любого типа, Text(name) — для текстовой формы, в
которой живёт большинство настроек, а Declare() её закрывает: собирает ключ, кладёт объявление в
каталог и отдаёт ключ обратно. Незакрытая цепочка не объявляет ничего — такого ключа не
существует, и ни один его уровень не привязан.
Адреса по уровням
Одно правило решает здесь больше ошибок, чем все остальные: из корня конфигурации хоста адресуется
только уровень ядра. Его запись — это и есть конфигурация приложения, поэтому
Acme:Approvals:MaxAttempts там полный путь. Выше ядра адрес относителен записи своего уровня —
записи клиента, записи ui_config, — и объявление поэтому не называет никакого стора. Адрес выше
ядра, открывающийся секцией конфигурации хоста, останавливает старт сообщением, в котором названы
ключ, уровень и адрес.
Ключ составного типа читается из поддерева по своему адресу строгим биндингом: член, которого
тип не несёт, — остаток прошлой версии, случайный ключ, «комментарий», написанный полем, — делает
нечитаемым всё поддерево, и шаг отрабатывает ровно как значение, отбракованное доменом (выше
уровня ядра запись выбрасывается, на уровне ядра ключ с
FailStart останавливает хост). Это
принятая цена гарантии: без неё элемент набора с опечаткой приезжает как набор поменьше, с виду
совершенно корректный. Ключей со скалярным значением это не касается — как и членов, которые вы
дописали рядом со своим ключом в чужую запись: запись клиента вашим поддеревом не является.2. Зарегистрируйте каталог из своей композиции
AcmeServiceCollectionExtensions.cs
Program.cs
- Он идемпотентен, покаталожно. Каталог, который контейнер уже держит, пропускается в одиночку. И композиция, вызванная дважды, и две композиции, назвавшие один каталог, оставляют ровно одну регистрацию.
- Он ничего не подавляет. Ваш вызов регистрирует названное вами и пропускает уже имеющееся — ни один владелец не может выбить каталоги другого, и ни один не регистрируется дважды.
- Порядок не важен. Каждый регистратор объявляет свои ключи до того, как зарегистрирован первый биндинг, поэтому от того, отработала ваша композиция раньше или позже композиции ядра, не зависит, найдёт ли биндинг свой ключ.
AddVeriqaConfigKeyCatalogs принимает любое число каталогов, AddVeriqaConfigKeyCatalog регистрирует
один. Поставляемый код идёт обоими путями: общий пакет канальных адаптеров
Veriqa.Core.ChannelAdapter заявляет свои три каталога одним вызовом, а каждый канальный адаптер —
свой на собственной регистрации, поэтому развёртывание объявляет креденшелы тех каналов, которые
добавило, и ничьи больше.
3. Прочитайте значение
IConfigurationResolver — единственный вход резолюции, и он асинхронный: уровень, обслуживаемый
внешним стором, читается асинхронно, а синхронная обёртка над ним была бы sync-over-async.
ResolutionContext.ForTenant(tenantId) закрывает случай «только тенант», ResolutionContext.Core —
спрашивает один уровень ядра.
Резолвите несколько ключей, которые обслуживает одна запись? Откройте ConfigResolutionScope над
контекстом и передавайте его вместо контекста — запись тогда читается один раз на всё, что
резолвится внутри:
4. Что может заявить объявление
Ваши ключи получают ту же модель, что и ключи ядра, а не урезанную. Семантика сведения уровней. По умолчанию — простое значение: верхний уровень свободно переопределяет нижний.
Домен значения и цена значения вне него:
SkipStep (по умолчанию) оставляет провинившийся шаг незаданным, и резолюция уходит на уровень ниже;
FailStart отказывает старт на уровне ядра и выбрасывает запись выше него. Текст boundary —
это то, что оператор читает в отчёте, а correctAt — где ему сказано это исправить. Само значение ни
в одно из сообщений не попадает. Ключ перечисления получает домен своих членов и без этого вызова.
Измерения. .Identity("channel") заявляет измерение, без которого вопрос теряет смысл
(«настройка какого канала»), — оно адресуется на каждом шаге цепочки фолбэка и никогда из неё не
выпадает. .Narrowing("theme", "surface") заявляет сужающие атрибуты, самый значимый первым; они
выпадают из цепочки по одному, начиная с наименее значимого. .Fallback(...) заменяет
сгенерированную цепочку там, где ключ пробует лишь часть комбинаций.
Остальная цепочка одним списком:
5. Проверьте, что объявление подхватилось
Старт-отчёт механизма печатает то, что объявляет развёртывание. Одна строка уровняInformation
называет всю схему:
Debug — и каждое объявление печатается со своими границами; строка ключа
выше выглядит так:
Information — из каких частей собрано развёртывание, решает оно само), и Warning
на каждую пару «ключ + уровень», где ключ объявил уровень, который обслуживает эта установка, а биндинг
на пару не зарегистрирован — такой уровень молча пуст. Ключ, которого в отчёте нет вовсе, не
зарегистрирован: обычная причина — незакрытая цепочка (нет Declare()) либо композиция, которая так
и не вызвала AddVeriqaConfigKeyCatalogs.
Пример в поставляемом коде
CorePageBrandingConfigKeys и LoginConfirmationConfigKeys живут в Veriqa.Core.ChannelAdapter —
сборке, которая не может ссылаться на сервер аутентификации, это была бы обратная зависимость, — и
объявляют ключи на уровнях приложения и ui_config, записи которых достаёт как раз сервер
аутентификации. Свои каталоги они регистрируют из собственной композиции пакета канальных адаптеров,
ровно как описано в §2.
Где этот путь нужен
- Свой канальный адаптер — его креденшелы, тексты и лимиты по тенантам.
Руководство по канальному адаптеру резолвит настройки через
IConfigurationResolver; эта страница — про то, откуда берётся резолвимый им ключ. - Сток аудита, экспортёр метрик, исходящий шлюз — всё, что поставляется отдельной сборкой и что оператору нужно настраивать по приложениям или по тенантам.
- Свой движок политик ограничения. Собственные правила допустимости Veriqa — внутренний контракт, и точкой расширения они не публикуются: интегратор, которому нужен другой движок политик — Rego, Cedar, собственный DSL, — поставляет такую проверку сам, и она держит свою конфигурацию на своих ключах, объявленных ровно так, как описано выше.
Дальше
Справочник конфигурации
Все секции и ключи продукта и таблица объявленных ключей.
Свой канальный адаптер
Вторая половина пути расширения — канальный SPI.