AWS AppSync ile GraphQL API Kurulumu ve Yönetimi
Bulut altyapısı yönetirken REST API’lerin getirdiği over-fetching ve under-fetching sorunlarıyla boğuştuysanız, AWS AppSync tam da aradığınız çözüm olabilir. GraphQL tabanlı bu yönetilen servis, özellikle birden fazla veri kaynağını tek bir endpoint üzerinden sunmanız gerektiğinde hayat kurtarıyor. Bu yazıda sıfırdan bir AppSync GraphQL API kurulumu yapacağız, gerçek dünya senaryolarıyla pekiştireceğiz ve production ortamına hazır hale getireceğiz.
AWS AppSync Nedir ve Ne Zaman Kullanmalısınız
AppSync, AWS’nin yönetilen GraphQL servisidir. Altında DynamoDB, Lambda, RDS, HTTP endpoint veya Elasticsearch gibi farklı veri kaynaklarını bağlayabilirsiniz. En güçlü özelliklerinden biri gerçek zamanlı subscription desteği ve offline veri senkronizasyonu sunmasıdır.
Şu senaryolarda AppSync tercih edilir:
- Mobil ve web uygulamalarında birden fazla mikroservisten veri çekmeniz gerektiğinde
- Gerçek zamanlı özellikler (chat, canlı dashboard) geliştiriyorsanız
- Farklı istemci tipleri aynı API’yi farklı veri ihtiyaçlarıyla kullanıyorsa
- Offline-first mobil uygulama geliştiriyorsanız
Saf REST ile yapılabilecek basit CRUD operasyonları için AppSync overkill olabilir. Ama karmaşık veri ilişkileri ve çoklu kaynak senaryolarında ciddi zaman kazandırır.
Ön Gereksinimler
Başlamadan önce şunlara ihtiyacınız var:
- AWS CLI kurulu ve yapılandırılmış bir ortam
- Node.js 18+ (test scriptleri için)
- Yeterli IAM yetkilerine sahip bir AWS hesabı
- Temel GraphQL bilgisi
AWS CLI’yi yapılandıralım:
aws configure
# AWS Access Key ID: [your-key]
# AWS Secret Access Key: [your-secret]
# Default region name: eu-west-1
# Default output format: json
# Mevcut kimliği doğrulayalım
aws sts get-caller-identity
DynamoDB Tablo Oluşturma
Örnek senaryomuzda bir blog uygulaması geliştireceğiz. Yazılar ve yorumlar için DynamoDB tabloları oluşturalım.
# Posts tablosu
aws dynamodb create-table
--table-name BlogPosts
--attribute-definitions
AttributeName=id,AttributeType=S
AttributeName=createdAt,AttributeType=S
--key-schema
AttributeName=id,KeyType=HASH
AttributeName=createdAt,KeyType=RANGE
--billing-mode PAY_PER_REQUEST
--region eu-west-1
# Comments tablosu
aws dynamodb create-table
--table-name BlogComments
--attribute-definitions
AttributeName=id,AttributeType=S
AttributeName=postId,AttributeType=S
--key-schema
AttributeName=id,KeyType=HASH
--global-secondary-indexes
'[{
"IndexName": "PostIdIndex",
"KeySchema": [
{"AttributeName": "postId", "KeyType": "HASH"}
],
"Projection": {"ProjectionType": "ALL"}
}]'
--billing-mode PAY_PER_REQUEST
--region eu-west-1
Tablolar oluşturuldu mu kontrol edelim:
aws dynamodb list-tables --region eu-west-1
aws dynamodb describe-table --table-name BlogPosts --region eu-west-1
--query 'Table.TableStatus'
IAM Rolü Oluşturma
AppSync’in DynamoDB ve CloudWatch Logs’a erişebilmesi için bir IAM rolü gerekiyor. Bu adımı atlamak en sık yapılan hatalardan biridir.
# Trust policy dosyası oluştur
cat > appsync-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# Rolü oluştur
aws iam create-role
--role-name AppSyncBlogRole
--assume-role-policy-document file://appsync-trust-policy.json
# DynamoDB erişim politikası ekle
aws iam attach-role-policy
--role-name AppSyncBlogRole
--policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
# CloudWatch Logs politikası ekle
aws iam attach-role-policy
--role-name AppSyncBlogRole
--policy-arn arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs
# Rol ARN'ini alın, sonraki adımda kullanacağız
aws iam get-role --role-name AppSyncBlogRole
--query 'Role.Arn' --output text
Production ortamında AmazonDynamoDBFullAccess yerine sadece gerekli tablolara izin veren özel bir politika kullanmanızı öneririm. Least privilege prensibi her zaman geçerli.
AppSync API Oluşturma
Şimdi asıl API’yi oluşturalım. Kimlik doğrulama için başlangıçta API_KEY kullanacağız, production’da Cognito veya IAM’a geçeceğiz.
# API oluştur
aws appsync create-graphql-api
--name "BlogAPI"
--authentication-type API_KEY
--region eu-west-1
# API ID ve endpoint'i kaydedin
export API_ID=$(aws appsync list-graphql-apis
--region eu-west-1
--query 'graphqlApis[?name==`BlogAPI`].apiId'
--output text)
export API_ENDPOINT=$(aws appsync get-graphql-api
--api-id $API_ID
--region eu-west-1
--query 'graphqlApi.uris.GRAPHQL'
--output text)
echo "API ID: $API_ID"
echo "Endpoint: $API_ENDPOINT"
# API Key oluştur (30 gün geçerli)
aws appsync create-api-key
--api-id $API_ID
--description "Development API Key"
--expires $(date -d "+30 days" +%s)
--region eu-west-1
GraphQL Schema Tanımlama
Schema, API’nin kalbidir. Tip tanımları, query’ler, mutation’lar ve subscription’ları burada belirliyoruz.
cat > schema.graphql << 'EOF'
type Post {
id: ID!
title: String!
content: String!
author: String!
status: PostStatus!
tags: [String]
createdAt: AWSDateTime!
updatedAt: AWSDateTime
comments: [Comment]
}
type Comment {
id: ID!
postId: ID!
content: String!
author: String!
createdAt: AWSDateTime!
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
input CreatePostInput {
title: String!
content: String!
author: String!
status: PostStatus!
tags: [String]
}
input UpdatePostInput {
id: ID!
title: String
content: String
status: PostStatus
tags: [String]
}
input CreateCommentInput {
postId: ID!
content: String!
author: String!
}
type Query {
getPost(id: ID!): Post
listPosts(status: PostStatus, limit: Int, nextToken: String): PostConnection
getCommentsByPost(postId: ID!, limit: Int): [Comment]
}
type Mutation {
createPost(input: CreatePostInput!): Post
updatePost(input: UpdatePostInput!): Post
deletePost(id: ID!): Post
createComment(input: CreateCommentInput!): Comment
}
type Subscription {
onCreatePost: Post
@aws_subscribe(mutations: ["createPost"])
onCreateComment(postId: ID): Comment
@aws_subscribe(mutations: ["createComment"])
}
type PostConnection {
items: [Post]
nextToken: String
}
EOF
# Schema'yı AppSync'e yükle
aws appsync start-schema-creation
--api-id $API_ID
--definition fileb://schema.graphql
--region eu-west-1
# Schema oluşturma durumunu kontrol et
aws appsync get-schema-creation-status
--api-id $API_ID
--region eu-west-1
Veri Kaynakları Bağlama
Schema hazır, şimdi DynamoDB tablolarını veri kaynağı olarak bağlayalım.
export ROLE_ARN=$(aws iam get-role
--role-name AppSyncBlogRole
--query 'Role.Arn' --output text)
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity
--query 'Account' --output text)
# Posts veri kaynağı
aws appsync create-data-source
--api-id $API_ID
--name "BlogPostsTable"
--type AMAZON_DYNAMODB
--dynamodb-config '{
"tableName": "BlogPosts",
"awsRegion": "eu-west-1",
"useCallerCredentials": false
}'
--service-role-arn $ROLE_ARN
--region eu-west-1
# Comments veri kaynağı
aws appsync create-data-source
--api-id $API_ID
--name "BlogCommentsTable"
--type AMAZON_DYNAMODB
--dynamodb-config '{
"tableName": "BlogComments",
"awsRegion": "eu-west-1",
"useCallerCredentials": false
}'
--service-role-arn $ROLE_ARN
--region eu-west-1
Resolver Oluşturma
Resolver’lar, GraphQL field’larını veri kaynaklarına bağlayan mantık katmanıdır. AppSync, VTL (Velocity Template Language) veya JavaScript pipeline resolver kullanabilir. Burada JavaScript resolver kullanacağız çünkü daha okunabilir.
# createPost mutation resolver
cat > create-post-resolver.js << 'EOF'
import { util } from "@aws-appsync/utils";
export function request(ctx) {
const { input } = ctx.args;
const id = util.autoId();
const now = util.time.nowISO8601();
return {
operation: "PutItem",
key: {
id: { S: id },
createdAt: { S: now }
},
attributeValues: util.dynamodb.toMapValues({
...input,
id,
createdAt: now,
updatedAt: now
})
};
}
export function response(ctx) {
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type);
}
return ctx.result;
}
EOF
# getPost query resolver
cat > get-post-resolver.js << 'EOF'
import { util } from "@aws-appsync/utils";
export function request(ctx) {
return {
operation: "Query",
query: {
expression: "id = :id",
expressionValues: {
":id": util.dynamodb.toDynamoDB(ctx.args.id)
}
},
limit: 1
};
}
export function response(ctx) {
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type);
}
const items = ctx.result.items;
if (!items || items.length === 0) {
return null;
}
return items[0];
}
EOF
# Resolver'ları AppSync'e bağla
# Önce runtime config dosyası oluştur
aws appsync create-resolver
--api-id $API_ID
--type-name "Mutation"
--field-name "createPost"
--data-source-name "BlogPostsTable"
--runtime '{"name":"APPSYNC_JS","runtimeVersion":"1.0.0"}'
--code "$(cat create-post-resolver.js)"
--region eu-west-1
aws appsync create-resolver
--api-id $API_ID
--type-name "Query"
--field-name "getPost"
--data-source-name "BlogPostsTable"
--runtime '{"name":"APPSYNC_JS","runtimeVersion":"1.0.0"}'
--code "$(cat get-post-resolver.js)"
--region eu-west-1
Lambda ile Özel İş Mantığı
Bazen DynamoDB resolver yeterli olmaz. Örneğin post oluştururken otomatik email bildirimi göndermek istiyorsunuz. Bunun için Lambda veri kaynağı kullanılır.
# Lambda fonksiyonu için basit bir örnek
cat > post-notification.js << 'EOF'
const { SESClient, SendEmailCommand } = require("@aws-sdk/client-ses");
exports.handler = async (event) => {
const { field, arguments: args, identity } = event;
if (field === "createPost") {
const ses = new SESClient({ region: "eu-west-1" });
try {
await ses.send(new SendEmailCommand({
Source: "[email protected]",
Destination: {
ToAddresses: ["[email protected]"]
},
Message: {
Subject: { Data: `Yeni yazı: ${args.input.title}` },
Body: {
Text: {
Data: `${args.input.author} yeni bir yazı yayınladı: ${args.input.title}`
}
}
}
}));
} catch (err) {
console.error("Email gönderilemedi:", err);
}
}
return event.arguments;
};
EOF
# Lambda fonksiyonunu zip'le ve yükle
zip post-notification.zip post-notification.js
aws lambda create-function
--function-name PostNotificationHandler
--runtime nodejs18.x
--role $ROLE_ARN
--handler post-notification.handler
--zip-file fileb://post-notification.zip
--region eu-west-1
# Lambda'yı AppSync veri kaynağı olarak ekle
LAMBDA_ARN=$(aws lambda get-function
--function-name PostNotificationHandler
--query 'Configuration.FunctionArn'
--output text --region eu-west-1)
aws appsync create-data-source
--api-id $API_ID
--name "PostNotificationLambda"
--type AWS_LAMBDA
--lambda-config "{"lambdaFunctionArn": "$LAMBDA_ARN"}"
--service-role-arn $ROLE_ARN
--region eu-west-1
API Testi
Kurulum tamamlandı, şimdi test edelim. GraphQL sorguları için curl veya Node.js kullanabiliriz.
export API_KEY=$(aws appsync list-api-keys
--api-id $API_ID
--region eu-west-1
--query 'apiKeys[0].id'
--output text)
# Post oluşturma mutation testi
curl -X POST $API_ENDPOINT
-H "Content-Type: application/json"
-H "x-api-key: $API_KEY"
-d '{
"query": "mutation CreatePost($input: CreatePostInput!) { createPost(input: $input) { id title author status createdAt } }",
"variables": {
"input": {
"title": "AppSync ile GraphQL API Kurulumu",
"content": "Bu yazıda AWS AppSync kullanımını ele alıyoruz.",
"author": "[email protected]",
"status": "PUBLISHED",
"tags": ["aws", "graphql", "appsync"]
}
}
}' | jq .
# Post listeleme query testi
curl -X POST $API_ENDPOINT
-H "Content-Type: application/json"
-H "x-api-key: $API_KEY"
-d '{
"query": "query ListPosts { listPosts(status: PUBLISHED, limit: 10) { items { id title author status createdAt } nextToken } }"
}' | jq .
Dönen yanıt başarılıysa şöyle görünecektir:
# Beklenen başarılı yanıt yapısı
# {
# "data": {
# "createPost": {
# "id": "uuid-generated",
# "title": "AppSync ile GraphQL API Kurulumu",
# "author": "[email protected]",
# "status": "PUBLISHED",
# "createdAt": "2024-01-15T10:30:00.000Z"
# }
# }
# }
# Hata durumunda CloudWatch Logs'u kontrol edin
aws logs describe-log-groups
--log-group-name-prefix "/aws/appsync"
--region eu-west-1
aws logs get-log-events
--log-group-name "/aws/appsync/apis/$API_ID"
--log-stream-name $(aws logs describe-log-streams
--log-group-name "/aws/appsync/apis/$API_ID"
--query 'logStreams[0].logStreamName'
--output text --region eu-west-1)
--region eu-west-1
Güvenlik ve Production Hazırlığı
API Key, sadece geliştirme ortamı için uygundur. Production için Cognito User Pool entegrasyonu yapın.
# Mevcut API'ye Cognito auth ekle
COGNITO_POOL_ARN="arn:aws:cognito-idp:eu-west-1:$AWS_ACCOUNT_ID:userpool/eu-west-1_XXXXXXXXX"
aws appsync update-graphql-api
--api-id $API_ID
--name "BlogAPI"
--authentication-type AMAZON_COGNITO_USER_POOLS
--user-pool-config "{
"userPoolId": "eu-west-1_XXXXXXXXX",
"awsRegion": "eu-west-1",
"defaultAction": "ALLOW"
}"
--additional-authentication-providers '[{
"authenticationType": "API_KEY"
}]'
--region eu-west-1
Schema üzerinde field seviyesinde yetkilendirme için directive’ler kullanın:
- @aws_cognito_user_pools: Sadece Cognito kullanıcıları erişebilir
- @aws_api_key: API key ile erişim açık
- @aws_auth(cognito_groups: [“Admin”]): Sadece belirli Cognito grubu erişebilir
CloudWatch Alarmleri ve Monitoring
Production’da körü körüne çalışamazsınız. Temel metrikleri izleyin.
# 4XX hata oranı için alarm
aws cloudwatch put-metric-alarm
--alarm-name "AppSync-4XX-Errors"
--alarm-description "AppSync API 4XX hata oranı yüksek"
--metric-name "4XXError"
--namespace "AWS/AppSync"
--statistic Sum
--period 300
--threshold 10
--comparison-operator GreaterThanThreshold
--dimensions Name=GraphQLAPIId,Value=$API_ID
--evaluation-periods 2
--alarm-actions "arn:aws:sns:eu-west-1:$AWS_ACCOUNT_ID:sysadmin-alerts"
--region eu-west-1
# Latency alarmı
aws cloudwatch put-metric-alarm
--alarm-name "AppSync-High-Latency"
--alarm-description "AppSync API yanıt süresi 2 saniyeyi aştı"
--metric-name "Latency"
--namespace "AWS/AppSync"
--statistic p99
--period 300
--threshold 2000
--comparison-operator GreaterThanThreshold
--dimensions Name=GraphQLAPIId,Value=$API_ID
--evaluation-periods 3
--alarm-actions "arn:aws:sns:eu-west-1:$AWS_ACCOUNT_ID:sysadmin-alerts"
--region eu-west-1
Sık Karşılaşılan Sorunlar
Resolver bağlantı sorunlarında kontrol listesi:
- IAM rolü yanlış: AppSync rolünün DynamoDB tablonuza erişimi var mı?
aws iam simulate-principal-policyile test edin - Schema ve resolver uyumsuzluğu: Field adları case-sensitive’dir, typo’lara dikkat
- VTL/JS syntax hatası: AppSync konsolundaki resolver test aracını kullanın
- DynamoDB key schema hatası: Query yaparken partition key ve sort key birlikte kullanılması gerekir
- Timeout: Lambda resolver’lar için default timeout 30 saniyedir, uzun işlemler için artırın
CloudWatch Logs’ta resolver hatalarını görmek için log level’ı artırın:
aws appsync update-graphql-api
--api-id $API_ID
--name "BlogAPI"
--log-config '{
"fieldLogLevel": "ALL",
"cloudWatchLogsRoleArn": "'$ROLE_ARN'",
"excludeVerboseContent": false
}'
--authentication-type API_KEY
--region eu-west-1
Sonuç
AWS AppSync ile GraphQL API kurulumu ilk bakışta karmaşık görünse de adım adım gittiğinizde oldukça sistematik bir yapısı var. DynamoDB tabloları, IAM rolleri, schema tanımı ve resolver’lar zinciri doğru kurulduğunda çok güçlü bir API altyapısına sahip oluyorsunuz.
Burada anlattığım yapıyı gerçek projemde kullandım ve REST’e kıyasla client tarafındaki veri yönetimi kodumuz yüzde altmış azaldı. Over-fetching sorunu tamamen ortadan kalktı, özellikle mobil uygulamamızda bant genişliği tasarrufu gözle görülür hale geldi.
Bir sonraki adım olarak şunları incelemenizi öneririm:
- AWS Amplify ile AppSync’i frontend’e bağlamak
- AppSync Merged APIs ile çoklu mikro API’leri birleştirmek
- Caching özelliği ile resolver yanıtlarını cache’lemek
- WAF entegrasyonu ile API’yi dış tehditlere karşı korumak
Sorularınız veya farklı bir senaryo için çalışmayan bir kısım varsa yorumlarda belirtin, elimden geldiğince yardımcı olmaya çalışırım.
