AI Destekli Full-Stack Geliştirme: Servisler Arası İletişim ve Dockerization
Bu yazının amacı bir restoran uygulaması tanıtmak değil. Amaç, çok servisli bir sistemde (web arayüzü, backend API, veritabanı, gerçek zamanlı bildirim) servislerin birbiriyle nasıl haberleştiğini, bunu Docker ile nasıl paketlediğimi ve bu süreçte AI'ı bir geliştirme aracı olarak nasıl kullandığımı göstermek. Örnek olarak QR kod ile sipariş verilen basit bir sistem üzerinden anlatıyorum, ama buradaki mimari ve DevOps prensipleri herhangi bir çok-servisli projeye uygulanabilir.
Kullanılan teknolojiler
- Backend: Python, FastAPI, SQLAlchemy (ORM), Pydantic, python-jose (JWT), passlib + bcrypt
- Veritabanı: PostgreSQL 16
- Frontend: React (Vite)
- Gerçek zamanlı iletişim: WebSocket
- Konteynerizasyon: Docker, Docker Compose, nginx (statik dosya servisi)
AI ile geliştirme sürecim
Kodun büyük kısmını Claude ile birlikte yazdım, ama süreç "AI'a yaz dedim, oldu" değildi. Sırasıyla:
- Önce mimari — hangi servis kiminle, hangi protokolle konuşacak (REST mi, WebSocket mi?), veritabanı şeması ne olacak, bunları kod yazmadan önce netleştirdim.
- Parça parça üretim — DB modelleri, sonra API endpoint'leri, sonra WebSocket katmanı, sonra frontend; her adımda tek seferde her şeyi değil, küçük ve test edilebilir parçalar halinde ilerledim.
- Her adımı gerçek ortamda çalıştırıp doğrulama — kod "mantıken doğru görünüyor" ile yetinmedim, her parçayı kendi makinemde (WSL + Docker) çalıştırıp gerçek çıktısını gördüm.
- Hataları okuyup kök sebebini bulma — aşağıda anlatacağım üç DevOps sorununun hiçbiri "AI'ın ilk yazdığı kod"da görünmedi; hepsi gerçek ortamda çalıştırınca çıktı, ve çözüm için hata mesajını/log'u okuyup neden olduğunu anlamak gerekti (örneğin bir Docker Compose environment variable parsing detayı gibi, kodun kendisiyle alakasız bir sorun).
Bu son adım, AI destekli geliştirmenin gerçekçi tarafı: AI kod üretme hızını ciddi artırıyor, ama "çalıştır, gerçek hatayı oku, kök sebebi anla" adımını hiçbir şekilde ortadan kaldırmıyor — tam tersine, bu adımı iyi yapabilmek AI ile üretilen kodu güvenilir hale getiren şey.
Servisler arası iletişim mimarisi
Örnek sistemde üç servis var, üçü de farklı bir iletişim yöntemi kullanıyor — bu kasıtlı, çünkü her protokolün ne zaman doğru araç olduğunu göstermek istedim:
-
Web arayüzü → Backend: Standart REST (HTTP). İstemci bir kaynağı okumak/yazmak istediğinde (
GET /menu,POST /orders), senkron bir istek-cevap döngüsü yeterli. - Backend → Veritabanı: SQLAlchemy ORM üzerinden. ORM, ham SQL yazmak yerine Python nesneleri üzerinden çalışmayı sağlıyor, ama üretilen sorguları anlamak (N+1 query gibi tuzaklara düşmemek için) yine de önemli.
- Backend → Gerçek zamanlı istemci (WebSocket): Bir tarafın diğerine sürekli "bir şey değişti mi?" diye sorması (polling) yerine, sunucunun değişikliği anında ittiği (push) bir bağlantı. Bunu, istemcinin anlık bilgiye ihtiyacı olduğu ve bu bilginin ne zaman geleceğinin öngörülemediği senaryolarda tercih ettim.
Kritik bir tasarım kararı — sıralama: Bir işlem hem veritabanına yazma hem de bildirim gönderme içeriyorsa, önce veritabanı yazması tamamlanıyor, sonra bildirim gönderiliyor:
# 1. Önce veritabanına güvenilir şekilde yaz
db_order = models.Order(...)
db.add(db_order)
db.commit()
# 2. Sonra bağlı istemcilere bildir
await manager.broadcast({...})
Sıra tersine çevrilseydi, veritabanı yazma hatası durumunda istemciye "işlem oldu" bilgisi giderdi ama sistemde hiçbir kaydı olmazdı — sessiz bir veri tutarsızlığı. Bu tür sıralama kararları, servisler arası iletişim tasarlarken kod satırlarından daha önemli.
Dockerization
Üç servisi (db, backend, frontend) tek docker-compose.yml içinde tanımladım:
services:
db:
image: postgres:16
environment:
POSTGRES_USER: restaurant
POSTGRES_PASSWORD: restaurant123
POSTGRES_DB: restaurant_db
volumes:
- restaurant_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U restaurant"]
interval: 5s
timeout: 5s
retries: 5
backend:
build: ./backend
env_file:
- .env
environment:
DATABASE_URL: postgresql://restaurant:restaurant123@db:5432/restaurant_db
CORS_ORIGINS: http://localhost:3000,http://localhost:8080
depends_on:
db:
condition: service_healthy
frontend:
build: ./frontend
depends_on:
- backend
volumes:
restaurant_db_data:
Üç nokta önemli:
-
healthcheck+depends_on: condition: service_healthy— backend, veritabanı gerçekten istek almaya hazır olana kadar bekliyor. Sadece "container ayakta mı" değil, "servis gerçekten çalışıyor mu" kontrolü. -
Servis isimleri host adı olarak kullanılıyor —
DATABASE_URLiçindedbyazıyor,localhostdeğil. Docker network'ünde her servis, diğerine kendi servis ismiyle ulaşıyor. -
env_fileile secrets ayrımı — admin şifre hash'i ve JWT anahtarı gibi hassas değerler.envdosyasında, kod veya compose dosyasında değil.
Backend Dockerfile (layer caching'e dikkat: requirements.txt önce kopyalanıyor, böylece kod değiştiğinde kütüphaneler yeniden kurulmuyor):
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Frontend Dockerfile (multi-stage build — Node.js ve node_modules sadece derleme aşamasında var, final image'a hiç girmiyor):
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Sonuç: birkaç yüz MB yerine birkaç MB'lık bir nginx image'ı, sadece derlenmiş statik dosyaları içeriyor.
Karşılaşılan üç DevOps sorunu
1. .env dosyasındaki $ işareti Docker Compose'u yanıltıyor. bcrypt hash'i ($2b$12$...) birden fazla $ içeriyor. Docker Compose, .env'i kendi değişken yorumlama motorundan geçirdiği için bu işaretleri "değişken referansı" sanıp değeri sessizce bozdu — sonuç anlaşılması güç bir 500 hatası. Çözüm: .env içindeki her $'ı $$ olarak escape etmek.
2. CORS, "çalışmıyor" ile "sessizce reddediliyor" arasındaki farkı gizler. Yeni bir origin (frontend'in yeni portu, admin panelin ayrı portu) her eklendiğinde, backend'in izin listesine tek tek eklenmesi gerekti — eklenmeyince istek tarayıcı tarafından sessizce reddediliyor, hata sadece network sekmesinde görünüyor.
3. Güvenlik, fonksiyonellikten sonra gelme eğiliminde. Bir yazma endpoint'i önce kimlik doğrulamasız yazıldı ("sonra eklerim" diye), sonra fark edildi ki bu ciddi bir açık. JWT tabanlı bir katman sonradan eklendi. Bu, "önce çalıştır, sonra güvenli hale getir" kalıbının gerçek bir örneği — ve bunun bir alışkanlık değil, bilinçli bir kontrol listesi maddesi olması gerektiğinin hatırlatıcısı.
Sonuç
Buradaki asıl mesaj: bir sistemin çalışması ile production'a hazır olması farklı şeyler. Servisler arası doğru iletişim protokolünü seçmek, secrets'ı koddan ayırmak, sağlık kontrolleriyle başlatma sırasını garanti altına almak ve güvenliği sona bırakmamak — bunlar restoran uygulaması özelinde değil, herhangi bir çok-servisli projede karşılaşılan, genellenebilir mühendislik kararları.
Kod: https://github.com/tahayagizguler/restaurant-order-system
Top comments (0)