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

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
SecWiki News
SecWiki News
Project Zero
Project Zero
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy International News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Scott Helme
Scott Helme
A
Arctic Wolf
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
The Hacker News
The Hacker News
T
Tenable Blog
雷峰网
雷峰网
有赞技术团队
有赞技术团队
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
T
Threatpost
AWS News Blog
AWS News Blog
L
LINUX DO - 热门话题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
月光博客
月光博客
Spread Privacy
Spread Privacy
S
Secure Thoughts
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
The Last Watchdog
The Last Watchdog
Y
Y Combinator Blog
I
Intezer
博客园 - 【当耐特】
B
Blog RSS Feed
Attack and Defense Labs
Attack and Defense Labs
I
InfoQ
博客园 - 叶小钗
Cyberwarzone
Cyberwarzone
V2EX - 技术
V2EX - 技术
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
H
Help Net Security
C
CERT Recently Published Vulnerability Notes

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
OpenAPI仕様の差分を比較し、CIで破壊的変更をブロックする方法
Akira · 2026-06-16 · via DEV Community
Cover image for OpenAPI仕様の差分を比較し、CIで破壊的変更をブロックする方法

Akira

プルリクエストで openapi.yaml が編集され、CIチェックはグリーン。仕様は有効、リンターもクリーン、レビュー担当者2人が承認済み。ところが3日後、モバイルクライアントが、以前は存在していたレスポンスフィールドを読めずにヌルポインタでクラッシュしました。誰も意図的に削除したわけではありません。リファクタリング中に誰かがプロパティ名を変更し、レビューでは見落とされました。

今すぐApidogを試す

通常のOpenAPIバリデーターでは、この種の問題は検出できません。仕様が正しい構文で書かれていても、既存クライアントとの互換性を壊す可能性があります。必要なのは、新しい仕様をマージ前に既存仕様と比較し、「昨日まで動いていたクライアントを壊すか?」を機械的に判定することです。これがOpenAPI diffであり、CIのマージゲートとして実行すべきチェックです。

OpenAPI diffが比較するもの

OpenAPI diffは、2つの仕様ファイルを比較します。

  • base: ターゲットブランチ上の仕様。通常は現在稼働中の契約
  • head: プルリクエストで提案されている仕様

単なる git diff ではありません。OpenAPIの構造を理解し、パス、メソッド、パラメーター、スキーマ、レスポンスを比較して、変更を分類します。

安全な追加変更の例:

  • 新しいオプションのリクエストパラメーターを追加する
  • 新しいレスポンスフィールドを追加する
  • 新しいエンドポイントを追加する
  • リクエストボディに新しいEnum値を追加する

既存クライアントは、これらの変更があっても基本的には動作し続けます。

一方、後方互換性を壊す変更の例:

  • クライアントが読んでいるレスポンスフィールドを削除する
  • プロパティ名を変更する
  • オプションだったパラメーターを必須にする
  • 型を string から integer へ変更する
  • クライアントが送信する可能性のあるEnum値を削除する
  • エンドポイントまたはHTTPメソッドを削除する

OpenAPI diffツールの役割は、これらの変更を構造的に検出し、破壊的変更として報告することです。行単位の差分では、フォーマット変更の中に埋もれた required の変更を見落としがちです。構造的なdiffなら、どのパスのどのフィールドが互換性を壊したのかを直接示せます。

互換性ルールの背景を整理したい場合は、大規模なAPIのバージョン管理と非推奨化戦略も参考になります。diffは、レビュー担当者の記憶に頼らず、それらのルールをCIで強制する方法です。

oasdiffを使って破壊的変更を検出する

oasdiff は、OpenAPI diffでよく使われるオープンソースツールです。Go製の単一バイナリで、OpenAPI 3.0 / 3.1を比較できます。

主に使うサブコマンドは3つです。

oasdiff diff base-openapi.yaml head-openapi.yaml

すべての差分を表示します。

oasdiff breaking base-openapi.yaml head-openapi.yaml --fail-on ERR

後方互換性を壊す変更だけを検出します。CIゲートではこれを使います。

oasdiff changelog base-openapi.yaml head-openapi.yaml

人間が読みやすい変更履歴を生成します。

マージゲートとして使う最小コマンドは次のとおりです。

oasdiff breaking base-openapi.yaml head-openapi.yaml --fail-on ERR

base-openapi.yaml はターゲットブランチの仕様、head-openapi.yaml はPR側の仕様です。--fail-on ERR を付けると、重大な破壊的変更が見つかった場合にゼロ以外の終了コードで終了します。CIはこれを失敗として扱えます。

より厳格にしたい場合は WARN でも失敗させます。

oasdiff breaking base-openapi.yaml head-openapi.yaml --fail-on WARN

実運用では、まず ERR で導入し、チームの運用に合わせて WARN まで広げるのが現実的です。

oasdiff は出力形式も柔軟です。テキストだけでなく、HTML、JSON、YAML、Markdownなどに出力できるため、CIアノテーションやPRコメント、変更履歴生成に組み込みやすいです。

JVM環境なら openapi-diff も選択肢

Java / JVMベースのプロジェクトでは、OpenAPITools/openapi-diff も使えます。Java 8以降で動作し、OpenAPI 3.x仕様を比較できます。

基本コマンドは次のとおりです。

openapi-diff old-openapi.yaml new-openapi.yaml --fail-on-incompatible

--fail-on-incompatible を付けると、後方互換性を壊す変更がある場合だけゼロ以外の終了コードを返します。

用途別には次のオプションも使えます。

# 互換性の有無だけを機械的に取得
openapi-diff old-openapi.yaml new-openapi.yaml --state

# 互換性に関係なく、何らかの変更があれば失敗
openapi-diff old-openapi.yaml new-openapi.yaml --fail-on-changed

HTMLやMarkdownレポートも生成できるため、CIの成果物として差分レポートを残したい場合に便利です。すでにJVMランタイムを使っているチームなら導入しやすい選択肢です。軽量さを優先するなら oasdiff、JVM統合やレポート出力を重視するなら openapi-diff を選ぶとよいでしょう。

GitHub ActionsでOpenAPI diffをマージゲートにする

手元でdiffを実行しても、実行を忘れれば意味がありません。仕様ファイルを変更するすべてのPRで、自動的に実行する必要があります。

GitHub Actionsで oasdiff を使う例です。

name: openapi-diff

on:
  pull_request:
    paths:
      - "openapi.yaml"

jobs:
  breaking-changes:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get base spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml

      - name: Install oasdiff
        run: |
          curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh

      - name: Diff for breaking changes
        run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

重要なポイントは次の4つです。

  1. fetch-depth: 0 で履歴を取得する
  2. git show origin/${{ github.base_ref }}:openapi.yaml でベースブランチ上の仕様を取り出す
  3. PR側の openapi.yaml と比較する
  4. oasdiff breaking ... --fail-on ERR の終了コードでCIを失敗させる

これで、互換性を壊す仕様変更がPR内に含まれている場合、マージ前にチェックが赤くなります。

すべての破壊的変更が間違いとは限りません。メジャーバージョンアップや明示的な非推奨化の一環として、意図的に壊す場合もあります。その場合でも、デフォルトではブロックし、例外だけを明示的に許可する運用にすると安全です。

例:

  • PRに breaking-change-approved ラベルがある場合のみ通す
  • info.version のメジャーバージョンが上がっている場合のみ許可する
  • 承認者を追加で要求する

破壊的変更をどう扱うかは、APIバージョン管理戦略ガイドも参考になります。

diffだけでは検出できない問題

OpenAPI diffは、2つの仕様ファイルの差分を検出します。しかし、実装が仕様どおりに動いているかは検証しません。

たとえば次のケースです。

  • 仕様では created_at が必須だが、実装は返さなくなった
  • 仕様では 200 を返すが、特定条件で実サービスは 500 を返す
  • 仕様では string だが、実レスポンスでは null が返る

この場合、baseとheadの仕様に差分がなければOpenAPI diffは成功します。しかし、実際のAPIは契約に違反しています。

このギャップを埋めるには、ライブAPIを契約に対してテストする必要があります。仕様からテストを生成し、実行中のサービスへリクエストし、実レスポンスがOpenAPI定義と一致するか検証します。これが契約テストです。

ApidogとApidog CLIで契約テストをCIに入れる

Apidog は、OpenAPI仕様をもとにAPI設計、モック、テストを同じワークスペースで扱えます。OpenAPI仕様をインポートまたは同期すると、仕様からテストシナリオを生成できます。

生成されたテストでは、実レスポンスに対して次のような検証を行えます。

  • ステータスコードが仕様どおりか
  • 必須フィールドが存在するか
  • フィールド型がスキーマと一致するか
  • レスポンス構造がOpenAPI定義と一致するか

仕様変更のたびに手書きテストを追従させるより、仕様を中心にテストを保守する方が運用しやすくなります。Apidogをダウンロードし、既存のOpenAPI仕様をインポートして試せます。OpenAPI仕様をGitで管理する運用については、GitによるOpenAPI仕様のバージョン管理も参考になります。

CIでは Apidog CLI を使います。

npm install -g apidog-cli

シナリオIDと環境IDを指定して実行します。

apidog run \
  --access-token $APIDOG_ACCESS_TOKEN \
  -t <scenarioId> \
  -e <environmentId> \
  -r junit,cli \
  --out-dir ./apidog-reports

各オプションの意味は次のとおりです。

  • --access-token: Apidogのアクセストークン。CIシークレットで管理する
  • -t: 実行するシナリオID
  • -e: 実行対象の環境ID
  • -r junit,cli: CLI出力とJUnit XMLレポートを生成する
  • --out-dir: レポート出力先

シナリオIDや環境IDは、Apidogのシナリオ画面にあるCI/CDタブからコピーできます。すべてのオプションは完全なCLIガイドまたは次のコマンドで確認できます。

apidog run --help

契約テストが失敗すると、apidog run はゼロ以外の終了コードで終了します。CIはその終了コードを読み取り、ジョブを失敗として扱います。つまり、OpenAPI diffと同じように、契約違反もマージ前に止められます。

完全なマージ前パイプライン例

OpenAPI diffと契約テストを組み合わせると、2種類の問題を検出できます。

  • OpenAPI diff: 仕様変更が既存クライアントを壊すかを検出する
  • 契約テスト: 実装が仕様どおりに動いているかを検証する

GitHub Actionsでは、別ジョブとして並列実行できます。

name: api-contract-gate

on:
  pull_request:
    paths:
      - "openapi.yaml"
      - "src/**"
      - "tests/**"

jobs:
  breaking-changes:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get base spec
        run: git show origin/${{ github.base_ref }}:openapi.yaml > base-openapi.yaml

      - name: Install oasdiff
        run: curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh

      - name: Check breaking changes
        run: oasdiff breaking base-openapi.yaml openapi.yaml --fail-on ERR

  contract-conformance:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install Apidog CLI
        run: npm install -g apidog-cli

      - name: Run contract tests
        run: |
          apidog run \
            --access-token "$APIDOG_ACCESS_TOKEN" \
            -t 605067 \
            -e 1629989 \
            -r junit,cli \
            --out-dir ./apidog-reports
        env:
          APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: apidog-report
          path: ./apidog-reports

breaking-changes ジョブは仕様ファイルだけを比較するため高速です。contract-conformance ジョブは実際のAPIにアクセスするため、通常はステージング環境に対して実行します。

if: always() を付けてレポートをアップロードしている点も重要です。テストが失敗したときこそ、JUnitレポートやCLI出力を確認する必要があるためです。

実際のCIでApidog CLIを組み込む方法は、Apidog CLI GitHub Actionsガイドと、より広範なCI/CDパイプラインウォークスルーで確認できます。

まとめ

OpenAPI仕様の構文チェックだけでは、既存クライアントとの互換性は保証できません。

マージ前に最低限入れるべきチェックは次の2つです。

  1. oasdiff breaking で仕様変更の破壊的変更を検出する
  2. Apidog CLIでライブAPIを契約に対してテストする

この2層にすると、次の両方をPR段階で止められます。

  • 仕様そのものが後方互換性を壊した
  • 実装がOpenAPI契約からずれた

APIの破壊的変更は、リリース後に見つけるほど高くつきます。CIのマージゲートに入れて、レビューではなくパイプラインで検出するのが実装しやすく、再現性のある対策です。