07 — Softwareentwicklung
Vom siliziumnahen C zum Edge-TypeScript
Ich wähle die Sprache nach dem Fehlerfall, den ich zu vermeiden versuche.
Rust dort, wo Nebenläufigkeit beweisbar korrekt sein muss, Go dort, wo Netzwerkdienste einfach bleiben müssen, C nah an der Hardware, Python und TypeScript dort, wo Modelle und Menschen auf das System treffen — bewusst gewählt, in der Tiefe betrieben.
Das ehrliche Maß eines Ingenieurs ist nicht der Stack auf einem Lebenslauf — es ist das, was den Kontakt mit echten Nutzern überlebt hat. Meines sind elf Jahre davon.
Seit mehr als elf Jahren liefere ich mobile Anwendungen an echte Nutzer auf iOS, Android und Windows aus. Diese Zahl ist der Nachweis, dem ich am meisten vertraue, denn an ein Gerät in der Tasche eines Menschen auszuliefern ist unerbittlich: Es gibt keine Staging-Umgebung für einen Absturz auf dem Telefon eines Fremden, kein Rollback für eine Funktion, die den Nutzer verwirrt. Ein Jahrzehnt davon lehrt eine spezifische Disziplin — Versionsfragmentierung, Offline-Verhalten, Akku- und Speicherbudgets und das langsame Anwachsen des Gespürs dafür, was wegzulassen ist.
Hinter jeder dieser Apps stand ein Backend und die Cloud-Infrastruktur darunter. Die Praxis ist also nicht nur die Vorderseite des Glases; sie reicht vom Touch-Ereignis abwärts, durch die API, die Warteschlange, den Datenspeicher und den Container, bis zum Kernel, auf dem der Container läuft. Ich habe an jeder Schicht genug Zeit verbracht, um zu wissen, wo die Nähte sind, und um so zu entwerfen, dass die Nähte unter Last nicht reißen.
Was ich seitdem hinzugefügt habe, ist Tiefe in Systemsprachen und Multi-Cloud-Auslieferung: Rust und Go für die Kerne, die korrekt und schnell sein müssen, Docker und Kubernetes für die Auslieferung, und eine bewusste Weigerung, irgendetwas davon an einen einzigen Anbieter zu binden. Der Rest dieser Seite ist die konkrete Gestalt davon — Sprachen, Plattformen, Clouds und die kleinen Werkzeuge, die ich mir unterwegs baue.
Jahre Auslieferung mobiler Anwendungen an echte Nutzer
mobile Plattformen in Produktion: iOS, Android, Windows
Clouds betrieben ohne Anbieterbindung: GCP, Azure, AWS
Hauptsprachen in der Tiefe beherrscht: Rust, Go, Python, TypeScript
Ein Stack, gewählt nach Schicht, nicht nach Mode.
Schichtdiagramm der Systemsprachen
Jede Sprache verdient ihren Platz auf der Schicht, wo ihre Garantien zählen.
Ich greife nicht zu einer Lieblingssprache und biege jedes Problem zu ihr hin. Das Diagramm liest sich vom Silizium nach oben: C und C++ dort, wo der Code die Hardware berührt, Rust für die speichersicheren nebenläufigen Kerne, Go für die nebenläufige Netzwerkschicht, dann Python und TypeScript an der Oberfläche, wo KI, Daten und Nutzer leben.
Die Balkenbreiten sind ein grobes Gefühl für die Breite der Nutzung über meine Arbeit hinweg — kein Benchmark, nur dort, wo das Volumen an Code tendenziell sitzt.
- Untere Schichten kaufen Determinismus und Sicherheit
- Obere Schichten kaufen Geschwindigkeit und Reichweite
- Die Grenze ist eine bewusste Wahl, pro Projekt getroffen
Wie der Code tatsächlich aussieht.
Ein begrenzter Worker-Pool in Go — Arbeit über Goroutines auffächern, Ergebnisse über einen Channel einsammeln und Abbruch respektieren. Das Muster taucht unter fast jedem Netzwerkdienst auf, den ich schreibe.
// FanOut runs fn across at most n goroutines, streaming results
// back on a channel and stopping early if ctx is cancelled.
func FanOut[T, R any](ctx context.Context, in []T, n int, fn func(T) R) []R {
jobs := make(chan int)
out := make([]R, len(in))
var wg sync.WaitGroup
for w := 0; w < n; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := range jobs {
select {
case <-ctx.Done():
return
default:
out[i] = fn(in[i])
}
}
}()
}
for i := range in {
select {
case <-ctx.Done():
close(jobs)
wg.Wait()
return out
case jobs <- i:
}
}
close(jobs)
wg.Wait()
return out
} Wohin die Ingenieurarbeit tatsächlich geht.
Die Sprache nach dem Fehlerfall wählen, den ich vermeide.
Rust ist dort, wo Korrektheit unter Nebenläufigkeit nicht verhandelbar ist. Der Borrow-Checker ist keine Steuer — er ist ein Beweis zur Kompilierzeit, dass eine Klasse von Data-Races und Use-after-free-Fehlern nicht auftreten kann, was genau das ist, was ich für einen performanzkritischen Kern will, der unbeaufsichtigt laufen muss.
Go ist dort, wo ich nebenläufige Netzwerkdienste will, über die ich schnell nachdenken kann: Goroutines und Channels für das Nebenläufigkeitsmodell, der eingebaute pprof-Profiler zum Finden des heißen Pfads und ein statisches Binary, das sich sauber in einen Container deployen lässt. C und C++ bleiben für die hardwarenahe Arbeit reserviert — Firmware, eingebettetes SoC, der Code, der direkt auf der Registerkarte sitzt.
- Rust — Ownership, Lifetimes, kostenfreie Abstraktionen, furchtlose Nebenläufigkeit
- Go — Goroutines, Channels, Context-Abbruch, pprof-Profiling
- C / C++ — eingebettete Firmware, hardwarenahe Steuerung, deterministisches Timing
- Bash und SQL in Produktion, über PostgreSQL und BigQuery
Elf Jahre Auslieferung an echte Geräte, nicht an Simulatoren.
Auf iOS arbeite ich in Swift und Objective-C mit SwiftUI für neue Oberflächen, Core Data für Persistenz, Core Animation für Bewegung und SiriKit für Sprach-Intents. Auf Android ist es Kotlin und Java gegen das Android SDK, mit der Versionsfragmentierungs-Disziplin, die daher kommt, viele OS-Versionen im Feld zu unterstützen.
Plattformübergreifend greife ich zu React Native (fortgeschritten) oder Flutter (fortgeschritten), wenn eine geteilte Codebasis sich lohnt, und ich habe mit Xamarin auf Arbeitsniveau ausgeliefert. Hinter den Apps sitzen echte Backends — Node/Express, Spring Boot, Django/Flask, Ruby on Rails oder Go mit Gin und Gorm — die REST und GraphQL bereitstellen.
- iOS — Swift, Objective-C, SwiftUI, Core Data, Core Animation, SiriKit
- Android — Kotlin, Java, Android SDK über viele OS-Versionen
- Plattformübergreifend — React Native, Flutter (fortgeschritten), Xamarin (Arbeitsniveau)
- Daten — SQLite, Redis, ORMs (Sequelize, Mongoose); REST- und GraphQL-APIs
Portabel von Grund auf, nicht an einen Anbieter gebunden.
Ich halte Workloads portabel, damit die Cloud eine Ware bleibt statt einer Abhängigkeit. Auf GCP nutze ich Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery und Firestore. Auf Azure AKS, Functions, Cognitive Services und Container Apps. Auf AWS Lambda, ECS/EKS, S3, RDS und SQS/SNS hinter CloudFront.
Am Edge betreibe ich Cloudflare Workers und Vercel Edge; für sich schnell bewegende Produkte stütze ich mich auf Supabase oder Firebase als Backend. Alles wird über Container ausgeliefert — Docker und Kubernetes — mit CI/CD auf GitHub Actions oder GitLab CI und als Code definierter Infrastruktur, sodass eine Umgebung reproduzierbar statt von Hand gebaut ist.
- GCP — Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery, Firestore
- Azure — AKS, Functions, Cognitive Services, Container Apps
- AWS — Lambda, ECS/EKS, S3, RDS, SQS/SNS, CloudFront
- Edge und BaaS — Cloudflare Workers, Vercel Edge, Supabase, Firebase
Verteilte Systeme, die korrekt bleiben, während sich Dinge bewegen.
Die Backend-Arbeit ist dort, wo sich das Systemdenken auszahlt. Ich entwerfe rund um Nachrichtenwarteschlangen und ereignisgesteuerte Architektur, damit die Komponenten entkoppelt bleiben, und rund um Echtzeitsysteme, in denen WebSocket und Kommunikation auf Socket-Ebene den Zustand mit geringer Latenz zwischen Client und Server tragen.
Dienst-zu-Dienst-Protokolle, Idempotenz und Backpressure sind erstrangige Anliegen, keine nachträglichen Gedanken. Das Ziel ist eine Topologie, die anmutig degradiert — ein ausfallender Dienst sollte das System verlangsamen, nicht stoppen.
- Nachrichtenwarteschlangen und ereignisgesteuerte Architektur
- Echtzeitsysteme über WebSocket und Kommunikation auf Socket-Ebene
- Dienst-zu-Dienst-Protokolle, Idempotenz, Backpressure
- Verteilte Koordination mit anmutiger Degradation
Eine Anwendung, drei Clouds, keine Bindung.
Ein Workload, den ich portabel halte: Container und Infrastruktur als Code im Zentrum, deploybar zu dem Anbieter, in dem ein Engagement bereits lebt. Die Cloud ist eine Warenschicht, keine Abhängigkeit.
GCP
Vertex AI, Cloud Run, Cloud Functions, GKE, Pub/Sub, BigQuery, Firestore — meine Standardwahl für KI-Workloads und verwaltete Container-Auslieferung.
Azure
AKS, Functions, Cognitive Services, Container Apps — eingesetzt dort, wo eine Organisation bereits im Microsoft-Umfeld lebt.
AWS
Lambda, ECS/EKS, S3, RDS, SQS/SNS hinter CloudFront — die Breite an Primitiven für klassische Produktionsstacks.
Edge
Cloudflare Workers und Vercel Edge — Logik, die nah am Nutzer läuft, für Latenz und globale Reichweite.
BaaS
Supabase und Firebase (Auth, Firestore, Realtime DB) — wenn ein Produkt ein Backend in Tagen braucht, nicht in Wochen.
Auslieferung
Docker und Kubernetes, GitHub Actions und GitLab CI, Infrastruktur als Code — reproduzierbare Umgebungen von Anfang bis Ende.
Die Cloud sollte eine Ware sein, die ich austauschen kann, nie eine Abhängigkeit, die das Produkt besitzt.
Eine kontinuierliche persönliche Linux-Toolchain
Jede Reibung im Arbeitsablauf wird zu einem kleinen Programm.
Ich betreibe Linux auf Administratorebene — Kernel-Tuning, sysctl-Härtung, systemd-Units, Container-Runtimes und das Netzwerk darunter. Doch der Teil, der definiert, wie ich arbeite, ist kleiner: Über die Jahre habe ich Dutzende eigener Kommandozeilen-Werkzeuge geschrieben, eines nach dem anderen, jedes geboren aus einer Reibung, auf die ich mehr als einmal gestoßen bin.
Eine zweimal getane Aufgabe ist ein Kandidat für ein Werkzeug. Das Ergebnis ist eine Workstation, die zu meinen Händen passt statt zu den Standardeinstellungen — und, wichtiger, der Instinkt dessen, der das Werkzeug baut, statt die Lücke zu dulden. Dieser Instinkt ist derselbe, der am Ende in den Produkten ausgeliefert wird.
- Kernel-Tuning, sysctl-Härtung, systemd, Container-Runtimes, Netzwerk
- Dutzende selbstgeschriebener CLI-Werkzeuge, jedes löst eine wiederkehrende Reibung
- Der Reflex eines Werkzeugbauers, nicht nur eines Werkzeugnutzers
Was ich in eine Konstruktion einbringe.
Engineering-Stack
- Primäre Systemsprachen
- Rust, Go, C/C++
- Hochsprachen
- Python, TypeScript/JS
- Mobil — nativ
- Swift, Obj-C, Kotlin, Java
- Mobil — plattformübergreifend
- React Native, Flutter
- Backends
- Node, Spring, Django, Rails, Go
- API-Stile
- REST, GraphQL
- Datenspeicher
- PostgreSQL, BigQuery, SQLite, Redis
- Container
- Docker, Kubernetes
- CI/CD
- GitHub Actions, GitLab CI
- Linux
- Administratorebene
Nichts davon ist eine Wunschliste. Jede Zeile ist ein Werkzeug, mit dem ich ausgeliefert habe — gewählt, in einem bestimmten Projekt, weil es die richtige Antwort für diese Schicht und diesen Fehlerfall war.
Der rote Faden durch alles ist dasselbe Temperament: das System vor der Datei bauen, für die Nähte entwerfen, die Auslieferung reproduzierbar halten und die Abhängigkeiten ablehnen, die das Produkt später besitzen würden.
Entkoppelt per Nachricht, nicht per Hoffnung.
Eine Producer- / Broker- / Consumer-Topologie
Komponenten sprechen über einen Broker, damit ein Ausfall das System verlangsamt statt es zu stoppen.
Wenn Dienste sich direkt aufrufen, wird eine einzige langsame Abhängigkeit zu einer Kaskade. Stattdessen entwerfe ich rund um einen Ereignis-Broker: Producer veröffentlichen Fakten über das Geschehene, der Broker hält sie dauerhaft, und Consumer verarbeiten im eigenen Tempo. Der Producer blockiert nie auf dem Consumer, und ein Consumer, der zurückfällt, leert seinen Rückstau, ohne Arbeit zu verlieren.
Die Ereignisse werden zur Quelle der Wahrheit über das Geschehene — eine Dead-Letter-Queue fängt, was nicht verarbeitet werden kann, Wiederholungen sind begrenzt und idempotent, und ein neuer Consumer kann denselben Stream abonnieren, ohne dass jemand den Producer ändert. Das ist die Eigenschaft, die ich kaufe: die unabhängige Weiterentwicklung von Teilen, die nie voneinander wissen müssen.
- Producer veröffentlichen, Consumer abonnieren — keine direkte Kopplung
- Ein dauerhafter Broker fängt Stöße ab; Consumer verarbeiten im eigenen Tempo
- Dead-Letter-Queue und begrenzte, idempotente Wiederholungen bei Fehlern
Von einem Commit zur Produktion, mit dem Artefakt, das nach vorn befördert wird.
Eine CI/CD-Pipeline, die ich als nicht verhandelbare Infrastruktur behandle. Das Image wird einmal gebaut und unverändert durch jedes Tor befördert, sodass das, was in Produktion läuft, byteweise das ist, was die Tests bestanden hat.
Von Commit zu Canary
- 01 Commit Push auf einen Branch. Pre-Commit-Hooks führen Formatierung und Lint lokal aus, sodass das Offensichtliche gefangen wird, bevor die CI überhaupt anläuft.
- 02 Build und Test Container-Image einmal gebaut, Unit- und Integrationstests dagegen ausgeführt, Coverage-Schwelle erzwungen. Dasselbe Artefakt wird nach vorn befördert.
- 03 Scan Statische Analyse, Abhängigkeits-Audit und Image-Schwachstellenscan. Ein fehlschlagender Scan blockiert den Merge, nicht das Gedächtnis eines menschlichen Prüfers.
- 04 Staging Das signierte Image in eine Staging-Umgebung deployen, die die Produktion spiegelt, Smoke-Tests und einen kurzen Soak ausführen.
- 05 Befördern In Produktion hinter einem Canary oder einem Blue-Green-Switch ausrollen, die Metriken beobachten und die vorherige Version einen Befehl entfernt halten.
Zustand über einen Socket getragen, mit den Fehlerfällen behandelt.
Ein Socket-Service-Mesh
Ein WebSocket-Gateway vorn, zustandsbehaftete Verbindungen dahinter, ein geteilter Store für Dauerhaftigkeit.
Echtzeitarbeit lebt oder stirbt an den unglamourösen Teilen: was passiert, wenn die Verbindung eines Clients mitten in einer Nachricht abbricht, wenn ein Consumer hinter den Producer zurückfällt, wenn dasselbe Ereignis zweimal ankommt. Ich terminiere WebSocket- und Socket-Verbindungen an einem Gateway, leite jede an einen zustandsbehafteten Handler und halte dauerhaften Zustand in einem geteilten Store, sodass ein wiederverbindender Client fortsetzen statt neu starten kann.
Backpressure ist explizit — begrenzte Buffer mit einer definierten Verwerfen-oder-Zusammenfassen-Richtlinie, nie eine unbegrenzte Queue, die still den Speicher frisst, bis der Prozess stirbt. Präsenz wird mit Heartbeats und TTL-Schlüsseln verfolgt, und die Auslieferung ist idempotent, damit ein At-least-once-Transport nicht zu At-least-twice-Verhalten für den Nutzer wird.
- WebSocket-/Socket-Gateway terminiert und leitet Verbindungen
- Zustandsbehaftete Handler pro Verbindung; geteilter Store für Dauerhaftigkeit
- Heartbeats, TTL-Präsenz, Resume bei Wiederverbindung, idempotente Auslieferung
Echtzeit-Primitive
- Transport
- WebSocket, rohe TCP-Sockets, gRPC-Streams
- Fan-out
- Pub/Sub, Redis-Kanäle, SSE für eine Richtung
- Backpressure
- Begrenzte Buffer, Verwerfen-/Zusammenfassen-Richtlinien
- Auslieferung
- Idempotenzschlüssel, At-least-once + Dedupe
- Präsenz
- Heartbeats, TTL-Schlüssel, Wiederverbinden mit Resume
- Zustand
- Akteur pro Verbindung, geteilter Store für Dauerhaftigkeit
Zwei weitere Ausschnitte aus dem Stack im Einsatz.
Rusts Ownership und Gos Channels lösen dasselbe Problem — geteilter Zustand unter Nebenläufigkeit — von entgegengesetzten Enden: das eine bewegt das Ownership, sodass es nichts zu teilen gibt, das andere teilt durch Kommunizieren.
// Ownership moves; the compiler proves no use-after-move.
fn consume(buf: Vec<u8>) -> usize {
buf.len() // buf is owned here, freed at the end
}
fn main() {
let data = vec![1u8, 2, 3];
let n = consume(data);
// using 'data' here would not compile: it was moved.
println!('consumed {} bytes', n);
// Borrow instead of move when the caller keeps the value.
let kept = vec![4u8, 5];
let total: u8 = kept.iter().sum();
println!('{} still owns {} items', total, kept.len());
} // Share by communicating: a generator feeds a stage over a channel.
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
} Logik, die nah am Nutzer läuft.
Ein TypeScript-Edge-Handler — von der Art, die ich auf Cloudflare Workers oder Vercel Edge deploye. Er läuft am Präsenzpunkt, der der Anfrage am nächsten ist, sodass eine Cache-Prüfung oder ein Auth-Gate nie eine Rundreise zu einer zentralen Region zahlt.
// Runs at the edge: serve from cache, fall through to origin once.
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext) {
const cache = caches.default;
const hit = await cache.match(req);
if (hit) return hit;
const res = await fetch(req);
if (res.ok && req.method === 'GET') {
const cached = new Response(res.body, res);
cached.headers.set('cache-control', 'public, max-age=60');
ctx.waitUntil(cache.put(req, cached.clone()));
return cached;
}
return res;
},
}; Der richtige Speicher für das Zugriffsmuster.
Ich wähle einen Datenspeicher nicht aus Gewohnheit. Ich wähle ihn nach der Form der Lese- und Schreibvorgänge, die er absorbieren muss, und akzeptiere, dass die meisten realen Systeme mehr als einen nutzen.
Ein relationaler Speicher verdient die Standardposition, weil die meisten Daten relational sind und Transaktionen ihren Preis wert sind. Aber der heiße Pfad, den ein Cache bedient, die analytische Frage, die Milliarden Zeilen scannt, und die Local-First-Persistenz auf einem Telefon sind drei verschiedene Probleme — und vorzugeben, dass eine Engine alle drei beantwortet, ist die Art, wie Systeme auf schwer rückgängig zu machende Weise langsam werden.
Die Disziplin unter ihnen allen ist dieselbe: Das Schema lebt als versionierte, umkehrbare Migrationen im Repo, Indizes werden gegen den tatsächlichen Abfrageplan entworfen statt geraten, und die Abfragen auf dem heißen Pfad werden gelesen und von Hand abgestimmt, auch wenn ein ORM den ersten Entwurf geschrieben hat.
PostgreSQL
Der relationale Standardspeicher — Transaktionen, JSONB wo es seinen Platz verdient, und Indizes, die gegen den Abfrageplan entworfen statt geraten werden.
BigQuery
Analytische Skalierung. Spaltenspeicher und Partitionierung für die Fragen, die Milliarden Zeilen scannen, aber selten laufen.
SQLite
Persistenz auf dem Gerät und eingebettet für Mobil und Edge — eine einzige Datei, kein Server, das richtige Werkzeug für Local-First-Apps.
Redis
Der heiße Pfad — Caching, Rate-Limiting, Warteschlangen, flüchtige Präsenz und Locks, wo Mikrosekunden und TTLs zählen.
ORMs
Sequelize, Mongoose, GORM, wo sie die Auslieferung beschleunigen — aber das generierte SQL bleibt lesbar, und die heißen Abfragen werden von Hand geschrieben.
Migrationen
Schema als versionierte, umkehrbare Migrationen im Repo, sodass eine Datenbank reproduzierbar statt archäologisch ist.
Der Kernel, auf dem der Container läuft, als Teil des Produkts behandelt.
Ein Defense-in-Depth-Stack
Sicherheit als Schichten, vom signierten Boot bis zum Audit außerhalb des Hosts, nicht eine einzige Firewall-Regel.
Ich administriere Linux bis zum Kernel und behandle den Host als Teil der Angriffsfläche statt als trägen Ort, an dem ein Container läuft. Der Stack liest sich von unten nach oben: ein minimaler signierter Boot und eine sysctl-Baseline, Nutzer mit minimalen Rechten mit SSH-Schlüsseln und ohne Passwörter, eine Default-Deny-Firewall, Prozessisolation durch systemd-Sandboxing und cgroups und Audit-Logs, die aus dem Host versandt werden, sodass eine Kompromittierung ihre eigene Spur nicht still löschen kann.
Nichts davon ist exotisch — es ist die gewöhnliche Disziplin, einen Host so zu betreiben, als würde er angegriffen, weil er es irgendwann wird. Derselbe Instinkt, der eine kleine CLI für eine wiederkehrende Reibung baut, baut einmal ein gehärtetes Basis-Image und verwendet es überall wieder, sodass der sichere Weg auch der einfache Weg ist.
- Signierter Boot, minimale Kernel-Module, sysctl-Baseline
- nftables Default-Deny, systemd-Sandboxing, seccomp, cgroups
- auditd und Log-Versand außerhalb des Hosts; unbeaufsichtigte Sicherheitsupdates
Wie sich der Stack ansammelte, Schicht für Schicht.
Kein einzelnes Jahr lehrte all dies. Es sammelte sich an, wie ein Stack es sollte — vom Glas, das der Nutzer berührt, abwärts, bis der Kernel so vertraut war wie die Aussicht.
- Jahre 1–3 Native Mobil-Apps, von Anfang bis Ende Swift und Objective-C auf iOS, Kotlin und Java auf Android. Ich lernte die Disziplin, an Geräte im Feld auszuliefern — Fragmentierung, Offline, Akku- und Speicherbudgets.
- Jahre 3–6 Die Backends hinter den Apps Node, Spring, Django und Rails, die REST und GraphQL bedienen, gestützt von PostgreSQL und Redis. Die App hörte auf, an der API zu enden, und begann am Kernel.
- Jahre 6–9 Plattformübergreifend und Cloud React Native und Flutter, wo sich eine geteilte Codebasis auszahlte; Container, Kubernetes und Multi-Cloud-Auslieferung, sodass die Infrastruktur reproduzierbar wurde.
- Jahre 9–heute Tiefe in Systemen und Verteiltem Rust und Go für Kerne, die korrekt und schnell sein müssen, ereignisgesteuerte und Echtzeit-Topologien und eine kontinuierliche persönliche Linux-Toolchain unter alldem.
Open to the right work
Wenn Sie einen Ingenieur brauchen, der den gesamten Stack halten kann — vom Kernel bis zur Cloud — ist das die Arbeit, die ich mache.
If you are holding a problem that doesn't fit inside one field, that is the conversation I want.