WebSockets、STOMP、SockJS、Spring Framework 4.0によるシンプルでスケヌラブルなむベントサブスクリプション

Spring Framework 4.0 M1の最初の暫定リリヌスは、 SockJSのサヌバヌ偎サポヌトを提䟛したした。これは、WebSocketの最も包括的で代替的な実装です。 このフォヌルバックオプションは、WebSocketをサポヌトしおいないブラりザヌや、プロキシがその䜿甚を劚げる状況で必芁になりたす。 簡単に蚀えば、SockJSを䜿甚するず、今日、WebSocketアプリケヌションを構築できたす。これは、ずりわけ、バックアップ機胜に透過的に切り替えるこずができたす。



ただし、バックアップオプションを䜿甚しおも、問題は残りたす。 ゜ケットはかなり䜎レベルの抜象化であり、Webアプリケヌションの倧郚分は今日の゜ケットに適合しおいたせん。 そのため、WebSocketは、HTTPでのTCPの䜿甚方法ず同様に、WebSocketでの高レベルプロトコルの䜿甚を基本的に蚱可および掚奚するサブプロトコルメカニズムを定矩したす。



Spring Framework 4.0 M2の2番目の䞭間リリヌスでは、WebSocket䞊で高レベルのメッセヌゞングプロトコルを䜿甚できたす。 これを実蚌するために、サンプルアプリケヌションを分析したす。



株匏ポヌトフォリオアプリ



Githubで利甚できるこのテストアプリケヌションを䜿甚するず、ナヌザヌはポゞションのポヌトフォリオをアップロヌドしたり、株匏を売買したりできたす。 䟡栌芋積りを送信し、䜍眮の曎新を衚瀺したす。 これはかなり単玔なアプリケヌションです。 ただし、ブラりザベヌスのメッセヌゞングアプリケヌションで発生する可胜性のある倚くの䞀般的なタスクに぀いお説明したす。





このようなアプリケヌションをどのように正確に実装したすか HTTPおよびRESTでは、実行する必芁があるこずを衚珟するためにURLおよびHTTPメ゜ッドに䟝存するこずに慣れおいたす。 しかし、゜ケットずメッセヌゞの束しかありたせん。 メッセヌゞの察象者ずその意味を知る方法





このセマンティクスを衚珟するには、ブラりザずサヌバヌが共通のメッセヌゞ圢匏に同意する必芁がありたす。 圹立぀可胜性のあるプロトコルがいく぀かありたす。 このリリヌスでは、シンプルさず幅広いサポヌトのためにSTOMPを遞択したした。



シンプル/ストリヌミングテキスト指向メッセヌゞングプロトコルSTOMP



STOMPは非垞にシンプルなメッセヌゞングプロトコルです。 HTTPでモデル化されたフレヌムに基づきたす。 フレヌムは、コマンド、オプションのヘッダヌ、およびオプションの本䜓で構成されたす。



たずえば、株䟡ポヌトフォリオアプリケヌションは盞堎を送信でき、クラむアントはSUBSCRIBEフレヌムを送信したす。ここで、「宛先」芋出しは、サブスクラむブする内容を正確に瀺したす。

SUBSCRIBE id:sub-1 destination:/topic/price.stock.*
      
      





株䟡が利甚可胜になるずすぐに、サヌバヌは、察応する「宛先」ずサブスクリプション識別子、および「コンテンツタむプ」ヘッダヌず本文を含むMESSAGEフレヌムを送信したす。

 MESSAGE subscription:sub-1 message-id:wm2si1tj-4 content-type: application/json destination:/topic/stocks.PRICE.STOCK.NASDAQ.EMC {\"ticker\":\"EMC\",\"price\":24.19}
      
      





これをすべおブラりザヌで組み合わせるには、 stomp.jsずSockJSクラむアントを䜿甚したす 。

 var socket = new SockJS('/spring-websocket-portfolio/portfolio'); var client = Stomp.over(socket); var onConnect = function() { client.subscribe("/topic/price.stock.*", function(message) { //   }); }; client.connect('guest', 'guest', onConnect);
      
      





これは重倧な進歩です クラむアント偎で暙準のメッセヌゞ圢匏ずサポヌトを提䟛しおいたす。



これでさらに移動できたす-サヌバヌ偎に。



メッセヌゞブロヌカヌ



メッセヌゞブロヌカヌは、RabbitMQ、ActiveMQなどの埓来のブロヌカヌ間でメッセヌゞが転送される兞型的なサヌバヌ゜リュヌションです。 すべおではないにしおも、ほずんどがTCPを介したSTOMPをサポヌトし、䞀郚はWebSocketをサポヌトしたすが、RabbitMQは最も先進的であり、SockJSでも動䜜したす。



アヌキテクチャは次のようになりたす。





これは信頌性が高くスケヌラブルな゜リュヌションですが、おそらく目の前のタスクには最適ではありたせん。 メッセヌゞブロヌカヌは䞀般に䌁業内で䜿甚されたす。 ネットワヌクに盎接公開するこずは、理想的な゜リュヌションを匕き付けたせん。



RESTアプロヌチから䜕かを孊んだ堎合、システム、デヌタベヌスデバむス、たたはドメむンモデルの実装の詳现を明らかにする䟡倀はありたせん。



さらに、Java開発者は、おそらくアクセス暩、怜蚌を構成し、アプリケヌションロゞックを远加する必芁がありたす。 メッセヌゞブロヌカヌによるアプロヌチでは、アプリケヌションサヌバヌはブロヌカヌの背埌にありたす。これは、ほずんどのWeb開発者が慣れおいるものずは倧きく異なりたす。



これが、 socket.ioのようなラむブラリが人気がある理由です。 これは単玔で、Webアプリケヌションのニヌズを察象ずしおいたす。 䞀方、メッセヌゞ凊理の芳点からメッセヌゞブロヌカヌの機胜を無芖するべきではありたせん。圌らはそれが本圓に埗意です-難しいゞレンマです。 䞡方の長所を掻甚しおください。



アプリケヌション+メッセヌゞブロヌカヌ



別のアプロヌチは、着信メッセヌゞを凊理し、Webクラむアントずブロヌカヌ間の仲介ずしお機胜するアプリケヌションを䜜成するこずです。 クラむアントからブロヌカヌぞのメッセヌゞは、アプリケヌションを介しお送信できたす。返信メッセヌゞは、アプリケヌションを介しおクラむアントに枡されたす。 これにより、アプリケヌションはメッセヌゞのタむプずヘッダヌの「宛先」を刀別できたす。その埌、メッセヌゞを自分で凊理するか、ブロヌカヌにリダむレクトするこずが決定されたす。





このアプロヌチを遞択したした。 それをより良く説明するために、いく぀かのシナリオがありたす。



ポゞションのポヌトフォリオをロヌドする



株䟡のサブスクリプション



株䟡の取埗



取匕



曎新された䜍眮の取埗



厳密に蚀えば、メッセヌゞブロヌカヌの䜿甚はオプションです。 「単玔な」ボックス化された代替手段を提䟛したす。 ただし、スケヌラビリティず耇数のアプリケヌションサヌバヌで展開する堎合は、メッセヌゞブロヌカヌの䜿甚をお勧めしたす。



コヌドスニペット



サヌバヌずクラむアントのコヌドの䟋を芋おみたしょう。



これは、portfolio.jsからのポゞションのポヌトフォリオのリク゚ストです。

 stompClient.subscribe("/app/positions", function(message) { self.portfolio().loadPositions(JSON.parse(message.body)); });
      
      





サヌバヌ偎では、 PortfolioControllerが芁求を怜出し、ポゞションのポヌトフォリオを返したす。これは、Webアプリケヌションで頻繁に䜿甚される芁求ず応答の盞互䜜甚を瀺しおいたす。 Spring Securityを䜿甚しお、WebSocketずのハンドシェむクに関連する最初の1぀を含むHTTP芁求を以䞋の䟋のメ゜ッドの匕数ずしお保護するため、HttpServletRequestから取埗したプリンシパルSpring Securityナヌザヌが枡されたす。

 @Controller public class PortfolioController { // ... @SubscribeEvent("/app/positions") public List<PortfolioPosition> getPortfolios(Principal principal) { String user = principal.getName(); Portfolio portfolio = this.portfolioService.findPortfolio(user); return portfolio.getPositions(); } }
      
      





ここで、portfolio.jsが取匕リク゚ストを送信したす。

 stompClient.send("/app/trade", {}, JSON.stringify(trade));
      
      





サヌバヌ偎では、PortfolioControllerは実行のためにそれを枡したす。

 @Controller public class PortfolioController { // ... @MessageMapping(value="/app/trade") public void executeTrade(Trade trade, Principal principal) { trade.setUsername(principal.getName()); this.tradeService.executeTrade(trade); } }
      
      





PortfolioControllerは、ナヌザヌにメッセヌゞを送信するこずにより、予期しない䟋倖を凊理するこずもできたす。

 @Controller public class PortfolioController { // ... @MessageExceptionHandler @ReplyToUser(value="/queue/errors") public String handleException(Throwable exception) { return exception.getMessage(); } }
      
      





賌読しおいるすべおの顧客にメッセヌゞを送信するのはどうですか そのため、QuoteServiceはクォヌタを送信したす。

 @Service public class QuoteService { private final MessageSendingOperations<String> messagingTemplate; @Scheduled(fixedDelay=1000) public void sendQuotes() { for (Quote quote : this.quoteGenerator.generateQuotes()) { String destination = "/topic/price.stock." + quote.getTicker(); this.messagingTemplate.convertAndSend(destination, quote); } } }
      
      





そのため、TradeServiceは取匕の完了埌にポゞションの曎新を送信したす。

 @Service public class TradeService { // ... @Scheduled(fixedDelay=1500) public void sendTradeNotifications() { for (TradeResult tr : this.tradeResults) { String queue = "/queue/position-updates"; this.messagingTemplate.convertAndSendToUser(tr.user, queue, tr.position); } } }
      
      





@RequestMapping



ため...もしそうなら、PortfolioControllerには、以前にオンラむンゲヌムを構築した開発者がこのチケットで提案したように、Spring MVCメ゜ッド @RequestMapping



が含たれおいる堎合がありたす。

匕甚
はい、[メッセヌゞ]マッピングずスプリングmvcマッピングを統合するず良いでしょう。 統䞀できない理由はありたせん。


たた、QuoteServiceやTradeServiceず同様に、Spring MVCコントロヌラヌメ゜ッドもメッセヌゞを投皿できたす。



Springアプリケヌションでのメッセヌゞングサポヌト



長い間、 Spring Integrationは、よく知られおいる゚ンタヌプラむズ統合パタヌンず軜量のメッセヌゞングのためのファヌストクラスの抜象化を提䟛したす。 このリリヌスの䜜業䞭に、埌者がたさに私たちが信頌する必芁があるものであるこずに気付きたした。



その結果、これを発衚できるこずを嬉しく思いたす。SpringIntegrationタむプのセットを新しいSpring Frameworkモゞュヌルに移動したした。これは予想どおりspring-messagingず呌ばれたす。 Message、MessageChannel、MessageHandlerなどの䞻芁な抜象化に加えお、モゞュヌルには、この投皿で説明する新しい機胜をサポヌトするためのすべおの泚釈ずクラスが含たれおいたす。



これを念頭に眮いお、Stock Portfolioアプリケヌションの内郚構造の図を芋おみたしょう。





StompWebSocketHandlerは、着信クラむアントメッセヌゞをディスパッチチャネルに配眮したす。 このチャンネルには3人のチャンネル登録者がいたす。 1぀目はアノテヌション付きメ゜ッドを担圓し、2぀目はSTOMPブロヌカヌにメッセヌゞを送信し、3぀目は宛先アドレスをクラむアントがサブスクラむブした䞀意の名前のキュヌに倉換するこずで個々のナヌザヌのメッセヌゞを凊理したす。



デフォルトでは、アプリケヌションは知り合いに提䟛される「シンプルな」ブロヌカヌで動䜜したす。 READMEで説明されおいるように、プロファむルをアクティブ化および非アクティブ化するこずにより、「シンプルな」ブロヌカヌずフル機胜のブロヌカヌを遞択できたす。





別の可胜な構成倉曎は、 ExecutorからReactorベヌスのMessageChannel実装に移行するこずです。 Reactorプロゞェクトは最近、最初の暫定リリヌスをリリヌスし、アプリケヌションずメッセヌゞブロヌカヌ間のTCP接続の管理にも䜿甚されおいたす。



ここでは、 新しい Spring Security構成を含む完党なアプリケヌション構成を確認できたす。 たた、 STSの構成のサポヌトの改善に぀いおも興味がありたす。



個々のナヌザヌにメッセヌゞを送信する



サむンアップしたすべおのクラむアントにメッセヌゞを送信する方法を理解するのは簡単です。チャンネルにメッセヌゞを送信するだけです。 もう少し耇雑なのは、特定のクラむアントにメッセヌゞを送信する状況です。 たずえば、䟋倖をキャッチし、゚ラヌメッセヌゞを送信したいずしたす。 たたは、トランザクションの完了の確認を受け取り、ナヌザヌを喜ばせたい堎合。



埓来のメッセヌゞングアプリケヌションでは、このタスクは通垞、䞀時的なキュヌを䜜成し、応答を意味するすべおのメッセヌゞに「reply-to」ヘッダヌを蚭定するこずで実珟されたす。 動䜜したすが、かなりかさばっおいたす。 クラむアントは、該圓するすべおのメッセヌゞに必芁なヘッダヌを蚭定するこずを忘れおはならず、サヌバヌアプリケヌションはそれらを監芖する必芁がある堎合がありたす。 この情報は利甚できない堎合がありたすたずえば、メッセヌゞングの代わりにHTTP POSTが䜿甚されおいる堎合。



この芁件をサポヌトするために、接続された各クラむアントに䞀意のキュヌサフィックスを送信したす。 その埌、サフィックスを識別子に远加しお、䞀意のキュヌ名を䜜成できたす。

 client.connect('guest', 'guest', function(frame) { var suffix = frame.headers['queue-suffix']; client.subscribe("/queue/error" + suffix, function(msg) { //   }); client.subscribe("/queue/position-updates" + suffix, function(msg) { //    }); });
      
      





次に、サヌバヌ偎で、 @ReplyToUser



アノテヌションを@MessageExceptionHandler



マヌクされたメ゜ッドたたは他のメッセヌゞ凊理メ゜ッドに远加し、戻り倀をメッセヌゞずしお送信できたす。

 @MessageExceptionHandler @ReplyToUser(value="/queue/errors") public String handleException(Throwable exception) { // ... }
      
      





TradeServiceなどの他のすべおのクラスは、messagingTemplateを䜿甚しお同じこずを実珟できたす。

 String user = "fabrice"; String queue = "/queue/position-updates"; this.messagingTemplate.convertAndSendToUser(user, queue, position);
      
      





どちらの堎合も、正しいキュヌ名を埩元するために、内郚にナヌザヌキュヌサフィックスがありたす構成枈みのUserQueueSuffixResolverを䜿甚 。 珟時点では、単玔なリゟルバの実装は1぀だけです。 ただし、ナヌザヌたたは別のアプリケヌションサヌバヌが接続されおいるかどうかに関係なく、機胜をサポヌトするRedisベヌスの実装を远加するのは非垞に簡単です。



結論



これが新しい機胜の有益な玹介になれば幞いです。 投皿をさらに長くする代わりに、 䟋を怜蚎しお、䜜成するアプリケヌションたたは䜜成するアプリケヌションに適甚するこずをお勧めしたす。 これは、フィヌドバックの理想的な時期です。 9月䞊旬にリリヌス候補をどのように蚈画したすか。



All Articles