アプリケーションアーキテクチャを設計・実装する5つのステップ
「なんだこのクソコードは?誰だよ?アーキテクチャ設計したやつ」と感じたことは誰しも一度や二度はあるかと思います。
アプリケーションをどのようなアーキテクチャで作るかによって、生産性や性能が大きく変わります。
既存アプリのメンテナンスをする際にアプリケーションアーキテクチャが悪いせいで、不必要に複雑になっているシステムをよく目にします。
アーキテクチャには、いつでも通用する正解はありません。システムの特性に合わせて、その都度、適切な設計をする必要があります。
そこで、今回はアプリケーションアーキテクチャを設計・実装する5つのステップを紹介します。
Contents
1. 企画書・仕様書を読んでシステムの特徴を捉える
まず初めにプロジェクトの企画書・仕様書を読んでシステムの特徴をとらえます。
ハードウェアやミドルウェアの特徴
ハードウェアやミドルウェアの構成はどうなっているか?
通信の発生する箇所、通信料、データを保存、読み出しする箇所、DBの構成などを見ます。
通信の回数が多いアプリなのか、保存するデータ量が多い少ないなどによって最適なアーキテクチャは変わります。
画面の特徴
画面については、
- UIはどんなインターフェースなのか?
- テキストボックス中心のベーシックなUIか?
- グラフィカルなUIなのか?
- 画面項目が多いか少ないか?
- 画面遷移が深いのか浅いのか?
- 遷移が集中する画面はどれか?
を見ます。
バックエンドロジックの特徴
バックエンドロジックについては、
- 複雑な処理はあるか?
- 計算量が多い個所はあるか?
- メモリーの使用量が多くなりそうな箇所はあるか?
- IOが多いか?
を見ます。
開発を困難にする箇所を見つける
上記の項目を確認しながら、
- このシステムの難しさはどこだろうか?
- 共通化して難しさを隠ぺいできないか?
を頭の中でぼんやりと考えてみて、浮かんできたことをラフにメモします。
2. パターン分類する
上記のように企画書や仕様書で全体を眺めると、パターンが見えてきます。
画面遷移のパターン
「画面遷移はこういうパターンが多いな」などの特徴を見出します。
よくあるパターンは
- 一覧画面
- → 詳細表示画面
- → 更新画面
- → 確認画面
- → 完了画面
みたいな流れです。
画面遷移をパターン分類できたら、それぞれのパターン毎に共通した処理はないかを考えます。
パターンから外れた画面を見つけた場合、その遷移の仕方が妥当なのかを考えます。妥当なら、その遷移の仕方は特別なものとして個別に実装します。妥当でない場合は、パターンに沿った遷移に修正します。
画面遷移の仕方が統一されていた方が開発の生産性があがるのはもちろん、ユーザーにとってもわかりやすい操作性となるので一石二鳥です。
バックエンドロジックのバターン
処理もパターン分類します。
それぞれ個別のロジックがあるわけですが、それらの流れを大きくとらえて処理の流れをパターンとして抽出します。
- 入力チェックしてDB更新して画面表示するパターン
- 入力チェックして、DBアクセス後、外部システムにリモートコールをするパターン
のような大まかな処理のパターンを見つけます。処理の流れのパターンを見つけられると、TemplateMethodパターンやAOPを使って処理の流れを共通化することが出来ます。これについては後ろで説明します。
3. ライブラリを実装する(共通ロジックを見つける)
既存のライブラリを探す
既存のライブラリで使えそうなものがあれば使います。
既に存在する機能を自前で実装するのは開発コストの無駄遣いです。
- サードパーティ製のもの
- 他プロジェクトで作られたもの
から適したものを探します。
選んだライブラリをさらに使いやすくするために、ラップクラスを作るのも良いでしょう。ラップしておくとライブラリに変更を加える時にそれを使ってるプログラムに影響を与えずに済む場合があるというメリットもあります。
共通化すればする程、生産性が上がる
どんなアプリでも探せば共通化できる箇所を見つけられるはずです。
共通化出来れば出来るほど、開発コストは下がります。
書かなければならないコード量も減りますし、バグや仕様変更の場合にも共通部分を修正するだけで対処できる場合もあります。
難しいロジックを隠ぺい
ロジック的に、難しい箇所をライブラリ化して隠ぺい出来ると生産性と品質が上がるので、難しい処理ほど、共通化できないかを重点的に考えます。
4. フレームワークを実装する(共通した処理の流れを見つける)
既存のフレームワークを探す
フレームワークもライブラリ同様、全てを自前で実装するとは限りません。
MVCフレームワークとかテンプレートエンジンはサードパーティが提供しているものを使った方が、開発の手間も減りますし、既に多数のプロジェクトで使用された実績があるので、信頼性も担保できます。
処理の流れを共通化
例えば、DBアクセスする処理には少なくとも2つのパターンが存在します。トランザクションがあるかないかです。
トランザクションがある流れとない流れをTemplate Methodパターンを使って実装することが出来ます。以下はそのJavaコードです。
// トランザクションがある流れを実装した共通基底クラス public abstract class TransactionController { // トランザクションがある流れを実装したTemplate Method(処理のひな型となる流れを実装しているからTemplate Methodと言う) final public void execute(HttpServletRequest req) { Connection con = ConnectionFactory.getConnection(); try { doDBAccess(con); con.commit(); } catch (Exception e) { con.rollback(); } finally { con.close; } } // 子クラスで個別のロジックを実装するための抽象メソッド protected abstract void doDBAccess(HttpServletRequest req, Connection con); } //トランザクションがある流れの個別クラス(ユーザー登録処理を実行する処理) public class UserRegisterController extends TransactionController{ // ロジックのみを実装すれば、トランザクション、コネクションの取得、解放は親クラスがやってくれる protected void doDBAccess(HttpServletRequest req, Connection con) { PreparedStatement stmt = con.PreparedStatement("INSERT INTO users(id, name, email) VALUES(?,?,?)"); stmt.setString(1, req.getParameter("id")); stmt.setString(2, req.getParameter("name")); stmt.setString(3, req.getParameter("email")); stmt.executeUpdate(); } } // トランザクションがない流れを実装した共通基底クラス public abstract class NonTransactionController { // トランザクションがない流れを実装したTemplate Method(処理のひな型となる流れを実装しているからTemplate Methodと言う) final public void execute(HttpServletRequest req) { Connection con = ConnectionFactory.getConnection(); try { doDBAccess(con); } finally { con.commit(); con.close; } } // 子クラスで個別のロジックを実装するための抽象メソッド protected abstract void doDBAccess(HttpServletRequest req, Connection con); } //トランザクションがない流れの個別クラス(ユーザー情報表示を実行する処理) public class UserDetailController extends NonTransactionController{ // ロジックのみを実装すれば、コネクションの取得、解放は親クラスがやってくれる protected void doDBAccess(HttpServletRequest req, Connection con) { PreparedStatement stmt = con.PreparedStatement("SELECT id, name, email FROM users WHERE id=?"); stmt.setString(1, req.getParameter("id")); ResultSet rs = stmt.executeQuery(); if (rs.next) { String name = rs.getString("name"); String email = rs.getString("email"); } } }
トランザクションが必要な処理はTransactionControllerを継承したクラスを作成、不要の場合はNonTransactionControllerを継承したクラスを作成すればいいというわけです。NonTransactionControllerではrollbackの処理が不要なので覗いてあります。まぁ、実際はSELECT文だけの処理でもエラー時にrollbackしても問題ないので、TransactionControllerだけあれば事足りるのですが、例として書いてみました。
こういう風に処理の流れを親クラスに抽出することでフレームワークを作れるんです。
これがわかるとライブラリとフレームワークの違いがわかります。
ライブラリとフレームワークの違い
ライブラリは、共通ロジックを切り出して、個別処理からコールする。
フレームワークはその逆で共通ロジックを抽出して、個別処理を抽象メソッドとして切り出して個別クラスが抽象メソッドを実装し、親クラスからコールされます。これを制御の反転と言います。
- 個別処理 → ライブラリ
- フレームワーク → 個別処理
どっちから呼び出すかが逆なんです。
5. 実装したアプリケーションアーキテクチャをプロジェクトへ浸透させる
ライブラリ・フレームワークは正しく使ってもらってこそ成果が出る
ライブラリ・フレームワークを作るだけでは意味がありません。正しく使ってもらう必要がありますし、出来るだけかんたんに使えることが重要です。
使い方が難しすぎて導入に時間がかかるようでは生産性にマイナスです。なので、使うコストを下げる工夫をします。
アーキテクチャ説明書を作成
- アーキテクチャの設計思想や概要の説明
- どういう機能を持っていて、どういう風につかってほしいか
等を書きます。簡単に読めるように文章はコンパクトに図を入れて分かりやすい表現を心がけます。
サンプルコードを作成
アーキテクチャ説明書だけでなく、実際に動くサンプルも作ります。
ドキュメントを読むだけよりもサンプルコードを実際に動かした方が早く簡単に理解が深まります。
サンプルを流用して開発することも出来て、コードを書く手間が省けるのでとても有効です。
設定ファイル・ソースコード生成ツールを作成する
設定ファイルやクラスの数が多い場合は設定ファイル・ソースコード生成ツールを作成して、それらのファイル作成を自動化します。
自動化するメリットは2つあります。
- ファイルを作成する時間・労力が削減できる
- 手動による人為的なミスがなくなる
アーキテクチャ設計はおもしろい!
アプリケーションアーキテクチャの設計・実装はいろいろ考えることがあり、工夫のしがいがあります。アーキテクチャの出来不出来によって、プロジェクトの生産性が大きく変わるので、結果も見えやすく、大変面白い仕事です。汎用性の高いスキルなので、一度覚えれば複数のプロジェクトで応用が利きます。
ですから、アーキテクチャを設計するチャンスがあったら積極的に名乗り出ましょう!
「と言っても、うちの会社じゃそんなチャンスないなぁ…」という方もいるかもしれません。
であれば、転職を考えてみるのも良いと思います。私の場合、転職することで、技術力の幅が広がり、年収も上げることが出来ました。それに会社によって、自分に合う合わないがあることもわかりました。
転職活動すると、いろいろな会社や仕事があることを知れて、人生の可能性も広がると思います!