Apiway-クライアントサーバーデータ転送の新しい方法

画像

「生きたいと思っても、それほど興奮することはない」©全国狩りの特徴



アプリケーションのクライアント部分とサーバー部分の作業を「調整」し、メッセージングとその適時性を整理し、データへのアクセスを提供し、クライアント上での一定の関連性を提供する頻度はどれくらいですか。



しばしば? 次に、この記事で説明されているアイデアと実装に興味があるでしょう。

Javascript、Ruby、およびWebsocketについてです。



前戯



今年の初めに、私は自分の発明の1つに関する記事を公​​開ました。このコードでは、フロントエンドとバックエンドの両方の開発に関する(当時の)考えとビジョンをすべて注ぎました。 私は誰にもそれを使用するように促しませんでした、実際、それは起こりませんでした、私は1つの質問をしました-これは別の自転車ですか?



しばらくして、フロントエンド開発者としてLucidingチーム(明luc夢のアイデアを宣伝する若いスタートアップ)に加わりました。 それ以来、今日まで、私の仕事はReactと密接に関連しており、彼のおかげで、開発に対する私の見解は劇的に変わりました。 今私は自分自身に答えることができます-私の過去の発明はちょうど別の自転車です。 現在、MVCパターンに従ってアプリケーションのクライアント部分を実装する意味はありません。クライアント部分は、サーバーで発生している現実、ユーザーが見ることを許可されている現実を反映する鏡であり、このためにはそれらを視覚化するためのデータとツールのみが必要です。



Reactは視覚化タスクを完全に処理しますが、各プログラマーはクライアントにデータを配信して最新の状態に保つというタスクに対して次のアプローチを実装します。多くの場合、次のアプローチが使用されます。



データアクセス

1.サーバーにリクエストを送信して、関連データを受信します。

2.データを(次の操作後に)現在の状態にする変更のためのイベントハンドラーの作成。



データ操作

1.サーバーにリクエストを送信して、データを変更/削除します。

2.実行された操作に関するクライアント通知(イベント)の送信。



構想



そして、すべてがうまくいくように見えますが、最初のポイントが当たり前のように思われる場合、2番目は自動化するのに良いルーチンです。 クライアントとサーバー間のデータの配信/同期への参加を完全に拒否できるツールを作成するために、私の頭の中に新しい自転車のアイデアが生まれました。 開発者は、誰と誰に対してのみ明確に定義する必要があり、いつどのように考えるのではない。



出産



n番目の時間を経て、サーバーサイドとjavascript npmパッケージをクライアントに実装するためのruby gemを作成しました。 私は私の頭脳のApiwayに電話しました



サーバー側



Rubyコミュニティでよく知られているSinatraフレームワークの上に構築され、新しいアプリケーションとそのコンポーネントの構造をすばやく作成するためのジェネレーターを備えています。



構成部品



コンポーネントの詳細
お客様


受信メッセージを解析し、コントローラーの起動を制御し、リソースを同期します。 コントローラーおよびリソースコードで常に使用可能な現在のクライアントは、 client



メソッドを使用して取得できます。 接続されているすべてのクライアントの配列は、 Apiway::Client.all



呼び出すことで取得できます(さらに、このメソッドは各クライアントに適用されるコードブロックを受け入れます)。

各クライアントには独自の個人用ストレージがあり、データは接続全体に保存されます。

 #     client[:user_id] = 1 #     client[:user_id] # > 1
      
      





カスタムクライアントイベントハンドラー

新しいアプリケーションが生成されると、 app/base/client.rb



ファイルが作成されます。これにより、クライアント接続/切断イベントの処理と新しいメッセージの受信を設定できます。 各ハンドラーは、イベントが処理されているクライアントのコンテキストで呼び出されます。

 module Apiway class Client on_connected do #       #      end on_message do |message| #       end on_disconnected do #       #       end end end
      
      





モデル


デフォルトでは、生成時に新しいモデルはActiveRecord::Base



から継承されますが、これは必須ではありません。主なことは、 Apiway::Model



モジュールによって展開されることです。 このモジュールは、単一のsync



メソッドを追加します。この呼び出しは、このモデルに依存するリソースの同期プロセスを開始します(モデルの保存/削除後にActiveRecord



モデルで自動的に呼び出されます)。

 class Test < ActiveRecord::Base include Apiway::Model end
      
      





コントローラー


 class UsersController < ApplicationController include Apiway::Controller #     Rails # Before- before_action :method_name before_action :method_name, only: :action_name before_action :method_name, only: [ :action_name, :action_name ] before_action :method_name, except: :action_name before_action :method_name, except: [ :action_name, :action_name ] # After- after_action :method_name after_action :method_name, only: :action_name after_action :method_name, only: [ :action_name, :action_name ] after_action :method_name, except: :action_name after_action :method_name, except: [ :action_name, :action_name ] #   action :auth do #        # Api.query("Users.auth", {name: "Bob", pass: "querty"}) # .then(function( id ){ console.log("User id: ", id) }) # .catch(function( e ){ console.log("Error: ", e) }) begin #  params      user = User.find_by! name: params[ :name ], pass: params[ :pass ] rescue Exception => e #         ,   error #         #  ,    "Error: auth_error" error :auth_error #  error     , , ,  #    before-,      #       else #     client     #  ( Apiway::Client),      # id  client[:user_id] = user.id #          #     ,      id #    "User id: 1" end end end
      
      





資源


 #         # var userMessages = new Resource("UserMessages", {limit: 30}); # userMessages.onChange(function( data ){ console.log("New data", data) }); # userMessages.onError(function( e ){ console.log("Error", e) }); class UserMessagesResource < ApplicationResource include Apiway::Resource #   depend_on Message, User #   ,       Message  User, #      ,    #    #     access do #           #  client ,       , #        :user_id error :auth_error unless client[:user_id] #   error       #  "error"   "auth_error",    #  "Error: auth_error"  ,   "", #  "" ,    ,   #      "change"    #     "New data: [{mgs},{mgs},{mgs}...]" end #    #      ,      #    data do #  params      Message.find_by(user_id: client[:user_id]).limit(params[:limit]).map do |msg| { text: msg.text, user: msg.user.name } end end end
      
      







クライアント部



サーバーとの対話に必要なコンポーネントを提供します。 各コンポーネントはEventEmitterクラスから継承されているため、個別のパッケージに配置します



構成部品



コンポーネントの詳細
イベント


各クライアントパーツオブジェクトには、次のメソッドがあります。

 .on(event, callback[, context]) //   callback   event; .one(event, callback[, context]) //   .on(),      ; .off() //     ; .off(event) //     event; .off(event, callback) //   callback  event; .off(event, callback, context) //   callback  event, //    context;
      
      





API


生成されたイベント

  • 準備完了 -接続が確立され、 readyReadyPromise beforePromiseが正常に満たされた後に生成されます。
  • unready-接続が確立された後に生成され、 beforeReadyPromiseのpromise 「失敗」した。
  • エラー -サーバーとの接続でエラーが発生した場合に生成されます。
  • 切断 -サーバーから切断されたときに生成されます。


方法

 Api.connect(address[, options ]) //      //  : // aliveDelay -  ( )   "ping" , , //   ,       Api.query( "Messages.new", params ) //     new  MessagesController'a //   params    Api.disconnect() //     Api.beforeReadyPromise( callback ) //  ,   (Promise) //         //        // "ready" -     "unready" -   //    , ,   //     Api.onReady(callback, context) //  Api.on("ready", callback, context) Api.oneReady(callback, context) //  Api.one("ready", callback, context) Api.offReady(callback, context) //  Api.off("ready", callback, context) Api.onUnready(callback, context) //  Api.on("unready", callback, context) Api.oneUnready(callback, context) //  Api.one("unready", callback, context) Api.offUnready(callback, context) //  Api.off("unready", callback, context)
      
      





資源


生成されたイベント

  • 変更 -リソースデータの更新時に生成されます。
  • エラー -エラーが発生したときに生成されます(サーバーでerror



    メソッドを呼び出します)。


方法

 var resource = new Resource("Messages", {limit: 10}) //   MessagesResource   {limit: 10} resource.name //     resource.data //     resource.get("limit") //    limit,   : 10 resource.set({limit: 20, order: "ask"}) //    limit     order //      resource.unset("order") //   order //      resource.onChange(callback, context) //  resource.on("change", callback, context) resource.oneChange(callback, context) //  resource.one("change", callback, context) resource.offChange(callback, context) //  resource.off("change", callback, context) resource.onError(callback, context) //  resource.on("error", callback, context) resource.oneError(callback, context) //  resource.one("error", callback, context) resource.offError(callback, context) //  resource.off("error", callback, context)
      
      







調教



Apiwayを使用して簡単なコンソールチャットを作成することを検討してください



サーバー側



まず、 Apiwayをインストールして、アプリケーションフレームワークを生成します。

 $ gem install apiway #  gem'a $ apiway new Chat #    $ cd Chat #    
      
      





移行を使用して、データベースにメッセージテーブルを作成します。

 $ bundle exec rake db:create_migration NAME=create_messages
      
      





テーブルを形成するコードを追加します。

 # db/mirgations/20140409121731_create_messages.rb class CreateMessages < ActiveRecord::Migration def change create_table :messages do |t| t.text :text t.timestamps null: true end end end
      
      





そして、移行を実行します。

 $ bundle exec rake db:migrate
      
      





次に、モデルを作成します。

 $ apiway generate model Message
      
      





 # app/models/message.rb class Message < ActiveRecord::Base include Apiway::Model #         #    ,        validates :text, presence: { message: "blank" }, length: { in: 1..300, message: "length" } end
      
      





次に、リソース:

 $ apiway generate resource Messages
      
      





 # app/resources/messages.rb class MessagesResource < ApplicationResource include Apiway::Resource # ,       Message depend_on Message #  ,         data do Message.limit( params[ :limit ] ).order( created_at: :desc ).reverse.map do |message| { id: message.id, text: message.text } end # params - ,    ,     #    params = {limit: 10}      10   # [{id: 10, text: "Hello 10"}, {id: 9, text: "Hello 9"}, {id: 8, text: "Hello 8"}, ...] end end
      
      





そして最後に、コントローラー:

 $ apiway generate controller Messages
      
      





 # app/controllers/messages.rb class MessagesController < ApplicationController include Apiway::Controller #  ,    action :new do begin # params -     current_user.messages.create! text: params[ :text ] rescue ActiveRecord::RecordInvalid => e #       error e.record.errors.full_messages else true #   ,    end end end
      
      





これでサーバー部分が完成し、次のコマンドでサーバーを起動します。

 $ apiway server
      
      





クライアント部



大部分の人はnpm、gulp、grunt、browserifyなどが何であるかを知っていると思うので、アセンブリの複雑さについては説明しませんが、要点のみを説明します。



apiwayをインストールします。

 npm install apiway --save
      
      





実際には、クライアント部分自体(3つのコペックと同じくらい簡単で、数行に収まります):

 // source/app.js import { Api, Resource } from "apiway"; //    var Chat = { run: function(){ //      Messages   { limit: 10 } var messagesResource = new Resource( "Messages", { limit: 10 } ); //          render messagesResource.onChange( this.render ); //   send   window window.send = this.send; }, render: function( messages ){ //      console.clear(); //      messages.forEach( function( item ){ console.log( item.text ) }); }, send: function( text ){ //        new  Messages Api.query( "Messages.new", { text: text } ) //        console.warn .catch( function( errors ){ console.warn( errors.join( ", " ) ) }); } }; Api //          .connect( "ws://localhost:3000", { aliveDelay: 5000 } ) //      ,    .oneReady( function( e ){ Chat.run() }); // ""       // ,     onReady(),    //         
      
      





以上です。 結果を見るには、ブラウザを開き、コンソールを開いて最後の10件のチャットメッセージを表示し、 send("Hello world")



を書いて、最後の10件のメッセージを表示します。 空のメッセージを送信しようとしました-エラーが表示されます。

Reactを使用するクライアント側
最初に、 メッセージコンポーネントを作成します。 メッセージコンポーネントはメッセージの表示を担当します。

 // ./components/Message.jsx import React from "react"; class Message extends React.Component { render(){ return ( <div> <b>{ this.props.data.user }</b> <p>{ this.props.data.text }</p> </div> ); } } export default Message;
      
      





チャットコンポーネント自体:

 // ./components/Chat.jsx import React from "react"; import Message from "./Message.jsx"; import { Api, Resource } from "apiway"; //  ,     //        let errorsMsg = { "Text blank": "    ", "Text length": "     1  300 " }; class Chat extends React.Component { constructor( props ){ //     super( props ); this.state = { messages: [], errors: [] }; } componentDidMount(){ //     ,  //  Messages   { limit: 30 } this.MessagesResource = new Resource( "Messages", { limit: 30 } ); //       this.MessagesResource.onChange(( messages )=>{ this.setState({ messages }) }); //  callback        } componentWillUnmount(){ //      this.MessagesResource.destroy(); } onKeyUp( e ){ //     Enter'    ,   if( e.keyCode !== 13) return; //     new  Messages,   {text: " "} Api.query( "Messages.new", { text: e.target.value } ) //   (Promise) .then( ()=>{ this.setState({ errors: [] }) }, ( errors )=>{ this.setState({ errors }) }); //    -  ,    -   } render(){ return ( <div> //   <div> { this.state.messages.map( ( message )=>{ return <Message data={ message } key={ message.id } />; } ) } </div> //   <input type="text" onKeyUp={ ( e )=> this.onKeyUp( e ) } /> //   <div> { this.state.errors.map( function( key ){ return errorsMsg[ key ]; }).join( ", " ) } </div> </div> ); } } export default Chat;
      
      





最後に、 app.jsファイルを編集します

 import React from "react"; import Chat from "./components/Chat.jsx"; Api //    .connect( `ws://localhost:3000`, { aliveDelay: 5000 } ) .oneReady( function( e ){ // ,      ,    React.render( <Chat />, document.getElementById( "app" ) ); });
      
      









何が起こっているの?



ご覧のとおり、クライアントとのデータ同期に直接頼ることはありませんでした-Apiwayは私たちのために仕事をしてくれました。 どのように、どのような魔法で-より詳細に検討してください:







キャラクターの微妙さ



サーバーへの接続が失われた場合はどうなりますか?

すべてが大丈夫です! クライアントは自動的に再接続し、接続が確立されるとすぐに、すべてのリソースが現在の状態にデータを同期します。「接続」がない場合は、以前にダウンロードしたデータが利用可能になります。



リソースによって生成されるデータはどのような形式にすべきですか?

どなたでも! ハッシュ、配列、文​​字列、数値、またはas_json



メソッドがas_json



ているその他のオブジェクトなど、JSONに変換できるすべてのもの。



開いているリソースのパラメーターを変更できますか?

できる! 典型的な例:スクロール時にメッセージ履歴をロードする。 リソースパラメータmessageResource.set({limit: 50})



基本的な変更によって実装されます-リソースは最後の50メッセージをロードし、「 change 」イベントを発生させます。



アキレスヒール



多くのデータベースクエリ

はい、そうです! この場合、頻繁なリクエストのキャッシュを巧みに使用することが役立ちます。



パッチを計算する手順は比較的低速です。

はい、データ量が多いほど、計算に時間がかかります。 もちろん、一般にこの機能を切り捨てることはできますが、「駆動」トラフィックの量は大幅に増加します。 ここで、妥協点について考える必要があります。



データ、欠点は重要であると考えていますが、現時点ではApiwayは実装の例であり、低負荷のプロジェクトにのみ適しています。



タッチしてください!



チャットは、単純な認証を使用したもう少し複雑な例です。

Githubチャットソース

Github Apiwayサーバーソース

GithubのApiwayクライアント側のソース

readmeリポジトリの英語の「直感」レベルでの「不器用さ」を事前に謝罪します。



ありがとうございます!



この記事を読んでくれたすべての読者に感謝します。 私はあなたの批判、どんな助け、そしてただのサポートに喜んでいるでしょう。

よろしく、デニス。



All Articles