{"id":34552,"date":"2025-11-18T19:31:25","date_gmt":"2025-11-18T16:31:25","guid":{"rendered":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/"},"modified":"2025-11-18T19:31:25","modified_gmt":"2025-11-18T16:31:25","slug":"spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir","status":"publish","type":"post","link":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/","title":{"rendered":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?"},"content":{"rendered":"<p><body><\/p>\n<p>Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.<\/p>\n<p>G\u00fcn\u00fcm\u00fcz dijital d\u00fcnyas\u0131nda anl\u0131k ileti\u015fim, uygulamalar\u0131n vazge\u00e7ilmez bir par\u00e7as\u0131 haline geldi. Sosyal medya platformlar\u0131ndan i\u015fbirli\u011fi ara\u00e7lar\u0131na, hatta e-ticaret sitelerindeki m\u00fc\u015fteri destek sistemlerine kadar her yerde sohbet i\u015flevselli\u011fine rastl\u0131yoruz. Ancak, bu sohbet uygulamalar\u0131n\u0131 y\u00fcz binlerce veya milyonlarca kullan\u0131c\u0131ya hizmet verecek \u015fekilde tasarlamak ve da\u011f\u0131t\u0131k bir mimaride y\u00f6netmek, hi\u00e7 de k\u00fc\u00e7\u00fcmsenmeyecek bir m\u00fchendislik meydan okumas\u0131d\u0131r. \u00d6zellikle y\u00fcksek performans ve d\u00fc\u015f\u00fck gecikme s\u00fcresi beklentisi, mimari kararlar\u0131 daha da kritik hale getiriyor.<\/p>\n<p>Pek \u00e7ok geli\u015ftirici da\u011f\u0131t\u0131k sistemlerdeki durum y\u00f6netimi ve mesaj kuyruklama i\u00e7in Redis gibi bellek i\u00e7i veri depolar\u0131na ba\u015fvururken, her projenin Redis&#8217;e ihtiyac\u0131 olmayabilir veya farkl\u0131 k\u0131s\u0131tlamalar bulunabilir. Bu makalede, Spring Boot&#8217;un g\u00fcc\u00fcn\u00fc kullanarak, Redis&#8217;e ba\u011f\u0131ml\u0131l\u0131k olmadan nas\u0131l \u00f6l\u00e7eklenebilir bir da\u011f\u0131t\u0131k sohbet uygulamas\u0131 in\u015fa edebilece\u011fimizi ad\u0131m ad\u0131m inceleyece\u011fiz. Temel WebSocket kavramlar\u0131ndan ba\u015flayarak, \u00e7oklu uygulama \u00f6rnekleri aras\u0131nda mesajla\u015fmay\u0131 nas\u0131l senkronize edece\u011fimize ve veritaban\u0131 persistans\u0131n\u0131 nas\u0131l y\u00f6netece\u011fimize dair pratik bilgiler sunaca\u011f\u0131z. Haz\u0131rsan\u0131z, Spring Boot ile kendi da\u011f\u0131t\u0131k chat sisteminizi kurman\u0131n derinliklerine inelim!<\/p>\n<p>Bir sohbet uygulamas\u0131 geli\u015ftirmek ilk bak\u0131\u015fta basit g\u00f6r\u00fcnebilir: kullan\u0131c\u0131dan mesaj\u0131 al, di\u011fer kullan\u0131c\u0131lara g\u00f6nder. Ancak, bu sistemi tek bir sunucunun \u00f6tesine ta\u015f\u0131y\u0131p binlerce e\u015fzamanl\u0131 kullan\u0131c\u0131ya hizmet verecek \u015fekilde \u00f6l\u00e7eklemeye \u00e7al\u0131\u015ft\u0131\u011f\u0131m\u0131zda, karma\u015f\u0131kl\u0131k h\u0131zla artar. Bu durum, da\u011f\u0131t\u0131k sistemlerin do\u011fas\u0131ndan kaynaklanan belirli zorluklar\u0131 beraberinde getirir. \u00d6ncelikle, &#8220;durum&#8221; y\u00f6netimi en b\u00fcy\u00fck engellerden biridir. Geleneksel bir sunucuda, bir kullan\u0131c\u0131n\u0131n oturum bilgisi veya belirli bir sohbet odas\u0131n\u0131n anl\u0131k durumu sunucunun belle\u011finde tutulabilir. Ancak, uygulaman\u0131z\u0131n birden fazla sunucuya (instance) da\u011f\u0131t\u0131ld\u0131\u011f\u0131n\u0131 d\u00fc\u015f\u00fcn\u00fcn. Bir kullan\u0131c\u0131 ilk iste\u011fini sunucu A&#8217;ya, ikinci iste\u011fini ise y\u00fck dengeleyici (load balancer) taraf\u0131ndan sunucu B&#8217;ye y\u00f6nlendirilirse ne olur? Sunucu B, kullan\u0131c\u0131n\u0131n daha \u00f6nceki oturum bilgilerine veya kat\u0131ld\u0131\u011f\u0131 sohbet odalar\u0131na dair hi\u00e7bir bilgiye sahip olmayacakt\u0131r. Bu durum, tutarl\u0131l\u0131k sorunlar\u0131na ve k\u00f6t\u00fc bir kullan\u0131c\u0131 deneyimine yol a\u00e7abilir. Dolay\u0131s\u0131yla, da\u011f\u0131t\u0131k bir ortamda oturum bilgilerinin ve anl\u0131k durumun t\u00fcm sunucular aras\u0131nda tutarl\u0131 bir \u015fekilde payla\u015f\u0131lmas\u0131 veya y\u00f6netilmesi kritik hale gelir.<\/p>\n<p>\u0130kinci \u00f6nemli zorluk, mesajlar\u0131n ger\u00e7ek zamanl\u0131 olarak ve g\u00fcvenilir bir \u015fekilde t\u00fcm ilgili kullan\u0131c\u0131lara ula\u015ft\u0131r\u0131lmas\u0131d\u0131r. Tekil bir sunucuda, WebSocket ba\u011flant\u0131lar\u0131n\u0131 y\u00f6netmek ve mesajlar\u0131 do\u011frudan ba\u011fl\u0131 istemcilere g\u00f6ndermek nispeten kolayd\u0131r. Ancak, kullan\u0131c\u0131 A&#8217;n\u0131n sunucu 1&#8217;e, kullan\u0131c\u0131 B&#8217;nin ise sunucu 2&#8217;ye ba\u011fl\u0131 oldu\u011fu bir senaryoda, kullan\u0131c\u0131 A&#8217;n\u0131n g\u00f6nderdi\u011fi bir mesaj\u0131n sunucu 1&#8217;den sunucu 2&#8217;ye ve oradan da kullan\u0131c\u0131 B&#8217;ye nas\u0131l ula\u015faca\u011f\u0131 sorusu ortaya \u00e7\u0131kar. Bu, sunucular aras\u0131 ileti\u015fimi ve mesaj y\u00f6nlendirmeyi gerektirir. E\u011fer bir mesaj sunucu 1&#8217;den sunucu 2&#8217;ye g\u00f6nderilirken kaybolursa veya gecikirse, bu da kullan\u0131c\u0131 deneyimini olumsuz etkiler. Y\u00fcksek hacimli mesajla\u015fma, anl\u0131k bildirimler ve grup sohbetleri gibi \u00f6zellikler, bu y\u00f6nlendirme ve teslimat mekanizmalar\u0131n\u0131n sa\u011flaml\u0131\u011f\u0131n\u0131 ve verimlili\u011fini daha da \u00f6nemli k\u0131lar. Geleneksel HTTP istek-cevap d\u00f6ng\u00fcs\u00fc bu t\u00fcr ger\u00e7ek zamanl\u0131 senaryolar i\u00e7in uygun de\u011fildir; bu nedenle, WebSocket gibi kal\u0131c\u0131 ba\u011flant\u0131 protokolleri kullan\u0131l\u0131r.<\/p>\n<p>Son olarak, \u00f6l\u00e7eklenebilirlik ve hata tolerans\u0131, da\u011f\u0131t\u0131k sohbet uygulamalar\u0131nda g\u00f6z ard\u0131 edilemez. Uygulama b\u00fcy\u00fcd\u00fck\u00e7e daha fazla kullan\u0131c\u0131ya hizmet vermek i\u00e7in yeni sunucular ekleyebilmeliyiz (yatay \u00f6l\u00e7ekleme). Bu sunucular\u0131n sorunsuz bir \u015fekilde sisteme dahil olmas\u0131 ve mevcut ak\u0131\u015f\u0131 bozmamas\u0131 gerekir. Ayn\u0131 zamanda, bir sunucunun \u00e7\u00f6kmesi durumunda sistemin genel i\u015flevselli\u011finin etkilenmemesi, yani hata tolerans\u0131n\u0131n y\u00fcksek olmas\u0131 beklenir. Kullan\u0131c\u0131lar\u0131n ba\u011flant\u0131lar\u0131 kesilse bile, m\u00fcmk\u00fcn olan en k\u0131sa s\u00fcrede otomatik olarak yeniden ba\u011flanabilmeli ve sohbet ge\u00e7mi\u015fleri korunmal\u0131d\u0131r. T\u00fcm bu zorluklar, sadece do\u011fru teknolojileri se\u00e7meyi de\u011fil, ayn\u0131 zamanda bu teknolojileri da\u011f\u0131t\u0131k bir mimaride nas\u0131l entegre edip y\u00f6netece\u011fimize dair kapsaml\u0131 bir plan yapmay\u0131 gerektirir. Redis gibi ara\u00e7lar bu sorunlar\u0131n baz\u0131lar\u0131na kolay \u00e7\u00f6z\u00fcmler sunarken, Redis&#8217;e ba\u011f\u0131ml\u0131 olmadan bu hedeflere ula\u015fmak i\u00e7in farkl\u0131 stratejiler ve ara\u00e7lar ke\u015ffetmemiz gerekecektir.<\/p>\n<h2>Spring Boot ile Temel Bir WebSocket Uygulamas\u0131 Nas\u0131l Olu\u015fturulur?<\/h2>\n<p>Da\u011f\u0131t\u0131k bir chat uygulamas\u0131n\u0131n temelinde, istemciler ve sunucu aras\u0131nda ger\u00e7ek zamanl\u0131, \u00e7ift y\u00f6nl\u00fc ileti\u015fimi sa\u011flayan WebSocket teknolojisi yatar. Spring Boot, WebSocket entegrasyonunu Spring Framework&#8217;\u00fcn g\u00fc\u00e7l\u00fc mesajla\u015fma mod\u00fcl\u00fc sayesinde son derece basitle\u015ftirir. Bu b\u00f6l\u00fcmde, tekil bir Spring Boot uygulamas\u0131nda temel bir WebSocket tabanl\u0131 sohbet altyap\u0131s\u0131n\u0131 nas\u0131l kuraca\u011f\u0131m\u0131z\u0131 ad\u0131m ad\u0131m g\u00f6rece\u011fiz. Bu temel yap\u0131, daha sonra da\u011f\u0131t\u0131k hale getirece\u011fimiz uygulaman\u0131n \u00e7ekirde\u011fini olu\u015fturacakt\u0131r.<\/p>\n<h3>1. Gerekli Ba\u011f\u0131ml\u0131l\u0131klar\u0131 Eklemek<\/h3>\n<p>\u00d6ncelikle, projemizin <code>pom.xml<\/code> dosyas\u0131na Spring WebSocket ve Spring Messaging ba\u011f\u0131ml\u0131l\u0131klar\u0131n\u0131 eklememiz gerekiyor. Bu ba\u011f\u0131ml\u0131l\u0131klar, WebSocket ba\u011flant\u0131lar\u0131n\u0131 y\u00f6netmek ve mesajlar\u0131 STOMP (Simple Text Oriented Messaging Protocol) gibi alt protokoller \u00fczerinden i\u015flemek i\u00e7in gerekli t\u00fcm s\u0131n\u0131flar\u0131 ve yap\u0131land\u0131rmalar\u0131 sa\u011flar. STOMP, WebSocket \u00fczerinden g\u00f6nderilen mesajlar\u0131n format\u0131n\u0131 ve y\u00f6nlendirilmesini standartla\u015ft\u0131ran bir protokold\u00fcr, bu da istemci ve sunucu aras\u0131ndaki ileti\u015fimi daha d\u00fczenli hale getirir.<\/p>\n<pre><code>\n<dependencies>\n    <dependency>\n        <groupId>org.springframework.boot<\/groupId>\n        <artifactId>spring-boot-starter-web<\/artifactId>\n    <\/dependency>\n    <dependency>\n        <groupId>org.springframework.boot<\/groupId>\n        <artifactId>spring-boot-starter-websocket<\/artifactId>\n    <\/dependency>\n    <dependency>\n        <groupId>org.springframework.boot<\/groupId>\n        <artifactId>spring-boot-starter-data-jpa<\/artifactId>\n    <\/dependency>\n    <dependency>\n        <groupId>org.postgresql<\/groupId>\n        <artifactId>postgresql<\/artifactId>\n        <scope>runtime<\/scope>\n    <\/dependency>\n    <!-- E\u011fer RabbitMQ kullanacaksak ek ba\u011f\u0131ml\u0131l\u0131klar burada yer alacak -->\n<\/dependencies>\n    <\/pre>\n<p><\/code><\/p>\n<p>Yukar\u0131daki ba\u011f\u0131ml\u0131l\u0131klara ek olarak, veritaban\u0131 persistans\u0131 i\u00e7in <code>spring-boot-starter-data-jpa<\/code> ve bir PostgreSQL s\u00fcr\u00fcc\u00fcs\u00fc ekledik. Bu, ilerleyen a\u015famalarda mesaj ge\u00e7mi\u015fini depolamak i\u00e7in kullan\u0131lacakt\u0131r.<\/p>\n<h3>2. WebSocket Yap\u0131land\u0131rmas\u0131<\/h3>\n<p>WebSocket sunucumuzu ve STOMP mesajla\u015fmas\u0131n\u0131 yap\u0131land\u0131rmak i\u00e7in bir yap\u0131land\u0131rma s\u0131n\u0131f\u0131 olu\u015fturmam\u0131z gerekiyor. Bu s\u0131n\u0131f, <code>@EnableWebSocketMessageBroker<\/code> ve <code>WebSocketMessageBrokerConfigurer<\/code> aray\u00fcz\u00fcn\u00fc uygulayarak, mesaj arac\u0131s\u0131 tabanl\u0131 WebSocket ileti\u015fimini etkinle\u015ftirir.<\/p>\n<pre><code>\npackage com.example.chat.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.messaging.simp.config.MessageBrokerRegistry;\nimport org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;\nimport org.springframework.web.socket.config.annotation.StompEndpointRegistry;\nimport org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;\n\n@Configuration\n@EnableWebSocketMessageBroker\npublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {\n\n    @Override\n    public void configureMessageBroker(MessageBrokerRegistry config) {\n        config.enableSimpleBroker(\"\/topic\"); \/\/ Mesajlar\u0131 istemcilere yay\u0131nlamak i\u00e7in basit bir mesaj arac\u0131s\u0131 etkinle\u015ftirir.\n        config.setApplicationDestinationPrefixes(\"\/app\"); \/\/ \u0130stemciden sunucuya g\u00f6nderilen mesajlar\u0131n \u00f6neki.\n    }\n\n    @Override\n    public void registerStompEndpoints(StompEndpointRegistry registry) {\n        registry.addEndpoint(\"\/ws\").withSockJS(); \/\/ \u0130stemcilerin WebSocket ba\u011flant\u0131s\u0131 kuraca\u011f\u0131 u\u00e7 nokta.\n        \/\/ withSockJS() \u00f6zelli\u011fi, WebSocket'in desteklenmedi\u011fi taray\u0131c\u0131larda geri d\u00f6n\u00fc\u015f (fallback) se\u00e7enekleri sunar.\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>configureMessageBroker<\/code> metodunda, <code>enableSimpleBroker(\"\/topic\")<\/code> ile basit bir bellek i\u00e7i mesaj arac\u0131s\u0131 tan\u0131mlar\u0131z. Bu broker, <code>\/topic<\/code> \u00f6nekiyle ba\u015flayan destinasyonlara g\u00f6nderilen mesajlar\u0131 t\u00fcm abone olan istemcilere yay\u0131nlar. <code>setApplicationDestinationPrefixes(\"\/app\")<\/code> ise, istemcilerin sunucuya mesaj g\u00f6ndermek i\u00e7in kullanaca\u011f\u0131 \u00f6neki belirler. \u00d6rne\u011fin, bir istemci bir mesaj\u0131 <code>\/app\/chat<\/code> adresine g\u00f6nderirse, bu mesaj bir <code>@MessageMapping<\/code> anotasyonuna sahip bir metod taraf\u0131ndan i\u015flenir. <code>registerStompEndpoints<\/code> metodunda, istemcilerin WebSocket ba\u011flant\u0131s\u0131 kurmak i\u00e7in kullanaca\u011f\u0131 <code>\/ws<\/code> u\u00e7 noktas\u0131n\u0131 tan\u0131mlar\u0131z. <code>.withSockJS()<\/code> ekleyerek, WebSocket ba\u011flant\u0131s\u0131n\u0131n m\u00fcmk\u00fcn olmad\u0131\u011f\u0131 durumlarda HTTP tabanl\u0131 alternatifler (\u00f6rne\u011fin, XHR streaming\/polling) kullan\u0131larak uyumluluk sa\u011flan\u0131r.<\/p>\n<aside class=\"expert-tip\">\n        Uzman \u0130pucu: <code>withSockJS()<\/code> kullan\u0131m\u0131, taray\u0131c\u0131 uyumlulu\u011fu a\u00e7\u0131s\u0131ndan \u00f6nemlidir. \u00d6zellikle eski taray\u0131c\u0131larda WebSocket deste\u011fi s\u0131n\u0131rl\u0131 olabilir. Bu sayede uygulaman\u0131z daha geni\u015f bir kullan\u0131c\u0131 kitlesine ula\u015fabilir. Ancak \u00fcretim ortamlar\u0131nda, bu geri d\u00f6n\u00fc\u015f mekanizmalar\u0131n\u0131n ek gecikmelere neden olabilece\u011fini unutmay\u0131n.<br \/>\n    <\/aside>\n<h3>3. Mesaj Modeli ve Controller<\/h3>\n<p>Sohbet mesajlar\u0131n\u0131 temsil edecek bir POJO (Plain Old Java Object) s\u0131n\u0131f\u0131 ve bu mesajlar\u0131 i\u015fleyecek bir Spring Controller olu\u015fturmal\u0131y\u0131z. Mesaj modelimiz, mesaj\u0131n i\u00e7eri\u011fini, g\u00f6nderenini ve g\u00f6nderilme zaman\u0131n\u0131 i\u00e7erebilir.<\/p>\n<pre><code>\npackage com.example.chat.model;\n\nimport java.time.LocalDateTime;\n\npublic class ChatMessage {\n    private String sender;\n    private String content;\n    private LocalDateTime timestamp;\n\n    \/\/ Constructors, Getters, Setters\n    public ChatMessage() {\n        this.timestamp = LocalDateTime.now();\n    }\n\n    public ChatMessage(String sender, String content) {\n        this.sender = sender;\n        this.content = content;\n        this.timestamp = LocalDateTime.now();\n    }\n\n    public String getSender() {\n        return sender;\n    }\n\n    public void setSender(String sender) {\n        this.sender = sender;\n    }\n\n    public String getContent() {\n        return content;\n    }\n\n    public void setContent(String content) {\n        this.content = content;\n    }\n\n    public LocalDateTime getTimestamp() {\n        return timestamp;\n    }\n\n    public void setTimestamp(LocalDateTime timestamp) {\n        this.timestamp = timestamp;\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p>\u015eimdi de bu mesajlar\u0131 alacak ve geri g\u00f6nderecek olan Controller s\u0131n\u0131f\u0131m\u0131z\u0131 olu\u015ftural\u0131m:<\/p>\n<pre><code>\npackage com.example.chat.controller;\n\nimport com.example.chat.model.ChatMessage;\nimport org.springframework.messaging.handler.annotation.MessageMapping;\nimport org.springframework.messaging.handler.annotation.Payload;\nimport org.springframework.messaging.handler.annotation.SendTo;\nimport org.springframework.stereotype.Controller;\n\n@Controller\npublic class ChatController {\n\n    @MessageMapping(\"\/chat.sendMessage\") \/\/ \u0130stemciden \/app\/chat.sendMessage adresine gelen mesajlar\u0131 dinler\n    @SendTo(\"\/topic\/public\")             \/\/ \u0130\u015flenen mesaj\u0131 \/topic\/public adresine ba\u011fl\u0131 t\u00fcm istemcilere g\u00f6nderir\n    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {\n        System.out.println(\"Received message: \" + chatMessage.getContent() + \" from \" + chatMessage.getSender());\n        return chatMessage; \/\/ Gelen mesaj\u0131 oldu\u011fu gibi geri g\u00f6nderir\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>@MessageMapping(\"\/chat.sendMessage\")<\/code> anotasyonu, istemciden <code>\/app\/chat.sendMessage<\/code> adresine g\u00f6nderilen STOMP mesajlar\u0131n\u0131 dinleyen bir metodu i\u015faretler. <code>@SendTo(\"\/topic\/public\")<\/code> anotasyonu ise, bu metodun d\u00f6nd\u00fcrd\u00fc\u011f\u00fc de\u011feri al\u0131p <code>\/topic\/public<\/code> adresine ba\u011fl\u0131 t\u00fcm abonelere g\u00f6nderilmesini sa\u011flar. Bu sayede, bir istemci mesaj g\u00f6nderdi\u011finde, bu mesaj sunucu taraf\u0131ndan al\u0131n\u0131r, i\u015flenir ve ard\u0131ndan t\u00fcm di\u011fer ba\u011fl\u0131 istemcilere yay\u0131nlan\u0131r. Bu yap\u0131, tekil bir Spring Boot instance'\u0131 i\u00e7in \u00e7al\u0131\u015fan temel bir sohbet uygulamas\u0131 olu\u015fturmak i\u00e7in yeterlidir. Ancak, da\u011f\u0131t\u0131k bir ortama ge\u00e7ti\u011fimizde, <code>enableSimpleBroker<\/code> taraf\u0131ndan sa\u011flanan bellek i\u00e7i mesaj arac\u0131s\u0131 art\u0131k yeterli olmayacakt\u0131r, \u00e7\u00fcnk\u00fc her instance'\u0131n kendi bellek i\u00e7i arac\u0131s\u0131 birbirinden ba\u011f\u0131ms\u0131z \u00e7al\u0131\u015facakt\u0131r. Bu noktada, farkl\u0131 instance'lar aras\u0131nda mesajla\u015fmay\u0131 senkronize edecek harici bir mesaj arac\u0131s\u0131na ihtiyac\u0131m\u0131z olacak.<\/p>\n<h2>\u00c7oklu Spring Boot Instance'lar\u0131 Aras\u0131nda Mesajla\u015fma Nas\u0131l Sa\u011flan\u0131r?<\/h2>\n<p>\u00d6nceki b\u00f6l\u00fcmde temel bir WebSocket sohbet uygulamas\u0131 olu\u015fturduk. Bu uygulama, tek bir Spring Boot instance'\u0131 \u00fczerinde sorunsuz \u00e7al\u0131\u015f\u0131r. Ancak, uygulaman\u0131z\u0131 \u00f6l\u00e7eklendirmek ve binlerce kullan\u0131c\u0131ya hizmet vermek istedi\u011finizde, tek bir instance yetersiz kalacak ve birden fazla Spring Boot instance'\u0131n\u0131 bir y\u00fck dengeleyici (load balancer) arkas\u0131na yerle\u015ftirmeniz gerekecektir. \u0130\u015fte bu noktada b\u00fcy\u00fck bir zorluk ortaya \u00e7\u0131kar: E\u011fer kullan\u0131c\u0131 A sunucu 1'e, kullan\u0131c\u0131 B ise sunucu 2'ye ba\u011fl\u0131ysa, kullan\u0131c\u0131 A'n\u0131n g\u00f6nderdi\u011fi mesaj\u0131n kullan\u0131c\u0131 B'ye nas\u0131l ula\u015faca\u011f\u0131n\u0131 nas\u0131l garanti edece\u011fiz? Her instance'\u0131n kendi bellek i\u00e7i mesaj arac\u0131s\u0131 birbirinden izole \u00e7al\u0131\u015ft\u0131\u011f\u0131 i\u00e7in, sunucu 1'deki broker sunucu 2'deki abonelerden haberdar olmayacakt\u0131r. Bu durumda, da\u011f\u0131t\u0131k sistemlerdeki ger\u00e7ek zamanl\u0131 mesajla\u015fma i\u00e7in harici bir mesaj arac\u0131s\u0131na ihtiyac\u0131m\u0131z vard\u0131r.<\/p>\n<p>Redis yerine kullanabilece\u011fimiz g\u00fc\u00e7l\u00fc ve pop\u00fcler alternatifler aras\u0131nda RabbitMQ ve Apache Kafka gibi mesaj kuyruklar\u0131 (message queues) veya mesaj broker'lar\u0131 bulunur. Bu ara\u00e7lar, farkl\u0131 uygulama instance'lar\u0131 aras\u0131nda g\u00fcvenilir ve \u00f6l\u00e7eklenebilir bir \u015fekilde mesaj al\u0131\u015fveri\u015fi yapmay\u0131 sa\u011flayan publish\/subscribe (yay\u0131nlama\/abone olma) mekanizmalar\u0131 sunar. Bu makalede, RabbitMQ'yu bir \u00f6rnek olarak ele alaca\u011f\u0131z, ancak benzer prensipler Apache Kafka gibi di\u011fer mesaj broker'lar\u0131 i\u00e7in de ge\u00e7erlidir. RabbitMQ, hafifli\u011fi, esnekli\u011fi ve geni\u015f topluluk deste\u011fi sayesinde bu t\u00fcr senaryolar i\u00e7in m\u00fckemmel bir se\u00e7imdir.<\/p>\n<h3>RabbitMQ ile Da\u011f\u0131t\u0131k Mesajla\u015fma<\/h3>\n<p>RabbitMQ, mesajlar\u0131 kuyruklara y\u00f6nlendiren ve bu kuyruklardan mesajlar\u0131 t\u00fcketicilere (uygulama instance'lar\u0131) ileten bir \"mesaj arac\u0131s\u0131\" g\u00f6revi g\u00f6r\u00fcr. Her Spring Boot instance'\u0131, kendi WebSocket ba\u011flant\u0131lar\u0131n\u0131 y\u00f6netirken ayn\u0131 zamanda RabbitMQ'ya mesaj yay\u0131nlayacak ve RabbitMQ'dan mesaj t\u00fcketecektir. Mimari \u015f\u00f6yle i\u015fleyecektir: Bir kullan\u0131c\u0131 bir mesaj g\u00f6nderdi\u011finde, bu mesaj o an ba\u011fl\u0131 oldu\u011fu Spring Boot instance'\u0131na ula\u015f\u0131r. Instance, mesaj\u0131 al\u0131r, veritaban\u0131na kaydeder (ilerleyen b\u00f6l\u00fcmde ele al\u0131nacak) ve ard\u0131ndan RabbitMQ'daki belirli bir 'exchange'e yay\u0131nlar. Di\u011fer t\u00fcm Spring Boot instance'lar\u0131, ayn\u0131 'exchange'e ba\u011fl\u0131 bir kuyruktan bu mesaj\u0131 dinler. Mesaj\u0131 alan her instance, kendi ba\u011fl\u0131 WebSocket istemcilerine bu mesaj\u0131 iletir. B\u00f6ylece, mesaj t\u00fcm kullan\u0131c\u0131lara, hangi sunucuya ba\u011fl\u0131 olurlarsa olsunlar, ula\u015fm\u0131\u015f olur.<\/p>\n<h3>1. RabbitMQ Ba\u011f\u0131ml\u0131l\u0131klar\u0131n\u0131 Eklemek<\/h3>\n<p>RabbitMQ ile entegrasyon i\u00e7in <code>spring-boot-starter-amqp<\/code> ba\u011f\u0131ml\u0131l\u0131\u011f\u0131n\u0131 eklememiz gerekiyor. AMQP (Advanced Message Queuing Protocol), RabbitMQ'nun kulland\u0131\u011f\u0131 protokold\u00fcr.<\/p>\n<pre><code>\n<dependencies>\n    <!-- Mevcut ba\u011f\u0131ml\u0131l\u0131klar... -->\n    <dependency>\n        <groupId>org.springframework.boot<\/groupId>\n        <artifactId>spring-boot-starter-amqp<\/artifactId>\n    <\/dependency>\n<\/dependencies>\n    <\/pre>\n<p><\/code><\/p>\n<h3>2. WebSocket Yap\u0131land\u0131rmas\u0131n\u0131 RabbitMQ ile G\u00fcncellemek<\/h3>\n<p><code>WebSocketConfig<\/code> s\u0131n\u0131f\u0131m\u0131zdaki <code>configureMessageBroker<\/code> metodunu, bellek i\u00e7i basit broker yerine harici bir broker (RabbitMQ) kullanacak \u015fekilde g\u00fcncelleyece\u011fiz. Spring, STOMP mesajla\u015fmas\u0131n\u0131 harici broker'lara y\u00f6nlendirmek i\u00e7in \u00f6zel bir yap\u0131land\u0131rma sa\u011flar.<\/p>\n<pre><code>\npackage com.example.chat.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.messaging.simp.config.MessageBrokerRegistry;\nimport org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;\nimport org.springframework.web.socket.config.annotation.StompEndpointRegistry;\nimport org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;\n\n@Configuration\n@EnableWebSocketMessageBroker\npublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {\n\n    @Override\n    public void configureMessageBroker(MessageBrokerRegistry config) {\n        \/\/ Harici bir mesaj arac\u0131s\u0131 (RabbitMQ) kullanarak STOMP mesajlar\u0131n\u0131 yay\u0131nlamak i\u00e7in yap\u0131land\u0131rma\n        config.enableBrokerChannel().setPreservePublishOrder(true); \/\/ Geli\u015fmi\u015f broker channel y\u00f6netimi\n        config.enableStompBrokerRelay(\"\/topic\") \/\/ \/topic destinasyonlar\u0131 i\u00e7in harici broker'a y\u00f6nlendirme\n              .setRelayHost(\"localhost\")        \/\/ RabbitMQ sunucusunun adresi (veya IP)\n              .setRelayPort(61613)             \/\/ STOMP over WebSocket i\u00e7in RabbitMQ'nun varsay\u0131lan portu\n              .setClientLogin(\"guest\")          \/\/ RabbitMQ kullan\u0131c\u0131 ad\u0131\n              .setClientPasscode(\"guest\");      \/\/ RabbitMQ \u015fifresi\n\n        config.setApplicationDestinationPrefixes(\"\/app\");\n    }\n\n    @Override\n    @Override\n    public void registerStompEndpoints(StompEndpointRegistry registry) {\n        registry.addEndpoint(\"\/ws\").withSockJS();\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>enableStompBrokerRelay(\"\/topic\")<\/code> metodu, <code>\/topic<\/code> \u00f6nekiyle ba\u015flayan mesajlar\u0131n Spring'in i\u00e7 mesaj arac\u0131s\u0131 yerine harici bir STOMP uyumlu mesaj broker'\u0131na (bu durumda RabbitMQ) y\u00f6nlendirilmesini sa\u011flar. <code>setRelayHost<\/code> ve <code>setRelayPort<\/code> ile RabbitMQ'nun STOMP adapt\u00f6r\u00fcn\u00fcn dinledi\u011fi adresi ve portu belirtiriz. Varsay\u0131lan olarak RabbitMQ, STOMP ba\u011flant\u0131lar\u0131 i\u00e7in 61613 portunu kullan\u0131r. Kimlik do\u011frulama bilgileri de burada ayarlan\u0131r. Bu yap\u0131land\u0131rma ile, bir instance'dan <code>\/topic\/public<\/code> adresine g\u00f6nderilen bir mesaj art\u0131k RabbitMQ \u00fczerinden t\u00fcm ba\u011fl\u0131 instance'lara iletilir.<\/p>\n<h3>3. Mesaj\u0131 RabbitMQ \u00dczerinden G\u00f6ndermek ve Dinlemek<\/h3>\n<p><code>ChatController<\/code> s\u0131n\u0131f\u0131m\u0131zda, mesaj\u0131 do\u011frudan <code>@SendTo<\/code> ile g\u00f6ndermek yerine, <code>SimpMessagingTemplate<\/code> kullanarak mesaj\u0131 manuel olarak RabbitMQ'ya y\u00f6nlendirebiliriz. Asl\u0131nda, yukar\u0131daki <code>enableStompBrokerRelay<\/code> yap\u0131land\u0131rmas\u0131 sayesinde, <code>@SendTo(\"\/topic\/public\")<\/code> zaten otomatik olarak RabbitMQ'ya mesaj\u0131 iletecektir. Ancak, daha fazla kontrol veya farkl\u0131 destinasyonlara mesaj g\u00f6nderme ihtiyac\u0131n\u0131z oldu\u011funda <code>SimpMessagingTemplate<\/code> kullan\u0131\u015fl\u0131d\u0131r.<\/p>\n<pre><code>\npackage com.example.chat.controller;\n\nimport com.example.chat.model.ChatMessage;\nimport org.springframework.messaging.handler.annotation.MessageMapping;\nimport org.springframework.messaging.handler.annotation.Payload;\nimport org.springframework.messaging.handler.annotation.SendTo;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.messaging.simp.SimpMessagingTemplate; \/\/ E\u011fer manuel g\u00f6ndermek istersek\n\n@Controller\npublic class ChatController {\n\n    \/\/ SimpMessagingTemplate do\u011frudan RabbitMQ'ya ba\u011fl\u0131 de\u011fildir,\n    \/\/ ancak yap\u0131land\u0131rmam\u0131z sayesinde \/topic adreslerine g\u00f6nderilen mesajlar oraya y\u00f6nlendirilir.\n    \/\/ @Autowired\n    \/\/ private SimpMessagingTemplate messagingTemplate;\n\n    @MessageMapping(\"\/chat.sendMessage\")\n    @SendTo(\"\/topic\/public\")\n    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {\n        System.out.println(\"Received message: \" + chatMessage.getContent() + \" from \" + chatMessage.getSender());\n        \/\/ messagingTemplate.convertAndSend(\"\/topic\/public\", chatMessage); \/\/ Alternatif manuel g\u00f6nderme\n        return chatMessage;\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>@SendTo<\/code> anotasyonu, yap\u0131land\u0131rmam\u0131z sayesinde zaten mesaj\u0131 RabbitMQ'ya g\u00f6nderir ve RabbitMQ da bu mesaj\u0131 <code>\/topic\/public<\/code>'e abone olan di\u011fer t\u00fcm Spring Boot instance'lar\u0131na iletir. Her instance, mesaj\u0131 ald\u0131\u011f\u0131nda kendi ba\u011fl\u0131 WebSocket istemcilerine yayar. Bu sayede, uygulaman\u0131z\u0131n birden fazla instance'\u0131 olsa bile, t\u00fcm kullan\u0131c\u0131lar ger\u00e7ek zamanl\u0131 olarak mesajlar\u0131 alabilir. Bu yakla\u015f\u0131m, Redis gibi bir payla\u015f\u0131lan bellek katman\u0131na ihtiya\u00e7 duymadan da\u011f\u0131t\u0131k bir sohbet uygulamas\u0131 olu\u015fturman\u0131n etkili bir yoludur. RabbitMQ'nun kendisi, mesajlar\u0131n g\u00fcvenilir bir \u015fekilde teslim edilmesini, mesaj kal\u0131c\u0131l\u0131\u011f\u0131n\u0131 ve y\u00fck dengelemesini y\u00f6netir, b\u00f6ylece bizim uygulama katman\u0131nda bu karma\u015f\u0131kl\u0131kla u\u011fra\u015fmam\u0131za gerek kalmaz. Kurulum ve y\u00f6netim i\u00e7in bir RabbitMQ sunucusuna ihtiyac\u0131n\u0131z olaca\u011f\u0131n\u0131 unutmay\u0131n (\u00f6rne\u011fin Docker ile kolayca \u00e7al\u0131\u015ft\u0131r\u0131labilir).<\/p>\n<h2>Mesaj Persistans\u0131 ve Ge\u00e7mi\u015fi Y\u00f6netmek \u0130\u00e7in Ne Yap\u0131lmal\u0131?<\/h2>\n<p>Ger\u00e7ek bir sohbet uygulamas\u0131nda, kullan\u0131c\u0131lar\u0131n g\u00f6nderdi\u011fi mesajlar\u0131n kal\u0131c\u0131 olarak saklanmas\u0131 kritik bir gereksinimdir. Kullan\u0131c\u0131lar uygulamadan \u00e7\u0131ksalar veya ba\u011flant\u0131lar\u0131 kopsa bile, geri d\u00f6nd\u00fcklerinde sohbet ge\u00e7mi\u015flerini g\u00f6rebilmelidirler. Ayr\u0131ca, yeni kat\u0131lan bir kullan\u0131c\u0131, \u00f6nceki mesajlar\u0131 da g\u00f6rebilmelidir. Bu nedenle, mesajlar\u0131 veritaban\u0131nda saklamam\u0131z gerekir. Spring Boot, JPA (Java Persistence API) ve pop\u00fcler veritabanlar\u0131 (PostgreSQL, MySQL vb.) ile entegrasyonu son derece kolayla\u015ft\u0131r\u0131r. Bu b\u00f6l\u00fcmde, mesaj persistans\u0131n\u0131 nas\u0131l sa\u011flayaca\u011f\u0131m\u0131z\u0131 ve sohbet ge\u00e7mi\u015fini nas\u0131l y\u00f6netece\u011fimizi inceleyece\u011fiz.<\/p>\n<h3>1. Mesaj Modeline Veritaban\u0131 Varl\u0131\u011f\u0131 \u00d6zellikleri Ekleme<\/h3>\n<p>\u00d6ncelikle, <code>ChatMessage<\/code> s\u0131n\u0131f\u0131m\u0131z\u0131 bir JPA varl\u0131\u011f\u0131 (entity) haline getirmeliyiz. Bu, s\u0131n\u0131f\u0131n veritaban\u0131nda bir tabloya kar\u015f\u0131l\u0131k gelmesini sa\u011flar. Gerekli anotasyonlar\u0131 ekleyerek, her bir mesaj\u0131n veritaban\u0131nda nas\u0131l depolanaca\u011f\u0131n\u0131 tan\u0131mlar\u0131z.<\/p>\n<pre><code>\npackage com.example.chat.model;\n\nimport jakarta.persistence.Entity;\nimport jakarta.persistence.GeneratedValue;\nimport jakarta.persistence.GenerationType;\nimport jakarta.persistence.Id;\nimport jakarta.persistence.Table;\nimport java.time.LocalDateTime;\n\n@Entity\n@Table(name = \"chat_messages\")\npublic class ChatMessage {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String sender;\n    private String content;\n    private LocalDateTime timestamp;\n\n    public ChatMessage() {\n        this.timestamp = LocalDateTime.now();\n    }\n\n    public ChatMessage(String sender, String content) {\n        this.sender = sender;\n        this.content = content;\n        this.timestamp = LocalDateTime.now();\n    }\n\n    \/\/ Getters and Setters for id, sender, content, timestamp\n    \/\/ ... (\u00f6nceki koddan kopyalanabilir, id i\u00e7in getter\/setter eklenmeli)\n    public Long getId() {\n        return id;\n    }\n\n    public void setId(Long id) {\n        this.id = id;\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>@Entity<\/code> anotasyonu bu s\u0131n\u0131f\u0131n bir JPA varl\u0131\u011f\u0131 oldu\u011funu belirtir. <code>@Table(name = \"chat_messages\")<\/code> ise veritaban\u0131nda kullan\u0131lacak tablo ad\u0131n\u0131 tan\u0131mlar. <code>@Id<\/code> ile birincil anahtar\u0131, <code>@GeneratedValue<\/code> ile de birincil anahtar\u0131n otomatik olarak \u00fcretilme stratejisini belirtiriz. Bu \u00f6rnekte, PostgreSQL'in <code>SERIAL<\/code> tipi gibi otomatik artan bir de\u011fer i\u00e7in <code>GenerationType.IDENTITY<\/code> kullan\u0131lm\u0131\u015ft\u0131r.<\/p>\n<h3>2. Mesajlar\u0131 Kaydetmek \u0130\u00e7in Repository Olu\u015fturma<\/h3>\n<p>Spring Data JPA, veritaban\u0131 i\u015flemlerini kolayla\u015ft\u0131rmak i\u00e7in repository aray\u00fczleri sa\u011flar. <code>JpaRepository<\/code>'den t\u00fcreyen bir aray\u00fcz olu\u015fturarak, temel CRUD (Create, Read, Update, Delete) operasyonlar\u0131n\u0131 \u00fccretsiz elde ederiz.<\/p>\n<pre><code>\npackage com.example.chat.repository;\n\nimport com.example.chat.model.ChatMessage;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\n\n@Repository\npublic interface ChatMessageRepository extends JpaRepository<ChatMessage, Long> {\n    List<ChatMessage> findTop50ByOrderByTimestampDesc(); \/\/ En son 50 mesaj\u0131 almak i\u00e7in\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>findTop50ByOrderByTimestampDesc()<\/code> gibi metod adland\u0131rma kurallar\u0131 sayesinde, Spring Data JPA bize otomatik olarak bu sorgular\u0131 olu\u015fturur. Bu, sohbet ge\u00e7mi\u015fini al\u0131rken \u00e7ok kullan\u0131\u015fl\u0131 olacakt\u0131r.<\/p>\n<h3>3. Mesajlar\u0131 Kaydetme ve Ge\u00e7mi\u015fi Y\u00fckleme<\/h3>\n<p>\u015eimdi <code>ChatController<\/code> s\u0131n\u0131f\u0131m\u0131z\u0131, gelen mesajlar\u0131 veritaban\u0131na kaydedecek ve istemcilerin sohbet ge\u00e7mi\u015fini talep etmesi durumunda bu ge\u00e7mi\u015fi sunacak \u015fekilde g\u00fcncelleyelim.<\/p>\n<pre><code>\npackage com.example.chat.controller;\n\nimport com.example.chat.model.ChatMessage;\nimport com.example.chat.repository.ChatMessageRepository;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.messaging.handler.annotation.MessageMapping;\nimport org.springframework.messaging.handler.annotation.Payload;\nimport org.springframework.messaging.handler.annotation.SendTo;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport java.util.List;\n\n@Controller\npublic class ChatController {\n\n    @Autowired\n    private ChatMessageRepository chatMessageRepository;\n\n    @MessageMapping(\"\/chat.sendMessage\")\n    @SendTo(\"\/topic\/public\")\n    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {\n        System.out.println(\"Received message: \" + chatMessage.getContent() + \" from \" + chatMessage.getSender());\n        ChatMessage savedMessage = chatMessageRepository.save(chatMessage); \/\/ Mesaj\u0131 veritaban\u0131na kaydet\n        return savedMessage; \/\/ Kaydedilen mesaj\u0131 geri d\u00f6nd\u00fcr\n    }\n\n    \/\/ Sohbet ge\u00e7mi\u015fini getiren REST u\u00e7 noktas\u0131\n    @GetMapping(\"\/chat\/history\")\n    @ResponseBody\n    public List<ChatMessage> getChatHistory() {\n        return chatMessageRepository.findTop50ByOrderByTimestampDesc(); \/\/ En son 50 mesaj\u0131 al\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p><code>sendMessage<\/code> metodunda, gelen <code>chatMessage<\/code> nesnesini <code>chatMessageRepository.save(chatMessage)<\/code> ile veritaban\u0131na kaydediyoruz. Bu i\u015flem, mesaj\u0131n kal\u0131c\u0131 olmas\u0131n\u0131 sa\u011flar. Ayr\u0131ca, yeni bir <code>\/chat\/history<\/code> HTTP GET u\u00e7 noktas\u0131 ekledik. Bu u\u00e7 nokta, yeni ba\u011flanan veya ge\u00e7mi\u015fi g\u00f6rmek isteyen istemciler taraf\u0131ndan kullan\u0131labilir. <code>chatMessageRepository.findTop50ByOrderByTimestampDesc()<\/code> metodu sayesinde, veritaban\u0131ndan en son 50 mesaj\u0131 kolayca \u00e7ekip istemciye sunabiliriz. Bu REST u\u00e7 noktas\u0131, kullan\u0131c\u0131lar bir sohbet odas\u0131na kat\u0131ld\u0131\u011f\u0131nda veya uygulaman\u0131n ana sohbet ekran\u0131 y\u00fcklendi\u011finde AJAX \u00e7a\u011fr\u0131lar\u0131 ile \u00e7a\u011fr\u0131labilir.<\/p>\n<aside class=\"expert-tip\">\n        Uzman \u0130pucu: B\u00fcy\u00fck \u00f6l\u00e7ekli uygulamalarda, sohbet ge\u00e7mi\u015fini almak i\u00e7in sayfalama (pagination) kullanmay\u0131 d\u00fc\u015f\u00fcnebilirsiniz. \u00d6rne\u011fin, <code>Pageable<\/code> parametresi alarak, belirli bir sayfa ve boyutta mesajlar\u0131 \u00e7ekebilirsiniz. Bu, sunucu \u00fczerindeki y\u00fck\u00fc azalt\u0131r ve daha iyi bir kullan\u0131c\u0131 deneyimi sunar. Ayr\u0131ca, indeksleme (indexing) stratejilerini do\u011fru bir \u015fekilde uygulayarak veritaban\u0131 sorgular\u0131n\u0131n performans\u0131n\u0131 art\u0131rmak da \u00f6nemlidir. <code>timestamp<\/code> alan\u0131 \u00fczerinde bir indeks, tarih bazl\u0131 s\u0131ralama sorgular\u0131n\u0131 h\u0131zland\u0131racakt\u0131r.<br \/>\n    <\/aside>\n<p>Veritaban\u0131 ba\u011flant\u0131 bilgilerini (URL, kullan\u0131c\u0131 ad\u0131, \u015fifre) <code>application.properties<\/code> veya <code>application.yml<\/code> dosyas\u0131na eklemeniz gerekti\u011fini unutmay\u0131n. \u00d6rnek bir <code>application.properties<\/code> yap\u0131land\u0131rmas\u0131:<\/p>\n<pre><code>\nspring.datasource.url=jdbc:postgresql:\/\/localhost:5432\/chatdb\nspring.datasource.username=user\nspring.datasource.password=password\nspring.jpa.hibernate.ddl-auto=update\nspring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect\n    <\/pre>\n<p><\/code><\/p>\n<p>Bu yap\u0131land\u0131rma ile, Spring Boot uygulaman\u0131z her ba\u015flat\u0131ld\u0131\u011f\u0131nda veritaban\u0131 \u015femas\u0131n\u0131 otomatik olarak g\u00fcncelleyebilir ve PostgreSQL ile etkile\u015fim kurabilir. Mesajlar\u0131n kal\u0131c\u0131 hale getirilmesi ve ge\u00e7mi\u015fin y\u00f6netilmesi, da\u011f\u0131t\u0131k bir sohbet uygulamas\u0131n\u0131n temel ta\u015flar\u0131ndan biridir. Bu sayede, uygulaman\u0131z\u0131n dayan\u0131kl\u0131l\u0131\u011f\u0131 ve kullan\u0131c\u0131 deneyimi \u00f6nemli \u00f6l\u00e7\u00fcde art\u0131r\u0131lm\u0131\u015f olur.<\/p>\n<h2>Y\u00fck Dengeleme ve Oturum Y\u00f6netimi Stratejileri Nelerdir?<\/h2>\n<p>Da\u011f\u0131t\u0131k bir chat uygulamas\u0131n\u0131n en kritik bile\u015fenlerinden biri y\u00fck dengelemedir (load balancing) ve bununla do\u011frudan ili\u015fkili olan oturum y\u00f6netimidir. Uygulaman\u0131z\u0131n birden fazla instance'\u0131 \u00e7al\u0131\u015f\u0131rken, gelen istemci isteklerinin (hem HTTP hem de WebSocket) bu instance'lar aras\u0131nda verimli bir \u015fekilde da\u011f\u0131t\u0131lmas\u0131 gerekir. Ayr\u0131ca, bir kullan\u0131c\u0131n\u0131n WebSocket ba\u011flant\u0131s\u0131 kurulduktan sonra, bu ba\u011flant\u0131n\u0131n hangi instance'a ait oldu\u011funun ve oturum bilgilerinin nas\u0131l y\u00f6netilece\u011finin net bir stratejiye ihtiyac\u0131 vard\u0131r.<\/p>\n<h3>1. Y\u00fck Dengeleme Mekanizmalar\u0131<\/h3>\n<p>Y\u00fck dengeleyiciler, genellikle HTTP trafi\u011fini da\u011f\u0131tmak i\u00e7in kullan\u0131l\u0131r ancak modern y\u00fck dengeleyiciler (\u00f6rne\u011fin Nginx, HAProxy, AWS ELB\/ALB) WebSocket trafi\u011fini de destekler. Temel da\u011f\u0131t\u0131m algoritmalar\u0131 \u015funlar\u0131 i\u00e7erir:<\/p>\n<ul>\n<li><strong>Round Robin:<\/strong> \u0130stekleri s\u0131rayla sunuculara da\u011f\u0131t\u0131r. Basit ve etkilidir, ancak sunucular\u0131n farkl\u0131 y\u00fck alt\u0131nda oldu\u011funu g\u00f6z ard\u0131 edebilir.<\/li>\n<li><strong>Least Connections:<\/strong> En az aktif ba\u011flant\u0131ya sahip sunucuya istek g\u00f6nderir. Daha dinamik bir da\u011f\u0131t\u0131m sa\u011flar.<\/li>\n<li><strong>IP Hash:<\/strong> \u0130stemcinin IP adresine g\u00f6re iste\u011fi belirli bir sunucuya y\u00f6nlendirir. Bu, \"sticky session\" (yap\u0131\u015fkan oturum) elde etmenin bir yoludur.<\/li>\n<\/ul>\n<h3>2. WebSocket ve Sticky Session (Yap\u0131\u015fkan Oturum)<\/h3>\n<p>WebSocket ba\u011flant\u0131lar\u0131, HTTP ba\u011flant\u0131lar\u0131ndan farkl\u0131d\u0131r \u00e7\u00fcnk\u00fc s\u00fcrekli a\u00e7\u0131kt\u0131rlar. Bir istemci bir kez bir sunucuya ba\u011fland\u0131\u011f\u0131nda, o sunucuya ba\u011fl\u0131 kalmas\u0131 genellikle istenir. Buna \"sticky session\" veya \"session affinity\" denir. Bunun birka\u00e7 nedeni vard\u0131r:<\/p>\n<ul>\n<li><strong>Durum Y\u00f6netimi:<\/strong> Uygulaman\u0131zda, kullan\u0131c\u0131n\u0131n belirli bir WebSocket ba\u011flant\u0131s\u0131na \u00f6zg\u00fc bellek i\u00e7i durum (\u00f6rne\u011fin, abonelik listesi, anl\u0131k kullan\u0131c\u0131 tercihleri) varsa, bu durumun tutarl\u0131l\u0131\u011f\u0131n\u0131 sa\u011flamak i\u00e7in istemcinin her zaman ayn\u0131 sunucuya y\u00f6nlendirilmesi gerekir.<\/li>\n<li><strong>Performans:<\/strong> Her yeni ba\u011flant\u0131da farkl\u0131 bir sunucuya y\u00f6nlendirilmek, yeniden ba\u011flant\u0131 maliyetlerini ve durum senkronizasyon maliyetlerini art\u0131rabilir.<\/li>\n<\/ul>\n<p>Sticky session'lar genellikle y\u00fck dengeleyici seviyesinde yap\u0131land\u0131r\u0131l\u0131r. \u00d6rne\u011fin, bir y\u00fck dengeleyici, bir istemcinin ilk ba\u011flant\u0131 iste\u011finden sonra belirli bir sunucuya y\u00f6nlendirildi\u011fini kaydeden bir \u00e7erez (cookie) ayarlayabilir. Sonraki t\u00fcm istekler (ayn\u0131 oturum i\u00e7indeki WebSocket ba\u011flant\u0131s\u0131 dahil), bu \u00e7erez sayesinde ayn\u0131 sunucuya y\u00f6nlendirilir. Ancak, bu yakla\u015f\u0131m\u0131n baz\u0131 dezavantajlar\u0131 vard\u0131r:<\/p>\n<ul>\n<li><strong>Sunucu Hatas\u0131:<\/strong> E\u011fer bir sunucu \u00e7\u00f6kerse, o sunucuya ba\u011fl\u0131 t\u00fcm istemcilerin ba\u011flant\u0131s\u0131 kesilir ve yeniden ba\u011flanmalar\u0131 gerekir. Sticky session olmadan, istemciler otomatik olarak \u00e7al\u0131\u015fan ba\u015fka bir sunucuya y\u00f6nlendirilebilirdi.<\/li>\n<li><strong>Y\u00fck Dengesizli\u011fi:<\/strong> Baz\u0131 sunucular daha fazla sticky ba\u011flant\u0131 al\u0131rken, di\u011ferleri daha az alarak y\u00fck\u00fcn dengesiz da\u011f\u0131lmas\u0131na neden olabilir.<\/li>\n<\/ul>\n<aside class=\"expert-tip\">\n        Uzman \u0130pucu: Sticky session kullanmak, basit da\u011f\u0131t\u0131k sohbet uygulamalar\u0131 i\u00e7in iyi bir ba\u015flang\u0131\u00e7 olabilir, \u00e7\u00fcnk\u00fc bellek i\u00e7i durum y\u00f6netimi basitle\u015fir. Ancak y\u00fcksek \u00f6l\u00e7ekli ve hata toleransl\u0131 sistemler i\u00e7in, sunucular\u0131n \"stateless\" (durumsuz) olmas\u0131 ve oturum bilgilerinin harici, payla\u015f\u0131lan bir depoda (\u00f6rne\u011fin veritaban\u0131 veya Redis gibi bir payla\u015f\u0131ml\u0131 cache) tutulmas\u0131 daha iyi bir yakla\u015f\u0131md\u0131r. Bizim \"Redis'siz\" senaryomuzda, RabbitMQ kullanarak sunucular aras\u0131 mesajla\u015fmay\u0131 sa\u011flad\u0131\u011f\u0131m\u0131z i\u00e7in, sunucular asl\u0131nda b\u00fcy\u00fck \u00f6l\u00e7\u00fcde durumsuzdur ve sticky session zorunlu de\u011fildir, ancak yine de performans veya belirli bellek i\u00e7i optimizasyonlar i\u00e7in tercih edilebilir. E\u011fer t\u00fcm mesajlar RabbitMQ \u00fczerinden ge\u00e7iyorsa, herhangi bir instance'\u0131n herhangi bir mesaj\u0131 al\u0131p iletebilmesi gerekir.<br \/>\n    <\/aside>\n<h3>3. Oturum Y\u00f6netimi ve Kimlik Do\u011frulama<\/h3>\n<p>WebSocket ba\u011flant\u0131lar\u0131nda kimlik do\u011frulama, genellikle ilk HTTP el s\u0131k\u0131\u015fmas\u0131 (handshake) s\u0131ras\u0131nda yap\u0131l\u0131r. Spring Security ile entegrasyon, bu s\u00fcreci kolayla\u015ft\u0131r\u0131r. Kullan\u0131c\u0131lar, \u00f6rne\u011fin bir JWT (JSON Web Token) veya standart oturum \u00e7erezleri kullanarak kimliklerini do\u011frulayabilirler. El s\u0131k\u0131\u015fma ba\u015far\u0131l\u0131 olduktan sonra, WebSocket ba\u011flant\u0131s\u0131 kurulur ve kullan\u0131c\u0131 kimli\u011fi sunucu taraf\u0131nda temsil edilebilir. Spring'in <code>Principal<\/code> objesi, ba\u011fl\u0131 kullan\u0131c\u0131n\u0131n kimli\u011fini temsil etmek i\u00e7in kullan\u0131labilir.<\/p>\n<pre><code>\n\/\/ WebSocketConfig s\u0131n\u0131f\u0131nda, iste\u011fe \u00f6zel bir HandshakeInterceptor ekleyerek kimlik do\u011frulama yapabilirsiniz.\nimport org.springframework.web.socket.server.HandshakeInterceptor;\nimport org.springframework.http.server.ServerHttpRequest;\nimport org.springframework.http.server.ServerHttpResponse;\nimport org.springframework.web.socket.WebSocketHandler;\nimport java.util.Map;\n\n\/\/ ... WebSocketConfig s\u0131n\u0131f\u0131 i\u00e7inde\n@Override\npublic void registerStompEndpoints(StompEndpointRegistry registry) {\n    registry.addEndpoint(\"\/ws\")\n            .setAllowedOriginPatterns(\"*\") \/\/ CORS ayarlar\u0131\n            .withSockJS()\n            .setInterceptors(new HttpHandshakeInterceptor()); \/\/ \u00d6zel Handshake Interceptor\n}\n\n\/\/ HttpHandshakeInterceptor s\u0131n\u0131f\u0131\nclass HttpHandshakeInterceptor implements HandshakeInterceptor {\n    @Override\n    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,\n                                   WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {\n        \/\/ HTTP oturumundan veya JWT'den kullan\u0131c\u0131 bilgisini al\u0131p attributes'a ekleyin\n        \/\/ \u00d6rne\u011fin: attributes.put(\"username\", \"someUser\");\n        \/\/ Kimlik do\u011frulama ba\u015far\u0131s\u0131z olursa false d\u00f6nd\u00fcrerek ba\u011flant\u0131y\u0131 reddedin.\n        System.out.println(\"Handshake Request: \" + request.getURI());\n        return true;\n    }\n\n    @Override\n    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,\n                                  WebSocketHandler wsHandler, Exception exception) {\n        System.out.println(\"Handshake Completed.\");\n    }\n}\n    <\/pre>\n<p><\/code><\/p>\n<p>Bu <code>HttpHandshakeInterceptor<\/code> \u00f6rne\u011fi, WebSocket ba\u011flant\u0131s\u0131 kurulmadan \u00f6nce kimlik do\u011frulama mant\u0131\u011f\u0131n\u0131 uygulaman\u0131z i\u00e7in bir yer sa\u011flar. Buradaki <code>attributes<\/code> haritas\u0131na eklenen veriler, WebSocket oturumu boyunca eri\u015filebilir olacakt\u0131r. Dolay\u0131s\u0131yla, kimlik do\u011frulama ba\u015far\u0131l\u0131 olursa, kullan\u0131c\u0131n\u0131n kimlik bilgileri bu haritaya eklenir ve daha sonra <code>@MessageMapping<\/code> metotlar\u0131nda <code>Principal<\/code> objesi ile eri\u015filebilir hale gelir. Bu sayede, g\u00f6nderilen her mesaj\u0131n hangi kullan\u0131c\u0131dan geldi\u011fi g\u00fcvenli bir \u015fekilde do\u011frulanabilir. Kimlik do\u011frulama ve yetkilendirme, herhangi bir da\u011f\u0131t\u0131k sistemin temel g\u00fcvenlik gereksinimlerindendir ve WebSocket tabanl\u0131 uygulamalarda da do\u011fru bir \u015fekilde ele al\u0131nmal\u0131d\u0131r.<\/p>\n<h2>G\u00fcvenlik ve Performans \u0130pu\u00e7lar\u0131 Nelerdir?<\/h2>\n<p>Bir da\u011f\u0131t\u0131k sohbet uygulamas\u0131n\u0131 ba\u015far\u0131l\u0131 bir \u015fekilde hayata ge\u00e7irmek, sadece temel i\u015flevselli\u011fi sa\u011flamakla kalmaz, ayn\u0131 zamanda uygulaman\u0131n g\u00fcvenli\u011fini ve performans\u0131n\u0131 da g\u00f6z \u00f6n\u00fcnde bulundurmay\u0131 gerektirir. Y\u00fcz binlerce kullan\u0131c\u0131n\u0131n e\u015fzamanl\u0131 olarak mesajla\u015ft\u0131\u011f\u0131 bir ortamda, g\u00fcvenlik a\u00e7\u0131klar\u0131 ciddi sonu\u00e7lar do\u011furabilirken, performans sorunlar\u0131 k\u00f6t\u00fc bir kullan\u0131c\u0131 deneyimine yol a\u00e7abilir. Bu b\u00f6l\u00fcmde, Spring Boot tabanl\u0131 da\u011f\u0131t\u0131k sohbet uygulaman\u0131z\u0131n g\u00fcvenli\u011fini art\u0131rmak ve performans\u0131n\u0131 optimize etmek i\u00e7in uygulayabilece\u011finiz baz\u0131 \u00f6nemli ipu\u00e7lar\u0131na de\u011finece\u011fiz.<\/p>\n<h3>1. G\u00fcvenlik \u0130pu\u00e7lar\u0131<\/h3>\n<p>G\u00fcvenlik, \u00e7ok kullan\u0131c\u0131l\u0131 bir platformda her zaman \u00f6ncelikli olmal\u0131d\u0131r. \u0130\u015fte dikkat etmeniz gerekenler:<\/p>\n<ul>\n<li><strong>Kimlik Do\u011frulama ve Yetkilendirme (Authentication & Authorization):<\/strong>\n<ul>\n<li><strong>WebSocket Handshake G\u00fcvenli\u011fi:<\/strong> WebSocket ba\u011flant\u0131s\u0131 kurulmadan \u00f6nce kullan\u0131c\u0131lar\u0131n kimli\u011fini do\u011frulad\u0131\u011f\u0131n\u0131zdan emin olun. Genellikle bu, HTTP el s\u0131k\u0131\u015fmas\u0131 s\u0131ras\u0131nda JWT (JSON Web Token) veya oturum \u00e7erezleri kullan\u0131larak yap\u0131l\u0131r. Spring Security, bu s\u00fcre\u00e7leri y\u00f6netmek i\u00e7in m\u00fckemmel entegrasyonlar sunar. Yukar\u0131daki b\u00f6l\u00fcmde g\u00f6sterdi\u011fimiz <code>HandshakeInterceptor<\/code> bu ama\u00e7 i\u00e7in kullan\u0131labilir.<\/li>\n<li><strong>Mesaj Baz\u0131nda Yetkilendirme:<\/strong> Sadece belirli kullan\u0131c\u0131lara veya rollere sahip kullan\u0131c\u0131lar\u0131n belirli sohbet odalar\u0131na mesaj g\u00f6ndermesine veya mesaj almas\u0131na izin verin. Spring Security'nin mesaj g\u00fcvenli\u011fi mod\u00fcl\u00fc, STOMP mesajlar\u0131 \u00fczerinde method seviyesinde yetkilendirme uygulaman\u0131za olanak tan\u0131r. \u00d6rne\u011fin, <code>@PreAuthorize(\"hasRole('USER')\")<\/code> ile bir kullan\u0131c\u0131n\u0131n yetkisini kontrol edebilirsiniz.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Giri\u015f Do\u011frulamas\u0131 (Input Validation):<\/strong> \u0130stemciden gelen t\u00fcm mesaj i\u00e7eriklerini sunucu taraf\u0131nda do\u011frulay\u0131n. K\u00f6t\u00fc ama\u00e7l\u0131 betik enjeksiyonlar\u0131n\u0131 (XSS - Cross-Site Scripting) veya SQL enjeksiyonlar\u0131n\u0131 \u00f6nlemek i\u00e7in metin giri\u015flerini temizleyin ve uygun \u015fekilde sanitize edin. \u00d6rne\u011fin, HTML etiketlerini filtreleyebilir veya yaln\u0131zca d\u00fcz metne izin verebilirsiniz.<\/li>\n<li><strong>Ta\u015f\u0131ma G\u00fcvenli\u011fi (Transport Security - WSS):<\/strong> WebSocket ba\u011flant\u0131lar\u0131n\u0131z\u0131 her zaman TLS\/SSL \u00fczerinden (<code>wss:\/\/<\/code>) \u015fifreleyin. Bu, mesajlar\u0131n a\u011f \u00fczerinde ele ge\u00e7irilmesini ve okunmas\u0131n\u0131 engeller. \u00dcretim ortamlar\u0131nda <code>ws:\/\/<\/code> kullanmaktan ka\u00e7\u0131n\u0131n.<\/li>\n<li><strong>CORS Politikalar\u0131:<\/strong> Uygulaman\u0131z\u0131n yaln\u0131zca g\u00fcvenilen kaynaklardan (origin) WebSocket ba\u011flant\u0131lar\u0131n\u0131 kabul etti\u011finden emin olun. <code>WebSocketConfig<\/code> s\u0131n\u0131f\u0131n\u0131zdaki <code>setAllowedOriginPatterns()<\/code> metodu bu ama\u00e7la kullan\u0131l\u0131r. Y\u0131ld\u0131z (*) kullanmak yerine belirli alan adlar\u0131n\u0131 listelemek daha g\u00fcvenlidir.<\/li>\n<\/ul>\n<h3>2. Performans \u0130pu\u00e7lar\u0131<\/h3>\n<p>Y\u00fcksek performans, kullan\u0131c\u0131lar\u0131n uygulaman\u0131z\u0131 severek kullanmas\u0131n\u0131 sa\u011flar. \u0130\u015fte performans iyile\u015ftirmeleri i\u00e7in baz\u0131 \u00f6neriler:<\/p>\n<ul>\n<li><strong>Verimli Mesaj Formatlar\u0131:<\/strong> Mesaj boyutunu minimumda tutmak i\u00e7in verimli veri formatlar\u0131 kullan\u0131n (\u00f6rne\u011fin JSON). Gereksiz veri g\u00f6ndermek a\u011f trafi\u011fini ve i\u015fleme s\u00fcresini art\u0131r\u0131r.<\/li>\n<li><strong>Veritaban\u0131 \u0130ndeksleri:<\/strong> Sohbet mesajlar\u0131 tablonuzdaki <code>timestamp<\/code> ve di\u011fer s\u0131k\u00e7a sorgulanan s\u00fctunlara uygun indeksler ekleyin. \u00d6zellikle sohbet ge\u00e7mi\u015fini \u00e7ekerken sorgu performans\u0131n\u0131 b\u00fcy\u00fck \u00f6l\u00e7\u00fcde art\u0131r\u0131r.<\/li>\n<li><strong>Ba\u011flant\u0131 Havuzlar\u0131:<\/strong> Veritaban\u0131 ba\u011flant\u0131 havuzunuzun (\u00f6rne\u011fin HikariCP) do\u011fru yap\u0131land\u0131r\u0131ld\u0131\u011f\u0131ndan emin olun. Yeterli ba\u011flant\u0131 say\u0131s\u0131 ve do\u011fru zaman a\u015f\u0131m\u0131 ayarlar\u0131, veritaban\u0131 performans\u0131n\u0131 etkiler.<\/li>\n<li><strong>Asenkron \u0130\u015fleme:<\/strong> Uzun s\u00fcren veya bloklay\u0131c\u0131 olabilecek i\u015flemleri (\u00f6rne\u011fin, resim i\u015fleme, bildirim g\u00f6nderme) asenkron olarak arka planda \u00e7al\u0131\u015ft\u0131r\u0131n. Spring'in <code>@Async<\/code> anotasyonu bu konuda yard\u0131mc\u0131 olabilir.<\/li>\n<li><strong>Y\u00fck Testi:<\/strong> Uygulaman\u0131z\u0131 ger\u00e7ek d\u00fcnya senaryolar\u0131n\u0131 taklit eden y\u00fck testleriyle d\u00fczenli olarak test edin. Apache JMeter, Gatling veya Locust gibi ara\u00e7lar, e\u015fzamanl\u0131 kullan\u0131c\u0131lar\u0131 sim\u00fcle ederek darbo\u011fazlar\u0131 tespit etmenize yard\u0131mc\u0131 olabilir.<\/li>\n<li><strong>RabbitMQ Optimizasyonu:<\/strong> RabbitMQ kurulumunuzu mesaj kal\u0131c\u0131l\u0131\u011f\u0131 (durability), mesaj do\u011frulamas\u0131 (acknowledgements) ve consumer say\u0131lar\u0131 a\u00e7\u0131s\u0131ndan optimize edin. Y\u00fcksek mesaj hacimlerinde do\u011fru RabbitMQ yap\u0131land\u0131rmas\u0131 kritik \u00f6neme sahiptir. Ayr\u0131ca, mesajlar\u0131 farkl\u0131 kuyruklar veya de\u011fi\u015fimler (exchanges) aras\u0131nda b\u00f6l\u00fcmlere ay\u0131rmak (sharding) da \u00f6l\u00e7eklenebilirli\u011fi art\u0131rabilir.<\/li>\n<li><strong>Sunucu \u0130zleme ve G\u00fcnl\u00fckleme (Monitoring & Logging):<\/strong> Uygulama ve altyap\u0131 metriklerini (CPU, bellek, a\u011f trafi\u011fi, RabbitMQ kuyruk uzunluklar\u0131) s\u00fcrekli izleyin. Prometheus, Grafana, ELK Stack gibi ara\u00e7lar performans sorunlar\u0131n\u0131 erken tespit etmenize yard\u0131mc\u0131 olur. Detayl\u0131 g\u00fcnl\u00fck kayd\u0131 (logging) da sorun giderme i\u00e7in vazge\u00e7ilmezdir.<\/li>\n<li><strong>Garbage Collection (\u00c7\u00f6p Toplama) Ayarlar\u0131:<\/strong> JVM'in \u00e7\u00f6p toplama mekanizmas\u0131n\u0131 uygulaman\u0131z\u0131n bellek kullan\u0131m\u0131na ve gecikme s\u00fcresi gereksinimlerine g\u00f6re ayarlay\u0131n. \u00d6zellikle y\u00fcksek bellek t\u00fcketimi olan uygulamalarda bu, uygulaman\u0131n tepkiselli\u011fini etkileyebilir.<\/li>\n<\/ul>\n<p>Bu g\u00fcvenlik ve performans ipu\u00e7lar\u0131n\u0131 dikkate alarak, Spring Boot ile geli\u015ftirdi\u011finiz da\u011f\u0131t\u0131k sohbet uygulamas\u0131n\u0131 daha sa\u011flam, g\u00fcvenli ve verimli hale getirebilirsiniz. Bu, kullan\u0131c\u0131lar\u0131n\u0131z\u0131n uygulaman\u0131za g\u00fcvenmesini ve sorunsuz bir deneyim ya\u015famas\u0131n\u0131 sa\u011flayacakt\u0131r.<\/p>\n<h2>Ger\u00e7ek Bir Senaryo: B\u00fcy\u00fck \u00d6l\u00e7ekli Bir Etkinlik Platformu \u0130\u00e7in Da\u011f\u0131t\u0131k Sohbet<\/h2>\n<p>\u015eimdiye kadar ele ald\u0131\u011f\u0131m\u0131z t\u00fcm kavramlar\u0131 ve teknikleri, ger\u00e7ek d\u00fcnyadan bir senaryo \u00fczerinde nas\u0131l uygulayabilece\u011fimizi inceleyelim. B\u00fcy\u00fck \u00f6l\u00e7ekli bir online etkinlik veya konferans platformu d\u00fc\u015f\u00fcn\u00fcn. Bu platform, binlerce e\u015fzamanl\u0131 kullan\u0131c\u0131n\u0131n farkl\u0131 sanal odalarda (\u00f6rne\u011fin, farkl\u0131 sunumlar veya paneller i\u00e7in) etkile\u015fime girmesine olanak tan\u0131yan canl\u0131 sohbet \u00f6zelliklerine sahip olmal\u0131d\u0131r. Bu senaryo, da\u011f\u0131t\u0131k bir sohbet uygulamas\u0131n\u0131n kar\u015f\u0131la\u015ft\u0131\u011f\u0131 tipik zorluklar\u0131 ve \u00e7\u00f6z\u00fcmlerini a\u00e7\u0131k\u00e7a ortaya koyar.<\/p>\n<h3>Vaka Analizi: Global Teknoloji Zirvesi Canl\u0131 Sohbet Sistemi<\/h3>\n<p><strong>Problem:<\/strong> Global bir teknoloji zirvesi, COVID-19 pandemisi nedeniyle tamamen \u00e7evrimi\u00e7i d\u00fczenleniyor. Organizat\u00f6rler, her biri 500 ila 2000 ki\u015fi aras\u0131nda de\u011fi\u015fen kat\u0131l\u0131mc\u0131 kapasitesine sahip 20 farkl\u0131 e\u015fzamanl\u0131 oturum odas\u0131 (konu\u015fma, panel, at\u00f6lyesi) olu\u015fturmak istiyor. Her odan\u0131n kendine ait bir canl\u0131 sohbet alan\u0131 olacak. Toplamda 20.000 ila 40.000 aras\u0131nda e\u015fzamanl\u0131 aktif sohbet kullan\u0131c\u0131s\u0131 bekleniyor. Sistemin y\u00fcksek kullan\u0131labilirlik, d\u00fc\u015f\u00fck gecikme s\u00fcresi ve g\u00fcvenilirlik sunmas\u0131 gerekiyor.<\/p>\n<p><strong>\u00c7\u00f6z\u00fcm Mimarisi (Redis Olmadan):<\/strong><\/p>\n<ol>\n<li><strong>Spring Boot Microservisleri:<\/strong> Sohbet i\u015flevselli\u011fi, bir dizi Spring Boot uygulamas\u0131ndan olu\u015fan ba\u011f\u0131ms\u0131z bir microservis olarak tasarlan\u0131r. Bu, sohbet servisinin di\u011fer platform bile\u015fenlerinden (kay\u0131t, \u00f6deme, video ak\u0131\u015f\u0131) ayr\u0131lmas\u0131n\u0131 ve ba\u011f\u0131ms\u0131z olarak \u00f6l\u00e7eklenmesini sa\u011flar.<\/li>\n<li><strong>WebSocket ve STOMP:<\/strong> Her sohbet odas\u0131 i\u00e7in ayr\u0131 bir STOMP topic'i (\u00f6rne\u011fin, <code>\/topic\/room\/session1<\/code>, <code>\/topic\/room\/session2<\/code>) tan\u0131mlan\u0131r. Kullan\u0131c\u0131lar, kat\u0131ld\u0131klar\u0131 oturum odas\u0131na ait topic'e abone olurlar.<\/li>\n<li><strong>RabbitMQ ile Mesaj Da\u011f\u0131t\u0131m\u0131:<\/strong> Birden fazla Spring Boot sohbet microservisi instance'\u0131 (\u00f6rne\u011fin, 10-15 instance) bir y\u00fck dengeleyici arkas\u0131nda \u00e7al\u0131\u015f\u0131r. Herhangi bir kullan\u0131c\u0131 bir mesaj g\u00f6nderdi\u011finde, bu mesaj ba\u011fl\u0131 oldu\u011fu instance taraf\u0131ndan al\u0131n\u0131r, veritaban\u0131na kaydedilir ve ard\u0131ndan RabbitMQ'daki ilgili topic'e (exchange) yay\u0131nlan\u0131r. RabbitMQ, bu mesaj\u0131 ilgili topic'e abone olan t\u00fcm di\u011fer sohbet microservisi instance'lar\u0131na da\u011f\u0131t\u0131r. B\u00f6ylece, t\u00fcm instance'lar mesaj\u0131 al\u0131r ve kendi ba\u011fl\u0131 istemcilerine iletir. Bu, mesajlar\u0131n t\u00fcm odalarda ve t\u00fcm kullan\u0131c\u0131lara tutarl\u0131 bir \u015fekilde ula\u015fmas\u0131n\u0131 sa\u011flar.<\/li>\n<li><strong>PostgreSQL ile Mesaj Kal\u0131c\u0131l\u0131\u011f\u0131:<\/strong> Her mesaj, g\u00f6nderildi\u011fi anda bir PostgreSQL veritaban\u0131na kaydedilir. <code>chat_messages<\/code> tablosu, mesaj i\u00e7eri\u011fi, g\u00f6nderen ID'si, oda ID'si ve zaman damgas\u0131 gibi bilgileri depolar. Sohbet ge\u00e7mi\u015fi, kullan\u0131c\u0131lar bir odaya kat\u0131ld\u0131\u011f\u0131nda veya sayfay\u0131 yeniledi\u011finde \u00e7ekilir. PostgreSQL, y\u00fcksek trafik alt\u0131nda bile g\u00fcvenilir ve performant bir \u00e7\u00f6z\u00fcm sunar; uygun indeksleme ve ba\u011flant\u0131 havuzu y\u00f6netimiyle on binlerce mesaj\u0131 sorunsuz bir \u015fekilde i\u015fleyebilir.<\/li>\n<li><strong>Y\u00fck Dengeleyici ve Sticky Sessions (Opsiyonel):<\/strong> Bir Nginx veya AWS Application Load Balancer (ALB) \u00f6n\u00fcnde konumland\u0131r\u0131l\u0131r. ALB, WebSocket trafi\u011fini destekler. Sticky sessions, belirli bir kullan\u0131c\u0131 oturumunun her zaman ayn\u0131 Spring Boot instance'\u0131na y\u00f6nlendirilmesini sa\u011flayabilir. Bu, belirli senaryolarda performans\u0131 art\u0131rsa da, RabbitMQ'nun g\u00fcc\u00fc sayesinde zorunlu de\u011fildir ve sunucular\u0131n tamamen durumsuz kalmas\u0131na olanak tan\u0131r. Hata tolerans\u0131 a\u00e7\u0131s\u0131ndan durumsuz sunucular daha avantajl\u0131d\u0131r.<\/li>\n<li><strong>Kimlik Do\u011frulama ve Yetkilendirme:<\/strong> Her kullan\u0131c\u0131n\u0131n kimli\u011fi JWT ile do\u011frulan\u0131r. WebSocket el s\u0131k\u0131\u015fmas\u0131 s\u0131ras\u0131nda JWT do\u011frulamas\u0131 yap\u0131l\u0131r ve kullan\u0131c\u0131n\u0131n yetkileri (hangi odalara eri\u015febilece\u011fi) kontrol edilir. <code>@MessageMapping<\/code> seviyesinde Spring Security ile yetkilendirme kurallar\u0131 uygulan\u0131r, b\u00f6ylece yaln\u0131zca yetkili kullan\u0131c\u0131lar belirli odalara mesaj g\u00f6nderebilir.<\/li>\n<li><strong>Performans \u0130zleme:<\/strong> Prometheus ve Grafana kullan\u0131larak Spring Boot uygulamalar\u0131n\u0131n CPU, bellek, a\u011f I\/O, WebSocket ba\u011flant\u0131 say\u0131lar\u0131 ve RabbitMQ kuyruk uzunluklar\u0131 gibi metrikler s\u00fcrekli izlenir. Anormal durumlar i\u00e7in uyar\u0131lar ayarlan\u0131r. Bu sayede, darbo\u011fazlar h\u0131zl\u0131ca tespit edilip \u00e7\u00f6z\u00fclebilir.<\/li>\n<\/ol>\n<p><strong>Uygulama S\u00fcrecinden \u00d6\u011frenilen Dersler:<\/strong><\/p>\n<ul>\n<li><strong>Erken Test ve Y\u00fck Testi:<\/strong> Uygulaman\u0131n geli\u015ftirme a\u015famas\u0131nda bile s\u00fcrekli y\u00fck testleri yapmak, beklenmedik performans darbo\u011fazlar\u0131n\u0131 erken tespit etmeyi sa\u011flad\u0131. \u00d6zellikle RabbitMQ ve veritaban\u0131 entegrasyonunda iyile\u015ftirmeler yap\u0131ld\u0131.<\/li>\n<li><strong>Mod\u00fcler Tasar\u0131m:<\/strong> Sohbet microservisini di\u011fer mod\u00fcllerden ayr\u0131 tutmak, geli\u015ftirme ve da\u011f\u0131t\u0131m s\u00fcre\u00e7lerini basitle\u015ftirdi. Hatta, farkl\u0131 sohbet odalar\u0131 i\u00e7in farkl\u0131 microservis instance'lar\u0131 tahsis ederek kaynak izolasyonu sa\u011fland\u0131.<\/li>\n<li><strong>Hata Tolerans\u0131:<\/strong> RabbitMQ'nun mesaj kal\u0131c\u0131l\u0131\u011f\u0131 ve Spring Boot instance'lar\u0131n\u0131n durumsuz olmas\u0131 sayesinde, bir sunucu \u00e7\u00f6kse bile kullan\u0131c\u0131lar\u0131n ba\u011flant\u0131lar\u0131 otomatik olarak ba\u015fka bir instance'a y\u00f6nlendirildi ve mesaj ak\u0131\u015f\u0131 kesintisiz devam etti. Veritaban\u0131 cluster'\u0131 sayesinde veri kayb\u0131 riski minimize edildi.<\/li>\n<li><strong>Geli\u015fmi\u015f Veritaban\u0131 \u0130ndeksleme:<\/strong> Sohbet ge\u00e7mi\u015fi sorgular\u0131n\u0131n (\u00f6zellikle b\u00fcy\u00fck odalar i\u00e7in) performans\u0131 kritikti. <code>timestamp<\/code>, <code>room_id<\/code> ve <code>sender_id<\/code> alanlar\u0131na do\u011fru indeksler eklenerek sorgu s\u00fcreleri milisaniyelere \u00e7ekildi.<\/li>\n<li><strong>A\u011f Yap\u0131land\u0131rmas\u0131:<\/strong> Y\u00fck dengeleyicinin ve g\u00fcvenlik duvarlar\u0131n\u0131n do\u011fru yap\u0131land\u0131r\u0131lmas\u0131, WebSocket ba\u011flant\u0131lar\u0131n\u0131n istikrarl\u0131 olmas\u0131n\u0131 sa\u011flad\u0131. \u00d6zellikle uzun s\u00fcreli ba\u011flant\u0131lar i\u00e7in TCP zaman a\u015f\u0131m\u0131 ayarlar\u0131 optimize edildi.<\/li>\n<\/ul>\n<p>Bu vaka analizi, Redis gibi spesifik bir teknolojiye ba\u011f\u0131ml\u0131 olmadan bile, Spring Boot ve uygun mesaj broker'lar\u0131 (RabbitMQ gibi) ile b\u00fcy\u00fck \u00f6l\u00e7ekli ve y\u00fcksek performansl\u0131 da\u011f\u0131t\u0131k sohbet sistemlerinin ba\u015far\u0131yla in\u015fa edilebilece\u011fini g\u00f6stermektedir. \u00d6nemli olan, da\u011f\u0131t\u0131k sistem prensiplerini anlamak ve her bile\u015fenin rol\u00fcn\u00fc do\u011fru bir \u015fekilde belirlemektir.<\/p>\n<h2>Sonu\u00e7 ve S\u0131k\u00e7a Sorulan Sorular<\/h2>\n<p>Bu kapsaml\u0131 rehber boyunca, Spring Boot kullanarak Redis'e ihtiya\u00e7 duymadan nas\u0131l \u00f6l\u00e7eklenebilir ve da\u011f\u0131t\u0131k bir sohbet uygulamas\u0131 in\u015fa edece\u011fimizi ad\u0131m ad\u0131m inceledik. Temel WebSocket ve STOMP yap\u0131land\u0131rmalar\u0131ndan ba\u015flayarak, \u00e7oklu Spring Boot instance'lar\u0131 aras\u0131nda ger\u00e7ek zamanl\u0131 mesajla\u015fmay\u0131 RabbitMQ gibi harici bir mesaj arac\u0131s\u0131 ile nas\u0131l sa\u011flad\u0131\u011f\u0131m\u0131z\u0131 g\u00f6rd\u00fck. Ayr\u0131ca, mesaj persistans\u0131n\u0131 veritaban\u0131 (PostgreSQL) kullanarak nas\u0131l y\u00f6netece\u011fimizi, y\u00fck dengeleme stratejilerini, g\u00fcvenlik \u00f6nlemlerini ve performans iyile\u015ftirmelerini detayland\u0131rd\u0131k. Son olarak, b\u00fcy\u00fck \u00f6l\u00e7ekli bir etkinlik platformu i\u00e7in ger\u00e7ek bir senaryo \u00fczerinden bu tekniklerin nas\u0131l uyguland\u0131\u011f\u0131n\u0131 analiz ettik.<\/p>\n<p>Redis, da\u011f\u0131t\u0131k sistemlerde bir\u00e7ok soruna zarif \u00e7\u00f6z\u00fcmler sunsa da, her zaman tek se\u00e7enek de\u011fildir. Bu makalede g\u00f6sterdi\u011fimiz gibi, Spring Boot'un g\u00fc\u00e7l\u00fc entegrasyon yetenekleri ve RabbitMQ gibi end\u00fcstri standard\u0131 mesaj broker'lar\u0131 ile bir araya geldi\u011finde, Redis'siz de y\u00fcksek performansl\u0131 ve g\u00fcvenilir da\u011f\u0131t\u0131k sohbet sistemleri kurmak m\u00fcmk\u00fcnd\u00fcr. Anahtar, do\u011fru mimari kararlar\u0131 vermek, uygulaman\u0131n her katman\u0131nda \u00f6l\u00e7eklenebilirli\u011fi ve hata tolerans\u0131n\u0131 g\u00f6z \u00f6n\u00fcnde bulundurmak ve g\u00fcvenlikten asla \u00f6d\u00fcn vermemektir. Umar\u0131m bu rehber, kendi da\u011f\u0131t\u0131k sohbet uygulaman\u0131z\u0131 geli\u015ftirme yolculu\u011funuzda size ilham verir ve yol g\u00f6sterir.<\/p>\n<h3>S\u0131k\u00e7a Sorulan Sorular<\/h3>\n<h4>1. Neden Redis kullanmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek isteyeyim?<\/h4>\n<p>Baz\u0131 projelerin maliyet k\u0131s\u0131tlamalar\u0131, mevcut altyap\u0131 k\u0131s\u0131tlamalar\u0131 veya belirli teknoloji tercihlerine g\u00f6re Redis kullanmak istemeyebilir. Redis'in sundu\u011fu bellek i\u00e7i cache ve pub\/sub \u00f6zelliklerini, bu makalede g\u00f6sterildi\u011fi gibi, RabbitMQ gibi bir mesaj broker'\u0131 ve bir ili\u015fkisel veritaban\u0131 (PostgreSQL) kombinasyonu ile de sa\u011flayabilirsiniz. Bu, \u00f6zellikle mevcut bir mesaj kuyru\u011fu altyap\u0131n\u0131z varsa veya daha geleneksel veritaban\u0131 \u00e7\u00f6z\u00fcmlerine y\u00f6nelmek istiyorsan\u0131z mant\u0131kl\u0131 olabilir.<\/p>\n<h4>2. RabbitMQ yerine ba\u015fka bir mesaj broker'\u0131 kullanabilir miyim?<\/h4>\n<p>Evet, kesinlikle! RabbitMQ yayg\u0131n olarak kullan\u0131lan bir se\u00e7enektir, ancak Apache Kafka veya Apache ActiveMQ gibi di\u011fer pop\u00fcler mesaj broker'lar\u0131 da benzer ihtiya\u00e7lar\u0131 kar\u015f\u0131layabilir. Her birinin kendine g\u00f6re avantajlar\u0131 ve dezavantajlar\u0131 vard\u0131r; se\u00e7iminiz projenizin \u00f6zel gereksinimlerine, \u00f6l\u00e7ek beklentilerine ve geli\u015ftirme ekibinizin deneyimine ba\u011fl\u0131 olacakt\u0131r. Spring Boot, bu broker'lar\u0131n \u00e7o\u011fuyla kolayca entegre olabilir.<\/p>\n<h4>3. Chat uygulamamda mobil uyumlulu\u011fu nas\u0131l sa\u011flayabilirim?<\/h4>\n<p>Mobil uyumluluk, kullan\u0131c\u0131 aray\u00fcz\u00fc (UI) taraf\u0131nda ele al\u0131n\u0131r. WebSocket ba\u011flant\u0131s\u0131 mobil uygulamalarda (iOS, Android) yerel SDK'lar arac\u0131l\u0131\u011f\u0131yla veya mobil taray\u0131c\u0131larda responsive web tasar\u0131m\u0131 ile sa\u011flan\u0131r. Web tabanl\u0131 bir \u00e7\u00f6z\u00fcm i\u00e7in CSS Media Query'leri veya CSS Flexbox\/Grid gibi modern d\u00fczen teknikleri kullanarak aray\u00fcz\u00fcn\u00fcz\u00fc farkl\u0131 ekran boyutlar\u0131na ve cihazlara uyumlu hale getirebilirsiniz. \u00d6rne\u011fin, k\u00fc\u00e7\u00fck ekranlar i\u00e7in sohbet penceresinin d\u00fczenini de\u011fi\u015ftirebilir, sanal klavyenin a\u00e7\u0131ld\u0131\u011f\u0131nda giri\u015f alan\u0131n\u0131n g\u00f6r\u00fcn\u00fcrl\u00fc\u011f\u00fcn\u00fc koruyabilirsiniz.<\/p>\n<pre><code>\n<style>\n  \/* Geni\u015f ekranlar i\u00e7in varsay\u0131lan stil *\/\n  .chat-container {\n    width: 80%;\n    margin: 0 auto;\n  }\n\n  \/* K\u00fc\u00e7\u00fck ekranlar i\u00e7in medya sorgusu *\/\n  @media (max-width: 768px) {\n    .chat-container {\n      width: 100%;\n      padding: 10px;\n    }\n    .message-input {\n      font-size: 0.9em;\n    }\n  }\n<\/style>\n    <\/pre>\n<p><\/code><\/p>\n<p>Bu, mobil cihazlarda sohbet uygulaman\u0131z\u0131n daha iyi g\u00f6r\u00fcnmesini ve kullan\u0131lmas\u0131n\u0131 sa\u011flar.<\/p>\n<h4>4. Sohbet mesajlar\u0131nda emoji veya dosya payla\u015f\u0131m\u0131 gibi ek \u00f6zellikler nas\u0131l eklenir?<\/h4>\n<p>Emoji deste\u011fi genellikle frontend taraf\u0131nda bir emoji se\u00e7ici entegrasyonu ve mesaj i\u00e7eri\u011finin Unicode karakterlerini desteklemesiyle sa\u011flan\u0131r. Dosya payla\u015f\u0131m\u0131 i\u00e7in ise daha karma\u015f\u0131k bir yap\u0131ya ihtiya\u00e7 vard\u0131r:<\/p>\n<ul>\n<li><strong>Dosya Y\u00fckleme:<\/strong> \u0130stemci, dosyay\u0131 do\u011frudan bir Spring Boot REST u\u00e7 noktas\u0131na (multipart\/form-data ile) veya AWS S3 gibi bir bulut depolama servisine y\u00fckler.<\/li>\n<li><strong>Dosya Bildirimi:<\/strong> Dosya y\u00fcklendikten sonra, sunucu sohbet mesaj\u0131 olarak dosyan\u0131n URL'sini veya meta verilerini di\u011fer kullan\u0131c\u0131lara g\u00f6nderir.<\/li>\n<li><strong>G\u00fcvenlik:<\/strong> Y\u00fcklenen dosyalara eri\u015fim kontrol\u00fc, k\u00f6t\u00fc ama\u00e7l\u0131 i\u00e7erik taramas\u0131 ve boyutsal s\u0131n\u0131rlamalar gibi g\u00fcvenlik \u00f6nlemleri alman\u0131z \u00f6nemlidir.<\/li>\n<\/ul>\n<h4>5. \u00c7ok odal\u0131 sohbet yap\u0131s\u0131 nas\u0131l kurulur ve y\u00f6netilir?<\/h4>\n<p>\u00c7ok odal\u0131 bir yap\u0131 i\u00e7in, her sohbet odas\u0131n\u0131 temsil eden ayr\u0131 STOMP topic'leri kullanabilirsiniz (\u00f6rne\u011fin, <code>\/topic\/room\/{roomId}<\/code>). \u0130stemciler, kat\u0131lmak istedikleri odan\u0131n topic'ine abone olur. Mesaj g\u00f6nderirken, istemci hangi odaya mesaj g\u00f6nderdi\u011fini belirten bir destinasyon kullan\u0131r (\u00f6rne\u011fin, <code>\/app\/chat.sendMessage\/{roomId}<\/code>). Sunucu taraf\u0131nda, <code>@MessageMapping(\"\/chat.sendMessage\/{roomId}\")<\/code> ile mesaj\u0131 i\u015fleyebilir ve ilgili odaya (<code>@SendTo(\"\/topic\/room\/{roomId}\")<\/code>) y\u00f6nlendirebilirsiniz. Veritaban\u0131nda mesajlar\u0131 kaydederken de her mesaj\u0131n hangi odaya ait oldu\u011funu belirten bir <code>roomId<\/code> alan\u0131 eklemelisiniz. Bu sayede, her odan\u0131n kendi mesaj ge\u00e7mi\u015fi ve ayr\u0131 bir ileti\u015fimi olur.<\/p>\n<p><\/body><\/p>\n","protected":false},"excerpt":{"rendered":"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek&hellip;","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"csco_page_header_type":"","csco_page_load_nextpost":"","csco_page_subscribe_form":"","csco_page_contact_form":"","footnotes":""},"categories":[1],"tags":[],"class_list":{"0":"post-34552","1":"post","2":"type-post","3":"status-publish","4":"format-standard","6":"category-genel","7":"cs-entry","8":"cs-video-wrap"},"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.5 (Yoast SEO v25.3.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?<\/title>\n<meta name=\"description\" content=\"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\" \/>\n<meta property=\"og:locale\" content=\"tr_TR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?\" \/>\n<meta property=\"og:description\" content=\"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\" \/>\n<meta property=\"og:site_name\" content=\"Kodlar\u0131n Gizemli D\u00fcnyas\u0131\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-18T16:31:25+00:00\" \/>\n<meta name=\"author\" content=\"Fatih Soysal\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Yazan:\" \/>\n\t<meta name=\"twitter:data1\" content=\"Fatih Soysal\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tahmini okuma s\u00fcresi\" \/>\n\t<meta name=\"twitter:data2\" content=\"37 dakika\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\"},\"author\":{\"name\":\"Fatih Soysal\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1\"},\"headline\":\"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?\",\"datePublished\":\"2025-11-18T16:31:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\"},\"wordCount\":6196,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1\"},\"inLanguage\":\"tr\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#respond\"]}],\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\",\"url\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\",\"name\":\"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?\",\"isPartOf\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#website\"},\"datePublished\":\"2025-11-18T16:31:25+00:00\",\"description\":\"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.\",\"breadcrumb\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#breadcrumb\"},\"inLanguage\":\"tr\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Anasayfa\",\"item\":\"https:\/\/fatihsoysal.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#website\",\"url\":\"https:\/\/fatihsoysal.com\/blog\/\",\"name\":\"Fatihsoysal.com\",\"description\":\"Blog - Yaz\u0131l\u0131m D\u00fcnyas\u0131 Tecr\u00fcbelerim\",\"publisher\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fatihsoysal.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"tr\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1\",\"name\":\"Fatih Soysal\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"tr\",\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/fatihsoysal.com\/blog\/wp-content\/uploads\/2024\/04\/cropped-replicate-prediction-3kgg1hgjn5rgp0cf0p5tr0jw7w-1.png\",\"contentUrl\":\"https:\/\/fatihsoysal.com\/blog\/wp-content\/uploads\/2024\/04\/cropped-replicate-prediction-3kgg1hgjn5rgp0cf0p5tr0jw7w-1.png\",\"width\":512,\"height\":512,\"caption\":\"Fatih Soysal\"},\"logo\":{\"@id\":\"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/image\/\"},\"description\":\"Kullan\u0131m ve kodlama m\u00fckemmeliyetini odak alan uygulamalar olu\u015fturma deneyimine sahip, profesyonel olarak 15+ y\u0131l \u00fczeri deneyime sahip bir yaz\u0131l\u0131m m\u00fchendisi.\",\"url\":\"https:\/\/fatihsoysal.com\/blog\/author\/fatihsoysal\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?","description":"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/","og_locale":"tr_TR","og_type":"article","og_title":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?","og_description":"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.","og_url":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/","og_site_name":"Kodlar\u0131n Gizemli D\u00fcnyas\u0131","article_published_time":"2025-11-18T16:31:25+00:00","author":"Fatih Soysal","twitter_card":"summary_large_image","twitter_misc":{"Yazan:":"Fatih Soysal","Tahmini okuma s\u00fcresi":"37 dakika"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#article","isPartOf":{"@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/"},"author":{"name":"Fatih Soysal","@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1"},"headline":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?","datePublished":"2025-11-18T16:31:25+00:00","mainEntityOfPage":{"@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/"},"wordCount":6196,"commentCount":0,"publisher":{"@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1"},"inLanguage":"tr","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#respond"]}],"copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/fatihsoysal.com\/blog\/#organization"}},{"@type":"WebPage","@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/","url":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/","name":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?","isPartOf":{"@id":"https:\/\/fatihsoysal.com\/blog\/#website"},"datePublished":"2025-11-18T16:31:25+00:00","description":"Spring Boot kullanarak Redis olmadan da\u011f\u0131t\u0131k bir chat uygulamas\u0131 geli\u015ftirmek mi istiyorsunuz? Bu kapsaml\u0131 rehberde, \u00f6l\u00e7eklenebilir ve y\u00fcksek performansl\u0131 bir mesajla\u015fma sistemi kurman\u0131n inceliklerini ad\u0131m ad\u0131m ke\u015ffedin.","breadcrumb":{"@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#breadcrumb"},"inLanguage":"tr","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/fatihsoysal.com\/blog\/spring-boot-ile-dagitik-chat-uygulamasi-nasil-gelistirilir\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Anasayfa","item":"https:\/\/fatihsoysal.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Spring Boot ile Da\u011f\u0131t\u0131k Chat Uygulamas\u0131 Nas\u0131l Geli\u015ftirilir?"}]},{"@type":"WebSite","@id":"https:\/\/fatihsoysal.com\/blog\/#website","url":"https:\/\/fatihsoysal.com\/blog\/","name":"Fatihsoysal.com","description":"Blog - Yaz\u0131l\u0131m D\u00fcnyas\u0131 Tecr\u00fcbelerim","publisher":{"@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fatihsoysal.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"tr"},{"@type":["Person","Organization"],"@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/002a254750921dcfd568a99e48240dd1","name":"Fatih Soysal","image":{"@type":"ImageObject","inLanguage":"tr","@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/fatihsoysal.com\/blog\/wp-content\/uploads\/2024\/04\/cropped-replicate-prediction-3kgg1hgjn5rgp0cf0p5tr0jw7w-1.png","contentUrl":"https:\/\/fatihsoysal.com\/blog\/wp-content\/uploads\/2024\/04\/cropped-replicate-prediction-3kgg1hgjn5rgp0cf0p5tr0jw7w-1.png","width":512,"height":512,"caption":"Fatih Soysal"},"logo":{"@id":"https:\/\/fatihsoysal.com\/blog\/#\/schema\/person\/image\/"},"description":"Kullan\u0131m ve kodlama m\u00fckemmeliyetini odak alan uygulamalar olu\u015fturma deneyimine sahip, profesyonel olarak 15+ y\u0131l \u00fczeri deneyime sahip bir yaz\u0131l\u0131m m\u00fchendisi.","url":"https:\/\/fatihsoysal.com\/blog\/author\/fatihsoysal\/"}]}},"yoast_meta":{"yoast_wpseo_title":"","yoast_wpseo_metadesc":"","yoast_wpseo_canonical":""},"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/posts\/34552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/comments?post=34552"}],"version-history":[{"count":0,"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/posts\/34552\/revisions"}],"wp:attachment":[{"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/media?parent=34552"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/categories?post=34552"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fatihsoysal.com\/blog\/wp-json\/wp\/v2\/tags?post=34552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}