Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

Bir chat uygulaması ilk bakışta “A kullanıcısı B kullanıcısına mesaj yollar” kadar basit görünür. Ama işin içine gerçek zamanlı iletişim, grup mesajlaşması, okundu bilgisi, son görülme ve ölçeklenebilirlik girince sistem tasarımı daha ilginç hale gelir. Bu yazıda WhatsApp benzeri bir uygulamanın temel mimarisini, servislerin görevlerini ve mesajların sistem içinde nasıl aktığını adım adım göreceksin.
Bir chat uygulaması ilk bakışta “A kullanıcısı B kullanıcısına mesaj yollar” kadar basit görünür. Ama işin içine gerçek zamanlı iletişim, grup mesajlaşması, okundu bilgisi, son görülme ve ölçeklenebilirlik girince sistem tasarımı daha ilginç hale gelir. Bu yazıda WhatsApp benzeri bir uygulamanın temel mimarisini, servislerin görevlerini ve mesajların sistem içinde nasıl aktığını adım adım göreceksin.
Bir chat sisteminde en kritik beklenti mesajın hızlı ulaşmasıdır. Kullanıcı mesaj gönderdiğinde karşı tarafın uygulaması mümkün olduğunca anında haberdar olmalıdır. HTTP bu iş için tek başına çok uygun değildir. Çünkü HTTP’de istemci istek atar, sunucu cevap verir. Sunucunun istediği anda istemciye veri göndermesi doğal akışın parçası değildir.
Burada WebSocket devreye girer. Yani şöyle düşün: kullanıcı uygulamayı açtığında sunucuyla sürekli açık kalan bir bağlantı kurar. Böylece sunucu, yeni mesaj geldiğinde istemciye beklemeden veri gönderebilir.
flowchart LR
A["Kullanıcı A"] --> G1["Gateway 1"]
B["Kullanıcı B"] --> G2["Gateway 2"]
G1 --> S["Session Service"]
G2 --> S
S --> M["Message Store"]
S --> G2
G2 --> BBu diyagramda kullanıcıların farklı gateway sunucularına bağlı olabileceğini görüyoruz. Session Service, hangi kullanıcının hangi gateway üzerinde aktif bağlantısı olduğunu bilir ve mesajı doğru yere yönlendirir.
Gateway, dış dünyadan gelen bağlantıların sisteme giriş noktasıdır. Ama gateway’in çok fazla iş yapması iyi bir fikir değildir. Çünkü WebSocket bağlantıları uzun süre açık kalır ve her bağlantı sunucuda bellek tüketir.
Bu yüzden gateway mümkün olduğunca sade kalmalıdır. Bunu şöyle hayal edebilirsin: gateway sadece kapıdaki görevli gibi davranır; gelen paketi alır ve içerideki doğru servise iletir. Paketin içeriğini yorumlamak, kullanıcı ilişkilerini çözmek veya grup üyelerini hesaplamak başka servislerin işidir.
flowchart TD
C["Mobil Uygulama"] --> GW["Gateway"]
GW --> P["Parser Service"]
P --> SS["Session Service"]
SS --> MS["Message Store"]
SS --> GS["Group Service"]
SS --> LS["Last Seen Service"]
subgraph "Gateway Katmanı"
GW
end
subgraph "İç Servisler"
P
SS
MS
GS
LS
endParser Service gelen ham mesajı sistemin anlayacağı iç formata dönüştürür. Session Service mesajın nereye gideceğini bulur. Group Service grup üyeliklerini yönetir. Last Seen Service ise kullanıcı aktivitesini takip eder.
Bire bir mesajlaşmada temel akış şudur: gönderen kullanıcı mesajı gateway’e yollar, gateway bunu iç servislere aktarır, mesaj kalıcı olarak saklanır ve alıcı kullanıcı bağlıysa ona iletilir.
Buradaki önemli ayrım “sent”, “delivered” ve “read” durumlarıdır. Sent, sistem mesajı aldı demektir. Delivered, mesaj karşı cihaz tarafından alındı demektir. Read ise kullanıcı ilgili sohbeti açıp mesajı gördü demektir.
sequenceDiagram
participant A as Kullanıcı A
participant G1 as Gateway 1
participant S as Session Service
participant DB as Message Store
participant G2 as Gateway 2
participant B as Kullanıcı B
A->>G1: Mesaj gönder
G1->>S: Mesajı ilet
S->>DB: Mesajı kaydet
S-->>G1: Sent bilgisi
G1-->>A: Tek tik
S->>G2: Mesajı B bağlantısına yönlendir
G2->>B: Mesajı gönder
B-->>G2: Alındı onayı
G2-->>S: Delivered bildirimi
S-->>G1: A'ya teslim bilgisini yönlendir
G1-->>A: Çift tikBu akışta mesaj önce veritabanına yazıldığı için sistem daha güvenli hale gelir. Alıcı o an çevrimdışıysa mesaj kaybolmaz; daha sonra tekrar gönderilebilir.
Okundu bilgisi, teslim bilgisinden farklıdır. Mesaj cihaza ulaşmış olabilir ama kullanıcı sohbet ekranını açmamış olabilir. Bu yüzden read receipt ayrı bir olay olarak ele alınır.
Yani şöyle düşün: uygulama mesajı arka planda aldıysa delivered oluşur. Kullanıcı konuşmayı açtığında ise read oluşur.
sequenceDiagram
participant B as Kullanıcı B
participant G2 as Gateway 2
participant S as Session Service
participant G1 as Gateway 1
participant A as Kullanıcı A
B->>G2: Sohbet ekranı açıldı
G2->>S: Read bildirimi
S->>G1: Gönderene okundu bilgisini ilet
G1->>A: Okundu durumunu gösterBurada teslim ve okundu durumlarının ayrı tasarlanması önemlidir. Çünkü ikisi farklı kullanıcı davranışlarını temsil eder.
Son görülme bilgisini hesaplamak için kullanıcının yaptığı aktiviteler takip edilir. Mesaj göndermek, sohbet açmak veya uygulama içinde anlamlı bir işlem yapmak kullanıcı aktivitesi sayılabilir.
Ama her istek aktivite değildir. Örneğin uygulamanın arka planda mesaj kontrol etmesi kullanıcının gerçekten çevrim içi olduğu anlamına gelmez. Bu yüzden istemciden gelen isteklerde “bu kullanıcı aksiyonu mu, sistem isteği mi?” ayrımı yapılmalıdır.
flowchart TD
R["İstemciden istek gelir"] --> Q{"Kullanıcı aktivitesi mi?"}
Q -- "Evet" --> L["Last Seen Service'e gönder"]
L --> U["last_seen timestamp güncelle"]
Q -- "Hayır" --> N["Son görülme güncellenmez"]
B["Başka kullanıcı sorgular"] --> LS["Last Seen Service"]
LS --> T{"Son aktivite eşik içinde mi?"}
T -- "Evet" --> O["Çevrim içi göster"]
T -- "Hayır" --> P["Son görülme zamanını göster"]Bu diyagram son görülme bilgisinin nasıl üretildiğini gösteriyor. Kullanıcı çok yakın zamanda aktif olduysa sistem “çevrim içi” diyebilir; daha eski bir aktivite varsa timestamp gösterilir.
Grup mesajlaşmasında problem biraz büyür. Çünkü artık bir mesaj tek bir kullanıcıya değil, grubun tüm üyelerine ulaştırılmalıdır. Buna fan-out denir. Yani şöyle düşün: tek bir mesaj, birçok alıcı için ayrı ayrı teslim operasyonuna dönüşür.
Grup üyelik bilgisini Session Service içinde tutmak doğru değildir. Bu bilgi ayrı bir Group Service tarafından yönetilmelidir.
flowchart LR
U["Gönderen Kullanıcı"] --> GW["Gateway"]
GW --> S["Session Service"]
S --> G["Group Service"]
G --> S
S --> DB["Message Store"]
S --> R1["Alıcı 1 Gateway"]
S --> R2["Alıcı 2 Gateway"]
S --> R3["Alıcı 3 Gateway"]
R1 --> A1["Grup Üyesi 1"]
R2 --> A2["Grup Üyesi 2"]
R3 --> A3["Grup Üyesi 3"]Burada Session Service, Group Service’e “bu grubun üyeleri kim?” diye sorar. Sonra her üye için aktif bağlantı bilgisini bulur ve mesajı ilgili gateway’e yönlendirir.
Grup mesajlaşmasında üye sayısı arttıkça tek bir mesajın maliyeti de artar. 10 kişilik grupta bir mesaj 9 alıcıya gider. 500 kişilik grupta aynı mesaj 499 teslim operasyonu anlamına gelir.
Bu yüzden chat uygulamalarında grup boyutuna limit koymak mimari açıdan mantıklıdır. Böylece sistem gerçek zamanlı davranışını koruyabilir.
flowchart TD
M["Grup mesajı gönderildi"] --> C{"Üye sayısı limit içinde mi?"}
C -- "Evet" --> F["Anlık fan-out yap"]
F --> D["Mesajı üyelere ilet"]
C -- "Hayır" --> B["Mesajı kuyrukla veya reddet"]
B --> E["Gönderene durum bildir"]Bu diyagram grup büyüklüğünün mesaj gönderim stratejisini nasıl etkilediğini anlatıyor. Küçük ve orta ölçekli gruplarda anlık dağıtım yapılabilir; aşırı büyük gruplarda kuyruklama veya farklı dağıtım stratejileri gerekir.
Dağıtık sistemlerde servisler her zaman ayakta olmayabilir. Group Service kısa süreli yanıt veremeyebilir, bir gateway düşebilir veya ağda gecikme olabilir. Bu noktada Message Queue kullanılır.
Message Queue, yani mesaj kuyruğu, işi hemen yapamıyorsan güvenli şekilde sıraya koymanı sağlar. Bunu şöyle hayal edebilirsin: mesaj bir görev olarak kuyruğa bırakılır, sistem uygun olduğunda tekrar işlemeyi dener.
flowchart LR
S["Session Service"] --> Q["Message Queue"]
Q --> W["Worker"]
W --> G["Group Service"]
W --> R{"Başarılı mı?"}
R -- "Evet" --> OK["Gönderim tamamlandı"]
R -- "Hayır" --> RT["Retry"]
RT --> Q
RT -. "Limit aşılırsa" .-> F["Başarısızlık bildir"]Bu yapı sistemin geçici hatalara karşı daha dayanıklı olmasını sağlar. Retry sayısı aşıldığında istemciye başarısızlık bilgisi dönülebilir.
Bu tarz bir sistemde birkaç temel veri ilişkisi vardır. Kullanıcılar mesaj gönderir, mesajlar sohbetlere veya gruplara bağlıdır, grup üyelikleri ayrı tutulur, bağlantı bilgisi ise geçici bir session verisidir.
erDiagram
USER ||--o{ MESSAGE : sends
CHAT ||--o{ MESSAGE : contains
GROUP ||--o{ GROUP_MEMBER : has
USER ||--o{ GROUP_MEMBER : joins
USER ||--o| SESSION : has
MESSAGE ||--o{ MESSAGE_RECEIPT : tracks
USER ||--o{ MESSAGE_RECEIPT : owns
USER {
string user_id
string display_name
}
MESSAGE {
string message_id
string sender_id
string chat_id
string body
datetime created_at
}
GROUP {
string group_id
string name
}
GROUP_MEMBER {
string group_id
string user_id
datetime joined_at
}
SESSION {
string user_id
string gateway_id
datetime connected_at
}
MESSAGE_RECEIPT {
string message_id
string user_id
string status
datetime updated_at
}Bu ER diyagramı sistemdeki ana varlıkları gösteriyor. Özellikle SESSION kalıcı kullanıcı verisinden ayrıdır; çünkü bağlantı bilgisi sık değişen, geçici bir bilgidir.
Gerçek dünyada bazı anlarda mesaj trafiği normalin çok üstüne çıkar. Böyle zamanlarda sistemin her özelliği aynı öncelikte ele alması doğru değildir. Mesajın alınması ve kaydedilmesi en kritik iştir. Son görülme, okundu bilgisi veya ikincil bildirimler daha düşük önceliğe alınabilir.
flowchart TD
T["Yük artışı algılandı"] --> P{"İş kritik mi?"}
P -- "Mesaj gönderimi" --> H["Yüksek öncelik"]
P -- "Mesaj kaydı" --> H
P -- "Okundu bilgisi" --> L["Düşük öncelik"]
P -- "Son görülme" --> L
P -- "Teslim bildirimi" --> M["Orta öncelik"]
H --> A["Hemen işle"]
M --> B["Gecikmeli işle"]
L --> C["Kuyruğa al veya geçici olarak azalt"]Bu diyagram yoğunluk altında sistemin nasıl kontrollü şekilde sadeleşebileceğini gösteriyor. Amaç tüm sistemi durdurmak yerine en kritik kullanıcı deneyimini ayakta tutmaktır.
WhatsApp benzeri bir chat uygulamasında asıl mesele sadece mesaj göndermek değildir. Gateway katmanını hafif tutmak, kullanıcı bağlantılarını Session Service ile takip etmek, mesajları güvenli şekilde saklamak, WebSocket ile gerçek zamanlı iletişim kurmak ve grup mesajlarında kontrollü fan-out yapmak gerekir. Okundu bilgisi, teslim durumu ve son görülme gibi özellikler kullanıcı deneyimini zenginleştirir; ancak yüksek trafikte bu özelliklerin önceliği temel mesajlaşmanın gerisinde kalmalıdır. Sağlam bir tasarım, servisleri doğru ayırarak ve geçici hatalara karşı kuyruk/retry mekanizmaları kullanarak ölçeklenebilir hale gelir.
Bu yazı WHATSAPP System Design: Chat Messaging Systems for Interviews videosundan ilham alınarak yazılmıştır.