人生は図を描き直すには短すぎるから 私は最近、 として新しい会社に入社しました。いつものことながら、ゼロから始めなければなりませんでした。たとえば、アプリのコードはどこにあるのか、どのようにデプロイされるのか、構成はどこから来るのか、などです。ありがたいことに、同僚たちがすべてを「インフラストラクチャ アズ コード」にする素晴らしい仕事をしてくれました。そこで、私はこう考えました。 ソフトウェア エンジニア 「すべてがコード内にあるのなら、すべての点を結びつけるツールがないのはなぜだろう?」 このツールはコードベースをレビューし、重要な側面を強調したアプリケーション アーキテクチャ ダイアグラムを作成します。新しいエンジニアはダイアグラムを見て、「ああ、なるほど、これが仕組みだ」と言うことができます。 まず最初に どれだけ検索しても、そのようなものは見つかりませんでした。私が見つけた最も近いものは、インフラストラクチャ図を描くサービスでした。 では、それらのいくつかを取り上げましたので、詳しく見てみてください。結局、私はグーグル検索をあきらめて、何か新しいクールなものを開発してみることにしました。 このレビュー まず、Gradle、 、Terraform を使用してサンプル アプリを構築しました。GitHub アクション パイプラインは、Amazon Elastic Container Service にアプリをデプロイします。このリポジトリは、これから構築するツールのソースになります (コードは )。 Docker Java こちら 次に、結果として何を実現したいのかを非常に高レベルで図解しました。 リソースには次の 2 種類があると判断しました。 遺物 という用語には意味が多すぎると思ったので、 選びました。では、遺物とは何でしょうか? 遺物とは、見たいものの 90% です。これには以下が含まれますが、これらに限定されません。 アーティファクト 遺物 を アーティファクト(スキーム上の青いボックス、つまりJar、Dockerイメージ)、 Terraformリソース(スキーム上のピンク色のボックス、つまりEC2インスタンス、ECS、SQSキュー)を設定します。 Kubernetesリソース、 その他多数 すべての Relic には、名前 (例: my-shiny-app)、オプションのタイプ (例: Jar)、および Relic を完全に説明するキー → 値のペアのセット (例: パス → /build/libs/my-shiny-app.jar) があります。これらは と呼ばれます。Relic の定義が多ければ多いほど、より優れたものになります。 定義 ソース 2 番目のタイプは です。Source は Relic を定義、構築、またはプロビジョニングします (上記の黄色のボックスなど)。Source は Relic をある場所で説明し、その Relic がどこから来たのかを示します。Source は最も多くの情報を取得するコンポーネントですが、通常は図では二次的な意味を持ちます。Terraform または Gradle から他のすべての Relic に向かう矢印はおそらくあまり必要ありません。 Source Relic と Source には多対多の関係があります。 分割統治 すべてのコードをカバーするのは不可能です。最新のアプリには、多くのフレームワーク、ツール、クラウド コンポーネントが含まれている可能性があります。AWS だけでも、Terraform 用のリソースとデータ ソースが約 950 個あります。ツールは、他の人や企業が貢献できるように、簡単に拡張でき、設計上分離されている必要があります。 私は、信じられないほどプラグ可能な Terraform プロバイダーのアーキテクチャの大ファンですが、簡略化しては同じものを構築することにしました。 には、要求されたソース ファイルに基づいて Relic を構築するという明確な責任が 1 つあります。たとえば、 *.gradle ファイルを読み取り、 、 、または Relic を返します。各プロバイダーは、認識しているタイプの Relic を構築します。プロバイダーは Relic 間のやり取りを気にしません。プロバイダーは Relic を宣言的に構築し、互いに完全に分離します。 プロバイダー GradleProvider は Jar War Gz このアプローチでは、必要なだけ深く掘り下げることが容易です。良い例としては、GitHub Actions があります。一般的なワークフロー YAML ファイルは、疎結合されたコンポーネントとサービスを使用した数十のステップで構成されています。ワークフローでは、JAR をビルドしてから Docker イメージをビルドし、それを環境にデプロイできます。ワークフローのすべてのステップは、そのプロバイダーによってカバーできます。そのため、たとえば の開発者は、関心のあるステップにのみ関連するプロバイダーを作成します。 Docker Actions このアプローチにより、任意の数の人が並行して作業できるようになり、ツールにロジックを追加できます。エンド ユーザーは、プロバイダーをすばやく実装することもできます (独自のテクノロジの場合)。詳細については、以下の「カスタマイズ」を参照してください。 合併するかしないか 最も興味深い部分に入る前に、次の罠を見てみましょう。2 つのプロバイダーがあり、それぞれが 1 つの Relic を作成します。これは問題ありません。しかし、これらの Relic のうち 2 つが、2 か所で定義された同じコンポーネントの単なる表現である場合はどうなるでしょうか。次に例を示します。 タスク定義 JSON を解析し、 タイプの Relic を生成します。GitHub アクションワークフローにも ECS 関連のステップがあるため、別のプロバイダーが Relic を作成します。両方のプロバイダーが互いについて何も知らないため、重複が発生します。さらに、いずれかのプロバイダーが別のプロバイダーがすでに Relic を作成していると想定するのは誤りです。では、どうすればよいのでしょうか。 AmazonECSProvider は AmazonECSTask AmazonECSTaskDeployment それぞれの定義 (属性) があるため、重複したものを削除することはできません。唯一の方法は、それらをマージすることです。デフォルトでは、次のロジックがマージの決定を定義します。 relic1.name() == relic2.name() && relic1.source() != relic2.source() 名前が同じでも、異なるソースで定義されている 2 つの Relic をマージします (この例では、リポジトリ内の JSON とタスク定義参照が GithHub アクション内にあります)。 合併すると、次のようになります。 単一の名前を選択 すべての定義をマージする(キー→値のペア) 両方の元のソースを参照する複合ソースを作成する 線を引く 私は Relic の重要な側面を 1 つ意図的に省略しました。Relic には があるかもしれませんが、あった方がよいのです。Matcher は引数を受け取ってテストするブール関数です。Matcher はリンク処理の重要な部分です。Relic が別の Relic の定義と一致する場合、それらはリンクされます。 Matcher プロバイダーは他のプロバイダーによって作成された Relic について何も知らないと言ったことを覚えていますか? それは今でも真実です。ただし、プロバイダーは Relic の Matcher を定義します。言い換えると、結果の図の 2 つのボックス間の矢印の片側を表します。 例: Dockerfile には ENTRYPOINT 命令があります。 ENTRYPOINT java -jar /app/arch-diagram-sample.jar ある程度の確信を持って、Docker は で指定されたものをすべて と言えます。したがって、 Relic には、 という単純な Matcher 関数があります。おそらく、 が Definitions に含まれている Relic がこれに一致するでしょう。一致する場合は、 と Relic の間に矢印が表示されます。 ENTRYPOINT コンテナ化する Dockerfile entrypointInstruction.contains(anotherRelicsDefinition) arch-diagram-sample.jar Jar Dockerfile Jar Matcher が定義されていると、リンク プロセスは非常に簡単に見えます。リンク サービスはすべての Relic を反復処理し、それぞれの Matcher の関数を呼び出します。Relic A は Relic B の定義のいずれかと一致しますか? はい? 結果のグラフでそれらの Relic 間にエッジを追加します。エッジに名前を付けることもできます。 視覚化 最後のステップは、前の段階の最終的なグラフを視覚化することです。明らかな PNG に加えて、このツールは 、 、 などの追加の形式をサポートしています。これらのテキスト形式は見た目があまり魅力的ではないかもしれませんが、大きな利点は、これらのテキストをほぼすべての wiki ページ ( 、 などなど)。 Mermaid Plant UML DOT GitHub 、 合流点 サンプル リポジトリの最終的な図は次のようになります。 カスタマイズ カスタム コンポーネントをプラグインしたり、既存のロジックを微調整したりする機能は、特にツールが初期段階にある場合には不可欠です。Relics と Sources はデフォルトで十分に柔軟であるため、必要なものを何でも入れることができます。その他のすべてのコンポーネントはカスタマイズ可能です。既存のプロバイダーでは必要なリソースがカバーされませんか? 独自のプロバイダーを簡単に実装できます。上記のマージまたはリンク ロジックに満足できませんか? 問題ありません。独自の または を追加してください。すべてを JAR ファイルにパックして、起動時に追加します。詳細については 参照してください。 LinkStrategy MergeStrategy 、こちらを アウトロ ソース コードに基づいてダイアグラムを生成することは、おそらく注目を集めるでしょう。特に ツール (はい、これは私が話していたツールの名前です)。 。 NoReDraw 貢献者を歓迎します 最も注目すべき利点(名前からわかる)は、コンポーネントが変更されてもダイアグラムを再描画する必要がないことです。エンジニアリングの注意が足りないため、ドキュメント全般(特にダイアグラム)が古くなります。NoReDraw の なツールを使用すると、PR/CI パイプラインに簡単にプラグインできるため、もう問題にはなりません。 ことを忘れないでください 😉 よう ダイアグラムを再描画するには人生は短すぎる