Recruit Data Blog

  • はてなブックマーク

目次

本記事の内容

ドキュメント、書いてますか?

こんにちは。データエンジニアの加藤です。社内データプロダクトである Knile1 と Crois2 の開発・運用や、社内に複数あるデータプロダクトの統合や連携などを見据えたリアーキテクチャを担当しています。

唐突ですが、チーム開発をしている皆様は、日常業務でドキュメントを書いていますか? 4 月の新卒社員の配属や組織再編・異動など、人や組織が大きく入れ替わる時期を終え、以下のような課題を再認識したチームも多いのではないでしょうか。

  • オンボーディングのドキュメントが整っていない
  • 現状のアーキテクチャ図が整備されていない
  • プロダクトのデバッグ対応が言語化されていない

自チームでもこういった問題は常に挙がっていましたが、ドキュメンテーション改善はなかなかされない状況が続いていました。

リモートワークが中心の業務では特に、暗黙知をきちんと言語化し、属人性を排除していくためにドキュメントを定常的にメンテナンスすることは非常に重要です。 Googleのソフトウェアエンジニアリング の書籍では、ドキュメンテーションはコードとして扱われるべきであるというプロセスの話や、ソフトウェア開発で必要なタスクの 1 つとして扱う文化の利益についても述べられており、その重要さを主張しています。 エンジニアのためのドキュメントライティング の書籍では、ドキュメントの作成・公開・測定・保守などの技術について体系的にまとめられており、ドキュメントの継続的な改善プロセスの重要さが述べられています。 しかし、ドキュメントの継続的な改善と運用には、ドキュメンテーションを開発プロセスに取り入れるチームの文化や個人のライティングスキルが求められます。

本ブログでは、チームのドキュメンテーション業務改善のために導入したプロセスやツールなどについて紹介します。

※ 自チームは、社内データプロダクト利用者向けドキュメントと、チームメンバー向けの内部向け開発ドキュメントの 2 つのドキュメントを管理運用していますが、今回は後者のチームメンバー向けの開発ドキュメント改善についてお話します。

チーム開発ドキュメントにおける課題

自チームでのドキュメントの管理運用における主な課題は以下が挙がっていました。

  1. ドキュメントを追加したいときに、何をどこに書いたらいいかわからない
    • 結果、情報が散乱してしまい欲しい情報を手早く見つけることができない
    • また、「何を」の部分のスコープが人によってブレるため、ドキュメントごとの情報の粒度が揃っていない
  2. 一度作られたアーキテクチャ図の加筆修正がしづらい
    • キレイに整形しようとして、細かいオブジェクトの位置の調整や矢印などの微調整といった細かい修正に時間が取られてしまう
    • 図の書き方に属人性が生まれてしまうため、複数人で管理運用がし辛い
  3. 似たようなアーキテクチャ図が乱立するため、管理コストの増加や修正漏れが発生する
    • 微妙に伝えたい抽象度が異なることもあり、似たような図を多く作らなければいけない
    • そのため、システム構成が修正されたときに複数のアーキテクチャ図の修正が発生してしまう

課題の解決方法

1 つ目の「ドキュメントを追加したいときに、何をどこに書いたらいいかわからない」という問題に対しては、 Diátaxis というフレームワークを導入しました。

2 つ目の「一度作られたアーキテクチャ図の加筆修正がしづらい」及び 3 つ目の「似たようなアーキテクチャ図が乱立するため、管理コストの増加や修正漏れが発生する」という問題に対しては、 C4Model 及びその実装である Structurizr というツールを導入し、アーキテクチャ図の継続的な改善フローを確立しました。

Diátaxis の採用

Diátaxis は、技術ドキュメント管理フレームワークです。 2021 年に Canonical 社が Ubuntu のドキュメント管理を Diátaxis Framework へ移行することを宣言した ことでも有名です。Cloudflare Workers, Django, Gatsby, Numpy, Tesla Motors など、著名なプロジェクトでも採用されています(採用事例は https://diataxis.fr/adoption.html を御覧ください)。 ユーザーニーズの体系的な記述と分析に基づいて、以下のようなドキュメント構造を定義しています。

diataxis

※ 画像は https://diataxis.fr/ より引用致しました

横軸は習得↔応用、縦軸は実践↔理論という 2 軸に分けることで Tutorials / How-to Guides / Reference / Explanation の 4 象限でドキュメントを管理します。 公式ページにある Characteristics of documentation の表が、それぞれのドキュメントの特性を非常にわかりやすくまとめています。

Tutorials How-to guides Reference Explanation
what they do introduce, educate, lead guide, demonstrate state, describe, inform explain, clarify, discuss
answers the question "Can you teach me to…?" "How do I…?" "What is…?" "Why…?"
oriented to learning tasks information understanding
purpose to allow the newcomer to get started to show how to solve a specific problem to describe the machinery to explain
form a lesson a series of steps dry description discursive explanation
analogy teaching a child how to cook a recipe in a cookery book a reference encyclopaedia article an article on culinary social history

※ 表は https://diataxis.fr/needs/#characteristics-of-documentation より引用致しました

チームでは、ドキュメントを管理するリポジトリで Diátaxis に対応する 4 つのディレクトリを作成しています。

.
├── tutorial
├── guides
├── references
└── explanation

各ディレクトリに配置されるドキュメントは以下のような構成です。

  • Tutorial
    • 着任直後に行う必要がある、開発業務のための最低限必要な情報
      • 環境構築
      • Pull Request の出し方
      • リリース方法
  • How-to Guides
    • 開発運用プロセスの中で必要になる特定のユースケースにおける手順をまとめたもの
      • サーバーログの閲覧方法
      • ローカルから開発環境の DB への接続方法
      • 負荷テストの実施方法
  • References
    • プロダクトの仕様
      • アーキテクチャ図
      • システム監視設定一覧
      • プロダクトの特定機能のインタフェース
  • Explanation
    • 機能の導入理由や歴史的背景・Architecture Decision Records のような議論や意思決定のプロセスを含むもの
      • Job 並列実行の機能提供背景
      • システム監視の構成検討
      • 2023 年度上期開発ロードマップ

フレームワークに準拠することで、ドキュメント執筆時に「何をどこに書けばいいかわからない」といった問題はほぼ解消されています。 また、各ドキュメントのスコープがシンプルになることで、それぞれのドキュメントが短く読みやすい形になるような力学も働くようになりました。

Diátaxis は今後、チーム内部ドキュメントだけでなく、データプロダクト利用者向けドキュメントにも適用していく予定です。

C4 model の採用

アーキテクチャ図と一口に言っても、説明すべき情報に合わせて適切な抽象度で構成する必要があります。 例えば、「Crois」という社内の横断データプロダクトが、どの事業でなんの用途で使われているかを非エンジニアに説明するシチュエーションを考えます。 このようなシチュエーションでは、細かい AWS のインフラ構成情報を入れたアーキテクチャ図を用いて説明しても、それは読み手にとって不要な情報です。 そういった抽象度をコントロールするモデル化をしているのが C4 model です。 C4 model では、 System Context > Container > Component > Code という異なる抽象度のアーキテクチャ図を用いて階層的に情報を管理します。 詳細については、 公式のページ https://www.infoq.com/jp/articles/C4-architecture-model/ などをご参照ください。

C4 model の実装は PlantUML Mermaid などにもありますが、今回チームで採用したのは Structurizr と呼ばれる OSS です。 こちらは C4 model の提唱者である Simon Brown さんがメンテナです。

Structurizr の導入理由は以下のとおりです。

  1. 1 つの DSL ファイルから、複数の抽象度の図を出力可能
  2. ユースケース単位で描画したいオブジェクトの取捨選択ができる
  3. AWS / GCP / Kubernetes などのアイコンをテーマとして使うことができる
  4. 社内の別組織で既に導入事例があった

12 の詳細ですが、Structurizr では複数のアーキテクチャ図を単一の DSL ファイルで管理します。 複数のアーキテクチャ図を独立して管理している場合と比べて管理コストが小さくなり、修正漏れも防ぐことができます。 4 については、 ゼクシィオンライン招待状 の開発チームで既に Structurizr を導入・運用していました。 ゼクシィオンライン招待状については以下をご参照ください。

そのため、環境構築を始めとしたコードベースを共有してもらえたことでスムーズにチームに導入できました。

Structurizr 環境構築

以下のようなディレクトリ構成です。

.
├── docker-compose.yaml
└── system-diagram
    ├── Dockerfile.plantuml
    ├── gen
    │   ├── output
    │   └── plantuml
    └── workspace.dsl

Dockerfile, docker-compose は以下のような構成です。

FROM plantuml/plantuml:latest

WORKDIR /data

# 日本語フォントのインストール
RUN apt-get update && \
    apt-get install -y wget && \
    apt-get install -y zip unzip && \
    apt-get install -y fontconfig
RUN wget https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00301.zip
RUN unzip IPAexfont00301.zip
RUN mkdir -p /usr/share/fonts/ipa
RUN cp IPAexfont00301/*.ttf /usr/share/fonts/ipa
RUN fc-cache -fv

version: "3"

services:
  build-system-diagram:
    image: structurizr/cli:latest
    volumes:
      - ./system-diagram:/usr/local/structurizr
    entrypoint: "/bin/sh -c"
    command:
      '"
        structurizr.sh validate -workspace ./workspace.dsl &&
        rm -rf ./gen/plantuml &&
        structurizr.sh export -workspace ./workspace.dsl -format plantuml -output ./gen/plantuml
      "'
  gen-system-diagram:
    build:
      context: ./system-diagram
      dockerfile: Dockerfile.plantuml
    volumes:
      - ./system-diagram:/data
    entrypoint: "/bin/sh -c"
    command:
      '"
        rm -rf ./gen/output &&
        java -jar /opt/plantuml.jar -charset UTF-8 plantuml -o "../output" "./gen/plantuml/*.puml"
      "'

下記コマンドを実行することで、workspace.dsl → PlantUML → PNG というステップで最終的な画像を出力しています。

$ docker compose run --rm build-system-diagram && docker compose run --rm gen-system-diagram

DSL と生成画像のサンプル

ここで、実際の DSL のサンプルとそこから生成される画像群を見てみましょう。前述した通り、以下の画像はすべて 1 つの DSL から出力されています。

workspace.dsl (クリックすると展開されます)
# https://github.com/structurizr/dsl/blob/master/docs/language-reference.md
workspace "SystemDiagram" {

    model {
        properties {
            "structurizr.groupSeparator" "/"
        }
        developer = Person "Crois 利用者"
        knile_user = Person "Knile 利用者" "Knile 利用者も間接的に Crois を利用している"
        crois = softwareSystem "Crois" "コンテナベースのジョブスケジューラー・ワークフローエンジン" {
            frontend = container "web frontend" "vue で書かれたフロントエンド" {
                tags "Application" "Amazon Web Services - Fargate"
            }
            backend = container "web backend" "django rest framework で書かれたバックエンド" {
                tags "Application" "Amazon Web Services - Fargate"

                workflow_manager = component "WorkflowComponent" "Workflow / Job 周辺の処理を責務とする"
                user_manager = component "UserComponent" "ユーザーのログイン周りや権限管理を責務とする"
            }
            database = container "RDB (Aurora MySQL)" "ユーザー情報や Workflow 登録情報などを管理" {
                tags "Database" "Amazon Web Services - Aurora MySQL Instance"
            }

            # relationships to/from containers
            frontend -> backend "Call REST API"
            backend -> database "データ永続化"

            # relationships to/from components
            frontend -> user_manager "Authentication/Authorization"
            frontend -> user_manager "Authentication/Authorization"
            frontend -> workflow_manager "Call Workflow/Job CRUD API"
            user_manager -> database "ユーザー情報 CRUD"
            workflow_manager -> database "Workflow/Job 関連情報 CRUD"
        }

        developer -> crois "Workflow 登録や Job 実行"

        knile = softwareSystem "Knile" "データ施策向けインフラ・CI/CD 環境を提供するプラットフォーム"
        crois -> knile "データ連携"
        knile -> crois "リソース参照・書き込み"
        knile_user -> knile "Knile 経由で Workflow 登録や Job 実行"
        
        production = deploymentEnvironment "Production" {
            deploymentNode "AWS" {
                tags "Amazon Web Services - Cloud"
                route53 = deploymentNode "Route 53" {
                    description "Highly available and scalable cloud DNS service"
                    tags "Amazon Web Services - Route 53"

                    main_domain = infrastructureNode "crois web domain" {
                        description "メインエンドポイント"
                        tags "Amazon Web Services - Route 53 Resolver"
                    }
                    api_domain = infrastructureNode "crois api domain" {
                        description "Backend API エンドポイント。利用者の CI/CD 環境からアクセスされることがある"
                        tags "Amazon Web Services - Route 53 Resolver"
                    }
                }
                availability_zone = deploymentNode "Availability Zone" {
                    tags = "Amazon Web Services - Region"
                    
                    alb = infrastructureNode "Application Load Balancer" {
                        tags "Amazon Web Services - Elastic Load Balancing Application Load Balancer"
                    }

                    deploymentNode "AWS Fargate" {
                        tags "Amazon Web Services - Fargate"
                        frontend_instance = containerInstance "frontend"
                        backend_instance = containerInstance "backend"
                    }

                    deploymentNode "Amazon RDS" {
                        tags "Amazon Web Services - RDS"
                        db_instance = containerInstance "database"
                    }

                    ecr = infrastructureNode "Container Image 管理" {
                        tags "Amazon Web Services - Elastic Container Registry"
                    }
                    cloudwatch = infrastructureNode "CloudWatch" {
                        description "ログ管理"
                        tags "Amazon Web Services - CloudWatch"
                    }
                }

                main_domain -> alb
                api_domain -> alb
                alb -> frontend_instance "ユーザー画面のリクエスト"
                alb -> backend_instance "api アクセス"
                backend_instance -> ecr "モジュールリポジトリの管理"
                backend_instance -> cloudwatch "ログ送信"
            }
        }
    }

    views {
        systemLandscape "SystemLandscape" {
            include *
            autolayout lr
            title "SystemLandscape (周辺システム連携図)"
        }

        container crois "Containers" {
            include *
            autolayout lr
            title "Container (Crois システム概要図)"
        }

        component backend "Components" {
            include *
            autolayout lr
            title "Component (Backend に焦点を絞って詳細化した図)"
        }

        deployment crois "Production" "ProductionDeployment" {
            include *
            autolayout lr
            title "Deployment (クラウドインフラまで含めた詳細図)"
        }

        dynamic backend "Worker" "Worker 管理" {
            workflow_manager -> database "Workflow 情報登録"

            autolayout lr
            title "Dynamic (特定のユースケースに関連するスコープのみ描画)"
        }

        # ref: https://github.com/structurizr/themes
        theme https://static.structurizr.com/themes/amazon-web-services-2022.04.30/theme.json
        theme https://static.structurizr.com/themes/kubernetes-v0.3/theme.json
        theme https://static.structurizr.com/themes/google-cloud-platform-v1.5/theme.json

        styles {
            element "Element" {
                shape roundedbox
                background #ffffff
            }
            element "Container" {
                background #ffffff
            }
            element "Application" {
                background #ffffff
            }
            element "Database" {
                shape cylinder
            }
            element "Person" {
                shape Person
                background #08427b
                color #ffffff
            }
        }
    }
}

チームでは、System Landscape > Container > Component という階層での情報整理に加え、DeploymentDynamic によるインフラやユースケースの可視化を行っています。

まずは、System Landscape Diagram を見ていきましょう。

SystemLandscape
System Landscape Diagram

view Block の以下のコードで出力されます。

systemLandscape "SystemLandscape" {
    include *
    autolayout lr
    title "SystemLandscape (周辺システム連携図)"
}

softwareSystemPerson のみが描画され、containercomponent は省略されます。

次に Container Diagram です。

Containers
Container Diagram

view Block の以下のコードで出力されます。

container crois "Containers" {
    include *

    autolayout lr
    title "Container (Crois システム概要図)"
}

crois softwareSystem を対象に、container 同士の関連性を描画します。

Container Diagram を更に詳細化した Component Diagram のサンプルは以下のとおりです。

Components
Component Diagram

view Block の以下のコードで出力されます。

component backend "Components" {
    include *
    autolayout lr
    title "Component (Backend に焦点を絞って詳細化した図)"
}

backend container を対象に、component まで描画しています。これにより、backend がどのような機能で構成されているか把握できます。

クラウドインフラのデプロイ構成などを描画する Deployment Diagram は以下のとおりです。

ProductionDeployment
Deployment Diagram

view Block の以下のコードで出力されます。

deployment crois "Production" "ProductionDeployment" {
    include *
    autolayout lr
    title "Deployment (クラウドインフラまで含めた詳細図)"
}

model Block で以下のように定義されている構成図を描画しています。

production = deploymentEnvironment "Production" {
    ...
}

containerInstance で、container として事前に定義しているものを描画しつつ、Container Diagram や Component Diagram のレイヤーで説明する必要がない DNS サービスやロードバランサーなどのインフラ構成はこちらで定義します。

最後に、特定のユースケースを説明する Dynamic Diagram です。

Worker
Dynamic Diagram
dynamic backend "Worker" "Worker 管理" {
    workflow_manager -> database "Workflow 情報登録"

    autolayout lr
    title "Dynamic (特定のユースケースに関連するスコープのみ描画)"
}

特定のユースケースに関係するオブジェクトのみを定義します。

以下、サンプルコードのまとめです。

  • 1 つの DSL の model Block に詳細なインフラ構成まで定義し、view Block でどの抽象度で描画するかをコントロールする
  • System Landscape → Container → Component という階層構造で出力されるアーキテクチャ図が詳細化される
  • deployment によってクラウドインフラの詳細図まで含めて描画する
  • dynamicinclude / exclude を使うことで、特定のユースケースに関連するコンポーネントだけを抽出する

システム構成が変わったり、説明すべきユースケースが出てきたときにもこの DSL のみを修正することで、出力されているアーキテクチャ図すべてに修正が反映されるため、修正漏れなどが発生しません。

改善策の実施後

Diátaxis と C4 model の採用後、以下のような状態になりました。

  • 複数のトピックが混在するドキュメントを短く簡潔なドキュメントに分割した
  • コード修正と同時に References に仕様が追加されるようになった
  • 機能開発時、 Explanation に技術選定のログや機能追加の背景の情報が追加されるようになった
  • drawio 等で個別に作成されていたアーキテクチャ図が集約された

チーム開発ドキュメントにおける課題 の解消は行われ、ドキュメントを書き始めるまでに考えることが簡易化されたことから、 今後のドキュメンテーションを効率的に行えるようになりました。

終わりに

Diátaxis と C4 model によるドキュメント・アーキテクチャ図の形式化を行うことで、人によってスキルや抽象度がブレがちなドキュメンテーション業務を改善した事例紹介でした。 現在、自チームでは、新規参画者の増加やデータプロダクト利用者増加の背景から、チームのドキュメンテーション業務に対する必要性や知見共有を通じた文化醸成の一環として エンジニアのためのドキュメントライティング の輪読会なども行っています。そういった取り組みを踏まえ、今後は内部のドキュメントだけではなく、データプロダクト利用者向けのドキュメント改善も進めていく予定です。 本ブログが、ドキュメンテーションに関して同じような悩みを抱えているチームの一助になれば幸いです。

References


  1. Knileについては以下を御覧ください

    https://speakerdeck.com/recruitengineers/mlops-kato https://speakerdeck.com/recruitengineers/cndt2021-kojisuganuma-eb8b8398-1a05-416a-bee4-af8be882af29 ↩︎

  2. Croisについては以下をご参照ください

    https://aws.amazon.com/jp/solutions/case-studies/recruit-case-study/ https://blog.recruit.co.jp/data/articles/aws_innovate_data_edition_2021/ ↩︎

加藤 広大

データエンジニア

加藤 広大

moonlander というキーボードを愛用しています