Kärntner Linuxtåg 2026 · Villach
Aosabi:GraphQL
everywhere
Ein GraphQL-Server vom Mikrocontroller bis zur Edge.
Ganz kurz zu mir
Florian H. Kleber · Netsnek
Netsnek ist Co-Sponsor des Kärntner Linuxtågs und hilft mit, die Linuxtage nach Kärnten zu bringen.
Beim Linux-Stammtisch bin ich regelmäßig dabei.
Warum GraphQL
Eine API, die sich selbst beschreibt.
Ein Endpoint mit Introspection
Alle Anfragen gehen an /graphql. Das Schema beschreibt sich selbst, Werkzeuge wie GraphiQL lesen es direkt.
Zentrale Validierung von Anfragen
Jede Anfrage wird gegen das Schema geprüft, bevor eine Zeile App-Code läuft.
Objektorientierte API
Die Grundzüge der API ergeben sich aus der Funktionsweise von GraphQL. Als Developer weiß man schon im Vorhinein zirka, was einen erwartet.
Anfrage
{
song(id: "1") {
title
artist { name }
}
}Antwort, in derselben Form
{
"data": {
"song": {
"title": "Rock Me Amadeus",
"artist": { "name": "Falco" }
}
}
}Warum GraphQL
Eine Anfrage, über jede Leitung.
Keine Roundtrips
REST braucht für Song, Interpret und dessen weitere Songs drei Anfragen. GraphQL holt alles in einer.
Funktioniert auch ohne HTTP
GraphQL ist eine Abfragesprache, kein HTTP-Protokoll. Die Leitung darunter ist austauschbar.
Aosabi spricht GraphQL auch über Serial, USB, Bluetooth und LoRa, mit GraphTP als schlankem Rahmen für jede dieser Leitungen.
Warum nicht GraphQL
Fünf Gründe, bei REST zu bleiben.
Parser, Validator, Executor, Schema und Resolver statt einer Route pro Ressource.
Jede Anfrage wird geparst, als Baum aufgebaut und mehrfach durchlaufen.
Schema und Code liegen getrennt und laufen mit der Zeit auseinander.
Jede Sprache hat ihren eigenen Server, mit eigenem Verhalten in den Details.
Viel JavaScript, Python und Co. mit langen Ketten an Dependencies.
Vier davon lassen sich beheben.
Was sich ändern lässt
4 von 5 lassen sich fixen.
Mehr Moving Parts als REST
BleibtGraphQL hat mehr Teile als REST, dafür eine klare Struktur.
Unperformant und ressourcenintensiv
Single PassValidierung in einem Durchgang, in Rust.
Schema-Files
Schema aus OOPDas Schema entsteht aus Klassen und Typen im Code.
Implementierung je Sprache anders
Rust-Core, in JS, Python usw.Ein Kern, der in jeder Sprache gleich antwortet.
Security: JavaScript, Python und Dependencies
Security: Software FactoryAgenten finden und beheben, Menschen entscheiden.
AgenticBackend Framework
Speed durch Single Pass und Rust
Ein Durchgang statt Baum und 27 Regeln.
Das Schema wird beim Build zu einer Tabelle. Jedes Token wird direkt gegen sie geprüft.
Lehnt der Single Pass eine Anfrage ab, prüfen die 27 Regeln nach. Die Antwort bleibt gleich.
Ohne Garbage Collector und ohne std: derselbe Kern läuft auf Server, Edge und ESP32.
Generiertes Schema
Das Schema kommt aus dem Code.
Rust · src/lib.rs
use aosabi::prelude::*;
/// A song in the library.
pub struct Song {
pub title: String,
pub artist: Artist,
}
pub struct Artist {
pub name: String,
}
impl Artist {
pub fn songs(&self) -> Vec<Song> {
library().by(&self.name)
}
}
pub struct Query;
impl Query {
pub fn song(&self, id: ID) -> Option<Song> {
library().song(id)
}
pub fn songs(&self) -> Vec<Song> {
library().all()
}
}
pub fn app() -> App { App::new().query(Query) }TypeScript · src/index.ts
import {App, type ID} from 'aosabi'
/** A song in the library. */
class Song {
constructor(
public title: string,
public artist: Artist
) {}
}
class Artist {
constructor(public name: string) {}
songs(): Song[] {
return library.by(this.name)
}
}
export default new App({
graphql: {
Query: {
song: (id: ID): Song | null =>
library.song(id),
songs: (): Song[] => library.all()
}
}
})schema.graphql · generiert aus lib.rs
type Query {
song(id: ID!): Song
songs: [Song!]!
}
"""
A song in the library.
"""
type Song {
title: String!
artist: Artist!
}
type Artist {
name: String!
songs: [Song!]!
}Software Factory
Agenten bauen, Menschen entscheiden.
Jede Runde liest die Logs der letzten Runde und die Logs aus dem Produktivbetrieb, statt von vorne zu beginnen.
Entscheidungen mit Tragweite trifft ein Mensch. Agenten bereiten sie vor.
Findings werden behoben, nie nachgebaut. Jeder Fix ist ein eigener, prüfbarer Commit.
aoviewer
Die Song-App, wie aoviewer sie zeichnet.
#/whole/Query · der ganze Graph
#/op/query/song · der Weg der Operation song
Das Schema kommt aus dem Rust-Code, aoviewer zeichnet es als SVG. Genau diese App läuft in den Benchmarks.
Song.artist führt zu Artist und Artist.songs zurück zu Song. Diesen Weg nimmt die Query von vorhin.
Der ganze Graph, ein einzelner Typ oder der Weg einer Operation, jede Ansicht hat ihre eigene Adresse.
Benchmark
Zeit und Größe, gegen REST und graphql-js.
Derselbe Generator fragt alle Server ab, eine Anfrage nach der anderen, mit den Daten von vorhin. REST braucht drei Anfragen, GraphQL eine.
Ohne JIT läuft JavaScript nur im Interpreter. graphql-js wird dann auf Bun fast neunmal und auf Node.js viereinhalbmal langsamer. Aosabi braucht keinen JIT.
Gezählt wird, was den Server startet. Die App selbst hat überall nur 1 bis 25 KB, der Rest sind Laufzeit und Bibliotheken.
Puhh,wer kann sich heutzutage noch so viel Speicher leisten?
Benchmark
ESP32-C3: drei Roundtrips gegen einen.
REST fragt dreimal nacheinander, denn erst die erste Antwort nennt den Interpreten. Aosabi fragt einmal.
Jedes Byte jeder Anfrage und jeder Antwort braucht 86,8 µs auf der Leitung.
Die Song-App von vorhin mit denselben Daten, als eigenes Image je Seite auf einem simulierten ESP32-C3.
Resultat
Embedded-Demo: ESP32 in Rust.
Query
{
hello
uptime
heap { free used peak }
temperature
}Mutation
mutation {
led(on: true, colour: CYAN) {
on
colour
}
}Resultat
Cloudflare Worker: TypeScript mit HTML-Client.
Klassen und Typen in index.ts, das Schema entsteht daraus.
Die Engine läuft als core.wasm direkt im Worker.
Eine statische Seite fragt /graphql direkt, ohne Framework.
Benchmark
Cloudflare Worker: Kaltstart und Größe.
26
Dependencies weniger im ausgelieferten Worker
Pylon bündelt 29 fremde Pakete in den Worker, Aosabi 3: @cloudflare/unenv-preset und unenv, die Wrangler beiden mitgibt, und once_cell in core.wasm.
Die Song-App in TypeScript, einmal mit Pylon und graphql-js, einmal mit Aosabi. Beide antworten byte-gleich.
Nach dem Kaltstart antworten beide in unter einer Millisekunde, Aosabi in 0,66 ms, Pylon in 0,59 ms.
Aosabi bringt die Engine als core.wasm mit. Sie macht 2,2 der 2,4 MB aus.
Danke!
Florian H. Kleberlinkedin.com/in/kleberbaum
Netsnek
Aosabigithub.com/netsnek/aosabi