コネヒト開発者ブログ

コネヒト開発者ブログ

iOS/Android 開発Tips共有会 potatotips #86 を開催しました

こんにちは!iOSエンジニアのyoshitakaです。

先日コネヒト主催でiOS/Android 開発Tips共有会 potatotips #86 を開催しました!

potatotips.connpass.com

前回コネヒトで主催した際のブログがこちらです。

tech.connehito.com

今回はオンライン/オフラインのハイブリッド開催という形を取りました。

現地参加22名、オンライン視聴も常時30名以上とたくさんの方に参加していただき感謝です。

会場の雰囲気

登壇者のスライドを一部ご紹介させていただきます。(当日xでも紹介しておりました

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

コネヒトからもiOSとAndroidでそれぞれ1名LTさせていただきました!

www.docswell.com

当日は概ねタイムライン通りに進めることができました。

登壇された方々にはスムーズな運営にご協力頂きありがとうございました!

LT発表後の懇親会も盛り上がっておりました🎉

引き続き勉強会を開催していきたいと思っておりますので、コラボして頂ける企業様がございましたらぜひお声がけ頂けると嬉しいです!

Draft Releaseデプロイフローを作成してみました

こんにちは。サーバーサイドエンジニアの岡田です

みなさんはどんなデプロイフローを採用していますでしょうか?今回は自分が取り組んでいたデプロイフローのちょっとした改善を皆さんに紹介したいと思います!

きっかけ

現状コネヒトではgdpを使ったデプロイ方法を採用しており、手元のコマンドからデプロイするのが標準になっています。

tech.connehito.com

元々自分はgit-pr-releaseを使ったワンクリックデプロイを以前の現場で使っており、手元でコマンドを打たなければいけないのが地味にめんどうだなあと感じていました。 そこでワンクリックでもっとラクしてデプロイしたい!と思ったのが今回のデプロイフローを考案したきっかけになります!

それ以外にも手元のコマンドからデプロイする事に感じていた課題感

  • 手元でコマンド打つ場合、打ち間違いやコマンド履歴等から意図せずデプロイしてしまう可能性
  • devデプロイ〜本番リリースの間(ビルドの7〜8分)地味にブランチを切り替え辛い(リリース前にmainブランチにcheckoutが必要なため)
  • 新しい人が入社してきた場合にgdp&hubのインストールと設定が必要になる
  • 本番デプロイ後にリリースノートを公開(gdp publish)するのを忘れてしまう

そしてこのデプロイフローが生まれた背景

当初はgit-pr-releaseを導入すれば簡単にワンクリックデプロイ出来るようになる!と考えていました。しかしいざgit-pr-releaseを導入しようと調べてみると、難しい背景がいくつか見つかりました。

ちなみにgit-pr-releaseとは

git-pr-releaseとは、本番環境向けの対象ブランチへのリリースPRを作成&リリース内容を一覧にして作成してくれるgemライブラリです。導入も簡単なので使える環境であればぜひ使ってみて欲しいです

こちらの記事が非常に参考になります!

songmu.jp

まずgit-pr-releaseが想定しているブランチモデルは

  • 本番用のmainブランチと開発用のdevelopブランチが並走する形で、developブランチからfeatureブランチを切ってmainブランチへマージしていく、いわゆるgit flowの簡易版の様なフローが前提
  • mainブランチへのマージが本番デプロイへのトリガーとして想定されている
  • 環境構成:本番環境(main)、dev環境(develop)

git-pr-releaseが想定しているブランチモデル

対してコネヒトでは

  • GitHub Flowを踏襲しており、本番用のmainブランチからfeatureブランチを切ってmainブランチへマージしていくフローになっている
  • 最新のmainブランチから切ったtagのpushが本番デプロイへのトリガーになっている
  • 環境構成:本番環境(最新のtag)、dev環境(featureブランチ)

コネヒトのブランチモデル

なのでもしgit-pr-releaseを採用する場合はブランチ戦略自体を見直す必要があり、流石にそれは影響範囲が大きすぎるかつ現実的ではありませんでした。そこで今あるブランチ戦略を変えずにもっと簡単にデプロイを実現する方法として考えたのがこの「Draft Releaseデプロイフロー」です。

そして実際にできたデプロイフローがこちら

いつものようにPRをマージします

Slackにdev環境へのデプロイ完了とドラフトリリースの作成が通知されます

作成されたドラフトリリースを

Publishすると

tagが切られ本番デプロイが走ると言った感じです

ビフォーアフター

既存のgdpを使ったデプロイフロー

  1. mainブランチからfeatureブランチを切る
  2. feature PRをmainブランチへマージ(dev環境へデプロイ)
  3. dev環境へのデプロイが完了したら、手元でgdpコマンドからタグを切ってpush
    1. 手元のmainを最新化(git checkout main && git pull origin main && git pull --tags)
    2. リリース内容確認(gdp deploy -d)
    3. タグを切って本番デプロイ(gdp deploy)
  4. 本番環境へのデプロイが完了したら、手元でgdpを使ってリリースノートを公開
    1. リリースノート内容確認(gdp publish -d)
    2. リリースノート公開(gdp publish)

デプロイフローBefore

今回作成したDraft Releaseデプロイフロー

  1. mainブランチからfeatureブランチを切る
  2. feature PRをmainブランチへマージ(dev環境へデプロイ)
  3. dev環境へのデプロイが完了したら、GitHub上から下書きリリースノートの内容を確認し、問題なければ公開(合わせてタグも切られるのでそのまま本番デプロイ)

デプロイフローAfter

改善ポイント

  • 手元でコマンド打つ場合、打ち間違いやコマンド履歴等から意図せずデプロイしてしまう可能性
    • GUI上から確認できるので、意図せずデプロイしてしまうといったことを防げる
  • devデプロイ〜本番リリースの間(ビルドの7〜8分)地味にブランチを切り替え辛い(リリース前にmainブランチにcheckoutが必要なため)
    • devデプロイ後は別のブランチに切り替えてすぐ作業の続きができるように
  • 新しい人が入社してきた場合にgdp&hubのインストールと設定が必要になる
    • 今回の対応で不要に
  • 本番デプロイ後にリリースノートを公開(gdp publish)するのを忘れてしまう
    • 必ずリリースノートが作成されるフローに

実際のコード

呼び出し元

ドラフトリリース作成ワークフロー

下記コードを各PJのリポジトリに展開するだけでドラフトリリースが作成されます!

on:
  push:
    branches:
      - main

permissions:
  contents: write

jobs:
  deploy:
    # デプロイ処理
    ...

  create_draft_release:
    if: ${{ success() }}
    needs: [ deploy ]
    uses: {Reusable Workflow用リポジトリ}/reusable-workflow/.github/workflows/create_draft_release.yml@main
    secrets:
      slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}

呼び出される側

ドラフトリリース作成ワークフロー

ドラフトリリース作成actionの実行と、Slack通知を行っています。

name: Create Draft Release
on:
  workflow_call:
    secrets:
      slack-webhook-url:
        required: true

jobs:
  start:
    runs-on: ubuntu-latest
    steps:
      - name: Action create-draft-release
        id: create-draft-release
        uses: {Reusable Workflow用リポジトリ}/reusable-workflow/.github/actions/create-draft-release@main

      - name: Notify slack of create draft release
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          fields: repo,workflow
          author_name: ${{ github.actor }}
          text: |
            ドラフトリリースが作成されました。<${{ steps.create-draft-release.outputs.draft-release-link }}|${{ steps.create-draft-release.outputs.draft-release-tag }}>
            <${{ github.server_url }}/${{ github.repository }}/releases|リリース一覧>
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook-url }}

実装時のポイント

Reusable Workflowとはいわゆる再利用可能なワークフローの事で、各リポジトリで共通のワークフローを実行することができます! 作成方法はシンプルでon.workflow_callを定義したymlファイルを作成するだけです。

on:
  workflow_call:

今回Reusable Workflowとして実装した理由ですが、Reusable Workflowと後述のaction.ymlの大きな違いとしては実行環境に左右されるかどうかだと考えています。

Reusable Workflowの場合はワークフロー(1つ以上のジョブ)でしか実行できないので前後の処理や実行環境に左右されません。逆にaction.ymlではジョブの一ステップとして実行されるので、必要であれば actions/checkout@v2actions/setup-node@v2 など使って実行環境を整える必要があります。

そして今回のDraft Release作成処理は完全に処理が独立しているので、リポジトリ毎の実行環境に左右されずに横展開しやすいReusable Workflowで実装しました

Reusable Workflow使用時の注意点としては実行されるコードはあくまで呼び出し元で実行されるので

  • actions/checkout を使用する場合は呼び出し元のブランチにチェックアウトされる
  • Reusable Workflowのリポジトリ内にあるファイルは参照できない

と言った点に注意が必要です!上記点を解決するために次のaction.ymlへとつながります

詳しくは公式ドキュメントを確認してください!

docs.github.com

ドラフトリリース作成処理本体

こちらがメインのドラフトリリース作成処理です。実行している処理はシンプルにGitHub CLIを使ったリリースノートの作成と削除を行なっています。(削除はリリースノートの自動生成を使いたいため一度削除→再作成しています)

name: Create Draft Release Action
description: Create draft release with calver tag

outputs:
  draft-release-link:
    description: Create draft release link.
    value: ${{ steps.create-draft-release.outputs.draft-release-link }}
  draft-release-tag:
    description: Create draft release tag.
    value: ${{ steps.create-draft-release.outputs.draft-release-tag }}

runs:
  using: "composite"
  steps:
    - name: Create Draft Release
      id: create-draft-release
      env:
        TZ: "Asia/Tokyo"
        GH_TOKEN: ${{ github.token }}
      shell: bash
      run: |
        # 最新のタグ取得
        latest_tag=$(gh release list --repo '${{ github.repository }}' | awk -F'\\t' '$2=="Latest" { print $3 }')
        echo "latest_tag:${latest_tag}"

        # calver形式での次バージョンのタグ取得
        next_tag=$(/bin/bash ${{ github.action_path }}/calver.sh $latest_tag)
        echo "next_tag:${next_tag}"

        # draft release取得
        draft_tag=$(gh release list --repo '${{ github.repository }}' | awk -F'\\t' '$2=="Draft" { print $3 }' | head -n 1)
        echo "draft_tag:${draft_tag}"

        # draft release が既に存在していれば、削除してから再作成(リリースノートの自動生成を使いたいため)
        if [ "$draft_tag" != "" ]; then
          gh release delete $draft_tag --repo '${{ github.repository }}' -y
        fi
        draft_release_link=$(
          gh release create $next_tag \\
            --repo '${{ github.repository }}' \\
            --title "Release ${next_tag}" \\
            --latest --draft --generate-notes
        )
        echo "draft-release-link=${draft_release_link}" >> "${GITHUB_OUTPUT}"
        echo "draft-release-tag=${next_tag}" >> "${GITHUB_OUTPUT}"
        echo $draft_release_link

実装時のポイント

action.ymlとはいわゆるメタデータ構文と言われているもので、ワークフロー内の処理を切り出したり共通化することができます! 普段よく使っているようなactions/checkoutのようなアクションを作成できるイメージです。作成方法はこちらもシンプルでaction.ymlまたはaction.yamlのファイル名で作成するだけです

action.ymlではパブリックリポジトリにあるアクションはもちろん、同一リポジトリ内のアクションからプライベートリポジトリのアクションも実行可能です。

    # パブリックのaction
    uses: actions/create-draft-release@main
    # 同一リポジトリ内のaction
    uses: ./.github/actions/create-draft-release
    # プライベートリポジトリのaction
    uses: {owner}/{repo}/.github/actions/create-draft-release@main

詳しくは公式ドキュメントと

docs.github.com

こちらの記事が参考になりました!

developer.mamezou-tech.com

calver形式でのタグ作成シェルスクリプト

このシェルスクリプトを作成した背景ですが、前提としてコネヒトではcalver形式でのバージョン管理を採用しています。

タグ作成にあたってできるだけ既存と同じようなcalver形式のタグを作成する必要があったのですが、現状これと同じようなタグを作成するツールが見つかりませんでした。そこで元々のgdpコマンドで行なっていたタグ作成ロジックをそのままコピーしてきて、このシェルスクリプトを作成することにしました。(GitHub Actionsとの相性や実行の手軽さも考えてシェルスクリプトにしました)

#!/bin/bash
set -eu

##############################
## Get next calver
## @ref <https://github.com/Connehito/gdp/blob/main/format.go>
##############################
today=$(date +'%Y%m%d')
regexp="(.*)([0-9]{8})\\.(.+)"

version=${1:-$today}

if [[ $version =~ $regexp ]]; then
    if [[ $today = ${BASH_REMATCH[2]} ]]; then
        minor=${BASH_REMATCH[3]}
        next=$(($minor + 1))
        echo "${BASH_REMATCH[1]}${today}.${next}"
        exit 0
    fi
    echo "${BASH_REMATCH[1]}${today}.1"
    exit 0
fi
echo "${today}.1"
exit 0

おわりに

同じような環境でデプロイフローに悩みを感じでいる方の参考になれば嬉しいです

AWSコスト削減の一環でecrmを使ってECRの不要イメージを削除した話

こんにちは、コネヒト プラットフォームグループの@yosshiです。

昨今、円安の進行によりAWSのコスト増が無視できない状況となっています。
このような状況下で、コスト削減関連のイベントに注目が集まるなど、コスト削減への関心が徐々に高まってきているように感じます。

弊社でもさまざまな方法でコスト削減施策を進めており、今回はその一環で「ecrm」というツールを使用しECRイメージを削除した話をしたいと思います。

弊社では、コンテナのオーケストレーションにAmazon ECSを主に使用しています。 ECSにタスクをデプロイする場合は、イメージをビルドした上でAmazon ECRにpushし、そのECRのイメージを使用しタスクを起動しています。 ECRはイメージが増えれば増えるほど、容量に応じてコストがかかるので、この機会に対応したいと考えていました。

ECRで発生するコスト

ECRではデータストレージの容量に応じて料金がかかります。
ap-northeast-1(アジアパシフィック(東京))のリージョンでは、GB/月当たり 0.10USDの料金が発生します。
※2024/01/17時点の情報。ECRでは上記とは別に転送コストなどが発生します。

参考:https://aws.amazon.com/jp/ecr/pricing/

ecrmとは?

ecrmはAmazon ECRから不要なイメージを安全に削除するOSSです。
github.com

こちらのブログに詳細が書いてありますので、詳しくはこちらをご確認ください。
techblog.kayac.com

ECRには元々ライフサイクルポリシーという機能があり、世代数やイメージプッシュからの日数に基づき古いイメージを自動的に削除することができるのですが、「現在のECSタスクで使用しているもの」という観点でチェックをしてくれるわけではないので、使用中のイメージを削除してしまうリスクがあると感じていました。

そこで調べていたところecrmの存在を知り、試してみることにしました。

試してみる

インストール(macOS で brew を使う場合)

$ brew install fujiwara/tap/ecrm

イメージ削除にあたり、削除対象を決めるための設定ファイルが必要になります。
AWSアカウントのcredentialを適切に環境変数などで設定した状態で 以下コマンドを実行することで、アカウントに存在するECRやECS、Lambdaのリソースを元に、設定ファイル(ecrm.yaml)を自動生成してくれます。

$ ecrm generate

実行すると設定ファイル(ecrm.yaml)が作成されました。
クラスタやタスク定義はワイルドカードのパターンで指定でき、prefixが記号([_/-])で区切れてまとめられそうなものは、適宜まとめたパターンで自動生成してくれます。

ecrm.yaml

clusters:
  - name_pattern: <cluster1>-*
  - name_pattern: <cluster2>-*
    ・
    ・
task_definitions:
  - name_pattern: <task_definition1>-*
    keep_count: 5
  - name_pattern: <task_definition2>-*
    keep_count: 5
    ・
    ・
lambda_functions:
  - name_pattern: <lambda_function1>-*
    keep_count: 5
    keep_aliase: true
  - name_pattern: <lambda_function2>-*
    keep_count: 5
    keep_aliase: true
    ・
    ・
repositories:
  - name_pattern: <repository1>-*
    expires: 30d
    keep_count: 5
    keep_tag_patterns:
      - latest
  - name_pattern: <repository2>-*
    expires: 30d
    keep_count: 5
    keep_tag_patterns:
      - latest
    ・
    ・

この設定ファイル(ecrm.yaml)を弊社の都合に合わせて修正していきます。

設定した条件 (削除対象外とするもの)

  • 現在使用中のイメージ
    • ECSクラスタで現在実行中のタスクに含まれるイメージ
    • ECSサービスで指定されているタスク定義に含まれるイメージ
  • ECSタスク定義で指定されているイメージ(タスク定義最新リビジョンから5世代分
  • Lambda関数で指定されているイメージ(Lambda最新バージョンから5世代分)
  • 90日以内に作成されたイメージ
  • タグ名にlatest, releaseがついているイメージ ※弊社ではリリース対象に左記のようなタグ名をつけています

上記の下線部は可変にできるので、適宜適切な内容に置き換えてください。 設定ファイル(ecrm.yaml)は以下のようになりました

clusters:
  - name_pattern: <cluster1>-*
  - name_pattern: <cluster2>-*
    ・
    ・
task_definitions:
  - name_pattern: <task_definition1>-*
    keep_count: 5
  - name_pattern: <task_definition2>-*
    keep_count: 5
    ・
    ・
lambda_functions:
  - name_pattern: <lambda_function1>-*
    keep_count: 5
    keep_aliase: true
  - name_pattern: <lambda_function2>-*
    keep_count: 5
    keep_aliase: true
    ・
    ・
repositories:
  - name_pattern: <repository1>-*
    expires: 90d
    keep_count: 5
    keep_tag_patterns:
      - *latest*
      - *release*
  - name_pattern: <repository2>-*
    expires: 90d
    keep_count: 5
    keep_tag_patterns:
      - *latest*
      - *release*
    ・
    ・

設定ファイル(ecrm.yaml)の編集が完了したら、どの程度イメージが削減できそうか確認します。

削除対象は以下コマンドで確認できます。

$ ecrm plan

実行したところ以下のようになりました。

REPOSITORY     |   TYPE    |    TOTAL     |    EXPIRED    |    KEEP      
---------------------------+-------------+--------------+---------------+--------------
<repository1>  | Image     | 256 (38 GB)  | -187 (27 GB)  | 69 (11 GB)      
<repository2>  | Image     | 227 (44 GB)  | -209 (41 GB)  | 18 (3.2 GB)  
<repository3>  | Image     | 92 (13 GB)   | -82 (12 GB)   | 10 (1.5 GB)  
<repository4>  | Image     | 109 (307 GB) | -57 (181 GB)  | 52 (126 GB)  
<repository5>  | Image     | 63 (67 GB)   | -52 (53 GB)   | 11 (14 GB)   
<repository6>  | Image     | 251 (2.2 GB) | -236 (2.0 GB) | 15 (190 MB)  
<repository7>  | Image     | 771 (67 GB)  | -760 (66 GB)  | 11 (862 MB)  
<repository8>  | Image     | 150 (75 GB)  | -135 (67 GB)  | 15 (7.8 GB)  
<repository9>  | Image     | 65 (22 GB)   | -60 (20 GB)   | 5 (1.7 GB)   
<repository10> | Image     | 138 (29 GB)  | -128 (27 GB)  | 10 (2.1 GB)  
<repository11> | Image     | 116 (21 GB)  | -104 (19 GB)  | 12 (2.2 GB)  
<repository12> | Image     | 927 (129 GB) | -910 (126 GB) | 17 (2.8 GB)  
<repository13> | Image     | 284 (55 GB)  | -231 (44 GB)  | 53 (11 GB)   
<repository14> | Image     | 57 (546 MB)  | -21 (186 MB)  | 36 (360 MB) 

トータルのイメージに対して削除対象がどの程度あるか(EXPIRED)、残るイメージがどの程度あるか(KEEP)がわかります。

実際に削除を実行する際には以下コマンドを実行します。

$ ecrm delete

実行した後、実際に削除されたかどうかを確認したいので再度ecrm planを実行します。

$ ecrm plan

実行結果

REPOSITORY       |    TYPE     |    TOTAL    | EXPIRED |    KEEP      
-----------------+-------------+-------------+---------+--------------
<repository1>    | Image       | 69 (11 GB)  |         | 69 (11 GB)   
<repository2>    | Image       | 18 (3.2 GB) |         | 18 (3.2 GB)  
<repository3>    | Image       | 10 (1.5 GB) |         | 10 (1.5 GB)  
<repository4>    | Image       | 52 (126 GB) |         | 52 (126 GB)  
<repository5>    | Image       | 11 (14 GB)  |         | 11 (14 GB)   
<repository6>    | Image       | 15 (190 MB) |         | 15 (190 MB)  
<repository7>    | Image       | 11 (862 MB) |         | 11 (862 MB)  
<repository8>    | Image       | 15 (7.8 GB) |         | 15 (7.8 GB)  
<repository9>    | Image       | 5 (1.7 GB)  |         | 5 (1.7 GB)   
<repository10>   | Image       | 10 (2.1 GB) |         | 10 (2.1 GB)  
<repository11>   | Image       | 12 (2.2 GB) |         | 12 (2.2 GB)  
<repository12>   | Image       | 17 (2.8 GB) |         | 17 (2.8 GB)  
<repository13>   | Image       | 53 (11 GB)  |         | 53 (11 GB)   
<repository14>   | Image       | 36 (360 MB) |         | 36 (360 MB)  

EXPIREDの列がブランクとなり、対象の不要イメージが削除されたことがわかります。

結果

今回のケースでは685GBのイメージの削減に成功しました。 現時点のAWSのコストベース(GB/月当たり 0.10USD)で換算すると、月間68.5USDの削減となります。
※2024/01時点のAWS ECRのコスト

感想

今回はecrmを使用してECRイメージを削除した話をしました。

金額としては小さいかもしれませんが、もともと使っていないイメージに対する余分なコストだったので、このタイミングで簡単に削減できてよかったなと思います。

コスト削減に注目が集まっている状況だと思うので、各社のコスト削減の参考になれば幸いです。

今回は単発でイメージの削除を行いましたが、今後は自動で定期実行する仕組みなども合わせて検討していきたいと考えています。

2023年のコネヒト開発組織を振り返る

こんにちは。CTOの永井(shnagai)です。

早いもので今年も残すところ数日ですね。

アドベントカレンダー最終日ということで、技術的なトピックやチームでの工夫はこれまでの記事でみんなが思う存分書いてくれたので、今回は2023年のコネヒト開発組織の1年を自分の視点で振り返っていこうと思います。

この記事は、コネヒトAdvent Calendar2023の25日目の記事です。

adventar.org

開発組織のアップデートと特に目立った技術的なトピックという構成で書いてます。

開発組織のリフレーミングと頼れる多くの仲間のジョイン

FY23のスタートに合わせて、開発組織のリフレーミングを行いました。

それぞれのチームの関係を可視化

期初のタイミングでチームトポロジーを参考に、それぞれのチームの構造を改めて可視化しました。

自分はチームトポロジーの、ストリームアラインドチームが出す価値の総量が開発組織のバリューという考えに非常に感銘を受けており、プロダクトの先にいるユーザーファーストの考えをうまく組織に当てはめた考えだなと思っています。

ママリや自治体向け事業等の事業毎にストリームアラインドチームを配置して、事業部と密に連携しながら開発するスタイルをコネヒトでは採用しています。

コンプリケイテッドサブシステムチームは技術的難易度の高い領域を受け持ち、プラットフォームチームでは、ストリームアラインドチームの認知負荷を減らしプラットフォームエンジニアリングでプロダクトの価値最大化に貢献することをミッションにしています。

下記は、期初戦略で全社に説明した資料の一部です。

※現在はメンバー増によりストリームアラインドが増えています。

ピープルマネジメントをEMに集約 〜エンジニアがよりプロダクト・事業に集中出来る体制へ〜

元々コネヒトでは、各開発グループにグループリーダー(以後、GL)を配置し、そのGLがチームのマネジメント4象限(ピープル/プロダクト/プロジェクト/テクノロジー)を全て担う構造にしていました。

だいたい2,3名のチームにそれぞれGLがいる構造だったのですが、今の組織フェーズ的にはエンジニアがよりプロダクトや事業に集中する時間を作るほうが良いと判断して、ピープルマネジメント業務をチーム横断でEM 2名に集約しました。

全てがうまくいっているわけではありませんが、ピープルマネジメントを集約したことでメンバーからはよりプロダクトや事業に集中出来ているというフィードバックをもらっており、一定うまくいっている施策だなと思っています。

制度やロールは常に見直しながらアップデートしていくのが良いと思っており、これからも組織拡大に合わせて柔軟に見直していき、アジリティの高い開発が出来るようにしていきたいと思っています。

きれいなことばかり書きましたが、テックリードという役割を設けるトライもしたのですが、中々イメージの言語化や定義が追いつかずに、役割を担ってくれたメンバーと対話しながら、半年でまた別のロールにしたりと頼れる同僚とともに試行錯誤しながらやっています。

3人に1人を目指して 多くの仲間のジョイン

今年は、7名の新しいエンジニアがコネヒトにジョインしてくれました。

コネヒトでは、テックビジョンにおいて「3人に1人」という戦術を掲げており、会社としてやりたい事業を全て実現し、テクノロジーやエンジニアリングの恩恵を社内も受け、そしてエンジニア以外でもテクノロジーを使いこなせるような世界を目指しています。

今年ジョインしてくれた仲間の職種も多岐に渡るのですが、それぞれが所属するチームや開発組織でバリューを発揮してくれており全体として活気づいています。

社内のエンジニア比率が高まっていく中で、生成AIの技術革新もあいまってテックビジョンに掲げる世界をより実現フェーズに持っていくために新たな動きも走り始めています。

全社向けに生成AIの活用とテクノロジーを利用した業務改善を推進する「Run with Tech 」プロジェクトで、こちらは、また、どこかの機会で紹介出来ればと思っています。

続いて、今年特に印象的に残った技術的なトピックについても紹介していければと思います。

検索システムの内製化完了

今年技術的なトピックで一番大きなトピックといえば検索システムの内製化が完了したことがまず挙げられます。

これまで、SaaSの検索システムを内部のAPIから呼び出す形式で使っていたものを、フルリプレイスする形で0から検索システムを構築しました。

時系列で見る検索システム内製化の軌跡

  • 2021年10月 構想開始
  • 2022年3月 経営承認
  • 2022年4月 開発開始
  • 2022年8月 最初の機能(新着質問順)のリプレイス完了
  • 2023年6月 全ての機能のリプレイス完了

主な狙いは、検索システムを内部で持つことによるユーザー体験の強化とSaaS入れ替えによる費用削減の2点でした。

時系列で書くとすんなり言ったように見えますが、すべての機能において既存とのABテストを行いママリにおける検索体験のユーザー毀損がないことを細かく確認しながらリプレイスを行いました。

新着順、人気順、サジェスト、関連キーワードと多岐に渡る機能のABテストはSaaS提供と同品質をゴールにしている関係で中々にヒリヒリするもので、メインで担当していた同僚達のやり切る力には脱帽です。

技術的な要素を少し解説すると、検索システム内製化には下記技術要素があります。

  • 内部APIから呼ばれる検索API(Go)
  • 検索エンジンにはOpenSearchを採用
  • 技術的難易度が一番高かったデータ同期の仕組みにはAWS GlueとStepFunctionsでニアリアルタイムな基盤を構築   tech.connehito.com

担当したメンバーは全員検索システムは未経験だったので、みんなでペンギン本を読んで輪読会をしたり、壁にぶつかるたびに議論して一歩ずつ前に進んでいきました。私自身もメンバーとして当初コミットしていて、検索システムは奥深くエンジニアとしてまた一つ成長できた良い機会だったと感じています。

tech.connehito.com

もちろん、リプレイスをして終了ではなく、ようやく検索システムをママリの武器に出来るスタートラインに立てたので、昨今のLLM全盛の時勢も鑑みながら、キーワード検索にとらわれずよりユーザーにとってママリの検索がより使いやすいものであり続けられるように技術的な検証や新たなトライも既に始まっています。

tech.connehito.com

「終わらせることを始めよう」を合言葉に最終的に3ヶ月計画を前倒しし、検索システム内製化という成果に対して社内表彰もされたプロジェクトとなりました。

ユーザーへの価値提供という点では、まさにこれから真価を問われることになりますが、テクノロジーでプロダクトを伸ばす先端事例になるような取組みなので今後がより楽しみです。

Let’s Go

コネヒトのテックビジョンでは、「バックエンドのシステムにGoを積極的に導入する」という戦略を掲げていおりその戦術名が「Let's Go」です。

これまでも、前述の検索APIしかり新規のシステムでのGoの採用は何度か行ってきました。それらを通しての、組織としての知見の習得はそこそこ進んできたかと感じています。

このLet’s Goの作戦は、aboyがボールを持って中心に進めてくれているのですが、今年の大きな進歩として、既存のPHPで書かれたコードベースをGoに置き換えるというプロジェクトが走り出しました。

まずは、Goの特徴である並列処理やワーカー実装のしやすさを活かせる非同期のバッチ処理のリプレイスを実施していますが、メインシステムを適材適所でGoに置き換える動きがスタートしているのは開発組織として大きな一歩だなと感じています。

中期戦略では、2024年中にバッチ処理を全てGoに置き換えるというチャレンジングな目標を掲げており、その実現に向けて日々開発を進めています。

また、Let’s Go Talkという社主催のイベントも定期的に実施しており、Go好きがゆるく繋がれるようなコミュニティも作っていきたいと思っているので興味のある方は是非ご参加ください。

コネヒト/Connehito Inc. - connpass

まとめ

2024年も引き続きプロダクトや事業を伸ばしていくことに注力しつつ、テックビジョンに掲げる「Beyond a Tech Company」の実現に向けてCTOというラベルを活かして色々と策を打っていこうと思います。

テックビジョンの存在が羅針盤からより会社の中で身近なものになっていけば、コネヒトとしてユーザーや社会に約束するありたい世界観の実現に近づいていくと自分は信じています。

最後にですが、エンジニアはもちろん、幅広い職種で採用もしていますので興味持たれた方は是非ご応募いただきカジュアルにお話出来るとうれしいです。

hrmos.co

trivyとGithub Actionsを使用しTerraform設定ファイルのセキュリティスキャンを実行する仕組みを作りました

この記事はコネヒトアドベントカレンダー21日目の記事です。

コネヒト Advent Calendar 2023って?
コネヒトのエンジニアやデザイナーやPdMがお送りするアドベント カレンダーです。
コネヒトは「家族像」というテーマを取りまく様々な課題の解決を 目指す会社で、
ママの一歩を支えるアプリ「ママリ」などを 運営しています。

adventar.org

はじめに

コネヒトのプラットフォームグループでインフラ関連を担当している@yosshiです。 今年の7月に入社してから早いもので半年が経ちました。時が経つのは本当に早いですね。

今回のブログでは、セキュリティスキャンツールであるtrivyを使って、自動的にIaC (Infrastructure as Code)スキャンを実行する仕組みを構築した話をしたいと思います。

弊社ではインフラ構成をTerraform利用して管理するようにしており、それをモノレポの構成で運用しています。

インフラリソースの作成・変更・削除をする際には必ず相互レビューを必須としているものの、人的なチェックのみに依存しているためファイルの設定ミスやセキュリティ上の見落としが潜在的なリスクとなっていました。

これらを事前に検知するツールを調べていたところtrivyの存在を知り、今回導入に至りました。

trivyとは

  • コンテナイメージやアプリケーションの依存ライブラリ・OSのパッケージなどを迅速にスキャンし、セキュリティリスクを効率的に検出するセキュリティツールです。
  • 当初はコンテナのセキュリティ問題に焦点を当てたツールとして開発されたようですが、後にTerraformやKubernetesなどの設定ファイルのチェック機能も追加されました。
  • 設定ファイルのチェックでは、設定ミスやセキュリティのベストプラクティスに沿っていない構成などを検知することができます。

参考:https://github.com/aquasecurity/trivy

(参考)スキャン可能な対象 2023/12時点

  • コンテナイメージ
  • ファイルシステム
  • リモートGitリポジトリ
  • 仮想マシンイメージ
  • Kubernetes
  • AWS

(参考)検出可能な内容 2023/12時点

  • OSパッケージとソフトウェア依存関係(SBOM)
  • 既知の脆弱性(CVE)
  • IaCの問題と設定ミス
  • 機密情報と秘密
  • ソフトウェアライセンス

上記の通りtrivyでは、Terraformのコードだけでなくさまざまなセキュリティスキャン行うことができます。

trivy自体の詳しい説明はここでは割愛するので、詳しくは公式サイトや他の方の記事などをご確認いただけると幸いです。

Github Actionsでの実装

では早速ですが実装内容の説明に移りたいと思います。 今回はTerraformコードのスキャンをCIに組み込んでいます。 弊社では CIツールとしてGithub Actionsを利用しているため、今回もこちらを利用します。

スキャン実行は、trivy公式で用意しているGithub Actions用のツール(tricy-action)があるので、こちらをそのまま利用しています。

背景

まず、前提条件となる弊社のディレクトリ構造を説明します。

弊社では、サービスで共通利用するリソース(base_system)と各サービスで利用するリソース(product_system)とでディレクトリを分けており、 product_sytem以下にはサービスごと関連するリソースが紐づいています。以下のようなイメージです。

.
├── base_system
│   ├── common
│   │   └── terraform
│   ├── privilege
│   │   └── terraform
│   .
│   .
└── product_system
    ├── (サービス1)
    │   └── terraform
    ├── (サービス2)
    │   └── terraform
    .
    .
    .

実装方針と内容

実装したGithub Actionsのコードは以下の通りです。

主に以下のことをやっています。

  1. シェルスクリプト(sync-updated-dirs-to-work-dir.sh)の実行
    • mainブランチとプルリクエスト中のブランチのコードの差分を検知
    • 差分となっているディレクトリを作業用のディレクトリに同期
  2. 作業ディレクトリに対してスキャン実行
  3. 検出されたスキャン結果をPRのコメントに残す。

Github Actionsのコードは以下の通りです。

name: trivy-scan

on:
  pull_request:
    types: [opened, reopened, synchronize]

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  trivy_scan:
    name: Run Trivy Scan
    runs-on: ubuntu-latest
    steps:
      - name: Clone repo
        uses: actions/checkout@v4

      - name: fetch origin/main for getting diff
        run: git fetch --depth 1 origin $GITHUB_BASE_REF

      - name: Sync Updated Dir to Work Dir
        env:
          GITHUB_TOKEN: ${{ secrets.github_token }}
        run: |
          bash utils/scripts/sync-updated-dirs-to-work-dir.sh

      - name: Trivy Scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'config'
          severity: 'HIGH,CRITICAL'
          scan-ref: scan_work_dir
          output: trivy-scan-result.txt

      - name: Format Trivy Scan Result
        run: |
          if [ -s trivy-scan-result.txt ]; then
            # ファイルに内容がある場合
            echo -e "## 脆弱性スキャン結果\n<details><summary>詳細</summary>\n\n\`\`\`\n$(cat trivy-scan-result.txt)\n\`\`\`\n</details>" > formatted-trivy-result.md
          else
            # ファイルが空の場合
            echo -e "## 脆弱性スキャン結果\n脆弱性が検知されませんでした。" > formatted-trivy-result.md
          fi

      - name: Comment PR with Trivy scan results
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          recreate: true
          GITHUB_TOKEN: ${{ secrets.github_token }}
          path: formatted-trivy-result.md

詳しく見ていきます。

まずは変更差分となったディレクトリを調べるため、Github Actionsの中でシェルスクリプトを実行しています。

ここでは一時的なディレクトリを用意し、プルリクエスト上で変更が発生したディレクトリの内容を作業用のディレクトリに同期するという作業を行なっています。

Github Actionsの関連部分は以下です。

- name: Sync Updated Dir to Work Dir
   run: |
     bash utils/scripts/sync-updated-dirs-to-work-dir.sh

sync-updated-dirs-to-work-dir.shはrsyncをwrapしたもので、前述の通りプルリクエスト上で変更が発生したterraformディレクトリを、スキャン実行する一時的な作業用ディレクトリに同期する処理をしています。1

細かな実装は内部事情に特化したものとなっているため割愛しますが、例えば、base_system/common/terraform, base_system/privilege/terraform, product_system/(サービス1)/terraformのディレクトリで変更が発生している場合、このスクリプトを実行することで作業用ディレクトリ(scan_work_dir)が作成され、以下のようなディレクトリ構造となります。

.
├── base_system
│   ├── common
│   │   └── terraform
│   └── privilege
│   │   └── terraform
│   .
│   .
├── product_system
│   ├── (サービス1)
│   │   └── terraform
│   ├── (サービス2)
│   │   └── terraform
│   .
│   .
└── scan_work_dir (ここのディレクトリにtrivyによるスキャンを実行する)
    ├── base_system
    │   ├── common
    │   │   └── terraform
    │   └── privilege
    │   │   └── terraform
    └── product_sytem
        └── (サービス1)
            └── terraform

次に同期してきた作業用ディレクトリに対してスキャンを実行します。
ここでは前述の通り、公式が用意しているtricy-actionを利用しています。

- name: Trivy Scan
  uses: aquasecurity/trivy-action@master
  with:
   scan-type: 'config'
   severity: 'HIGH,CRITICAL'
   scan-ref: scan_work_dir
   output: trivy-scan-result.txt

オプションの内容は以下の通りです。

  • scan-type: 'config'
    • configはTerraformなどの設定ファイルをスキャンする際に使用する。
  • scan-ref: scan_work_dir
    • 作業用ディレクトリ scan_work_dir を指定している。
  • output: trivy-scan-result.txt
    • スキャンした結果をテキストファイルtrivy-scan-result.txtに出力する。
    • このテキストファイルは次のアクションでフォーマットを整形して出力するために使用している。
  • serverity
    • 脆弱性の深刻度で'CLITICAL'と'HIGH'を指定しています。
    • ここのレベルは、CVSS2によって定量化された脆弱性の深刻度をもとに設定されています。

他のオプションなどについてはtricy-actionのページをご確認ください。

次に、前のアクションで出力したテキストファイルを見やすく整形した上で、marocchino/sticky-pull-request-commentを使用しプルリクエストのコメント欄に出力しています。

プルリクエスト更新時には、既存の脆弱性に関するコメントを削除した上で新規コメントを残して欲しかったので、その点でmarocchino/sticky-pull-request-comment(recreate: trueのオプション指定)を利用することで楽に実装することができました。

# スキャンした結果を整える
- name: Format Trivy Scan Result
  run: |
    if [ -s trivy-scan-result.txt ]; then
      # ファイルに内容がある場合
      echo -e "## 脆弱性スキャン結果\n<details><summary>詳細</summary>\n\n\`\`\`\n$(cat trivy-scan-result.txt)\n\`\`\`\n</details>" > formatted-trivy-result.md
    else
      # ファイルが空の場合
      echo -e "## 脆弱性スキャン結果\n脆弱性が検知されませんでした。" > formatted-trivy-result.md
    fi

- name: Comment PR with Trivy scan results
  uses: marocchino/sticky-pull-request-comment@v2
  with:
    recreate: true
    GITHUB_TOKEN: ${{ secrets.github_token }}
    path: formatted-trivy-result.md

スキャン結果

  • 脆弱性が検知されなかった場合

  • 脆弱性が検知された場合

「詳細」部分を開くと以下のような形で表示されています。

今回のケースだとCritical0件、High61件の脆弱性が検知されていることがわかります。

base_system/privilege/terraform/xxx.tf (terraform)
=====================================================================
Tests: 75 (SUCCESSES: 14, FAILURES: 61, EXCEPTIONS: 0)
Failures: 61 (HIGH: 61, CRITICAL: 0)

HIGH: IAM policy document uses sensitive action 'autoscaling:Describe*' on wildcarded resource '*'
════════════════════════════════════════
You should use the principle of least privilege when defining your IAM policies. This means you should specify each exact permission required without using wildcards, as this could cause the granting of access to certain undesired actions, resources and principals.

See https://avd.aquasec.com/misconfig/avd-aws-0057
────────────────────────────────────────
 base_system/privilege/terraform/xxx.tf:376
   via base_system/privilege/terraform/xxx.tf:376 (aws_iam_policy.xxx.xxx)
    via base_system/privilege/terraform/xxx.tf:375-438 (aws_iam_policy.xxx.xxx)
     via base_system/privilege/terraform/xxx.tf:373-439 (aws_iam_policy.xxx)
────────────────────────────────────────
 373   resource "aws_iam_policy" "xxx" {
 ...
 376 [     Version = "2012-10-17",
 ...
 439   }
────────────────────────────────────────
・
・
・(以下省略)

上記の例では、重要度「HIGH」の脆弱性が検知されています。

IAMポリシーは最小特権で付与するべきなので、ワイルドカード使うのはリスクがありますよ、という指摘のようです。

補足

検出された脆弱性の中で、この内容は指摘する対象から除外したい、というケースもあると思います。

その場合は.trivyignoreというファイルをトップディレクトリに置くことで勝手に参照して検出対象から除外してくれます。

以下のような形で記載します。

.trivyignore

AVD-AWS-0057
AVD-AWS-XXXX

(参考)

以下のようにtrivyignoresのオプションを利用することで、トップディレクトリに配置するだけでなく別のディレクトリに配置しているファイルを参照させたり、.trivyignore以外の別のファイル名を指定することもできるようです。

- name: Trivy Scan
  uses: aquasecurity/trivy-action@master
  with:
   scan-type: 'config'
   severity: 'HIGH,CRITICAL'
   scan-ref: trivy_temp_dir
   output: trivy-scan-result.txt
   trivyignores: test-trivyignore

まとめ

今回はtrivyとGitHub Actionsを活用し、Terraformでのセキュリティ上のリスクを効果的に検知する仕組みを構築しました。

今回検知されたものについては、優先度の高いものから順次改善していきたいと思います。

また、冒頭に説明した通り、trivyでは他にも様々なものを検知してくれる機能があるので、 Terraformの設定だけでなく色々な場面での活用を検討していきたいと思います。


  1. 変更が発生したディレクトリの抽出には git diff origin/main --name-only の結果をパースしています。
  2. CVSS( Common Vulnerability Scoring System ) = 共通脆弱性評価システム

ママリiOSアプリで今年取り組んだ3つの改善について

「コネヒト Advent Calendar 2023」の20日目のブログです!

adventar.org

iOSエンジニアのyoshitakaです。 コネヒトに入社してそろそろ1年が経ちます。

コネヒトに入社してからはママリiOSアプリの開発を担当しています。

今回はママリiOSアプリで行った3つの改善をまとめました。

  1. フォルダ構成の変更
  2. 開発中アプリの配布フローの改善
  3. 画面遷移の改善

それぞれどんな課題があったか、どんな改善をしたかを紹介します。

フォルダ構成の変更

課題に感じていたこと

既存のフォルダ構成は機能追加をする際に対象となるコードが見つけにくい状況でした。

前提

  • ママリiOSアプリのアーキテクチャーはMVVMを採用している
  • View・ViewModelModelでモジュール分割している
    • 今回改善したのはView・ViewModel側のフォルダ構成

    ※ モジュール分割の取り組みについてはこちらの記事を見てください。 tech.connehito.com

どんな改善をしたか

変更前のフォルダ構成のイメージがこちらです。

App
├── ViewController
│   ├── 機能AのViewController
│   ├── 機能AのViewController
│   ├── 機能BのViewController
│   └── ...
├── View
│   ├── 機能AのView
│   ├── 機能AのView
│   ├── 機能BのView
│   ├── 機能AB共通のView
│   └── ...
└── ViewModel
    ├── 機能AのViewModel
    ├── 機能AのViewModel
    ├── 機能BのViewModel
    └── ...

アーキテクチャーがわかりやすい構成になっていると思います。

しかし、機能追加をする際に対象となるコードにたどり着くのに時間がかかっていました。

改善案は大きく二つありました。

  1. 現在のフォルダ構成の配下に機能ごとのフォルダを作る
    • メリット: 変更が少なく済む
    • デメリット: 機能ごとのコードが分散する
  2. App配下に機能ごとのフォルダ作り、機能フォルダ内でさらにViewとViewModelのフォルダを作る
    • メリット: 機能ごとのコードがまとまる
    • デメリット: フォルダ構成の変更が大きい

機能改善する際ViewとViewModelをセットで修正することがよくあるので、2のApp配下で機能ごとにフォルダ分けをすることにしました。

改善したフォルダ構成イメージがこちらです。

App
├── Features
│   ├── 機能A
│   │   ├── View:ViewController・DataSource・View置き場
│   │   └── ViewModel
│   ├── 機能B
│   ├── 機能C
│   └── ...
└── Common:共通化されたView・ViewModel置き場
    ├── View
    └── ViewModel

複数の機能で使われているものはCommonフォルダにまとめるようにしました。

フォルダ構成は好みもあるかと思いますが、整理したことで不要なファイルもお掃除でき、良い改善だったと思います。

開発中アプリの配布フローの改善

課題に感じていたこと

特定の開発中ブランチから開発中アプリのを配布する際に、CIでは実行できず、常にローカルでfastlaneを実行する必要がありました。

前提

  • CI/CDはBitriseとfastlaneを使っている
  • 開発中アプリはFirebase Distributionを使い社内に配布している
  • Firebase Distributionへの配布方法は以下の2つ
    • PullRequestをメインブランチにマージすると自動で配布される
    • ローカルでfastlaneを実行して配布する

どんな改善をしたか

CIでfastlaneのアプリ配布のワークフローを実行できるようにすることで、ローカルでのfastlane実行を不要にしました。

トリガーはGitHub Actionsを使い、ブランチを指定して実行するとBitriseのアプリ配布のワークフローが実行されるようにしました。

改善方法は別の記事にまとめております。

tech.connehito.com

この改善により、ローカル環境に依存しないアプリ配布ができるようになり、開発スピードのUPに繋がっています。

画面遷移の改善

課題に感じていたこと

アプリ全体の各ViewConrollerに画面生成と遷移のロジックが実装されていて、コードの見通しが悪く、ViewControllerの肥大化要因にもなっていました。

前提

  • 画面遷移のアーキテクチャーは採用していない
  • 各ViewControllerの任意の箇所でViewControllerの生成と遷移処理を行っている

どんな改善をしたか

画面生成と遷移処理のロジックをなるべく1箇所にまとめる仕組みを作りました。

具体的には画面生成と遷移処理部分で以下のような変更をしました。

let viewController = QuestionContainerViewController.instantiate(
    id: question.id
)
navigationController?.pushViewController(viewController, animated: true)

pushScreen(screen: .questionContainer(
    questionId: question.id
)

Screenというenumを作り、Screenの値を元にViewControllerを生成するようにしました。

enum Screen {
    case questionContainer(
        questionId: Int
    )
    ...
}

extension UIViewController {
    func makeViewController(from screen: Screen) -> UIViewController {
        switch screen {
        case .questionContainer(let questionId):
            return QuestionContainerViewController.instantiate(
                id: questionId
            )
        ...
        }
    }

    func pushScreen(screen: Screen) {
        let viewController = makeViewController(from: screen)

        self.navigationController?.pushViewController(viewController, animated: true)
    }
}

画面遷移のアーキテクチャーパターンの採用も検討しましたが、実装コストとメリットが見合わず、今回はよりライトにできる方法を採用しました。

画面遷移のアーキテクチャー検討について以前外部イベントで発表した資料がありますので、興味がある方はご覧ください。

www.docswell.com

この改善は現在も段階的に置き換えを進めているところですが、すでに実装完了している部分だけでもコードの見通しが良くなり、またテストコードも書きやすくなりました。

まとめ

どの改善も日々の開発業務のスピードを上げることができていると実感する部分が多くありました。

来年はより難易度の高い改善にも取り組めたらと思っております!

ドメインモデリングを通じて起きたチームの変化 ~ytake氏のワークショップ~

この記事はコネヒト Advent Calendarのカレンダー 13日目の記事です。

adventar.org

2023/10/06にytakeさんをお招きして、社内メンバーを対象にドメインモデリングの講義を開催しました。 今回は当日の様子と、その後の変化について紹介したいと思います。

現時点では、定量的に測れる変化までは至っていませんが、業務上定性的な変化が起きていると感じています。 今回、本記事で例示するのは以下の2つです。

  • チーム内でモデルと命名の話題が増えた
  • 「境界づけられたコンテキスト」を基準に設計・実装に反映する

本記事は、以下の構成でお送りします。

ワークショップの目的

ワークショップの目的は、以下の2点です。

  1. コンテキストギャップ埋める方法を身につけ、リリースまでの機動力を高める
  2. 技術コミュニティと新しい学びのループを作る

コンテキストギャップ埋める方法を身につけ、リリースまでの機動力を高める

日々の業務では、個人個人が持つ情報差分や専門職種の特性などから作られるコミュニケーションのコンテキストに違いが生まれます。 その違いが大きいと、コミュニケーションのコストが高くなったり、思わぬ手戻りが発生することで、ボトルネックとなってしまうことがあります。 どの程度のボトルネックかは、個人個人の暗黙的な感覚に依存してしまうため、 まずはワークショップを通じた現状認識と課題の発見を目的としました。

技術コミュニティと新しい学びのループを作る

コネヒトにはスマイル制度という「技術コミュニティになくてはならない開発組織をつくる」をコンセプトにしたアウトプット支援制度があります。 インプットとアウトプットのループを作ることで、コネヒトも技術コミュニティもWin-Winな関係を築いていく狙いがあります。

tech-vision.connehito.com

これまでは研修参加や書籍の購入など、個人で利用されるケースが多かったのですが、複数人で同じことを学び、発信する事例としてこの制度を利用しました。 本制度の多様な活用事例を生み出すことで、より技術コミュニティとWin-Winになること2つめの目的と設定しています。

講義の内容

当日の講義は、座学とワークショップを組み合わせた形式で、合計4時間の内容でした。参加者は事前に目的を共有し希望者を募りました。 ytakeさんに参加者と予定時間を相談の上、半日で収まる内容にチューニングしていただきました。

タイムライン

座学(120min)

  • 14:00 ~ 14:05: イベント概要おさらい
  • 14:05 ~ 14:15: 自己紹介/チェックインタイム
  • 14:15 ~ 15:00: ytakeさん自己紹介・座学
  • 15:00 ~ 15:10: 休憩
  • 15:10 ~ 16:00: 座学
  • 16:00 ~ 16:10: 休憩

イベントストーミング形式ワークショップ(120min)

  • 16:10 ~ 17:00: ワークショップ
  • 17:00 ~ 17:10: 休憩
  • 17:10 ~ 17:50: ワークショップ
  • 17:50 ~ 18:00: チェックアウト

参加者内訳

  • エンジニア:12名
  • デザイナー:1名
  • PdM/PMM: 2名

なお、イベントストーミングは、Alberto Brandolini氏が考案した協働的にドメインモデルを発見していく手法です。 EventStormingのサイトを参照すると以下のような記載があります。

The adaptive nature of EventStorming allows sophisticated cross-discipline conversation between stakeholders with different backgrounds, delivering a new type of collaboration beyond silo and specialisation boundaries.

翻訳:EventStorming の適応的な性質により、異なる背景を持つ関係者間で専門分野を超えた洗練された会話が可能になり、サイロや専門分野の境界を超えた新しいタイプのコラボレーションが実現します。`

今回はこのようなコラボレーションを実現するために、エンジニアとPdM/PMM、デザイナーの3つの職種のメンバーに参加してもらい、この方式を採用しました。

当日の様子

座学

当日は以下の内容を中心にお話ししていただきました。

  • ドメインモデルとはなに?
  • 分析するための考え方
  • ユースケース

座学の間も適宜質問を受けていただき、理解を深めながらお聞きすることができました。 ここで教えていただいた「コンテキストの境界」が今後の変化に繋がっていきます。

わいわいと講義を受けています

イベントストーミング

今回はママリアプリで起こる出来事を題材にイベントストーミングを開催しました。 みんなでわいわいと、出来事や関連する要素を書き出していき、ytakeさんのファシリテーションを受けながら、認識の違いを発見していきました。

グルーピングしたものをもとに議論しています

付箋の色分けは以下の通りです。

  • 画面やUIが絡むようなもの: 青
  • システム的なもの: ピンク
  • サービスの仕様、重要な出来事: オレンジ

当日のサンプル

その後の変化

このようにモデリングを利用し、認識の違いを発見する方法を学んだことで起きたエピソードを2つ紹介します。

チーム内でモデルと命名の話題が増えた

ミーティングで話す用語は、どのように整理すると自然なのかというコミュニケーションが以前より増え、用語集を作る動きがチーム内で生まれています。 筆者の所属するチームが動画コンテンツの改修を担当しているため、まずは動画施策の用語の整理から始めています。

用語集の整理

職種を問わず共同作業ができるように、Notionのデータベースを利用しています。

「境界づけられたコンテキスト」を基準に設計・実装に反映する

コネヒトが管理する動画には、外部サイトへのリンクがあるものないものがあります。この違いをRestAPIリソースとして個別に分けるかの議論がなされました。

miroで整理した様子

ワークショップ開催前は、設計においてアクターが誰かという話題は出てこなかったのですが、ワークショップを通じて、「コンテキストの境界」が重要であることを学んだことで、 この議論では、アクターが同じため同一のリソースとして定義しておこうと判断することができるようになりました。

この他にも

  • チームの担当領域に絞りイベントストーミングを実施する
  • 特定施策のユースケースを書き出してみる

など少しずつモデリング実施する機会が増えてきています。 それらの事例も、今後の変化に繋がっていくと思うので、また別の機会に紹介したいと思います。

まとめ

今回のワークショップでは、まずはモデリングのエッセンスを学ぶことにフォーカスしておりました。 本来の目的であるリリースの機動力を高める目的の実現には、まだまだ課題が多くあり、モデリングの実践とコードへの設計・実装への反映を定期的に繰り返す必要があると感じています。

ytakeさんから「目の前のものに騙されないように本質はなにか分析しましょう!100回くらい分析を繰り返しましょう!」というアドバイスをいただきました。 今後はより日常的にモデリングを実践できるように、常にMiro*1を開いてミーティングに参加しようと思います。

目指せモデリング100回!

最後となりますが、ytakeさん講義の開催ありがとうございました! モデリングにお悩みの場合、最初の一歩として、ytakeさんに相談することをおすすめしたいと思います。

*1:コネヒトではオンラインホワイトボードツールとして利用しています。https://miro.com/ja/