Applications
Eine Application ist eine veroeffentlichte Frontend-Flaeche ueber einem Backend. Ihr application.json-Manifest benennt die Driver, deren Vertraege ein gebundenes Backend erfuellen muss.
TL;DR
- Eine Application ist ein Container, der eine UI ausliefert und ueber ein
application.json-Manifest an ein oder mehrere Backends gebunden ist. Sie laeuft on demand: der Container startet bei der ersten Anfrage und stoppt, sobald er ungenutzt ist. - Das Manifest traegt
needs_driver_id(den Live-Wire-Vertrag) undtaps_driver_id(den Aufzeichnungs-/Replay-Vertrag). Die endpoint-Anforderungen liegen auf jenen Driver-Zeilen alsrequired_endpoints, nie inline im Manifest. - endpoint-URLs sind eine Eigenschaft des Backends, und erst ein laufendes Deployment laesst diese URLs ausliefern. Ein required endpoint loest zu einer konkreten URL am Backend auf, nicht an einem bestimmten Deployment.
- Die Plattform baut das Image aus deinem Quellcode. Es gibt keinen Weg, ihr ein anderswo gebautes zu uebergeben.
- Vier Verben, vier verschiedene Wirkungen:
publishbaut eine Version,promoteentscheidet, welche Version ausgeliefert wird,releaseentscheidet, wer die Application sehen darf,deploystartet nur den Container frueher.
Mentales Modell — needs ↔ aliase
Ein required_endpoints-Eintrag eines Drivers ist die Konsumentenhaelfte eines Bindungsvertrags; die andere Haelfte ist ein endpoint-alias am Backend. Das endpoint-alias-Modell — wie aliase den Graphen von der Flaeche entkoppeln und ein (vertex, endpoint-name) auf eine role abbilden — gehoert zu /concepts/solutions; diese Seite behandelt nur, wie eine Application auf einen Driver zeigt und was dieser Driver dann vom Backend fordert.
Die Application benennt einen Driver; der Driver benennt die endpoints:
application.json driver-Zeile
┌────────────────────┐ ┌─────────────────────────────┐
│ needs_driver_id ──►│───►│ required_endpoints[] │
│ taps_driver_id │ │ name: upload │
└────────────────────┘ │ role: upload │
│ direction: ingress │
│ transports: [http] │
│ required: true │
└─────────────────────────────┘
Die Plattform ordnet einen required endpoint ueber role einem alias zu, prueft dann, dass die direction passt und dass mindestens einer der gelisteten transports mit dem ueberlappt, was der alias unterstuetzt. Existiert der alias nicht, passt die direction nicht oder ueberlappt kein Transport, schlaegt die Aufloesung fail-closed fehl: ein required: true-endpoint blockiert die Application, ein required: false-endpoint wird verworfen.
Die Application besitzt weder den Backend-Graphen noch den Deployment-Lebenszyklus, die Component-Implementierungen, das Container-Layout oder die Runtime-Platzierung. Das sind vorgelagerte Belange, die die Application ueber aufgeloeste endpoint-URLs konsumiert.
Mentales Modell — bauen, ausliefern, zeigen
publish ──────► Version N existiert was gebaut wurde
promote ──────► Version N wird geliefert was Besucher bekommen
release ──────► im Katalog gelistet wer sie sehen darf
Veroeffentlichen aendert nicht, was Besuchern ausgeliefert wird — genau das erlaubt es, eine Version zu bauen und zu pruefen, bevor sie irgendwer sonst bekommt. Promoten ist der Schritt, der umschaltet. Releasen ruehrt an keinem von beidem.
Durchlauf — eine UI an ein Backend binden
Voraussetzungen: ein Backend mit den endpoint-aliasen, die die UI braucht (deklariert mit ppl backend change-endpoint-alias), ein Driver, der beschreibt, was die UI konsumiert, und ein Quellverzeichnis mit einem Dockerfile.
# 1. den Driver-Vertrag in application.json benennen:# {# "name": "doc_scanner_ui",# "read_me": { "schema_version": 1, ... },# "read_me_agent": { "schema_version": 1, ... },# "needs_driver_id": "<driver id>"# }# 2. die Application + ihren Vertrag registrierenppl application create --name doc_scanner_ui --manifest ./application.json# 3. den Quellcode zu einer Version bauenppl application publish doc_scanner_ui --dir ./ui -m "first cut"# 4. diese Version zur ausgelieferten machenppl application promote doc_scanner_ui# 5. im Workspace-Katalog listenppl application release doc_scanner_ui
Sobald das Backend hinter einem laufenden Deployment live ist, loest jeder required endpoint des Drivers zu einer konkreten URL am veroeffentlichten endpoint des Backends auf. Die UI schickt Uploads an die upload-URL und abonniert die events-URL; das vertex-Layout des Backends, die Container-Platzierung und die Runtime bleiben fuer sie unsichtbar.
Der Build-Vertrag
Der Baum unter --dir wird hochgeladen und auf dem Build-Cluster der Plattform gebaut. Er muss ein Dockerfile enthalten, das das Projekt baut und ueber HTTP auf Port 80 ausliefert.
Builds nutzen den klassischen Docker-Builder, nicht BuildKit, daher schlagen RUN --mount=type=secret und RUN --mount=type=cache fehl. Lies Registry-Credentials fuer die Build-Zeit aus der .npmrc, die die Plattform in den Build-Kontext einspielt.
Ein Projekt in einem Monorepo teilt Lockfile und Packages mit seinen Geschwistern und kann daher nicht allein hochgeladen werden. Lade den Workspace hoch und benenne das Projekt darin:
ppl application publish doc_scanner_ui --dir . --project apps/doc_scanner_ui
Das Dockerfile wird aus dem Projekt gelesen; der Build sieht weiterhin den gesamten Workspace.
Auslieferungs-Performance
Eine Application ist ein statischer Origin, also sind die Response-Header, die sie sendet, die gesamte Caching-Geschichte. Komprimiere einmal zur Build-Zeit statt bei jeder Anfrage:
RUN find out -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' \ -o -name '*.json' -o -name '*.svg' \) -exec gzip -9 -k {} \;
Liefere dann das vorkomprimierte Artefakt aus und cache unveraenderliche Assets getrennt von der Shell:
gzip_static on;gzip on;gzip_comp_level 6;gzip_vary on;# Inhaltsgehashte Pfade: der Name aendert sich, sobald die Bytes es tun.location /_next/static/ { add_header Cache-Control "public, max-age=31536000, immutable"; add_header Vary "Accept-Encoding";}# Die Shell benennt die aktuellen Asset-Hashes; sie zu cachen fixiert# Besucher auf einen Build, den es nicht mehr gibt.location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ /index.html;}
Drei Regeln, die ausdruecklich genannt gehoeren, weil jede davon schon eine laufende Application lahmgelegt hat:
try_filesdurchlaeuft das location-Matching nicht erneut. Es liefert die getroffene Datei direkt aus, einlocation ~ \.br$-Block, derContent-Encodingan vorkomprimierte Assets haengen soll, greift bei einer uebertry_fileserreichten Datei also nie. Das Asset kommt alsapplication/octet-streaman, und der Browser fuehrt es nicht aus.gzip_staticwird nicht ueber location verteilt und umgeht das vollstaendig.add_headerin einer verschachtelten location ersetzt die geerbte Menge, statt sie zu ergaenzen. Jeder Header, den eine location braucht, muss in dieser location stehen.- Der Container muss auf Port 80 lauschen.
Varianten
Nur registrieren — lokale Iteration. ppl application register doc_scanner_ui legt eine Application-Zeile im Modus internal ohne Container und ohne Vertrag an; es nimmt einen Namen und sonst nichts. Die Zeile sieht nur du; kombiniere sie beim Iterieren mit einem lokalen Dev-Server, haenge Vertraege mit update an und veroeffentliche, wenn die UI so weit ist.
Eine aeltere Version ausliefern. ppl application promote doc_scanner_ui --version <version_id> liefert eine bestimmte veroeffentlichte Version statt der neuesten aus — so wird ein schlechter Build zurueckgerollt. Das Flag nimmt die id der Version aus ppl application versions, nicht die daneben gezeigte seq.
Den Vertrag binden oder neu binden. ppl application update doc_scanner_ui --needs-driver <driver_id> --taps-driver <driver_id> setzt die beiden Vertraege direkt; --manifest tut dasselbe aus einer Datei. --new-name benennt die Application um.
Den Kaltstart ueberspringen. ppl application deploy doc_scanner_ui startet den Container, bevor die erste Anfrage eintrifft, damit der erste Besucher nicht darauf wartet. --force ersetzt einen Container, der bereits ausliefert.
Fuer alle veroeffentlichen. ppl application release doc_scanner_ui --public listet die Application im oeffentlichen Katalog und liefert sie ohne Login unter ihrem Namen aus.
Referenz — application.json
{ "name": "doc_scanner_ui", "read_me": { "schema_version": 1, "...": "..." }, "read_me_agent": { "schema_version": 1, "...": "..." }, "needs_driver_id": "<uuid>", "taps_driver_id": "<uuid>"}
| Feld | Bedeutung |
|---|---|
name | Application-Bezeichner. Wird von jedem Application-Befehl genutzt. |
read_me | Typisiertes Produktdokument: summary, what_this_does, who_it_is_for, primary_workflows, launch_modes, limitations. |
read_me_agent | Typisierter Auswahlvertrag: summary, pick_when, do_not_pick_when, interaction_model, required_launch_modes, workflows, limitations. Positive wie negative Kriterien sind Pflicht, damit ein Agent Relevanz nicht aus blosser Kompatibilitaet ableitet. |
needs_driver_id | Driver, dessen Vertrag ein Backend auf der ingress-Seite erfuellen muss — die Live-Wire-Flaeche, die die UI konsumiert. Optional; laesst man ihn weg, bleibt die Application unbeschraenkt. |
taps_driver_id | Driver fuer die Aufzeichnungs-/Replay-Seite. Optional und oft dieselbe Driver-Zeile wie needs_driver_id. |
Die endpoint-Anforderungen, die das Backend erfuellen muss, werden von jenen Driver-Zeilen gelesen, als required_endpoints-Eintraege der Form {name, role, direction, transports, required}.
Ausfuehren
ppl application create --name <name> [--manifest ./application.json] [--readme <pfad>] [--agent-manifest <pfad>] [--needs-driver <id>] [--taps-driver <id>]ppl application register <name> # interner Stub, kein Container, keine Flagsppl application publish <name> [--dir <source_dir>] [--project <pfad>] [-m <note>] [--no-cache] [--node <id>]ppl application versions <name>ppl application promote <name> [--version <version_id>]ppl application release <name> [--public]ppl application deploy <name> [--force]ppl application stop <name>ppl application update <name> [--manifest <pfad>] [--needs-driver <id>] [--taps-driver <id>] [--new-name <neu>] [--public | --private]ppl application listppl application delete <name>
Verwandt
- Solutions — der gesamte Stack; die Application ist die UI oben auf einem Backend und dessen Deployment.
- Backends — wo die endpoint-aliase deklariert werden, an die die
required_endpointsdes Drivers binden. - Deployments — die Runtime hinter den veroeffentlichten endpoint-URLs des Backends.