愛記システム概念設計:システム構築の品質評価のポイント7 試験性 | 続・ティール組織 研究会のブログ

続・ティール組織 研究会のブログ

ティール組織が話題になっているが、具現化するにはどうしたらよいか?
その研究を続けるにあたり、さらに次の形態である、続・ティール組織なるものまで視野に入れ、具体的な施策・行動内容を研究・支援する会。

先までは、"愛記"についての記載で、どのようにブロックチェーンSNSに組み込んで実装していけばよいのか、概念的なところからアプローチ方法を記載していった。大まかな概念としてはひとまず終えた。次は、ブロックチェーンの概念設計といえるところまで、基本設計書に着手できるようなところまで、概念を具体化していきたい。

愛記システムのシステム評価について

システム評価とは、つまりは、このシステムを導入して成功だったか失敗だったかという効果検証という意味だ。概念設計をする上で必ず抑えておくべきポイントということだ。それには各項目があり、それぞれの項目を見ていくことで、その結果が得られる。そのシステム評価項目を1つずつ見ていきたい。

システム構築の品質評価のポイント1:理解可能性(Understandability)

システム構築の品質評価のポイント2:完全性(Completeness)

システム構築の品質評価のポイント3:簡潔性(Conciseness)

システム構築の品質評価のポイント4:移植性(Portability)

システム構築の品質評価のポイント5:一貫性(Consistency)と構造化の度合い

システム構築の品質評価のポイント6:保守性(Maintainability)

システム構築の品質評価のポイント7:試験性(Testability)

システム構築の品質評価のポイント8:ユーザビリティ(Usability)

システム構築の品質評価のポイント9:効率性(Efficiency)

システム構築の品質評価のポイント10:セキュリティ(Security)

システム構築の品質評価のポイント7:試験性(Testability)

システム構築の品質評価ポイントとなっている試験性とは、不具合があるかどうかを確認するための各種テスト項目が簡単に実施できるかどうかである。不具合があった際にその不具合が階層まで調査する必要があり、見つけるまで非常に時間がかかるのは問題である。この試験性は先ほど記載した保守性と非常に関連性があり、試験性の高さは保守性の高さに繋がる。逆に低いと保守性の品質評価も下がる。

 

さて、試験性の定義として、7つの特性があるのでそれぞれについて見ていこう。

  • 実行円滑性(Operability)
  • 観測容易性(Observability)
  • 制御容易性(Controllability)
  • 分解容易性(Decomposability)
  • 単純性(Simplicity)
  • 安定性(Stability)
  • 理解容易性(Understandability)

これらの特性はJames Bach著『ソフトウェアテスト293の法則』やRoger S. Pressman著『実践ソフトウェアエンジニアリング』 で挙げられている。

1. 実行円滑性(Operability)

ソフトウェアが効率的に実行され、必要な操作や監視がスムーズに行えるかどうかを表す特性である。例えば次のような状況は、実行円滑性が低い。

・テストの準備や実行にコスト(時間、費用)がかかる

・安全に実行できる環境が無い

ここが特に難しい。ブロックチェーンシステムの場合、サーバーとは全く異なるので、安全に実行できる環境がほぼ無いと言える。参加者もどんどん増えていくし、バリデーターも変わっていく。サーバーのように試験環境を整えることが難しいのだ。だからこそ、既存のブロックチェーンの事例をしっかりと学び、既存のブロックチェーンで起こってきたトラブルの数々に対処していくこと、さらには、起こりえるトラブルを予測してシンプルな設計していくしかないのだろう。

 

実行円滑性(Operability)をブロックチェーンシステムに特有の課題に焦点を当てて説明すると下記のような具合である。

  1. テストの準備や実行のコストがかかる場合:

    • ブロックチェーンは分散型であり、各参加者が同じ分散台帳を保持する必要がある。このため、テストケースを作成する際には、各参加者の振る舞いや相互作用を模擬する必要がある。これにより、テストの実行にコストがかかる。解決策として、仮想環境やブロックチェーンの模擬ツールを活用して、実際のネットワークの挙動を模倣することが挙げられる。例えば以下のようにだ。
    • DockerやVagrantを使用した仮想環境の構築:

      • DockerやVagrantを利用して、仮想環境を構築し、複数のノードを起動することが可能である。各ノードはブロックチェーンネットワークの参加者を模倣する。これにより、実際の運用環境に近い条件でテストが行える。
    • GanacheやTruffleなどのブロックチェーン開発用ツール:

      • GanacheやTruffleは、イーサリアムなどのブロックチェーンの開発、デバッグ、テストを支援するためのツールである。これらを使用すると、ローカルで仮想のブロックチェーンネットワークを構築し、デモやテストを行うことができる。
    • シミュレーションツールの活用:

      • ブロックチェーンの挙動をシミュレートするためのツールやフレームワークも存在する。例えば、Ethereumのトランザクションの動きやスマートコントラクトの挙動を模擬するツールなどがある。これらを使用して、様々な状況下でのネットワークの挙動を確認できる。
    • 分散システムシミュレーションツール:

      • ブロックチェーンは分散システムの一形態であるため、分散システムのシミュレーションツールを使用することも考えられる。例えば、Apache CassandraやDynamoDBなどの分散データベースのシミュレーションを利用して、ネットワークの性能や信頼性を検証することができる。
  2. 安全に実行できる環境がない場合:

    • ブロックチェーンはセキュリティが不可欠である。特にスマートコントラクトの脆弱性やネットワーク攻撃に対処する必要がある。コードレビューやセキュリティテストを通じて、潜在的な脆弱性を特定し、修正することが求められる。また、暗号学的手法やハッシュ関数の選択にも十分な注意が必要である。
  3. ネットワーク参加者の増加やバリデーターの変更:

    • ブロックチェーンは分散型であり、ネットワーク参加者やバリデーターが頻繁に変更される可能性がある。これに対処するためには、ネットワークの拡張性を確保し、新しい参加者が容易に加われるような仕組みを構築することが必要である。また、参加者間の信頼性や評価基準も考慮することが重要である。例えば以下のようにだ。
    • 拡張性の確保:

      • ブロックチェーンプロトコルやアプリケーションは、ネットワークの規模が拡大しても十分なスケーラビリティを保持する必要がある。スケーラビリティの向上には、分散型データベースの最適化やフェデレーション内での合意形成の最適化があげられる。
      • 分散型データベースの最適化の例として、IPFS(InterPlanetary File System)などがその一例で、データを複数のノードに分散して格納することで、ネットワーク全体での読み書き速度を向上させる。
      • フェデレーション内での合意形成の最適化の例として、PBFT(Practical Byzantine Fault Tolerance)やその他のBFT(Byzantine Fault Tolerance)アルゴリズムが考えられる。PBFTや他のBFTアルゴリズムを実装したメインチェーンのプログラムを作成するための例を示す。この例では、Nodeという構造体を使用してノードを表し、main関数でノードの作成と合意形成プロセスをシミュレートする。
      • use std::collections::HashMap;
        use std::sync::{Arc, Mutex};
        use std::thread;
        use std::time::Duration;

        // ノードを表す構造体
        #[derive(Clone)]
        struct Node {
            id: usize,
            peers: Arc<Mutex<HashMap<usize, String>>>,
            message: String,
            agreed_message: Arc<Mutex<Option<String>>>,
        }

        impl Node {
            // メッセージを送信するメソッド
            fn send_message(&self, peer_id: usize, message: &str) {
                let mut peers = self.peers.lock().unwrap();
                if let Some(peer_address) = peers.get(&peer_id) {
                    println!("Node {} sending message to Node {}: {}", self.id, peer_id, message);
                    // 実際のネットワーク通信やメッセージ送信の処理をここに追加する
                } else {
                    println!("Node {} could not send message to Node {}: peer not found", self.id, peer_id);
                }
            }

            // 合意形成プロセスを開始するメソッド
            fn start_agreement(&self) {
                // メッセージを送信し、合意形成を開始する
                self.send_message(1, &self.message);

                // メッセージの合意形成を待機する
                thread::sleep(Duration::from_secs(2));

                // 合意されたメッセージを取得する
                let agreed_message = self.agreed_message.lock().unwrap();
                if let Some(message) = &*agreed_message {
                    println!("Node {} agreed on message: {}", self.id, message);
                } else {
                    println!("Node {} did not reach agreement on message", self.id);
                }
            }

            // メッセージを合意するメソッド
            fn agree_message(&self, message: &str) {
                let mut agreed_message = self.agreed_message.lock().unwrap();
                *agreed_message = Some(message.to_string());
                println!("Node {} agreed on message: {}", self.id, message);
            }
        }

        fn main() {
            // ノードの初期化
            let peers = Arc::new(Mutex::new(HashMap::new()));
            let agreed_message = Arc::new(Mutex::new(None));

            let node1 = Node {
                id: 1,
                peers: peers.clone(),
                message: "Hello from Node 1".to_string(),
                agreed_message: agreed_message.clone(),
            };

            let node2 = Node {
                id: 2,
                peers: peers.clone(),
                message: "Hello from Node 2".to_string(),
                agreed_message: agreed_message.clone(),
            };

            // ノード間の通信を設定
            let mut peers_map = peers.lock().unwrap();
            peers_map.insert(2, "Node 2 Address".to_string());
            peers_map.insert(1, "Node 1 Address".to_string());

            // メッセージの合意形成プロセスを開始
            let node1_handle = thread::spawn(move || {
                node1.start_agreement();
            });

            let node2_handle = thread::spawn(move || {
                node2.start_agreement();
            });

            node1_handle.join().unwrap();
            node2_handle.join().unwrap();
        }
      • BFTアルゴリズムは、一般的に従来のブロックチェーンのコンセンサスアルゴリズムよりも高いパフォーマンスを提供するが、より多くの通信や計算リソースを必要とすることがある。そのため、追加のリソースを確保する必要があるかもしれない。PBFTや他のBFTアルゴリズムをブロックチェーンに追加することができるが、これらの変更は慎重に行う必要があり、追加する価値があるかどうかを十分に検討する必要があるだろう。

    • 新規参加者の容易な参加:

      • 新しい参加者がネットワークに参加しやすい状態を維持するために、ユーザーフレンドリーなプロセスやトークンエコノミクスの設計が重要である。セットアップや参加のハードルが低く、ユーザーがスムーズに参加できるようにする必要がある。
    • バリデーターの管理と評価:

      • バリデーターの信頼性や評価基準を明確に定義し、新しいバリデーターがネットワークに加わる際には、信頼性や過去の実績を確認できるメカニズムを構築する。これには信頼性スコアやステーキングの仕組みが含まれる。以下は、先に挙げたフェデレーションモデルにおいてバリデーターの役割を当てはめてみたものである:
      • GulfStream(ガルフストリーム):

        • バリデーター: GulfStreamノードがバリデーターとなり、トランザクションをフェデレーション内で検証し、妥当なものとしてブロックに追加する責任がある。
      • Turbine(タービン):

        • バリデーター: Turbineノードがバリデーターとして、新しいブロックをネットワークに伝搬し、他のノードにブロックの正当性を検証させる役割がある。
      • Proof of History(PoH):

        • バリデーター: PoHにおいては、新しいデータが正確に履歴に追加されるかどうかを検証するバリデーターが存在する。
      • AiKiSystem(愛記システム):

        • バリデーター: AiKiSystem全体がブロック生成やトランザクション処理を行う中で、各要素(GulfStream、Turbine、PoH)が内部でバリデーターとしての役割を果たす。
    • 分散型議決メカニズム:

      • ネットワークの運営や意思決定において、分散型の議決メカニズムを採用することで、参加者全体がネットワークの進化に参加しやすくなる。投票、プロポーザル、提案などのメカニズムが含まれる。
      • 市町村のブロックチェーンが独自に行う意思決定プロセスは、市町村内のメンバーや関係者がネットワークの進化や変更に関与しやすくすることを目的としている。このようなメカニズムにより、ブロックチェーンのルールや機能の変更、新しい機能の追加、あるいはネットワークの方針に関する決定が分散的かつデモクラティックな方法で行われることが期待される。
      • 分散的な意思決定プロセスの実装: ブロックチェーン内の自治体は、自身のブロックチェーン上で意思決定を行う。これにより、自治体内のメンバーや関係者がネットワークの進化や変更に関与しやすくなる。

      • 投票やプロポーザル機能の実装: プログラム内で意思決定を行う際に、投票やプロポーザルのメカニズムを実装していることが想定される。これにより、ブロックチェーンのルールや機能の変更、新機能の追加、方針に関する決定が行われる。

      • 分散的かつデモクラティックな方法での決定: ブロックチェーンの仕組みにより、自治体内の参加者全体がネットワークの進化に参加でき、決定が分散的かつデモクラティックな方法で行われることが期待される。

    • アップグレードプロセス:

      • ネットワークのアップグレードプロセスを透明で柔軟なものに設計し、新しい機能や変更が簡単かつ安全に適用できるようにする。フォークのメカニズムやスマートコントラクトのアップグレード機能が含まれる。
    • プライバシーとセキュリティ:

      • 新しい参加者やバリデーターがプライバシーを保護でき、セキュリティを確保できるような設計が求められる。データの秘匿や暗号化、アクセスコントロールの強化が含まれる。

 

2. 観測容易性(Observability)

システムの内部状態がテスト中にどれだけ容易に観察できるかを表す特性である。例えば次のような状況は、観測容易性が低い。

・外部から状態の観測ができない(結果を返さない、privateになっている)

・影響が観測範囲外に発生する(外部サービス、メール送信など)

・デバック情報が不足していて、エラーやテスト失敗の原因を追跡できない

観測に関しては容易であろう。ここはそこまで大きな問題では無い。

 

内部状態の観測容易性がテスト中に重要な特性であるという観点で、以下のような具体的なアプローチや戦略を考えることができる:

  1. ログの導入: システム内で発生するイベントや動作に関するログを詳細に取得し、これをテスト中に活用する。ログはエラー発生時や特定の処理が実行される際に、内部の状態や挙動を理解するのに役立つ。また、ログはテスト実行時に観測可能であるだけでなく、後で解析やデバッグにも使用できる。

  2. デバッグモードの有効化: システムにデバッグモードを導入し、テスト実行中に特定の情報やステップを詳細に記録できるようする。デバッグモードでは通常、変数の値や条件分岐、関数の呼び出し履歴などが記録され、テスト中の内部の挙動を逐一確認できる。デバッグモードでは特に対話的なデバッグが可能であり、コードの特定の箇所でステップ実行や変数の確認を行うことができる。
    def test_example():
      # デバッグモードを有効にする箇所
      pdb.set_trace()

        # テストの実行処理
        variable_a = 10
        variable_b = 5

        result = variable_a + variable_b

        # デバッグ情報を表示
        print(f"変数A: {variable_a}")
        print(f"変数B: {variable_b}")
        print(f"結果: {result}")

        # テストの実行
        test_example()

  3. モニタリングツールの利用: システム全体や特定のコンポーネントにモニタリングツールを導入し、リアルタイムで内部の状態を把握する。これにより、テスト中に外部からでもシステムの動作やパフォーマンスを観測できる。フェデレーションモデルにおいても、モニタリングツールの活用は重要である。具体的には、各市町村のブロックチェーンノードや関連するコンポーネントに対してモニタリングを行い、以下のような点に注意を払うことが考えられる。 

    • ネットワークの参加者やバリデーターの状態: モニタリングツールを使用して、各市町村のブロックチェーンノードやバリデーターの状態をリアルタイムで監視する。参加者の増減やバリデーターの変更があった場合に、それを迅速に把握できる。
    • トランザクションの処理状況: ブロックチェーン上で行われるトランザクションの処理状況やブロックの生成状態をモニタリングする。これにより、ネットワーク内でのトランザクションの進捗やエラーの発生などを把握できる。

    • ネットワークのパフォーマンス: ブロックチェーンネットワーク全体や各市町村のノードのパフォーマンスをモニタリングする。トランザクションの処理時間やネットワークの応答時間などを観測し、パフォーマンスの低下がないか確認する。

    • セキュリティイベントの検知: ブロックチェーンネットワークにおいてはセキュリティが極めて重要である。モニタリングツールを使用して、異常なアクティビティや攻撃の検知を行い、セキュリティ対策を強化する。

    • ダミー実装の導入: テスト中に、実際の外部サービスやメール送信などの影響が及ぶ処理を、ダミー実装やモック化して外部の影響を避けつつ、内部の挙動を確認する。これにより、外部の要因を排除して内部の状態を観測しやすくなる。

  4. 可視化と分析: モニタリングデータはグラフやダッシュボードとして視覚的に表示されることが一般的である。これにより、テスターや開発者はデータを効果的に分析し、システムのトラブルシューティングや改善のための情報を得ることができる。フェデレーションモデルにおいても、可視化と分析は重要な活動である。以下は、フェデレーションモデルにおける可視化と分析の一般的なアプローチである。

    • ネットワークの状態可視化:各市町村のブロックチェーンノードや関連コンポーネントの状態を可視化するダッシュボードを構築する。ダッシュボードを作成するためのデータを選定し、可視化ツールやライブラリ(例: Grafana, Kibana, D3.jsなど)を使用してデータを視覚的に表現する。ダッシュボードをリアルタイムに更新するために、データソースからの新しい情報を受け取り、表示を更新するメカニズムを構築する。WebSocketやHTTPプッシュなどの技術を使用して、データの変更を即座に反映させる。これにより、各ノードの参加者数やバリデーターの状態、ネットワークのトランザクション数などがリアルタイムに把握できる。以下に、プログラム例を示す:

      #まず、websockets ライブラリをインストールする。
      pip install websockets

      #サーバーサイドのPythonスクリプトを作成する。これはWebSocketサーバーを立ち上げ、新しい情報が到着したときに接続中のクライアントに情報を送信する。
      import asyncio
      import websockets

      async def dashboard_server(websocket, path):
          # 仮のデータソースから情報を受け取り、クライアントに送信
          while True:
              # 仮のデータソースから新しい情報を取得
              new_data = fetch_new_data()

              # クライアントに新しい情報を送信
              await websocket.send(new_data)

              # 一定の間隔で情報を更新する (例: 5秒ごと)
              await asyncio.sleep(5)

      def fetch_new_data():
          # 仮のデータソースから新しい情報を生成
          # ここではランダムな数値を生成していると仮定
          import random
          return str(random.randint(1, 100))

      # WebSocketサーバーを起動
      start_server = websockets.serve(dashboard_server, "localhost", 8765)

      # イベントループを開始
      asyncio.get_event_loop().run_until_complete(start_server)
      asyncio.get_event_loop().run_forever()


      #このサーバーコードは、fetch_new_data 関数で仮のデータを生成し、接続中のクライアントに対して一定の間隔でデータを送信する。次に、クライアントサイドのPythonスクリプトを作成する。これはWebSocketを使用してサーバーと通信し、新しいデータをダッシュボードに反映する。
      import asyncio
      import websockets

      async def dashboard_client():
          uri = "ws://localhost:8765"
          async with websockets.connect(uri) as websocket:
              while True:
                  # WebSocketを通じてサーバーから新しいデータを受信
                  new_data = await websocket.recv()

                  # 受信したデータをダッシュボードに表示(ここでは単にプリント)
                  print("Received new data:", new_data)

      # クライアントを開始
      asyncio.get_event_loop().run_until_complete(dashboard_client())


      このクライアントコードは、WebSocketを使用してサーバーに接続し、新しいデータが到着するたびに表示する。実際のダッシュボードの更新は、データを受信したところでダッシュボードの更新処理を実行するように拡張できる。これにより、データソースからの新しい情報をリアルタイムにダッシュボードに反映するメカニズムが構築される。

    • トランザクションの処理状況分析:

      ブロックチェーン上でのトランザクションの処理状況をグラフやチャートで可視化する。トランザクションの成功率や処理時間の傾向を分析し、問題があれば素早く対応する。
    • ネットワークのパフォーマンス分析:

      ネットワーク全体や各市町村のノードのパフォーマンスデータを可視化する。具体的なメトリクスに基づいて、トランザクションの応答時間の推移を示す折れ線グラフ、ネットワーク帯域幅やCPU使用率の変動を示す棒グラフ、ボトルネックの特定に役立つピエーチャートなどを配置する。パフォーマンスが予め定義した閾値を下回ったり、特定の条件が発生した場合には、アラートを生成し、関係者に通知する仕組みを構築する。これにより、問題が発生した際に素早く対応できる。
    • セキュリティイベントの可視化:

      セキュリティイベントや異常なアクティビティを可視化することで、セキュリティの脅威に対処する。ログデータやネットワークトラフィックのパターンを分析し、不審な挙動を検知する。
    • データの履歴と変化の追跡:

      ブロックチェーン上のデータの変更や履歴を可視化し、特定のトランザクションやブロックの影響を追跡する。トランザクションごとにトランザクションIDやハッシュ、関連するデータを表示することで、ブロックチェーン上でのデータ変更を追跡できる。ブロックチェーン上のデータ変更のタイムラインを可視化する。各トランザクションやブロックが発生した時間軸上で、変更がどのように広がったかを追跡できるようにする。これにより、システム内での変更がどのように広がっているかを理解する。
      #まず、簡単なトランザクションを表すクラスを作成する:
      class Transaction:
          def __init__(self, transaction_id, data_changes):
              self.transaction_id = transaction_id
              self.data_changes = data_changes

      #次に、トランザクションを管理するクラスとして、トランザクションの履歴を持つ Blockchain クラスを作成する:
      class Blockchain:
          def __init__(self):
              self.transactions = []

          def add_transaction(self, transaction):
              self.transactions.append(transaction)

          def visualize_timeline(self):
              for transaction in self.transactions:
                  print(f"Transaction ID: {transaction.transaction_id}")
                  print(f"Data Changes: {transaction.data_changes}")
                  print("-" * 20)

      #これで、トランザクションの履歴を保持し、その履歴を可視化する Blockchain クラスができた。これを用いて、トランザクションを追加し、その履歴を表示してみよう:
      # ブロックチェーンのインスタンスを作成
      blockchain = Blockchain()

      # トランザクションを追加
      transaction1 = Transaction("ABC123", {"user1": "modified data"})
      blockchain.add_transaction(transaction1)

      transaction2 = Transaction("XYZ789", {"user2": "new data"})
      blockchain.add_transaction(transaction2)

      # タイムラインを可視化
      blockchain.visualize_timeline()

      これにより、各トランザクションの情報がタイムラインとして表示される。これは非常にシンプルな例だが、フェデレーションモデルにおいても、各市町村が行うトランザクションやデータ変更に関する情報を集約し、その変更履歴を追跡・可視化する仕組みを構築することが考えられる。

これらのアプローチを組み合わせることで、テスト中にシステムの内部状態を効果的に観測できるようになる。

 

いかがであろうか、まずは、実行円滑性(Operability)、観測容易性(Observability)について記載した。一つずつ精度をあげていくことで、全体的な試験性もあがっていく。地道に積み上げていくことがもとめられるのだろう。