数週間前、GitHubでプロジェクトをシェアしている開発者に出会いました。そのプロジェクトはTrelloのクローンで、React、Redux、Express、MongoDBからなるプロジェクトでした。全階層、広い範囲にわたりフルスタックのJavaScriptスキルが駆使されていました。
開発者のMoustapha Dioufに、プロジェクトに取り組む際の技術選択、設計、開発について書いてみないかとたずねると、うれしいことに彼は賛成してくれました。この記事があなたのプロジェクトに役立てば幸いです。(by SitePoint Editor:Nilson Jacques)
プロジェクトのアプローチを順に見ながら、Webアプリ作成時に実践したことを説明します。紹介するテクニックは、どんなプログラミング言語においても当てはまります。私はJava/JavaScriptの仕事でこの方法を使い、生産性がぐっと向上しました。
本記事で説明するのは以下の3つです。
- プロジェクト開始前にどうやってゴールを定めたか
- 利用する一連の技術をどうやって決定しているのか
- どうやってアプリを開発しているのか
全プロジェクトはGitHub(madClones)にあるで、コードの中身ではなく設計や構造を説明します。コードの動作デモも用意しました。認証画面でTest/Testと入力すればログインできます。
ゴールを定める
1日数時間、アプリでなにを成し遂げたいのか考える時間を取りました。単純すぎるTo-Doアプリは論外でした。最低でも4カ月は集中して真剣に取り組みたかったので単純すぎるTo-Doアプリは論外でした(現在8カ月目です)。1週間考え、毎日使うアプリのクローンを作成する案が浮かびました。Trelloのクローンを作るサイドプロジェクトが始まりました。
私がやりたかったことは以下6つです。
- フルスタックのJavaScriptアプリケーションを作りたい。自分のコンフォート・ゾーン(快適領域)を出て、別のサーバー技術を使いたい
- アプリをゼロから設計、デザイン、開発、デプロイ、メンテナンスする能力を磨きたい
- テスト駆動開発(Test Driven Development、TDD)と振る舞い駆動開発(Behavior Driven Development、BDD)を実践したい。TDDとは、開発者がテストを書いて失敗と修正を繰り返し、テストにパスできる最小限のコードをで洗練させていく(Red、Green、Refactorのサイクル)ソフト開発手法で、BDDは、機能とシナリオによる開発に重点を置く。ビジネス(クライアントの要求仕様)により近く、読んで理解できる記述にすることだ
- 話題の最新フレームワークを学びたい。仕事では残念ながらAngular 1.4とNode 0.10.32を使っているので、新技術に慣れたい
- 3Rの原則、Readability(可読性)、Refactorability(リファクタリングの容易性)、Reusability(再利用性)に沿ったコードにしたい
- 楽しみたい。これが一番大切。自己責任で楽しみながら多くのことを試してみたい
技術スタックの選択
ExpressでNode.jsサーバーを作り、Mongoデーターベースを使おうと考えていました。1つのリクエストで必要なデータがすべて取得できるよう、各ビューはドキュメントに反映させます。一番の悩みどころはフロントエンドで、AngularとReactで迷いました。
テスト容易性、デバッグのしやすさ、スケーラビリティ(拡張性)が重要なので、フレームワーク選びは慎重です。フレームワークの拡張性は実際に経験しなければ分かりません。
概念実証(Proof-Of-Concept、POC)から始めました。1つはAngular 2、もう1つはReactです。目的はアプリ制作なので、ライブラリーでもフレームワークでも問題ありません。重要なのは、なにができるかです。悩んだ結果Reactに決めました。
開発スタート
アプリ用のフォルダー「TrelloClone」を作り、Expressアプリを入れるためのサーバーフォルダーを作ります。ReactアプリにはCreate React Appを使用します。
複数アプリ間で混乱しないように、クライアント側とサーバー側に以下の構造を採用します。役割別にフォルダーを用意することで、探しているものが素早く見つかります。
- src:アプリを動かすコード
- src/config:データベース、URL、アプリケーションなどの設定(コンフィギュレーション)にかかわるすべて
- src/utils:特定のタスクの実行に使うユーティリティモジュール。たとえばミドルウェア
- test:テスト時に必要な設定
- src/static:画像などの静的リソース
- index.js:アプリのエントリーポイント
クライアント側のセットアップ
多くの設定が自動化できるcreate-react-appを使います。レポジトリでは「すべては裏で事前に設定されるので、コードだけに集中できる」とあります。
アプリの構成です。
- 各ビュー/コンポーネントはフォルダー名に表されている
- omponentsに、各ビューを構成するリソースが入っている
- Reutesでは、彼/彼女がビューに表示されている際の、異なるルート選択肢を定義する
- Modules(ducks構造)にはビューとコンポーネントの機能部分が入っている
サーバー側のセットアップ
アプリの構成方法です。ドメインごとにフォルダーを設けます。
- HTTPリクエストにもとづいたルート
- リクエストパラメーターを検査するバリデーション用ミドルウェア
- リクエストを受け取り、最後に結果を返すコントローラー
複数のビジネスロジックがあるなら、サービスファイルを加えるでしょう。予言をしているわけではなく、アプリの拡大に適応するのです。
依存オブジェクトの選択
依存オブジェクトの選択に考えることはただ1つ、「なにを得るのか」です。たいした効果が得られないならやめます。POC(概念実証)から始めれば「早いうちに失敗する」ので、より安全になります。
アジャイル開発経験者なら手順を知った上で嫌っているかもしれません。しかし、早く失敗し修正することで、期待通りに動くものが早く出来上がります。成功に至るまでの失敗と修正のサイクルなのです。
クライアント側
Reactアプリでいつもインストールしている依存オブジェクトのリストです。
パッケージ | 説明 |
---|---|
redux | 状態管理のための枠組み |
react-redux | ReactとReduxをバインドする |
redux-thunk | 関数を返すアクションを書くためのミドルウェア |
redux-logger | Redux用のロガーライブラリー |
react-router | ルーティングライブラリー |
lodash | ユーティリティライブラリー |
chai(開発時のみ) | BDD/TDDに基づく、Node用の成否判定(アサーション)ライブラリー |
sinon(開発時のみ) | 単体テスト用のスパイ、スタブ、モック |
enzyme(開発時のみ) | React用のテストユーティリティ |
nock(開発時のみ) | Node.js用のHTTPモックと期待値(expectation)のライブラリー |
redux-mock-store(開発時のみ) | Reduxの非同期アクションとミドルウェアのテストに使える、ストアのモック |
reduxは必須ではないと思う人もいるでしょう。しかし良いアプリは成長し拡大します。reduxの採用で得られるツール群は開発エクスペリエンスを変えます。
サーバー側
Expressアプリでいつもインストールしている依存オブジェクトのリストです。
パッケージ | 説明 |
---|---|
lodash | |
joi | オブジェクトスキーマ定義言語かつ、JavaScriptオブジェクトの文法検査(バリデーション)ツール |
express-valiation | リクエストの本文、パラメータ、クエリ、ヘッダー、クッキーの文法検査(バリデーション)用ミドルウェア |
boom | HTTPフレンドリーなエラーオブジェクト |
cookie-parser | クッキーの取得とパース、req.cookies(クッキー参照)の使用 |
winston | 非同期処理のロギングライブラリー |
mocha(開発時のみ) | Node.jsおよびブラウザーのテスト用フレームワーク |
chai(開発時のみ) | |
chai-http(開発時のみ) | HTTPレスポンスの成否判定(アサーション) |
sinon(開発時のみ) | |
nodemon(開発時のみ) | 監視およびアプリの自動再起動 |
istanbul(開発時のみ) | コード網羅率(カバレッジ)算出 |
アプリのビルド
開発したい画面を選び、ユーザーが使える機能を洗い出します。1つを選び、実装します。画面と機能ができたら、時間を取って追加のコードや、必要ならばリファクタリングを検討します。
実例:ホーム画面
すべての画面と機能を開発するにあたり、フロントエンドとバックエンドの2つを分けて考えます。フロントエンドから始めることで、表示する内容を把握できます。サーバーのデータベースモデルを実装し、ビジネスロジックを加えるのは容易です。
機能の説明と多数のシナリオを書きます。サンプルです。
機能:ホーム画面では、ヘッダー上に自分の名前および自分のボードの一覧が表示される
シナリオ:ヘッダーに自分の名前が表示されているのを見つける
いまいる場所がホーム画面なら、ユーザー名がヘッダーに表示される
基本のシナリオを想定し、ホーム画面をどう作るか解説します。
クライアント側
コンポーネント駆動開発(Component-Driven Development、CDD)の方法論とBDDを組み合わせるなら、ビューは小さなコンポーネントに分解し、それぞれが互いに依存せず切り離されていて再利用可能な状態にします。
プレーンテキストで書かれた模擬データを使い静的なページを作り、CSSでスタイルを付けます。
サンプルです。
- コンポーネントが正しく描画されているか
- プロパティは正しく処理されるか
- イベントリスナー(もしあれば)は正しく起動し、適切なメソッドを呼ぶか
- コンポーネントがストアから状態を受け取るか
Header Userコンポーネントとコンテナを作り、Reduxモジュール初期状態で模擬テストをしたデータを見直します。
ducks構造を採用しているため、一度に取り組むのは1つのビューだけです。2つのビューが同じデータを共用するなら、ステートを変更して上位モジュールを作り、データを持たせます。アプリの最終的なReduxステートは模擬で用意したデータをすべて持っています。
用意したシナリオをすべてパスしたら、コンポーネントを見直し、作成済みの別のコンポーネントに似ているなら、リファクタリングします。新しく作る前に古いコンポーネントを修正したり、新しく作り試してみて、より良い方法を考えたりします。
ユーザーから見た画面に必要な要素を考えて時間を浪費しません。ビューを作り、表示すべきデータを決めます。表示する要素を事前に検討するのはオススメしません。開発中ならデザインを見直したり検討したりしやすいのです。再利用性のことを常に意識しながら、作り始めても問題ありません。
サーバー側
データベース設計とビジネスロジックに反映されるため、Reduxのストア構成の検討は重要です。ビューの作成が終わっているので、ユーザー名とボード一覧を取得します。個人のボードと所属組織のボードがあるなら、2つを分離して別々のスキーマを適用します。本来のゴールは、データが正規化され、重たい処理はサーバー側で済ませて、こちらで考える必要はない状態です。
CRUD:新規作成(Create)、読み出し(Read)、更新(Update)、削除(Delete)はどのアプリでも必要な基本操作ですが、すべてのルートに加えるのではなく、データの取得が必要なので、読み出す機能だけを実装します。Mongoクエリを書き、あとからユーザーをデータベースに追加できるようにします。
最後に
フルスタックアプリの開発方法を紹介しました。楽しんでもらえたなら幸いです。主なアドバイスは、大掛かりなリファクタリングを恐れないことです。そのままでは拡充できないためにアプリのファイル構造を変更した経験は数えきれないほどあります。フォルダー名や階層構造、機能別のグループ分け方法など細かな配慮が違いを生みます。
失敗を恐れません。失敗から学ぶことがあります。早く失敗すればより早く学べて、より早く成長します。100回ミスをしてもそこから学べば、100通りのエラー回避方法を学んだことになります。
好ましくないものを見つけたら、すぐ、もしくは数日内に修正します。コードをテストしているので、変更によって正常に動いていた機能に問題がないかすぐに確認できます。
アインシュタインは「学ぶことをやめた人は、死につつあるのも同然だ(once you stop learning you start dying)」と言いましたが、失敗することをやめた人は学ぶことをやめたも同然だと思います。失敗して学んで、成長しましょう。
この先の計画
常に進行中の案件があるため、プロジェクトに対する取り組みを続けています。本物のアプリと同様、常に変化しています。私の計画は、
- 一枚岩(モノリシック)だったプロジェクトから、mono repoへ移行しマイクロサービスのサーバーを活用する。HipChatクローンの開発中に思いついた。ユーザー認証のロジックで同じようなコードを大量に書いていたので、マイクロサービスはうってつけの選択だ。プログラミングにおいてCtrl-CとCtrl-Vは味方ではない
- マイクロサービスをKubernetesにデプロイする
- HipChatクローンをmono repoに移管し、Vue.jsでアプリ開発する
- ElectronとReact Nativeについて詳しく調べる
- Travisを使用した継続的インテグレーション(CI)とデプロイを実行する
- TypeScriptについて学ぶ
新技術についていく方法
自分に厳しいルーティンを課します。
- 月曜から木曜:HackerRankとGeeksforGeeksでのアルゴリズム学習、週末の仕事のためのドキュメント作成、新しい言語の学習、技術関係書籍の閲覧、ポッドキャストの視聴
- 金曜から日曜まで:自分のアプリの新機能および/もしくはバグ修正
すべてをつぎ込むのではなく、平日は1、2時間だけでも十分価値があります。週末は縛りをかけず、時間が許す限りプロジェクトに打ち込みます。コーディングしたり、ツールを試用したり、ドキュメント類を読んだりでいます。
プログラミングは芸術であり職人芸です。性能と美しさを保ちながら最小限の記述できちんと動作するコードを書くことに誇りを感じます。
(原文:How I Designed & Built a Fullstack JavaScript Trello Clone)
[翻訳:西尾 健史/編集:Livit]