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.
