Dependency Injection Pattern
Kodun büyüyor. Bir sınıf veritabanına bağlanıyor, başka bir sınıf e-posta gönderiyor, üçüncüsü ödeme işliyor. Her şey çalışıyor — ta ki test yazmaya çalışana kadar. OrderService test etmek istiyorsun ama gerçek veritabanına bağlanıyor, gerçek ödeme API'sına istek atıyor. Test ortamında bunlar yok. Ya da var ama her test çalışmasında gerçek para mı çekeceksin?
Dependency Injection (DI) bu sorunu çözer. Bağımlılıkları sınıfın içinde oluşturmak yerine dışarıdan verirsin. Test ederken sahte (mock) bağımlılık verirsin, production'da gerçeğini verirsin. Kod değişmez — sadece bağımlılık değişir.
1. DI Nedir, Neden Gerekli?
🔌 Analoji: Elektrik Prizi
Bir lamba düşün. Lamba içinde kablo direkt duvardaki bakır tele lehimlenmiş olsaydı ne olurdu? Lambayı başka odaya taşıyamazsın. Voltajı değiştiremezsin. Test etmek için tüm binanın elektrik sistemini çalıştırman lazım.
Gerçek dünyada lamba bir prize takılır. Priz bir arayüzdür (interface). Lambanın umurunda değil arkasında ne var — şehir şebekesi mi, jeneratör mü, güneş paneli mi. O sadece "bana 220V ver" der. İstediğin kaynağı prize tak, lamba çalışır.
DI tam olarak bu. Sınıfın bağımlılıklarını kendi içinde oluşturmak yerine, bir "priz" (constructor parametresi) üzerinden dışarıdan alır. Böylece istediğin implementasyonu takarsın.
DI Olmadan: Sıkı Bağlantı (Tight Coupling)
import smtplib
class OrderService:
def __init__(self):
# ❌ Bağımlılık sınıfın İÇİNDE oluşturuluyor
self.email_sender = smtplib.SMTP("smtp.gmail.com", 587)
self.db = PostgresDatabase("localhost", 5432)
def create_order(self, user_id, items):
order = self.db.insert("orders", user_id=user_id, items=items)
self.email_sender.send(f"Siparişiniz alındı: {order.id}")
return orderBu kodun sorunları:
Test edilemez: Test ederken gerçek SMTP sunucusuna bağlanır, gerçek veritabanı gerekir
Değiştirilemez: Gmail yerine AWS SES kullanmak istersen sınıfın kodunu değiştirmen lazım
Esnek değil: Farklı ortamlarda (dev, staging, prod) farklı ayar kullanmak zor
DI ile: Gevşek Bağlantı (Loose Coupling)
class OrderService:
def __init__(self, db, email_sender):
# ✅ Bağımlılıklar DIŞARIDAN veriliyor
self.db = db
self.email_sender = email_sender
def create_order(self, user_id, items):
order = self.db.insert("orders", user_id=user_id, items=items)
self.email_sender.send(f"Siparişiniz alındı: {order.id}")
return order
# Production'da
service = OrderService(
db=PostgresDatabase("prod-server", 5432),
email_sender=GmailSender("app@company.com")
)
# Test'te
service = OrderService(
db=InMemoryDatabase(),
email_sender=FakeEmailSender()
)Aynı OrderService sınıfı, hiç değişmeden hem production'da hem test'te çalışıyor. Tek fark: dışarıdan verilen bağımlılıklar.
2. Constructor Injection
DI'ın en yaygın ve önerilen biçimi constructor injection'dır. Bağımlılıklar __init__ parametresi olarak alınır.
Temel Pattern
class NotificationService:
"""Bildirim gönderme servisi."""
def __init__(self, sender, template_engine):
self.sender = sender
self.template_engine = template_engine
def notify_user(self, user, event):
message = self.template_engine.render(event)
self.sender.send(user.email, message)
class EmailSender:
def __init__(self, smtp_host, smtp_port):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
def send(self, to, message):
print(f"E-posta gönderildi: {to} — {message[:50]}...")
class JinjaTemplateEngine:
def render(self, event):
return f"Merhaba! {event} olayı gerçekleşti."
# Bağımlılıkları oluştur ve enjekte et
sender = EmailSender("smtp.gmail.com", 587)
engine = JinjaTemplateEngine()
service = NotificationService(sender=sender, template_engine=engine)Bu yapıda her sınıfın tek bir sorumluluğu var. NotificationService nasıl e-posta gönderileceğini bilmiyor — sadece sender.send() çağırıyor. E-posta yerine SMS göndermek istersen, aynı send() metoduna sahip bir SmsSender sınıfı yazıp verirsin.
Neden Constructor?
DI'ı setter (metod) veya property üzerinden de yapabilirsin ama constructor tercih edilir:
# ❌ Setter injection — nesne yarım oluşturulmuş olabilir
service = NotificationService()
service.set_sender(email_sender) # Bunu çağırmayı unutursan?
service.notify_user(user, event) # 💥 AttributeError
# ✅ Constructor injection — nesne tam oluşturulur veya hiç oluşturulmaz
service = NotificationService(sender=email_sender, template_engine=engine)
# Eksik parametre verirsen TypeError — hata anında yakalanırConstructor injection, sınıfın her zaman geçerli bir durumda olmasını garanti eder. Zorunlu bağımlılıklar constructor'da, opsiyonel olanlar varsayılan değerle veya setter ile verilebilir.
3. ABC ile Arayüz Tanımlama
Python'da Java'daki gibi zorunlu interface yok ama ABC (Abstract Base Class) ile benzer bir yapı kurabilirsin. Bu, bağımlılıkların hangi metotlara sahip olması gerektiğini açıkça belirtir.
ABC + Concrete Sınıflar
from abc import ABC, abstractmethod
# Arayüz (Interface) tanımla
class PaymentGateway(ABC):
"""Ödeme sistemi arayüzü."""
@abstractmethod
def charge(self, amount: float, currency: str) -> str:
"""Ödeme al, transaction ID döndür."""
pass
@abstractmethod
def refund(self, transaction_id: str) -> bool:
"""İade yap."""
pass
# Concrete implementasyon 1: Stripe
class StripeGateway(PaymentGateway):
def __init__(self, api_key: str):
self.api_key = api_key
def charge(self, amount: float, currency: str) -> str:
print(f"Stripe: {amount} {currency} çekildi")
return f"stripe_txn_{id(self)}"
def refund(self, transaction_id: str) -> bool:
print(f"Stripe: {transaction_id} iade edildi")
return True
# Concrete implementasyon 2: PayPal
class PayPalGateway(PaymentGateway):
def __init__(self, client_id: str, secret: str):
self.client_id = client_id
self.secret = secret
def charge(self, amount: float, currency: str) -> str:
print(f"PayPal: {amount} {currency} çekildi")
return f"paypal_txn_{id(self)}"
def refund(self, transaction_id: str) -> bool:
print(f"PayPal: {transaction_id} iade edildi")
return True
# Servis — hangi gateway olduğunu bilmiyor, umursamıyor
class CheckoutService:
def __init__(self, payment: PaymentGateway):
self.payment = payment
def process(self, cart_total: float):
txn_id = self.payment.charge(cart_total, "TRY")
print(f"İşlem tamamlandı: {txn_id}")
return txn_id
# Stripe ile
checkout = CheckoutService(payment=StripeGateway("sk_test_xxx"))
checkout.process(299.99)
# PayPal ile — aynı servis, farklı bağımlılık
checkout = CheckoutService(
payment=PayPalGateway("client_xxx", "secret_xxx")
)
checkout.process(299.99)ABC kullanmanın avantajı: birisi PaymentGateway'i implemente ederken refund metodunu yazmayı unutursa, sınıf oluşturulurken TypeError alır — runtime hatası beklemezsin.
class BrokenGateway(PaymentGateway):
def charge(self, amount, currency):
return "txn_123"
# refund() eksik!
# gateway = BrokenGateway()
# TypeError: Can't instantiate abstract class BrokenGateway
# with abstract method refund4. Protocol ile Duck-Typing DI
Python 3.8+ ile gelen Protocol, ABC'nin daha Pythonic alternatifidir. Kalıtım gerektirmez — sadece doğru metotlara sahip olmak yeterlidir. Bu duck typing'in resmileştirilmiş halidir: "Ördek gibi yürüyorsa ve ördek gibi ötüyorsa, ördektür."
Protocol Tanımlama
from typing import Protocol, runtime_checkable
@runtime_checkable
class EmailSender(Protocol):
"""E-posta gönderebilen herhangi bir nesne."""
def send(self, to: str, subject: str, body: str) -> bool:
...
@runtime_checkable
class DataStore(Protocol):
"""Veri saklayabilen herhangi bir nesne."""
def save(self, key: str, data: dict) -> None:
...
def load(self, key: str) -> dict | None:
...
# Bu sınıf EmailSender Protocol'ünü IMPLEMENTE ETMİYOR (extends yok)
# Ama doğru metoda sahip — yeterli!
class GmailClient:
def __init__(self, credentials: str):
self.credentials = credentials
def send(self, to: str, subject: str, body: str) -> bool:
print(f"Gmail → {to}: {subject}")
return True
class RedisStore:
def __init__(self, host: str):
self.host = host
self._data = {}
def save(self, key: str, data: dict) -> None:
self._data[key] = data
def load(self, key: str) -> dict | None:
return self._data.get(key)
# Protocol uyumu kontrolü
gmail = GmailClient("creds")
print(isinstance(gmail, EmailSender)) # True — metod imzası uyuyor
redis = RedisStore("localhost")
print(isinstance(redis, DataStore)) # True
# Servis — Protocol tipini bekliyor
class UserService:
def __init__(self, store: DataStore, notifier: EmailSender):
self.store = store
self.notifier = notifier
def register(self, email: str, name: str):
self.store.save(email, {"name": name, "active": True})
self.notifier.send(email, "Hoş geldiniz!", f"Merhaba {name}")
# Kalıtım yok, Protocol yok — sadece doğru metotlar
service = UserService(store=RedisStore("localhost"), notifier=GmailClient("creds"))
service.register("ali@example.com", "Ali")ABC vs Protocol: Ne Zaman Hangisi?
| Özellik | ABC | Protocol |
|---|---|---|
| Kalıtım gerekli mi? | Evet | Hayır |
| Mevcut sınıflarla uyumlu mu? | Hayır (extends lazım) | Evet (duck typing) |
| Hata yakalama zamanı | Sınıf oluşturulurken | Tip kontrolü sırasında (mypy) |
| Python versiyonu | 3.0+ | 3.8+ |
| Ne zaman kullan? | Kendi kodunda sıkı kontrol | Üçüncü parti kütüphanelerle çalışırken |
Protocol özellikle mevcut sınıfları değiştirmeden DI yapmak istediğinde güçlüdür. Bir kütüphanenin sınıfını extends edemezsin ama Protocol ile uyumluluğunu kontrol edebilirsin.
5. Basit DI Container Yazma
Küçük projelerde bağımlılıkları elle oluşturup geçirmek yeterli. Ama proje büyüdükçe, 10-15 servisin birbirine bağımlı olduğu bir noktaya gelirsin. Her birini elle oluşturmak ve doğru sırada bağlamak zahmetleşir. DI Container bu işi otomatikleştirir.
Minimal Container
from typing import Any, Callable
class Container:
"""Basit Dependency Injection container."""
def __init__(self):
self._factories: dict[str, Callable] = {}
self._singletons: dict[str, Any] = {}
self._singleton_flags: set[str] = set()
def register(self, name: str, factory: Callable, singleton: bool = False):
"""Bir bağımlılık fabrikası kaydet."""
self._factories[name] = factory
if singleton:
self._singleton_flags.add(name)
def resolve(self, name: str) -> Any:
"""Bir bağımlılığı çözümle (oluştur veya cache'den al)."""
if name in self._singletons:
return self._singletons[name]
if name not in self._factories:
raise KeyError(f"Kayıtlı olmayan bağımlılık: {name}")
instance = self._factories[name](self)
if name in self._singleton_flags:
self._singletons[name] = instance
return instance
# Kullanım
container = Container()
# Kayıt — factory fonksiyonları
container.register("config", lambda c: {
"db_host": "localhost",
"db_port": 5432,
"smtp_host": "smtp.gmail.com"
}, singleton=True)
container.register("database", lambda c: {
"host": c.resolve("config")["db_host"],
"port": c.resolve("config")["db_port"],
"connection": "active"
}, singleton=True)
container.register("email_sender", lambda c: {
"smtp": c.resolve("config")["smtp_host"],
"type": "gmail"
})
container.register("order_service", lambda c: {
"db": c.resolve("database"),
"email": c.resolve("email_sender"),
"name": "OrderService"
})
# Çözümleme — bağımlılıklar otomatik enjekte edilir
service = container.resolve("order_service")
print(service["db"]["host"]) # localhost
print(service["email"]["type"]) # gmail
# Singleton testi
db1 = container.resolve("database")
db2 = container.resolve("database")
print(db1 is db2) # True — aynı nesneContainer'ın temel fikri: "neye ihtiyacım var" ile "nasıl oluşturulur" arasındaki bağlantıyı merkezi bir yerde tanımlarsın. resolve() çağrıldığında container bağımlılık ağacını gezer ve her şeyi doğru sırada oluşturur.
⚠️ Dikkat: Kendi DI container'ını yazmak eğitici ama production'da battle-tested kütüphaneleri tercih et. Circular dependency tespiti, scope yönetimi, thread safety gibi konular hızla karmaşıklaşır.
6. dependency-injector Kütüphanesi
Python ekosisteminin en olgun DI kütüphanesi dependency-injector'dır. Tip güvenliği, scope yönetimi, konfigürasyon entegrasyonu ve daha fazlasını sağlar.
Kurulum ve Temel Kullanım
pip install dependency-injectorfrom dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
# Servisler
class DatabaseClient:
def __init__(self, host: str, port: int):
self.host = host
self.port = port
print(f"DB bağlantısı: {host}:{port}")
def query(self, sql: str):
return f"Sonuç: {sql}"
class CacheClient:
def __init__(self, host: str, ttl: int):
self.host = host
self.ttl = ttl
class UserRepository:
def __init__(self, db: DatabaseClient, cache: CacheClient):
self.db = db
self.cache = cache
def find_user(self, user_id: int):
return self.db.query(f"SELECT * FROM users WHERE id={user_id}")
class UserService:
def __init__(self, repo: UserRepository):
self.repo = repo
def get_user(self, user_id: int):
return self.repo.find_user(user_id)
# DI Container tanımla
class AppContainer(containers.DeclarativeContainer):
"""Uygulama DI container'ı."""
config = providers.Configuration()
# Singleton — uygulama boyunca tek instance
database = providers.Singleton(
DatabaseClient,
host=config.db.host,
port=config.db.port,
)
cache = providers.Singleton(
CacheClient,
host=config.cache.host,
ttl=config.cache.ttl,
)
# Factory — her çağrıda yeni instance
user_repo = providers.Factory(
UserRepository,
db=database,
cache=cache,
)
user_service = providers.Factory(
UserService,
repo=user_repo,
)
# Yapılandır ve kullan
container = AppContainer()
container.config.from_dict({
"db": {"host": "localhost", "port": 5432},
"cache": {"host": "localhost", "ttl": 300}
})
# Servis al
service = container.user_service()
result = service.get_user(42)
print(result)dependency-injector'ın gücü provider tiplerinde: Singleton (tek instance), Factory (her seferinde yeni), ThreadLocalSingleton (thread başına tek) gibi scope'lar kullanabilirsin. Konfigürasyon .env, YAML, JSON veya environment variable'lardan okunabilir.
dependency-injector'ın override() mekanizması ile test'te herhangi bir provider'ı mock ile değiştirebilirsin — container.database.override(providers.Object(MockDB())) gibi. Test bitince reset_override() ile temizlersin.
7. FastAPI'nin DI Sistemi (Depends)
FastAPI, Python ekosistemindeki en zarif DI sistemlerinden birine sahiptir. Depends() fonksiyonu ile bağımlılıkları route handler'lara enjekte edersin.
Temel Kullanım
from fastapi import FastAPI, Depends, HTTPException
app = FastAPI()
# Bağımlılık fonksiyonu
def get_database():
"""Veritabanı bağlantısı sağlar."""
db = {"connection": "active", "host": "localhost"}
try:
yield db # Generator — cleanup için
finally:
print("DB bağlantısı kapatıldı")
def get_current_user(token: str = None):
"""Mevcut kullanıcıyı döndürür."""
if not token:
raise HTTPException(status_code=401, detail="Token gerekli")
return {"id": 1, "name": "Ali", "token": token}
# Route'ta bağımlılık kullanımı
@app.get("/users/{user_id}")
def get_user(
user_id: int,
db=Depends(get_database),
current_user=Depends(get_current_user)
):
return {
"requested_id": user_id,
"db_status": db["connection"],
"requested_by": current_user["name"]
}FastAPI bağımlılık fonksiyonunu otomatik çağırır, sonucunu parametre olarak geçirir. yield kullanıldığında cleanup kodu (finally bloğu) response gönderildikten sonra çalışır — veritabanı bağlantılarını kapatmak için mükemmel.
Sınıf Bazlı Bağımlılıklar
from fastapi import FastAPI, Depends
app = FastAPI()
class UserRepository:
"""Kullanıcı veritabanı erişim katmanı."""
def __init__(self):
# Gerçek uygulamada DB connection alırsın
self.users = {
1: {"id": 1, "name": "Ali", "email": "ali@example.com"},
2: {"id": 2, "name": "Ayşe", "email": "ayse@example.com"},
}
def find_by_id(self, user_id: int):
return self.users.get(user_id)
def find_all(self):
return list(self.users.values())
class UserService:
"""Kullanıcı iş mantığı katmanı."""
def __init__(self, repo: UserRepository = Depends()):
self.repo = repo
def get_user(self, user_id: int):
user = self.repo.find_by_id(user_id)
if not user:
return None
return {**user, "display": f"{user['name']} <{user['email']}>"}
@app.get("/users/{user_id}")
def get_user(user_id: int, service: UserService = Depends()):
user = service.get_user(user_id)
if not user:
return {"error": "Kullanıcı bulunamadı"}
return user
@app.get("/users")
def list_users(repo: UserRepository = Depends()):
return repo.find_all()Depends() parantez içi boş olduğunda, FastAPI parametrenin tip annotation'ına bakar ve o sınıfı oluşturur. Sınıfın __init__'inde de Depends() varsa, zincirleme çözümleme yapar. Bu Java Spring'in DI'ına çok benzer ama çok daha az boilerplate ile.
Test'te Override
from fastapi.testclient import TestClient
class FakeUserRepository:
"""Test için sahte repository."""
def find_by_id(self, user_id: int):
return {"id": user_id, "name": "Test User", "email": "test@test.com"}
def find_all(self):
return [{"id": 1, "name": "Test User"}]
# Bağımlılığı override et
app.dependency_overrides[UserRepository] = FakeUserRepository
client = TestClient(app)
response = client.get("/users/99")
print(response.json())
# {"id": 99, "name": "Test User", "email": "test@test.com", "display": "..."}
# Testi bitirince temizle
app.dependency_overrides.clear()dependency_overrides dict'i ile herhangi bir bağımlılığı test sırasında değiştirebilirsin. Gerçek veritabanı, e-posta servisi veya ödeme sistemi yerine sahte implementasyonlar kullanırsın.
8. Test'te Mock Injection
DI'ın en büyük faydası test edilebilirlik. Bağımlılıkları dışarıdan aldığın için, test sırasında mock nesneler enjekte edebilirsin.
unittest.mock ile
from unittest.mock import Mock, MagicMock, patch
from abc import ABC, abstractmethod
# Arayüzler
class NotificationSender(ABC):
@abstractmethod
def send(self, to: str, message: str) -> bool: ...
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float) -> str: ...
# Servis
class OrderService:
def __init__(self, notifier: NotificationSender, payment: PaymentProcessor):
self.notifier = notifier
self.payment = payment
def place_order(self, user_email: str, amount: float):
# Ödeme al
txn_id = self.payment.charge(amount)
# Bildirim gönder
self.notifier.send(user_email, f"Ödeme alındı: {txn_id}")
return {"txn_id": txn_id, "status": "completed"}
# TEST
def test_place_order():
# Mock bağımlılıklar oluştur
mock_notifier = Mock(spec=NotificationSender)
mock_notifier.send.return_value = True
mock_payment = Mock(spec=PaymentProcessor)
mock_payment.charge.return_value = "TXN-12345"
# Servise mock'ları enjekte et
service = OrderService(
notifier=mock_notifier,
payment=mock_payment
)
# Test et
result = service.place_order("ali@example.com", 99.99)
# Doğrula
assert result["txn_id"] == "TXN-12345"
assert result["status"] == "completed"
# Mock'ların doğru çağrıldığını kontrol et
mock_payment.charge.assert_called_once_with(99.99)
mock_notifier.send.assert_called_once_with(
"ali@example.com", "Ödeme alındı: TXN-12345"
)
print("✅ Test geçti!")
test_place_order()Mock(spec=NotificationSender) ile mock nesne, gerçek arayüzün metotlarıyla sınırlandırılır. Yanlışlıkla mock_notifier.sned() (typo) çağırırsan AttributeError alırsın — bu hataları erken yakalar.
Hata senaryolarını test etmek de kolay: mock_payment.charge.side_effect = ConnectionError(...) ile ödeme servisinin çökmesini simüle edip, bildirimin gönderilmediğini mock_notifier.send.assert_not_called() ile doğrularsın. DI olmadan böyle bir testi yazmak çok zor olurdu.
9. Anti-Pattern: Service Locator vs DI
DI ile karıştırılan bir pattern var: Service Locator. Benzer görünür ama önemli farkları var.
Service Locator Nedir?
# ❌ Service Locator — anti-pattern
class ServiceLocator:
_services = {}
@classmethod
def register(cls, name, instance):
cls._services[name] = instance
@classmethod
def get(cls, name):
return cls._services[name]
# Kayıt
ServiceLocator.register("database", PostgresDB())
ServiceLocator.register("email", EmailSender())
class OrderService:
def create_order(self, user_id, items):
# ❌ Bağımlılık İÇERİDE çözümleniyor
db = ServiceLocator.get("database")
email = ServiceLocator.get("email")
order = db.insert("orders", user_id=user_id)
email.send(f"Sipariş: {order.id}")
return orderNeden Anti-Pattern?
# Problem 1: Gizli bağımlılıklar
service = OrderService() # Ne bağımlılığı var? Belli değil!
# Constructor'a baksan hiç parametre yok — ama ServiceLocator'a bağımlı
# Problem 2: Test zorluğu
# Test için global state'i değiştirmen lazım
ServiceLocator.register("database", MockDB()) # Global state kirletme
service.create_order(1, [])
ServiceLocator.register("database", RealDB()) # Geri al — ya unutursan?
# Problem 3: Sıra bağımlılığı
# ServiceLocator.register() çağrılmadan OrderService kullanırsan → KeyError
# Runtime hatası — compile time'da yakalanamazDI ile Doğru Yol
# ✅ Dependency Injection — bağımlılıklar açık ve net
class OrderService:
def __init__(self, db, email):
self.db = db
self.email = email
def create_order(self, user_id, items):
order = self.db.insert("orders", user_id=user_id)
self.email.send(f"Sipariş: {order.id}")
return order
# Bağımlılıklar açıkça görünüyor
service = OrderService(db=postgres_db, email=email_sender)
# Test — temiz, izole, global state yok
test_service = OrderService(db=MockDB(), email=MockEmail())| Özellik | Service Locator | Dependency Injection |
|---|---|---|
| Bağımlılıklar | Gizli (sınıf içinde) | Açık (constructor'da) |
| Test | Global state kirletir | İzole, temiz |
| Hata zamanı | Runtime (KeyError) | Compile/init time |
| Okunabilirlik | Düşük | Yüksek |
💡 İpucu: "Bağımlılıklarım constructor'da mı, yoksa sınıfın içinde mi oluşturuluyor?" sorusunu sor. İçeride oluşturuluyorsa, ya hard-coded bağımlılık ya da Service Locator kullanıyorsundur — ikisi de DI'a dönüştürülebilir.
10. DI Uygulama Rehberi
Ne Zaman DI Kullanmalı?
DI her yerde kullanılmaz. Bazı durumlarda gereksiz karmaşıklık ekler:
# ❌ Gereksiz DI — basit utility fonksiyonu
class MathService:
def __init__(self, adder, multiplier):
self.adder = adder
self.multiplier = multiplier
def calculate(self, a, b):
return self.adder.add(a, b)
# ✅ Basit tut
def calculate(a, b):
return a + b
# ✅ DI kullan — dış servislerle etkileşim, I/O, state
class OrderService:
def __init__(self, db, payment_gateway, notifier):
self.db = db
self.payment = payment_gateway
self.notifier = notifierDI kullan: Veritabanı, API, dosya sistemi, e-posta gibi dış bağımlılıklar olduğunda. DI kullanma: Saf hesaplama, string manipülasyonu gibi yan etkisi olmayan işlerde.
Composition Root
Tüm bağımlılıkların oluşturulup birbirine bağlandığı tek yer Composition Root olarak adlandırılır — genellikle uygulamanın giriş noktasıdır (main.py):
# main.py — Composition Root
def create_app():
"""Tüm bağımlılıkları oluştur ve bağla."""
# Infrastructure
db = PostgresDatabase("postgresql://localhost/mydb")
cache = RedisCache("localhost:6379")
email = SmtpEmailSender("smtp.gmail.com", 587)
# Repositories
user_repo = UserRepository(db=db, cache=cache)
order_repo = OrderRepository(db=db)
# Services
user_service = UserService(repo=user_repo)
order_service = OrderService(
repo=order_repo,
notifier=email,
user_service=user_service
)
return order_service
# Test için ayrı Composition Root
def create_test_app():
return OrderService(
repo=InMemoryOrderRepo(),
notifier=FakeEmailSender(),
user_service=FakeUserService()
)Geri kalan tüm kod bağımlılıklarını constructor'dan alır — hiçbir yerde new Database() veya import settings ile doğrudan bağımlılık oluşturmaz.
Özet
Dependency Injection, bağımlılıkları sınıf içinde oluşturmak yerine dışarıdan verme prensibidir — test edilebilirlik ve esneklik sağlar
Constructor injection en yaygın ve önerilen DI biçimidir; nesnenin her zaman geçerli durumda olmasını garanti eder
ABC sıkı arayüz kontrolü, Protocol duck-typing uyumu sağlar — projenin ihtiyacına göre seç
DI Container bağımlılık ağacını otomatik çözümler; küçük projelerde gereksiz, büyük projelerde vazgeçilmez
FastAPI'nin Depends() sistemi web uygulamalarında zarif ve Pythonic DI sunar
Service Locator anti-pattern'dir — bağımlılıkları gizler, test ve okunabilirliği zorlaştırır; DI'ı tercih et
AI Asistan
Sorularını yanıtlamaya hazır