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:
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:
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:
- Baris 5-6 (
def parameter_halaman(skip: int = 0, ...)): fungsi ini sekaligus mendeklarasikan query parameterskipdanlimit— type hints dan nilai default dibaca FastAPI sama seperti pada rute. - Baris 23 (
halaman: dict = Depends(parameter_halaman)): sebelumdaftar_produkdijalankan, FastAPI memanggilparameter_halamanlebih dulu; hasilnya dikirim sebagai argumenthalaman. - Baris 28 (
koneksi: KoneksiDatabase = Depends(get_koneksi)): polaget_koneksi()adalah konvensi FastAPI — satu fungsi kecil yang tahu cara membuat kolaborator. - 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:
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:
{'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.