Komponenten bauen
Eine Komponente ist ein wiederverwendbares Wippy-Modul, das im Hub veröffentlicht und in eine Host-Anwendung eingebunden wird. Eine Komponente kann von einer Datenbank, einem Process Host oder einem Router abhängen, ohne die Entry-IDs des Hosts zu kennen. Sie deklariert diese Abhängigkeiten über eine Requirement-Schnittstelle, und der Host stellt ihre Werte bereit.
Dieser Leitfaden behandelt die Autorenseite: die Deklaration dieser Schnittstelle und den Wertefluss in Einträge. Die Verbraucherseite mit Lock-Dateien, Versionsbeschränkungen und wippy add/update beschreibt die Abhängigkeitsverwaltung. Die interne Struktur einer Komponente behandelt die Anwendungsarchitektur.
Die drei Entry-Kinds
| Art | Seite | Rolle |
|---|---|---|
ns.definition |
Komponente | Modul-Metadaten; erforderlich zum Veröffentlichen. |
ns.requirement |
Komponente | Ein Loch, das der Host füllen muss, und wohin der Wert injiziert wird. |
ns.dependency |
Host | Montiert eine Komponente und liefert Werte für ihre Requirements. |
ns.definition
Jedes veröffentlichte Modul muss genau eine Definition besitzen. Sie kann Modulmetadaten sowie Referenzen auf README- und Wiki-Seiten enthalten.
- name: definition
kind: ns.definition
module: jobs # optional module metadata
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 und wiki sind Definitionsdaten; alle drei sind optional. meta enthält gewöhnliche Entry-Metadaten für Verwaltungsoberflächen. Release Notes werden beim Veröffentlichen angegeben, nicht hier.
ns.requirement
Ein Requirement ist ein benannter Wert mit einer Liste von Injektionszielen. Der Host stellt den Wert bereit, und die Runtime schreibt ihn am angegebenen Pfad in jeden Zieleintrag.
- 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 — verpflichtend vs. optional
Das Feld default entscheidet, ob der Host einen Wert liefern muss:
defaultmit einem Wert ungleich null vorhanden (auch ein leerer String) → das Requirement ist optional. Liefert der Host nichts, wird der Default verwendet.defaultfehlt → das Requirement ist verpflichtend. Ohne gelieferten Wert schlägt das Linken im Strict-Modus fehl (und warnt andernfalls).
default: "") unterscheidet sich von einem fehlenden oder null gesetzten Default. Ein leerer String bedeutet „optional, fällt auf nichts zurück“; fehlend und default: null bedeuten beide „der Host muss dies bereitstellen“. Verwenden Sie einen nicht-null Default für Infrastruktur mit einer sinnvollen Anwendungskonvention wie app:db oder app:processes; lassen Sie ihn bei Werten weg, die nur der Host kennen kann.
targets — wo der Wert landet
Jedes Target ist ein {entry, path}-Paar:
entry— der Eintrag, in den der Wert injiziert wird. Ein bloßer Name (schema) löst innerhalb des eigenen Namespace des Requirements auf; eine vollqualifizierte ID (app.jobs.migrations:schema) trifft genau diesen Eintrag, über Namespaces hinweg.path— ein Punktpfad in den Ziel-Eintrag, z.B..meta.target_db,.host,.database.url. Der führende Punkt ist Konvention.
Ein Requirement muss mindestens ein Ziel deklarieren.
Anhängen statt Setzen mit dem +=-Suffix am Pfad — nützlich, wenn mehrere Requirements zu einer Liste beitragen (z.B. Middleware):
targets:
- entry: app.api:router
path: .middleware+= # appends the value to the list at .middleware
Ein Requirement, viele Targets
Gruppieren Sie Ziele, die denselben Wert benötigen, unter einem Requirement. Beispielsweise kann target_db .meta.target_db jeder Migration und .db jeder Persistenzbibliothek versorgen, process_host die .host-Felder überwachter Services und api_router die .meta.router-Felder der Endpoints:
- 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 }
Der Host stellt einen Wert bereit, und die Runtime schreibt ihn in jedes deklarierte Ziel. Der Requirement-Eintrag enthält diese Verdrahtung direkt.
Eine Komponente konsumieren
Der Host montiert eine Komponente mit ns.dependency und füllt ihre Requirements über 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
Jeder parameter.name entspricht einem Requirement; sein value ist das, was in die Targets dieses Requirements injiziert wird. Requirements mit Default dürfen weggelassen werden; verpflichtende müssen geliefert werden.
Zuordnung von Parameternamen
Wie ein Parametername an ein Requirement bindet:
- Bloßer Name (
target_db) trifft ein Requirement dieses Namens, das zu der montierten Komponente gehört. Er greift nicht auf die Requirements eines anderen Moduls über. - Qualifizierter Name (
acme.jobs:target_db) trifft genau diese Requirement-ID. Verwenden Sie ihn zur Disambiguierung beim Verdrahten transitiver Abhängigkeiten.
Liefern zwei Dependencies unterschiedliche Werte für dasselbe Requirement, ist das ein Konflikt und wird gemeldet (identische Werte sind in Ordnung).
Wann Werte aufgelöst werden
Die Injektion geschieht in der Link-Phase der Build-Pipeline — bei der Veröffentlichung, während der Dependency-Expansion und beim Boot — nicht zur Laufzeit. Die Phase:
- Sammelt jedes
ns.requirementund jedesns.dependencymit seinen Parametern. - Löst für jedes Requirement einen Wert auf: Ein passender Parameter gewinnt; andernfalls der Default; andernfalls (kein Default) bleibt es unaufgelöst.
- Schreibt den aufgelösten Wert in jeden Ziel-Eintrag an seinem Pfad (Setzen, oder Anhängen bei
+=).
Unter Strict Requirements lässt ein unaufgelöstes verpflichtendes Requirement den Build fehlschlagen; andernfalls wird eine Warnung geloggt und fortgefahren. Wenn die Einträge die Runtime erreichen, ist jedes gefüllte Requirement bereits in seine Targets eingebacken.
Integration mit einem Mount-Test prüfen
Unit-Tests prüfen nicht die Registry-Beziehungen des zusammengesetzten Moduls. Fügen Sie einen Packaging- oder Mount-Test gegen die mit Requirements injizierte Registry hinzu und prüfen Sie:
- jeder überwachte
servicezeigt auf einen existierenden Prozess-Eintrag, - jede gespawnte oder geplante ID löst zu einem realen Eintrag auf,
- der Speicher jeder
env.variableist registriert.
Damit werden unaufgelöste Beziehungen sichtbar, etwa ein Supervisor, der einen nicht registrierten Worker referenziert, oder ein Test-Fixture mit einer nur im Harness vorhandenen Storage-ID. Siehe Supervision und das Test-Framework.
Siehe auch
- Anwendungsarchitektur — interne Struktur einer Komponente
- Abhängigkeitsverwaltung — Lock-Dateien, Versionen und Verbraucher-Workflow
- Module veröffentlichen — eine Komponente im Hub veröffentlichen
- Leitfaden zu Entry-Kinds — Referenz für
ns.definition,ns.requirementundns.dependency