MeteorGuide1.4 Application Structureの意訳

Meteor Guide 1.4のApplication Structureを勉強がてら、勝手に翻訳(意訳)してみました。

また、前の記事ではCodeStyleについて意訳しています。まず日本語でざっと読んで、原文に行けば良いのかなぁと思います。素人の意訳ですので、ご理解ください。なお、ご指摘は大歓迎です。誤訳などがあれば、教えて下さい

Application Structure

クライアントやサーバへのコードの受け渡しや複数アプリへのコード分割など、ES2015モジュールを用いたMeteorアプリの構成について

この記事を読むと、以下のことがわかります:

  1. ファイル構成において、どのようにMeteorアプリケーションが他のアプリケーションと違うのか
  2. 大小のアプリケーションをどう構成するのか
  3. 管理しやすくするには、どうコードと名称をフォーマット化するのか

Universal JavaScript

MeteorはJavaScriptアプリケーションを構築するためのフルスタックフレームワークです。これは、Meteorアプリケーションが、他の殆どのアプリケーションと異なることを意味します。それらは、WebブラウザやCordovaモバイル内などのクライアント上で実行されるコードを含み、Node.jsコンテナー内などのサーバ上で実行されるコードを含み、そして、両方の環境で実行される共通コードを含みます。Meteorのビルドツールは、任意のサポートしているUIテンプレートやCSSルールや静的ファイルを含むJavaScriptのコードを、S2015のインポートとエクスポートの組み合わせとMeteorビルドシステムのデフォルトファイルロード順序のルールを使用して、どの環境で実行させるかを簡単に指定することができます。

ES2015 modules

Meteor 1.3では、MeteorはES2015モジュールを完全にサポートするようになりました。標準ES2015モジュールは、JavaScriptモジュールフォーマットとロードシステムで幅広く使用されているCommonJSAMDに替わるものです。

ES2015では、exportキーワードを使用し、ファイルの外で変数を使用することができます。他のファイルに定義している変数を使用するには、ソースパスを添えてimportする必要があります。変数をエクスポートしているファイルは、再利用可能なコードユニットとして”モジュール”と呼ばれます。使用するモジュールとパッケージを明確にインポートすることで、グローバルシンボルの紹介や遠隔作用を回避するなどモジュールを用いたコードを書くことができます。

ES2015がMeteor 1.3の新しい機能として発表されて以来、古いバージョンのコードをオンライン上で多く見かけます。それらの古いコードでは、より中央集中的な慣習がグローバルシンボルを定義しているパッケージやアプリの周りに作られています。この古いシステムはまだ機能しており、新しいモジュールシステムコードは、アプリケーションのimportsディレクトリ内に置く必要があります。近い将来、Meteorでは、デフォルトでモジュールを機能させるようにする予定です。なぜならば、広いJavaScriptコミュニティで開発者のコードの書き方を統一させることができるからです。

モジュールシステムについては、modules package READMEに記載されています。このパッケージは、自動的に全ての新しいMeteorアプリをecmascript meta-packageの一部として含んでおり、ほとんどのアプリは直ぐにモジュールを使用するすることができます。

Introduction to using import and export

Meteorでは、JavaScriptファイルだけではなく、CSSやHTMLファイルも読み込み順序をコントロールするためにimportさせることができます。

インポートスタイルについては、Build Systemの記事に詳細が記載されています。

Meteorでは、標準ES2015モジュールのexportシンタックスもサポートしています。

Importing from packages

Meteorでは、npmパッケージをクライアントやサーバにロードするためにimportシンタックスを使用したり、パッケージからエクスポートされたシンボルにアクセスすることが簡単に素直に実現できます。もちろん、Meteor Atmosphereパッケージからのインポートも可能ですが、npmパッケージとのコンフリクトを避けるために、Atmosphereパッケージを使用する際はインポートパスの先頭にmeteor/を付ける必要があります。例えば、momentをnpmから、HTTPをAtmosphereからインポートする場合は、次のようになります。

importsの使用方法については、Meteor GuideのUsing Packagesに詳しく記載されています。

Using require

Meteorでは、importステートメントは、CommonJSのrequireシンタックスにコンパイルされます。しかし、慣習として、requireではなく、importを使用することをオススメします。

そうは言っても、requireを使用しなければならない場合があります。共通ファイルからクライアントかサーバだけのコードが必要な場合です。importは必ずトップレベルのスコープである必要があり、ifステートメント内に記載することができません。そのため、次のようなコードにする必要があります。

ちなみに、(要求される名前がランタイム時に変化する可能性がある)require()の動的呼び出しは、正確に分析されないと、壊れたクライアントバンドルを返す可能性がありますので、注意してください。

もし、defaultエクスポート付きでES2015モジュールをrequireする場合は、require("package").defaultでエクスポートをつかむことができます。

requireを使用するその他のケースとしては、CoffeeScriptファイル内です。CoffeeScriptはimportシンタックスをまだサポートしていないため、requireを使用しなければなりません。

Exporting with CoffeeScript

CoffeeScriptでは、変数のインポートシンタックスが異なるのみならず、エクスポートも異なります。エクスポートしたい変数をexportsオブジェクトに入れる必要があります。

File structure

モジュールシステムを全て使用し、コードを要求した時にのみ実行させるため、全てのアプリケーションコードをimportsディレクトリ配下に置くことをお勧めします。これは、Meteorのビルドシステムが、importされているファイルのみを対象に含めることを意味します(これは、”lazy evaluation or loading”と呼ばれます)。

Meteorは、デフォルトのファイルロード順序に従い、importsディレクトリ以外のファイルを読み込みます(これは“eager evaluation or loading”と呼ばれます)。クライアントとサーバのエンドポイントを明確にするために、client/main.jsserver/main.jsの2つのファイルを作成することを推奨します。Meteorでは、server/ディレクトリ配下のファイルはサーバサイドでしか機能しません。同じように、client/ディレクトリ配下のファイルはクライアントサイドでしか機能しません。client/ディレクトリ配下のファイルがサーバサイドでインポートされるのを防ぎ、また逆に、sever/ディレクトリ配下のファイルがクライアントサイドでインポートされるのを防ぎます。それが、たとえimports/ディレクトリでネストされていようともです。

これらのmain.jsファイルは、それら自体では機能しませんが、アプリがロードされると直ぐにクライアントサイドとサーバサイドのそれぞれで起動するstartupモジュールをインポートする必要があります。これらのモジュールでは、アプリで使用するパッケージの必要な設定をさせ、残りのコードをインポートさせる必要があります。

Example directory layout

まずはじめに、TODOサンプルアプリケーションを見てください。このアプリは、あなたがアプリを構成する上でとても良いお手本になります。ここに、ディレクトリ構成の概要を載せます。

Structuring imports

ここではimports/内の全ファイルに注目し、モジュールを使用したコードをどう整理していくのが良いのかを考えてみましょう。 アプリがスタートする際に機能するコードをimports/startupに置くのは理解できます。また、データとビジネスロジックをUIレンダリングコードから分けることも良いアイデアです。このアイデアに従って、imports/apiimports/uiのディレクトリを使用することを推奨します。

imports/apiディレクトリにおいて、APIで用いるドメインによってコードをディレクトリで分けることは賢明です。 たとえば、TODOサンプルアプリでは、imports/api/listsimports/api/todosのドメインを持っています。 それぞれのディレクトリでは、関連するドメインデータを操作するためのcollectionやpublicationやmethodを定義しています。

Note: このtodos自体がリストの一部であるような規模の大きなアプリケーションでは、これらのドメインを一つの大きな”list”モジュールに集めることは納得できます。モジュールについて実証するのに、このTodosサンプルは、これ以上分ける必要がない小ささです。

imports/uiディレクトリ内では、トップレベルのpagesやラッピングlayoutsや再利用できるcomponentsなどで定義できるUIコードのタイプ別でグループ分けするのは理にかなっています。

これまで定義してきたモジュールにおいて、ベースのJavaScriptファイルを使用し、様々な補助ファイルを一箇所に置くことは理にかなっています。 たとえば、BlazeUIテンプレートでは、テンプレートHTMLやJavaScriptロジックやCSSルールを同じディレクトリ内に置くべきです。 ビジネスロジックを含むJavaScriptモジュールはモジュールのユニットテストと一緒に置くべきです。

Startup files

いくつかのコードは、ビジネスロジックやUIコードではなく、アプリを起動する際に必要な設定を記述しているだけのものもあります。 Todosサンプルアプリで言うと、imports/startup/client/useraccounts-configuration.jsは、useraccountsロジックテンプレートを設定するファイルであり(useraccountsについてはAccountsに詳しい記事があります)、imports/startup/client/routes.jsは全てのルートを設定し、かつ、クライアントサイドで必要なコードをインポートしているファイルです。

imports/startup/client/index.js内でこれらのファイルをインポートしています。

一つのインポートで全てのクライアントのスタートアップコードを、メインのeagerly loadedクライアントエントリポイントclient/main.jsとしてインポートする簡単な方法は、次の通りです。

サーバサイドでも同じように全てのスタートアップコードをimports/startup/server/index.js内でインポートしています。

メインサーバのエントリポイントであるserver/main.jsは、このスタートアップモジュールでインポートします。これらのファイルから変数をインポートしていないことに気付いたでしょうか。これらのファイルをインポートしたら、この順序でそれらは処理されます。

Importing Meteor “pseudo-globals”

後方互換性のためMeteor 1.3では、Meteor coreパッケージのためのグローバル名前空間を提供しています。 前のバージョンのMeteorみたいに、インポートなしでMeteor.publish関数を直接使用することもできます。 しかし、ベストプラクティスとして、import { Name } from 'meteor/package'シンタックスを使用してMeteorの”pseudo-globals”(擬似グローバル)を最初にロードすることを推奨します。 たとえば、こんな感じです。

Default file load order

ES2015モジュールとimportsディレクトリを使用することを推奨しますが、Meteor1.3ではファイルのeager loadingをサポートし続けます。これは、Meteor1.2以前のアプリケーションとの後方互換性を提供するためです。シングルアプリケーションの中で、importを使用するlazy loadingとeager loadingの両方を使用することができます。ファイルが読み込まれ、ファイルの読み込みルールが評価される際に、インポートのステートメントはファイル内でのリスト順に評価されています。

ファイルの読み込み順序のルールはいくつか存在します。それらは、アプリケーション内で全ての該当ファイルに対して淀みなく適用されます。プライオリティが高い順に以下のようになっています。

  1. HTMLテンプレートはいつも一番最初に読み込まれます。
  2. main.で始まるファイルは、最後に読み込まれます。
  3. lib/内のファイルは、次の読み込まれます。
  4. ファイルの階層が深い順に読み込まれます。
  5. ファイルは、全体パスのアルファベット順に読み込まれます。

例えば、上記のファイルリストは、正しい読み込み順序で並べられています。 mainで始まっているにもかかわらず、main.htmlは2番めに読み込まれているのは、このファイルがHTMLテンプレートであり、HTMLテンプレートはいつも1番最初に読み込まれるためです。ルール1はルール2より優先度が高いわけです。 しかし、nav.htmlよりあとにmain.htmlが読み込まれているのは、ルール2がルール5より優先度が高いためです。

client/lib/style.jslib/feature/styles.jsは、同じルール4の読み込み順序に従っていますが、アルファベット順に従ってclientの方がlibより前に読み込まれます。

Meteor.startup`を使用し、サーバとクライアントの両サイドで、コードを実行するタイミングを管理することもできます。

Special directories

基本的に、MeteorアプリケーションのJavaScriptファイルはクライアントとサーバサイドでまとめられ、読み込まれます。 しかし、ファイルやディレクトリの名前によって、読み込み順序やどこで読み込まれるのかなどを制御することができます。 Meteorにとって特別な意味を持つファイルとディレクトリのリストを次にあげていきます。

  • imorts
    imports/ディレクトリは読み込まれず、それらのファイルはimportによってインポートされる必要があります。

  • node_modules
    node_modulesディレクトリは読み込まれません。 node_modulesにインストールされたnode.jsパッケージはimportによってインポートされるか、package.js内でNpm.dependsで使用される必要があります。

  • client
    if (Meteor.isClient) { ... }と同じように、clientディレクトリはサーバサイドでは読み込まれません。 クライアントサイドで読み込まれたファイルは、プロダクトモードの際、自動的にまとめられ、圧縮されます。 デベロップメントモードの際は、JavaScriptとCSSファイルはデバッグしやすいように圧縮されません。 CSSファイルは、プロダクトモードでもデベロップメントモードでも一つのファイルにまとめられます。なぜならば、CSSファイルのURLが変わることは、URLの処理に影響を及ぼすからです。

MeteorアプリケーションのHTMLファイルは、サーバサイドフレームワークと扱いが異なります。Meteorでは、と“の3つのトップレベルエレメントで全てのHTMLファイルがスキャンされます。headとbodyセクションは、それぞれ1つのheadとbodyにまとめられ、クライアントの最初のページロード時に転送されます。

  • server
    if (Meteor.isServer) { ... }と同じように、server/のディレクトリは、クライアントでは読み込まれません。 パスワードや認証ロジックなどのクライアントに渡したくないコードは、server/ディレクトリに格納しておくのが良いでしょう。

clientpublicprivateを除いて、Meteorは全てのJavaScriptフィルをまとめ、それらをNode.jsサーバノードに読み込ませます。 Meteorでは、サーバコードはリクエスト毎にシングルスレッドで実行します。これは、典型的なNodeの非同期コールバックスタイルではありません。

  • public
    トップディレクトリであるpublic/ディレクトリ配下の全てのファイルはクライアントに渡されます。 これらのコードを参照する際は、URLにpublic/を含めてはいけません。 例えば、public/bg.pngをリファレンスする場合、<img src="/bg.png" />となります。 publicディレクトリは、favicon.icorobots.txtなどを置くのに、最適なディレクトリです。

  • private
    privateディレクトリ配下の全てのファイルは、サーバコード内からのみアクセスでき、AssetsAPIを通して読み込み可能です。 外部からアクセスさせたくないようなファイルやプライベートなファイルのために使用できます。

  • client/compability
    このフォルダは、グローバル変数に紐づくJavaScriptライブラリとの互換性のためのフォルダです。 このディレクトリ内のファイルは、新しい変数スコープでラッピングされずに実行されます。 これらのファイルは、他のクライアントサイドJavaScriptファイルよりも前に実行されます。

サードパティのJavaScriptライブラリとしてnpmを使用し、importで読み込みタイミングを制御することを推奨します。

  • tests
    tests/ディレクトリは、読み込まれません。 Meteor’s built-in test tools以外で、テストランナーで実行させたいテストコードを格納するために使用して下さい。

次のディレクトリは、アプリでは読み込まれません。

  • .meteor.gitなどのドット”.”で始まるファイルとディレクトリ
    • packages/: ローカルパッケージのために使用させます
    • cordova-build-override/: advanced mobile build customizationsで使用されます
    • programs: レガシー理由のため

Files outside special directories

特定のディレクトリ外にある全てのJavaScriptファイルはクライアントとサーバの両方で読み込まれます。 Meteorでは、変数Meteor.isClientMeteor.isServerが提供されており、クライアントかサーバのどちらで処理させるのかを制御できます。

特定のディレクトリ外にあるCSSとHTMLファイルは、クライアントでのみ読み込まれ、サーバ側のコードからは使用できません。

Splitting into multiple apps

もし複雑なシステムを書いているのであれば、コードをマルチアプリケーションに分ける良いチャンスです。 例えば、管理者UIを分けるアプリケーションやモバイルとデスクトップアプリを分けるアプリのような場合です。

もう一つのよくあるケースとしては、処理プロセスを分けるケースです。負荷の高いジョブでも、シングルWebサーバを占有せず、ユーザエクスペリエンスに影響しないようにできます。

マルチアプリケーションに分ける利点を載せておきます:

  • もし特定のユーザが絶対に使用しないコードを分けた場合、クライアントJavaScriptはとても小さくなります。
    • 安全に異なるアプリケーションを異なるスケーリングセットアップを用いてデプロイできます。(例えば、ユーザにファイアーウォールを介した管理アプリケーションへのアクセスを強要するなどです。)
    • 異なるチームで、それぞれが異なるアプリケーションを開発させることができます。

しかし、コードを分けるにはチャレンジングな箇所が多々あるため、実行する前によく考えるべきです。

Sharing code

一番のチャレンジは、異なるアプリケーション間で適切にコードを共有することです。 最も単純な解決方法は、異なるsettingsを用いて管理して、同じアプリケーションを異なるWebサーバにデプロイさせる方法です。 この方法は、異なるスケールを持つ異なるバージョンのデプロイを簡単にしますが、上記の利点が十分に活用できなくなってしまいます。

コードを分けてMeteorアプリケーションを作成したいのであれば、共有したいモジュールを用意するべきです。 これらのモジュールを使用するにあたり、パッケージシステムとしての公開方法についてよく考慮する必要があります。

もしコードがプライベート、または他に影響しないのであれば、同じモジュールを両方のアプリケーションで有することは理にかなっています。(これは、private npm modules)で実現可能です。) これを実現する方法はいくつかあります:

  • 純粋な方法は、両方のアプリケーションで共通コードをgit submoduleとして単純に含める方法です。
    • それ以外では、ひとつのレポジトリで両方のアプリケーションを管理し、両方のアプリ内で共通モジュールを含めるためにシンボリックリンクを使用する方法です。

Sharing data

その他に考慮するべき重要な点は、異なるアプリケーション間でどうやってデータを共有するかです。

最も単純な解決方法は、両方のアプリケーションが同じMONGO_URLを使用し、両方のアプリケーションからデータベースへのダイレクトな読み込みと書き込みを許可する方法です。 この方法は、データベースを介したリアクティブなMeteorのお陰で上手く動作します。 一方のアプリがMongoDBにあるデータを変更させた際、同じデータベースに接続している他のアプリのユーザは、Meteorライブクエリ(livequery)のお陰で、即座にその変化を見ることができます。

しかし、APIを介し他のアプリケーションにマスタ管理者として、アクセスコントロールさせた方が良い場合があります。 この場合、異なるアプリケーションを異なるタイミングでデプロイさせるときやデータ変更方法の保守性が必要なときに役に立ちます。

サーバからサーバへのAPIを実装する簡単な方法は、直接MeteorのビルトインDDPプロトコルを使用する方法です。これは、Meteorクライアントがサーバからデータを取得する方法と同じで、アプリケーション間の通信にも使用できます。 あるクライアントサーバからマスターサーバに接続するために、DDP.connect()を使用できます。そして、パブリケーションからの読み込みとメソッドの実行から返される接続オブジェクトを使用できます。

Sharing accounts

同じデータベースに2つのサーバがアクセスしており、認証されたユーザにDDP呼び出しを許可したい場合、一つのログイン接続で、もう一つもログインできるresume tokenを使用できます。

ユーザがサーバAに接続している場合、サーバBにも接続をオープンさせるためDDP.connect()を使用し、サーバAのresume tokenを認証のためにサーバBに渡せます。 両方のサーバで、同じDBをを使用しているならば、同じサーバトークンは両方で機能します。 認証のコードはこんな感じになります:

この構成のコンセプトの実例は、サンプルレポジトリで確認できます。