Создание компонентов
Компонент — это переиспользуемый модуль Wippy: слайс функциональности, опубликованный в хаб и монтируемый в хост-приложение. Проблема компонента в том, что он не может назвать то, от чего зависит: ему нужна какая-то база данных, какой-то process host, какой-то роутер, но он не знает, какие именно даст ему хост. Wippy решает это через интерфейс требований — компонент объявляет дыры, хост их заполняет.
Это руководство описывает сторону автора: объявление такого интерфейса и понимание того, как значения попадают в ваши записи. О стороне потребителя (lock-файлы, ограничения версий, wippy add/update) см. Управление зависимостями. О том, как компонент устроен внутри, см. Архитектура приложения.
Три типа записей
| Тип | Сторона | Роль |
|---|---|---|
ns.definition |
компонент | Метаданные модуля; обязательна для публикации. |
ns.requirement |
компонент | Дыра, которую должен заполнить хост, и куда внедрить значение. |
ns.dependency |
хост | Монтирует компонент и передаёт значения для его требований. |
ns.definition
Одна на модуль, обязательна для публикации. Несёт отображаемое имя модуля и путь к README — ничего больше.
- name: definition
kind: ns.definition
module: jobs # optional; defaults to the entry name
readme: file://README.md # path to the module's documentation
meta:
title: Durable Jobs
description: Leased job queue with retry and dead-lettering.
Данными компонента являются только module и readme; meta — обычные метаданные записи для управляющих UI. Заметки к релизу передаются во время публикации, а не здесь.
ns.requirement
Требование — это именованная дыра со списком целей внедрения. Хост передаёт значение; среда выполнения записывает это значение в каждую целевую запись по указанному пути.
- name: target_db
kind: ns.requirement
meta:
description: SQL database backing every table in this module.
default: app:db
targets:
- entry: app.jobs.migrations:schema
path: .meta.target_db
- entry: app.jobs.persist:lifecycle
path: .db
default — обязательное или необязательное
Поле default решает, обязан ли хост передать значение:
defaultприсутствует (любое значение, включая пустую строку) → требование необязательное. Если хост ничего не передал, используется значение по умолчанию.defaultотсутствует → требование обязательное. Если ничего не передано, компоновка падает в строгом режиме (и предупреждает в остальных).
default: "") отличается от отсутствия default вовсе. Пустая строка означает «необязательно, откатывается в ничто»; отсутствие означает «хост обязан это предоставить». Используйте default для инфраструктуры с разумной внутриприложенческой конвенцией (app:db, app:processes); опускайте его для значений, которые может знать только хост.
targets — куда попадает значение
Каждая цель — пара {entry, path}:
entry— запись, в которую внедряется значение. Голое имя (schema) разрешается внутри пространства имён самого требования; полностью квалифицированный id (app.jobs.migrations:schema) указывает ровно на эту запись, через пространства имён.path— точечный путь внутрь целевой записи, например.meta.target_db,.host,.database.url. Ведущая точка — конвенция.
Требование без целей — ошибка: дыра, которая никуда не внедряет, бессмысленна.
Суффикс += у пути добавляет вместо установки — полезно, когда несколько требований наполняют один список (например, middleware):
targets:
- entry: app.api:router
path: .middleware+= # appends the value to the list at .middleware
Одно требование, много целей
Группируйте под одним требованием всё, чему нужно одно и то же значение. Это идиоматический паттерн: требование target_db, внедряющее в .meta.target_db каждой миграции и в .db каждой библиотеки персистентности; process_host, внедряющее в .host каждого супервизируемого service; api_router, внедряющее в .meta.router каждого эндпоинта:
- name: process_host
kind: ns.requirement
default: app:processes
targets:
- { entry: app.jobs.service:worker.service, path: .host }
- { entry: app.jobs.service:sweeper.service, path: .host }
Хост заполняет одну дыру; среда выполнения разносит значение по всем целям. Ничего не зеркалится в параллельную конфигурационную запись — запись требования и есть проводка.
Потребление компонента
Хост монтирует компонент через ns.dependency и заполняет его требования через parameters:
version: "1.0"
namespace: app
entries:
- name: dep.jobs
kind: ns.dependency
component: acme/jobs
version: "^1.0.0"
parameters:
- name: target_db
value: app:db
- name: process_host
value: app:processes
- name: api_router
value: app:api
Каждый parameter.name соответствует требованию; его value — то, что внедряется в цели этого требования. Требования со значением по умолчанию можно опустить; обязательные должны быть переданы.
Сопоставление имён параметров
Как имя параметра привязывается к требованию:
- Голое имя (
target_db) сопоставляется с требованием с этим именем, принадлежащим монтируемому компоненту. Оно не пересекает границу требований другого модуля. - Квалифицированное имя (
acme.jobs:target_db) сопоставляется ровно с этим id требования. Используйте его для устранения неоднозначности при проводке транзитивных зависимостей.
Если две зависимости передают разные значения для одного и того же требования — это конфликт, о котором сообщается (одинаковые значения допустимы).
Когда разрешаются значения
Внедрение происходит на стадии Link конвейера сборки — при публикации, при разворачивании зависимостей и при загрузке — а не во время выполнения. Стадия:
- Собирает каждое
ns.requirementи каждоеns.dependencyс его параметрами. - Для каждого требования разрешает значение: побеждает подходящий параметр; иначе — значение по умолчанию; иначе (нет default) требование не разрешено.
- Записывает разрешённое значение в каждую целевую запись по её пути (установка или добавление для
+=).
В режиме строгих требований неразрешённое обязательное требование проваливает сборку; иначе логируется предупреждение и работа продолжается. К моменту, когда записи достигают среды выполнения, каждое заполненное требование уже впечатано в свои цели.
Проверьте швы: тест монтирования
Юнит-тесты прогоняют слайс в изоляции; они не видят, целостен ли собранный модуль. Добавьте упаковочный тест (тест монтирования), который проверяет модуль целиком против живого реестра с внедрёнными требованиями:
- каждый супервизируемый
serviceуказывает на существующую запись процесса, - каждый порождаемый или планируемый id разрешается в реальную запись,
- хранилище каждой
env.variableзарегистрировано.
Это интеграционные швы, которые маскируют изолированные юнит-наборы, — зазоры, из-за которых супервизор может ссылаться на воркер, который никогда не был зарегистрирован, или тестовая фикстура протащить id хранилища из тестовой обвязки в смонтированную загрузку. См. Супервизия и фреймворк Тестирование.
См. также
- Архитектура приложения — как компонент устроен внутри
- Управление зависимостями — lock-файлы, версии, рабочий процесс потребителя
- Публикация модулей — размещение компонента в хабе
- Типы записей — справочник по
ns.definition,ns.requirement,ns.dependency