『継続的デリバリー』 読書会
     第13章
コンポーネントや依存関係を管理す
       る
    大崎的デリバリー
       @favril
13.1 導入(1/2)
• 本章で説明する内容
 – コンテキストが切り替わるときにもアプリ
   ケーションを常にリリース可能に保つための
   方法
 – 巨大なアプリケーションのコンポーネント化
  • コンポーネント分割の方法
  • 複数コンポーネントからなる巨大プロジェクトの
    ビルド方法
  • その管理方法
13.1 導入(2/2)
• 言葉の定義
 – コンポーネント
  •   それなりに大規模なコード
  •   よく作られたAPIを提供
  •   別の実装にも切り替えられる
  •   モジュールとも呼ぶ
  •   Windows: DLL, UNIX: SOファイル, Java: JARファイル..
 – コンポーネントベースのシステム
  • いくつかの部品に分解可能
  • それぞれのふるまいが定義されている
  • 他のコンポーネントと最小限のやりとり
13.2 アプリケーションをリリース
      可能な状態に保つ(1/3)
• 通常、下記の手法で開発を行うとリリー
  ス間隔が長くなりがち
                      リリー
trunk                 ス
        開発   bugfix               開発

                      release 1

• 「常にリリース可能な状態」を保ててい
  ない
13.2 アプリケーションをリリース
      可能な状態に保つ(2/3)
• バージョン管理システムのブランチを使うこと
  で、メインラインを常にリリース可能な状態に保
  つことが可能
               リリー
 trunk         ス
                     マー
                     ジ
 feature 1                 feature 2
             開発 / bugfix               開発

• しかし、これはあくまで次善の策
 –  ブランチ上での作業が継続的に統合されていない
13.2 アプリケーションをリリース
      可能な状態に保つ(3/3)
• 全員がメインライン上で作業する方式を
  推奨
• ただし、常にリリース可能状態は保つ
 – そのための4つの作戦
  • 新機能は完成するまで隠す
  • すべての変更を細かくインクリメンタルに行い、
    個々の変更をすべてリリース可能な状態にする
  • 抽象化によるブランチを使って、大規模な変更を
    コードベースに加える
  • 変更の頻度が異なる部分をコンポーネント化し
    て、アプリケーションから切り離す
13.2.1 新機能は完成するまで隠せ
           (1/2)
• 開発中の機能を本番に反映しつつ、それ
  を利用できないようにする
 – 開発中の機能に個別のURIを与え、機能のリ
   リースまではWebサーバの設定でアクセス拒
   否
 – 古い機能と、開発中の新しい機能を設定で切
   り替えれるように作る
13.2.1 新機能は完成するまで隠せ
           (2/2)
• 未完成の機能もメインラインで開発するメ
  リット
 – システム全体の統合やテストをありのままの状態
   でいつでも実行できる
 – 依存関係の整理や統合のフェーズをプロジェクト
   の計画に含める必要がなくなる

• この方法での開発は
 – 準備や検討などそれなりに大変
 – 新機能を開発途中でもリリースできるという利点
   を考慮すると、その価値はある
 – ブランチ開発より優れている
13.2.2 すべての変更はインクリメン
          タルに(1/2)
• 大規模な変更を加えるとき、以下をやりがち
 – ブランチを作り
 – アプリが動かなくなるくらいがっつり変更を加え
 – その後、正しく動くように細かい部分を含めて修
   正する(メインラインにマージする)

 – 上記手法は、確かに開発を高速化するかもしれな
   い
 – が、すべて正しく修正するのは大変で難しい
 – したがって、変更量が大規模であればあるほど、
   実際はブランチを作るべきではない
13.2.2 すべての変更はインクリメン
          タルに(2/2)
• 大規模な変更を細かく分割して、小さな
  変更をインクリメンタルに進める
 – 要件を小さなタスクに分解していく過程と類
   似
 – 分析が重要
 – 間違いが尐なくなる
 – 作業をどう進めるか(本当に進めるべきか)
   を判断できる
 – いつでもやめれる(?)
13.2.3 抽象化によるブランチ
           (1/3)
• 大規模な変更を加えたいが、小さくてインク
  リメンタルな段階に分解できない場合
 – 1. 既存システムにおいて、変更したい部分を抽象
   化
 – 2. 既存システムをリファクタリングし、その抽象
   化レイヤを使うようにする
 – 3. 新しい実装をする(ただし、完成までは実行パ
   スに含まれないように)
 – 4. 抽象化レイヤを書き換え、新しい実装に処理を
   委譲
 – 5. 古い実装を削除
 – 6. 不要なら抽象化レイヤも削除
13.2.3 抽象化によるブランチ
           (2/3)
• この方法で困難な2点
 – 対象のコードベースから、エントリポイント
   を分離すること
 – 開発中の機能に対して施すべき変更を管理す
   ること
13.2.3 抽象化によるブランチ
           (3/3)
• アプリに大規模な変更を加えるとき、包
  括的な自動受け入れテストスイートの存
  在が重要
 – ユニットテストやコンポーネントテストは、
   この目的で使うには粒度が細か過ぎる
 – 業務的な機能が壊れないようにするための助
   けにならない
13.3 依存関係(1/2)
• ソフトウェアのある部分をビルドあるい
  は実行するために別の何かが必要
 – 大半のアプリは何らかの依存関係がある
 – 例えば、動作環境
  •   Java  JVM
  •   .NET  CLR
  •   Rails  Ruby, Rails
  •   C  C言語標準ライブラリ
13.3 依存関係(2/2)
• 言葉の定義
 – コンポーネントとライブラリ
   • ライブラリ
     – ソフトウェアパッケージの中で、自分たちで制御しようのないも
       の(どれを使うかの選択しかできない)
   • コンポーネント
     – ソフトウェアの部品としてアプリが依存しているもの
     – 自分達が開発する

 – ビルド時の依存関係と実行時の依存関係
   • ビルド時の依存関係
     – アプリをコンパイルしたときにあらわれるもの
   • 実行時の依存関係
     – アプリを実行して機能を使うときにあらわれる
13.3.1 依存地獄(1/3)
• 別名:DLL地獄
 – 実行時に発生するライブラリの依存関係
 – あるアプリケーションが、特定のバージョン
   の何かに依存しているのに、別のバージョン
   をデプロイしてしまった場合、あるいはそも
   そもデプロイしなかった場合に起こる
   • 例: 初期windowsは異なるバージョンのDLLを使うア
     プリの共存が不可能  .NET, GACで改善
   • 例: Linuxは命名規約で回避
13.3.1 依存地獄(2/3)
• OS全体の依存関係
 – 静的にコンパイルしたまともなアプリを使う
 – コンパイル時に依存関係を1つのアセンブリ
   にまとめれば実行時の依存関係はほぼなくな
   る
 – ただし、バイナリが肥大化するし、特定のOS
   バージョンに密結合するので、おすすめしな
   い
13.3.1 依存地獄(3/3)
• 動的言語の場合
 – 依存するフレームワークやライブラリも、ア
   プリと一緒に出荷することで依存関係を解決
   する
   • 例: Rails
• Javaの場合
 – あるクラスの複数のバージョンを同じJVM内
   で使えないという深刻な問題がある
 – OSGiフレームワーク形式を使って解決する
           JAR1 ver1
      アプリ                     JAR3

                JAR2   ver2
13.3.2 ライブラリを管理する
           (1/3)
• ビルドが再現性のあるものであることが
  重要
• 適切な方法が2つ
 – ライブラリもバージョン管理にチェックイン
   する
  • 最もシンプル
  • 小規模なプロジェクトならこれで十分
 – 使うライブラリを宣言した上でMaven, Ivyを使
   い、組織内のリポジトリからライブラリをダ
   ウンロードして使う
13.3.2 ライブラリを管理する
           (2/3)
• ライブラリチェックインの問題点
 – リポジトリの肥大化
 – 同一プラットフォーム上で、別のプロジェク
   トを共存させないといけなくなった場合

 – プロジェクト間の依存関係を手動で管理する
   のは厳しい
13.3.2 ライブラリを管理する
           (3/3)
• 依存関係の管理を自動化する仕組みを提供す
  るツール(Maven, Ivy)を使う
 – 必要なライブラリとそのバージョンを、プロジェ
   クトに設定できる
 – 他プロジェクトとの依存関係をよろしく解決して
   くれる

• 自前の成果物リポジトリを持つ
 – Artifactory, Nexusなど
 – 自社内のプロジェクトで、どのライブラリのどの
   バージョンが使えるか制御できる
13.4 コンポーネント
• なぜコードベースを複数のコンポーネン
  トに分割しないといけないのか?

• コンポーネント間の関係はどうやって管
  理すべきか?
13.4.1 コードベースをコンポーネン
       トに分割する方法(1/6)
• コンポーネントに分割することが、開発
  プロセスを効率化する理由
 – 1. 大きな問題を、より小さくわかりやすい問
   題に分割できる
 – 2. コンポーネントが、システム中の更新頻度
   が違う部分、ライフサイクルが違う部分をあ
   らわす
 – 3. ソフトウェアの設計や保守の段階で、その
   責務を明確化することにつながる
 – 4. ビルドプロセスやデプロイメントプロセス
   を最適化する際に、自由をくれる
13.4.1 コードベースをコンポーネン
       トに分割する方法(2/6)
• 大半のコンポーネントの特徴
 – 何らかの形式でAPIを公開していること
  • APIは、外部の協調オブジェクトとの情報交換を表
    している
  • また、結合度合いも表している


 – コンポーネント間の依存は考慮しないといけ
   ない
  • 結合が強いと、別々に分割して、個別にビルドや
    デプロイメントをするのが難しくなる
13.4.1 コードベースをコンポーネン
       トに分割する方法(3/6)
• コンポーネント化すべき場面
 – 1. コードベースの一部を個別にデプロイする必要がある
 – 2. コードベースをコアとプラグイン群に分割し、一部を別
   の実装に切り替えたり、ユーザ側での拡張性を高めたりし
   たい
 – 3. そのコンポーネントが別のシステムへのインタフェース
   を提供する
 – 4. コードのコンパイルとリンクに時間がかかる
 – 5. 開発ツールでプロジェクトを開くのに時間がかかる
 – 6. コードベースが大きくなりすぎて、1つのチームで手に
   負えない

 – 後半3点は著者の主観だが、すべて理由としては妥当だし、
   最後の項目は最も重要
13.4.1 コードベースをコンポーネン
       トに分割する方法(4/6)
• チームの力を最大限に発揮できるのは
 – 10人前後のメンバーで構成
 – 各自がコードベースの特定の部分を隅々まで
   知り尽くしている
  • 特定の部分:機能的な区切りでも、それ以外の何
    かの基準による区切でもよい
13.4.1 コードベースをコンポーネン
       トに分割する方法(5/6)
• 10人以上必要な場合
 – システムを疎結合なコンポーネントに分割し、
   チームも分割する
 – ただし、コンポーネント毎のチーム分けは勧めな
   い
  • 要件の分割はコンポーネントの境界と沿わない
  • コミュニケーションコストの浪費
  • プロジェクト全体で何がベストか見えなくなりがち
 – 各チームが1つのストーリーの流れを扱うのがよ
   い
  • 必要なコンポーネントはすべて手をいれられる権限を
    与える
  • 統合チームに責任を押し付けることがなくなる
13.4.1 コードベースをコンポーネン
       トに分割する方法(6/6)
• コンポーネント分割するにあたっての、強力
  でお手軽なツールは無い
 – 説明してきたより良い設計を考えるだけ
 – その際の罠
  • 何もかもコンポーネント化してしまうこと
  • 1つのコンポーネントにすべてを任せること

• コンウェイの法則
 – システムを設計する組織は、その組織のコミュニ
   ケーション構造をそっくりまねた設計を生み出す
  • チーム編成には注意
コンポーネントを使うからといって必
ずしもN層アーキテクチャになるわけ
       ではない
• N層アーキテクチャとは
 – アプリケーションを表示、業務処理、データ等、複数の階層で
   分割する分散アプリケーション設計手法
• メリット
 – 経験の浅い開発者が集まった巨大なチームでも密結合な泥団子
   を作らずにすむ
 – キャパシティやスケーラビリティの面で有利
• デメリット
 – 階層が物理的に離れている場合、リクエストを処理する待ち時
   間が増える
   • 複雑なキャッシュの仕組みの導入につながる
   • 保守やデバッグがしづらい
   • コードは理解しにくく、各層間の依存関係のせいで変更もしにくい
• N層化はコンポーネントベースの開発と同義ではなく、直交
  する概念
13.4.2 コンポーネントをパイプライ
         ン化する(1/4)
• 基本的には、何か変更がコミットされる
  たびに、すべてをビルドし、テストする
  のが良い
 – 時間がかかりすぎるようになったら、そのと
   き対応する
 – 巨大で複雑なシステムでも対応可能
13.4.2 コンポーネントをパイプライ
         ン化する(2/4)
• パイプラインに分割したほうがやりやすい場面
 – アプリケーションの一部が、他の部分とは異なるラ
   イフサイクルをたどる
 – アプリケーション内で機能的に分かれた部分をそれ
   ぞれ別チームが担当しており、個々のチームで専用
   のコンポーネントを使っている
 – あるコンポーネントが他とは異なる技術やビルドプ
   ロセスを使っている
 – 共有コンポーネントが他のプロジェクトでも使われ
   ている
 – あるコンポーネントが比較的安定しいて、変更頻度
   が低い
 – ビルドに時間がかかり、コンポーネントごとに分け
   た方が速い
13.4.2 コンポーネントをパイプライ
         ン化する(3/4)
• 各コンポーネントあるいはコンポーネン
  ト群のビルドには独自のパイプラインが
  必要
• パイプラインの動き
 – 必要に応じてコードをコンパイルする
 – ひとつあるいは複数のバイナリを、あらゆる
   環境へのデプロイメントに対応できるよう組
   み立てる
 – ユニットテストを実行する
 – 受け入れテストを実行する
 – 手動テストをする場面ではそれに対応する
13.4.2 コンポーネントをパイプライ
         ン化する(4/4)
• 各バイナリが自身のリリースプロセスを
  通過すれば、次のインテグレーションビ
  ルドに進む準備ができたことになる
• DLLやJARの単位でパイプラインを作るべき、
  と主張しているわけではない
• 運用するビルド数はできるだけ尐ない方
  がよい
13.4.3 インテグレーションパイプラ
          イン(1/3)
• 適切なバイナリを組み合わせてデプロイ
  用のパッケージを作成
• できあがったアプリケーションを疑似本
  番環境にデプロイしてスモークテストを
  実行
• 受け入れテストを実行

• 例、図13-1 (p.427)
13.4.3 インテグレーションパイプラ
          イン(2/3)
• デプロイメントパイプラインに関する一
  般原則2つ
 – 素早いフィードバック
 – ビルドの状況を関係者全員が見れる
  • 失敗した場合、なぜ失敗したかが正確にわかる必
    要
   – インテグレーションビルド側から、元になった各コンポ
     ーネントのバージョンをたどれる必要
   – コンポーネント開発側からも、どのバージョンのコンポ
     ーネントがインテグレーションパイプラインで成功した
     か見れる必要
13.4.3 インテグレーションパイプラ
          イン(3/3)
• インテグレーションパイプラインの実行間隔
  が離れていて、その間に複数のコンポーネン
  トに多くの変更があるのは好ましくない状況
 – どの変更がアプリを壊したのかわかりにくい

• 最もシンプルな解決策
 – 各コンポーネントのうまく動いているバージョン
   について、考えうるすべての組み合わせをビルド
   する
  • 人手が必要ない&賢いアルゴリズムを考える必要もな
    い
• 次善の策
 – スケジューリングを使って、定期的にビルドする
13.5 依存グラフを管理する
• 依存関係は、ライブラリやコンポーネン
  トも含めてバージョン管理しておくこと
  が重要
 – コンポーネント間の依存関係を図示した際は、
   無閉路有向グラフでなければならない
 – そうならない場合、特に閉路グラフになる場
   合は、依存関係に病的な問題がある
13.5.1 依存グラフを作成する
            (1/4)
• 例、図13-2 (p429)
 上流                レポート
                               下流
                   エンジン


  フレームワー                    資産管理アプ
               決済エンジン
    ク                       リケーション


    CDS        プライシング
  ライブラリ         エンジン
 他社
                    それぞれパイプラインを持っていて、
                    自身あるいは上流に変更があったら、
                    パイプラインが起動
13.5.1 依存グラフを作成する
           (2/4)
• この例のビルド時に起こりうるシナリオ
 – 1. 資産管理アプリケーションを変更
  • 資産管理アプリケーションだけ再ビルド
 – 2. レポートエンジンを変更
  • レポートエンジンを再ビルドし、すべてのテストを通す
  • その後、資産管理アプリケーションを再ビルド
 – 3. CDSプライシングライブラリに変更
  • プライシングエンジンの再ビルド
  • その後、資産管理アプリケーションの再ビルド
 – 4. フレームワークに変更
  • フレームワークを再ビルド
  • 直近の下流にあるすべての依存コンポーネントを再ビルド
  • 上記がすべてできたら、資産管理アプリケーションを再ビル
    ド
    – 1つでも失敗していたら、資産管理アプリケーションは再ビルド
      しない
13.5.1 依存グラフを作成する
           (3/4)
• 続き
 – 5. フレームワークとプライシングエンジンに
   変更
   • グラフ全体の再ビルドが必要
   • 理想は、すべてのビルドが成功すること
   • もし、決済エンジンのビルドが失敗したら?
       – 資産管理アプリケーションを新しいフレームワークでは
         ビルドできない
       – しかし、CDSプライシングライブラリは動作検証済みな
         ので、これだけ新しいバージョンで、資産管理アプリ
         ケーションをビルドしたい
          » が、そんなバージョンのプライシングエンジンは存
            在しない
13.5.1 依存グラフを作成する
           (4/4)
• この例の最も重要な制約は、
 – 資産管理アプリケーションをビルドする際に
   使えるフレームワークのバージョンが1つだ
   けということ
  • これは典型的な「菱形依存」
13.5.2 依存グラフをパイプライン化
          する(1/2)
• 図13-3 (p432)
  – フィードバックを早くするために、各プロジェク
    トのパイプライン内でコミットステージが完了し
    た時点で、依存プロジェクトのパイプラインを立
    ち上げる(受け入れテストの完了を待たない)
  – すべてのトリガーは自動(手動テスト、本番デプ
    ロイを除く)
  – アプリケーションのあるバージョンをビルドする
    ときに使った各コンポーネントをたどれることが
    重要
  – どのバージョンのコンポーネントの組み合わせが
    統合に成功したかを知れる
• 図13-4, 図13-5 (p433)
13.5.2 依存グラフをパイプライン化
          する(2/2)
• 大幅な変更をコンポーネントに加える必要がある場合
  は新しいリリースを作る
 – 図13-6 (p434)
 – APIの後方互換性がない新バージョンを開発する際、開発
   をはじめる前に1.0系のリリース用ブランチを作ってから
   開発する
 – 下流の資産管理アプリケーションは、1.0ブランチから
   作ったバイナリを使える
 – Bugfixは1.0で行い、trunkにマージする
 – 資産管理アプリケーションが新バージョンに対応した時点
   で、trunkに切り替える

 – 継続的インテグレーションの観点では次善の策
 – だが、コンポーネントが疎結合であることが、統合を先延
   ばしにしたときのリスクを抑えている
13.5.3 いつビルドすべきか?
           (1/2)
• 上流の依存を最新版に保ち、最新の機能
  とバグフィックスを使いたいが、すべて
  の依存を最新にする統合にはコストがか
  かる
 – 動かなくなった部分を修正しないといけない
   ので
13.5.3 いつビルドすべきか?
           (2/2)
• 依存関係更新の頻度を決める際の検討事項
 – 依存コンポーネントの最新版をどの程度信頼できるか?
   • 数が尐なく、自分たちで開発しているなら、修正も素早く行え
     るので、頻繁に統合すべき
   • 社内の別のチームの場合、個別のパイプラインにすべき。追従
     するか、しないかを都度判断できる
   • サードパーティのライブラリは、明らかな必要性があるときの
     みにすべき
 – コンポーネントの変更にどの程度関与でき、どの程度知れ
   るかがポイント
   • 程度が低いほど、信頼性は下がる
 – 基本的には、依存コンポーネントの新バージョンの統合を
   継続的に行えるのがベスト
   • コストがかかる
   • 素早いフィードバックを得るには、統合を頻繁に行う必要があ
     る
   • が、頻繁に行うと自分たちの関与しないところで問題が発生し
     うる
   • 両者のバランスをとるための解決策の1つとして「慎重な楽天
13.5.4 慎重な楽天主義
• 依存グラフに、3つの状態を導入
 – static, gruarded, fluid
 – staticな上流の依存を変更しても新しいビルドは
   引き起こさない
 – fluidな上流の依存が変更されたら常にビルドを実
   行する
   • ビルドが失敗すると、依存コンポーネントは
     「guarded」になり、動作確認済みの既知のバージョン
     に固定される
      – guardedな依存はstaticと同じふるまいをする
      – 開発チームには通知が飛ぶ
 – 例、図13-7 (p437)
13.5.5 循環依存
• 最もやっかいな問題
 – 依存グラフに閉路が含まれる場合に起こる
 – 一番シンプルな例
  • コンポーネントAはコンポーネントBに依存してい
    るが、
  • コンポーネントBもコンポーネントAに依存してい
    る
  • 最初の起動時に致命的な問題

  • プロジェクトの開始時点では循環していない
  • おすすめしないが解決策は図13-8 (p438)
13.6 バイナリを管理する
• 成果物リポジトリを使うときの一般的な
  法則
• ファイルシステムだけを使ってバイナリ
  管理する方法
• Mavenを使って依存関係を管理する方法
13.6.1 成果物リポジトリの活用法
           (1/3)
• 成果物リポジトリに再生成できないもの
  を含めてはいけない
 – 気兼ねなく削除でき、それによって取り戻せ
   ない何かがあってはいけない
 – あらゆるバイナリを再生成するために必要な
   すべてをバージョン管理する
13.6.1 成果物リポジトリの活用法
           (2/3)
• バイナリをどの程度保持し続けるにし
  ろ、ハッシュ(バイナリのMD5)も保持して
  おく必要がある
 – それによりバージョン管理システム内のどの
   リビジョンを使って作成したバイナリかわか
   る
 – 保存は、ビルドシステムでもよいし、バー
   ジョン管理システムでもよい
  • ハッシュ管理は構成管理の一貫として重要な位置
    を占める
13.6.1 成果物リポジトリの活用法
           (3/3)
• 最もシンプルな成果物リポジトリ
 – ディスク上のディレクトリ構造として構成し
   たもの
  • バイナリと、それを作るソースのバージョンを関
    連づけられる必要がある
  • パイプラインごとにディレクトリを作り、その中
    でビルド番号ごとのディレクトリを作る
  • ビルドの成果物はすべてこのディレクトリに保管
    する
13.6.2 デプロイメントパイプライン
  と成果物リポジトリのやりとり
           (1/2)
• デプロイメントパイプラインで以下を実装
 – ビルドの成果物を成果物リポジトリに保存
 – 後で使うときに、それを取り出す
• コンパイル、ユニットテスト、受け入れテスト、
  手動受け入れテスト、本番 というパイプライン
  を例とする
 – コンパイルでバイナリができるので、それを成果物
   リポジトリに保存
 – ユニットテスト/受け入れテストでは、このバイナ
   リを取得してテストを行う。レポートは成果物リポ
   ジトリに保存
 – 手動受け入れテストでは、このバイナリを取得し、
   テスト環境にデプロイして手動テストする
 – リリースでは、このバイナリを本番環境にデプロイ
   する
13.6.2 デプロイメントパイプライン
  と成果物リポジトリのやりとり
           (2/2)
• 成果物リポジトリは、
 – 共有ファイルシステム上に保存する方法
 – Nexus, Artifactoryといったリポジトリ管理ツー
   ルも使える
13.7 Mavenを使って依存関係を管理
            する
• Maven
  – Javaプロジェクト用のビルド管理システム
  – 依存関係の管理が得意
  – 外部のライブラリとの依存関係/アプリケー
    ション内のコンポーネント間の依存関係 を
    抽象化してどちらもほぼ同様に扱えるように
    する

  –略
13.8 まとめ
• チームでの開発をできる限り効率的に進めつ
  つ、アプリケーションを常にリリース可能な
  状態に保つ方法を議論
 – すべての変更を小さくインクリメンタルなものに
   分割し、尐しずつメインラインにチェックインす
   る方法
 – アプリケーション自体をコンポーネントに分割す
   る方法
  • 基本的には、コンポーネントを個別にビルドする必要
    はない
  • ある一定ラインを超えたら、コンポーネントや依存関
    係にもとづいたパイプライン、成果物管理を用いない
    と、効率的なデリバリーや素早いフィードバックが実
    現できなくなる
感想
• ブランチはあくまで次善の策

Continuous delivery chapter13

  • 1.
    『継続的デリバリー』 読書会 第13章 コンポーネントや依存関係を管理す る 大崎的デリバリー @favril
  • 2.
    13.1 導入(1/2) • 本章で説明する内容 – コンテキストが切り替わるときにもアプリ ケーションを常にリリース可能に保つための 方法 – 巨大なアプリケーションのコンポーネント化 • コンポーネント分割の方法 • 複数コンポーネントからなる巨大プロジェクトの ビルド方法 • その管理方法
  • 3.
    13.1 導入(2/2) • 言葉の定義 – コンポーネント • それなりに大規模なコード • よく作られたAPIを提供 • 別の実装にも切り替えられる • モジュールとも呼ぶ • Windows: DLL, UNIX: SOファイル, Java: JARファイル.. – コンポーネントベースのシステム • いくつかの部品に分解可能 • それぞれのふるまいが定義されている • 他のコンポーネントと最小限のやりとり
  • 4.
    13.2 アプリケーションをリリース 可能な状態に保つ(1/3) • 通常、下記の手法で開発を行うとリリー ス間隔が長くなりがち リリー trunk ス 開発 bugfix 開発 release 1 • 「常にリリース可能な状態」を保ててい ない
  • 5.
    13.2 アプリケーションをリリース 可能な状態に保つ(2/3) • バージョン管理システムのブランチを使うこと で、メインラインを常にリリース可能な状態に保 つことが可能 リリー trunk ス マー ジ feature 1 feature 2 開発 / bugfix 開発 • しかし、これはあくまで次善の策 –  ブランチ上での作業が継続的に統合されていない
  • 6.
    13.2 アプリケーションをリリース 可能な状態に保つ(3/3) • 全員がメインライン上で作業する方式を 推奨 • ただし、常にリリース可能状態は保つ – そのための4つの作戦 • 新機能は完成するまで隠す • すべての変更を細かくインクリメンタルに行い、 個々の変更をすべてリリース可能な状態にする • 抽象化によるブランチを使って、大規模な変更を コードベースに加える • 変更の頻度が異なる部分をコンポーネント化し て、アプリケーションから切り離す
  • 7.
    13.2.1 新機能は完成するまで隠せ (1/2) • 開発中の機能を本番に反映しつつ、それ を利用できないようにする – 開発中の機能に個別のURIを与え、機能のリ リースまではWebサーバの設定でアクセス拒 否 – 古い機能と、開発中の新しい機能を設定で切 り替えれるように作る
  • 8.
    13.2.1 新機能は完成するまで隠せ (2/2) • 未完成の機能もメインラインで開発するメ リット – システム全体の統合やテストをありのままの状態 でいつでも実行できる – 依存関係の整理や統合のフェーズをプロジェクト の計画に含める必要がなくなる • この方法での開発は – 準備や検討などそれなりに大変 – 新機能を開発途中でもリリースできるという利点 を考慮すると、その価値はある – ブランチ開発より優れている
  • 9.
    13.2.2 すべての変更はインクリメン タルに(1/2) • 大規模な変更を加えるとき、以下をやりがち – ブランチを作り – アプリが動かなくなるくらいがっつり変更を加え – その後、正しく動くように細かい部分を含めて修 正する(メインラインにマージする) – 上記手法は、確かに開発を高速化するかもしれな い – が、すべて正しく修正するのは大変で難しい – したがって、変更量が大規模であればあるほど、 実際はブランチを作るべきではない
  • 10.
    13.2.2 すべての変更はインクリメン タルに(2/2) • 大規模な変更を細かく分割して、小さな 変更をインクリメンタルに進める – 要件を小さなタスクに分解していく過程と類 似 – 分析が重要 – 間違いが尐なくなる – 作業をどう進めるか(本当に進めるべきか) を判断できる – いつでもやめれる(?)
  • 11.
    13.2.3 抽象化によるブランチ (1/3) • 大規模な変更を加えたいが、小さくてインク リメンタルな段階に分解できない場合 – 1. 既存システムにおいて、変更したい部分を抽象 化 – 2. 既存システムをリファクタリングし、その抽象 化レイヤを使うようにする – 3. 新しい実装をする(ただし、完成までは実行パ スに含まれないように) – 4. 抽象化レイヤを書き換え、新しい実装に処理を 委譲 – 5. 古い実装を削除 – 6. 不要なら抽象化レイヤも削除
  • 12.
    13.2.3 抽象化によるブランチ (2/3) • この方法で困難な2点 – 対象のコードベースから、エントリポイント を分離すること – 開発中の機能に対して施すべき変更を管理す ること
  • 13.
    13.2.3 抽象化によるブランチ (3/3) • アプリに大規模な変更を加えるとき、包 括的な自動受け入れテストスイートの存 在が重要 – ユニットテストやコンポーネントテストは、 この目的で使うには粒度が細か過ぎる – 業務的な機能が壊れないようにするための助 けにならない
  • 14.
    13.3 依存関係(1/2) • ソフトウェアのある部分をビルドあるい は実行するために別の何かが必要 – 大半のアプリは何らかの依存関係がある – 例えば、動作環境 • Java  JVM • .NET  CLR • Rails  Ruby, Rails • C  C言語標準ライブラリ
  • 15.
    13.3 依存関係(2/2) • 言葉の定義 – コンポーネントとライブラリ • ライブラリ – ソフトウェアパッケージの中で、自分たちで制御しようのないも の(どれを使うかの選択しかできない) • コンポーネント – ソフトウェアの部品としてアプリが依存しているもの – 自分達が開発する – ビルド時の依存関係と実行時の依存関係 • ビルド時の依存関係 – アプリをコンパイルしたときにあらわれるもの • 実行時の依存関係 – アプリを実行して機能を使うときにあらわれる
  • 16.
    13.3.1 依存地獄(1/3) • 別名:DLL地獄 – 実行時に発生するライブラリの依存関係 – あるアプリケーションが、特定のバージョン の何かに依存しているのに、別のバージョン をデプロイしてしまった場合、あるいはそも そもデプロイしなかった場合に起こる • 例: 初期windowsは異なるバージョンのDLLを使うア プリの共存が不可能  .NET, GACで改善 • 例: Linuxは命名規約で回避
  • 17.
    13.3.1 依存地獄(2/3) • OS全体の依存関係 – 静的にコンパイルしたまともなアプリを使う – コンパイル時に依存関係を1つのアセンブリ にまとめれば実行時の依存関係はほぼなくな る – ただし、バイナリが肥大化するし、特定のOS バージョンに密結合するので、おすすめしな い
  • 18.
    13.3.1 依存地獄(3/3) • 動的言語の場合 – 依存するフレームワークやライブラリも、ア プリと一緒に出荷することで依存関係を解決 する • 例: Rails • Javaの場合 – あるクラスの複数のバージョンを同じJVM内 で使えないという深刻な問題がある – OSGiフレームワーク形式を使って解決する JAR1 ver1 アプリ JAR3 JAR2 ver2
  • 19.
    13.3.2 ライブラリを管理する (1/3) • ビルドが再現性のあるものであることが 重要 • 適切な方法が2つ – ライブラリもバージョン管理にチェックイン する • 最もシンプル • 小規模なプロジェクトならこれで十分 – 使うライブラリを宣言した上でMaven, Ivyを使 い、組織内のリポジトリからライブラリをダ ウンロードして使う
  • 20.
    13.3.2 ライブラリを管理する (2/3) • ライブラリチェックインの問題点 – リポジトリの肥大化 – 同一プラットフォーム上で、別のプロジェク トを共存させないといけなくなった場合 – プロジェクト間の依存関係を手動で管理する のは厳しい
  • 21.
    13.3.2 ライブラリを管理する (3/3) • 依存関係の管理を自動化する仕組みを提供す るツール(Maven, Ivy)を使う – 必要なライブラリとそのバージョンを、プロジェ クトに設定できる – 他プロジェクトとの依存関係をよろしく解決して くれる • 自前の成果物リポジトリを持つ – Artifactory, Nexusなど – 自社内のプロジェクトで、どのライブラリのどの バージョンが使えるか制御できる
  • 22.
    13.4 コンポーネント • なぜコードベースを複数のコンポーネン トに分割しないといけないのか? • コンポーネント間の関係はどうやって管 理すべきか?
  • 23.
    13.4.1 コードベースをコンポーネン トに分割する方法(1/6) • コンポーネントに分割することが、開発 プロセスを効率化する理由 – 1. 大きな問題を、より小さくわかりやすい問 題に分割できる – 2. コンポーネントが、システム中の更新頻度 が違う部分、ライフサイクルが違う部分をあ らわす – 3. ソフトウェアの設計や保守の段階で、その 責務を明確化することにつながる – 4. ビルドプロセスやデプロイメントプロセス を最適化する際に、自由をくれる
  • 24.
    13.4.1 コードベースをコンポーネン トに分割する方法(2/6) • 大半のコンポーネントの特徴 – 何らかの形式でAPIを公開していること • APIは、外部の協調オブジェクトとの情報交換を表 している • また、結合度合いも表している – コンポーネント間の依存は考慮しないといけ ない • 結合が強いと、別々に分割して、個別にビルドや デプロイメントをするのが難しくなる
  • 25.
    13.4.1 コードベースをコンポーネン トに分割する方法(3/6) • コンポーネント化すべき場面 – 1. コードベースの一部を個別にデプロイする必要がある – 2. コードベースをコアとプラグイン群に分割し、一部を別 の実装に切り替えたり、ユーザ側での拡張性を高めたりし たい – 3. そのコンポーネントが別のシステムへのインタフェース を提供する – 4. コードのコンパイルとリンクに時間がかかる – 5. 開発ツールでプロジェクトを開くのに時間がかかる – 6. コードベースが大きくなりすぎて、1つのチームで手に 負えない – 後半3点は著者の主観だが、すべて理由としては妥当だし、 最後の項目は最も重要
  • 26.
    13.4.1 コードベースをコンポーネン トに分割する方法(4/6) • チームの力を最大限に発揮できるのは – 10人前後のメンバーで構成 – 各自がコードベースの特定の部分を隅々まで 知り尽くしている • 特定の部分:機能的な区切りでも、それ以外の何 かの基準による区切でもよい
  • 27.
    13.4.1 コードベースをコンポーネン トに分割する方法(5/6) • 10人以上必要な場合 – システムを疎結合なコンポーネントに分割し、 チームも分割する – ただし、コンポーネント毎のチーム分けは勧めな い • 要件の分割はコンポーネントの境界と沿わない • コミュニケーションコストの浪費 • プロジェクト全体で何がベストか見えなくなりがち – 各チームが1つのストーリーの流れを扱うのがよ い • 必要なコンポーネントはすべて手をいれられる権限を 与える • 統合チームに責任を押し付けることがなくなる
  • 28.
    13.4.1 コードベースをコンポーネン トに分割する方法(6/6) • コンポーネント分割するにあたっての、強力 でお手軽なツールは無い – 説明してきたより良い設計を考えるだけ – その際の罠 • 何もかもコンポーネント化してしまうこと • 1つのコンポーネントにすべてを任せること • コンウェイの法則 – システムを設計する組織は、その組織のコミュニ ケーション構造をそっくりまねた設計を生み出す • チーム編成には注意
  • 29.
    コンポーネントを使うからといって必 ずしもN層アーキテクチャになるわけ ではない • N層アーキテクチャとは – アプリケーションを表示、業務処理、データ等、複数の階層で 分割する分散アプリケーション設計手法 • メリット – 経験の浅い開発者が集まった巨大なチームでも密結合な泥団子 を作らずにすむ – キャパシティやスケーラビリティの面で有利 • デメリット – 階層が物理的に離れている場合、リクエストを処理する待ち時 間が増える • 複雑なキャッシュの仕組みの導入につながる • 保守やデバッグがしづらい • コードは理解しにくく、各層間の依存関係のせいで変更もしにくい • N層化はコンポーネントベースの開発と同義ではなく、直交 する概念
  • 30.
    13.4.2 コンポーネントをパイプライ ン化する(1/4) • 基本的には、何か変更がコミットされる たびに、すべてをビルドし、テストする のが良い – 時間がかかりすぎるようになったら、そのと き対応する – 巨大で複雑なシステムでも対応可能
  • 31.
    13.4.2 コンポーネントをパイプライ ン化する(2/4) • パイプラインに分割したほうがやりやすい場面 – アプリケーションの一部が、他の部分とは異なるラ イフサイクルをたどる – アプリケーション内で機能的に分かれた部分をそれ ぞれ別チームが担当しており、個々のチームで専用 のコンポーネントを使っている – あるコンポーネントが他とは異なる技術やビルドプ ロセスを使っている – 共有コンポーネントが他のプロジェクトでも使われ ている – あるコンポーネントが比較的安定しいて、変更頻度 が低い – ビルドに時間がかかり、コンポーネントごとに分け た方が速い
  • 32.
    13.4.2 コンポーネントをパイプライ ン化する(3/4) • 各コンポーネントあるいはコンポーネン ト群のビルドには独自のパイプラインが 必要 • パイプラインの動き – 必要に応じてコードをコンパイルする – ひとつあるいは複数のバイナリを、あらゆる 環境へのデプロイメントに対応できるよう組 み立てる – ユニットテストを実行する – 受け入れテストを実行する – 手動テストをする場面ではそれに対応する
  • 33.
    13.4.2 コンポーネントをパイプライ ン化する(4/4) • 各バイナリが自身のリリースプロセスを 通過すれば、次のインテグレーションビ ルドに進む準備ができたことになる • DLLやJARの単位でパイプラインを作るべき、 と主張しているわけではない • 運用するビルド数はできるだけ尐ない方 がよい
  • 34.
    13.4.3 インテグレーションパイプラ イン(1/3) • 適切なバイナリを組み合わせてデプロイ 用のパッケージを作成 • できあがったアプリケーションを疑似本 番環境にデプロイしてスモークテストを 実行 • 受け入れテストを実行 • 例、図13-1 (p.427)
  • 35.
    13.4.3 インテグレーションパイプラ イン(2/3) • デプロイメントパイプラインに関する一 般原則2つ – 素早いフィードバック – ビルドの状況を関係者全員が見れる • 失敗した場合、なぜ失敗したかが正確にわかる必 要 – インテグレーションビルド側から、元になった各コンポ ーネントのバージョンをたどれる必要 – コンポーネント開発側からも、どのバージョンのコンポ ーネントがインテグレーションパイプラインで成功した か見れる必要
  • 36.
    13.4.3 インテグレーションパイプラ イン(3/3) • インテグレーションパイプラインの実行間隔 が離れていて、その間に複数のコンポーネン トに多くの変更があるのは好ましくない状況 – どの変更がアプリを壊したのかわかりにくい • 最もシンプルな解決策 – 各コンポーネントのうまく動いているバージョン について、考えうるすべての組み合わせをビルド する • 人手が必要ない&賢いアルゴリズムを考える必要もな い • 次善の策 – スケジューリングを使って、定期的にビルドする
  • 37.
    13.5 依存グラフを管理する • 依存関係は、ライブラリやコンポーネン トも含めてバージョン管理しておくこと が重要 – コンポーネント間の依存関係を図示した際は、 無閉路有向グラフでなければならない – そうならない場合、特に閉路グラフになる場 合は、依存関係に病的な問題がある
  • 38.
    13.5.1 依存グラフを作成する (1/4) • 例、図13-2 (p429) 上流 レポート 下流 エンジン フレームワー 資産管理アプ 決済エンジン ク リケーション CDS プライシング ライブラリ エンジン 他社 それぞれパイプラインを持っていて、 自身あるいは上流に変更があったら、 パイプラインが起動
  • 39.
    13.5.1 依存グラフを作成する (2/4) • この例のビルド時に起こりうるシナリオ – 1. 資産管理アプリケーションを変更 • 資産管理アプリケーションだけ再ビルド – 2. レポートエンジンを変更 • レポートエンジンを再ビルドし、すべてのテストを通す • その後、資産管理アプリケーションを再ビルド – 3. CDSプライシングライブラリに変更 • プライシングエンジンの再ビルド • その後、資産管理アプリケーションの再ビルド – 4. フレームワークに変更 • フレームワークを再ビルド • 直近の下流にあるすべての依存コンポーネントを再ビルド • 上記がすべてできたら、資産管理アプリケーションを再ビル ド – 1つでも失敗していたら、資産管理アプリケーションは再ビルド しない
  • 40.
    13.5.1 依存グラフを作成する (3/4) • 続き – 5. フレームワークとプライシングエンジンに 変更 • グラフ全体の再ビルドが必要 • 理想は、すべてのビルドが成功すること • もし、決済エンジンのビルドが失敗したら? – 資産管理アプリケーションを新しいフレームワークでは ビルドできない – しかし、CDSプライシングライブラリは動作検証済みな ので、これだけ新しいバージョンで、資産管理アプリ ケーションをビルドしたい » が、そんなバージョンのプライシングエンジンは存 在しない
  • 41.
    13.5.1 依存グラフを作成する (4/4) • この例の最も重要な制約は、 – 資産管理アプリケーションをビルドする際に 使えるフレームワークのバージョンが1つだ けということ • これは典型的な「菱形依存」
  • 42.
    13.5.2 依存グラフをパイプライン化 する(1/2) • 図13-3 (p432) – フィードバックを早くするために、各プロジェク トのパイプライン内でコミットステージが完了し た時点で、依存プロジェクトのパイプラインを立 ち上げる(受け入れテストの完了を待たない) – すべてのトリガーは自動(手動テスト、本番デプ ロイを除く) – アプリケーションのあるバージョンをビルドする ときに使った各コンポーネントをたどれることが 重要 – どのバージョンのコンポーネントの組み合わせが 統合に成功したかを知れる • 図13-4, 図13-5 (p433)
  • 43.
    13.5.2 依存グラフをパイプライン化 する(2/2) • 大幅な変更をコンポーネントに加える必要がある場合 は新しいリリースを作る – 図13-6 (p434) – APIの後方互換性がない新バージョンを開発する際、開発 をはじめる前に1.0系のリリース用ブランチを作ってから 開発する – 下流の資産管理アプリケーションは、1.0ブランチから 作ったバイナリを使える – Bugfixは1.0で行い、trunkにマージする – 資産管理アプリケーションが新バージョンに対応した時点 で、trunkに切り替える – 継続的インテグレーションの観点では次善の策 – だが、コンポーネントが疎結合であることが、統合を先延 ばしにしたときのリスクを抑えている
  • 44.
    13.5.3 いつビルドすべきか? (1/2) • 上流の依存を最新版に保ち、最新の機能 とバグフィックスを使いたいが、すべて の依存を最新にする統合にはコストがか かる – 動かなくなった部分を修正しないといけない ので
  • 45.
    13.5.3 いつビルドすべきか? (2/2) • 依存関係更新の頻度を決める際の検討事項 – 依存コンポーネントの最新版をどの程度信頼できるか? • 数が尐なく、自分たちで開発しているなら、修正も素早く行え るので、頻繁に統合すべき • 社内の別のチームの場合、個別のパイプラインにすべき。追従 するか、しないかを都度判断できる • サードパーティのライブラリは、明らかな必要性があるときの みにすべき – コンポーネントの変更にどの程度関与でき、どの程度知れ るかがポイント • 程度が低いほど、信頼性は下がる – 基本的には、依存コンポーネントの新バージョンの統合を 継続的に行えるのがベスト • コストがかかる • 素早いフィードバックを得るには、統合を頻繁に行う必要があ る • が、頻繁に行うと自分たちの関与しないところで問題が発生し うる • 両者のバランスをとるための解決策の1つとして「慎重な楽天
  • 46.
    13.5.4 慎重な楽天主義 • 依存グラフに、3つの状態を導入 – static, gruarded, fluid – staticな上流の依存を変更しても新しいビルドは 引き起こさない – fluidな上流の依存が変更されたら常にビルドを実 行する • ビルドが失敗すると、依存コンポーネントは 「guarded」になり、動作確認済みの既知のバージョン に固定される – guardedな依存はstaticと同じふるまいをする – 開発チームには通知が飛ぶ – 例、図13-7 (p437)
  • 47.
    13.5.5 循環依存 • 最もやっかいな問題 – 依存グラフに閉路が含まれる場合に起こる – 一番シンプルな例 • コンポーネントAはコンポーネントBに依存してい るが、 • コンポーネントBもコンポーネントAに依存してい る • 最初の起動時に致命的な問題 • プロジェクトの開始時点では循環していない • おすすめしないが解決策は図13-8 (p438)
  • 48.
    13.6 バイナリを管理する • 成果物リポジトリを使うときの一般的な 法則 • ファイルシステムだけを使ってバイナリ 管理する方法 • Mavenを使って依存関係を管理する方法
  • 49.
    13.6.1 成果物リポジトリの活用法 (1/3) • 成果物リポジトリに再生成できないもの を含めてはいけない – 気兼ねなく削除でき、それによって取り戻せ ない何かがあってはいけない – あらゆるバイナリを再生成するために必要な すべてをバージョン管理する
  • 50.
    13.6.1 成果物リポジトリの活用法 (2/3) • バイナリをどの程度保持し続けるにし ろ、ハッシュ(バイナリのMD5)も保持して おく必要がある – それによりバージョン管理システム内のどの リビジョンを使って作成したバイナリかわか る – 保存は、ビルドシステムでもよいし、バー ジョン管理システムでもよい • ハッシュ管理は構成管理の一貫として重要な位置 を占める
  • 51.
    13.6.1 成果物リポジトリの活用法 (3/3) • 最もシンプルな成果物リポジトリ – ディスク上のディレクトリ構造として構成し たもの • バイナリと、それを作るソースのバージョンを関 連づけられる必要がある • パイプラインごとにディレクトリを作り、その中 でビルド番号ごとのディレクトリを作る • ビルドの成果物はすべてこのディレクトリに保管 する
  • 52.
    13.6.2 デプロイメントパイプライン と成果物リポジトリのやりとり (1/2) • デプロイメントパイプラインで以下を実装 – ビルドの成果物を成果物リポジトリに保存 – 後で使うときに、それを取り出す • コンパイル、ユニットテスト、受け入れテスト、 手動受け入れテスト、本番 というパイプライン を例とする – コンパイルでバイナリができるので、それを成果物 リポジトリに保存 – ユニットテスト/受け入れテストでは、このバイナ リを取得してテストを行う。レポートは成果物リポ ジトリに保存 – 手動受け入れテストでは、このバイナリを取得し、 テスト環境にデプロイして手動テストする – リリースでは、このバイナリを本番環境にデプロイ する
  • 53.
    13.6.2 デプロイメントパイプライン と成果物リポジトリのやりとり (2/2) • 成果物リポジトリは、 – 共有ファイルシステム上に保存する方法 – Nexus, Artifactoryといったリポジトリ管理ツー ルも使える
  • 54.
    13.7 Mavenを使って依存関係を管理 する • Maven – Javaプロジェクト用のビルド管理システム – 依存関係の管理が得意 – 外部のライブラリとの依存関係/アプリケー ション内のコンポーネント間の依存関係 を 抽象化してどちらもほぼ同様に扱えるように する –略
  • 55.
    13.8 まとめ • チームでの開発をできる限り効率的に進めつ つ、アプリケーションを常にリリース可能な 状態に保つ方法を議論 – すべての変更を小さくインクリメンタルなものに 分割し、尐しずつメインラインにチェックインす る方法 – アプリケーション自体をコンポーネントに分割す る方法 • 基本的には、コンポーネントを個別にビルドする必要 はない • ある一定ラインを超えたら、コンポーネントや依存関 係にもとづいたパイプライン、成果物管理を用いない と、効率的なデリバリーや素早いフィードバックが実 現できなくなる
  • 56.