Satu API yang sama dipakai oleh aplikasi web dan aplikasi mobile sering berakhir sebagai kompromi yang tidak menyenangkan: respons terlalu besar untuk mobile yang hemat kuota, terlalu ringkas untuk web yang butuh data lengkap, dan endpoint perlahan berubah menjadi kantong campuran kebutuhan semua orang. Pola Backend-for-Frontend (BFF) menjawab masalah ini dengan cara yang lugas: setiap jenis klien mendapat lapisan backend-nya sendiri. Pada chapter ini kita akan memetakan perbedaan kebutuhan antar klien, menggambar posisi BFF dalam arsitektur, membangun agregator tipis dengan fungsi murni, lalu membahas kapan pola ini justru berlebihan.

Satu API untuk Semua Klien #

Mari kita buat masalahnya konkret. Layanan katalog dan layanan inventori berdiri terpisah, dan sebuah aplikasi toko online mengakses keduanya dari dua klien berbeda. Kebutuhan kedua klien tersebut ternyata jauh dari seragam:

AspekAplikasi WebAplikasi Mobile
Ukuran responsLengkap: nama, deskripsi panjang, spesifikasiRingkas: hemat kuota dan baterai
AutentikasiCookie session di browserBearer token dari penyimpanan aman perangkat
Halaman detail produkButuh 3 data sekaligus: produk, stok, ulasanHanya produk + indikator stok
Toleransi latensiLebih longgarKetat: pengguna mobile cepat meninggalkan aplikasi
Kebutuhan khususBanner promosi, struktur menuDeep link, payload notifikasi

Jika satu API generis dipaksa melayani keduanya, muncul dua penyakit klasik. Pertama, respons membengkak: mobile menerima field yang tidak dipakainya karena web membutuhkannya. Kedua, endpoint berubah jadi bercabang: muncul parameter seperti ?client=mobile&fields=sku,nama yang memindahkan logika penyusunan respons ke tangan klien — hal yang persis ingin dihindari kontrak API yang jelas.

Pola Backend-for-Frontend #

BFF menempatkan satu lapisan API khusus di depan setiap jenis klien. Lapisan ini milik tim frontend masing-masing klien: tim web memiliki web BFF, tim mobile memiliki mobile BFF. Kedua BFF memanggil layanan-layanan di belakangnya lalu menyusun respons yang paling pas untuk kliennya:

Membaca diagram dari kiri ke kanan: browser hanya berbicara dengan Web BFF, aplikasi mobile hanya berbicara dengan Mobile BFF, dan keduanya bebas memanggil layanan Katalog maupun Inventori sesuai kebutuhan. Layanan belakang tetap bersih — tidak perlu tahu bahwa ada dua jenis klien — karena penyesuaian bentuk data terjadi sepenuhnya di lapisan BFF.

Kunci kepemilikan ada di soal kecepatan: tim frontend web tidak perlu menunggu antrean tim backend untuk menambahkan field agregasi atau mengubah bentuk respons halaman, karena perubahan itu terjadi di BFF milik mereka sendiri. Tim backend fokus pada layanan inti; tim frontend fokus pada pengalaman klien.

Agregasi Tipis dengan Fungsi Murni #

Tugas paling khas sebuah BFF adalah agregasi: menggabungkan data dari beberapa layanan menjadi satu respons yang siap dipakai layar. Karena pekerjaan ini adalah penyusunan data murni tanpa efek samping, ia sangat cocok ditulis sebagai fungsi murni yang mudah diuji. Mari kita buat agregator yang menyatukan data layanan Katalog dan layanan Inventori:

php
<?php

final class ProductAggregator
{
    /**
     * @param array<string, array{nama: string, deskripsi: string, hargaCents: int}> $productService
     * @param array<string, int> $inventoryService
     */
    public function __construct(
        private readonly array $productService,
        private readonly array $inventoryService,
    ) {}

    /** @return array{sku: string, nama: string, deskripsi: string, hargaCents: int, stok: int, tersedia: bool}|null */
    public function forWeb(string $sku): ?array
    {
        $produk = $this->productService[$sku] ?? null;
        if ($produk === null) {
            return null;
        }

        $stok = $this->inventoryService[$sku] ?? 0;
        return [
            'sku' => $sku,
            'nama' => $produk['nama'],
            'deskripsi' => $produk['deskripsi'],
            'hargaCents' => $produk['hargaCents'],
            'stok' => $stok,
            'tersedia' => $stok > 0,
        ];
    }

    /** @return array{sku: string, nama: string, hargaCents: int}|null */
    public function forMobile(string $sku): ?array
    {
        $web = $this->forWeb($sku);
        if ($web === null) {
            return null;
        }

        return [
            'sku' => $web['sku'],
            'nama' => $web['nama'],
            'hargaCents' => $web['hargaCents'],
        ];
    }
}

$katalog = [
    'PHP-101' => [
        'nama' => 'Kaos PHP',
        'deskripsi' => 'Katun combed 30s dengan logo biru-ungu.',
        'hargaCents' => 9500000,
    ],
];
$inventori = ['PHP-101' => 12];

$aggregator = new ProductAggregator($katalog, $inventori);

echo "WEB: " . json_encode($aggregator->forWeb('PHP-101'), JSON_UNESCAPED_UNICODE) . "\n";
echo "MOBILE: " . json_encode($aggregator->forMobile('PHP-101'), JSON_UNESCAPED_UNICODE) . "\n";

Output yang Dihasilkan:

text
WEB: {"sku":"PHP-101","nama":"Kaos PHP","deskripsi":"Katun combed 30s dengan logo biru-ungu.","hargaCents":9500000,"stok":12,"tersedia":true}
MOBILE: {"sku":"PHP-101","nama":"Kaos PHP","hargaCents":9500000}

Mari kita bedah agregator di atas:

  1. Constructor: menerima dua “layanan” sebagai array biasa. Dalam aplikasi nyata, keduanya adalah client HTTP ke layanan Katalog dan Inventori — tetapi karena fungsi penyusunannya hanya mengolah data, pengujian cukup memakai array statis tanpa jaringan sama sekali.
  2. forWeb(): menggabungkan produk dan stok, lalu menurunkan field tersedia agar template web tidak perlu menulis logika perbandingan. Ketika produk tidak dikenal, fungsi mengembalikan null agar handler merender halaman 404.
  3. forMobile(): memangkas respons web menjadi tiga field inti. Alih-alih parameter ?fields=... yang kacau, bentuk ringkas mobile menjadi kode eksplisit yang bisa direview dan diuji.

Kapan BFF Tidak Diperlukan #

Pola ini punya biaya: dua basis kode BFF yang harus dirawat, dua kontrak yang harus diuji, dan dua deployment yang harus diawasi. Jika jumlah klien Anda sedikit (hanya web), kebutuhan antar klien nyaris seragam, dan tim frontend kecil, satu API yang dirancang baik dengan disiplin API-first biasanya sudah lebih dari cukup. Tambahkan BFF ketika perbedaan kebutuhan klien benar-benar mulai melukai: respons membengkak, parameter ?client= bermunculan, atau tim frontend antre terlalu lama pada tim backend.

Sebagai alternatif, ada pendekatan GraphQL: satu endpoint yang menerima query deklaratif sehingga tiap klien memilih field yang dibutuhkannya sendiri. GraphQL menjawab masalah “bentuk respons per klien” dengan elegan, tetapi memindahkan kompleksitas ke sisi lain: caching, pembatasan query, dan batas kompleksitas menjadi tanggung jawab Anda. Untuk tim PHP, memilih antara beberapa BFF sederhana versus satu server GraphQL adalah keputusan arsitektur yang layak dicatat — kebiasaan yang akan kita bahas di chapter Architecture Decision Records.

Ringkasan #

Backend-for-Frontend memberi setiap jenis klien lapisan API miliknya sendiri: web dan mobile tidak lagi berebut satu kontrak, agregasi dan pemangkasan data menjadi kode eksplisit yang mudah diuji, dan tim frontend memegang kendali atas bentuk data kliennya. Harga yang dibayar adalah basis kode dan deployment tambahan, jadi pola ini paling masuk akal saat perbedaan kebutuhan klien sudah nyata. Bagaimana pun BFF menyusun datanya, layanan-layanan di belakangnya perlu saling berkomunikasi tanpa saling mengunci — dan di situlah event-driven architecture berperan, yang menjadi pembahasan chapter berikutnya.

Referensi #