惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
F
Fortinet All Blogs
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
S
SegmentFault 最新的问题
Blog — PlanetScale
Blog — PlanetScale
T
Tailwind CSS Blog
WordPress大学
WordPress大学
Engineering at Meta
Engineering at Meta
S
Schneier on Security
N
News and Events Feed by Topic
N
News | PayPal Newsroom
H
Help Net Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
博客园 - Franky
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
A
About on SuperTechFans
AWS News Blog
AWS News Blog
S
Secure Thoughts
The Cloudflare Blog
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
C
Cybersecurity and Infrastructure Security Agency CISA
V2EX - 技术
V2EX - 技术
Recorded Future
Recorded Future
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
Latest news
Latest news
C
Cyber Attacks, Cyber Crime and Cyber Security
大猫的无限游戏
大猫的无限游戏
The Last Watchdog
The Last Watchdog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
H
Hacker News: Front Page
P
Proofpoint News Feed
N
News and Events Feed by Topic
H
Heimdal Security Blog
L
Lohrmann on Cybersecurity
有赞技术团队
有赞技术团队
L
LangChain Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
I let my OpenAPI spec do the work: one contract for Go, Flutter, and the LLM
tkode · 2026-05-10 · via DEV Community

There was a period on a Django project where a simple question of "is this field actually used on the frontend?" would trigger a small investigation. You'd check the backend, grep through the React code, and ask someone who might know. The answer was always in there somewhere, but it was never in one place. Not like anyone was being careless. There just wasn't a contract. So every question got answered by digging.

My stack is Go on the backend and Flutter on the frontend. Before I settled on the current approach, adding an endpoint meant the same work twice. Define request and response structs in Go, wire the handler, add middleware. Then repeat the whole model layer in Dart, and build a repository around Dio to actually call it. Every change paid the same tax. Low-grade, but it compounds.

The fix I landed on isn't novel in isolation: generate code from an OpenAPI spec. But the way it fits together took a few projects to get right. The spec isn't documentation. It's the artifact everything compiles against — and the layer where human judgment, compiler enforcement, and LLM reasoning all meet before any implementation happens.


The insight: the spec isn't documentation, it's the artifact

Most OpenAPI content treats the spec as something you generate from code or maintain alongside it. The approach I kept arriving at, across three projects, was the opposite: write the spec first, generate everything from it, and never let code drift from it.

This works because OpenAPI is structured, checkable, and well-represented in LLM training data. When I ask a model to design an endpoint, it writes OpenAPI YAML reliably — better than it writes idiomatic Go or Dart from scratch. That makes the spec a natural handoff point: the human reviews it, the compiler enforces it, and the LLM never touches implementation until the contract is settled.

The first project where I tried this properly was in Flutter. Dart is typed by default, and the friction of keeping Go structs and Dart models manually aligned was obvious from the start. I set up code generation on both sides, and the gap closed. When I started the current project (an institute ERP), this was the natural starting point, not an experiment.


The codegen pipeline

The monorepo structure matters here. Everything lives together because the spec has to be the single source of truth across consumers.

api/
├── services/
│   ├── auth.yaml
│   ├── students.yaml
│   ├── attendance.yaml
│   ├── fees.yaml
│   └── ... (23 service files total)
├── openapi.yaml          ← combined, generated by redocly
├── index/                ← auto-generated navigation for LLMs
├── redocly.yaml
└── scripts/
    └── post-build.js

backend/gen/api/
├── attendance/
│   ├── codegen.yaml
│   ├── generate.go
│   └── schema.gen.go
├── auth/
│   ├── codegen.yaml
│   ├── generate.go
│   └── schema.gen.go
├── fees/
│   ├── codegen.yaml
│   ├── generate.go
│   └── schema.gen.go
└── ... (23 services, mirroring the spec split)

frontend/packages/shared_api_client/lib/
├── attendance/
│   ├── attendance_client.dart        ← generated
│   └── attendance_client.g.dart      ← generated
├── models/
│   ├── attendance_status.dart
│   ├── get_today_attendance_response.dart
│   ├── get_today_attendance_response.freezed.dart
│   ├── get_today_attendance_response.g.dart
│   ├── today_attendance_item.dart
│   ├── today_attendance_item.freezed.dart
│   └── today_attendance_item.g.dart
│   └── ... (400+ model files across all services)
└── export.dart

Enter fullscreen mode Exit fullscreen mode

Each service gets its own YAML file. openapi.yaml is assembled from these by redocly. A post-build.js script handles two things after the build: setting the correct server URLs for hosted documentation, and stripping a handful of empty fields that break swagger-parser downstream. That second task isn't something glamorous, but is a real part of any pipeline like this.

Backend. oapi-codegen generates Go interfaces and types from the spec. The backend mirrors the service split: backend/gen/api/ has a folder per service, each with a config, a generate.go, and a schema.gen.go. I wire these to gin handlers and implement the logic in backend/src/, same folder structure. Running go generate regenerates the backend code when the spec changes. It's intentionally kept as a manual trigger. You run it when you've made a deliberate change to the spec, not on every save.

Frontend. The combined openapi.yaml feeds into swagger-parser with a configuration that splits output by OpenAPI tags and generates frozen Dart classes via freezed. The generated code lives in a shared_api package that both the institute-facing and parent-facing apps consume. Dart tree-shaking handles unused paths.

The components/schemas section from attendance.yaml (API paths excluded for brevity):

AttendanceStatus:
  type: string
  enum:
    - PRESENT
    - ABSENT
    - NOT_SET

TodayAttendanceItem:
  type: object
  required:
    - studentId
    - studentName
    - admissionNumber
    - rollNumber
    - status
  properties:
    studentId:
      type: string
      format: uuid
      x-go-type: uuid.UUID
      x-go-type-import:
        path: github.com/google/uuid
    studentName:
      type: string
    admissionNumber:
      type: string
    rollNumber:
      type: string
    status:
      $ref: '#/components/schemas/AttendanceStatus'
    remarks:
      type: string

GetTodayAttendanceResponse:
  type: object
  required:
    - date
    - items
    - totalCount
  properties:
    date:
      type: string
      example: '2026-03-21'
    items:
      type: array
      items:
        $ref: '#/components/schemas/TodayAttendanceItem'
    totalCount:
      type: integer

Enter fullscreen mode Exit fullscreen mode

Generated Go (schema.gen.go):

Generated Go Code

Generated Dart (shared_api package):

Generated Dart Code

The x-go-type extension on studentId tells the generator to use uuid.UUID instead of plain string. The Dart side doesn't see it; it gets String, which is correct. One spec, two different type systems, no compromise.

The enforcement layer is the LSP. If the spec changes and codegen runs, any mismatch between how the backend implements an interface and what the spec defines is a type error, caught before the code runs. Same on the Flutter side. It's not a document you can ignore. It's a thing the compiler checks.

The tooling is table stakes. Here's why this setup pays off specifically when working with an LLM.


Agentic coding is why this is worth the setup cost

I use agentic coding heavily on this project. The spec-first approach and the agentic approach reinforce each other in a way that's hard to see until you've hit the alternative.

Without a contract, LLM-generated backend and LLM-generated frontend code drift silently. The model that wrote your Go handler and the model that wrote your Dart repository have no shared ground truth. They make different assumptions about field names, nullable types, error shapes. You find out at runtime, or you don't find out until a user does.

With codegen, drift becomes a compile error.

The loop looks like this: I describe the feature I want, then explicitly ask the agent to update the relevant YAML files first — and nothing else. I review the spec change before proceeding. Only once the contract looks right do I tell the agent to run go generate and continue to the backend implementation and tests. The frontend client is regenerated after the backend is stable — same script, pointed at the combined openapi.yaml. At every step, the generated types are what both sides compile against. If something doesn't line up, the build fails, not the app.

This sequencing is a discipline I maintain deliberately, not an automated constraint. You could enforce it via a CLAUDE.md instruction and get the same result. But the point is the same either way: the human reviews the contract before any implementation happens.

The Flutter side took more iteration to get right, but the principle was the same: let the spec own the types, and let the compiler enforce it.


The index layer: making the truth accessible to an agent efficiently

The spec grew. It's now 23 service files, covering everything from auth to timetable to parent-facing dashboards. Without explicit instructions, an LLM working on a feature would load the entire combined openapi.yaml — large, token-expensive, and mostly irrelevant. Pointing it to service-level specs helped, but only partially. The larger service files are 1000+ lines, and even those contain models unrelated to the task at hand. The agent still had no way to navigate to just what it needed.

So I built a lightweight navigation layer, auto-generated by a Python script and kept up to date via a pre-commit hook. It lives in api/index/ and produces three things: a top-level README with a service summary table; one detail file per service listing its endpoints and the models they use; and a combined models.md covering every model across all services, so an agent can search by model name without knowing which service file to open.

The entry point is a README that gives a high-level service table:

Service Size Operations Backend owner hints Detail
admission.yaml large (1795) 29 backend/src/admission/ Admission Service
attendance.yaml medium (561) 8 backend/src/attendance/ Attendance Service
institute.yaml small (237) 3 backend/src/institute/ Institute Service
students.yaml large (1760) 18 backend/src/students/ Students Service
...

The guidance in the README: small specs are cheap to read whole, large specs should be read by operation. Each service links to a detail file with an operation table and model index:

Method Path Operation ID Request models Response models Summary
POST /auth/onboarding submitInstituteOnboarding OnboardingRequest 200: User, 400/401/409/500: ErrorResponse Submit institute details (Onboarding Stage 1)
PATCH /auth/me/institute updateCurrentUserInstitute UpdateCurrentUserInstituteRequest 200: User, 400/401/403/404/500: ErrorResponse Update current user's institute details

An agent looking at a narrow task — say, updating the fees endpoint — doesn't need to load auth.yaml or students.yaml. It looks at the index, navigates to fees.md, finds the operation, and loads only what it needs.

It's still rough in places — the size classification (small, medium, large) is a heuristic, not a formal metric. But a rough heuristic maintained automatically is more reliable than a precise one maintained manually.


The honest tradeoffs

Multi-developer friction. Frontend and backend for a feature have to move together because they share generated types. When you're solo, that's fine. On a team, parallel work on the same service generates conflicts in generated files, and onboarding someone new requires a conversation before they can be productive. There are mitigations (per-service generated clients, stricter spec ownership boundaries), but I haven't needed to solve this yet.

Spec evolution. When an endpoint changes shape: update the spec, regenerate, fix the compile errors. The compile errors are useful: they surface exactly where something is breaking, which sometimes makes it obvious that old clients would be affected. Once that led me to mark a field as deprecated and add a new field alongside it rather than rename in place. The weak part is observability. There's no reliable way to know if old client versions in the wild are still hitting deprecated fields. For this project that's fine, as it's pre-launch. But worth being honest about if you're running something in production.

The swagger-parser error type limitation. The Flutter codegen only produces one response type per endpoint: the success type. All error codes get parsed manually. I've accepted this for now. It could be addressed by extending swagger-parser, but it hasn't been worth the time.

The post-build patching. The fact that post-build.js exists to strip empty fields that break downstream tooling is a real thing. The pipeline works, but it has seams. If you adopt this approach, you'll find your own seams. Budget for them.


What this is actually about

This is a workflow tradeoff: upfront structure in exchange for eliminating a specific category of mechanical toil. The toil it eliminates is model drift: the silent divergence between what your backend says and what your frontend expects. In an agentic workflow, that divergence happens faster and is harder to catch. The spec doesn't prevent LLMs from making mistakes. It just means a certain class of them — type mismatches, missing fields, inconsistent response shapes — become compile errors instead of bugs.

The core of it — one spec, three consumers, everything compiled against the same ground truth — has been stable across multiple projects. That stability is what I was actually looking for. The index layer and the error type handling are still evolving; I'll write about those when there's more to say.


Resources