Skip to content

GraphQL

Giriş

GraphQL, Facebook tarafından geliştirilen modern bir API query language'dır. REST API'lerin sınırlamalarını aşarak, client'ların ihtiyaç duydukları veriyi tam olarak almalarını sağlar.

GraphQL vs REST

REST API Sınırlamaları

# REST API - Over-fetching örneği
GET /api/users/123
Response: {
  "id": 123,
  "name": "Ahmet",
  "email": "ahmet@example.com",
  "phone": "+90 555 123 4567",
  "address": "İstanbul, Türkiye",
  "birthDate": "1990-01-01",
  "profilePicture": "https://...",
  "lastLogin": "2024-01-15T10:30:00Z"
}

# Client sadece name ve email istiyor ama tüm veri geliyor

GraphQL Çözümü

# GraphQL Query - Sadece ihtiyaç duyulan veri
query GetUserBasicInfo($id: ID!) {
  user(id: $id) {
    name
    email
  }
}

# Response - Sadece istenen veri
{
  "data": {
    "user": {
      "name": "Ahmet",
      "email": "ahmet@example.com"
    }
  }
}

GraphQL Temel Kavramlar

Schema Definition

# Schema definition
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  profile: Profile
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  createdAt: DateTime!
}

type Profile {
  bio: String
  avatar: String
  website: String
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
  posts(authorId: ID): [Post!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}

input UpdateUserInput {
  name: String
  email: String
  bio: String
}

Resolvers

// ASP.NET Core GraphQL resolver örneği
public class UserResolver
{
    private readonly IUserService _userService;
    private readonly IPostService _postService;

    public UserResolver(IUserService userService, IPostService postService)
    {
        _userService = userService;
        _postService = postService;
    }

    public async Task<User> GetUser(string id)
    {
        return await _userService.GetByIdAsync(id);
    }

    public async Task<IEnumerable<Post>> GetUserPosts(User user, int? limit = null)
    {
        var posts = await _postService.GetByAuthorIdAsync(user.Id);

        if (limit.HasValue)
            posts = posts.Take(limit.Value);

        return posts;
    }

    public async Task<Profile> GetUserProfile(User user)
    {
        return await _userService.GetProfileAsync(user.Id);
    }
}

GraphQL Implementation (.NET)

HotChocolate Setup

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// GraphQL services
builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddMutationType<Mutation>()
    .AddType<UserType>()
    .AddType<PostType>()
    .AddType<ProfileType>()
    .AddDataLoader<UserDataLoader>()
    .AddFiltering()
    .AddSorting()
    .AddProjections();

var app = builder.Build();

// GraphQL endpoint
app.MapGraphQL();

app.Run();

Type Definitions

public class UserType : ObjectType<User>
{
    protected override void Configure(IObjectTypeDescriptor<User> descriptor)
    {
        descriptor.Field(u => u.Id).Type<NonNullType<IdType>>();
        descriptor.Field(u => u.Name).Type<NonNullType<StringType>>();
        descriptor.Field(u => u.Email).Type<NonNullType<StringType>>();

        descriptor.Field("posts")
            .ResolveWith<UserResolver>(r => r.GetUserPosts(default!, default!))
            .UseDbContext<ApplicationDbContext>()
            .UsePaging()
            .UseFiltering()
            .UseSorting();

        descriptor.Field("profile")
            .ResolveWith<UserResolver>(r => r.GetUserProfile(default!))
            .UseDbContext<ApplicationDbContext>();
    }
}

Query Implementation

public class Query
{
    public async Task<User?> GetUser(string id, [Service] IUserService userService)
    {
        return await userService.GetByIdAsync(id);
    }

    public async Task<IEnumerable<User>> GetUsers(
        int? limit = null, 
        int? offset = null,
        [Service] IUserService userService)
    {
        var users = await userService.GetAllAsync();

        if (offset.HasValue)
            users = users.Skip(offset.Value);

        if (limit.HasValue)
            users = users.Take(limit.Value);

        return users;
    }

    public async Task<Post?> GetPost(string id, [Service] IPostService postService)
    {
        return await postService.GetByIdAsync(id);
    }
}

Mutation Implementation

public class Mutation
{
    public async Task<User> CreateUser(
        CreateUserInput input,
        [Service] IUserService userService)
    {
        var user = new User
        {
            Name = input.Name,
            Email = input.Email,
            PasswordHash = HashPassword(input.Password)
        };

        return await userService.CreateAsync(user);
    }

    public async Task<User> UpdateUser(
        string id,
        UpdateUserInput input,
        [Service] IUserService userService)
    {
        var user = await userService.GetByIdAsync(id);
        if (user == null)
            throw new UserNotFoundException(id);

        if (input.Name != null)
            user.Name = input.Name;

        if (input.Email != null)
            user.Email = input.Email;

        if (input.Bio != null)
            user.Bio = input.Bio;

        return await userService.UpdateAsync(user);
    }
}

DataLoader Pattern

N+1 Problem Çözümü

public class UserDataLoader : BatchDataLoader<string, User>
{
    private readonly IUserService _userService;

    public UserDataLoader(IUserService userService, IBatchScheduler batchScheduler)
        : base(batchScheduler)
    {
        _userService = userService;
    }

    protected override async Task<IReadOnlyDictionary<string, User>> LoadBatchAsync(
        IReadOnlyList<string> keys, CancellationToken cancellationToken)
    {
        var users = await _userService.GetByIdsAsync(keys);
        return users.ToDictionary(u => u.Id);
    }
}

// Resolver'da kullanım
public async Task<IEnumerable<Post>> GetUserPosts(User user, int? limit = null)
{
    var posts = await _postService.GetByAuthorIdAsync(user.Id);

    if (limit.HasValue)
        posts = posts.Take(limit.Value);

    return posts;
}

GraphQL Subscriptions

Real-time Updates

public class Subscription
{
    [Subscribe]
    [Topic("UserCreated")]
    public User OnUserCreated([EventMessage] User user)
    {
        return user;
    }

    [Subscribe]
    [Topic("PostUpdated")]
    public Post OnPostUpdated([EventMessage] Post post)
    {
        return post;
    }
}

// Publisher
public class UserService
{
    private readonly ITopicEventSender _eventSender;

    public async Task<User> CreateUser(CreateUserInput input)
    {
        var user = new User { /* ... */ };
        await _context.Users.AddAsync(user);
        await _context.SaveChangesAsync();

        // Publish event
        await _eventSender.SendAsync("UserCreated", user);

        return user;
    }
}

Performance Optimization

Query Complexity Analysis

// Program.cs'de complexity limit
builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddMutationType<Mutation>()
    .AddComplexityAnalyzer(options =>
    {
        options.MaximumAllowedComplexity = 100;
        options.MaximumAllowedDepth = 10;
    });

Caching

// Redis cache integration
builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddRedisQueryStorage(options =>
    {
        options.ConnectionMultiplexerFactory = sp =>
            Task.FromResult(ConnectionMultiplexer.Connect("localhost:6379"));
    });

Mülakat Soruları

Temel Sorular

  1. GraphQL nedir ve REST'e göre avantajları nelerdir?
  2. Cevap: Query language, over-fetching/under-fetching'i önler, single endpoint, strong typing, real-time updates.

  3. GraphQL'de N+1 problem nedir?

  4. Cevap: Multiple database queries, DataLoader pattern ile çözülür, batching ve caching kullanılır.

  5. GraphQL schema nedir?

  6. Cevap: API'nin contract'ı, type definitions, queries, mutations ve subscriptions tanımlar.

  7. Resolver nedir ve nasıl çalışır?

  8. Cevap: GraphQL field'larının nasıl resolve edileceğini tanımlayan fonksiyonlar.

  9. GraphQL vs REST performance karşılaştırması nasıldır?

  10. Cevap: GraphQL single request, REST multiple requests. Caching ve batching önemli.

Teknik Sorular

  1. GraphQL'de introspection nedir?
  2. Cevap: Schema'yı query edebilme, GraphQL Playground, documentation generation.

  3. GraphQL'de error handling nasıl yapılır?

  4. Cevap: Errors array, partial results, proper error codes ve messages.

  5. GraphQL'de authentication nasıl implement edilir?

  6. Cevap: HTTP headers, context, directives, custom middleware.

  7. GraphQL'de caching stratejileri nelerdir?

  8. Cevap: HTTP caching, application-level caching, Redis integration, query result caching.

  9. GraphQL'de rate limiting nasıl uygulanır?

  10. Cevap: Query complexity analysis, depth limiting, field limiting, custom middleware.

Best Practices

  1. Schema Design
  2. Meaningful type names kullanın
  3. Proper nullability tanımlayın
  4. Input types kullanın
  5. Documentation ekleyin

  6. Performance

  7. DataLoader pattern kullanın
  8. Query complexity limitleri ayarlayın
  9. Caching implement edin
  10. Pagination kullanın

  11. Security

  12. Authentication implement edin
  13. Authorization kontrol edin
  14. Input validation yapın
  15. Rate limiting uygulayın

  16. Error Handling

  17. Consistent error formatları kullanın
  18. Proper error codes verin
  19. User-friendly messages yazın
  20. Logging yapın

Kaynaklar