Python Programming

FastAPI ve WebSockets ile Gerçek Zamanlı Olay Odaklı API'ler Oluşturma

Modern yazılım mimarisi manzarasında, gerçek zamanlı iletişim talebi bir "olması güzel" özellikten temel bir gereksinime dönüşmüştür. İster canlı hisse senedi ticaret panelleri, ister işbirlikli düzenleme araçları, isterse de yüksek frekanslı oyun uygulamaları olsun, sistemler artık durum değişikliklerine anında tepki vermek zorundadır. Geleneksel HTTP sorgulama (polling) bu senaryolar için verimsizdir, gereksiz gecikme ve sunucu yükü getirir. İşte tam da bu noktada, Python'da sağlam, olay odaklı API'ler oluşturmak için FastAPI, WebSocket protokolleriyle birlikte en iyi tercih olarak öne çıkar.

FastAPI'nin gücü, Starlette ve Pydantic'ın üzerinde inşa edilmiş asenkron çerçevesinde yatar. Yüksek performanslı REST uç noktaları açısından HTTP üzerinden tanınmasına rağmen, olay odaklı alandaki gerçek gücü WebSockets sayesinde açığa çıkar. Python'un `asyncio` ekosisteminden yararlanarak, sunucunun bir olay gerçekleştiği anda veriyi istemcilere itebileceği, istemcinin güncellemeler için tekrar tekrar sorgulama yapmasına gerek kalmayan kalıcı, çift yönlü bağlantılar kurabiliriz.

Mimariyi Anlamak: Önce Asenkron

Her gerçek zamanlı uygulamanın temeli, ana iş parçığını (thread) bloklamadan eşzamanlı bağlantıları yönetebilme yeteneğidir. Senkron bir dünyada, dikkatlice yönetilmezse tek bir uzun ömürlü WebSocket bağlantısı tüm sunucuyu kilitleyebilir. FastAPI bunu yerel olarak çözer. `async` ve `await` anahtar kelimelerini kullanarak sunucu, tek bir iş parçığında binlerce eşzamanlı WebSocket bağlantısını çoklayabilir (multiplex) ve yürütmenin durmasına neden olmadan I/O olaylarına (gelen mesajlar veya veritabanı sorguları gibi) bekleyebilir.

Bir olay odaklı API tasarlarken akış genellikle üç bileşen içerir: İstemci, Aracı/Sunucu ve Olay Bus'u. Saf bir FastAPI kurulumunda, bağlı istemciler arasında durumu yönetmek için genellikle paylaşılan bir bellek içi olay sözlüğünü veya `asyncio` kuyrukları kullanarak bir üretici-tüketici deseni kullanırız.

WebSocket Uç Noktasının Uygulanması

Başlamak için, `@app.websocket` dekoratörünü kullanarak bir WebSocket rotası tanımlarız. Standart REST yollarının aksine, WebSockets bir el sıkışma (handshake) işlemini yönetir, kalıcı bir bağlantı kurar ve ardından sunucunun hem okuyup hem de mesajları sınırsız yazabileceği bir döngüye girer.

Aşağıda, bağlantı yaşam döngüsünü anlamak için temel bir temel oluşturan, istemciye mesajları yansıtan (echo) temel bir WebSocket uç noktasının pratik bir örneği yer almaktadır.

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            # Mesajı işleyin veya burada bir olay tetikleyin
            await websocket.send_text(f"Gelen mesaj metni: {data}")
    except WebSocketDisconnect:
        print("İstemci bağlantısı koptu")

Çoklu İstemciler İçin Ölçeklendirme: Yayın (Broadcast) Deseni

Yukarıdaki yansıma (echo) örneği basittir. Olay odaklı API'lerin gerçek gücü, tek bir olayı bağlı tüm istemcilere yaymak istediğimizde ortaya çıkar. Bunu başarmak için, aktif WebSocket bağlantılarının bir kaydını tutmamız gerekir. Bir üretim ortamında bu genellikle bir Redis pub/sub kanalı olurdu, ancak ara düzey öğrenme için bellek içi bir liste mükemmel çalışır.

Uygulamamızı, bir istemciden bildirimleri kabul edip bunları diğerlerine yayacak şekilde geliştirelim.

from typing import List
from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            # Alınan mesajı bağlı tüm istemcilere yayınlayın
            await manager.broadcast(f"Yayınlanıyor: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast("Bir istemci bağlantısı koptu")

Dış Olaylar ve Arka Plan Görevleri ile Entegrasyon

Gerçek bir olay mimarisinde, olaylar genellikle WebSocket katmanının kendisinin dışında köken alır. Bu bir veritabanı değişikliği, harici bir API'den gelen bir mesaj veya planlanmış bir görev olabilir. FastAPI, bu dış olayları dinlemek ve bunları WebSocket bağlantılarımız üzerinden iletmek için `BackgroundTasks` veya `asyncio.create_task` deseni kullanarak arka plan görevlerini çalıştırmamıza olanak tanır.

Örneğin, her dakika üçüncü taraf bir hava durumu API'sini sorgulayan ve yeni veriyi bağlı tüm panellerdeki istemcilere yayınlayan bir arka plan hizmeti oluşturabilirsiniz. Bu, olay üretimi ile istemci teslimatını birbirinden ayırarak, ilgi alanlarının ayrımı (separation of concerns) ilkesine uygunluk sağlar.

Sonuç

FastAPI ve WebSockets ile gerçek zamanlı, olay odaklı API'ler oluşturmak, modern web uygulamaları için güçlü ve ölçeklenebilir bir çözüm sunar. Python'un asenkron yeteneklerinden yararlanarak, geliştiriciler duyarlı, verimli ve yüksek eşzamanlılık yüklerini yönetebilen sistemler oluşturabilir. İster bir sohbet uygulaması, ister canlı bildirim sistemi, isterse de karmaşık bir izleme paneli yapıyor olun, FastAPI'nin performansı ve WebSocket'in kalıcı bağlantısı ideal teknik temeli sunar. Projelerinizde ilerledikçe, ölçeklenebilirliği göz önünde bulundurmayı unutmayın; geliştirme için bellek içi listeler iş görse de, üretim seviyesindeki dağıtımlar için durum yönetimi amacıyla Redis'e geçiş kritik önem taşıyacaktır.

Share: