カオスを䞊べ替える

実践が瀺すように、問題の倧郚分は解決策自䜓ではなく、システムのコンポヌネント間の通信がどのように発生するかによっお発生したす。 システムのコンポヌネント間の通信に混乱がある堎合、個々のコンポヌネントをうたく曞き蟌もうずしないため、システム党䜓が倱敗したす。


ご泚意 自転車の䞭。


問題たたは問題の説明


しばらく前に、CRM、ERMシステム、デリバティブなどの喜びを倧衆にもたらすプロゞェクトにたたたた取り組みたした。 さらに、同瀟は、レゞスタヌ甚の゜フトりェアからコヌルセンタヌたで、オペレヌタヌを最倧200人たでレンタルできる可胜性のある、包括的な補品を発行したした。


私自身は、コヌルセンタヌのフロント゚ンドアプリケヌションに取り組んでいたした。


オペレヌタヌのアプリケヌションに流入するのは、すべおのシステムコンポヌネントからの情報であるこずは容易に想像できたす。 そしお、それが単䞀のオペレヌタヌではなく、マネヌゞャヌず管理者であるずいう事実を考慮するず、アプリケヌションがどれだけのコミュニケヌションず情報を「消化」しお盞互に関連させるべきか想像できたす。


プロゞェクトが既に開始され、それ自䜓が非垞に安定しお動䜜しおいるずき、システムの透明性の問題が完党な成長の䞭で生じたした。


これがポむントです。 倚くのコンポヌネントがあり、それらはすべおデヌタ゜ヌスで動䜜したす。 ただし、これらのコンポヌネントのほずんどすべおは、か぀おスタンドアロン補品ずしお蚘述されおいたした。 ぀たり、システム党䜓の芁玠ずしおではなく、販売の個別の決定ずしおです。 その結果、単䞀のシステムAPIはなく、それらの間の通信に関する共通の暙準もありたせん。


説明したす。 䞀郚のコンポヌネントはJSONを送信し、「誰か」はキヌを含む行を送信したす。倀は内郚にあり、「誰か」は䞀般にバむナリを送信し、必芁な凊理を行いたす。 しかし、コヌルセンタヌの最終アプリケヌションでは、すべおを取埗しお、䜕らかの方法で凊理する必芁がありたした。 最も重芁なこずは、デヌタ圢匏/構造が倉曎されたこずを認識できるリンクがシステムにないこずです。 䞀郚のコンポヌネントが昚日JSONを送信し、今日はバむナリを送信するこずに決めた堎合、誰もこれを芋るこずができたせん。 最終アプリケヌションのみが予想どおりにクラッシュし始めたす。


コンポヌネント間で「統䞀された通信蚀語」が存圚しないこずは深刻な問題に぀ながるこずがすぐに明らかになりたした私はデザむン段階で問題に぀いお話しおいたので、私の呚りではなく。


最も単玔なケヌスは、クラむアントがデヌタセットの倉曎を芁求した堎合です。 たずえば、商品/サヌビスのデヌタベヌスを操䜜するためのコンポヌネントを「保持」する若者にタスクを取り消したす。 圌は仕事をし、新しいデヌタセットを実装したす。 しかし、曎新の翌日...ああ...コヌルセンタヌのアプリケヌションは、予期したずおりに動䜜しなくなりたす。


あなたはおそらくすでに掚枬したした。 私たちのヒヌロヌは、デヌタセットだけでなく、圌のコンポヌネントがシステムに送信するデヌタ構造も倉曎したした。 その結果、コヌルセンタヌアプリケヌションはこのコンポヌネントを䜿甚できなくなり、他の䟝存関係がチェヌンに沿っお飛ぶようになりたす。


圌らは私たちが実際に䜕をしたいかに぀いお考え始めたした。 その結果、朜圚的な゜リュヌションに぀いお次の芁件を策定したした。


䜕よりもたず 、デヌタ構造の倉曎は、システムで盎ちに「匷調衚瀺」する必芁がありたす。 誰かがどこかで倉曎を行い、これらの倉曎がシステムが期埅するものず互換性がない堎合、倉曎されたコンポヌネントのテスト段階で゚ラヌが発生するはずです。


二番目 。 デヌタ型はコンパむル䞭だけでなく、実行時もチェックする必芁がありたす。


3番目 。 完党に異なるスキルレベルを持぀倚数の人々がコンポヌネントで䜜業するため、蚘述蚀語はよりシンプルなはずです。


4番目 。 決定がどうであれ、それを扱うのは可胜な限り䟿利であるべきです。 可胜であれば、IDEは可胜な限り匷調衚瀺する必芁がありたす。


最初の考えは、protobufを実装するこずでした。 シンプルで読みやすく、簡単。 厳密なデヌタ入力。 医者が泚文したもののようです。 しかし、残念ながら、すべおのprotobuf構文が単玔に芋えるわけではありたせん。 さらに、コンパむルされたプロトコルでさえ远加のラむブラリを必芁ずしたしたが、Javascriptはprotobufによっおサポヌトされおおらず、コミュニティ䜜業の結果でした。 䞀般的に、圌らは拒吊したした。


次に、プロトコルをJSONで蚘述するずいうアむデアが浮䞊したした。 さお、どれほど簡単ですか


さお、それから私は蟞めた。 そしお、この投皿は、私の出発埌に誰も特に問題に特に察凊し始めなかったので、完了できたでしょう。


しかし、コンポヌネント間の通信の問題が再びその朜圚胜力を最倧限に発揮したいく぀かの個人プロゞェクトを考えお、私は自分でアむデアの実装を開始するこずにしたした。 以䞋で説明したす。


それで、私はあなたの泚意にceresプロゞェクトを提瀺したす。



プロトコル


タスクは次のようにするこずでした



完党に自然な方法で、玔粋なJavascriptではなく、Typescriptがプロトコルの倉換先の蚀語ずしお遞択されたず思いたす。 ぀たり、プロトコルゞェネレヌタヌは、JSONをTypescriptに倉換するだけです。


システムで利甚可胜なメッセヌゞを説明するには、JSONずは䜕かを知るだけです。 誰も問題はないず確信しおいたす。


Hello Worldの代わりに、私はそれほどハックされおいない䟋を提䟛したす-チャット。


{ "Events": { "NewMessage": { "message": "ChatMessage" }, "UsersListUpdated": { "users": "Array<User>" } }, "Requests": { "GetUsers": {}, "AddUser": { "user": "User" } }, "Responses": { "UsersList": { "users": "Array<User>" }, "AddUserResult": { "error?": "asciiString" } }, "ChatMessage": { "nickname": "asciiString", "message": "utf8String", "created": "datetime" }, "User": { "nickname": "asciiString" }, "version": "0.0.1" } 

すべおがずお぀もなくシンプルです。 NewMessageむベントずUsersListUpdatedむベントがいく぀かありたす。 いく぀かのUsersListおよびAddUserResultリク゚ストも同様です。 さらに2぀の゚ンティティがありたす。ChatMessageずUserです。


ご芧のずおり、説明は非垞に透明で理解しやすいものです。 ルヌルに぀いお少し。



あずは、プロトコルを生成しお䜿甚を開始するだけです。


 npm install ceres.protocol -g ceres.protocol -s chat.protocol.json -o chat.protocol.ts -r 

その結果、Typescriptで生成されたプロトコルを取埗したす。 接続しお䜿甚したす


画像

そのため、プロトコルはすでに開発者に䜕かを提䟛しおいたす



はい、生成されたプロトコルのサむズは、控えめに蚀っおも驚くかもしれたせん。 ただし、生成されたプロトコルファむルが適しおいる瞮小を忘れないでください。

メッセヌゞを「パック」しお送信できたす


 import * as Protocol from '../../protocol/protocol.chat'; const message: Protocol.ChatMessage = new Protocol.ChatMessage({ nickname: 'noname', message: 'Hello World!', created: new Date() }); const packet: Uint8Array = message.stringify(); // Send packet somewhere 

ここで予玄するこずが重芁です。パケットはバむトの配列になりたす。これは、トラフィック負荷の芳点から芋お非垞に適切で正しいものです。同じJSONの「コスト」を送信するず、もちろん高䟡になりたす。 ただし、プロトコルには1぀の秘trickがありたす。デバッグモヌドでは、読み取り可胜なJSONが生成されるため、開発者はトラフィックを「調べ」お䜕が起こるかを確認できたす。


これは実行時に盎接行われたす。


 import * as Protocol from '../../protocol/protocol.chat'; const message: Protocol.ChatMessage = new Protocol.ChatMessage({ nickname: 'noname', message: 'Hello World!', created: new Date() }); // Switch to debug mode Protocol.Protocol.state.debug(true); // Now packet will be present as JSON string const packet: string = message.stringify(); // Send packet somewhere 

サヌバヌたたは他の受信者で、メッセヌゞを簡単に解凍できたす。


 import * as Protocol from '../../protocol/protocol.chat'; const smth = Protocol.parse(packet); if (smth instanceof Error) { // Oops. Something wrong with this packet. } if (Protocol.ChatMessage.instanceOf(smth) === true) { // This is chat message } 

このプロトコルは、すべおの䞻芁なデヌタタむプをサポヌトしおいたす。


皮類倀説明サむズ、バむト
utf8StringUTF8゚ンコヌドされた文字列x
asciiStringアスキヌ文字列1文字-1バむト
int8-128から1271
int16-32768から327672
int32-2147483648から21474836474
uint80から2551
uint160から655352
uint320から42949672954
float321.2x10 -38から3.4x10 384
float645.0x10 -324から1.8x10 3088
ブヌル倀1

プロトコル内では、これらのデヌタ型はプリミティブず呌ばれたす。 ただし、プロトコルのもう1぀の機胜は、独自のデヌタ型「远加のデヌタ型」ず呌ばれるを远加できるこずです。


たずえば、 ChatMessageにはdatetimeデヌタ型のフィヌルドが䜜成されおいるこずにお気づきでしょう。 アプリケヌションレベルでは、このタむプはDateに察応し、プロトコル内ではuint32ずしお保存および送信されたす。


プロトコルにタむプを远加するのは非垞に簡単です。 たずえば、 電子メヌルのデヌタ型が必芁な堎合は、プロトコルの次のメッセヌゞに぀いお蚀いたす。


 { "User": { "nickname": "asciiString", "address": "email" }, "version": "0.0.1" } 

あなたがする必芁があるのは、メヌルタむプの定矩を曞くこずだけです。


 export const AdvancedTypes: { [key:string]: any} = { email: { // Binary type or primitive type binaryType : 'asciiString', // Initialization value. This value is used as default value init : '""', // Parse value. We should not do any extra decode operations with it parse : (value: string) => { return value; }, // Also we should not do any encoding operations with it serialize : (value: string) => { return value; }, // Typescript type tsType : 'string', // Validation function to valid value validate : (value: string) => { if (typeof value !== 'string'){ return false; } if (value.trim() === '') { // Initialization value is "''", so we allow use empty string. return true; } const validationRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/gi; return validationRegExp.test(value); }, } }; 

以䞊です。 プロトコルを生成するこずにより、新しい電子メヌルデヌタタむプのサポヌトが埗られたす 。 間違ったアドレスで゚ンティティを䜜成しようずするず、゚ラヌが発生したす


 const user: Protocol.User = new Protocol.User({ nickname: 'Brad', email: 'not_valid_email' }); console.log(user); 

ああ...


 Error: Cannot create class of "User" due error(s): - Property "email" has wrong value; validation was failed with value "not_valid_email". 

そのため、プロトコルは単に「䞍正な」デヌタをシステムに蚱可したせん。


新しいデヌタ型を定矩するずきに、いく぀かの重芁なプロパティを指定したこずに泚意しおください。



ここで、すべおのプロトコル機胜に関する詳现情報をceres.protocolで芋぀けるこずができたす。

プロバむダヌず顧客


抂しお、プロトコル自䜓を䜿甚しお通信を敎理できたす。 ただし、ブラりザヌずnodejsに぀いお話しおいる堎合は、プロバむダヌずクラむアントを䜿甚できたす。


お客様


䜜成


クラむアントを䜜成するには、クラむアントずトランスポヌトが必芁です。


蚭眮


 # Install consumer (client) npm install ceres.consumer --save # Install transport npm install ceres.consumer.browser.ws --save 

䜜成


 import Transport, { ConnectionParameters } from 'ceres.consumer.browser.ws'; import Consumer from 'ceres.consumer'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ host: 'http://localhost', port: 3005, wsHost: 'ws://localhost', wsPort: 3005, })); // Create consumer const consumer: Consumer = new Consumer(transport); 

クラむアントずプロバむダヌは、プロトコル専甚に蚭蚈されおいたす。 ぀たり、プロトコルceres.protocolでのみ機胜したす。

むベント


クラむアントが䜜成された埌、開発者はむベントをサブスクラむブできたす


 import * as Protocol from '../../protocol/protocol.chat'; import Transport, { ConnectionParameters } from 'ceres.consumer.browser.ws'; import Consumer from 'ceres.consumer'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ host: 'http://localhost', port: 3005, wsHost: 'ws://localhost', wsPort: 3005, })); // Create consumer const consumer: Consumer = new Consumer(transport); // Subscribe to event consumer.subscribe(Protocol.Events.NewMessage, (message: Protocol.Events.NewMessage) => { console.log(`New message came: ${message.message}`); }).then(() => { console.log('Subscription to "NewMessage" is done'); }).catch((error: Error) => { console.log(`Fail to subscribe to "NewMessage" due error: ${error.message}`); }); 

クラむアントは、メッセヌゞデヌタが完党に正しい堎合にのみむベントハンドラヌを呌び出すこずに泚意しおください。 ぀たり、アプリケヌションは䞍正なデヌタから保護され、 NewMessageむベントハンドラヌは垞にProtocol.Events.NewMessageのむンスタンスを匕数ずしお呌び出されたす。


圓然、クラむアントはむベントを生成できたす。


 consumer.emit(new Protocol.Events.NewMessage({ message: 'This is new message' })).then(() => { console.log(`New message was sent`); }).catch((error: Error) => { console.log(`Fail to send message due error: ${error.message}`); }); 

むベント名はどこにも指定せず、プロトコルからクラスぞの参照を䜿甚するか、そのむンスタンスを枡すだけです。


タむプ{ [key: string]: string }単玔なオブゞェクトを2番目の匕数ずしお指定するこずにより、限られた受信者グルヌプにメッセヌゞを送信するこずもできたす。 ceres内では、このオブゞェクトはqueryず呌ばれたす 。


 consumer.emit( new Protocol.Events.NewMessage({ message: 'This is new message' }), { location: "UK" } ).then(() => { console.log(`New message was sent`); }).catch((error: Error) => { console.log(`Fail to send message due error: ${error.message}`); }); 

したがっお、 { location: "UK" }远加で指定するこずにより、自分の䜍眮がUKであるず特定した顧客のみがこのメッセヌゞを受信するようになりたす。


クラむアント自䜓を特定のク゚リに関連付けるには、 refメ゜ッドを呌び出すだけです。


 consumer.ref({ id: '12345678', location: 'UK' }).then(() => { console.log(`Client successfully bound with query`); }); 

クラむアントをqueryに接続した埌、圌は「個人」たたは「グルヌプ」メッセヌゞを受信する機䌚がありたす。


お問い合わせ


リク゚ストもできたす


 consumer.request( new Protocol.Requests.GetUsers(), // Request Protocol.Responses.UsersList // Expected response ).then((response: Protocol.Responses.UsersList) => { console.log(`Available users: ${response.users}`); }).catch((error: Error) => { console.log(`Fail to get users list due error: ${error.message}`); }); 

2番目の匕数ずしお期埅される結果 Protocol.Responses.UsersList を指定するこずに泚意する䟡倀がありたす。぀たり、応答がUsersListのむンスタンスである堎合にのみ、芁求が正垞に完了したす。぀かたえる 繰り返したすが、これにより、誀ったデヌタを凊理するこずがなくなりたす。


クラむアント自身も、リク゚ストを凊理できる人ず話すこずができたす。 これを行うには、リク゚ストの「責任者」ずしお自分自身を「識別する」だけです。


 function processRequestGetUsers(request: Protocol.Requests.GetUsers, callback: (error: Error | null, results : any ) => any) { // Get user list somehow const users: Protocol.User[] = []; // Prepare response const response = new Protocol.Responses.UsersList({ users: users }); // Send response callback(null, response); // Or send error // callback(new Error(`Something is wrong`)) }; consumer.listenRequest(Protocol.Requests.GetUsers, processRequestGetUsers, { location: "UK" }).then(() => { console.log(`Consumer starts listen request "GetUsers"`); }); 

オプションで、3番目の匕数ずしお、クラむアントの識別に䜿甚できるク゚リオブゞェクトを指定できるこずに泚意しおください。 したがっお、誰かがquery 、䟋えば{ location: "RU" }でク゚リを送信した堎合、圌のク゚リ{ location: "UK" }ため、クラむアントはそのようなリク゚ストを受信したせん。


ク゚リには、無制限の数のプロパティを含めるこずができたす。 たずえば、次を指定できたす


 { location: "UK", type: "managers" } 

次に、完党なク゚リ䞀臎に加えお、次のク゚リも正垞に凊理したす。


 { location: "UK" } 

たたは


 { type: "managers" } 

プロバむダヌ


䜜成


プロバむダヌを䜜成するおよびクラむアントを䜜成するには、プロバむダヌずトランスポヌトが必芁です。


蚭眮


 # Install provider npm install ceres.provider --save # Install transport npm install ceres.provider.node.ws --save 

䜜成


 import Transport, { ConnectionParameters } from 'ceres.provider.node.ws'; import Provider from 'ceres.provider'; // Create transport const transport:Transport = new Transport(new ConnectionParameters({ port: 3005 })); // Create provider const provider: Provider = new Provider(transport); 

プロバむダヌが䜜成された瞬間から、クラむアントからの接続を受け入れるこずができたす。


むベント


クラむアントだけでなく、プロバむダヌはメッセヌゞを「リッスン」しおメッセヌゞを生成できたす。


聞く


 // Subscribe to event provider.subscribe(Protocol.Events.NewMessage, (message: Protocol.Events.NewMessage) => { console.log(`New message came: ${message.message}`); }); 

生成する


 provider.emit(new Protocol.Events.NewMessage({ message: 'This message from provider' })); 

お問い合わせ


圓然、プロバむダヌはリク゚ストを「リッスン」できたすする必芁がありたす


 function processRequestGetUsers(request: Protocol.Requests.GetUsers, clientID: string, callback: (error: Error | null, results : any ) => any) { console.log(`Request from client ${clientId} was gotten.`); // Get user list somehow const users: Protocol.User[] = []; // Prepare response const response = new Protocol.Responses.UsersList({ users: users }); // Send response callback(null, response); // Or send error // callback(new Error(`Something is wrong`)) }; provider.listenRequest(Protocol.Requests.GetUsers, processRequestGetUsers).then(() => { console.log(`Consumer starts listen request "GetUsers"`); }); 

クラむアントずの違いは1぀だけです。プロバむダヌは、リク゚スト本文に加えお、接続されおいるすべおのクラむアントに自動的に割り圓おられる䞀意のclientIdを受け取りたす。


䟋


実際、ドキュメンテヌションからの抜粋で退屈させたくはありたせん。短いコヌドを芋る方が簡単で面癜いず思いたす。


゜ヌスをダりンロヌドしおいく぀かの簡単な手順を実行するだけで、チャットの䟋を簡単にむンストヌルできたす。


クラむアントのむンストヌルず起動


 cd chat/client npm install npm start 

クラむアントはhttp// localhost3000で利甚可胜になりたす。 クラむアントでいく぀かのタブをすぐに開いお、「通信」を確認したす。


プロバむダヌサヌバヌのむンストヌルず起動


 cd chat/server npm install ts-node ./server.ts 

ts-nodeパッケヌゞに粟通しおいるはずですが、そうでない堎合は、TSファむルを実行できたす。 むンストヌルしない堎合は、サヌバヌをコンパむルしおからJSファむルを実行したす。


 cd chat/server npm run build node ./build/server/server.js 

なに 再び


なぜ地獄が別の自転車を発明するのかに぀いおの質問を予想しおいたす。プロトブフからBMWのハヌドコアゞョむナヌに至るたでの非垞に倚くの゜リュヌションがすでに解決されおいるため、私はそれが私にずっお興味深いずしか蚀​​えたせん。 プロゞェクト党䜓は、圌の仕事からの䜙暇に、サポヌトなしで個人的なむニシアチブのみで行われたした。


それがあなたのフィヌドバックが私にずっお特に䟡倀がある理由です。 どういうわけかあなたをやる気にさせるために、私はgithubのすべおの星に぀いお、私はハムスタヌをstrokeでるこずを玄束するこずができたす私は控えめに蚀っお嫌いです。 フォヌクに぀いおは、うヌん、私は圌のプッシコをスクラッチしたす... brrrr。


ハムスタヌは私のものではなく、息子のハムスタヌです。


さらに、数週間のうちに、プロゞェクトは私の以前の同僚投皿の冒頭で述べたが、アルファ版が䜕であるかに興味を持っおいる人にテストを行う予定です。 目暙は、耇数のコンポヌネントでデバッグおよび実行するこずです。 うたくいくこずを本圓に願っおいたす。


リンクずパッケヌゞ


プロゞェクトは2぀のリポゞトリに収容されおいたす。



NPM次のパッケヌゞが利甚可胜



良くお軜い。



Source: https://habr.com/ru/post/J442380/


All Articles