Tüm Yazılar
KategoriBackend
Okuma Süresi
23 dk okuma
Yayın Tarihi
...
Kelime Sayısı
2.284kelime

Kahveni hazırla - bu içerikli bir makale!

Type-safe SDK, schema-first yaklaşım, PostgreSQL backend, auto-generated queries, real-time sync ve Firebase ekosistemiyle derinlemesine entegrasyon rehberi.

Firebase Data Connect: GraphQL + Cloud SQL

# Firebase Data Connect: GraphQL + Cloud SQL

Firebase'in en büyük eleştirisi hep aynıydı: "Firestore güçlü ama ilişkisel veri modellemesi zayıf." Google bu sorunu Firebase Data Connect ile çözdü. Cloud SQL (PostgreSQL) üzerine inşa edilmiş, GraphQL ile sorgulanan ve otomatik olarak type-safe SDK üreten bir servis. Artık Firebase'in kolaylığını PostgreSQL'in gücüyle birleştirebilirsin. Haydi, Data Connect'in mimarisini, schema-first yaklaşımını, auto-generated SDK'ları ve production'da nasıl kullanacağını derinlemesine inceleyelim.

💡 Not: Firebase Data Connect, 2024'te GA oldu. Bu yazıdaki bilgiler Şubat 2026 itibarıyla güncel. Resmi kaynak: Data Connect Docs, backend: Cloud SQL, şema dili: GraphQL Spec. GraphQL Mobile yazımızda GraphQL temellerini ele almıştık — burada Firebase'e özgü detaylara odaklanıyoruz.

İçindekiler


Data Connect Nedir?

Firebase Data Connect, Firebase ekosistemi içinde çalışan bir GraphQL API servisi. Arkasında Cloud SQL for PostgreSQL var. Sen schema ve query tanımlarsın, Firebase otomatik olarak iOS, Android, Flutter ve Web için type-safe SDK üretir.

Mimari Genel Bakış

\\\`text

Mobil/Web App

↓ (Type-safe SDK)

Firebase Data Connect Service

↓ (GraphQL)

Cloud SQL for PostgreSQL

\\\`

Neden Data Connect?

Sorun
Firestore Çözümü
Data Connect Çözümü
İlişkisel veri
Subcollection + denormalizasyon
Foreign key + JOIN
Complex query
Composite index + client filtering
SQL query gücü
Type safety
Zayıf (any benzeri)
Tam type-safe SDK
Schema
Schema-less (esnek ama riskli)
Schema-first (güvenli)
Transaction
Batch write (sınırlı)
ACID transaction
Aggregate
Client-side hesaplama
SUM, AVG, COUNT server-side
Migration
Manuel
SQL migration

Kurulum

\\\`bash

# Firebase CLI güncelle

npm install -g firebase-tools@latest

# Data Connect başlat

firebase init dataconnect

# Proje yapısı oluşur:

# dataconnect/

# ├── schema/ → GraphQL schema dosyaları

# ├── connector/ → Query ve mutation tanımları

# └── dataconnect.yaml

\\\`

🔍 Pro Tip: Data Connect, mevcut Firebase projene eklenebilir. Firestore'u tamamen bırakmana gerek yok — real-time sync için Firestore, ilişkisel veriler için Data Connect kullanabilirsin.

Schema-First Yaklaşım

Data Connect'te önce veritabanı şemasını GraphQL SDL ile tanımlarsın. Firebase bunu otomatik olarak PostgreSQL tablolarına dönüştürür.

GraphQL Schema + Query Hızlı Başlangıç

\\\`graphql

# dataconnect/schema/schema.gql — minimal blog şeması

type Post @table {

id: UUID! @default(expr: "uuidV4()")

title: String!

slug: String! @unique

content: String!

status: PostStatus! @default(value: "DRAFT")

author: User! @relation

publishedAt: Timestamp

createdAt: Timestamp! @default(expr: "request.time")

}

enum PostStatus { DRAFT PUBLISHED ARCHIVED }

# connector/queries.gql — yayınlanmış yazıları listele

query ListPosts($limit: Int! = 10) @auth(level: PUBLIC) {

posts(

where: { status: { eq: PUBLISHED } }

orderBy: [{ publishedAt: DESC }]

limit: $limit

) {

id title slug publishedAt

author { displayName }

}

}

\\\`

Schema tanımlandıktan sonra \firebase dataconnect:sdk:generate\ komutu çalıştırılır ve TypeScript, Swift, Dart için type-safe SDK'lar otomatik oluşturulur. Oluşturulan SDK dosyaları elle düzenlenmemelidir.


Schema Tanımı

\\\`graphql

# dataconnect/schema/schema.gql

# Kullanıcı tablosu

type User @table {

id: UUID! @default(expr: "uuidV4()")

email: String! @unique

displayName: String!

photoUrl: String

bio: String

createdAt: Timestamp! @default(expr: "request.time")

updatedAt: Timestamp! @default(expr: "request.time")

}

# Blog yazısı

type Post @table {

id: UUID! @default(expr: "uuidV4()")

title: String!

slug: String! @unique

content: String!

excerpt: String

status: PostStatus! @default(value: "DRAFT")

publishedAt: Timestamp

author: User! @relation

category: Category @relation

viewCount: Int! @default(value: 0)

createdAt: Timestamp! @default(expr: "request.time")

}

# Kategori

type Category @table {

id: UUID! @default(expr: "uuidV4()")

name: String! @unique

slug: String! @unique

description: String

posts: [Post!]! @relation # Ters ilişki

}

# Yorum

type Comment @table {

id: UUID! @default(expr: "uuidV4()")

content: String!

author: User! @relation

post: Post! @relation

parentComment: Comment @relation # Nested yorumlar

createdAt: Timestamp! @default(expr: "request.time")

}

# Beğeni (many-to-many)

type Like @table(

key: ["user", "post"] # Composite primary key

) {

user: User! @relation

post: Post! @relation

createdAt: Timestamp! @default(expr: "request.time")

}

# Enum

enum PostStatus {

DRAFT

PUBLISHED

ARCHIVED

}

\\\`

Schema Direktifleri

Direktif
Açıklama
Örnek
\`@table\`
PostgreSQL tablosu oluştur
\`type User @table\`
\`@unique\`
Unique constraint
\`email: String! @unique\`
\`@default\`
Varsayılan değer
\`@default(value: 0)\`
\`@relation\`
Foreign key ilişkisi
\`author: User! @relation\`
\`@index\`
Veritabanı indexi
\`@index(fields: ["status"])\`
\`@check\`
Constraint kontrolü
\`@check(expr: "rating >= 1")\`

Query ve Mutation Tanımlama

Schema'dan sonra, query (okuma) ve mutation (yazma) işlemlerini tanımlarsın:

Query Tanımları

\\\`graphql

# dataconnect/connector/queries.gql

# Tüm yayınlanmış yazıları getir (sayfalama ile)

query ListPublishedPosts(

$limit: Int! = 10,

$offset: Int! = 0,

$categorySlug: String

) @auth(level: PUBLIC) {

posts(

where: {

status: { eq: PUBLISHED },

category: { slug: { eq: $categorySlug } }

},

orderBy: [{ publishedAt: DESC }],

limit: $limit,

offset: $offset

) {

id

title

slug

excerpt

publishedAt

viewCount

author {

displayName

photoUrl

}

category {

name

slug

}

}

}

# Tek yazı detayı (slug ile)

query GetPostBySlug($slug: String!) @auth(level: PUBLIC) {

post: posts_findMany(where: { slug: { eq: $slug }, status: { eq: PUBLISHED } }) {

id

title

slug

content

publishedAt

viewCount

author {

id

displayName

photoUrl

bio

}

category {

name

slug

}

comments(orderBy: [{ createdAt: DESC }]) {

id

content

createdAt

author {

displayName

photoUrl

}

}

}

}

# Kullanıcı profili ve istatistikleri

query GetUserProfile($userId: UUID!) @auth(level: USER) {

user(id: $userId) {

id

displayName

email

bio

photoUrl

posts_on_author(where: { status: { eq: PUBLISHED } }) {

id

title

viewCount

}

comments_on_author {

id

}

likes_on_user {

post {

id

title

}

}

}

}

\\\`

Mutation Tanımları

\\\`graphql

# dataconnect/connector/mutations.gql

# Yeni yazı oluştur

mutation CreatePost($data: Post_Data!) @auth(level: USER) {

post_insert(data: $data)

}

# Yazı güncelle (sadece yazar)

mutation UpdatePost(

$postId: UUID!,

$title: String,

$content: String,

$status: PostStatus

) @auth(expr: "auth.uid == this.author.id") {

post_update(

id: $postId,

data: {

title: $title,

content: $content,

status: $status,

updatedAt_expr: "request.time"

}

)

}

# Yorum ekle

mutation AddComment(

$postId: UUID!,

$content: String!,

$parentCommentId: UUID

) @auth(level: USER) {

comment_insert(data: {

content: $content,

post: { id: $postId },

author: { id_expr: "auth.uid" },

parentComment: { id: $parentCommentId }

})

}

# Beğeni toggle

mutation ToggleLike($postId: UUID!) @auth(level: USER) {

like_upsert(data: {

user: { id_expr: "auth.uid" },

post: { id: $postId }

})

}

# Yazı sil (soft delete → ARCHIVED)

mutation ArchivePost($postId: UUID!) @auth(expr: "auth.uid == this.author.id") {

post_update(

id: $postId,

data: { status: ARCHIVED }

)

}

\\\`

🔍 Pro Tip: Mutation'larda \@auth\ direktifini MUTLAKA kullan. \level: USER\ giriş yapmış herkes, \expr: "auth.uid == this.author.id"\ sadece kaynak sahibi. Güvenlik sıfırdan düşünülmeli. iOS Security yazımızdaki prensipleri backend'e de uygula.

Auto-Generated SDK'lar

Data Connect'in en büyük avantajı: query ve mutation tanımlarından otomatik olarak iOS (Swift), Android (Kotlin), Flutter (Dart) ve Web (TypeScript) SDK'ları üretmesi.

SDK Oluşturma

\\\`bash

# SDK'ları oluştur

firebase dataconnect:sdk:generate

# Çıktı:

# ├── ios/ → Swift SDK

# ├── android/ → Kotlin SDK

# ├── dart/ → Flutter SDK

# └── web/ → TypeScript SDK

\\\`

TypeScript SDK Kullanımı

\\\`typescript

// Auto-generated — elle düzenleme YAPMA

import {

listPublishedPosts,

getPostBySlug,

createPost,

toggleLike,

ListPublishedPostsData,

} from '@firebasegen/my-connector';

// Type-safe query

const posts: ListPublishedPostsData = await listPublishedPosts({

limit: 10,

offset: 0,

categorySlug: 'typescript',

});

// posts.posts[0].title → string (type-safe!)

// posts.posts[0].author.displayName → string

// Type-safe mutation

await createPost({

data: {

title: 'Yeni Yazı',

slug: 'yeni-yazi',

content: 'İçerik...',

status: 'DRAFT',

// author otomatik: auth.uid

},

});

\\\`


iOS / Swift Entegrasyonu

Swift SDK Kullanımı

\\\`swift

import FirebaseDataConnect

// Auto-generated connector

let connector = DataConnect.dataConnect(

connectorConfig: ConnectorConfig(

serviceId: "my-service",

location: "us-central1"

)

)

// Type-safe query

func loadPosts(category: String? = nil) async throws -> [Post] {

let result = try await ListPublishedPostsQuery

.ref(limit: 10, offset: 0, categorySlug: category)

.execute()

return result.data.posts.map { post in

Post(

id: post.id,

title: post.title,

slug: post.slug,

excerpt: post.excerpt ?? "",

authorName: post.author.displayName,

categoryName: post.category?.name ?? "Genel"

)

}

}

// Type-safe mutation

func createNewPost(title: String, content: String) async throws {

try await CreatePostMutation

.ref(data: Post_Data(

title: title,

slug: title.slugified(),

content: content,

status: .draft

))

.execute()

}

// Beğeni toggle

func toggleLike(postId: UUID) async throws {

try await ToggleLikeMutation

.ref(postId: postId)

.execute()

}

\\\`


Flutter Entegrasyonu

Dart SDK Kullanımı

\\\`dart

import 'package:firebase_data_connect/firebase_data_connect.dart';

import 'package:my_connector/my_connector.dart';

class PostRepository {

final _connector = MyConnector.instance;

// Yayınlanmış yazıları getir

Future<List<Post>> getPublishedPosts({

int limit = 10,

int offset = 0,

String? categorySlug,

}) async {

final result = await _connector.listPublishedPosts

.ref(

limit: limit,

offset: offset,

categorySlug: categorySlug,

)

.execute();

return result.data.posts.map((p) => Post(

id: p.id,

title: p.title,

slug: p.slug,

excerpt: p.excerpt ?? '',

authorName: p.author.displayName,

viewCount: p.viewCount,

)).toList();

}

// Slug ile tek yazı

Future<PostDetail?> getPostBySlug(String slug) async {

final result = await _connector.getPostBySlug

.ref(slug: slug)

.execute();

final posts = result.data.post;

if (posts.isEmpty) return null;

final p = posts.first;

return PostDetail(

id: p.id,

title: p.title,

content: p.content,

author: Author(

name: p.author.displayName,

photo: p.author.photoUrl,

),

comments: p.comments.map((c) => Comment(

content: c.content,

authorName: c.author.displayName,

createdAt: c.createdAt,

)).toList(),

);

}

}

\\\`

🔍 Pro Tip: Flutter Clean Architecture yazımızdaki repository pattern ile Data Connect SDK'yı sarmala. Böylece veri kaynağını (Firestore vs Data Connect) kolayca değiştirebilirsin. Domain layer SDK detaylarından habersiz kalır.

Authorization ve Güvenlik

Data Connect, Firebase Auth ile entegre güçlü bir authorization sistemi sunar:

Auth Seviyeleri

Seviye
Açıklama
Kullanım
\`PUBLIC\`
Herkes erişebilir
Blog listesi, ürün kataloğu
\`USER_ANON\`
Anonim dahil tüm auth
Rate limiting gereken yerlerde
\`USER\`
Email doğrulanmış kullanıcı
Yorum yazma, beğeni
\`USER_EMAIL_VERIFIED\`
Email doğrulanmış
Hassas işlemler
\`NO_ACCESS\`
Sadece admin SDK
Server-side işlemler

CEL Expression ile Detaylı Kontrol

\\\`graphql

# Sadece kaynak sahibi düzenleyebilir

mutation UpdateProfile($data: User_Data!)

@auth(expr: "auth.uid == vars.data.id") {

user_update(data: $data)

}

# Admin rolü kontrolü

mutation DeletePost($postId: UUID!)

@auth(expr: "'admin' in auth.token.roles") {

post_delete(id: $postId)

}

# Zaman bazlı kontrol (son 24 saat)

mutation EditComment($commentId: UUID!, $content: String!)

@auth(expr: "auth.uid == this.author.id && (request.time - this.createdAt) < duration('24h')") {

comment_update(id: $commentId, data: { content: $content })

}

\\\`


Firestore vs Data Connect

Her iki servisi de production'da kullanan biri olarak karşılaştıralım:

Kriter
Firestore
Data Connect
**Veri Modeli**
NoSQL (Document)
İlişkisel (SQL)
**Schema**
Schema-less
Schema-first (GraphQL)
**Query**
Collection query
SQL gücünde GraphQL
**JOIN**
Yok (denormalizasyon)
Native JOIN
**Aggregation**
count() (sınırlı)
SUM, AVG, COUNT, GROUP BY
**Real-time**
Snapshot listener
Henüz sınırlı
**Type Safety**
Zayıf
Tam (auto-generated SDK)
**Offline**
Güçlü cache
Henüz yok
**Migration**
Manuel
SQL migration
**Fiyat**
Read/write/delete başına
Cloud SQL instance + query
**Ölçeklenme**
Otomatik
Instance boyutunu ayarla

Ne Zaman Hangisi?

  • Firestore:: Real-time sync, offline-first, basit veri modeli, hızlı prototipleme
  • Data Connect:: İlişkisel veri, karmaşık sorgular, type safety, SQL bilgisi olan ekip

Production Best Practices

1. Schema Migration

\\\`bash

# Schema değişikliği yaptıktan sonra

firebase dataconnect:sql:migrate --force

# Migration SQL'ini görmek için

firebase dataconnect:sql:diff

\\\`

2. Query Optimizasyonu

\\\`graphql

# KÖTÜ: Tüm alanları çek (over-fetching)

query GetPosts @auth(level: PUBLIC) {

posts {

id title slug content excerpt status

publishedAt viewCount createdAt updatedAt

author { id displayName email photoUrl bio }

comments { id content author { id displayName } }

}

}

# İYİ: Sadece ihtiyacın olan alanlar

query GetPostCards @auth(level: PUBLIC) {

posts(

where: { status: { eq: PUBLISHED } }

limit: 10

) {

id title slug excerpt

author { displayName }

category { name }

}

}

\\\`

3. Index Stratejisi

\\\`graphql

# Schema'da index tanımla

type Post @table @index(fields: ["status", "publishedAt"]) {

# ...

}

# Composite index

type Comment @table @index(fields: ["post", "createdAt"]) {

# ...

}

\\\`


Sonuç ve Öneriler

Firebase Data Connect, Firebase ekosisteminin en büyük eksikliğini kapatıyor: ilişkisel veri yönetimi. GraphQL schema-first yaklaşımı, auto-generated type-safe SDK'lar ve Cloud SQL'in gücü ile production uygulamaları için güçlü bir seçenek. GraphQL Mobile yazımızdaki GraphQL best practice'leri burada da geçerli. Firebase Advanced ile birleştirince Firebase ekosisteminin tam gücünü kullanırsın.

Öneriler

  1. İlişkisel veri? Data Connect kullan — Firestore'da JOIN yapma çabası boşa
  2. Schema-first düşün — Önce veri modelini, sonra UI'ı tasarla
  3. SDK'yı repository pattern ile sarmala — Veri kaynağı değişimine hazır ol. Swift 6 Yenilikler yazımızdaki concurrency pattern'leri ile güçlendir
  4. Auth direktiflerini her yere koy — Güvenlik varsayılan olmalı
  5. Firestore ile birlikte kullan — Real-time için Firestore, ilişkisel için Data Connect

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Data Connect + Firestore hibrit pattern: İlişkisel verileri (kullanıcılar, siparişler, ürünler) Data Connect'te tut, real-time ihtiyaçları (chat mesajları, online durum, bildirimler) Firestore'da tut. İki servisi köprüleyen bir Cloud Function yaz — Data Connect'te sipariş oluşunca Firestore'a real-time bildirim yaz. Bu hibrit yaklaşım, her iki dünyanın en iyisini sunar.

Etiketler

#Firebase#GraphQL#PostgreSQL#Cloud SQL#Type Safety#SDK#Backend
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz