View on GitHub

Today I Learned

Software Engineering Blog

8. Progressing from Metrics to Engineering

8.1 The Path to Fitness Functions

「アーキテクチャ適応度関数とは、アーキテクチャ特性に対する客観的な評価基準を提供するあらゆるメカニズムである」

8.2 From Metrics to Engineering / 8.3 Automation Operationalizes Metrics

デプロイメントパイプラインに適応度関数を追加し、自動化を用いて継続的に検証することによって、メトリクスはエンジニアリングへと変換される。

適応度関数を継続的インテグレーションに関連付けることで、手作業のガバナンスチェックを定期的な完全性の検証に変換できる。

8.4 Case Study: Coupling

レイヤードアーキテクチャを採用したが、開発チームがそれを正しく実装できる保証がない。

これをArchUnitを使ってユニットテストで構造テストを定義することで、設計が正しいかを確認することができる。

layeredArchitecture()
    .layer("Controller").definedBy("..controller..")
    .layer("Service").definedBy("..service..")
    .layer("Persistence").definedBy("..persistence..")
  
    .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
    .whereLayer("Service").mayNotBeAccessedByAnyLayer("Controller")
    .whereLayer("Persistence").mayNotBeAccessedByAnyLayer("Service")

ArchUnitはJavaのプラットフォームでしか利用できないので、アーキテクトはそのための必要な情報を見つけるコードを書かなければならない。

直近24時間のログからそれぞれの呼び出し先を解析し、呼び出し先がルールと異なる場合は例外を発生させる。

def ensure_domain_services_communicate_only_with_orchestrator
  list_of_services = List.new()
                        .add("orchestrator")
                        .add("order placement")
                        .add("payment")
                        .add("inventory")
  list_of_services.each { |service|
    service.import_logsFor(24.hours)
    calls_from(service).each { |call|
      unless call.destination.equals("orchestrator") 
          raise FitnessFunctionFailure.new()
    }
   }
end

8.5 Case Study: Zero-Day Security Check

もしすべてのプロジェクトにデプロイメントパイプラインがあり、セキュリティチームがそこに適応度関数をいつでも追加できる状態であるとする。

その場合、パスワードが平文で保存されているかのチェックやゼロデイ攻撃のセキュリティパッチなどをすぐに適用できる。つまり広範で重要なガバナンスタスクを一元的に管理し自動化できる。

8.6 Case Study: Fidelity Fitness Functions

GitHubのエンジニアリングブログ Move Fast and Fix Things - The GitHub Blog の事例の話。

Gitのマージ機能に変わる新しいマージ機能を導入したい。PRのマージボタンを押したときに実行されるコード。

Scientistというツールを作ってユーザがバグにさらされることなくチームが安全に検証できるようにした。

Scientistは usetry の2つの節を提供し、新旧のコードをそこに含める。

def create_merge_commit(author, base, head, options = {})
  commit_message = options[:commit_message] || "Merge #{head} into #{base}"
  now = Time.current

  science "create_merge_commit" do |e|
    e.context :base => base.to_s, :head => head.to_s, :repo => repository.nwo
    e.use { create_merge_commit_git(author, now, base, head, commit_message) }
    e.try { create_merge_commit_rugged(author, now, base, head, 
        commit_message) }
  end
end

use は必ず実行されユーザは常に個の結果を受け取る。一定の割合(1%程度)で tryの新コードも実行される。

try で発生した例外はすべて記録される。それぞれのメトリクスからパフォーマンスや正確性、エラー数、それらの類似度がダッシュボードに公開される。

4日間これを動かし続け、問題ないと判断して tryのコードに切り替わった。