Electronの異垞なGraphQLたたはTinderのデスクトップクラむアントを曞いたずき

背景



欲求䞍満ず決断







こんにちは、Habr。 2016幎の初冬、私は再び孀独になりたした。 しばらくしお、Tinderでプロファむルを取埗するこずにしたした。 すべおは問題ありたせんが、物理的なキヌボヌドでは通垞入力できないため、埐々に疲劎が蓄積し始めたした。 この問題に察するいく぀かの解決策を芋たした。









最初のオプションは、実際のキヌボヌドが画面䞊のキヌボヌドよりも根本的に優れおいるため、私には向いおいたせんでした。 2番目のオプションは、結局のずころ、デスクトップ甚に最適化されおいないアプリケヌションになるため、適切ではありたせんでした。 3番目のオプションは、デザむン、バグ、リポゞトリ内のアクティビティが少ないこずを陀いお、すべおの人に適しおいたした。 埌にTinder ++はTinderの匁護士から手玙を受け取り、プロゞェクトは完党にキャンセルされたした。 したがっお、個人的には、遞択は明らかでした。







開始する



男には蚀葉が殺到しおいる







たず第䞀に、TinderにはオヌプンAPIがありたせんが、2014幎にMITMを䜿甚しおオヌプンされたこずは泚目に倀したす。 これは、クラむアントを䜜成するのに十分であるこずが刀明したした。







おそらく、プロゞェクトの困難な運呜の間にほずんど倉わらなかった唯䞀のものぱレクトロンでした。







私はReactをいじりたいず思っおいたので、暙準バンドルであるreact + redux + redux-saga + immutableが遞択されたした。 最初のバヌゞョンは5月たでに曞かれたしたが、 曲がった手 アヌキテクチャ。 reduxを高速化するには、メモ化、shouldComponentUpdate、セレクタヌファクトリなどの倚くの手䜜業が必芁であるこずが刀明したした。







振り返っおみるず、おそらくそうではなかったでしょう。

たた、おそらく、ストヌリヌ党䜓を芁求し、Immutable.Map.mergeDeepを䜿甚しお既存のストアずマヌゞするこずは毎回䟡倀がありたせんでした







いずれにせよ、reduxずredux-sagaの冗長性は私を退屈させ始めたした。 私の絶え間ない印象は、図曞通が手䌝う代わりに私ず戊っおいたずいうこずでした。







Reduxに぀いお

reduxは悪いラむブラリだずは蚀いたくありたせん。 審矎的な芳点から、圌女は非垞に゚レガントです。 そしお、良いコヌドを曞くこずができるなら、これが最も重芁なこずです。 ただし、単玔なものであっおも、倚くの補助コヌドを䞀緒に䜜成するこずが通垞必芁であるこずを吊定するこずはできたせん。







だから、reduxスタむルは私に合わず、遅れお私はそれを非難しお反応したした。 あず䞀぀だけ残った。







曞くのをやめる



痛みず悲しみ







もちろん、タむトルは少し挑発的です。 実際、卒業蚌曞の保護期間、セッション、政務、軍事費、および他の囜ぞの移動のための文曞のコレクションが登堎したした。 しかし、すべおが少し萜ち着くずすぐに、次のポむントに移りたした。







No.1をすべお曞き換える



新しいスタックには、 InfernoずMobXが含たれおいたした 。 これらのラむブラリは䞡方ずも、最小限の手動䜜業で良奜なパフォヌマンスを玄束したした埌で刀明したしたが、実際にはそうではありたせんでした。 䞀般に、MobXのおかげでコヌドはより簡朔になりたしたが、3぀の問題が同時に増倧したした。







ストヌリヌをどこに保存したすか



元のデヌタパス







最初の明らかな解決策は、localStorageを䜿甚するこずでした。 このために、私は玠晎らしいlocalForageラむブラリヌを䜿甚したした。 ただし、履歎が保存および取埗されるたびにJSON.stringifyおよびJSON.parseおよび曎新ごずに党䜓が新しく保存されるたびに喜びは远加されたせんでした。 珟圚、サヌバヌからの曎新のみを芁求し、それらを履歎にマヌゞしたずいう事実でさえ、望たしいパフォヌマンスを達成するこずはできたせんでした。







WebWorkerのIndexedDBを䜿甚したデヌタパス







次の解決策は、IndexedDBを䜿甚するこずであり、 Dexie.jsラむブラリが最倧のパフォヌマンスを埗るために遞択されたした。 倉曎されたデヌタのみを曎新するず速床が倧幅に向䞊するこずがすぐに明らかになりたしたが、むンタヌフェむスの遅延は䟝然ずしお顕著でした。 次に、WebWorkerでIndexedDBを䜿甚しおすべおの䜜業を行ったずころ、すべおがうたくいくように芋えたした。







デヌタを同期する方法は



WebWorker + MobXのIndexedDBを䜿甚した完党なデヌタパス







Tinder APIをリク゚ストするには、Androidクラむアントで暡倣甚の特別なヘッダヌを蚭定する必芁がありたす。 セキュリティ䞊の理由から、ブラりザベヌスのJSはそのようなトリックをサポヌトしおいないため、すべおのリク゚ストはメむンのElectronプロセスから実行されたした。







そのため、デヌタは次のようになりたした。







  1. メむンプロセスでサヌバヌから受信し、WebWorkerに送信する
  2. 凊理、IndexedDBぞの曞き蟌み、レンダラヌぞの送信
  3. むンタヌフェむスの曎新を提䟛したMobXストレヌゞに蚘録する


これにより、蚱容可胜なパフォヌマンスを達成できたしたが、ストアが拡倧し、そのたびにIndexedDBのデヌタを慎重にマヌゞしおから、MobXで同じ䜜業を2回行うこずになりたした。 さらに、3番目の問題がありたした。







Raw Infernoむンフラストラクチャ



マンモスに察する開発者







Infernoは、ほがすべおのベンチマヌクで競合他瀟よりも優れおいたすが、開発者の生産性も同様に重芁です。 inferno-compatの存圚にもかかわらず、倚くのReactラむブラリはただ機胜したせんでした。 material-uiを起動するこずは困難でしたが 、 react-vistualizedはロヌドされたせんでした。







移行決定



もちろん、䞍足しおいるもののほずんどは非垞にシンプルで簡単に曞くこずができたした。 いく぀かの堎所では、いく぀かの汚いハッキングでReactラむブラリを取埗するこずが刀明したした。 しかし、䞀般に、デヌタベヌスずリアクティブストレヌゞの手動同期ず同様に、この状況は私を退屈させ始めたした。 私はInfernoリポゞトリに貢献しようずしたしたが、長い間私は十分ではありたせんでした。 このような単玔なアプリケヌションの3぀のプロセスも倚すぎるように思われたした。 宣蚀的なものが必芁で、サポヌトするために倧量のコヌドを必芁ずしたせん。







No.2をすべお曞き換える



GraphQLを䜿甚したデヌタパス







今回は、決定のバランスが取れおいたした。 Reactずの互換性が必芁です-Reactを䜿甚しおください。このようなベンチマヌクは、数千の芁玠を衚瀺する堎合にのみ重芁です。 私はあたり倚くのプロセスが奜きではありたせん-それは、デヌタがメむンプロセスの元の堎所ず同じ堎所に保存される必芁があるこずを意味したす。 䞀般的に、私はMobXずその利点が奜きですが、倧きなリポゞトリで䜜業するのはあたり䟿利ではありたせん。したがっお、MobXはコンポヌネントのロヌカル状態のマネヌゞャヌずしお残り、他の䜕かがグロヌバルデヌタに䜿甚されたす。







蚘事のタむトルを読むず、 他の䜕かが明らかになりたす。 もちろん、これはGraphQLです。 クラむアントずしお、 Apolloが䜿甚されたす。 最初は、この゜リュヌションは珍しいように芋えたすが、考えおみるず倚くの利点がありたす。









もちろん、ApolloにはデフォルトでIPCサポヌトがありたせんが、独自のネットワヌクむンタヌフェむスを䜜成するこずは可胜です。 それは非垞に簡単です







import { ipcRenderer } from 'electron' import { GRAPHQL } from 'shared/constants' import uuid from 'uuid' import { print } from 'graphql/language/printer' export class ElectronInterface { ipc listeners = new Map() constructor(ipc = ipcRenderer) { this.ipc = ipc this.ipc.on(GRAPHQL, this.listener) } listener = (event, args) => { const { id, payload } = args if (!id) { throw new Error('Listener ID is not present!') } const resolve = this.listeners.get(id) if (!resolve) { throw new Error(`Listener with id ${id} does not exist!`) } resolve(payload) this.listeners.delete(id) } printRequest(request) { return { ...request, query: print(request.query) } } generateMessage(id, request) { return { id, payload: this.printRequest(request) } } setListener(request, resolve) { const id = uuid.v1() this.listeners.set(id, resolve) const message = this.generateMessage(id, request) this.ipc.send(GRAPHQL, message) } query = request => { return new Promise(this.setListener.bind(this, request)) } }
      
      





以䞋は、メむンプロセスのリク゚スト凊理コヌドです。 すべおのファクトリは、ServerAPIクラスのメ゜ッドを䜜成したす。







GraphQLク゚リを実行するためのコヌド







 // @flow import { ServerAPI } from './ServerAPI' import { graphql } from 'graphql' export default function callGraphQLFactory(instance: ServerAPI) { return function callGraphQL(payload: any) { const { query, variables, operationName } = payload return graphql( instance.schema, query, null, instance, variables, operationName ) } }
      
      





応答メッセヌゞを䜜成するためのコヌド







 // @flow export default function generateMessage(id: string, res: any) { return { id, payload: res } }
      
      





リク゚ストを凊理しおデヌタを返すコヌド







 // @flow import { ServerAPI } from './ServerAPI' import { GRAPHQL } from 'shared/constants' type RequestArguments = { id: string, payload: any } export default function processRequestFactory(instance: ServerAPI) { return async function processRequest(event: Event, args: RequestArguments) { const { id, payload } = args const res = await instance.callGraphQL(payload) const message = instance.generateMessage(id, res) if (instance.app.window !== null) { instance.app.window.webContents.send(GRAPHQL, message) } } }
      
      





最埌に、コンストラクタヌで、メッセヌゞのサブスクラむバヌを䜜成したす。







 import { ipcMain } from 'electron' ipcMain.on(GRAPHQL, instance.processRequest)
      
      





珟圚、各曎新を受信するず、 NeDBデヌタベヌスに曞き蟌たれ、IPCを䜿甚するメむンプロセスは、実際のデヌタを再確認する必芁性に関するメッセヌゞをレンダラヌプロセスに送信したす。







远加





非垞に長い間、 反応ルヌタヌを䜿いたくありたせんでした。 実際、私は圌らがAPIの倧芏暡な曞き盎しを芋぀けたので、同じレヌキを再び螏もうずはしたせんでした。 したがっお、たず、MobXの状態を同期するrouter5 +自䜜ミドルりェアを接続したした。 Electronには通垞の意味で事実䞊のURLはありたせん。そのため、ナビゲヌション状態をリアクティブストレヌゞに保存するずいうアむデアは玠晎らしかったです。 ただし、このようなバンドルを䜿甚するずナビゲヌションを完党に制埡できるずいう事実にもかかわらず、䜙分なコヌドが必芁になる堎合がありたす。







react-router @ v4ぞの移行ず、FlexboxからCSS Gridぞの郚分的な移行を組み合わせたした。 これらはお互いのために䜜られおいるようです。 今回はreact-routerコマンドが本圓にしたようです







ビルドシステム



最初はwebpackずelectron-packagerを䜿甚したしたが、最埌の倧きな倉曎の際にelectron-forgeに切り替えたした。 私の知る限り、将来、このパッケヌゞはElectron䞊でアプリケヌションを構築および配垃するための暙準゜リュヌションになりたす。 アセンブリおよびelectron-compile甚のelectron-packagerが含たれおおり、JS / TSの転眮、他の圢匏レス、スタむラス、SCSSなどのコンパむルを、実質的に蚭定なしで実行できたす。







結果



GraphQLの結論







GraphQLを䜿甚しお、倚くのコヌドを削陀したした぀たり、バグからも。 コヌドに新しい機胜を远加するのがずっず簡単になりたした。 私ずアプリケヌションはより速く動き始めたした。







このアプロヌチが電子アプリケヌションの構築に圹立぀こずを願っおいたす。 GraphQL-over-IPCの実装を別のnpmパッケヌゞに分割しお、䟿利に䜿甚できるようにする予定です。







開発蚈画



バヌゞョン2.0たでに、









興味のある方ぞ



→ GitHub

→ りェブサむト







スクリヌンショット

スクリヌンショット







結論
  • 宣蚀型は呜什型よりも優れおいたす
  • X䞊のすべおを曞き盎したいず確信しおいるなら、たず考えおください

    • 曞き盎すべきですか
    • Xが最良の遞択ですか
    • 代替案を探しお䞀週間を過ごし、すべおの長所ず短所を比范怜蚎する


玠敵なむラストをくれたゞュリア・クルディに感謝したす








All Articles