# GraphQL-Schema in der Fachsprache

URL: https://www.mikebild.dev/de/blog/graphql-schema-in-der-fachsprache/

„Fachsprache" hat zwei Lesarten, und die geläufigere ist hier ausdrücklich nicht gemeint. Es geht nicht um Jargon – nicht um das Vokabular, mit dem eine Branche Außenstehende auf Abstand hält. Gemeint ist das, was Eric Evans 2003 in „Domain-Driven Design" als ubiquitous language beschrieben hat: die eine gemeinsame Sprache, die Fachseite und Entwicklung in einem Projekt tatsächlich sprechen. Dieselben Wörter im Meeting, im Ticket, im Modell, im Code. Ein Vokabular für alle – keine zwei Vokabulare mit einer Übersetzungstabelle dazwischen.

Die Idee ist fünfzehn Jahre alt, und sie scheitert in der Praxis selten am Willen. Sie scheitert am Ort. Ein Glossar im Wiki veraltet ab dem Tag, an dem es angelegt wird, weil niemand es mitzieht, wenn ein Feld umbenannt wird. Der Code wäre der ehrlichere Ort, aber der Code gehört nur einer Seite: Die Fachabteilung liest keine Java-Klassen. Was all die Jahre fehlte, war ein Artefakt, das beide Seiten lesen können und das gleichzeitig maschinell geprüft wird.

Seit ich mit GraphQL arbeite, bin ich überzeugt, dass es dieses Artefakt inzwischen gibt: das Schema. Und – das ist der unbequemere Teil der These – man darf die Fachsprache dort auch deutsch sprechen.

## Zweimal übersetzen, zweimal verlieren

In Projekten mit deutscher Fachseite ist mir über die Jahre immer wieder dasselbe Muster begegnet. Die Fachabteilung sagt „Angebot". In der Datenbank steht `OFFER`, im Service heißt die Klasse `Quote`, im Frontend taucht ein `Proposal` auf – drei Übersetzungen desselben Wortes, entstanden zu drei Zeitpunkten unter drei verschiedenen Zeitdrücken. Jedes Gespräch zwischen Fachseite und Entwicklung übersetzt seitdem zweimal: hin beim Zuhören, zurück beim Antworten.

Jede dieser Übersetzungen kostet nicht nur Zeit, sie verfälscht. Ob „Angebot" nun offer, quote, bid oder proposal heißen soll, ist keine neutrale Wahl – jedes dieser Wörter trägt im Englischen eine eigene Bedeutung, und keine deckt sich genau mit dem, was die Fachseite meint. Die Entscheidung trifft am Ende ein Entwickler unter Termindruck, und sie steht dann für Jahre im System – unbeachtet, aber wirksam bei jedem einzelnen Gespräch über genau dieses Feld.

```mermaid
flowchart LR
  F["Fachseite<br/>sagt „Angebot“"] -->|übersetzen| E["Entwicklung<br/>Offer? Quote? Bid?"]
  E -->|übersetzen| S["System<br/>OFFER, Quote, Proposal"]
  S -->|rückübersetzen| E
  E -->|rückübersetzen| F
```

Die teuersten Missverständnisse, die ich in Reviews erlebt habe, waren selten technischer Natur. Es waren Begriffe, die auf dem Weg durch diese Kette ihre Bedeutung gewechselt hatten – ein „storniertes Angebot", das im System ein `cancelled quote` war, obwohl die Fachseite unter Stornierung etwas anderes verstand als die Entwicklung unter cancelled.

## Das Experiment: uber-ng

Im März habe ich dazu ein kleines Repository angelegt: [uber-ng](https://github.com/MikeBild/uber-ng), eine bewusst überschaubare Nachstellung einer Fahrtvermittlung, wie man sie von Uber kennt. Kein Produkt – ein Prototyp mit In-Memory-Daten, ein paar Mocha-Tests und einer einzigen Frage: Wie liest sich ein GraphQL-Schema, wenn die Fachbegriffe deutsch bleiben?

Das Schema steht dort als eigene Datei in Schema-Notation – jener SDL, die gerade in die GraphQL-Spezifikation aufgenommen wird, nachdem sie jahrelang nur Konvention der Werkzeuge war. Der Kern sieht so aus:

```graphql
enum STATUS {
  angefragt
  warteAufBestaetigung
  angenommen
}

type Angebot implements Node {
  id: ID!
  fahrgast: Fahrgast
  von: String!
  nach: String!
  fahrer: Fahrer
  wartezeit: String
  status: STATUS
}

type Mutation {
  fahrtAnfragen(input: AnfrageInput!): Angebot
  angebotAnnehmen(input: AngebotAnnehmenInput!): Angebot
  angebotAblehnen(input: AngebotAblehnenInput!): Angebot
}

type Subscription {
  onAngebotChanged: Angebot
}
```

Wer das liest, braucht kein Glossar daneben. Eine Fahrt wird angefragt; was dabei entsteht, ist ein Angebot; ein Angebot kann angenommen oder abgelehnt werden – und wer ablehnt, muss einen Grund nennen, denn `AngebotAblehnenInput` verlangt ein `grund: String!`. Die Mutationen sind Verben der Domäne, keine CRUD-Prosa. `fahrtAnfragen` erzwingt sogar eine fachliche Unterscheidung, die ein `createOffer` verschluckt hätte: Angefragt wird eine Fahrt – das Angebot ist die Antwort des Systems darauf, nicht das, was der Fahrgast selbst anlegt. Dieser Unterschied ist mir erst beim Formulieren des Schemas aufgefallen, nicht vorher.

Auch die Anfragen der Clients sprechen anschließend diese Sprache:

```graphql
mutation {
  fahrtAnfragen(input: {
    fahrgastId: "f1"
    von: "Alexanderplatz"
    nach: "Flughafen Tegel"
  }) {
    id
    status
    wartezeit
  }
}
```

Man kann diese Mutation in einem Termin laut vorlesen, und die Fachseite versteht sie ohne Dolmetscher. Das ist keine Kleinigkeit. Es ist der Moment, in dem eine API aufhört, ein reines Entwickler-Artefakt zu sein.

Technisch ist der Aufbau bewusst gewöhnlich gehalten: Node, ein GraphQL-Server, Subscriptions über WebSockets, wie sie sich mit `subscriptions-transport-ws` inzwischen eingespielt haben. `onAngebotChanged` schiebt jede Zustandsänderung eines Angebots an die verbundenen Clients – der Fahrgast sieht, wenn aus `angefragt` ein `warteAufBestaetigung` wird, ohne nachfragen zu müssen. Für das Experiment ist das Beiwerk. Interessant ist nur, dass auch dieser Kanal die Fachsprache spricht: Was beim Client ankommt, heißt Angebot, nicht `OfferDTO`.

## Was das Schema zum Glossar macht

Ein Glossar beschreibt eine Sprache. Das Schema erzwingt sie – und genau das hat es jedem Glossar voraus, das mir bisher untergekommen ist:

- Jede Query wird gegen das Schema validiert. Wer einen Begriff erfindet oder falsch schreibt, bekommt einen Fehler statt eines stillen Missverständnisses.
- Introspection macht das Vokabular abfragbar: GraphiQL zeigt Typen, Felder und Beschreibungen schon beim Tippen an – das Glossar liefert sich selbst aus.
- `@deprecated` mustert Begriffe geordnet aus, mit Begründung und sichtbar für jeden Client. In uber-ng hängt genau so ein Vermerk an `fahrgastId`.
- Das Schema steht in der Versionsverwaltung. Jede Begriffsänderung ist ein Diff, und ein Diff kann ein Review bekommen, an dem auch die Fachseite teilnimmt.

Dazu kommen die Beschreibungstexte: Die SDL erlaubt an jedem Typ, jedem Feld und jedem Enum-Wert eine Beschreibung, die Introspection mit ausliefert. Dort steht dann nicht nur, dass es ein `Angebot` gibt, sondern was die Fachseite darunter versteht – die Definition wandert mit dem Begriff mit, statt in einem anderen System zu altern.

Ein Wiki kann nichts davon. Ein UML-Modell auch nicht. Das GraphQL-Schema ist meines Wissens das erste Artefakt, das gleichzeitig für Menschen lesbar, für Maschinen prüfbar und für den Betrieb verbindlich ist. Das Schema ist das Glossar – nicht dessen Abschrift.

## Zustände haben Namen

Der unscheinbarste Teil des Schemas trägt dabei am meisten: das Enum. `STATUS` mit seinen drei Werten ist eine vollständige, prüfbare Beschreibung des Lebenszyklus eines Angebots – und die Werte heißen so, wie die Fachseite sie am Telefon nennen würde.

Diesen Lebenszyklus habe ich in älteren Systemen selten irgendwo aufgeschrieben gefunden. Er verteilt sich dort auf Status-Strings in der Datenbank, if-Kaskaden im Service und das Gedächtnis der dienstältesten Kollegin. Hier ist er ein paar Zeilen lang, versioniert und für jeden Client sichtbar:

```mermaid
stateDiagram-v2
  [*] --> angefragt: fahrtAnfragen
  angefragt --> warteAufBestaetigung: Vermittlung läuft
  warteAufBestaetigung --> angenommen: angebotAnnehmen
  warteAufBestaetigung --> [*]: angebotAblehnen
```

Hier zeigt sich allerdings auch die erste hässliche Stelle: `warteAufBestaetigung`. GraphQL-Namen erlauben nur ASCII – Buchstaben, Ziffern, Unterstrich. Das „ä" muss draußen bleiben, die Fachsprache kommt nur gefiltert ins Schema. Ich schreibe das bewusst hin, statt es zu verstecken: Wer deutsche Bezeichner wählt, wählt auch `Bestaetigung`, `Strasse` und `ueberweisen`. Im Fließtext wäre das ein Rechtschreibfehler; im Schema ist es der Preis der Entscheidung, und man sollte ihn kennen, bevor man sie trifft.

## Schema deutsch, Implementierung englisch

Die zweite Frage, die das Experiment beantworten sollte: Wo verläuft die Grenze? Nach ein paar Wochen liegt sie für mich exakt an der Resolver-Map. Das Schema trägt die Fachsprache, alles dahinter bleibt englisch – Variablen, Hilfsfunktionen, Kommentare. Vereinfacht, hier mit `graphql-subscriptions` notiert, sieht das Muster so aus:

```javascript
const uuid = require('uuid/v4');
const { PubSub } = require('graphql-subscriptions');

const pubsub = new PubSub();
const offers = {}; // in-memory store, resets on restart

const resolvers = {
  Query: {
    angebote: (root, { take = 10, skip = 0 }) =>
      Object.values(offers).slice(skip, skip + take),
  },
  Mutation: {
    fahrtAnfragen: (root, { input }) => {
      // a ride request creates a new offer in its initial state
      const offer = {
        id: uuid(),
        von: input.von,
        nach: input.nach,
        status: 'angefragt',
      };
      offers[offer.id] = offer;
      pubsub.publish('offerChanged', { onAngebotChanged: offer });
      return offer;
    },
    angebotAnnehmen: (root, { input }) => {
      const offer = offers[input.angebotId];
      // accepting is only valid while the offer awaits confirmation
      if (offer.status !== 'warteAufBestaetigung') return offer;
      offer.status = 'angenommen';
      pubsub.publish('offerChanged', { onAngebotChanged: offer });
      return offer;
    },
  },
  Subscription: {
    onAngebotChanged: {
      subscribe: () => pubsub.asyncIterator('offerChanged'),
    },
  },
};
```

Die deutschen Wörter tauchen genau dort auf, wo das Schema sie verlangt: als Schlüssel der Resolver-Map und als Enum-Werte. Der Rest ist gewöhnlicher englischer Code. Diese Trennung hat sich als erstaunlich stabil erwiesen, und sie entspricht der fachlichen Wahrheit: `offers` und `pubsub` sind Technik und dürfen englisch sein. `Angebot` ist ein Begriff mit fachlicher Bedeutung, und der gehört der Fachseite – nicht dem, der ihn gerade implementiert.

Ein Nebeneffekt, mit dem ich nicht gerechnet hatte: Auch die Tests lesen sich plötzlich wie Fachtexte. Ein Mocha-Test, der `fahrtAnfragen` aufruft und danach prüft, ob der Status `angefragt` ist, beschreibt einen fachlichen Sachverhalt – man könnte ihn fast wörtlich als Abnahmekriterium ins Ticket kopieren. Aus Testabdeckung wird ein Stück dokumentierte Fachlichkeit, ohne dass dafür ein zusätzliches Werkzeug nötig wäre.

## Was dagegen spricht

Ehrlich bleiben: Das Experiment hat auch die Widerstände sichtbar gemacht, und die sind nicht klein.

Der Stilbruch zuerst. `type Angebot implements Node` mischt zwei Sprachen in einer Zeile. `onAngebotChanged` klebt ein englisches Präfix an ein deutsches Substantiv und hängt ein englisches Partizip dahinter – ein Bezeichner aus drei Sprachschichten. camelCase ist für englische Wortgrenzen gebaut; mit deutschen Komposita wird es holprig, `warteAufBestaetigung` liest niemand flüssig. Das stört beim Lesen, jeden Tag ein bisschen, und es hört nicht auf zu stören.

Dazu kommt das Umfeld. Jedes Tutorial, jede Stack-Overflow-Antwort, jedes Beispiel in der Apollo-Dokumentation ist englisch – wer Schnipsel überträgt, übersetzt jetzt in die Gegenrichtung. Und die härteste Grenze ist das Team selbst: Sobald eine Kollegin dazukommt, die kein Deutsch spricht, kippt die Rechnung vollständig. Dann ist das deutsche Schema keine Brücke zur Fachseite mehr, sondern eine Mauer mitten durch die Entwicklung.

Und ein praktischer Punkt, der im Alltag zählt: Suchbarkeit. Wer zu `angebotAnnehmen` nach einer Fehlermeldung sucht, findet nichts – wer zu `acceptOffer` sucht, findet wenigstens fremde Diskussionen, die halb passen. Deutsche Bezeichner koppeln ein Projekt ein Stück weit von der kollektiven Fehlersuche des Netzes ab. Für einen Prototyp ist das egal, für ein Team unter Termindruck nicht.

## Wann die Fachsprache gewinnt

Die Abwägung ist deshalb keine Geschmacksfrage, sondern eine Frage an die Domäne: In welcher Sprache ist sie selbst verfasst?

Eine Fahrtvermittlung ist ein dankbares Spielfeld, aber der eigentliche Anwendungsfall sind Domänen, die deutsch sind – nicht aus Gewohnheit, sondern von Rechts wegen. Verträge nach deutschem Recht. Tarifwerke. Abrechnungsregeln, deren Begriffe eine deutsche Verordnung wörtlich vorgibt. In einem Abrechnungsprojekt vor einigen Jahren waren Wörter wie „Abschlag" und „Gutschrift" juristisch belegt; ihre englischen Übersetzungen im Code haben regelmäßig Diskussionen ausgelöst, die es fachlich gar nicht gab – war der `discount` nun ein Abschlag oder doch ein Rabatt? Wenn das Gesetz deutsch spricht und die Fachseite deutsch denkt, ist eine englische API-Schicht die einzige Stelle im System, die übersetzt. Und damit die einzige Stelle, die unbemerkt lügen kann.

Umgekehrt gilt das genauso. Ein internationales Team, ein Produkt für mehrere Märkte, eine öffentliche API – dort ist Englisch die Fachsprache, und dann gehört sie auch konsequent ins Schema. Einen brauchbaren Mittelweg bietet die SDL übrigens gleich mit: englische Bezeichner, aber deutsche Beschreibungstexte an Typen und Feldern. Dann trägt das Schema wenigstens die Definitionen der Begriffe, wenn schon nicht ihre Namen – und die Übersetzungstabelle steht dort, wo sie geprüft und versioniert wird, statt in einem Wiki zu verstauben.

## Unterm Strich

uber-ng wird kein Produkt, und das musste es nie werden. Das Experiment hat mir zwei Dinge gezeigt. Erstens: Der Ort stimmt. Ein GraphQL-Schema kann leisten, was Glossare, Wikis und Modell-Dokumente fünfzehn Jahre lang versprochen und nicht gehalten haben – eine gemeinsame Sprache, die geprüft wird, statt nur beschrieben zu werden. Zweitens: Die Sprache im Schema ist eine Architekturentscheidung, keine Stilfrage. Deutsch im Schema kostet – Stilbrüche, ASCII-Krücken, Reibung mit dem Ökosystem. Es zahlt sich genau dann aus, wenn die Domäne deutsch ist und jede Übersetzung eine zweite, ungeprüfte Wahrheit erzeugen würde.

Für die Projekte dazwischen bleibt die Frage offen, und das ist in Ordnung. Wichtig ist mir nur, dass sie überhaupt gestellt wird. Bisher wurde sie in jedem Schema, das ich gesehen habe, stillschweigend mit „englisch" beantwortet – und stillschweigende Antworten sind selten die durchdachten.

## Weiterführende Quellen

- [uber-ng – GraphQL-Demo mit deutschem Schema (GitHub)](https://github.com/MikeBild/uber-ng)
- [GraphQL-Dokumentation: Schemas and Types](https://graphql.org/learn/schema/)
- [GraphQL Specification](https://spec.graphql.org/)
- [Martin Fowler: Ubiquitous Language](https://martinfowler.com/bliki/UbiquitousLanguage.html)
- [Eric Evans: Domain-Driven Design Reference](https://www.domainlanguage.com/ddd/reference/)
