Hasura ile Çoklu Veritabanı Federasyonu: PostgreSQL ve MySQL’i Tek API’de Birleştirme

Modern mikroservis mimarilerinde en büyük sorunlardan biri, farklı ekiplerin farklı veritabanı teknolojileri kullanmasıdır. Bir ekip yıllarca PostgreSQL ile çalışmış, diğeri MySQL’e alışmış, üçüncüsü belki MongoDB ile devam etmektedir. Peki tüm bu dağıtık veriyi tek bir tutarlı API üzerinden sunmak istediğinizde ne yapacaksınız? İşte Hasura’nın çoklu veritabanı federasyonu tam bu noktada devreye giriyor. Bu yazıda PostgreSQL ve MySQL veritabanlarını Hasura üzerinde nasıl birleştireceğinizi, gerçek dünya senaryolarıyla birlikte adım adım anlatacağım.

Hasura Federasyonu Neden Gerekli?

Düşünün: E-ticaret platformunuzda kullanıcı verileri PostgreSQL’de, sipariş geçmişi MySQL’de tutuluyor. İki farklı database, iki farklı bağlantı, iki farklı sorgu dili. Frontend ekibi her özellik için iki ayrı API endpoint’i çağırmak zorunda kalıyor. Bu hem geliştirme sürecini yavaşlatıyor hem de network katmanında gereksiz yük yaratıyor.

Hasura’nın Data Federation özelliği ile:

  • Tek GraphQL endpoint üzerinden tüm veritabanlarına erişebilirsiniz
  • Cross-database join yapabilirsiniz, yani PostgreSQL tablosunu MySQL tablosuyla birleştirebilirsiniz
  • Unified schema sayesinde frontend hangi veri nerede diye düşünmez
  • Role-based access control tek yerden yönetilir

Hasura v2.0 ile gelen “Data Sources” özelliği bu federasyonun temelini oluşturuyor. Her veritabanı ayrı bir “source” olarak tanımlanıyor ve Hasura bunları tek bir GraphQL schema altında birleştiriyor.

Ortam Kurulumu

Önce Docker Compose ile geliştirme ortamımızı kuralım. Gerçek bir senaryoyu simüle etmek için hem PostgreSQL hem MySQL’i aynı ortamda ayağa kaldıracağız.

# docker-compose.yml dosyamızı oluşturalım
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_USER: pguser
      POSTGRES_PASSWORD: pgpassword
      POSTGRES_DB: users_db
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init-postgres.sql:/docker-entrypoint-initdb.d/init.sql

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: mysqlpassword
      MYSQL_DATABASE: orders_db
      MYSQL_USER: mysqluser
      MYSQL_PASSWORD: mysqlpassword
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init-mysql.sql:/docker-entrypoint-initdb.d/init.sql

  hasura:
    image: hasura/graphql-engine:v2.35.0
    ports:
      - "8080:8080"
    depends_on:
      - postgres
      - mysql
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://pguser:pgpassword@postgres:5432/users_db
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_ADMIN_SECRET: mysecretkey
      HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://pguser:pgpassword@postgres:5432/users_db

volumes:
  pg_data:
  mysql_data:
EOF

Şimdi PostgreSQL için başlangıç verilerini hazırlayalım:

cat > init-postgres.sql << 'EOF'
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    full_name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    is_active BOOLEAN DEFAULT true
);

CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    phone VARCHAR(20),
    address TEXT,
    loyalty_points INTEGER DEFAULT 0
);

INSERT INTO users (email, full_name) VALUES
    ('[email protected]', 'Ahmet Yilmaz'),
    ('[email protected]', 'Fatma Kaya'),
    ('[email protected]', 'Mehmet Demir');

INSERT INTO user_profiles (user_id, phone, address, loyalty_points) VALUES
    (1, '+90 555 111 2233', 'Istanbul, Kadikoy', 1500),
    (2, '+90 555 444 5566', 'Ankara, Cankaya', 750),
    (3, '+90 555 777 8899', 'Izmir, Karsiyaka', 2300);
EOF

MySQL tarafı için de sipariş tablolarını oluşturalım:

cat > init-mysql.sql << 'EOF'
USE orders_db;

CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    total_amount DECIMAL(10,2) NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
    created_at DATETIME DEFAULT NOW(),
    updated_at DATETIME DEFAULT NOW() ON UPDATE NOW()
);

CREATE TABLE order_items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT REFERENCES orders(id),
    product_name VARCHAR(255) NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL
);

INSERT INTO orders (user_id, total_amount, status) VALUES
    (1, 299.90, 'delivered'),
    (1, 149.50, 'processing'),
    (2, 599.00, 'shipped'),
    (3, 89.99, 'pending');

INSERT INTO order_items (order_id, product_name, quantity, unit_price) VALUES
    (1, 'Laptop Cantasi', 1, 299.90),
    (2, 'Kablosuz Mouse', 2, 74.75),
    (3, 'Mekanik Klavye', 1, 599.00),
    (4, 'USB Hub', 1, 89.99);
EOF

# Servisleri ayaga kaldiralim
docker-compose up -d

# Servislerin hazir olmasini bekleyelim
sleep 30
echo "Servisler hazir!"

MySQL Data Source Ekleme

Hasura varsayılan olarak PostgreSQL’i primary database olarak kullanıyor. Şimdi MySQL’i ikinci bir source olarak ekleyelim. Bunu Hasura Metadata API üzerinden yapacağız:

# MySQL data source'u Hasura'ya ekle
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "mysql_add_source",
    "args": {
      "name": "mysql_orders",
      "configuration": {
        "connection_info": {
          "connection_string": "mysql://mysqluser:mysqlpassword@mysql:3306/orders_db",
          "pool_settings": {
            "max_connections": 50,
            "idle_timeout": 180,
            "retries": 1,
            "pool_timeout": 360,
            "connection_lifetime": 600
          }
        }
      }
    }
  }'

echo "MySQL source eklendi"

# MySQL tablolarini track et
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "mysql_track_table",
    "args": {
      "source": "mysql_orders",
      "table": {
        "name": "orders",
        "schema": "orders_db"
      }
    }
  }'

curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "mysql_track_table",
    "args": {
      "source": "mysql_orders",
      "table": {
        "name": "order_items",
        "schema": "orders_db"
      }
    }
  }'

Cross-Database Remote Schema ile İlişki Kurma

İşte asıl sihir burada başlıyor. PostgreSQL’deki users tablosu ile MySQL’deki orders tablosu arasında ilişki kuracağız. Hasura bunu “Remote Relationships” özelliğiyle yapıyor:

# PostgreSQL users tablosu ile MySQL orders arasinda iliski kur
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "create_remote_relationship",
    "args": {
      "name": "orders",
      "source": "default",
      "table": {
        "name": "users",
        "schema": "public"
      },
      "definition": {
        "to_source": {
          "relationship_type": "array",
          "source": "mysql_orders",
          "table": {
            "name": "orders",
            "schema": "orders_db"
          },
          "field_mapping": {
            "id": "user_id"
          }
        }
      }
    }
  }'

echo "Cross-database iliski olusturuldu"

# MySQL orders tablosindan PostgreSQL users tablosuna ters iliski
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "mysql_create_remote_relationship",
    "args": {
      "name": "user_info",
      "source": "mysql_orders",
      "table": {
        "name": "orders",
        "schema": "orders_db"
      },
      "definition": {
        "to_source": {
          "relationship_type": "object",
          "source": "default",
          "table": {
            "name": "users",
            "schema": "public"
          },
          "field_mapping": {
            "user_id": "id"
          }
        }
      }
    }
  }'

Gerçek Dünya Sorgu Senaryoları

Şimdi bu federasyonun gerçekte nasıl çalıştığını görelim. Frontend ekibiniz artık tek bir GraphQL sorgusuyla hem kullanıcı bilgilerini hem de siparişleri çekebilir:

# Kullanici ve siparislerini tek sorguda getir
curl -X POST 
  http://localhost:8080/v1/graphql 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "query": "query GetUserWithOrders($userId: Int!) { users(where: {id: {_eq: $userId}}) { id email full_name user_profiles { phone address loyalty_points } orders { id total_amount status created_at order_items { product_name quantity unit_price } } } }",
    "variables": {"userId": 1}
  }'

# Tum siparisleri kullanici bilgileriyle getir
curl -X POST 
  http://localhost:8080/v1/graphql 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "query": "query GetOrdersWithUsers { orders(order_by: {created_at: desc}) { id total_amount status user_info { email full_name } order_items { product_name quantity } } }"
  }'

Hasura Metadata Yönetimi ve Versiyon Kontrolü

Üretim ortamında metadata’yı kod olarak yönetmek kritik önem taşıyor. Hasura CLI kullanarak bu yapıyı versiyon kontrolüne alabiliriz:

# Hasura CLI kurulumu
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

# Proje baslat
hasura init eticaret-api --endpoint http://localhost:8080 --admin-secret mysecretkey

cd eticaret-api

# Mevcut metadata'yi indir
hasura metadata export

# Metadata dizin yapisi
ls -la metadata/
# databases/
# remote_schemas/
# actions/
# cron_triggers/

# databases klasorune bakalim
ls metadata/databases/
# default/   -> PostgreSQL
# mysql_orders/  -> MySQL

# Degisiklikleri uygula
hasura metadata apply

# Metadata tutarliligini kontrol et
hasura metadata inconsistency list

Metadata dosyalarınızı Git’e ekleyip her ortam için ayrı konfigürasyon yönetebilirsiniz. CI/CD pipeline’ınıza hasura metadata apply komutunu eklemek yeterli.

Performans Optimizasyonu: Connection Pooling ve Caching

Çoklu database federasyonunda en büyük sorun performans olabiliyor. Her cross-database ilişki aslında arka planda birden fazla sorgu anlamına geliyor. Bunu yönetmek için birkaç kritik ayar yapmanız gerekiyor:

# PgBouncer ile PostgreSQL connection pooling
cat > pgbouncer.ini << 'EOF'
[databases]
users_db = host=postgres port=5432 dbname=users_db

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 600
EOF

# Hasura'nin response caching'ini aktif et
curl -X POST 
  http://localhost:8080/v1/graphql 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "query": "query GetPopularProducts @cached(ttl: 120) { order_items { product_name } }"
  }'

# Query timeout ayarlari
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "update_source",
    "args": {
      "name": "default",
      "configuration": {
        "connection_info": {
          "connection_string": "postgres://pguser:pgpassword@pgbouncer:6432/users_db",
          "pool_settings": {
            "max_connections": 50,
            "idle_timeout": 180
          }
        },
        "query_timeout": 60
      }
    }
  }'

echo "Performance ayarlari tamamlandi"

İzleme ve Hata Ayıklama

Federasyon ortamında bir sorun çıktığında hangi database’den geldiğini anlamak zor olabilir. Hasura’nın built-in analitik ve log sistemini aktif edelim:

# Hasura log seviyelerini ayarla
export HASURA_GRAPHQL_LOG_LEVEL=debug
export HASURA_GRAPHQL_ENABLED_LOG_TYPES="startup,http-log,webhook-log,websocket-log,query-log"

# Canli log takibi
docker-compose logs -f hasura | grep -E "(error|query|database)"

# Hasura'nin dahili metrikleri
curl http://localhost:8080/v1/metrics 
  -H 'x-hasura-admin-secret: mysecretkey' | grep hasura_graphql

# Yavaş sorguları tespit et
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "get_inconsistent_metadata",
    "args": {}
  }'

# Database baglanti durumunu kontrol et
curl http://localhost:8080/healthz?strict=false 
  -H 'x-hasura-admin-secret: mysecretkey'

Role-Based Access Control Federasyon Ortamında

Farklı database’lerdeki tablolar için tek yerden erişim kontrolü tanımlamak federasyonun en güçlü özelliklerinden biri:

# Musteri rolunu tanimla - sadece kendi siparislerini gorebilsin
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "mysql_create_select_permission",
    "args": {
      "source": "mysql_orders",
      "table": {
        "name": "orders",
        "schema": "orders_db"
      },
      "role": "customer",
      "permission": {
        "columns": ["id", "total_amount", "status", "created_at"],
        "filter": {
          "user_id": {
            "_eq": "X-Hasura-User-Id"
          }
        }
      }
    }
  }'

# PostgreSQL users tablosu icin musteri permissioni
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: mysecretkey' 
  -d '{
    "type": "create_select_permission",
    "args": {
      "source": "default",
      "table": {
        "name": "users",
        "schema": "public"
      },
      "role": "customer",
      "permission": {
        "columns": ["id", "email", "full_name"],
        "filter": {
          "id": {
            "_eq": "X-Hasura-User-Id"
          }
        }
      }
    }
  }'

echo "RBAC kurulumu tamamlandi"

Olası Sorunlar ve Çözümleri

Federasyon kurulumu sırasında karşılaşılan yaygın sorunları ve çözümlerini paylaşayım:

Cross-database sorgu N+1 problemi: Hasura her kullanıcı için ayrı ayrı MySQL sorgusu atmak yerine batch query kullanır. Ancak çok büyük result set’lerde bu yeterli olmayabilir. Bu durumda @cached directive’i veya materialized view kullanmayı düşünün.

MySQL zaman dilimi tutarsızlıkları: PostgreSQL ve MySQL farklı timezone davranışları sergileyebilir. MySQL container’ınıza TZ=Europe/Istanbul environment variable’ı ekleyin ve PostgreSQL’de timezone = 'Europe/Istanbul' ayarını postgresql.conf’a yazın.

Metadata sync sorunları: İki database’i aynı anda güncellerken metadata tutarsız kalabilir. hasura metadata inconsistency list komutu ile bunu tespit edip hasura metadata inconsistency drop ile temizleyebilirsiniz.

Connection timeout yönetimi: MySQL’in default wait_timeout değeri 8 saat, PostgreSQL’in idle connection timeout’u çok daha uzun olabilir. Hasura tarafında connection_lifetime değerini her iki database için de makul bir değere çekin, genellikle 600 saniye iyi bir başlangıç noktasıdır.

Sonuç

Hasura ile çoklu veritabanı federasyonu, legacy sistemleri değiştirmeden onları modern bir API katmanıyla birleştirmenin en pratik yollarından biri. PostgreSQL ve MySQL’i tek bir GraphQL endpoint altında toplamak, frontend ekibinin iş yükünü dramatik biçimde azaltıyor ve backend tarafında veri tutarlılığını merkezi bir noktadan yönetme imkanı sunuyor.

Bu yazıda ele aldığımız konuları özetle değerlendirirsek:

  • Docker Compose ile gerçekçi bir çoklu database ortamı kurduk
  • MySQL Data Source ekleme ve tablo tracking işlemlerini öğrendik
  • Remote Relationships ile cross-database ilişkiler tanımladık
  • Metadata yönetimini Hasura CLI ile versiyon kontrolüne aldık
  • Connection pooling ve query caching ile performans optimizasyonu yaptık
  • RBAC kurallarını federasyon ortamında nasıl uygulayacağımızı gördük

Üretim ortamına geçmeden önce kesinlikle yapmanız gereken birkaç şey var: Metadata dosyalarını Git’e alın, her ortam için ayrı admin secret kullanın, monitoring için Prometheus + Grafana entegrasyonu ekleyin ve cross-database sorgu performansını düzenli olarak ölçün.

Hasura her geçen gün daha fazla database desteği ekliyor; MongoDB, SQL Server ve BigQuery entegrasyonları da mevcut. Eğer organizasyonunuzda büyük bir veri karmaşası varsa bu federasyon yaklaşımı size hem geliştirme hızı hem de operasyonel sadelik kazandıracak. Sorularınız veya ek senaryolarınız için yorumları kullanabilirsiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir