Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

モブプログラミング開発に ADR を導入した話

こんにちは、 Gunosy Tech Lab 所属の m-hamashita です。 最近は良い年明けを迎えるために dotfiles の整備をしています。好きな Neovim の plugin は reacher.nvim です。

この記事は Gunosy Advent Calendar 2022 の 20 日目の記事です。 19 日目の記事は sugiyama さんの 『Recoil を触ってみた話』 でした。

今回はモブプログラミング開発する中で ADR を導入した話をしたいと思います。

モブプログラミング開発については 8 日目の記事に詳しく書かれているので、気になる方はこちらを読んでみてください。

tech.gunosy.io

ADR とは

ADR(Architecture Decision Records)*1とは、アーキテクチャに関する意思決定を文書化するものです。 ADR を使うことで意思決定の背景や理由が残り、後から見返すことができるようになります。

我々が現在使っている ADR の構成要素は以下のようになっています*2

- ステータス
  - 下書き、提案済み、承認済み、廃棄の 4 つから選択
  - 関連する ADR がある場合は、その ADR のリンクを記載
- コンテキスト
- 決定
  - 決定とその根拠
- 影響
  - Pros
  - Cons
- コンプライアンス
  - 決定が遵守されていることを確認する方法
  - 決定の内容を見直すタイミング
- 備考
  - 著者
    - GitHub のアカウント名
  - 承認日
  - 承認者
  - 変更履歴
    - YYYY/MM/DD

導入の背景

モブプログラミングでの開発によって議論が活発になった一方で、決めたことが記録に残っておらず、あれなんだっけとなることがたびたびありました*3。 そこで、決めたことを記録し、属人化を防ぐために ADR を導入することにしました。

今日時点での ADR の運用ルールは以下のようになっています。

  • タスクの着手時、終了時に必要に応じて ADR を記述
  • モビング作業中に必要になった場合には、モビング中のメンバーで ADR を記述
  • 過去に決定した事項についても、必要と判断した場合は ADR を記述
  • テンプレートをもとに GitHub Discussions に ADR を記述

この運用ルールを採用した理由は以下のようになっています。

  • ADR は決定事項ごとに分割されているため、粒度が適切だと判断
  • ADR はフォーマットが定まっているので、比較的記述が容易
  • GitHub Discussions はバージョン管理、追跡、リンク作成などが可能なため

余談ですが、この ADR 導入についても ADR に記録しています*4

ADR を作成する workflow の導入

ADR が運用され始めたとき、以下の手順で ADR を作成していました。

  1. Discussion を作成
  2. テンプレートをコピペ
  3. Labels の設定
  4. タイトル、本文の記述

個人的に用意されたテンプレートをコピペしたり、Labels の設定をしたりするのが面倒でした。 また、タイトルに ADR 1., ADR 2., ... と連番を振るようにしているので、前の ADR の番号を確認する必要があったのも面倒だと感じていました。

そこで、これらのつらみを解消するために GitHub Actions Workflow を作成しました。

作成した workflow は以下のリポジトリに置いてあります。

github.com

使い方

まずは workflow の使い方を紹介します。

GitHub Actions から workflow を(必要であればタイトルを入力して)実行します。

そうすると、本文、タイトル、Labels が埋まった状態の Discussion が作成されます。

また、連番もきちんと振られていることがわかります。これで前の ADR の番号を確認する必要がなくなりました。

workflow の実装

実際にどのようにして ADR を作成しているかを説明します。

まず、GitHub Actions workflow を定義します。ここではタイトルを受け取って、ADR を作成する shell script を実行しています。

name: create-adr
on:
  workflow_dispatch:
    inputs:
      title:
        type: string
        description: "Title of the ADR (optional)"
jobs:
  create-issue:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Create ADR
        run: sh ./.github/workflows/create-adr.sh ${{ github.event.inputs.title }}
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ADR を作成する shell script の全貌は以下のようになります。 今回は GitHub GraphQL API を用いて、Discussion などの作成をおこなっています。

#!/bin/sh
set -eu

REPOSITORY_OWNER="m-hamashita"
REPOSITORY_NAME="create-adr-workflow"
TEMPLATE_ID=57

# get repositoryId
repository_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            id
        }
    }' --jq '.data.repository.id')

# get categoryId
category_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussionCategory(slug: "ADR") {
                id
            }
        }
    }' --jq '.data.repository.discussionCategory.id')

# increment ADR number
adr_num=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussions(first: 10, orderBy:{field:CREATED_AT, direction: DESC}) {
                nodes {
                    title
                }
            }
        }
    }' --jq '.data.repository.discussions.nodes[].title' | grep ADR | sed -e 's/ADR \([0-9]*\).*/\1/' | awk '$0>x{x=$0};END{print x+1}')

title="ADR "$adr_num". "${1:-""}
body=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussion(number: '$TEMPLATE_ID') {
                body
            }
        }
    }' --jq ".data.repository.discussion.body")

# create ADR discussion and get discussionId
discussion_id=$(gh api graphql -f query='
    mutation CreateDiscussion($title: String!, $body: String!){
        createDiscussion(input: {repositoryId: "'$repository_id'", categoryId: "'$category_id'", body: $body, title: $title}) {
            discussion {
                id
            }
        }
    }' --jq ".data.createDiscussion.discussion.id" -f "body=$body" -f "title=$title")

# get draft labelId
draft_label_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            label(name: "ADR:draft") {
                id
            }
        }
    }' --jq '.data.repository.label.id')

# add draft label to discussion
gh api graphql -f query='
    mutation($labelableId:ID!, $labelIds:[ID!]!) {
        addLabelsToLabelable(input: {clientMutationId: "", labelableId: $labelableId, labelIds: $labelIds}) {
            clientMutationId
        }
    }' -f labelableId=$discussion_id -f labelIds=$draft_label_id

repositoryId と categoryId の取得

最初に repositoryIdcategoryId をリポジトリ名から取得しています。

直接指定しても大丈夫なのですが、他のリポジトリで使おうと思ったときに手間がかかるので、リポジトリ名から取得するようにしています*5

REPOSITORY_OWNER="m-hamashita"
REPOSITORY_NAME="create-adr-workflow"
TEMPLATE_ID=57

# get repositoryId
repository_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            id
        }
    }' --jq '.data.repository.id')

# get categoryId
category_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussionCategory(slug: "ADR") {
                id
            }
        }
    }' --jq '.data.repository.discussionCategory.id')

ADR の番号の取得

次は ADR の番号を取得しています。 ここでは直近 10 件の Discussion のタイトルから ADR の番号を取得し、最大の数字をインクリメントすることで、次の ADR の番号を取得しています。

adr_num=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussions(first: 10, orderBy:{field:CREATED_AT, direction: DESC}) {
                nodes {
                    title
                }
            }
        }
    }' --jq '.data.repository.discussions.nodes[].title' | grep ADR | sed -e 's/ADR \([0-9]*\).*/\1/' | awk '$0>x{x=$0};END{print x+1}')

ADR のタイトルと本文の取得

タイトルを作成、本文をテンプレートの Discussion から取得しています。 ADR のタイトルは先程取得した ADR の番号と workflow から渡されたタイトルを連結しています。

title="ADR "$adr_num". "${1:-""}
body=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            discussion(number: '$TEMPLATE_ID') {
                body
            }
        }
    }' --jq ".data.repository.discussion.body")

Discussion の作成

前段で取得した情報を使って Discussion を作成しています。

# create ADR discussion and get discussionId
discussion_id=$(gh api graphql -f query='
    mutation CreateDiscussion($title: String!, $body: String!){
        createDiscussion(input: {repositoryId: "'$repository_id'", categoryId: "'$category_id'", body: $body, title: $title}) {
            discussion {
                id
            }
        }
    }' --jq ".data.createDiscussion.discussion.id" -f "body=$body" -f "title=$title")

下書き label の付与

作成された Discussion に下書き label を付与しています。

labelId を取得するときに label 名を ADR:draft としているので、他の label 名を使いたい場合は変更する必要があります。

# get draft labelId
draft_label_id=$(gh api graphql -f query='
    query {
        repository(owner: "'$REPOSITORY_OWNER'", name: "'$REPOSITORY_NAME'") {
            label(name: "ADR:draft") {
                id
            }
        }
    }' --jq '.data.repository.label.id')

# add draft label to discussion
gh api graphql -f query='
    mutation($labelableId:ID!, $labelIds:[ID!]!) {
        addLabelsToLabelable(input: {clientMutationId: "", labelableId: $labelableId, labelIds: $labelIds}) {
            clientMutationId
        }
    }' -f labelableId=$discussion_id -f labelIds=$draft_label_id

リポジトリ名とテンプレートの Discussion ID を変更するだけで、他のリポジトリでも使えるので気に入っています。

テンプレートが変更されない前提であれば、Discussion を作成するのではなく、markdown ファイルを作成して読むように変更しても良いかもしれません*6

ADR を導入して

ADR を導入して、以下のようなメリットがありました。

  • ADR を書いてレビューするというフローがあることで、開発メンバーのコンテキストがより共有されるようになった
  • ADR を参照することで、どのような意思決定がおこなわれたか把握できる

また、最近 ADR が役に立った事例があったのでその時のやりとりをのせておきます*7

ADR が役に立った事例

今後モブプログラミング開発が終わって通常の開発になったときに、ADR がより活用されるようになると期待しています。

おわりに

最近は弊社の他リポジトリにも ADR が導入され、今回紹介した workflow が使われていてうれしいです。

明日は yamaYu さんの『【サイエンスで機会をつくる】有志による社内勉強会の紹介』です。 自分が参加している勉強会がたくさん紹介される予定です!お楽しみに!

*1:Michael Nygard 氏によって紹介 https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions

*2:ソフトウェアアーキテクチャの基礎という本を参考にしています

*3:グラウンドホッグデーアンチパターンに近い状態に陥っていました

*4:「ADR 0. ADR の導入」という ADR をはじめに書きました

*5:リポジトリ名は自明ですが、ID は R_kgDOHOGEg のようなフォーマットであり、直接指定する場合も同様の処理をおこなう必要があります

*6:複数リポジトリに展開するときの手間が減ります

*7:完全に忘れていても大丈夫なのはとてもありがたいですね