Kursus
Dependency injection adalah pola desain di mana objek atau layanan (yang dimaksud dengan dependensi) disediakan ke sebuah kelas dari luar, alih-alih kelas tersebut membuatnya secara internal. Ini membuat komponen terlepas satu sama lain (decoupled).
Menggunakan dependency injection di Python memiliki banyak manfaat, seperti:
- Modularitas: Kode Anda dipecah menjadi bagian-bagian yang lebih kecil dan dapat digunakan kembali.
- Kemudahan pengujian: Kode lebih mudah diuji karena Anda bisa mengganti bagian nyata dengan mock.
- Kemudahan pemeliharaan: Anda dapat memperbarui atau mengubah bagian kode tanpa merusak bagian lain dari sistem.
Dalam tutorial ini, saya akan memperlihatkan cara menggunakan dependency injection di Python melalui contoh-contoh sederhana dan praktis.
Ringkasnya
-
Dependency injection adalah pola desain di mana sebuah kelas menerima dependensinya dari luar alih-alih membuatnya sendiri. Ini membuat kode Anda lebih modular.
-
Anda dapat menyuntikkan dependensi melalui konstruktor, metode setter, atau langsung ke metode, dan Python mendukung ini secara manual atau melalui framework seperti
dependency-injector,injector, danpunq. -
Framework web populer menanganinya secara berbeda: FastAPI memiliki dukungan bawaan melalui
Depends(). Flask dan Django membutuhkan pustaka sepertidependency-injector.
Memahami Dependency Injection di Python
Untuk memahami cara mengimplementasikan dependency injection, penting bagi Anda untuk menguasai prinsip-prinsip kunci yang mengaturnya.
Dependency injection didasarkan pada prinsip yang disebut Inversion of Control (IoC), yang berarti alih-alih sebuah kelas membuat dan mengelola dependensinya, kelas tersebut menerimanya dari sumber eksternal. Ini membantu fokus pada satu tugas dan membuat kode Anda bersih.
Menyuntikkan dependensi di Python mencakup cara umum berikut, meskipun ada cara lain:
- Constructor injection: Oper ke konstruktor kelas.
- Setter injection: Tetapkan dependensi menggunakan metode setter setelah objek dibuat.
- Method injection: Oper langsung ke metode yang membutuhkannya.
Mengapa Menggunakan Dependency Injection di Python?
Sebagai pengembang, memahami dan menerapkan dependency injection dapat memberikan banyak perbedaan pada desain kode Anda. Mari kita lihat beberapa manfaat dependency injection.
Modularitas, keterpakai-ulangan, dan fleksibilitas
Dependency injection memungkinkan Anda memecah kode menjadi komponen yang lebih kecil dan fokus, sehingga memberi fleksibilitas dan modularitas yang lebih besar.
Setiap bagian atau komponen menangani tugas tertentu dan bergantung pada dependensi eksternal, sehingga lebih mudah digunakan kembali di bagian lain dari aplikasi Anda atau orang lain
Misalnya, jika Anda memiliki kelas yang mengirim pesan. Alih-alih membangun logika email atau SMS langsung di layanan Anda, Anda dapat menyuntikkan kelas pengirim email atau SMS.
Sekarang, kelas yang mengirim pesan tidak peduli bagaimana pesan dikirim, melainkan hanya menggunakan pengirim yang disediakan. Ini berarti Anda dapat menggunakan pengirim pesan Anda dengan berbagai pengirim email atau SMS.
Kemudahan pemeliharaan dan pengujian
Saat menggunakan dependency injection, Anda dapat dengan mudah mengganti dependensi nyata dengan data mock saat pengujian. Ini membuat penulisan unit test lebih sederhana tanpa bergantung pada API atau database.
Sebagai latihan berpikir, misalkan Anda memiliki kelas yang menyimpan data ke database. Untuk tujuan pengujian, Anda dapat menyuntikkan database mock alih-alih yang nyata untuk menghindari keterikatan (coupling) pengujian Anda ke database produksi.
Dependency injection memungkinkan Anda memodifikasi bagian tertentu dari kode tanpa merusak seluruh sistem. Ini sangat berguna ketika Anda perlu mengganti penyedia pembayaran dalam aplikasi, misalnya dari Stripe ke PayPal, tanpa mengubah kode lainnya.
Inversion of control dan dependensi yang dapat ditukar
Gagasan inversion of control adalah ini: dependency injection akan mengalihkan tanggung jawab untuk membuat dan mengelola dependensi ke sumber eksternal.
Ini berarti Anda dapat dengan mudah menukar satu implementasi dengan implementasi lain; misalnya, jika Anda memiliki kelas ReportGenerator yang memformat dan mengekspor laporan.
Alih-alih kelas memutuskan format ekspor, Anda dapat menyuntikkan kelas exporter, seperti PDFExporter, ExcelExporter, atau CSVExporter, ke dalamnya.
Kelas ReportGenerator tidak peduli bagaimana ekspor terjadi, sehingga untuk mengubah format, Anda cukup menukar exporter yang Anda suntikkan, tanpa mengubah ReportGenerator sama sekali.
Mengimplementasikan Dependency Injection di Python
Sekarang Anda sudah memiliki gambaran jelas tentang apa itu dependency injection dan mengapa bermanfaat, mari lihat bagaimana Anda dapat mengimplementasikannya di Python. Kita mulai dari metode paling sederhana: dependency injection manual.
Dependency injection manual
Dalam dependency injection manual, Anda membuat dependensi sendiri dan memberikannya ke kelas atau fungsi yang membutuhkannya.
Metode ini bekerja baik untuk aplikasi kecil, tetapi saat aplikasi Anda berkembang, Anda perlu menggunakan framework dependency injection untuk menghindari kesalahan.
Berikut contoh kode tanpa dependency injection
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self):
self.email_service = EmailService() # Creates its own dependency
def notify(self, message):
self.email_service.send_email(message)
notifier = UserNotifier()
notifier.notify("Welcome!")
Pada contoh di atas, UserNotifier terikat erat (tightly coupled) ke EmailService. Anda tidak bisa dengan mudah menukar atau memock EmailService untuk pengujian.
Berikut versi lainnya, tetapi dengan dependency injection manual.
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service):
self.email_service = email_service # Dependency is injected
def notify(self, message):
self.email_service.send_email(message)
email_service = EmailService()
notifier = UserNotifier(email_service)
notifier.notify("Welcome!")
Versi di atas fleksibel dan memungkinkan Anda menyuntikkan berbagai layanan pengiriman pesan; Anda juga dapat mengganti EmailService dengan mock dalam pengujian.
Menggunakan framework dependency-injector
Saat aplikasi Anda tumbuh, manajemen dependensi menjadi kompleks dan berantakan.
Framework dependency-injector memberikan pendekatan yang terstruktur.
Arsitektur container-provider
Framework dependency-injector bekerja berdasarkan arsitektur container-provider.
- Container: Registri pusat untuk dependensi aplikasi Anda.
- Providers: Mendefinisikan bagaimana dependensi Anda dibuat.
- Configuration: Memungkinkan Anda mengubah pengaturan dan menyuntikkannya ke objek.
- Overriding: Memungkinkan Anda mengganti dependensi tanpa mengubah kode aplikasi, terutama saat pengujian.
from dependency_injector import containers, providers
# Services
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service):
self.email_service = email_service
def notify(self, message):
self.email_service.send_email(message)
# Container
class Container(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
user_notifier = providers.Factory(UserNotifier, email_service=email_service)
# Usage
container = Container()
notifier = container.user_notifier()
notifier.notify("Hello!")
Pada contoh di atas:
-
Containermendefinisikan bagaimana instance dibuat -
Singletonmemastikan hanya ada satu instanceEmailService -
FactorymembuatUserNotifierbaru denganEmailServiceyang disuntikkan secara otomatis.
Mekanisme wiring
Anda dapat menggunakan decorator seperti @inject untuk mengurangi kebutuhan penerusan dependensi secara manual.
Berikut versi lain dari contoh sebelumnya:
from dependency_injector import containers, providers
# Define services
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def notify(self, message):
self.email_service.send_email(message)
# Create container
class Container(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
user_notifier = providers.Factory(UserNotifier, email_service=email_service)
from dependency_injector.wiring import inject, Provide
@inject
def main(notifier: UserNotifier = Provide[Container.user_notifier]):
notifier.notify("Hello!")
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__]) # This wires the current module
main()
Dalam versi ini;
-
Tanpa menambahkan dependensi secara manual, decorator
@injectsecara otomatis menyuntikkan dependensi fungsi atau kelas. -
Kelas
Providememberi tahudependency-injectordependensi mana yang akan disuntikkan.
Pendekatan ini menghilangkan kebutuhan wiring dependensi secara manual dari container.
Ini bermanfaat pada aplikasi yang lebih besar di mana layanan mungkin bergantung pada banyak komponen (sehingga perlu diinisialisasi secara otomatis).
Menggunakan Dependency Injection di Framework Python Populer
Kebanyakan framework Python modern memudahkan untuk bekerja dengan dependency injection, sehingga Anda dapat menulis kode yang lebih bersih dan mudah diuji.
Pada bagian ini, Anda akan mempelajari bagaimana dependency injection diterapkan di Flask, Django, dan FastAPI.
Dependency injection di Flask
Flask tidak memiliki dukungan bawaan untuk dependency injection, tetapi Anda dapat mengimplementasikannya menggunakan pustaka dependency-injector melalui langkah-langkah berikut.
Langkah 1: Definisikan layanan Anda
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Langkah 2: Siapkan container dependency injection Anda
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Langkah 3: Buat aplikasi Flask dan suntikkan dependensi.
from flask import Flask, request
from dependency_injector.wiring import inject, Provide
container = Container()
app = Flask(__name__)
@app.route("/greet")
@inject
def greet(greeting_service: GreetingService = Provide[Container.greeting_service]):
name = request.args.get("name", "World")
return greeting_service.get_greeting(name)
container.wire(modules=[__name__])
if __name__ == "__main__":
app.run(debug=True)
Dari kode di atas:
-
Fungsi
name = request.args.get(“name”, “World”)mengekstraknamedari query string, dan menggunakan nilai default”World”jika tidak disediakan. -
return greeting_service.get_greeting(name)memanggil metode pada layanan yang disuntikkan dan mengembalikan pesan sapaan. -
container.wire(modules=[__name__])memberi tahu dependency injector untuk memindai modul ini untuk decorator@injectdan penandaProvide[...]guna menghubungkannya ke provider aktual.
Langkah 4: Uji aplikasi
Jalankan aplikasi di terminal Anda menggunakan perintah python <name_of_file>.py, dan kunjungi URL http://localhost:5000/greet?name=Jacob.
Anda akan melihat hasil berikut.

Dependency injection di Django
Sama seperti Flask, Django tidak menawarkan dependency injection bawaan. Namun, Anda dapat menggunakan class-based views, middleware, dan struktur app untuk mengintegrasikan dependency injection secara manual atau menggunakan pustaka dependency-injector.
Berikut prosedur langkah demi langkah untuk mengimplementasikan dependency injection di Django.
Langkah 1: Definisikan layanan Anda
Buat layanan sederhana yang berisi logika bisnis Anda di file baru services.py dalam folder app Anda.
# myapp/services.py
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Langkah 2: Siapkan container dependency injector
Di dalam direktori aplikasi Anda, buat container menggunakan pustaka dependency-injector di file baru containers.py.
# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Langkah 3: Buat View dengan dependensi yang disuntikkan
Gunakan decorator @inject di view Anda untuk menerima dependensi di views.py.
# myapp/views.py
from django.http import HttpResponse
from dependency_injector.wiring import inject, Provide
from .containers import Container
from .services import GreetingService
@inject
def greet_view(
request, greeting_service: GreetingService = Provide[Container.greeting_service]
):
name = request.GET.get("name", "World")
message = greeting_service.get_greeting(name)
return HttpResponse(message)
Langkah 4: Perbarui urls.py
Di folder proyek Anda, buka file urls.py dan hubungkan dependency injector Anda ke sistem routing URL Django.
from django.contrib import admin
from django.urls import path
from myapp.views import greet_view
urlpatterns = [
path("admin/", admin.site.urls),
path("greet/", greet_view),
]
Langkah 5: Wire container di apps.py
Buka apps.py dan wire container ke aplikasi.
# myapp/apps.py
from django.apps import AppConfig
from .containers import Container
class MyAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "myapp"
def ready(self):
container = Container()
container.wire(modules=["myapp.views"])
Lalu pergi ke konfigurasi app Anda di INSTALLED_APPS dan tambahkan aplikasi Anda.
# settings.py
INSTALLED_APPS = [
'myapp.apps.MyAppConfig',
...
]
Langkah 6: Uji View
Jalankan server Django Anda dan akses view melalui URL http://localhost:8000/greet/?name=Django.
Anda akan melihat hasil berikut.

Dependency Injection di FastAPI
FastAPI memiliki dukungan bawaan untuk dependency injection melalui penanda khusus Depends() yang menyuntikkan apa pun yang dikembalikan oleh sebuah fungsi.
Berikut panduan langkah demi langkah cara melakukan dependency injection di FastAPI.
Langkah 1: Definisikan layanan
class GreetingService:
def get_greeting(self, name: str) -> str:
return f"Hello, {name}!"
Langkah 2: Buat fungsi dependency
def get_greeting_service():
return GreetingService()
Langkah 3: Siapkan aplikasi FastAPI dan gunakan Dependency Injection
app = FastAPI()
@app.get("/greet")
def greet(
name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
return {"message": service.get_greeting(name)}
Langkah 4: Jalankan dan uji aplikasi
Mulai server Anda menggunakan perintah uvicorn main:app –reload dan kunjungi URL http://localhost:8000/greet?name=FastAPI. Anda akan melihat respons berikut.

Membandingkan Framework Dependency Injector Python
Ada berbagai framework dependency Python, masing-masing dengan kekuatan, sintaks, dan kasus penggunaan sendiri. Memilih framework yang tepat bergantung pada ukuran dan struktur proyek Anda.
Berikut perbandingan beberapa framework dependency injection Python yang umum.
|
Framework |
Terbaik Untuk |
Kekuatan |
Keterbatasan |
|
|
Aplikasi kompleks untuk penggunaan produksi |
Fitur lengkap, cepat, dan dapat dikonfigurasi |
Verbosity tinggi |
|
Sederhana dan cocok untuk aplikasi minimal |
Sintaks bersih |
Kurang canggih dibanding lainnya. |
|
|
Logika binding tingkat lanjut |
Auto-wiring |
Lebih lambat karena introspeksi |
|
|
Aplikasi sederhana |
Minimal dan cepat |
Fitur terbatas dibanding yang lain |
|
|
Aplikasi FastAPI |
Dukungan bawaan untuk async, serta mudah diuji |
Tidak dapat digunakan kembali di luar FastAPI |
|
|
Aplikasi Flask |
Integrasi mudah dengan aplikasi Flask |
Bergantung pada |
|
|
Proyek Django |
Menyediakan dependency injection untuk view dan middleware |
Bergantung pada |
Pola Implementasi Lanjutan
Seiring basis kode aplikasi Anda menjadi besar, dependency injection menjadi menantang untuk dipelihara.
Anda dapat menggunakan dependency injection tingkat lanjut untuk menangani tugas seperti resource ber-scope permintaan, pembersihan, dan pemisahan tanggung jawab.
Scoped dependencies
Scoped dependencies memungkinkan Anda mengendalikan berapa lama sebuah dependensi bertahan, misalnya saat Anda menginginkan objek baru per permintaan, objek bersama sepanjang masa pakai aplikasi, atau pembersihan resource otomatis seperti koneksi database.
Kebanyakan framework dependency injector mendukung scope seperti:
- Singleton: Satu instance untuk seluruh aplikasi.
- Factory: Instance baru setiap kali disuntikkan.
- Thread-local atau request: Satu instance per thread/permintaan.
- Resource: Dikelola dengan logika setup dan teardown.
Berikut contoh scoped dependencies dengan container bertingkat menggunakan dependency-injector.
from dependency_injector import containers, providers, resources
# A simple resource that needs setup and cleanup
class MyResource:
def __enter__(self):
print("Setting up the resource")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Cleaning up the resource")
# Create a container to manage the resource
class MyContainer(containers.DeclarativeContainer):
resource = providers.Resource(MyResource)
# Using the resource with a context manager
container = MyContainer()
with container.resource() as r:
print("Using the resource")
Pada contoh di atas, MyResource meniru kelas yang memerlukan setup dan teardown, seperti membuka/menutup berkas atau database.
container menyediakan resource menggunakan providers.Resource, dan saat Anda menggunakan with container.resource(), konteks akan masuk dan keluar dengan benar.
Pola ini memastikan resource, seperti handle file, koneksi database, atau sesi jaringan, dikelola dengan bersih.
Bayangkan endpoint API yang membutuhkan sesi database untuk permintaan saat ini atau logger dengan konteks spesifik permintaan.
Dengan scoped dependencies, framework membuat container ber-scope permintaan ketika permintaan dimulai dan menyelesaikan semua dependensi dari scope tersebut.
Saat permintaan berakhir, pembersihan yang diperlukan, seperti menutup sesi database, terjadi secara otomatis. Ini memastikan state tidak dibagikan antar permintaan dan menghindari kebocoran resource.
Penyuntikan asinkron
Untuk menangani tugas I/O-bound secara efisien, Anda memerlukan eksekusi asinkron. Sistem dependency injection dalam framework seperti FastAPI dan dependency-injector menawarkan penyuntikan asinkron menggunakan async/await.
Penyuntikan asinkron mengacu pada penyuntikan dependensi yang berupa fungsi async def atau pengelolaan async resources seperti koneksi database async, API eksternal, atau tugas latar belakang.
Alih-alih menyelesaikannya secara sinkron, sistem dependency injection menunggu hingga terselesaikan sebelum menyuntikkan.
Berikut contoh dependensi async di FastAPI:
from fastapi import FastAPI, Depends
app = FastAPI()
# Async dependency
async def get_user():
# Simulate async I/O
await some_async_db_query()
return {"name": "Philip Jones"}
@app.get("/profile")
async def read_profile(user: dict = Depends(get_user)):
return {"user": user}
FastAPI secara otomatis menangani penyelesaian async dari get_user dan menyuntikkan hasilnya.
dependency-injector juga mendukung manajemen siklus hidup async menggunakan provider AsyncResource.
from dependency_injector import containers, providers, resources
class AsyncDB:
async def connect(self):
print("Connected")
return self
async def disconnect(self):
print("Disconnected")
class AsyncDBResource(resources.AsyncResource):
async def init(self) -> AsyncDB:
db = AsyncDB()
return await db.connect()
async def shutdown(self, db: AsyncDB):
await db.disconnect()
class Container(containers.DeclarativeContainer):
db = providers.Resource(AsyncDBResource)
# Usage
async def main():
container = Container()
await container.init_resources()
db = await container.db() # Get the AsyncDB instance
# Use db here...
await container.shutdown_resources()
Pada kode di atas:
-
resources.AsyncResourcedigunakan untuk logika setup/teardown async. -
await container.init_resources()menginisialisasi semua resource dan memanggilinit. -
await container.shutdown_resources()membersihkan semua resource. -
await container.db()mendapatkan instance resource sebenarnya, yaituAsyncDB.
Menggunakan dependency injection async membantu menjaga performa aplikasi dengan menghindari I/O sinkron di rute asinkron, dan menghilangkan kebutuhan menunggu dependensi secara manual dalam logika Anda.
Implikasi Keamanan
Walaupun dependency injection meningkatkan modularitas dan kemudahan pengujian, ini dapat memperkenalkan potensi risiko keamanan jika tidak dikelola dengan hati-hati.
Salah satu risiko signifikan adalah dependency confusion, di mana paket berbahaya meniru yang sah. Misalnya, penyerang telah mengunggah paket ke PyPI dengan nama yang digunakan dalam sistem internal perusahaan untuk menipu aplikasi agar menggunakan versi berbahaya.
Agar tetap aman, pastikan Anda tidak pernah menyuntikkan komponen yang tidak tepercaya atau dikendalikan pengguna dan validasi sumber konfigurasi, seperti environment variables, sebelum disuntikkan.
Gunakan file requirements.txt atau poetry.lock untuk mengunci versi tepat, dan otomatisasi pemindaian keamanan saat terjadi perubahan dependensi. Selain itu, pastikan Anda secara rutin mengaudit dependensi untuk kerentanan.
Sentralisasi kontrol atas dependensi Anda juga penting, karena memastikan konsistensi di seluruh aplikasi dan mempermudah penerapan patch keamanan atau pembaruan versi. Ini membantu menghindari "shadow dependencies" yang disuntikkan dengan cara berbeda di seluruh basis kode.
Beberapa alat dapat mendeteksi risiko dan meningkatkan postur keamanan Anda:
- Snyk: Memindai kerentanan yang diketahui dalam paket Python dan menyarankan perbaikan.
- Xygeni: Menawarkan perlindungan untuk pipeline CI/CD.
- Safety: Memeriksa paket tidak aman dan kerentanan kode.
- Bandit: Penganalisis kode statis untuk Python yang berfokus pada isu keamanan.
Aplikasi Dunia Nyata
Dependency injection bukan hanya pola teoretis; ini digunakan di berbagai sistem produksi untuk mengelola kompleksitas, meningkatkan fleksibilitas, dan merapikan pengujian.
Konfigurasi layanan web
Dalam aplikasi web modern, dependency injection berperan penting dalam mengelola layanan standar, seperti autentikasi, logging, dan akses database.
Framework seperti FastAPI berperan penting dalam menyelesaikan rute dan dependensi.
Contoh di bawah ini mengilustrasikan implementasi autentikasi terpusat di FastAPI.
from fastapi import Depends, HTTPException
def get_current_user(token: str = Depends(oauth2_scheme)):
user = verify_token(token)
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/protected")
def protected_route(user: dict = Depends(get_current_user)):
return {"message": f"Hello {user['name']}"}
Pada kode di atas, get_current_user() disuntikkan sebagai dependensi rute, tetapi logika untuk memeriksa token bersifat terpusat dan dapat digunakan kembali.
Anda juga dapat melakukan hal yang sama untuk akses database, sehingga sesi database dikelola dan dibersihkan secara otomatis.
Berikut contoh bagaimana Anda dapat menggunakan dependency injection untuk memusatkan akses database.
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db_session() -> AsyncSession:
async with async_session() as session:
yield session
@app.get("/items")
async def read_items(db: AsyncSession = Depends(get_db_session)):
return await db.execute("SELECT * FROM items")
Pada kode di atas, get_db_session adalah penyedia dependensi. Saat rute read_items dipanggil, FastAPI secara otomatis menyuntikkan parameter db, memungkinkan rute mengakses database tanpa mengelola setup atau teardown sesi secara manual.
Pengembangan berbasis pengujian
Dependency injection memudahkan Anda mengubah dependensi yang diterima komponen, yang penting saat pengujian.
Anda dapat mengganti layanan nyata dengan mock saat pengujian, membuat kode Anda lebih modular dan lebih mudah diuji.
FastAPI dan dependency-injector mendukung override dependensi saat pengujian.
Berikut contoh di FastAPI.
from fastapi.testclient import TestClient
from main import app, get_db_session
def override_db():
return TestDBSession()
app.dependency_overrides[get_db_session] = override_db
client = TestClient(app)
response = client.get("/items")
assert response.status_code == 200
Pada contoh kode di atas, get_db_session dioverride dengan database mock selama pengujian; sehingga tidak perlu melakukan patch atau mengubah kode produksi.
Demikian juga di dependency-injector, Anda dapat menimpa database nyata dengan database mock untuk tujuan pengujian, seperti pada contoh di bawah.
from containers import Container
def test_service_behavior():
container = Container()
container.db.override(providers.Factory(TestDB))
service = container.service()
assert service.get_data() == "mocked"
Metode override() menggantikan dependensi nyata dengan mock, yang bersih, terkontrol, dan dapat dibalik.
Berikut beberapa strategi yang harus Anda gunakan untuk override dependensi:
- Constructor injection: Pastikan Anda meneruskan mock langsung ke konstruktor.
- Override framework: Gunakan alat override bawaan seperti
FastAPI.dependency_overridesdi FastAPI ataudependency_injector.override()didependency-injector. - Fixtures: Gunakan framework pengujian seperti
pytestuntuk membuat mock yang dapat digunakan kembali dan menyuntikkannya melalui fixtures.
Praktik Terbaik dan Anti-Pattern
Meskipun dependency injection dimaksudkan untuk membuat kode modular dan mudah diuji, ini dapat menimbulkan bug atau menciptakan kompleksitas dalam kode.
Praktik yang direkomendasikan
Berikut beberapa praktik terbaik yang harus Anda patuhi saat membuat dependensi:
- Program ke antarmuka, bukan implementasi: Definisikan dependensi menggunakan abstract base class atau antarmuka alih-alih implementasi kompleks. Ini membuat kode lebih mudah diuji dan mendorong penggunaan mock dalam unit test.
- Lebih utamakan komposisi daripada pewarisan: Alih-alih membangun hierarki kelas yang kompleks, komposisikan objek menggunakan dependensi yang disuntikkan untuk menjaga komponen tetap kecil dan fokus.
- Konfigurasi dependensi terpusat: Gunakan satu container atau modul untuk mendefinisikan dan mengelola dependensi, sehingga proses override dan pengujian menjadi lebih mudah.
- Hindari circular dependencies: Rancang grafik dependensi Anda dengan hati-hati untuk mencegah referensi melingkar dengan menggunakan fungsi factory atau provider untuk menunda instansiasi.
Kesalahan umum
Berikut beberapa kesalahan umum yang harus Anda hindari saat menggunakan dependency injection.
- Anti-pattern service locator: Hindari menggunakan container global untuk mengambil dependensi secara dinamis, karena ini dapat menyembunyikan dependensi dan membuat kode sulit dipahami serta diuji.
- Over-injection: Hindari menyuntikkan terlalu banyak layanan ke dalam satu kelas atau fungsi untuk mencegah konstruktor yang membengkak dan logika yang sulit diuji.
- Salah kelola scope dan kebocoran state: Pastikan Anda selalu menggunakan scope yang tepat untuk menghindari state dibagikan antar pengguna atau pengujian.
Pengujian Dependency Injection di Python
Dependency injection memudahkan penyuntikan objek mock ke dalam komponen Anda, sehingga menyederhanakan unit test. Ini memungkinkan Anda memisahkan komponen fundamental dari yang spesifik pengujian.
Dengan dependency injection, pengujian menjadi deterministik, tidak memiliki efek samping, dan menghasilkan eksekusi pengujian yang lebih cepat.
Berikut contoh yang menunjukkan bagaimana Anda dapat menggunakan dependency injection untuk menyuntikkan versi mock, stub, atau fake dari dependensi tersebut dalam pengujian.
class EmailService:
def send_email(self, to: str, message: str): ...
class Notifier:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def alert(self, user_email):
self.email_service.send_email(user_email, "Alert!")
Dalam pengujian, ganti EmailService dengan mock:
class TestEmailService:
def send_email(self, to, message):
self.sent_to = to
def test_notifier_sends_email():
test_email = TestEmailService()
notifier = Notifier(test_email)
notifier.alert("test@example.com")
assert test_email.sent_to == "test@example.com"
Pastikan sering menggunakan dependency injection dan hindari monkey patching demi kemudahan pemeliharaan dan untuk menghindari breaking changes saat nama internal berubah.
Kesimpulan
Meskipun Anda mungkin tidak membutuhkan dependency injection pada awalnya untuk proyek kecil, seiring proyek tumbuh dan skala membesar, Anda akan mendapat manfaat dari struktur dan modularitas yang ditawarkan dependency injection.
Ke depannya, masa depan dependency injection di Python menjanjikan, dan kita bisa mengharapkan kelanjutannya dengan sistem tipe yang lebih baik dan peningkatan manajemen siklus hidup async.
Menguasai dependency injection penting untuk menjadi pengembang Python yang mahir. Namun itu tidak berhenti di sana; masih banyak konsep lanjutan lainnya yang dapat meningkatkan keterampilan Python Anda.
Untuk membantu mempercepat perjalanan belajar Anda, berikut beberapa sumber daya kami yang sebaiknya Anda lihat.
Kursus:
- Developing Python Packages
- Introduction to PySpark
- Object-Oriented Programming in Python
- Intermediate Object-Oriented Programming in Python
Artikel Blog:
Instruktur sains data berpengalaman dan Biostatistikawan dengan keahlian dalam Python, R, dan machine learning.
FAQs
Apa itu Dependency Injection?
Dependency injection adalah pola desain yang memungkinkan Anda menulis kode lebih bersih dengan membuat dependensi dalam sebuah kelas yang dapat diberikan dari luar, sehingga kode Anda lebih mudah dikelola dan dimodifikasi.
Apa manfaat utama dari Dependency Injection?
Dependency injection membuat kode Anda menjadi modular, mudah diuji, dan mudah dipelihara.
Bagaimana cara mengimplementasikan dependency injection di Python?
Anda dapat mengimplementasikan dependency injection di Python secara manual atau menggunakan pustaka seperti dependency_injector.
Apa itu Asynchronous Injection?
Penyuntikan asinkron mengacu pada penyuntikan dependensi yang berupa fungsi async def atau pengelolaan async resources seperti koneksi database async, API eksternal, atau tugas latar belakang.
Apa risiko keamanan dari dependency injection, dan bagaimana menanganinya?
Salah satu risiko signifikan adalah dependency confusion, di mana paket berbahaya meniru yang sah. Untuk mencegah ini, Anda dapat menggunakan alat seperti Snyk, Xygeni, Safety, atau Bandit untuk memindai kode dan paket Anda dari kerentanan.

