「生きたいと思っても、それほど興奮することはない」©全国狩りの特徴
アプリケーションのクライアント部分とサーバー部分の作業を「調整」し、メッセージングとその適時性を整理し、データへのアクセスを提供し、クライアント上での一定の関連性を提供する頻度はどれくらいですか。
しばしば? 次に、この記事で説明されているアイデアと実装に興味があるでしょう。
Javascript、Ruby、およびWebsocketについてです。
前戯
今年の初めに、私は自分の発明の1つに関する記事を公開しました。このコードでは、フロントエンドとバックエンドの両方の開発に関する(当時の)考えとビジョンをすべて注ぎました。 私は誰にもそれを使用するように促しませんでした、実際、それは起こりませんでした、私は1つの質問をしました-これは別の自転車ですか?
しばらくして、フロントエンド開発者としてLucidingチーム(明luc夢のアイデアを宣伝する若いスタートアップ)に加わりました。 それ以来、今日まで、私の仕事はReactと密接に関連しており、彼のおかげで、開発に対する私の見解は劇的に変わりました。 今私は自分自身に答えることができます-私の過去の発明はちょうど別の自転車です。 現在、MVCパターンに従ってアプリケーションのクライアント部分を実装する意味はありません。クライアント部分は、サーバーで発生している現実、ユーザーが見ることを許可されている現実を反映する鏡であり、このためにはそれらを視覚化するためのデータとツールのみが必要です。
Reactは視覚化タスクを完全に処理しますが、各プログラマーはクライアントにデータを配信して最新の状態に保つというタスクに対して次のアプローチを実装します。多くの場合、次のアプローチが使用されます。
データアクセス
1.サーバーにリクエストを送信して、関連データを受信します。
2.データを(次の操作後に)現在の状態にする変更のためのイベントハンドラーの作成。
データ操作
1.サーバーにリクエストを送信して、データを変更/削除します。
2.実行された操作に関するクライアント通知(イベント)の送信。
構想
そして、すべてがうまくいくように見えますが、最初のポイントが当たり前のように思われる場合、2番目は自動化するのに良いルーチンです。 クライアントとサーバー間のデータの配信/同期への参加を完全に拒否できるツールを作成するために、私の頭の中に新しい自転車のアイデアが生まれました。 開発者は、誰と誰に対してのみ明確に定義する必要があり、いつどのように考えるのではない。
出産
n番目の時間を経て、サーバーサイドとjavascript npmパッケージをクライアントに実装するためのruby gemを作成しました。 私は私の頭脳のApiwayに電話しました 。
サーバー側
Rubyコミュニティでよく知られているSinatraフレームワークの上に構築され、新しいアプリケーションとそのコンポーネントの構造をすばやく作成するためのジェネレーターを備えています。
構成部品
- クライアント
Apiway::Client
インスタンス。接続ごとに個人的。 - モデル -通常のActiveRecordモデル。
- コントローラー -データを操作するアクションを決定するように設計されています。
- リソース -データへのアクセスとクライアントへのその提供を担当します。
コンポーネントの詳細
受信メッセージを解析し、コントローラーの起動を制御し、リソースを同期します。 コントローラーおよびリソースコードで常に使用可能な現在のクライアントは、
メソッドを使用して取得できます。 接続されているすべてのクライアントの配列は、
呼び出すことで取得できます(さらに、このメソッドは各クライアントに適用されるコードブロックを受け入れます)。
各クライアントには独自の個人用ストレージがあり、データは接続全体に保存されます。
カスタムクライアントイベントハンドラー
新しいアプリケーションが生成されると、
ファイルが作成されます。これにより、クライアント接続/切断イベントの処理と新しいメッセージの受信を設定できます。 各ハンドラーは、イベントが処理されているクライアントのコンテキストで呼び出されます。
デフォルトでは、生成時に新しいモデルは
から継承されますが、これは必須ではありません。主なことは、
モジュールによって展開されることです。 このモジュールは、単一の
メソッドを追加します。この呼び出しは、このモデルに依存するリソースの同期プロセスを開始します(モデルの保存/削除後に
モデルで自動的に呼び出されます)。
お客様
受信メッセージを解析し、コントローラーの起動を制御し、リソースを同期します。 コントローラーおよびリソースコードで常に使用可能な現在のクライアントは、
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クラスから継承されているため、個別のパッケージに配置します 。
構成部品
- API-サーバーに接続し、コントローラーにリクエストを送信するためのメソッドを提供するオブジェクト。
- リソース -インスタンスがサーバーリソースを開き、常に関連データを含むクラス。
- ストア -アプリケーションのクライアント部分の異なる部分間で「一般」データを転送するように設計された空のオブジェクト。
コンポーネントの詳細
各クライアントパーツオブジェクトには、次のメソッドがあります。
生成されたイベント
方法
生成されたイベント
方法
イベント
各クライアントパーツオブジェクトには、次のメソッドがあります。
.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を使用するクライアント側
最初に、 メッセージコンポーネントを作成します。 メッセージコンポーネントはメッセージの表示を担当します。
チャットコンポーネント自体:
最後に、 app.jsファイルを編集します
// ./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は私たちのために仕事をしてくれました。 どのように、どのような魔法で-より詳細に検討してください:
- サーバーコントローラーのアクションにリクエストを送信すると、 Apiwayはアクションの実行中に変更を受けたすべてのモデルを記憶します。この場合、新しいメッセージモデルが作成されました。
- アクションの実行後、変更されたモデルに応じたすべてのリソースの同期が開始されます。この場合、これらはすべてMessagesResourceリソースのインスタンスです。
- リソースはモデルの新しい選択を行い、関連データを生成し、それらをJSON文字列に変換します(同時にクライアント上にあるJSON文字列を記憶します)、2行を比較し、パッチを計算します(古いデータを変換するための最小命令新規-これによりトラフィックを大幅に節約できます);
- 次に、完全なデータとパッチの「重み付け」が行われ、その後、クライアントにボリュームの小さいものが送信されます。
- 新しい命令を受け取ったクライアントリソースは、それをデータに適用し、「 変更 」イベントを生成します。
キャラクターの微妙さ
サーバーへの接続が失われた場合はどうなりますか?
すべてが大丈夫です! クライアントは自動的に再接続し、接続が確立されるとすぐに、すべてのリソースが現在の状態にデータを同期します。「接続」がない場合は、以前にダウンロードしたデータが利用可能になります。
リソースによって生成されるデータはどのような形式にすべきですか?
どなたでも! ハッシュ、配列、文字列、数値、または
as_json
メソッドが
as_json
ているその他のオブジェクトなど、JSONに変換できるすべてのもの。
開いているリソースのパラメーターを変更できますか?
できる! 典型的な例:スクロール時にメッセージ履歴をロードする。 リソースパラメータ
messageResource.set({limit: 50})
基本的な変更によって実装されます-リソースは最後の50メッセージをロードし、「 change 」イベントを発生させます。
アキレスヒール
多くのデータベースクエリ
はい、そうです! この場合、頻繁なリクエストのキャッシュを巧みに使用することが役立ちます。
パッチを計算する手順は比較的低速です。
はい、データ量が多いほど、計算に時間がかかります。 もちろん、一般にこの機能を切り捨てることはできますが、「駆動」トラフィックの量は大幅に増加します。 ここで、妥協点について考える必要があります。
データ、欠点は重要であると考えていますが、現時点ではApiwayは実装の例であり、低負荷のプロジェクトにのみ適しています。
タッチしてください!
チャットは、単純な認証を使用したもう少し複雑な例です。
Githubチャットソース
Github Apiwayサーバーソース
GithubのApiwayクライアント側のソース
readmeリポジトリの英語の「直感」レベルでの「不器用さ」を事前に謝罪します。
ありがとうございます!
この記事を読んでくれたすべての読者に感謝します。 私はあなたの批判、どんな助け、そしてただのサポートに喜んでいるでしょう。
よろしく、デニス。