Skip to content

Gelişmiş Ağ Desenleri

Gelişmiş ağ desenleri, yalnızca polling yetersiz kaldığında kullanılmalıdır. Mobilde her kalıcı bağlantı batarya, ağ değişimi ve lifecycle maliyeti getirir.

Hızlı Karar

İhtiyaçDesenDikkat
Çift yönlü canlı iletişimWebSocketReconnect ve auth yenileme gerekir
Tek yönlü güncellemeSSE veya pushArka plan kısıtları var
Seyrek veri yenilemePolling + cacheSürekli bağlantı gereksiz
Kritik teslimatAck + queueOffline tekrar senaryosu şart

Üretim Kontrol Listesi

  • Problem: Bu desen hangi kullanıcı gecikmesini veya tutarsızlığı çözüyor?
  • Çözüm: Connection lifecycle, retry/backoff, auth refresh, offline queue ve fallback net mi?
  • Trade-off: Daha canlı deneyim, daha fazla batarya ve sunucu state maliyeti getirir.
  • Hata durumu: Reconnect storm, duplicate message, stale token ve network switch ele alınmalı.
  • Ölçüm: Connection count, message latency, reconnect rate, delivery success ve battery impact izlenmeli.
  • Güvenlik/maliyet: Kanal yetkisi mesaj bazında korunmalı; açık bağlantılar altyapı maliyeti üretir.

WebSocket Uygulaması

Bağlantı Yönetimi

WebSocket bağlantıları long-running connections oldukları için dikkatli bir connection management gerektirir:

Kalp Atışı Mekanizması

javascript
class WebSocketManager {
  constructor(url) {
    this.url = url;
    this.heartbeatInterval = 30000; // 30 saniye
    this.heartbeatTimer = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('WebSocket bağlantısı kuruldu');
      this.startHeartbeat();
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === 'pong') {
        console.log('Heartbeat yanıtı alındı');
      } else {
        this.handleMessage(message);
      }
    };

    this.ws.onclose = () => {
      console.log('WebSocket bağlantısı kapandı');
      this.stopHeartbeat();
      this.reconnect();
    };
  }

  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, this.heartbeatInterval);
  }

  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
}

Reconnection Strategy

javascript
class ReconnectingWebSocket {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      maxReconnectAttempts: 5,
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      ...options
    };
    this.reconnectAttempts = 0;
  }

  reconnect() {
    if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
      const timeout = this.options.reconnectInterval * 
        Math.pow(this.options.reconnectDecay, this.reconnectAttempts);
      
      setTimeout(() => {
        console.log(`Yeniden bağlanma denemesi: ${this.reconnectAttempts + 1}`);
        this.reconnectAttempts++;
        this.connect();
      }, Math.min(timeout, this.options.maxReconnectInterval));
    } else {
      console.error('Maksimum yeniden bağlanma denemesi aşıldı');
    }
  }
}

Connection Pooling

javascript
class WebSocketPool {
  constructor(baseUrl, poolSize = 3) {
    this.baseUrl = baseUrl;
    this.poolSize = poolSize;
    this.connections = [];
    this.currentIndex = 0;
  }

  initialize() {
    for (let i = 0; i < this.poolSize; i++) {
      const ws = new ReconnectingWebSocket(`${this.baseUrl}/${i}`);
      this.connections.push(ws);
    }
  }

  getConnection() {
    const connection = this.connections[this.currentIndex];
    this.currentIndex = (this.currentIndex + 1) % this.poolSize;
    return connection;
  }

  broadcast(message) {
    this.connections.forEach(connection => {
      if (connection.readyState === WebSocket.OPEN) {
        connection.send(JSON.stringify(message));
      }
    });
  }
}

Message Handling

Message Queuing

javascript
class MessageQueue {
  constructor() {
    this.queue = [];
    this.processing = false;
  }

  enqueue(message) {
    this.queue.push({
      ...message,
      timestamp: Date.now(),
      retries: 0
    });
    
    if (!this.processing) {
      this.process();
    }
  }

  async process() {
    this.processing = true;
    
    while (this.queue.length > 0) {
      const message = this.queue.shift();
      
      try {
        await this.sendMessage(message);
      } catch (error) {
        console.error('Mesaj gönderimi başarısız:', error);
        
        if (message.retries < 3) {
          message.retries++;
          this.queue.unshift(message); // Tekrar kuyruğun başına ekle
        } else {
          console.error('Mesaj maksimum deneme sayısını aştı:', message);
        }
      }
    }
    
    this.processing = false;
  }

  async sendMessage(message) {
    return new Promise((resolve, reject) => {
      if (this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify(message));
        resolve();
      } else {
        reject(new Error('WebSocket bağlantısı kapalı'));
      }
    });
  }
}

Priority-Based Delivery

javascript
class PriorityMessageQueue {
  constructor() {
    this.highPriorityQueue = [];
    this.normalPriorityQueue = [];
    this.lowPriorityQueue = [];
  }

  enqueue(message, priority = 'normal') {
    const queuedMessage = {
      ...message,
      timestamp: Date.now(),
      priority
    };

    switch (priority) {
      case 'high':
        this.highPriorityQueue.push(queuedMessage);
        break;
      case 'low':
        this.lowPriorityQueue.push(queuedMessage);
        break;
      default:
        this.normalPriorityQueue.push(queuedMessage);
    }

    this.processNext();
  }

  processNext() {
    let nextMessage;
    
    if (this.highPriorityQueue.length > 0) {
      nextMessage = this.highPriorityQueue.shift();
    } else if (this.normalPriorityQueue.length > 0) {
      nextMessage = this.normalPriorityQueue.shift();
    } else if (this.lowPriorityQueue.length > 0) {
      nextMessage = this.lowPriorityQueue.shift();
    }

    if (nextMessage) {
      this.sendMessage(nextMessage);
    }
  }
}

Platform-Specific WebSocket Implementation

Android WebSocket

kotlin
class AndroidWebSocketManager {
    private lateinit var webSocket: WebSocket
    private val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()

    fun connect(url: String) {
        val request = Request.Builder()
            .url(url)
            .build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                Log.d("WebSocket", "Bağlantı kuruldu")
                startHeartbeat()
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                handleMessage(text)
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                Log.d("WebSocket", "Bağlantı kapanıyor: $reason")
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                Log.e("WebSocket", "Bağlantı hatası", t)
                scheduleReconnect()
            }
        })
    }

    private fun startHeartbeat() {
        val handler = Handler(Looper.getMainLooper())
        val heartbeatRunnable = object : Runnable {
            override fun run() {
                webSocket.send("""{"type":"ping"}""")
                handler.postDelayed(this, 30000)
            }
        }
        handler.post(heartbeatRunnable)
    }
}

iOS WebSocket

swift
class iOSWebSocketManager: NSObject, URLSessionWebSocketDelegate {
    private var webSocketTask: URLSessionWebSocketTask?
    private var urlSession: URLSession?
    private var heartbeatTimer: Timer?
    
    func connect(to url: URL) {
        urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        webSocketTask = urlSession?.webSocketTask(with: url)
        webSocketTask?.resume()
        
        startHeartbeat()
        receiveMessage()
    }
    
    private func startHeartbeat() {
        heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { _ in
            self.sendPing()
        }
    }
    
    private func sendPing() {
        let message = URLSessionWebSocketTask.Message.string("""{"type":"ping"}""")
        webSocketTask?.send(message) { error in
            if let error = error {
                print("Ping gönderimi başarısız: \(error)")
            }
        }
    }
    
    private func receiveMessage() {
        webSocketTask?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    self?.handleMessage(text)
                case .data(let data):
                    self?.handleData(data)
                @unknown default:
                    break
                }
                self?.receiveMessage() // Sonraki mesajı dinle
            case .failure(let error):
                print("Mesaj alma hatası: \(error)")
                self?.scheduleReconnect()
            }
        }
    }
}

GraphQL Optimizasyonu

Query Optimization

Field Selection

javascript
// Gereksiz field'ları almak yerine sadece ihtiyaç duyulanları al
const USER_PROFILE_QUERY = gql`
  query GetUserProfile($userId: ID!) {
    user(id: $userId) {
      id
      name
      email
      avatar {
        url
        alt
      }
      # Gereksiz field'ları çıkar
      # createdAt
      # updatedAt
      # permissions
    }
  }
`;

Fragment Usage

javascript
// Ortak field'lar için fragment kullan
const USER_FRAGMENT = gql`
  fragment UserInfo on User {
    id
    name
    email
    avatar {
      url
      alt
    }
  }
`;

const USER_LIST_QUERY = gql`
  query GetUsers {
    users {
      ...UserInfo
      role
    }
  }
  ${USER_FRAGMENT}
`;

const USER_DETAIL_QUERY = gql`
  query GetUserDetail($id: ID!) {
    user(id: $id) {
      ...UserInfo
      bio
      preferences {
        theme
        language
      }
    }
  }
  ${USER_FRAGMENT}
`;

Query Batching

javascript
class GraphQLBatcher {
  constructor(client, batchTimeout = 10) {
    this.client = client;
    this.batchTimeout = batchTimeout;
    this.batchQueue = [];
    this.batchTimer = null;
  }

  query(query, variables = {}) {
    return new Promise((resolve, reject) => {
      this.batchQueue.push({
        query,
        variables,
        resolve,
        reject
      });

      if (!this.batchTimer) {
        this.batchTimer = setTimeout(() => {
          this.executeBatch();
        }, this.batchTimeout);
      }
    });
  }

  async executeBatch() {
    const currentBatch = [...this.batchQueue];
    this.batchQueue = [];
    this.batchTimer = null;

    try {
      const batchQuery = this.createBatchQuery(currentBatch);
      const result = await this.client.query(batchQuery);
      
      this.resolveBatchResults(currentBatch, result);
    } catch (error) {
      currentBatch.forEach(item => item.reject(error));
    }
  }

  createBatchQuery(batch) {
    // Birden fazla query'yi tek bir query'de birleştir
    const queries = batch.map((item, index) => {
      return `query_${index}: ${item.query.loc.source.body}`;
    });

    return gql`
      query BatchQuery {
        ${queries.join('\n')}
      }
    `;
  }
}

Caching Strategy

Client-Side Caching

javascript
class GraphQLCache {
  constructor() {
    this.cache = new Map();
    this.ttl = new Map();
  }

  set(key, data, ttl = 300000) { // 5 dakika TTL
    this.cache.set(key, data);
    this.ttl.set(key, Date.now() + ttl);
  }

  get(key) {
    if (this.ttl.has(key) && Date.now() > this.ttl.get(key)) {
      this.cache.delete(key);
      this.ttl.delete(key);
      return null;
    }
    return this.cache.get(key);
  }

  invalidate(pattern) {
    const regex = new RegExp(pattern);
    for (const key of this.cache.keys()) {
      if (regex.test(key)) {
        this.cache.delete(key);
        this.ttl.delete(key);
      }
    }
  }
}

Server-Side Caching

javascript
// GraphQL server-side caching with Redis
const redis = require('redis');
const client = redis.createClient();

const typeDefs = gql`
  type Query {
    user(id: ID!): User @cacheControl(maxAge: 300)
    posts(limit: Int): [Post] @cacheControl(maxAge: 60)
  }
`;

const resolvers = {
  Query: {
    user: async (parent, { id }, context) => {
      const cacheKey = `user:${id}`;
      const cached = await client.get(cacheKey);
      
      if (cached) {
        return JSON.parse(cached);
      }

      const user = await getUserById(id);
      await client.setex(cacheKey, 300, JSON.stringify(user));
      
      return user;
    }
  }
};

Flutter GraphQL Implementation

dart
class GraphQLService {
  late GraphQLClient _client;
  
  GraphQLService() {
    final HttpLink httpLink = HttpLink('https://api.example.com/graphql');
    final AuthLink authLink = AuthLink(getToken: () async => 'Bearer $token');
    final Link link = authLink.concat(httpLink);
    
    _client = GraphQLClient(
      cache: GraphQLCache(store: InMemoryStore()),
      link: link,
    );
  }

  Future<QueryResult> query(DocumentNode document, {Map<String, dynamic>? variables}) async {
    final QueryOptions options = QueryOptions(
      document: document,
      variables: variables ?? {},
      fetchPolicy: FetchPolicy.cacheAndNetwork,
    );
    
    return await _client.query(options);
  }

  Future<QueryResult> mutate(DocumentNode document, {Map<String, dynamic>? variables}) async {
    final MutationOptions options = MutationOptions(
      document: document,
      variables: variables ?? {},
    );
    
    return await _client.mutate(options);
  }
}

Gerçek Zamanlı Veri Senkronizasyonu

Conflict Resolution

Last-Write-Wins Strategy

javascript
class LastWriteWinsResolver {
  resolveConflict(localData, remoteData) {
    // Timestamp'e göre son güncellemeyi kabul et
    if (localData.updatedAt > remoteData.updatedAt) {
      return localData;
    } else {
      return remoteData;
    }
  }

  mergeData(local, remote) {
    const resolved = { ...local };
    
    Object.keys(remote).forEach(key => {
      if (remote[key].updatedAt > (local[key]?.updatedAt || 0)) {
        resolved[key] = remote[key];
      }
    });

    return resolved;
  }
}

Merge Strategies

javascript
class MergeStrategy {
  smartMerge(localData, remoteData) {
    const merged = { ...localData };
    
    // Conflict resolution rules
    const resolutionRules = {
      // String fields: Use remote if different
      title: (local, remote) => remote !== local ? remote : local,
      
      // Arrays: Merge unique items
      tags: (local, remote) => [...new Set([...local, ...remote])],
      
      // Objects: Deep merge
      settings: (local, remote) => this.deepMerge(local, remote),
      
      // Numbers: Use higher value
      score: (local, remote) => Math.max(local, remote)
    };

    Object.keys(remoteData).forEach(key => {
      if (resolutionRules[key]) {
        merged[key] = resolutionRules[key](localData[key], remoteData[key]);
      } else {
        merged[key] = remoteData[key];
      }
    });

    return merged;
  }

  deepMerge(obj1, obj2) {
    const result = { ...obj1 };
    
    Object.keys(obj2).forEach(key => {
      if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key])) {
        result[key] = this.deepMerge(result[key] || {}, obj2[key]);
      } else {
        result[key] = obj2[key];
      }
    });

    return result;
  }
}

Version Vectors

javascript
class VectorClock {
  constructor(nodeId) {
    this.nodeId = nodeId;
    this.vector = new Map();
    this.vector.set(nodeId, 0);
  }

  tick() {
    const currentValue = this.vector.get(this.nodeId) || 0;
    this.vector.set(this.nodeId, currentValue + 1);
    return this.getVector();
  }

  update(otherVector) {
    // Merge vector clocks
    Object.keys(otherVector).forEach(nodeId => {
      const currentValue = this.vector.get(nodeId) || 0;
      const otherValue = otherVector[nodeId];
      this.vector.set(nodeId, Math.max(currentValue, otherValue));
    });
    
    // Increment own counter
    this.tick();
  }

  compare(otherVector) {
    const thisKeys = [...this.vector.keys()];
    const otherKeys = Object.keys(otherVector);
    const allKeys = [...new Set([...thisKeys, ...otherKeys])];
    
    let thisGreater = false;
    let otherGreater = false;
    
    allKeys.forEach(key => {
      const thisValue = this.vector.get(key) || 0;
      const otherValue = otherVector[key] || 0;
      
      if (thisValue > otherValue) {
        thisGreater = true;
      } else if (otherValue > thisValue) {
        otherGreater = true;
      }
    });
    
    if (thisGreater && !otherGreater) return 1;  // This is newer
    if (otherGreater && !thisGreater) return -1; // Other is newer
    return 0; // Concurrent/conflicting
  }

  getVector() {
    return Object.fromEntries(this.vector);
  }
}

State Management

Optimistic Updates

javascript
class OptimisticUpdateManager {
  constructor() {
    this.pendingUpdates = new Map();
    this.rollbackStack = [];
  }

  async optimisticUpdate(id, updateFn, serverUpdateFn) {
    // Local state'i hemen güncelle
    const previousState = this.getCurrentState(id);
    const optimisticState = updateFn(previousState);
    
    // Rollback için önceki state'i sakla
    this.rollbackStack.push({ id, previousState });
    this.pendingUpdates.set(id, { optimisticState, timestamp: Date.now() });
    
    // UI'ı güncelle
    this.updateUI(id, optimisticState);
    
    try {
      // Server'a asenkron gönder
      const serverResult = await serverUpdateFn(optimisticState);
      
      // Başarılı olursa optimistic update'i confirm et
      this.confirmUpdate(id, serverResult);
    } catch (error) {
      // Hata durumunda rollback yap
      this.rollbackUpdate(id);
      throw error;
    }
  }

  confirmUpdate(id, serverResult) {
    this.pendingUpdates.delete(id);
    this.updateUI(id, serverResult);
    
    // Rollback stack'ten ilgili item'ı kaldır
    this.rollbackStack = this.rollbackStack.filter(item => item.id !== id);
  }

  rollbackUpdate(id) {
    const rollbackItem = this.rollbackStack.find(item => item.id === id);
    if (rollbackItem) {
      this.updateUI(id, rollbackItem.previousState);
      this.pendingUpdates.delete(id);
      this.rollbackStack = this.rollbackStack.filter(item => item.id !== id);
    }
  }

  isPending(id) {
    return this.pendingUpdates.has(id);
  }
}

State Reconciliation

javascript
class StateReconciler {
  constructor() {
    this.localState = new Map();
    this.remoteState = new Map();
    this.conflictResolver = new MergeStrategy();
  }

  async reconcile(entityId) {
    const local = this.localState.get(entityId);
    const remote = this.remoteState.get(entityId);
    
    if (!local && !remote) {
      return null;
    }
    
    if (!local) {
      this.localState.set(entityId, remote);
      return remote;
    }
    
    if (!remote) {
      // Local changes need to be synced to server
      await this.syncToServer(entityId, local);
      return local;
    }
    
    // Both exist, need to resolve conflicts
    const resolved = this.conflictResolver.smartMerge(local, remote);
    
    // Update both local and remote states
    this.localState.set(entityId, resolved);
    await this.syncToServer(entityId, resolved);
    
    return resolved;
  }

  async syncToServer(entityId, data) {
    try {
      const response = await fetch(`/api/entities/${entityId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      
      if (response.ok) {
        const serverData = await response.json();
        this.remoteState.set(entityId, serverData);
      }
    } catch (error) {
      console.error('Server sync failed:', error);
      throw error;
    }
  }
}

Bu gelişmiş network patterns, modern mobile uygulamalarda real-time communication, efficient data transfer ve robust state management için kritik öneme sahiptir. WebSocket, GraphQL ve state synchronization teknikleri, kullanıcı deneyimini önemli ölçüde iyileştirir.

Eren Demir tarafından oluşturulmuştur.