Uurige FastAPI täiustatud sõltuvuste süstimise (DI) mustreid skaleeritavate, hooldatavate ja testitavate rakenduste loomiseks. Õppige, kuidas struktureerida robustne DI-konteiner.
FastAPI sõltuvuste süstimine: täiustatud DI-konteineri arhitektuur
FastAPI on oma intuitiivse disaini ja võimsate funktsioonidega muutunud Pythonis kaasaegsete veebiapide loomise lemmikuks. Üks selle peamisi tugevusi peitub sujuvas integratsioonis sõltuvuste süstimisega (DI), mis võimaldab arendajatel luua lahtiselt seotud, testitavaid ja hooldatavaid rakendusi. Kuigi FastAPI sisseehitatud DI-süsteem on suurepärane lihtsate kasutusjuhtumite jaoks, saavad keerukamad projektid sageli kasu struktureeritumast ja täiustatumast DI-konteineri arhitektuurist. See artikkel uurib erinevaid strateegiaid sellise arhitektuuri loomiseks, pakkudes praktilisi näiteid ja ülevaateid robustsete ja skaleeritavate rakenduste kujundamiseks.
Sõltuvuste süstimise (DI) ja kontrolli inversiooni (IoC) mõistmine
Enne täiustatud DI-konteineri arhitektuuridesse süvenemist selgitagem põhimõisteid:
- Sõltuvuste süstimine (DI): Kujundusmuster, kus sõltuvused pakutakse komponendile välisallikatest, mitte ei looda neid sisemiselt. See soodustab lahtist sidumist, muutes komponendid lihtsamini testitavateks ja korduvkasutatavateks.
- Kontrolli inversioon (IoC): Laiem põhimõte, kus objektide loomise ja haldamise kontroll pööratakse ümber – delegeeritakse raamistiku või konteineri ülesandeks. DI on IoC spetsiifiline tüüp.
FastAPI toetab sõltuvuste süstimist oma sõltuvuste süsteemi kaudu. Määratlete sõltuvused kutsutavate objektidena (funktsioonid, klassid jne) ja FastAPI lahendab need automaatselt ning süstib need teie lõpp-punkti funktsioonidesse või teistesse sõltuvustesse.
Näide (põhiline FastAPI DI):
from fastapi import FastAPI, Depends
app = FastAPI()
# Sõltuvus
def get_db():
db = {"items": []} # Simuleerib andmebaasiĂĽhendust
try:
yield db
finally:
# Sulge andmebaasiĂĽhendus (vajadusel)
pass
# Lõpp-punkt sõltuvuste süstimisega
@app.get("/items/")
async def read_items(db: dict = Depends(get_db)):
return db["items"]
Selles näites on get_db sõltuvus, mis pakub andmebaasiühendust. FastAPI kutsub get_db automaatselt ja süstib tulemuse (db sõnastiku) read_items lõpp-punkti funktsiooni.
Miks täiustatud DI-konteiner?
FastAPI sisseehitatud DI töötab hästi lihtsate projektide jaoks, kuid rakenduste keerukuse kasvades pakub keerukam DI-konteiner mitmeid eeliseid:
- Tsentraliseeritud sõltuvuste haldamine: Spetsiaalne konteiner pakub kõigi sõltuvuste jaoks ühte tõeallikat, muutes rakenduse sõltuvuste haldamise ja mõistmise lihtsamaks.
- Konfiguratsiooni- ja elutsüklite haldamine: Konteiner saab hallata sõltuvuste konfiguratsiooni ja elutsüklit, nagu üksikute eksemplaride loomine, ühenduste haldamine ja ressursside utiliseerimine.
- Testitavus: Täiustatud konteiner lihtsustab testimist, võimaldades teil sõltuvusi hõlpsasti asendada võltsitud objektide või testiväljavõtetega.
- Lahtine sidumine: Soodustab komponentide vahel suuremat lahtist sidumist, vähendades sõltuvusi ja parandades koodi hooldatavust.
- Laiendatavus: Laiendatav konteiner võimaldab teil lisada vajadusel kohandatud funktsioone ja integratsioone.
Strateegiad täiustatud DI-konteineri loomiseks
Täiustatud DI-konteineri loomiseks FastAPI-s on mitmeid lähenemisviise. Siin on mõned levinumad strateegiad:
1. Spetsiifilise DI-raamatukogu kasutamine (nt `injector`, `dependency_injector`)
Pythoni jaoks on saadaval mitmeid võimsaid DI-raamatukogusid, nagu injector ja dependency_injector. Need raamatukogud pakuvad põhjalikku komplekti funktsioone sõltuvuste haldamiseks, sealhulgas:
- Sidumine: Määratleb, kuidas sõltuvused lahendatakse ja süstitakse.
- Ulatused (Scopes): Kontrollib sõltuvuste elutsüklit (nt üksik eksemplar, transientne).
- Konfiguratsioon: Haldab sõltuvuste konfiguratsiooniseadeid.
- AOP (Aspect-Oriented Programming): Intercepteerib meetodikutseid risti-lõikavate murede jaoks.
Näide `dependency_injector`-iga
dependency_injector on populaarne valik DI-konteinerite loomiseks. Illustreerime selle kasutamist näitega:
from dependency_injector import containers, providers
from fastapi import FastAPI, Depends
# Määratle sõltuvused
class Database:
def __init__(self, connection_string: str):
self.connection_string = connection_string
# Initialiseeri andmebaasiĂĽhendus
print(f"Connecting to database: {self.connection_string}")
def get_items(self):
# Simuleeri andmebaasist ĂĽksuste hankimist
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
class UserRepository:
def __init__(self, database: Database):
self.database = database
def get_all_users(self):
# Simuleerib andmebaasipäringut kõigi kasutajate saamiseks
return [{"id": "user1", "name": "Alice"},{"id": "user2", "name": "Bob"}]
class Settings:
def __init__(self, database_url):
self.database_url = database_url
# Määratle konteiner
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
settings = providers.Singleton(Settings, database_url = config.database_url)
database = providers.Singleton(Database, connection_string=config.database_url)
user_repository = providers.Factory(UserRepository, database=database)
# Loo FastAPI rakendus
app = FastAPI()
# Konfigureeri konteiner (keskkonnamuutuja abil)
container = Container()
container.config.database_url.from_env("DATABASE_URL", default="sqlite:///:memory:")
container.wire([__name__]) # võimaldab sõltuvuste süstimist FastAPI lõpp-punktidesse
# Sõltuvus FastAPI jaoks
def get_user_repository(user_repository: UserRepository = Depends(container.user_repository.provided)) -> UserRepository:
return user_repository
# Sissesüstitud sõltuvust kasutav lõpp-punkt
@app.get("/users/")
async def read_users(user_repository: UserRepository = Depends(get_user_repository)):
return user_repository.get_all_users()
@app.on_event("startup")
async def startup_event():
# Konteineri initialisatsioon
container.init_resources()
Selgitus:
- Määratleme oma sõltuvused (
Database,UserRepository,Settings) tavaliste Pythoni klassidena. - Loome
Containerklassi, mis päribcontainers.DeclarativeContainer-st. See klass määratleb sõltuvused ja nende pakkujad (ntproviders.Singletonüksikute eksemplaride jaoks,providers.Factoryiga kord uue eksemplari loomiseks). container.wire([__name__])rida võimaldab sõltuvuste süstimist FastAPI lõpp-punktidesse.get_user_repositoryfunktsioon on FastAPI sõltuvus, mis kasutabcontainer.user_repository.provided, et saada konteinerist UserRepository eksemplar.read_userslõpp-punkti funktsioon süstibUserRepositorysõltuvuse.configvõimaldab teil sõltuvuste konfiguratsioone väliseks muuta. See võib seejärel pärineda keskkonnamuutujatest, konfiguratsioonifailidest jne.startup_eventkasutatakse konteineris hallatavate ressursside initialiseerimiseks.
2. Kohandatud DI-konteineri implementeerimine
DI-protsessi üle suurema kontrolli saamiseks saate implementeerida kohandatud DI-konteineri. See lähenemisviis nõuab rohkem pingutust, kuid võimaldab teil konteinerit oma spetsiifilistele vajadustele kohandada.
Põhiline kohandatud DI-konteineri näide:
from typing import Callable, Dict, Type, Any
from fastapi import FastAPI, Depends
class Container:
def __init__(self):
self.dependencies: Dict[Type[Any], Callable[..., Any]] = {}
self.instances: Dict[Type[Any], Any] = {}
def register(self, dependency_type: Type[Any], provider: Callable[..., Any]):
self.dependencies[dependency_type] = provider
def resolve(self, dependency_type: Type[Any]) -> Any:
if dependency_type in self.instances:
return self.instances[dependency_type]
if dependency_type not in self.dependencies:
raise Exception(f"Dependency {dependency_type} not registered.")
provider = self.dependencies[dependency_type]
instance = provider()
return instance
def singleton(self, dependency_type: Type[Any], provider: Callable[..., Any]):
self.register(dependency_type, provider)
self.instances[dependency_type] = provider()
# Näidis sõltuvused
class PaymentGateway:
def process_payment(self, amount: float) -> bool:
print(f"Processing payment of ${amount}")
return True # Simuleerib edukat makset
class NotificationService:
def send_notification(self, message: str):
print(f"Sending notification: {message}")
# Näidis kasutamine
container = Container()
container.singleton(PaymentGateway, PaymentGateway)
container.singleton(NotificationService, NotificationService)
app = FastAPI()
# FastAPI sõltuvus
def get_payment_gateway(payment_gateway: PaymentGateway = Depends(lambda: container.resolve(PaymentGateway))):
return payment_gateway
def get_notification_service(notification_service: NotificationService = Depends(lambda: container.resolve(NotificationService))):
return notification_service
@app.post("/purchase/")
async def purchase_item(payment_gateway: PaymentGateway = Depends(get_payment_gateway), notification_service: NotificationService = Depends(get_notification_service)):
if payment_gateway.process_payment(100.0):
notification_service.send_notification("Purchase successful!")
return {"message": "Purchase successful"}
else:
return {"message": "Purchase failed"}
Selgitus:
Containerklass haldab sõltuvuste ja nende pakkujate sõnastikku.registermeetod registreerib sõltuvuse koos selle pakkujaga.resolvemeetod lahendab sõltuvuse, kutsudes selle pakkujat.singletonmeetod registreerib sõltuvuse ja loob sellest ühe eksemplari.- FastAPI sõltuvused luuakse lambda funktsiooni abil, et lahendada sõltuvused konteinerist.
3. FastAPI `Depends` kasutamine tehasefunktsiooniga
Täieõigusliku DI-konteineri asemel saate saavutada teatud taseme sõltuvuste haldamist, kasutades FastAPI Depends koos tehasefunktsioonidega. See lähenemisviis on lihtsam kui kohandatud konteineri implementeerimine, kuid pakub siiski mõningaid eeliseid võrreldes otseste sõltuvuste loomisega lõpp-punkti funktsioonide sees.
from fastapi import FastAPI, Depends
from typing import Callable
# Määratle sõltuvused
class EmailService:
def __init__(self, smtp_server: str):
self.smtp_server = smtp_server
def send_email(self, recipient: str, subject: str, body: str):
print(f"Sending email to {recipient} via {self.smtp_server}: {subject} - {body}")
# Tehasefunktsioon EmailService'i jaoks
def create_email_service(smtp_server: str) -> EmailService:
return EmailService(smtp_server=smtp_server)
# FastAPI
app = FastAPI()
# FastAPI sõltuvus, kasutades tehasefunktsiooni ja Depends
def get_email_service(email_service: EmailService = Depends(lambda: create_email_service(smtp_server="smtp.example.com"))):
return email_service
@app.post("/send-email/")
async def send_email(recipient: str, subject: str, body: str, email_service: EmailService = Depends(get_email_service)):
email_service.send_email(recipient=recipient, subject=subject, body=body)
return {"message": "Email sent!"}
Selgitus:
- Määratleme tehasefunktsiooni (
create_email_service), mis loobEmailServicesõltuvuse eksemplare. get_email_servicesõltuvus kasutabDependsja lambda funktsiooni, et kutsuda tehasefunktsiooni ja pakkudaEmailServiceeksemplari.send_emaillõpp-punkti funktsioon süstibEmailServicesõltuvuse.
Täiustatud kaalutlused
1. Ulatused ja elutsĂĽklid
DI-konteinerid pakuvad sageli funktsioone sõltuvuste elutsükli haldamiseks. Levinud ulatused hõlmavad:
- Üksik eksemplar (Singleton): Sõltuvuse üks eksemplar luuakse ja seda kasutatakse kogu rakenduse eluajal korduvalt. See sobib olekuta või globaalse ulatusega sõltuvustele.
- Transientne (Transient): Uus sõltuvuse eksemplar luuakse iga kord, kui seda küsitakse. See sobib olekuga või üksteisest isoleerimist vajavate sõltuvustele.
- Päring (Request): Üks sõltuvuse eksemplar luuakse iga sissetuleva päringu jaoks. See sobib sõltuvustele, mis peavad säilitama olekut ühe päringu kontekstis.
dependency_injector raamatukogu pakub sisseehitatud tuge ulatustele. Kohandatud konteinerite jaoks peate ise ulatusete haldamise loogika implementeerima.
2. Konfiguratsioon
Sõltuvused nõuavad sageli konfiguratsiooniseadeid, nagu andmebaasi ühenduskeeled, API võtmed ja funktsioonide lipud. DI-konteinerid saavad aidata neid seadeid hallata, pakkudes tsentraliseeritud viisi konfiguratsiooniväärtuste juurde pääsemiseks ja süstimiseks.
dependency_injector näites võimaldab config pakkuja konfiguratsiooni keskkonnamuutujatest. Kohandatud konteinerite jaoks saate konfiguratsiooni laadida failidest või keskkonnamuutujatest ja salvestada need konteinerisse.
3. Testimine
DI üks peamisi eeliseid on paranenud testitavus. DI-konteineri abil saate testimise ajal hõlpsasti asendada reaalsed sõltuvused võltsitud objektide või testiväljavõtetega.
Näide (Testimine `dependency_injector`-iga):
import pytest
from unittest.mock import MagicMock
from dependency_injector import containers, providers
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
# Määratle sõltuvused (nagu varem)
class Database:
def __init__(self, connection_string: str):
self.connection_string = connection_string
def get_items(self):
return [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
class UserRepository:
def __init__(self, database: Database):
self.database = database
def get_all_users(self):
return [{"id": "user1", "name": "Alice"},{"id": "user2", "name": "Bob"}]
class Settings:
def __init__(self, database_url):
self.database_url = database_url
# Määratle konteiner (nagu varem)
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
settings = providers.Singleton(Settings, database_url = config.database_url)
database = providers.Singleton(Database, connection_string=config.database_url)
user_repository = providers.Factory(UserRepository, database=database)
# Loo FastAPI rakendus (nagu varem)
app = FastAPI()
# Konfigureeri konteiner (keskkonnamuutuja abil)
container = Container()
container.config.database_url.from_env("DATABASE_URL", default="sqlite:///:memory:")
container.wire([__name__]) # võimaldab sõltuvuste süstimist FastAPI lõpp-punktidesse
# Sõltuvus FastAPI jaoks
def get_user_repository(user_repository: UserRepository = Depends(container.user_repository.provided)) -> UserRepository:
return user_repository
# Sissesüstitud sõltuvust kasutav lõpp-punkt (nagu varem)
@app.get("/users/")
async def read_users(user_repository: UserRepository = Depends(get_user_repository)):
return user_repository.get_all_users()
@app.on_event("startup")
async def startup_event():
# Konteineri initialisatsioon
container.init_resources()
# Test
@pytest.fixture
def test_client():
# Asenda andmebaasi sõltuvus võltsitud objektiga
database_mock = MagicMock(spec=Database)
database_mock.get_items.return_value = [{"id": 3, "name": "Test Item"}]
user_repository_mock = MagicMock(spec = UserRepository)
user_repository_mock.get_all_users.return_value = [{"id": "test_user", "name": "Test User"}]
# Asenda konteiner võltsitud sõltuvustega
container.user_repository.override(providers.Factory(lambda: user_repository_mock))
with TestClient(app) as client:
yield client
container.user_repository.reset()
def test_read_users(test_client: TestClient):
response = test_client.get("/users/")
assert response.status_code == 200
assert response.json() == [{"id": "test_user", "name": "Test User"}]
Selgitus:
- Loome
Databasesõltuvuse jaoks võltsitud objekti, kasutadesMagicMock. - Asendame konteineri
databasepakkuja võltsitud objektiga, kasutadescontainer.database.override(). test_read_itemstestifunktsioon kasutab nüüd võltsitud andmebaasi sõltuvust.- Pärast testi täitmist nullitakse konteineri asendatud sõltuvus.
4. Asünkroonsed sõltuvused
FastAPI on ehitatud asünkroonsele programmeerimisele (async/await). Kui töötate asünkroonsete sõltuvustega (nt asünkroonsed andmebaasiühendused), veenduge, et teie DI-konteiner ja sõltuvuste pakkujad toetavad asünkroonseid toiminguid.
Näide (Asünkroonne sõltuvus `dependency_injector`-iga):
import asyncio
from dependency_injector import containers, providers
from fastapi import FastAPI, Depends
# Määratle asünkroonne sõltuvus
class AsyncDatabase:
def __init__(self, connection_string: str):
self.connection_string = connection_string
async def connect(self):
print(f"Connecting to database: {self.connection_string}")
await asyncio.sleep(0.1) # Simuleerib ĂĽhenduse aega
async def fetch_data(self):
await asyncio.sleep(0.1) # Simuleerib andmebaasipäringut
return [{"id": 1, "name": "Async Item 1"}]
# Määratle konteiner
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
database = providers.Singleton(AsyncDatabase, connection_string=config.database_url)
# Loo FastAPI rakendus
app = FastAPI()
# Konfigureeri konteiner
container = Container()
container.config.database_url.from_env("DATABASE_URL", default="sqlite:///:memory:")
container.wire([__name__])
# Sõltuvus FastAPI jaoks
async def get_async_database(database: AsyncDatabase = Depends(container.database.provided)) -> AsyncDatabase:
await database.connect()
return database
# Sissesüstitud sõltuvust kasutav lõpp-punkt
@app.get("/async-items/")
async def read_async_items(database: AsyncDatabase = Depends(get_async_database)):
data = await database.fetch_data()
return data
@app.on_event("startup")
async def startup_event():
# Konteineri initialisatsioon
container.init_resources()
Selgitus:
AsyncDatabaseklass määratleb asünkroonsed meetodid, kasutadesasyncjaawait.get_async_databasesõltuvus on samuti määratletud asünkroonse funktsioonina.read_async_itemslõpp-punkti funktsioon on märgitud kuiasyncja ootabdatabase.fetch_data()tulemuse saamist.
Õige lähenemisviisi valimine
Täiustatud DI-konteineri loomiseks parim lähenemisviis sõltub teie rakenduse keerukusest ja teie spetsiifilistest nõudmistest:
- Väikeste ja keskmise suurusega projektide jaoks: FastAPI sisseehitatud DI või tehasefunktsiooni lähenemisviis koos
Depends-iga võib olla piisav. - Suuremate, keerukamate projektide jaoks: Spetsiifiline DI-raamatukogu nagu
dependency_injectorpakub põhjalikku komplekti funktsioone sõltuvuste haldamiseks. - Projektide jaoks, mis nõuavad DI-protsessi üle täpse kontrolli: Kohandatud DI-konteineri implementeerimine võib olla parim variant.
Kokkuvõte
Sõltuvuste süstimine on võimas tehnika skaleeritavate, hooldatavate ja testitavate rakenduste loomiseks. Kuigi FastAPI sisseehitatud DI-süsteem on suurepärane lihtsate kasutusjuhtumite jaoks, võib täiustatud DI-konteineri arhitektuur pakkuda märkimisväärseid eeliseid keerukamate projektide jaoks. Valides õige lähenemisviisi ja kasutades ära DI-raamatukogude funktsioone või implementeerides kohandatud konteineri, saate luua robustse ja paindliku sõltuvuste haldamise süsteemi, mis parandab teie FastAPI rakenduste üldist kvaliteeti ja hooldatavust.
Globaalsed kaalutlused
Globaalsete rakenduste jaoks DI-konteinerite kujundamisel on oluline kaaluda järgmist:
- Lokaliseerimine: Lokaliseerimisega seotud sõltuvused (nt keelesätted, kuupäevavormingud) peaksid olema hallatavad DI-konteineri poolt, et tagada järjepidevus erinevate piirkondade vahel.
- Ajavööndid: Ajavööndi teisendusi käsitlevad sõltuvused tuleks süstida, et vältida ajavööndi teabe koodi sisse kirjutamist.
- Valuuta: Valuutakonversiooni ja vormindamise sõltuvused peaksid olema konteineri poolt hallatavad, et toetada erinevaid valuutasid.
- Regionaalsed sätted: Muud regionaalsed sätted, nagu arvu- ja aadressivormingud, peaksid samuti olema DI-konteineri poolt hallatavad.
- Mitme rentniku mudel (Multi-tenancy): Mitme rentniku mudelite jaoks peaks DI-konteiner suutma pakkuda erinevaid sõltuvusi erinevatele rentnikele. Seda saab saavutada ulatuste või kohandatud sõltuvuste lahendusloogika abil.
- Vastavus ja turvalisus: Veenduge, et teie sõltuvuste haldamise strateegia vastab asjakohastele andmekaitsemäärustele (nt GDPR, CCPA) ja turvameetoditele erinevates piirkondades. Käsitsege tundlikke mandaate ja konfiguratsioone turvaliselt konteineri sees.
Neid globaalseid tegureid arvesse võttes saate luua DI-konteinereid, mis sobivad hästi globaalses keskkonnas tegutsevate rakenduste loomiseks.