Fungsi yang baik mengerjakan satu hal, lalu menyerahkan sisanya ke kolaborator: koneksi database, pengaturan aplikasi, client HTTP. Masalahnya, bila kolaborator dibuat sendiri di dalam fungsi, kode menjadi sulit diganti — dan otomatis sulit diuji. Dependency injection (DI) menyelesaikan masalah ini dengan satu prinsip: kolaborator disediakan dari luar, bukan dibuat di dalam. FastAPI membangun DI sebagai fitur inti lewat Depends, dan pola yang sama muncul di hampir semua framework modern.

Konsep: Suntikkan Kolaborator #

Bandingkan dua bentuk fungsi berikut:

python
def proses_pesanan_hardcode():
    koneksi = KoneksiDatabase("produk.db")  # kolaborator dikunci di dalam
    ...

def proses_pesanan(koneksi):  # kolaborator masuk dari luar
    ...

Pada bentuk pertama, proses_pesanan_hardcode() selalu terikat pada satu jenis koneksi. Pada bentuk kedua, siapa pun boleh menyuntikkan koneksi sungguhan saat produksi atau koneksi palsu saat pengujian — pola stub yang sudah kita pakai pada chapter Mocking dan Patching. Yang berubah hanyalah siapa yang membuat kolaborator: pemanggil, bukan fungsi itu sendiri.

Depends pada FastAPI #

FastAPI mengambil bentuk kedua itu dan mengotomatisasikannya. Fungsi dependensi ditulis seperti fungsi biasa; lalu rute menautkannya lewat Depends:

python
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient


def parameter_halaman(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}


class KoneksiDatabase:
    def __init__(self, status: str):
        self.status = status


def get_koneksi() -> KoneksiDatabase:
    # di aplikasi nyata: buka koneksi database sungguhan
    return KoneksiDatabase(status="tersambung")


app = FastAPI()


@app.get("/produk")
def daftar_produk(halaman: dict = Depends(parameter_halaman)):
    return {"halaman": halaman, "total": 42}


@app.get("/status-db")
def status_db(koneksi: KoneksiDatabase = Depends(get_koneksi)):
    return {"status": koneksi.status}

Mari kita telaah cara kerjanya:

  1. Baris 5-6 (def parameter_halaman(skip: int = 0, ...)): fungsi ini sekaligus mendeklarasikan query parameter skip dan limit — type hints dan nilai default dibaca FastAPI sama seperti pada rute.
  2. Baris 23 (halaman: dict = Depends(parameter_halaman)): sebelum daftar_produk dijalankan, FastAPI memanggil parameter_halaman lebih dulu; hasilnya dikirim sebagai argument halaman.
  3. Baris 28 (koneksi: KoneksiDatabase = Depends(get_koneksi)): pola get_koneksi() adalah konvensi FastAPI — satu fungsi kecil yang tahu cara membuat kolaborator.
  4. Baris 29 (koneksi.status): isi rute menerima objek siap pakai tanpa peduli bagaimana objek itu dibuat.

Mengganti Dependency saat Testing #

Kekuatan DI baru terasa saat pengujian. Dictionary app.dependency_overrides menggantikan fungsi dependensi dengan versi palsu tanpa menyentuh kode rute sama sekali:

python
client = TestClient(app)

print(client.get("/produk").json())
print(client.get("/produk?skip=10&limit=5").json())
print(client.get("/status-db").json())


def koneksi_uji() -> KoneksiDatabase:
    return KoneksiDatabase(status="stub-uji")


app.dependency_overrides[get_koneksi] = koneksi_uji
print("setelah override:", client.get("/status-db").json())
app.dependency_overrides.clear()
print("override dibuang:", client.get("/status-db").json())

Output yang Dihasilkan:

text
{'halaman': {'skip': 0, 'limit': 10}, 'total': 42}
{'halaman': {'skip': 10, 'limit': 5}, 'total': 42}
{'status': 'tersambung'}
setelah override: {'status': 'stub-uji'}
override dibuang: {'status': 'tersambung'}

Dua baris pertama memperlihatkan efek Depends(parameter_halaman): nilai default skip=0, limit=10 dipakai tanpa query string, dan query ?skip=10&limit=5 mengalir melewati fungsi dependensi menuju rute. Tiga baris terakhir memperlihatkan uji yang sesungguhnya: setelah get_koneksi digantikan koneksi_uji, response berubah dari tersambung menjadi stub-uji — tanpa satu pun koneksi database dibuka — lalu kembali normal begitu override dibersihkan.

DI di Luar FastAPI #

Dependency injection bukan monopoli FastAPI. Pada Django, settings.py berperan sebagai kolaborator global yang disuntikkan lewat django.setup() — kode kita membaca settings.DEBUG tanpa pernah membuat objek settings sendiri. Pada aplikasi Flask maupun proyek Python murni, bentuk paling sederhana DI adalah constructor injection: class menerima kolaborator lewat __init__, persis pola KoneksiDatabase(status=...) di atas — materi yang berakar di chapter Constructor dan init.

Intinya sama di mana pun: kode inti tidak membuat dunianya sendiri. Dunia disiapkan dari luar, sehingga mudah diganti — itulah seluruh teorinya.