DEBI PRAHARADIKA
← Back to Blog Index
System Design2026-07-0816 min read

Membangun Multi-Tenant Architecture pada Aplikasi SaaS Feedback

Membangun Multi-Tenant Architecture pada Aplikasi SaaS Feedback menggunakan PHP dan MySQL

Membangun aplikasi SaaS (Software as a Service) B2B seperti platform manajemen feedback (contoh: Sightmill, SurveyMonkey, atau Zendesk) memiliki satu tantangan arsitektur fundamental yang tidak ditemukan di aplikasi B2C (Business to Consumer).

Di platform B2B, Anda tidak berurusan dengan pengguna individual yang independen. Anda berurusan dengan "Penyewa" (Tenant) atau Perusahaan.

Misalnya, perusahaan "Acme Corp" memiliki 50 karyawan yang masuk ke aplikasi Anda, dan perusahaan "Globex Inc" memiliki 200 karyawan. Karyawan Acme Corp mutlak tidak boleh melihat respons feedback dari pelanggan Globex Inc. Data mereka harus terisolasi sempurna. Bagaimana kita mendesain database MySQL dan struktur backend PHP kita untuk menjamin keamanan ini tanpa harus membuat server baru untuk setiap klien?

Mari kita bahas Multi-Tenant Architecture.


Daftar Isi

  1. Anatomi Aplikasi SaaS B2B
  2. Memilih Arsitektur: Pool vs Silo
  3. Implementasi Row-Level Security (MySQL & PHP)
  4. Implementasi Schema Isolation (MySQL & PHP)
  5. Desain UI: Workspace
  6. Kesimpulan & Keputusan Bisnis

1. Anatomi Aplikasi SaaS B2B

Dalam SaaS B2B, hierarki data umumnya seperti ini:

  • Tenant: Entitas perusahaan (misal: Acme Corp).
  • Users: Karyawan dari perusahaan tersebut (misal: Alex R. Thompson, Manajer HR Acme).
  • Resources: Data yang dimiliki perusahaan (misal: Kampanye NPS, Daftar Pelanggan, Respons Feedback).

Untuk melayani ratusan klien dari satu basis kode (codebase) yang sama, kita harus mendesain database yang aware akan siapa yang sedang melakukan permintaan data.


2. Memilih Arsitektur: Pool vs Silo

Secara garis besar, ada dua cara ekstrem untuk mendesain arsitektur multi-tenant di MySQL.

Komparasi Arsitektur Multi-Tenant Database

A. Pool Model (Row-Level Security)

Semua klien B2B berbagi satu database dan satu tabel yang sama. Isolasi dilakukan di tingkat baris (Row) dengan menyisipkan kolom tenant_id di setiap tabel.

  • Kelebihan: Sangat murah, mudah dirawat, hanya ada satu skema database yang perlu dimigrasi jika ada fitur baru.
  • Kelemahan: Risiko "kebocoran" tinggi. Jika developer lupa menambahkan WHERE tenant_id = ? di kode PHP, karyawan Acme bisa melihat data Globex!

B. Silo Model (Schema Isolation)

Setiap klien B2B mendapatkan Database (Schema) MySQL yang terpisah. Data Acme ada di database db_acme, data Globex ada di db_globex.

  • Kelebihan: Keamanan absolut. Tidak mungkin query menyasar ke data klien lain. Klien besar bisa di- backup secara individual.
  • Kelemahan: Operasional berat. Jika Anda memiliki 1.000 klien, Anda punya 1.000 database. Melakukan penambahan kolom (ALTER TABLE) harus diulang 1.000 kali.

3. Implementasi Row-Level Security (MySQL & PHP)

Mari kita asumsikan Anda memilih Pool Model karena startup Anda baru berjalan dan butuh efisiensi biaya.

Di MySQL, setiap tabel Anda (kecuali tabel master tenants) wajib memiliki kolom tenant_id:

CREATE TABLE feedback_responses (
    id INT AUTO_INCREMENT PRIMARY KEY,
    tenant_id INT NOT NULL,
    customer_email VARCHAR(255),
    nps_score INT,
    FOREIGN KEY (tenant_id) REFERENCES tenants(id)
);

Untuk mencegah developer lupa menambahkan WHERE tenant_id, kita harus menguncinya di level Middleware / Repository Base pada PHP.

<?php

class TenantContext {
    private static $currentTenantId = null;

    public static function setTenantId($id) {
        self::$currentTenantId = $id;
    }

    public static function getTenantId() {
        if (!self::$currentTenantId) {
            throw new Exception("Security Violation: Tenant Context tidak diatur!");
        }
        return self::$currentTenantId;
    }
}

// Base Repository yang diwarisi oleh semua model
abstract class BaseRepository {
    protected $db;
    protected $table;

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    // Fungsi get all yang otomatis disuntik tenant_id
    public function getAllForCurrentTenant() {
        $tenantId = TenantContext::getTenantId();
        
        $stmt = $this->db->prepare("SELECT * FROM {$this->table} WHERE tenant_id = ?");
        $stmt->execute([$tenantId]);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // Fungsi insert yang otomatis menyisipkan tenant_id
    public function insert($data) {
        $data['tenant_id'] = TenantContext::getTenantId();
        
        $columns = implode(', ', array_keys($data));
        $placeholders = implode(', ', array_fill(0, count($data), '?'));
        
        $stmt = $this->db->prepare("INSERT INTO {$this->table} ($columns) VALUES ($placeholders)");
        $stmt->execute(array_values($data));
    }
}

Dalam framework seperti Laravel, ini dikenal dengan konsep Global Scopes. Dengan base class di atas, programmer tidak akan pernah bisa mengakses data di luar tenant yang sedang login.


4. Implementasi Schema Isolation (MySQL & PHP)

Bagaimana jika klien Enterprise Anda menuntut privasi data ketat dan meminta Anda menggunakan pendekatan Silo Model?

Dalam model ini, tabel feedback_responses tidak lagi memiliki tenant_id karena seluruh database tersebut adalah milik satu klien. Tantangan di sisi PHP adalah merutekan koneksi database secara dinamis pada saat pengguna login atau berdasarkan subdomain (misal: acme.sightmill.com).

<?php

class DatabaseRouter {
    private $masterDb;

    public function __construct(PDO $masterDb) {
        // Master DB hanya berisi daftar tabel 'tenants' dan letak database mereka
        $this->masterDb = $masterDb;
    }

    public function connectToTenantDatabase($subdomain) {
        // 1. Cari info database klien di master
        $stmt = $this->masterDb->prepare("SELECT db_name, db_host FROM tenants WHERE subdomain = ?");
        $stmt->execute([$subdomain]);
        $tenant = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$tenant) {
            throw new Exception("Klien tidak ditemukan.");
        }

        // 2. Buat koneksi PDO baru HANYA ke database klien tersebut
        $tenantDb = new PDO(
            "mysql:host={$tenant['db_host']};dbname={$tenant['db_name']}", 
            'saas_app_user', 
            'secure_password'
        );
        
        $tenantDb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        
        return $tenantDb;
    }
}

// Penggunaan (biasanya diletakkan di Middleware Aplikasi)
$subdomain = $_SERVER['HTTP_HOST']; // ex: acme.domain.com -> acme
$router = new DatabaseRouter($masterConnection);

// Mulai dari titik ini ke bawah, aplikasi HANYA tahu database 'db_acme'
$tenantDb = $router->connectToTenantDatabase('acme'); 

$stmt = $tenantDb->query("SELECT * FROM feedback_responses"); // Sangat aman!

[!CATATAN] Migrasi struktur tabel (schema migration) pada arsitektur Silo sangat menantang. Anda memerlukan skrip otomatis (CI/CD) yang akan melakukan LOOP ke seluruh database klien dan menjalankan perintah ALTER TABLE satu per satu. Jika proses terputus di tengah jalan, beberapa klien akan memiliki versi aplikasi yang berbeda!


5. Desain UI

Banyak SaaS B2B modern mengizinkan satu email pengguna menjadi anggota di beberapa Tenant sekaligus (misalnya seorang konsultan freelance). Oleh karena itu, di sisi antarmuka, Anda membutuhkan apa yang disebut Workspace Switcher.

Setiap kali pengguna mengganti workspace pada antarmuka dropdown, aplikasi akan memperbarui TenantContext di backend, dan secara otomatis seluruh dashboard akan me- render ulang data yang berbeda.

SaaS Workspace Switcher UI

Pada desain di atas, ruang kerja ("Acme Corp") dikunci di menu atas. Semua aksi yang dilakukan pengguna di bawah layar tersebut (baik memfilter, mengekspor laporan, maupun mengirim feedback) secara implisit terikat pada tenant_id Acme Corp.


6. Kesimpulan & Keputusan Bisnis

Kapan Anda harus menggunakan masing-masing arsitektur?

  • Pilih Pool Model (Row-Level Security) jika: Target pasar Anda adalah perusahaan kecil-menengah (SME/UMKM). Anda butuh go-to-market yang sangat cepat, biaya cloud yang murah, dan pemeliharaan yang mudah.
  • Pilih Silo Model (Schema Isolation) jika: Anda menargetkan klien korporat kelas Enterprise, Rumah Sakit, atau Perbankan (HIPAA/GDPR compliance). Klien-klien ini biasanya mewajibkan audit privasi bahwa data mereka secara fisik atau logis terpisah dari klien lain, dan bersedia membayar lisensi premium untuk jaminan tersebut.

Pilihan ketiga? Hybrid. Anda bisa menggunakan Pool Model untuk 95% pelanggan reguler Anda, dan mengalokasikan server dengan Silo Model khusus untuk pelanggan VVIP yang membayar. Desainlah codebase PHP Anda sejak awal agar fleksibel menampung kedua model tersebut!