# Multi-Tenant Boundaries & Logical Isolation

## Overview
BoonTrack menerapkan arsitektur **Logical Multi-Tenancy** pada layer database PostgreSQL dan FastAPI backend. Pendekatan ini memungkinkan ribuan merchant beroperasi secara aman di dalam infrastruktur bersama (*shared infrastructure*) dengan efisiensi sumber daya tinggi tanpa risiko kebocoran data (*cross-tenant data leakage*).

---

## 1. Strategi Logical Multi-Tenancy

Dibandingkan arsitektur *database-per-tenant* yang membutuhkan overhead manajemen koneksi masif, BoonTrack menggunakan skema bersama (*shared schema*) di mana setiap entitas transaksional dan katalog memiliki kolom diskriminator `tenant_id` (UUID).

```mermaid
flowchart TD
    subgraph Inbound_Request ["Request Masuk"]
        Req["HTTP Request (Host: toko-kopi.boontrack.com / Custom Domain / JWT)"]
    end

    subgraph FastAPI_Layer ["FastAPI Layer (Tenant Context Resolution)"]
        Middleware["TenantContextMiddleware"] -->|Resolve Host / JWT| Context["Inject tenant_id ke request.state"]
        Context --> DI["FastAPI Dependency: get_current_tenant()"]
    end

    subgraph Query_Boundary ["Scoped Query Enforcement"]
        DI --> Repo["Repository / Service Layer"]
        Repo -->|Mandatory WHERE tenant_id = :tenant_id| SQL["SQLAlchemy Scoped Query"]
    end

    subgraph Database_Layer ["PostgreSQL 16 Persistence"]
        SQL --> PG[("PostgreSQL Shared Schema (Indexed tenant_id)")]
    end
```

---

## 2. Resolusi Tenant Context di Middleware

Setiap request yang masuk melalui tahapan resolusi tenant sebelum mencapai *route handler*:

1. **Resolusi Domain Publik (Storefront / Checkout)**:
   - Membaca header `Host` atau `X-Forwarded-Host` yang diteruskan oleh Cloudflare.
   - Mencocokkan hostname dengan tabel `tenants` (baik domain default `.boontrack.com` maupun domain kustom merchant).
2. **Resolusi Terotentikasi (Merchant Dashboard / Admin API)**:
   - Memvalidasi token JWT Bearer.
   - Mengekstrak klaim `tenant_id` dari payload token yang telah ditandatangani secara kriptografis.
3. **Penyimpanan State Request**:
   - Nilai `tenant_id` yang terverifikasi disimpan di `request.state.tenant_id`.

---

## 3. Isolasi Data via Dependency Injection & Scoped Queries

Untuk mencegah kesalahan manusia (*developer oversight*) di mana developer lupa menambahkan filter `tenant_id`, FastAPI memanfaatkan pola **Dependency Injection** dan **Base Scoped Repository**.

### 3.1 FastAPI Dependency Provider
```python
from fastapi import Request, Depends, HTTPException, status
from uuid import UUID

async def get_current_tenant_id(request: Request) -> UUID:
    tenant_id = getattr(request.state, "tenant_id", None)
    if not tenant_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Tenant context could not be resolved"
        )
    return tenant_id
```

### 3.2 Implementasi Scoped Query di Service Layer
Setiap query pembacaan (*SELECT*), pembaruan (*UPDATE*), maupun penghapusan (*DELETE*) diwajibkan menyertakan predikat `tenant_id`:

```python
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

class OrderRepository:
    def __init__(self, db: AsyncSession, tenant_id: UUID):
        self.db = db
        self.tenant_id = tenant_id

    async def get_order_by_id(self, order_id: UUID):
        stmt = (
            select(Order)
            .where(Order.id == order_id)
            .where(Order.tenant_id == self.tenant_id)  # Strict Tenant Boundary
        )
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def create_order(self, order_data: OrderCreateSchema):
        # Memaksa tenant_id dari context, mengabaikan input payload dari luar
        order = Order(
            **order_data.model_dump(exclude={"tenant_id"}),
            tenant_id=self.tenant_id
        )
        self.db.add(order)
        await self.db.flush()
        return order
```

---

## 4. Pertahanan Berlapis (Defense-in-Depth): PostgreSQL Row-Level Security (RLS)

Sebagai lapisan pengaman tambahan (*defense-in-depth*) di tingkat mesin database PostgreSQL, tabel sensitif mengaktifkan **Row-Level Security (RLS)**:

```sql
-- Aktifkan RLS pada tabel transaksi
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Kebijakan isolasi berbasis session setting
CREATE POLICY tenant_isolation_policy ON orders
    FOR ALL
    USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);
```

Sebelum mengeksekusi transaksi pada sesi koneksi database, backend mengatur variabel sesi:
```python
await db_session.execute(
    text(f"SET LOCAL app.current_tenant_id = '{current_tenant_id}';")
)
```

Dengan arsitektur ini, meskipun terjadi kekeliruan sintaks query di level aplikasi, PostgreSQL engine secara otomatis menolak dan memfilter baris data yang bukan milik tenant aktif.

---

## 5. Atomic Merchant Provisioning & Transactional Registration

Untuk mencegah state data parsial (*zombie tenants*) dan inkonsistensi referal saat pendaftaran merchant baru, BoonTrack meniadakan pola compensating transaction di application layer dan menggantinya dengan **Single Transactional Provisioning Pipeline**:

1. **Atomic Execution**: Pembuatan record tenant, pemetaan afiliasi/vanity gateway, dan inisialisasi antrean event pendaftaran dieksekusi dalam satu blok transaksi database tunggal secara atomik.
2. **Credential Hardening**: Kredensial akses merchant diproteksi menggunakan standard hashing kriptografis sebelum persistensi; platform menolak secara mutlak penyimpanan kredensial mentah (*plaintext*).
3. **Idempotency Guard**: Request pendaftaran divalidasi dengan kunci idempotensi unik (`Idempotency-Key`) untuk mencegah pembuatan toko ganda akibat *network retry* dari sisi klien.
