Deteksi API Schema Drift di PHP
PHP Laravel Testing #php #laravel #api #testing

Deteksi API Schema Drift di PHP

A
Abd. Asis
6 min read
Bagikan:

Kamu pernah bangun pagi, cek notifikasi, dan menemukan puluhan transaksi gagal sejak tengah malam — padahal tidak ada deployment semalam? Log aplikasi bersih: tidak ada exception, tidak ada HTTP 5xx. Semua permintaan ke payment gateway kembali dengan status 200.

Inilah wajah asli dari API schema drift: perubahan struktur respons yang tidak menghasilkan error HTTP, tapi diam-diam merusak logika bisnis. Field yang kamu andalkan hilang, tipe data berubah dari string ke integer, atau nilai enum yang sebelumnya konsisten tiba-tiba bisa null. Provider menganggapnya “non-breaking” dari sisi mereka — tapi dari sisi aplikasimu, itu bencana.

Artikel ini membahas cara mendeteksi drift semacam ini sebelum merusak produksi, menggunakan php-sentinel — library monitoring pasif untuk PHP 8.3+ yang bekerja langsung di lapisan HTTP client.


Mengapa Monitoring Konvensional Tidak Cukup

Sebagian besar stack monitoring yang umum dipakai — Sentry, health check, atau bahkan test suite — tidak dirancang untuk menangkap perubahan struktural pada respons JSON dari API eksternal.

Sentry mencatat exception. Kalau kode kamu mengakses $response['invoice']['amount'] dan field amount hilang, exception baru muncul saat kode itu dieksekusi dengan data nyata. Artinya, drift sudah terjadi dan sudah merusak sesuatu sebelum kamu tahu.

Test suite dengan mock fixture bahkan lebih berbahaya. Fixture tidak berubah seiring waktu — sementara API provider bisa memperbarui kontrak mereka kapan saja tanpa pemberitahuan eksplisit. Kamu bisa punya 100% coverage tapi tetap kecolongan schema drift di produksi.

Mock fixture yang statis adalah false sense of security. Test hijau bukan berarti respons API di produksi masih punya struktur yang sama seperti enam bulan lalu.

Yang dibutuhkan adalah pemantauan yang berjalan langsung di produksi, mengamati respons nyata, dan memberi tahu kamu saat struktur berubah — bahkan sebelum kode kamu sempat menyentuh field yang hilang.


Mengenal php-sentinel

php-sentinel adalah middleware pasif untuk HTTP client. Ia tidak mengintervensi alur request-response; ia hanya mengamati.

Cara kerjanya sederhana:

  1. Saat pertama kali dipasang, sentinel mengumpulkan sampel dari respons sukses (default: 20 sampel pertama per endpoint).
  2. Dari sampel tersebut, ia menginferensi baseline schema secara probabilistik — field apa saja yang ada, tipenya, apakah nullable.
  3. Setelah baseline terbentuk, setiap respons baru dibandingkan terhadap baseline tersebut.
  4. Kalau ada deviasi, sentinel mengkategorikan perubahan dan mengirim notifikasi melalui PSR-14 event atau PSR-3 logger.

Kategorisasi perubahannya adalah:

KategoriContohDampak
BREAKINGField wajib hilang, tipe berubahKode bisa langsung error
ADDITIVEField baru ditambahkanUmumnya aman, tapi perlu diketahui
ADVISORYField jadi nullable, enum bertambah nilaiPotensi masalah tergantung logika

Instalasi di Laravel

Pasang library via Composer:

composer require malikad/php-sentinel

Untuk integrasi Laravel, sentinel menyediakan service provider yang otomatis terdaftar. Publish konfigurasinya:

php artisan vendor:publish --provider="Sentinel\Laravel\SentinelServiceProvider"

File konfigurasi config/sentinel.php akan dibuat. Ini adalah opsi yang paling sering disesuaikan:

// config/sentinel.php
return [
    'enabled' => env('SENTINEL_ENABLED', true),

    /*
     * Jumlah sampel yang dikumpulkan sebelum baseline terbentuk.
     * Nilai lebih tinggi = baseline lebih akurat, tapi butuh waktu lebih lama.
     */
    'sample_size' => env('SENTINEL_SAMPLE_SIZE', 20),

    /*
     * Hanya pantau endpoint yang cocok dengan pola ini.
     * Kosongkan untuk memantau semua endpoint.
     */
    'watch' => [
        'https://api.payment-gateway.com/v2/*',
        'https://api.notif-service.com/messages/*',
    ],

    'storage' => [
        'driver' => env('SENTINEL_STORAGE', 'redis'),
        'prefix' => 'sentinel:',
    ],
];

Memasang Middleware ke Guzzle Client

Sentinel bekerja sebagai middleware Guzzle. Cara termudah di Laravel adalah mendaftarkannya saat membuat HTTP client:

// app/Providers/AppServiceProvider.php

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Http;
use Sentinel\Laravel\Middleware\SentinelMiddleware;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Http::globalMiddleware(app(SentinelMiddleware::class));
    }
}

Dengan ini, semua HTTP request yang dibuat melalui Http:: facade di Laravel otomatis dipantau. Tidak perlu mengubah kode di setiap service yang memanggil API eksternal.

Kalau kamu menggunakan Guzzle secara langsung (misalnya di package atau service tertentu), tambahkan middleware secara eksplisit:

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Sentinel\Middleware\SentinelGuzzleMiddleware;

$stack = HandlerStack::create();
$stack->push(app(SentinelGuzzleMiddleware::class));

$client = new Client([
    'base_uri' => 'https://api.payment-gateway.com',
    'handler'  => $stack,
]);

Menangani Event Drift

Ketika sentinel mendeteksi perubahan schema, ia mendispatch event Sentinel\Events\SchemaDriftDetected. Buat listener untuk menangani notifikasi ini sesuai kebutuhan tim kamu.

Contoh listener yang mengirim alert ke Slack dan mencatat ke log:

// app/Listeners/HandleSchemaDrift.php

namespace App\Listeners;

use Sentinel\Events\SchemaDriftDetected;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;

class HandleSchemaDrift
{
    public function handle(SchemaDriftDetected $event): void
    {
        $payload = $event->drift;

        Log::critical('API schema drift detected', [
            'endpoint'  => $payload->endpoint,
            'severity'  => $payload->severity,
            'changes'   => $payload->changes,
            'detected_at' => now()->toIso8601String(),
        ]);

        if ($payload->severity === 'BREAKING') {
            $this->notifySlack($payload);
        }
    }

    private function notifySlack(object $payload): void
    {
        Http::post(config('services.slack.webhook_url'), [
            'text' => sprintf(
                ':rotating_light: *BREAKING schema drift* di `%s`%s%sField berubah: `%s`',
                $payload->endpoint,
                PHP_EOL,
                PHP_EOL,
                implode('`, `', array_keys($payload->changes))
            ),
        ]);
    }
}

Daftarkan listener di EventServiceProvider:

// app/Providers/EventServiceProvider.php

use Sentinel\Events\SchemaDriftDetected;
use App\Listeners\HandleSchemaDrift;

protected $listen = [
    SchemaDriftDetected::class => [
        HandleSchemaDrift::class,
    ],
];

Untuk drift dengan severity ADVISORY atau ADDITIVE, pertimbangkan mencatat ke log saja tanpa notifikasi real-time. Simpan noise di Slack hanya untuk yang benar-benar BREAKING.


Melihat Baseline yang Tersimpan

Setelah sentinel mengumpulkan cukup sampel, kamu bisa memeriksa baseline yang sudah diinferensi. Ini berguna untuk memvalidasi apakah baseline sudah mencerminkan struktur API yang sebenarnya:

php artisan sentinel:show-baseline "https://api.payment-gateway.com/v2/invoices"

Output-nya akan menampilkan JSON Schema yang diinferensi, lengkap dengan tipe tiap field dan apakah nullable. Kalau baseline terlihat tidak akurat (misalnya karena sampel awal berasal dari edge case), kamu bisa reset dan mulai ulang:

php artisan sentinel:reset "https://api.payment-gateway.com/v2/invoices"

Pola API Drift yang Paling Sering Merusak Produksi

Dari observasi di lapangan, ada beberapa pola yang paling sering menyebabkan kerusakan diam-diam:

Field removal adalah yang paling berbahaya. Provider menghapus field yang dianggap deprecated, padahal konsumen masih mengandalkannya. Sentinel langsung mengkategorikan ini sebagai BREAKING.

Type coercion juga umum terjadi: field "amount" yang sebelumnya selalu string "150000" tiba-tiba dikembalikan sebagai integer 150000. PHP biasanya toleran dengan ini, tapi bisa menyebabkan masalah di validasi ketat atau saat data diserialisasi ke format lain.

Nullability shift adalah yang paling sulit dideteksi secara manual. Field yang sebelumnya selalu terisi bisa mulai mengembalikan null untuk kasus-kasus tertentu — misalnya field "tax_code" yang null untuk transaksi internasional yang sebelumnya tidak pernah ada di dataset domestik.


Menggabungkan dengan Contract Testing

Sentinel adalah pertahanan di produksi. Tapi kamu juga perlu pertahanan sebelum produksi — dan di sinilah contract testing berperan.

Pact PHP memungkinkan kamu mendefinisikan ekspektasi terhadap API eksternal di sisi consumer, lalu memverifikasi bahwa provider memenuhi kontrak tersebut. Kombinasi keduanya memberikan dua lapis perlindungan:

  • Pact menangkap drift yang bisa kamu prediksi dan spesifikasikan sebelumnya.
  • Sentinel menangkap drift yang tidak kamu antisipasi — perubahan yang muncul tiba-tiba di produksi.

Kesimpulan

API schema drift adalah kategori bug yang susah dideteksi justru karena tidak terlihat seperti bug — tidak ada error, tidak ada alert, hanya data yang salah mengalir diam-diam ke logika bisnis. Sentinel mengisi celah ini dengan memantau struktur respons nyata di produksi dan memberi peringatan sebelum kode sempat menyentuh field yang sudah tidak ada. Untuk aplikasi yang bergantung pada integrasi API pihak ketiga — payment gateway, notification service, atau data provider — ini bukan luxury, ini kebutuhan dasar.

Referensi

  1. 1php-sentinel — Passive API Contract Monitoring for PHP 8.3+ (GitHub)
  2. 2pact-php — Consumer-Driven Contract Testing untuk PHP (GitHub)
  3. 3Contract Testing a Laravel API with OpenAPI — APIs You Won’t Hate
  4. 4Guzzle Handlers and Middleware — Dokumentasi Resmi Guzzle

Tentang Penulis

Abd. Asis

Abd. Asis

Software Developer dan Laravel Programmer dari Madura, Indonesia. Passionate tentang PHP, Laravel, dan teknologi web modern.

Komentar