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) und taps_driver_id (den Aufzeichnungs-/Replay-Vertrag). Die endpoint-Anforderungen liegen auf jenen Driver-Zeilen als required_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: publish baut eine Version, promote entscheidet, welche Version ausgeliefert wird, release entscheidet, wer die Application sehen darf, deploy startet 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_files durchlaeuft das location-Matching nicht erneut. Es liefert die getroffene Datei direkt aus, ein location ~ \.br$-Block, der Content-Encoding an vorkomprimierte Assets haengen soll, greift bei einer ueber try_files erreichten Datei also nie. Das Asset kommt als application/octet-stream an, und der Browser fuehrt es nicht aus. gzip_static wird nicht ueber location verteilt und umgeht das vollstaendig.
  • add_header in 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>"}
FeldBedeutung
nameApplication-Bezeichner. Wird von jedem Application-Befehl genutzt.
read_meTypisiertes Produktdokument: summary, what_this_does, who_it_is_for, primary_workflows, launch_modes, limitations.
read_me_agentTypisierter 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_idDriver, 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_idDriver 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_endpoints des Drivers binden.
  • Deployments — die Runtime hinter den veroeffentlichten endpoint-URLs des Backends.

War diese Seite hilfreich?