JSON Toolkit

Memversi Kontrak JSON: Keserasian, Penandaan Usang, dan Migrasi Data

Inti pati: Menambah medan tidak secara automatik selamat. Merangkumi sempadan keserasian, medan versi, tetingkap penandaan usang, migrasi tulis berganda, ujian kontrak dan metrik kenari.

Lanjutan~10 min bacaanDiterbitkan 2026-10-31Dikemas kini 2026-10-31
Kulit untuk memversi kontrak JSON, keserasian, dan migrasi data

Kesimpulan utama: jalankan evolusi versi sebagai kembang, isi semula, kemudian kontrak, dan simpan versi di dalam dokumen; sahkan pengguna bertoleransi terhadap medan dan nilai enum tidak dikenali sebelum menghantar apa-apa, dan lalui penandaan usang melalui beku, umum, perhati, dan buang dalam urutan itu.

Bahagian sukar memversi bukanlah mengubah kod; ia adalah anda tidak boleh tahu siapa yang masih membaca medan lama. Keserasian tidak pernah janji sehala — ia penjajaran berterusan antara penerbit dan pengguna tidak dikenali.

1. Sempadan keserasian: penambahan tidak secara automatik selamat

Buku teks memanggil medan pilihan baharu "serasi ke belakang", tetapi itu hanya berlaku apabila pengguna bertoleransi terhadap medan tidak dikenali. Menambah medan masih memecahkan klien dalam sekurang-kurangnya tiga situasi:

  • pengguna mengesahkan dengan skema yang menetapkan additionalProperties: false;
  • pengguna menelusuri objek dalam susunan tetap, atau membaca nilai mengikut kedudukan dan bukannya mengikut kunci;
  • medan baharu menggunakan semula nama lama tetapi mengubah maksudnya, contohnya meluaskan status daripada "status pesanan" kepada "status pemenuhan".

Penambahan selamat memerlukan tiga prasyarat: medan itu pilihan, maksudnya bebas, dan pengguna mengabaikan kunci tidak dikenali. Sahkan ketiga-tiganya sebelum anda berbincang nombor versi.

{
  "schemaVersion": 2,
  "orderId": "ord-8842",
  "status": "paid",
  "refundDeadline": "2026-11-30T00:00:00+08:00",
  "channel": "wechat"
}

Di sini refundDeadline dan channel ialah penambahan v2 pilihan, manakala orderId dan status mengekalkan semantik asal. schemaVersion disimpan bersama dokumen dan bukannya hidup hanya dalam pengepala HTTP.

2. Perubahan memecahkan yang tidak boleh anda hantar

Layan perubahan berikut sebagai garis merah yang memerlukan versi baharu atau peralihan tulis berganda. Mengemas kini dokumentasi tidak akan menyelamatkan anda:

Perubahan Mengapa ia memecahkan keserasian Alternatif yang boleh diterima
Buang medan Pengguna membaca undefined serta-merta Tanda usang, perhati, kemudian buang selepas tetingkap
Tukar jenis medan "1" menjadi 1; klien bertaip kuat gagal terus dan klien bertaip longgar sering gagal pengesahan Tambah nama medan baharu dan tulis berganda semasa peralihan
Tambah medan wajib Pemanggil lama tidak menghantarnya, jadi pengesahan selalu gagal Berikan nilai lalai, atau jadikan wajib hanya dalam versi baharu
Tukar set nilai enum Pernyataan switch pengguna tiada cabang sandaran Ajar klien menerima nilai tidak dikenali dahulu, kemudian luaskan enum
Tukar maksud medan Jenis kekal sah manakala tingkah laku berubah — kes paling sukar dinyahpepijat Layan sebagai medan baharu dan bekukan semantik lama
Tukar kedalaman bersarang Setiap laluan akses mesti berubah Kekalkan bentuk lama dan tambah medan rata atau parameter expand

Mengubah maksud ialah baris paling berbahaya: pengesahan skema kekal hijau, pemantauan melaporkan tiada kesalahan, dan hanya metrik perniagaan hanyut. Sebarang perubahan maksud harus dihantar sebagai medan baharu.

3. Di mana versi tinggal: dokumen, URL, jenis media

Tiga penempatan biasa melayan tujuan berbeza:

Lokasi Contoh Baik untuk Kekangan
Medan dokumen "schemaVersion": 2 Pangkalan data, baris gilir mesej, sandaran luar talian Lapisan pengangkutan tidak boleh menyalurkan berdasarkan itu
Laluan URL /v2/orders/8842 API awam Mengganding alamat sumber dengan versi dan menjadikan migrasi mahal
Jenis media application/vnd.acme.order.v2+json Perkhidmatan dalaman yang memerlukan rundingan kandungan Pemanggil mesti menetapkan Accept dengan betul

Pembahagian kerja praktikal: gunakan rundingan jenis media di wayar, dan sentiasa simpan medan versi dalam dokumen. Mesej lama dalam baris gilir tidak akan naik taraf kerana anda menukar URL, dan fail sandaran juga tidak. Hanya schemaVersion dalam dokumen memberikan asas kepada main semula, isi semula, dan audit.

GET /orders/ord-8842 HTTP/1.1
Host: api.bbdrug.com
Accept: application/vnd.acme.order.v2+json

Jika anda benar-benar memversi URL, sekurang-kurangnya pastikan laluan lama memulangkan bentuk lama sepanjang tetingkap penandaan usang dan bukannya secara senyap menunjuknya kepada pelaksanaan baharu. Untuk keputusan kontrak API yang lebih luas, lihat Perkara penting untuk mereka bentuk kontrak data JSON API.

4. Membuka tetingkap penandaan usang

Penandaan usang bukan satu pengumuman; ia tetingkap dengan permulaan, tempoh pemerhatian, dan penghujung. Irama yang boleh dilaksanakan:

  1. Beku: berhenti menulis medan lama, tetapi terus membacanya.
  2. Umum: tandakan tarikh pembuangan dalam dokumentasi, changelog, dan pengepala respons pada masa yang sama.
  3. Perhati: kira bacaan medan lama per pemanggil sehingga ia kekal sifar selama dua tempoh berturut-turut.
  4. Buang: hanya selepas tetingkap ditutup, bacaan sifar, dan ujian regresi lulus, padam medan dan kod keserasiannya.

Pengepala respons Sunset (RFC 8594) memberikan klien tarikh pembuangan yang boleh dibaca mesin. Deprecation masih draf IETF: boleh digunakan, tetapi jangan anggap setiap klien memahaminya. Gandingkannya dengan pengepala Link yang menunjuk panduan migrasi:

Deprecation: true
Sunset: Wed, 31 Mar 2027 23:59:59 GMT
Link: <https://bbdrug.com/docs/deprecations/order-status>; rel="deprecation"; type="text/html"

Panjang tetingkap ialah konvensyen pasukan. Lantai biasa ialah satu kitaran pelepasan atau 90 hari, mana yang lebih panjang. API awam patut mendapat lebih, perkhidmatan dalaman boleh kurang, tetapi tidak pernah kurang daripada masa yang diperlukan setiap pemanggil untuk naik taraf.

5. Mencari pengguna sebelum anda menandakan usang

Sebelum menandakan usang, anda mesti dapat menjawab "siapa masih membaca ini?" Jika tidak tetingkap itu ialah tipu diri sendiri. Susun bukti mengikut kebolehpercayaan:

  • Log gerbang: agregat akses medan lama mengikut appId, User-Agent, dan versi klien.
  • Jejak teragih: tandakan medan lama dalam lapisan pensirian supaya jejak menunjukkan nama perkhidmatan pemanggil secara langsung.
  • Daftar pengguna baris gilir: senarai kumpulan pengguna lebih boleh dipercayai daripada carian kod.
  • Carian repositori: rg hanya meliputi kod anda sendiri; pemanggil luaran masih memerlukan log.
rg --type ts --type js 'legacyStatus|statusCode' src/ --stats

Hits carian membuktikan "seseorang dalam repositori ini membacanya", bukan "tiada orang lain melakukannya". Eksport log gerbang hari demi hari dan sahkan bacaan medan lama kekal sifar sebelum membuang apa-apa.

6. Migrasi data: kembang, isi semula, kontrak

Parallel Change Martin Fowler menerangkan tiga langkah: kembang (medan lama dan baharu wujud bersama dan kedua-dua versi boleh dibaca), isi semula (pindahkan rekod sejarah ke bentuk baharu), dan kontrak (buang medan lama dan cabang keserasian). Jangan balikkan urutan, atau anda tiada laluan pembalikan.

// Migration skeleton: cursor by primary key, conditional writes for idempotency
async function migrateBatch(collection, cursor, batchSize = 500) {
  const records = await collection
    .find({ _id: { $gt: cursor } })
    .limit(batchSize)
    .toArray();

  if (records.length === 0) {
    return { nextCursor: cursor, done: true };
  }

  const operations = records
    .filter((record) => record.schemaVersion < 2)
    .map((record) => ({
      updateOne: {
        filter: { _id: record._id, schemaVersion: record.schemaVersion },
        update: {
          $set: {
            schemaVersion: 2,
            status: record.legacyStatus ?? record.status,
            refundDeadline: record.refundDeadline ?? null,
          },
        },
      },
    }));

  if (operations.length > 0) {
    await collection.bulkWrite(operations);
  }

  return { nextCursor: records.at(-1)._id, done: false };
}

Butiran yang mudah terlepas:

  • Idempotensi: sertakan nombor versi lama dalam filter, supaya larian semula tidak pernah menulis semula rekod yang sudah dimigrasi.
  • Pengelompokan dan titik semak: maju mengikut kursor _id, simpan titik semak, dan sambung semula daripadanya selepas kegagalan.
  • Latihan: jalankan skrip terhadap snapshot pengeluaran dan bandingkan kiraan rekod dan checksum sebelum dan selepas.
  • Pembalikan: kekalkan medan lama sehingga medan baharu lulus pengesahan; tukar laluan baca dengan bendera dan jangan sekali-kali membalikkan dengan menyunting schemaVersion sahaja.
  • Pengesahan: jalankan kekangan perniagaan selepas migrasi (jumlah bukan negatif, peralihan keadaan sah), bukan sekadar semakan penghuraian JSON.

Data konfigurasi mengikut proses yang sama; lihat Mengurus JSON untuk konfigurasi, fixture, dan data setempat untuk cara menyusun fail tersebut.

7. Ujian kontrak: letakkan keserasian dalam CI

Keserasian tidak boleh bergantung pada ingatan semakan kod; ia memerlukan ujian. Dua lapisan sudah cukup:

  1. Ujian bentuk: sahkan respons dengan JSON Schema, tetapi jangan tetapkan additionalProperties: false pada skema yang digunakan oleh pengguna.
  2. Ujian subset pengguna: ekstrak medan yang benar-benar diandalkan pengguna lama ke dalam sub-skema dan tuntut bahawa respons baharu masih memenuhinya.
// Contract test: new responses must still satisfy the old consumer subset
import Ajv from 'ajv';
import assert from 'node:assert/strict';
import { test } from 'node:test';

const ajv = new Ajv({ strict: false });
const validateLegacySubset = ajv.compile(legacyConsumerSubsetSchema);

test('a v2 response still satisfies the v1 consumer subset', () => {
  const response = buildOrderResponse({ schemaVersion: 2 });
  const { schemaVersion, ...legacyFields } = response;
  const isValid = validateLegacySubset(legacyFields);

  assert.equal(isValid, true, JSON.stringify(validateLegacySubset.errors));
});

Sub-skema hanya menyatakan medan dan jenis yang dibaca pengguna lama dan membiarkan setiap medan baharu melalui. Itu menangkap medan dibuang dan perubahan jenis tanpa amaran palsu pada penambahan. Simpan data kes dalam direktori fixture, tersusun dan boleh dibezakan; untuk konflik cantum dalam fail tersebut, lihat Konflik Git fail JSON: union menulis JSON rosak, cantum mengikut kunci sebaliknya.

8. Pelancaran kenari: skala mengikut dimensi, berhenti pada metrik

Masalah keserasian jarang muncul dalam ujian unit; ia muncul dalam kenari. Luaskan pendedahan daripada sempit kepada luas:

Dimensi Amalan Apa yang disahkan
Senarai putih penyewa Aktifkan laluan baca baharu untuk pelanggan dalaman atau mesra dahulu Perbezaan bentuk data dunia sebenar
Versi klien Aktifkan hanya untuk klien yang sudah naik taraf ke bentuk baharu Klien lama dan baharu berjalan bersebelahan
Peratusan trafik 5% → 25% → 100%, memerhati satu kitaran perniagaan penuh setiap langkah Panggilan ekor panjang dan tingkah laku hits cache

Perhatikan sekurang-kurangnya tiga metrik: kadar kesalahan API, kadar kegagalan pengesahan kontrak, dan nisbah bacaan antara medan lama dan baharu. Takrifkan syarat berhenti automatik, seperti 5xx melebihi 1% atau lonjakan semula mendadak dalam bacaan medan lama, dan balikkan bendera. Kekalkan tulis berganda semasa kenari supaya pembalikan hanya memerlukan penukaran laluan baca, bukan pembaikan data.

9. Senarai semak keserasian sebelum menghantar

  • [ ] Medan baharu pilihan, bebas semantik, dan tidak menggunakan semula nama lama.
  • [ ] Pengguna mengendalikan medan tidak dikenali dan nilai enum tidak dikenali dengan sandaran.
  • [ ] Medan versi disimpan bersama dokumen dan boleh dirunding di wayar.
  • [ ] Tetingkap penandaan usang mempunyai pengumuman, pengepala Sunset, dan pemerhatian.
  • [ ] Bacaan medan lama boleh dijejak kepada pemanggil tertentu melalui log atau jejak.
  • [ ] Skrip migrasi idempoten, berkelompok, boleh disambung semula, dan mempunyai laluan pembalikan.
  • [ ] Ujian kontrak meliputi subset pengguna warisan dan berjalan dalam CI.
  • [ ] Kenari mempunyai dimensi pendedahan eksplisit dan metrik berhenti.

FAQ

Adakah menambah medan pilihan sentiasa serasi ke belakang?

Tidak. Ia serasi hanya apabila pengguna mengabaikan medan tidak dikenali dan mengelakkan pengesahan skema ketat. Pengguna yang menggunakan additionalProperties: false atau membaca mengikut kedudukan masih akan gagal. Sahkan toleransi mereka dengan ujian kontrak sebelum menghantar.

Patutkah schemaVersion berada dalam URL atau badan respons?

Wayar boleh merundingkan versi melalui jenis media atau laluan URL, tetapi rekod tersimpan mesti membawa schemaVersion di dalam dokumen. Baris gilir, sandaran luar talian, dan audit tidak pernah melalui URL, jadi hanya versi dalam dokumen boleh menyokong main semula dan isi semula.

Bolehkah medan usang dipadam serta-merta?

Tidak. Berhenti menulisnya, umumkan tarikh pembuangan, dan kira bacaan per pemanggil. Padam hanya selepas tetingkap ditutup dan bacaan kekal sifar. Sebelum pemadaman, jalankan regresi penuh untuk mengesahkan tiada tugas cron tersembunyi atau laporan masih membacanya.

Bagaimana jika skrip migrasi gagal separuh jalan?

Reka bentuknya sebagai tugas idempoten: kemas kini bersyarat, kursor kunci utama, dan titik semak. Sambung semula daripada titik semak selepas kegagalan, dan larian berulang tidak boleh menulis dua kali. Untuk pembalikan, kekalkan medan lama dan snapshot; jangan sekadar membalikkan schemaVersion.

Bagaimana saya harus menjujukkan migrasi data?

Ikuti Parallel Change: kembang dahulu supaya medan lama dan baharu wujud bersama dan kedua-dua versi boleh dibaca, isi semula rekod sejarah ke bentuk baharu, kemudian kontrak dengan membuang medan lama dan cabang keserasian. Jangan balikkan urutan, atau anda kehilangan laluan pembalikan, dan jadikan skrip migrasi idempoten, berkelompok, dan boleh disambung semula.