Penerapan versi orkestrasi

Menyebarkan perubahan pada logika orkestrator adalah pertimbangan utama saat bekerja dengan sistem orkestrasi yang tahan lama. Jika orkestrasi terganggu dan kemudian dilanjutkan (misalnya, selama pembaruan host), runtime memutar ulang peristiwa orkestrasi, memastikan semua langkah sebelumnya berhasil dijalankan sebelum mengambil langkah berikutnya. Jika kode orkestrasi berubah di antara penyebaran, langkah-langkah yang diperlukan mungkin tidak lagi sama. Dalam hal ini, sistem melemparkan kesalahan nondeterminisme alih-alih memungkinkan orkestrasi berlanjut.

Penerapan versi orkestrasi mencegah masalah yang terkait dengan nondeterminisme, memungkinkan Anda untuk bekerja dengan mulus dengan orkestrasi baru (atau lama) sambil mempertahankan model eksekusi deterministik yang diperlukan orkestrasi tahan lama.

Fitur bawaan ini menyediakan isolasi versi otomatis dengan konfigurasi minimal. Ini agnostik terhadap backend, sehingga aplikasi apa pun yang menggunakan salah satu penyedia penyimpanan Durable Functions, termasuk Durable Task Scheduler, dapat menggunakannya.

Durable Task SDK mendukung dua gaya penerapan versi, yang dapat Anda gunakan secara terpisah atau bersama-sama:

Terminologi

Artikel ini menggunakan dua istilah terkait tetapi berbeda:

  • Fungsi orkestrator (atau hanya "orkestrator"): Kode fungsi yang menentukan logika alur kerja — templat atau cetak biru tentang bagaimana alur kerja harus dijalankan.
  • Instans orkestrasi (atau hanya "orkestrasi"): Eksekusi spesifik yang berjalan dari fungsi orkestrator, dengan status, ID instans, dan inputnya sendiri. Beberapa instans orkestrasi dapat berjalan bersamaan dari fungsi orkestrator yang sama.

Memahami perbedaan ini sangat penting untuk penerapan versi orkestrasi. Kode fungsi orkestrator berisi logika sadar versi, sementara instans orkestrasi secara permanen dikaitkan dengan versi tertentu saat dibuat.

Cara kerjanya

Versi orkestrasi beroperasi berdasarkan prinsip-prinsip inti ini:

  • Asosiasi versi: Saat instans orkestrasi dibuat, instans orkestrasi akan mendapatkan versi yang terkait secara permanen dengannya.
  • Eksekusi sadar versi: Kode orkestrator memeriksa nilai versi yang terkait dengan instans orkestrasi saat ini dan menyesuaikan jalannya eksekusi berdasarkan versi tersebut.
  • Kompatibilitas mundur: Pekerja yang menjalankan versi orkestrator yang lebih baru terus menjalankan instans orkestrasi yang dibuat oleh versi yang lebih lama.
  • Perlindungan versi ke depan: Waktu proses mencegah pekerja yang menggunakan versi orkestrator lama dari menjalankan orkestrasi yang dimulai oleh versi yang lebih baru.

Dalam praktiknya, Anda mengatur string versi default pada klien (atau di host.json untuk Durable Functions), dan kode orkestrator Anda menggunakan context.Version untuk bercabang antara logika lama dan baru.

Prasyarat

Sebelum Anda menggunakan penerapan versi orkestrasi, pastikan Anda memiliki versi paket yang diperlukan untuk bahasa pemrograman Anda.

Jika Anda menggunakan bahasa non-.NET (JavaScript, Python, PowerShell, atau Java) dengan bundel ekstensi, aplikasi fungsi Anda harus mereferensikan Bundel Ekstensi versi 4.30.0 atau yang lebih baru. Silakan konfigurasikan rentang extensionBundle dalam host.json sehingga versi minimum setidaknya 4.30.0. Contohnya:

{
    "version": "2.0",
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[4.30.0, 5.0.0)"
    }
}

Untuk detail tentang memilih dan memperbarui versi bundel, lihat dokumentasi konfigurasi bundel ekstensi.

Selain persyaratan bundel ekstensi untuk bahasa non-.NET, Anda juga perlu menggunakan versi minimum paket SDK khusus bahasa yang tercantum di bawah ini. Bundel ekstensi dan paket SDK diperlukan agar versi orkestrasi dapat berfungsi dengan benar.

Gunakan Microsoft.Azure.Functions.Worker.Extensions.DurableTask versi 1.14.0 atau yang lebih baru.

Mengatur versi default

Untuk menggunakan penerapan versi orkestrasi, pertama-tama konfigurasikan versi default untuk instans orkestrasi baru.

Tambahkan atau perbarui pengaturan defaultVersion dalam file host.json di proyek Azure Functions Anda:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>"
    }
  }
}

String versi dapat mengikuti format apa pun yang sesuai dengan strategi penerapan versi Anda:

  • Versi multi-bagian: "1.0.0", "2.1.0"
  • Penomoran sederhana: "1", "2"
  • Berbasis tanggal: "2025-01-01"
  • Format kustom: "v1.0-release"

Setelah Anda mengkonfigurasi defaultVersion, semua instans orkestrasi baru akan secara permanen dikaitkan dengan versi tersebut.

Atur versi default di penyusun klien saat mengonfigurasi aplikasi Anda.

Nota

Tersedia di SDK .NET (Microsoft.DurableTask.Client.AzureManaged) sejak v1.9.0.

builder.Services.AddDurableTaskClient(builder =>
{
    builder.UseDurableTaskScheduler(connectionString);
    builder.UseDefaultVersion("1.0.0");
});

Versinya adalah string sederhana dan menerima nilai apa pun. SDK mencoba mengonversinya ke System.Version .NET. Jika berhasil, pustaka tersebut digunakan untuk perbandingan. Jika tidak, digunakan perbandingan string sederhana.

Setelah Anda mengatur versi default pada klien, orkestrasi apa pun yang dimulai oleh klien ini secara permanen dikaitkan dengan versi tersebut. Versi ini juga tersedia dalam konteks orkestrasi, memungkinkan Anda untuk menggunakannya dalam pernyataan kondisional.

Aturan perbandingan versi

Ketika strategi Strict atau CurrentOrOlder dipilih (lihat Pencocokan versi), runtime membandingkan versi instans orkestrasi dengan nilai defaultVersion pekerja menggunakan aturan berikut:

  • Versi kosong atau null diperlakukan sama.
  • Versi kosong atau null dianggap lebih lama dari versi yang ditentukan.
  • Jika kedua versi bersifat numerik (misalnya, "1.0" dan "2.0"), keduanya dibandingkan sebagai nomor versi, jadi "2.0" lebih baru dari "1.0".
  • Jika tidak, perbandingan string yang tidak peka terhadap huruf besar/kecil dilakukan.

Contoh berikut menggambarkan cara kerja perbandingan versi:

Versi A Versi B Result
"1.0" "2.0" A lebih tua
null "1.0" A lebih tua
null null Setara
"v1-release" "v2-release" A lebih dahulu (urut abjad)

Saat strategi kecocokan Strict atau CurrentOrOlder dipilih (lihat Pencocokan versi), perbandingan versi bergantung pada bahasa:

  • .NET: SDK mencoba mengurai versi sebagai System.Version. Jika kedua penguraian berhasil, perbandingan menggunakan CompareTo. Jika tidak, SDK menggunakan perbandingan string.
  • Python: SDK menggunakan packaging.version untuk perbandingan penerapan versi semantik.
  • Java: SDK membandingkan versi sebagai string sederhana.

Logika orkestrator yang sadar versi

Untuk menerapkan logika sadar versi, gunakan parameter konteks untuk mengakses versi instans orkestrasi saat ini dan eksekusi cabang.

Penting

Saat menerapkan logika sadar versi, sangat penting untuk mempertahankan logika orkestrator yang tepat untuk versi yang lebih lama. Setiap perubahan pada urutan, urutan, atau tanda tangan panggilan aktivitas untuk versi yang ada dapat memutus pemutaran ulang deterministik dan menyebabkan orkestrasi dalam penerbangan gagal atau menghasilkan hasil yang salah. Jaga agar jalur kode versi lama tidak berubah setelah penyebaran.

[Function("MyOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    if (context.Version == "1.0")
    {
        // Original logic for version 1.0
        ...
    }
    else if (context.Version == "2.0")
    {
        // New logic for version 2.0
        ...
    }
    ...
}
[DurableTask]
class HelloCities : TaskOrchestrator<string, List<string>>
{
    private readonly string[] Cities = ["Seattle", "Amsterdam", "Hyderabad"];

    public override async Task<List<string>> RunAsync(
        TaskOrchestrationContext context, string input)
    {
        List<string> results = [];
        foreach (var city in Cities)
        {
            results.Add(await context.CallSayHelloAsync($"{city} v{context.Version}"));
            if (context.CompareVersionTo("2.0.0") >= 0)
            {
                results.Add(await context.CallSayGoodbyeAsync($"{city} v{context.Version}"));
            }
        }
        return results;
    }
}

Nota

Properti context.Version bersifat baca-saja dan mencerminkan versi yang terkait secara permanen dengan instans orkestrasi pada waktu pembuatan. Nilai ini tidak dapat dimodifikasi selama eksekusi orkestrasi.

Petunjuk / Saran

Jika Anda sudah memiliki orkestrasi yang sedang berjalan yang dibuat sebelum Anda menentukan versi default, context.Version mengembalikan null (atau yang setara dalam bahasa terkait) untuk instans tersebut. Susun logika orkestrator Anda untuk menangani warisan (versi null) dan orkestrasi versi baru.

Perilaku penyebaran

Inilah yang diharapkan saat Anda menyebarkan fungsi orkestrator yang diperbarui dengan logika versi baru:

  • Koeksistensi Pekerja: Pekerja dengan kode fungsi orkestrator baru mulai bekerja, sambil beberapa pekerja dengan kode lama berpotensi masih aktif.
  • Penetapan versi untuk instans baru: Semua orkestrasi dan sub-orkestrasi baru yang dibuat oleh pekerja baru mendapatkan versi dari defaultVersion yang ditetapkan kepada mereka.
  • Kesesuaian pekerja baru: Pekerja baru dapat memproses baik orkestrasi yang baru dibuat maupun yang sudah ada sebelumnya, karena logika percabangan yang mendukung versi memastikan kesesuaian ke belakang.
  • Pembatasan pekerja lama: Pekerja lama hanya dapat memproses orkestrasi dengan versi yang sama dengan atau lebih rendah dari versi yang ditentukan sendiri defaultVersion di host.json, karena mereka tidak diharapkan memiliki kode orkestrator yang kompatibel dengan versi yang lebih baru.

Nota

Penerapan versi orkestrasi tidak memengaruhi siklus hidup pekerja. Platform Azure Functions mengelola penyiapan dan penonaktifan pekerja berdasarkan aturan reguler tergantung pada model hosting.

Contoh: Mengganti aktivitas dalam urutan

Contoh ini menunjukkan cara mengganti aktivitas di tengah urutan dengan menggunakan penerapan versi orkestrasi.

Versi 1.0

Konfigurasi host.json:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "1.0"
    }
  }
}

Fungsi dari orkestrator:

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();

    await context.CallActivityAsync("ValidateOrder", orderId);
    await context.CallActivityAsync("ProcessPayment", orderId);
    await context.CallActivityAsync("ShipOrder", orderId);

    return "Order processed successfully";
}

Versi 2.0 dengan pemrosesan diskon

Konfigurasi host.json:

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "2.0"
    }
  }
}

Fungsi dari orkestrator:

[Function("ProcessOrderOrchestrator")]
public static async Task<string> ProcessOrder(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var orderId = context.GetInput<string>();

    await context.CallActivityAsync("ValidateOrder", orderId);

    if (TaskOrchestrationVersioningUtils.CompareVersions(context.Version, "1.0") <= 0)
    {
        // Preserve original logic for existing instances
        await context.CallActivityAsync("ProcessPayment", orderId);
    }
    else
    {
        // New logic with discount processing
        await context.CallActivityAsync("ApplyDiscount", orderId);
        await context.CallActivityAsync("ProcessPaymentWithDiscount", orderId);
    }

    await context.CallActivityAsync("ShipOrder", orderId);

    return "Order processed successfully";
}

Pencocokan versi

Strategi pencocokan versi menentukan instans orkestrasi mana yang diproses oleh pekerja berdasarkan kompatibilitas versi.

Tabel berikut ini menjelaskan strategi yang tersedia:

Strategi Deskripsi
Tidak Versi tidak diperhitungkan ketika memproses pekerjaan. Semua pekerjaan diproses terlepas dari versinya.
Ketat (mode ketat) Versi orkestrasi dan versi pekerja harus sama persis.
Saat Ini Atau Lebih Lama Versi orkestrasi harus sama dengan atau kurang dari versi pekerja. Ini adalah strategi default.

Konfigurasi

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionMatchStrategy": "CurrentOrOlder"
    }
  }
}
  • None (tidak disarankan): Menonaktifkan pemeriksaan versi. Setiap pekerja dapat memproses instans orkestrasi apa pun.
  • Strict: Memproses tugas hanya dari orkestrasi dengan versi yang sama persis dengan defaultVersion. Memerlukan koordinasi penyebaran yang cermat untuk menghindari orkestrasi yatim piatu.
  • CurrentOrOlder (default): Memproses tugas dari orkestrasi dengan versi kurang dari atau sama dengan defaultVersion. Memungkinkan kompatibilitas mundur sambil mencegah pekerja yang lebih lama memproses orkestrasi yang lebih baru.

Konfigurasikan strategi pencocokan melalui penyusun pekerja.

Nota

Tersedia di .NET SDK (Microsoft.DurableTask.Worker.AzureManaged) sejak v1.9.0.

builder.Services.AddDurableTaskWorker(builder =>
{
    builder.AddTasks(r => r.AddAllGeneratedTasks());
    builder.UseDurableTaskScheduler(connectionString);
    builder.UseVersioning(new DurableTaskWorkerOptions.VersioningOptions
    {
        Version = "1.0.0",
        DefaultVersion = "1.0.0",
        MatchStrategy = DurableTaskWorkerOptions.VersionMatchStrategy.Strict,
        FailureStrategy = DurableTaskWorkerOptions.VersionFailureStrategy.Reject,
    });
});

Penanganan ketidakcocokan versi

Strategi penanganan ketidakcocokan versi menentukan apa yang terjadi ketika versi instance orkestrasi tidak cocok dengan versi worker.

Tabel berikut ini menjelaskan strategi yang tersedia:

Strategi Deskripsi
Tolak Orkestrasi ditolak dan dikembalikan ke antrean kerja. Pekerja lain dapat mencobanya nanti. Strategi ini adalah default.
Fail Proses orkestrasi mengalami kegagalan dan dikeluarkan dari barisan pekerjaan.

Konfigurasi

{
  "extensions": {
    "durableTask": {
      "defaultVersion": "<version>",
      "versionFailureStrategy": "Reject"
    }
  }
}
  • Reject (default): Instans orkestrasi tetap dalam keadaan saat ini dan dapat dicoba kembali nanti ketika pekerja yang kompatibel tersedia. Strategi ini adalah opsi paling aman karena mempertahankan status orkestrasi.
  • Fail: Segera mengakhiri instans orkestrasi dengan status kegagalan. Opsi ini mungkin sesuai ketika ketidakcocokan versi menunjukkan masalah penyebaran yang serius.

Kapan menggunakan setiap strategi

Tolak: Gunakan strategi ini saat Anda ingin orkestrasi mencoba kembali nanti atau pada pekerja yang berbeda. Selama kegagalan sistem:

  1. Orkestrasi ditolak dan dikembalikan ke antrean kerja.
  2. Pekerja lain mengeluarkan orkestrasi dari antrean.
  3. Orkestrasi yang sudah dikeluarkan dari antrean dapat dijalankan pada pekerja yang berbeda atau yang sama lagi.

Proses ini berulang hingga pekerja yang dapat menangani orkestrasi tersedia. Strategi ini secara mulus menangani penerapan bertahap di mana pekerja diperbarui secara progresif.

Gagal: Gunakan strategi ini ketika tidak ada versi lain dari pekerja yang diharapkan untuk memproses orkestrasi. Orkestrasi gagal dan memasuki keadaan akhir.

Nota

Konfigurasikan strategi kegagalan melalui properti dalam opsi versioning, seperti yang ditunjukkan dalam sampel kode pencocokan versi .

Memulai orkestrasi dengan versi tertentu

Secara default, semua instans orkestrasi baru menggunakan saat ini defaultVersion yang ditentukan dalam konfigurasi Anda host.json . Namun, Anda mungkin memiliki skenario di mana Anda perlu membuat orkestrasi dengan versi tertentu yang berbeda dari default saat ini.

Kapan menggunakan versi tertentu

  • Migrasi bertahap: Terus buat orkestrasi dengan versi yang lebih lama bahkan setelah menyebarkan versi yang lebih baru.
  • Skenario pengujian: Menguji perilaku versi tertentu dalam produksi.
  • Situasi pembatalan: Kembalikan sementara untuk membuat instans dengan versi sebelumnya.
  • Alur kerja spesifik versi: Proses bisnis yang berbeda memerlukan pengaturan alur kerja versi yang berbeda.
[Function("HttpStart")]
public static async Task<HttpResponseData> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    var options = new StartOrchestrationOptions
    {
        Version = "1.0"
    };

    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        "ProcessOrderOrchestrator", orderId, options);
    // ...
}

Anda juga dapat memulai sub-orkestrasi dengan versi tertentu dari dalam fungsi orkestrator:

[Function("MainOrchestrator")]
public static async Task<string> RunMainOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var subOptions = new SubOrchestratorOptions
    {
        Version = "1.0"
    };

    var result = await context.CallSubOrchestratorAsync<string>(
        "ProcessPaymentOrchestrator", orderId, subOptions);
    // ...
}

Menghapus jalur kode warisan

Seiring waktu, Anda mungkin ingin menghapus jalur kode warisan dari fungsi orkestrator Anda untuk menyederhanakan pemeliharaan dan mengurangi utang teknis. Hapus kode dengan hati-hati untuk menghindari kerusakan instans orkestrasi yang ada.

Saat aman untuk menghapus kode warisan

  • Semua instance orkestrasi yang menggunakan versi lama telah selesai (berhasil, gagal, atau berakhir).
  • Tidak ada instans orkestrasi baru yang akan dibuat dengan versi lama.
  • Anda memastikan melalui pemantauan atau kueri bahwa tidak ada instans yang berjalan dengan versi lama.
  • Waktu yang cukup lama berlalu sejak terakhir kali versi lama diimplementasikan.

Untuk memeriksa instans yang sedang berjalan, gunakan API manajemen instans untuk mengkueri orkestrasi berdasarkan status dan memverifikasi bahwa tidak ada yang masih berlangsung dengan versi lama.

Untuk memeriksa instans yang sedang berjalan, gunakan DurableTaskClient untuk mencantumkan instans orkestrasi yang difilter berdasarkan status dan verifikasi bahwa tidak ada yang masih berlangsung dengan versi lama.

Peringatan

Menghapus jalur kode warisan saat instans orkestrasi masih menjalankan versi tersebut dapat menyebabkan kegagalan pemutaran ulang deterministik. Selalu periksa apakah tidak ada instans yang menggunakan versi warisan sebelum menghapus kode.

Praktik terbaik

Manajemen versi

  • Gunakan penerapan versi multi-bagian: Mengadopsi skema penerapan versi yang konsisten, seperti major.minor.patch.
  • Dokumentasikan perubahan mendasar: Jelaskan dengan jelas perubahan apa yang memerlukan versi baru.
  • Rencanakan siklus hidup versi: Tentukan kapan harus menghapus jalur kode warisan.

Organisasi kode

  • Logika versi terpisah: Gunakan metode percabangan yang jelas atau terpisah untuk versi yang berbeda.
  • Pertahankan determinisme: Jangan ubah logika versi yang ada setelah disebarkan. Jika perubahan benar-benar diperlukan, seperti perbaikan bug penting, pastikan mereka mempertahankan perilaku deterministik dan tidak mengubah urutan operasi.
  • Uji secara menyeluruh: Uji semua jalur versi, terutama selama transisi.

Pemantauan dan pengamatan

  • Informasi versi log: Sertakan versi dalam pencatatan log Anda untuk pemecahan masalah yang lebih mudah.
  • Memantau distribusi versi: Lacak versi mana yang berjalan secara aktif.
  • Siapkan pemberitahuan: Pantau masalah terkait versi apa pun.

Troubleshooting

Masalah umum

  • Masalah: Instans orkestrasi yang dibuat dengan versi 1.0 gagal setelah menyebarkan versi 2.0

    • Solusi: Pastikan jalur kode versi 1.0 di orkestrator Anda tetap sama persis. Setiap perubahan pada urutan eksekusi dapat memutus pemutaran ulang deterministik.
  • Masalah: Pekerja yang menjalankan versi orkestrator yang lebih lama tidak dapat menjalankan orkestrasi baru

    • Solusi: Perilaku ini diharapkan. Waktu proses mencegah pekerja yang lebih lama untuk menjalankan orkestrasi dengan versi yang lebih baru. Pastikan semua pekerja diperbarui ke versi terbaru dan bahwa pengaturan defaultVersion mereka di host.json juga diperbarui sesuai.
  • Masalah: Informasi versi tidak tersedia di orkestrator (context.Version atau context.getVersion() null, terlepas dari pengaturan defaultVersion)

    • Solusi: Periksa bagian Prasyarat untuk memastikan lingkungan Anda memenuhi semua persyaratan untuk penerapan versi orkestrasi.
  • Masalah: Orkestrasi versi terbaru membuat kemajuan yang sangat lambat atau macet

    • Solusi: Masalah ini dapat memiliki akar penyebab yang berbeda:
      1. Kekurangan pekerja baru: Pastikan cukup pekerja dengan versi yang sama atau lebih tinggi telah disebarkan defaultVersion dan aktif.
      2. Gangguan perutean orkestrasi dari pekerja yang lebih tua: Pekerja yang lebih tua dapat mengganggu mekanisme perutean orkestrasi, sehingga lebih sulit bagi pekerja baru untuk memproses orkestrasi. Gangguan ini dapat sangat terlihat dengan penyedia layanan penyimpanan tertentu (Azure Storage atau MSSQL). Biasanya, platform Azure Functions memastikan bahwa pekerja lama dibuang segera setelah penyebaran, sehingga penundaan apa pun biasanya tidak signifikan. Pertimbangkan untuk menggunakan Durable Task Scheduler untuk meningkatkan mekanisme perutean.

Troubleshooting

Masalah umum

  • Masalah: Orkestrasi macet atau tidak mengalami kemajuan setelah menyebarkan versi baru

    • Solusi: Verifikasi bahwa MatchStrategy dan FailureStrategy dikonfigurasi dengan benar dalam opsi versi pekerja Anda. Jika Anda menggunakan pencocokan Strict, hanya pekerja dengan versi yang persis sama yang dapat memproses orkestrasi tersebut. Beralih ke CurrentOrOlder jika Anda memerlukan kompatibilitas mundur.
  • Masalah: Orkestrasi segera gagal dengan kesalahan ketidakcocokan versi

    • Solusi: Periksa apakah Anda diatur ke FailureStrategy. Jika demikian, orkestrasi yang tidak cocok dengan versi pekerja yang tersedia akan memasuki status kegagalan terminal. Gunakan Reject sebagai gantinya untuk memungkinkan orkestrasi tetap berada dalam antrean hingga pekerja yang kompatibel tersedia.
  • Masalah: context.Version mengembalikan None/null/undefined untuk instans orkestrasi

    • Solusi: Orkestrasi yang dibuat sebelum Anda mengonfigurasi versi default tidak memiliki versi yang ditetapkan. Pastikan logika orkestrator Anda menangani null atau nilai versi kosong sebagai jalur kode warisan.

::: zone-end