Dependabotを導入してライブラリのアップデートを習慣化した話

こんにちは!こんばんは!エンジニアの大石です。

突然ですが、依存ライブラリのアップデートって大変ですよね。

大きなアプリケーションになればなるほど依存するライブラリが増えがちですが、定期的にアップデートされてないか確認したり、習慣的に小さくアップデートするのって結構大変だったりします。

アップデートされてないか確認するだけでも億劫ですし、フレームワークのバージョンを上げるってタイミングで芋づる式に全部上げることになってしまって辛かったり... などなど。

今回は、Dependabotを用いてGitHub上に自動で各ライブラリをアップデートしたPRを自動的に作る仕組みを導入して実際に運用してみた知見を共有したいとおもいます!

Dependabotって何?

Dependabotとは簡単に言うとGitHub上で自動的にプロジェクトが依存しているライブラリのバージョンを上げたPRを作ってくれるボットです。

npm、Bundler、Composer、Gradleなどなど主要なパッケージマネージャーやビルドツールにしてるので大体のケースにおいては有効化するだけで使えます!

入れておくと定期的に自動でライブラリのアップデートを確認しに行ってくれます(頻度は調整可能)。便利ですね!!

有効化してしばらくすると...

DependabotがGitHubに自動で作ってくれるPRの様子

このように自動でPRを作ってくれます!

DependabotのPRの画面から各ライブラリのChangelogも確認出来る

また、このようにライブラリごとにバージョンごとの差分と変更点をまとめて出してくれるので、PRを見て内容を確認してそのままマージする、もしくは詳細に調査してから慎重にアップデートするかの判断が出来ますね。

とりあえず有効化する

単純なLaravel+JSのプロジェクトであれば、以下のファイルをGitHubのプロジェクト上に配置してあげるだけで有効化できます。

.github/dependabot.yml に設定を記述したYAMLを置いておきましょう。

(設定ファイルの詳細な記述方法についてはGitHubの公式ドキュメントが非常に分かりやすいのでそちらを参照してください!)

https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    reviewers:
      - "※ レビュワーのGitHubのIDを指定"
    labels:
      - "アップデート対応"
      - "php"
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    reviewers:
      - "※ レビュワーのGitHubのIDを指定"
    labels:
      - "アップデート対応"
      - "javascript"

弊チームでは毎週火曜日に定期的にDependabotで作成されたPRを確認する運用にしているので、毎週月曜日にアップデートの確認が動くように設定して、レビュワーを指定してリマインドするようにしています。

アップデートの運用

アップデートの確認が自動化されて、PRも自動で作られるようになって大満足!!!!ってなりがちですが、ちゃんと運用しないとただPRが放置されて終わってしまうので、弊チームでは以下のような運用にしてアップデートに対処しています。

アップデートのPRが作成されたらやること

(1) 即マージできるか?

テストで動作が担保できると確信できる場合はマージする。

開発時のみに依存しているライブラリの場合はローカルで動かして問題ないと確信できる場合はマージする。

(2)即マージほどではないが開発環境にデプロイしてマージできるか?

開発環境にデプロイして動作に問題ないと確信できる場合はマージする。

デプロイのみに依存するライブラリの場合もこの対応で良い。

(3)メジャーバージョンか?

メジャーバージョンの場合は基本的にどんなライブラリであってもマージしない。

対応IssueをJira上に作成し、どんな変更があったのかを調査してから安全にアップデートする。

(JIRAのIssueを作成するときは、GitHubのコメントに /jira <コメント> を入力で自動的にIssue作成)

(4)マイナーバージョンか?

1と2で問題ない場合はマージできる。

テストが落ちている等の場合はissue化して別途対応する。

(5)パッチバージョンか?

4と同じ

備考

問題ないと確信できない場合は、懸念点・不安解消ポイントなどをあげてJIRA上にIssue化する。

例えば大きめな動作確認が必要である、などなど。


といった感じに運用しています。

上に書いた運用ルールは実際に社内で使用している開発のドキュメントから引用したものですが、実際にこの運用で週に5〜10個程度のライブラリをアップデートしています。

ちなみに、Jiraに作成したアップデートのIssueの優先度付けは、以前開発者ブログにて投稿した「インフラタスク優先度定量化」の仕組みで付けています!

tech.macloud.jp

こちらも併せて読んでみてください!!

JiraのIssue作成の仕組み

JiraのIssueに毎回GitHubのPRのURLを貼り付けて、タイトルを書いて... という作業は結構退屈で辛い作業なので、Zapierと組み合わせて自動化しています。

Zapier側で組んだ自動化

仕組みとしては、GitHub上に /jira と書かれたコメントを書くとZapier側で検知して対象のPRから必要な情報を抜き出して、自動的にそれを元にJiraにIssueを作成するようにしています。

さいごに

今回紹介させていただいたライブラリのアップデートの運用は取り組みを始めてばかりですが、より安全に早くバージョンアップが出来るように随時運用の方もアップデートしていきたいとおもいます!

M&Aクラウドではエンジニアを積極採用しています!

www.wantedly.com

完全に余談ですが、最近弊社の社内部活動である #アイドル部 が盛り上がっていて、私が推しているアイドル(Appare!藍井すず)の名前が広まりつつあって嬉しさを感じています。

Dependabotを使ったライブラリのアップデートの取り組みも社内のSlackに投稿したところから始まったので、些細なことでもシェアできる文化は忘れないようにしたいですね!

祝100記事!技術ブログを継続する意義について

こんにちは、M&Aクラウドのかずへいです。2019年10月に始めたこの技術ブログも、ついに100記事目になりました🎉

大体2週間に1記事は書き続けてきた計算になります。継続できるのって本当にすごいですね!弊社のプロダクトメンバーは本当にすごいです。

今回は、この技術ブログについて振り返りながら、ブログを書く意義について考えていければと思います。

ブログの始まり

良い人が集まり、たくさんの良い仕組みを導入し、良いプロダクトを作り、それを発信して人が集まる、という好循環を作っていきたいと思い、この技術ブログは始まりました。

ブログを書き始めたときには、このブログには発信の役割を期待していました。ですが、記事が増えていく中で、ブログの役割は発信だけでは無いなと思うようになりました。

ブログは発信ではなく、良い仕組み導入のドライバー

ブログでは、過去にもいろんな良い仕組みを導入し、紹介してきました。例えば以下のようなものがあります。

tech.macloud.jp

tech.macloud.jp

tech.macloud.jp

他にも、弊社のM&Aクラウドの開発を進める上での知見を、様々な記事にしてきました。

ブログに書いた記事が増えるにつれて、次の記事では何を書こうか?これは記事に出来るような良い仕組みだったのか?等、 導入する仕組みについての振り返りや技術的な深堀りのきっかけづくりという効果が出始めました。

弊社ではブログのネタを何にするか話あったり、ブログの内容レビューをしたりといったことが日常的に行われています。 振り返りがより次の仕組みへ繋がり、結果的に新しい仕組み導入のドライバーになったなあと思っています。

ブログはオンボーディングの助けになる

また、ブログは新しいメンバーのオンボーディングの助けになりました。

新しいメンバーがつまずいた時に、これってどういう仕組ですか?と聞くと、この記事を読んでとブログの記事を共有することがよくあります。 外に出しても問題ないように、文章に気を使ったり、他のチームメンバーにレビューしてもらったりしているため、ブログの記事は分かりやすく、情報共有にもってこいです。以下の記事などは、弊社独自の仕組みを説明していて、新しく入ったメンバーに読んでもらうと有益です。

tech.macloud.jp

tech.macloud.jp

tech.macloud.jp

ブログが、チーム全体の学習を促す役割を果たし、当初の発信という目的以上の価値が出てきたなあと思っています。

ブログは文章を書く練習になる

エンジニアとして働いている方は、プログラムを書くのはもちろんのこと、PRにコメントをつけたり、社内向けにもドキュメントを作ったりと文章を書く機会が多いと思います。 社外に向けたブログを書くことは、文章を書くとても良い練習の場でになります。弊社ではブログを書いたら、プレビューをみんなに見せてレビューしてもらっているので、その点でもとても勉強になります。

ブログは発信の役に立ったのか?

そして、ブログは当初期待した発信の役割をできているのか?と言うところです。これは正直言ってまだまだだと思います。 弊社のブログがバズりまくって、弊社の名前がTwitterはてなブックマークを駆け巡り、カジュアル面談が何件も発生するというようなことは、残念ながら起きていません。

しかし、採用のための面談で「技術ブログ読んできました!技術頑張ってますね!」と言ってもらえることは増えました。 弊社と接点を持っていただけた方に、弊社のことを理解してもらうためのツールの一つとしては十分に役立っていると思います。

目標を置かずに続けていく

これからも、この技術ブログは続けて行けたらなと思っていますが、そのために大事なことは目標を置かずに続けていくことだと思っています。

例えば、目標として採用のための申込み数やPV数を置いてしまうと、どうしてもそれらの目標に見合ったものを頑張って書こうとなってしまいます。そうなると、ブログを書くこと自体が難しくなり、ブログを書くことによる発信や副次的な効果を失ってしまうことになります。

気負いせず、日々のチャレンジをメンバーが書き込んでいく形を続けることで、振り返りの機会を提供できる。そんなブログが続いていけばよいなと思います。

はじめての npm package

こんにちは。エンジニアの濱田鈴木塚原です。この記事はモブプログラミングならぬ、モブブロギングにより3人により書かれています。

今回は、複数レポジトリにコードのコピペにより同様のものが書かれていた処理を、 npm パッケージとして切り出した話です。

ライブラリ概要

作ったライブラリはこちらです。

github.com

inflow source は直訳すると「流入源」です。ユーザーがサイトに流入した経路を localStorage に保存しておくことで、後で何らかの CV が発生した時にその localStorage の情報をサーバー側に一緒に POST するために利用される想定です。「ユーザーがサイトに流入した経路」は例えばリファラ、ランディングページ、UTMパラメータ、CV前最終閲覧ページ、デバイス情報(pc/mobile)などです。

このパッケージは以下のようなインターフェースを持ちます(※TypeScriptの型情報から自動生成された index.d.ts です。本記事において瑣末な部分は一部省略されています)。

export declare type InflowSourceParams = {
    referer: string | null;
    landingPageUrl: string | null;
    utmSource: string | null;
    utmMedium: string | null;
    utmCampaign: string | null;
    utmContent: string | null;
    gclid: string | null;
    lastPageUrl: string | null;
    device: string;
};

export declare const useInflowSource: (storage: Storage, baseUrl: URL) => {
    set: (rawCurrentDate: Date | CustomDate, referer?: URL, currentUrl?: URL) => void;
    setLastUrl: (currentUrl: URL | undefined, ignorePathRegexpList: string[]) => void;
    getAllParams: () => InflowSourceParams;
};

メインは useInflowSource の中のメソッドで、軽くだけ説明します(本記事はパッケージ化のところが本流なので、詳細は割愛します)。

  • setメソッドを呼び出すと、ランディング判定(UTMパラメータがあるか、最終閲覧から30分以上経っているか)をした上で、 localStorage に流入源の情報を保存してくれます。
  • setLastUrlメソッドを呼び出すと、 ignorePathRegexpList 引数で設定した CV ページ以外のページを localStorage に保存しておいてくれます。
  • getAllParamesメソッドを呼び出すと、localStorageから保存したものをオブジェクトの形式で取得できます。 CV ページで POST するときに localStorage に保存しておいたものを一緒に body に入れるために使用できます。

背景

このパッケージを作ろうとした背景は、まず同じような処理が弊社内の複数のレポジトリに散っていたことでした。

弊社には、Laravel と生の JavaScript で書かれたいわゆる ”旧front” のレポジトリと、 Nuxt.js と TypeScript で書かれたいわゆる ”新front” のレポジトリがあります。サイト内の古いページは ”旧front" で書かれているが、最近書き直されたページは "新front" で書かれている混在状態です。ユーザがランディングしうるページはどちらで作られたものもあり得るので、「inflow source(流入源)保存」はどちらのレポジトリにも同様のロジックを記述しなければなりませんでした。

このロジック重複により、まずは新frontで TypeScript で実装し、動いたら旧frontに JavaScript で移植するという作業が発生します。対応するテストも重複します。 Jest で書かれたテストを、漏れがないように目視で確認しながら移植しなければいけません。この形だと将来の拡張の時にまた同じ辛さを抱えることになるので、同じ処理を npm package として切り出すことにしました。

構成

ここからは TypeScript のプロジェクトを npm package として公開する観点で構成を紹介します。

現在のバージョン情報は以下です。

  • TypeScript: 4.6.3
  • Node.js: 16.13.2

TypeScript で実装されており、以下のようなディレクトリ構成になります。

.
├── README.md
├── dist
├── example
│   └── index.js
├── jest.config.cjs
├── package-lock.json
├── package.json
├── src
│   ├── date.ts
│   ├── index.ts
│   └── user-agent.ts
├── test
│   ├── date.spec.ts
│   ├── index.spec.ts
│   └── user-agent.spec.ts
└── tsconfig.json

npm package として公開するにあたって重要なのは tsconfig.json と package.json になるので、これらの設定について説明します。まず tsconfig.json は以下の通りです。

{
  "compilerOptions": {
    "outDir": "dist",
    "noImplicitAny": true,
    "lib": ["ES2016", "DOM"],
    "target": "es5",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node",
    "declaration": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "paths": {
      "~~/*": [
        "./*"
      ],
      "@@/*": [
        "./*"
      ],
      "~/*": [
        "./src/*"
      ],
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": [
    "src"
  ]
}

lib には ES2016 に加えて DOM を記述しています。今回はブラウザ上で document.localStorage を使用しているため、この設定がないとトランスパイル時に Storage の型チェックに失敗します。

declaration には true を設定しています。弊社のフロントエンドでは TypeScript を利用しているため、型定義ファイルを作成して inflow-source の提供する API の型を参照できるようにしています。

esModuleInterop には true に設定しています。元々は未設定(デフォルトは false)でしたが、 日時の取得・加工に使用している Day.js のメソッド呼び出し時に TypeError が発生してしまいました。 esModuleInteropfalse の場合、 CommonJS で書かれたコードを ES Module のコードベースから import しようとする際に問題が生じる場合があるようです。 Day.js のエンドポイントに指定された JS ファイルを読むと、確かに CommonJS でビルドされていたので、 esModuleInteroptrue に設定してこの問題を回避しています。

また module の設定は書いていないため、暗黙的に CommonJS としてビルドされるようになっています。

続いて package.json は以下のように記述しています。

{
  "name": "@macloud-developer/inflow-source",
  "version": "0.1.0",
  "description": "inflow source を front 側で保存するためのライブラリ",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/macloud-developers/inflow-source.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/macloud-developers/inflow-source/issues"
  },
  "homepage": "https://github.com/macloud-developers/inflow-source#readme",
  "devDependencies": {
    "@types/jest": "^28.1.1",
    "jest": "^28.1.1",
    "ts-jest": "^28.0.4",
    "typescript": "^4.6.3"
  },
  "dependencies": {
    "@types/ua-parser-js": "^0.7.36",
    "dayjs": "^1.11.3",
    "ua-parser-js": "^1.0.2"
  }
}

パッケージ名で @macloud-developer/ のように記述することで組織名を表すことができます。

パッケージのビルドは単純に tsc コマンドで実行しています。社内ではビルドに webpack を用いるプロジェクトが多いため、最初は訳も分からず webpack でビルドしようとしていました(爆)が、特に出力ファイルをバンドルする理由もなかったので外しました。

main および types はそれぞれパッケージのエントリーポイントおよび型定義ファイルのパスを指定します。基本的に tsconfig.jsonoutDir で指定したディレクトリ上の index.js と index.d.ts が参照される形に設定すれば良いと思います。

また明示的に書いていませんが、 type: module は外しておき、暗黙的に CommonJS としてビルドされていることをパッケージ使用者側に示しています。

パッケージの publish

npm publish コマンドを用いて npmjsレジストリに登録します。詳細はドキュメントに譲るとして、ざっくり以下の方法で行います。

  • npmjs にユーザー登録。organization を作り、他の開発者を招待しておく。
  • ビルドして dist ディレクトリに JS ファイルと TS の型定義ファイルがある状態で npm publish --access public コマンドを実行

ライブラリの読み込み

public なパッケージとして公開できたので、あとは他の通常のライブラリと同様に npm install してコードベース上で import して読み込むだけです。例えば Nuxt.js であれば、以下のように plugin 上で router.afterEach() のコールバックから useInflowSource().set() を呼び出すことで、ページ表示・遷移のたびに流入源の情報が localStorage に保存されます。

import { Context } from '@nuxt/types'
import { useInflowSource } from '~/compositions/common/inflow-source'
import useDate from '~/compositions/libs/date'

export default (ctx: Context) => {
  if (process.server) {
    return
  }

  const inflowSource = useInflowSource(window.localStorage)

  const getUrl = (url: string): URL | undefined => {
    try {
      return new URL(url)
    } catch (e) {
      return undefined
    }
  }

  if (ctx.app.router === undefined) {
    return
  }
  ctx.app.router.afterEach(() => {
    inflowSource.set(
      useDate().current(), getUrl(window.document.referrer), getUrl(location.href)
    )
  })
}

まとめ

今回、旧frontと新frontの共通機能を npm package 化したことによって、双方の環境にコピペされたコードは共通化され、保守性、拡張性に強い実装にすることができました。以前までは重複したコードが旧frontと新frontでそれぞれ300行近くありましたが、対応後はパッケージを読み出すだけのシンプルなコードで100行程度にまとめることができました。また今後機能追加が必要になっても、 package 側のコードを修正して、旧frontと新frontでパッケージのバージョンを上げるだけで済むようになりました。

README は現在進行形で自分たち以外の方が読んでも分かるようにアップデート中です。インストール方法から動かし方をまとめていきますので、もしご興味のある方はダウンロードしてみてください。

M&Aクラウドではエンジニアを積極採用しています!

www.wantedly.com

www.wantedly.com

マーケが求めるスピード感に伴走するためにデザイナーが行ったこと

こんにちは。M&Aクラウドのインハウスデザイナー、池田です。最近デザイナーが増え、長いこと1人体制だったのが急に3人になり、チームにな利ました。嬉しい反面、2人とも僕よりずっと優秀なので振り落とされないように頑張っていこうと思います!

デザイナーが増える一方で他部署の採用もトントンと進んでおり、前回記事を書いたタイミングから20人くらいメンバーが増えました。そうなるとデザインが絡む施策の数も増え、デザイナーもサービスデザインのみならず、さまざまな業務をこなさなければなりません。

その中でも少し頭を抱えていたのがバナーやLPの制作です。とくにバナーは同じような雰囲気で、文言だけ変えて制作して欲しいみたいな要望が多いです。その要望に都度デザイナーがアサインできるかというと難しいです。

そこで、いかにデザイナーのリソースを最小限に抑え、品質が担保されたバナーを制作できるかを考えました。

Figmaを用いたボトムアップ的にクリエイティブを制作してもらう

現在弊社のデザイン業務の大半はFigmaに移行しています。Figmaに移行したおかげで非デザイナーでもデザインファイルを網羅的に確認することができるようになったので、認識の齟齬がだいぶ小さくなったと実感しています。 それと同時に、非デザイナーでもボトムアップ的にバナーやサムネイル画像を制作できる仕組みを作ろうと思いました。

Figmaでテンプレートパターンを作成

実際にやったこととしては、Figmaデザインパターンを洗い出し、該当するテンプレートをコピペしてテキストを変更、エクスポートまでを一貫して行えるような体制を作り始めました。

Figmaのテンプレートファイル

テキストを入力することでレイアウトが崩れることを防ぐためにAuto Layoutを用いて制作しています。 すべてのバナーデザインをパターン化、テンプレート化はまだできていませんが、今後一定数以上のバナーのパターンが出てきたタイミングでテンプレート化を進めていこうと考えています。

ノーコードを導入、制作後は運営サイドに渡す

LPを制作する度にエンジニアリソースを割けないので、簡易なLPであればノーコードを用いてデザインテンプレートを制作し、量産化を図るなどを行っています。

STUDIOで制作
最初はSTUDIOで作成を行なっていましたが、最近ではグローバル観点からWixを導入して制作しています。 ツールに関しての説明は省きますが、便利な反面、FigmaAdobe XDの操作感に慣れている人からすると多少違和感があるかもしれません。

まとめ

インハウスで働いていると、マーケティングのみならず様々な部署からの依頼も四方八方から来ます。その中でデザイナーがバリューを発揮するためには、手段にとらわれず柔軟に動いていく必要があると改めて実感しました。

M&Aクラウドではエンジニアを積極的に募集してます

www.wantedly.com

www.wantedly.com

【対応急げ!】個人情報保護法改正に伴うCookie同意の対応について

こんにちは。エンジニアの濱田 (@hamakou108) です。

2022年4月に施行された個人情報保護法改正についてご存知でしょうか? 法改正に伴い、 Cookie 等の取り扱いについてユーザに確認を取ることが必須になりました。 弊社プラットフォームのM&Aクラウドでは、個人情報保護法改正にともなって Cookie policy, Privacy policy を改訂するとともに、ポップアップを表示することでユーザに明示的にCookieの取扱について確認するようにしています。

サイトに埋め込まれた Cookie 同意ポップアップ

背景

個人情報保護法の改正

2022年4月に改正個人情報保護法が施行されました。 この法改正には、個人関連情報 1 の第三者提供の制限等が付け加えられており、第三者が個人関連情報を個人データとして取得することが想定される場合の確認義務が定められています。

専門家ではないので厳密なことは言えませんが、 Web サイト上で Cookie 等を介して第三者にデータを提供する前に、多くのケースにおいてユーザーの同意を得る必要が生じています。

Cookie 同意のツールの環境

海外でも GDPR 等の個人データ保護規則の遵守に関する関心が高まっており、様々な同意管理プラットフォーム(以下 CMP と表記)が出現しています。 また Google Tag Manager (以下 GTM と表記)などの既存ツールも同意モードに対応しつつあります。

CMP の特徴と使わなかった理由

CMP を使うと、ユーザー同意を得るためのポップアップを埋め込む、サードパーティースクリプトの実行を制御するといった機能が簡単に実現できます。 多くの CMP は様々な要件に対応するために豊富な機能を提供している一方、その分定額課金となっています。 弊社のケースではシンプルに同意する・同意しないの二択を選択できれば良かったため、自前で実装してもペイするだろうと判断し、 CMP の利用は見送りました。

Google Tag Manager の同意モード概要

GTM では同意の状況に応じてGTMタグを発火させるかブロックさせるべきか選択できます。 例えば以下のGTMタグですと、赤枠で囲った部分が該当の設定になります。

同意設定が行われた GTM のタグ

「追加同意チェック」のところにユーザに許可されるべき同意モードを設定しておくと、それらの同意がされていない場合にはGTMが発火しないようにできます。これにより、該当のスクリプトがHTMLに差し込まれないので、当然ユーザの個人関連情報を送信することはなくなります。

対応

GTMの同意モードを用いることで、包括的にスクリプトを差し込む/差し込まないを管理できることがわかりました。弊社ではサードパーティスクリプトをGTMに乗せることを原則とし、GTMの同意モードで管理することにしました。

Google Tag Manager の設定

まずソースコード中で追加していたスクリプトタグは GTM に移行しました 2 。 元々なぜソースコード中に埋め込んでいたかと言うと、ユーザー ID など、ソースコード上でなければ取得できない値を使用するからでした。 今回はソースコード上で window のプロパティにこれらの値を仕込み、 GTM からその値を参照する形を取りました。

次に GTM の同意設定です。 任意のタグについて、設定の「追加同意チェック」から「タグの配信時に追加同意が必要」をチェックして必要な同意を追加します。 3 この設定により、ユーザーから同意を得られていない場合はタグが発火しないようになります。

また同意タイプを設定することで、ユーザーが行った同意の種類(機能性 Cookie には同意する、広告には同意しないなど)に応じてタグの発火を制御できます。 そのタグが発火する条件として適切な同意を設定することで、ユーザーが何について同意するか細かく選択できる仕様を実現できるでしょう。

追加同意チェックを設定する

ちなみに上記のようにタグごとに設定することもできますが、複数のタグをまとめて設定変更することも可能です。

盾マークのボタンを選択

複数選択してまとめて同意設定ができる

Cookie 同意状況の初期化

GTM に現在の同意状況を読み込んでもらうには、 window.dataLayer にデータを追加します。 各サイトに以下のようなコードを埋め込み、ページ表示時に同意状況が初期化されるようにしています。

export class GoogleTagManager {
  private setConsentMode() {
    // Define dataLayer and the gtag function.
    window.dataLayer = window.dataLayer || []

    function gtag() { window.dataLayer.push(arguments) }

    // デフォルトではすべての同意タイプについて 'denied' を設定
    gtag('consent', 'default', {
      ad_storage: 'denied',
      analytics_storage: 'denied'
    })

    // localStorage に過去の同意記録が残っていた場合に 'granted' を設定(詳細は後述)
    if (localStorage.getItem('cookie-consent-granted')) {
      gtag('consent', 'update', {
        ad_storage: 'granted',
        analytics_storage: 'granted'
      })
    }

    window.dataLayer.push({
      event: 'default_consent'
    })
  }
}

consent default を設定するタイミングについては注意が必要です。後述する Consent Initialization トリガーで発火するCookie同意ポップアップの中で 同意状況の初期化をしようとしたのですがそれは上手くいきませんでした。 それは、consent defualt の設定は必ずGTM自身のスクリプトを読み込む前に設定しておく必要があるためです。ドキュメントの中で(2022/05/27時点では文章中のタブの中に隠れていて分かりづらいのですが)以下のように書かれています。

サイトのすべてのページで、タグが呼び出される前に以下を行う必要があります。
dataLayer オブジェクトが定義されていることを確認する。
gtag() 関数が定義されていることを確認する。
gtag('consent', ...) コマンドを使用して測定機能を設定する。
dataLayer.push() を使用して default_consent イベントを送信する。

Cookie 同意ポップアップ

Cookie 同意ポップアップは各サイト共通の独立した社内ライブラリとして開発して、 GTM を使って各サイトに埋め込んでいます。

実装詳細

Cookie 同意ポップアップのメインの実装は以下の通りです。他にこれを読み込む薄い index.ts や scss もありますが、本筋に無関係なので省略します。

export class CookieConsentPopup {
    private popupElement: Node
    private storageKey = 'cookie-consent-granted'

    private static gtag(..._: any) {
        (window as any).dataLayer = (window as any).dataLayer || []
        // オリジナルのgtagファンクションを参考に arguments を直接使うように実装。
        // @ts-ignore
        (window as any).dataLayer.push(arguments)
    }

    private static gtmEventPush(eventName: string) {
        (window as any).dataLayer = (window as any).dataLayer || []
        (window as any).dataLayer.push({
            event: eventName
        })
    }

    constructor() {
        this.init()
    }

    private init() {
        const isAlreadyConsentGranted = localStorage.getItem(this.storageKey)
        if (isAlreadyConsentGranted) {
            return
        }

        this.showPopup()

        document.querySelector('.cookie-consent-popup__disagree').addEventListener('click', () => {
            this.disagree()
        })
        document.querySelector('.cookie-consent-popup__agree').addEventListener('click', () => {
            this.agree()
        })
    }

    private showPopup() {
        const popupElement = document.createElement('div') as HTMLDivElement
        this.popupElement = popupElement
        popupElement.innerHTML = `
<div class="cookie-consent-popup">
  <div class="cookie-consent-popup__content">
    <p>当サイトを引き続きご利用いただく場合は、当社のプライバシーポリシー及びCookieガイドラインをよくお読みいただき、これらに対する「同意する」ボタンを押して下さい。</p>
    <div class="cookie-consent-popup__button_wrapper">
        <button class="cookie-consent-popup__disagree">同意しない</button>
        <button class="cookie-consent-popup__agree">同意する</button>
    </div>
  </div>
</div>`
        document.body.appendChild(popupElement)
    }

    public agree() {
        CookieConsentPopup.gtag('consent', 'update', {
            'ad_storage': 'granted',
            'analytics_storage': 'granted',
        })

        this.destroy()
        localStorage.setItem(this.storageKey, 'true')
        // gtm event. Trigger for many gtm tags.
        CookieConsentPopup.gtmEventPush('cookieConsentGrantedGtmEvent')
    }

    public disagree() {
        this.destroy()
    }

    private destroy() {
        document.body.removeChild(this.popupElement)
    }
}
Cookie 同意ポップアップを表示するトリガー

GTMにおいて今回のような同意モードに関するトリガーはConsent Initialization - All Pages を使用するのがベストプラクティスだとされています。

「同意の初期化」トリガーは、他のトリガーが起動する前にすべての同意設定が適用されるように設計されています。

Cookie同意された時にGTMのタグを発火させる

Cookie 同意されていない時点では、All Pages のトリガーのうち「タグの配信時に追加同意が必要」なトリガーは発火がブロックされています。 ページが表示し終わった後にユーザがボタンクリックで同意した場合でも、すでにAll Pages のトリガーはGTMのContainer Loadedのタイミングで実行されているので、ユーザが同意したタイミングではすでに発火タイミングを逃してしまっている状態です。 このため、ボタンクリックのタイミングで発火する追加のトリガーを設定しておく必要があります。それが前述の実装コードにおけるCookieConsentGrantedGtmEventイベントです。 このイベントをAll Pages のトリガーを持つタグに追加で設定しておけば、ユーザが同意したタイミングでタグを発火させることができます。

トリガー設定例

タグへのトリガー追加例

例外として組み込み同意モードがあるGoogle 系のスクリプトの扱い

google 系のスクリプトは「組み込み同意チェック」という機能が最初から入っています。 組み込み同意チェックが入っているかどうかはGTMタグの設定の中の”同意設定”の項目を見るとわかります。 以下の画像のものですと、 ad_storage と analytics_storage については組み込みされており、これらの同意がされなければ個人関連情報を送信しないようになっています。詳細についてはドキュメントを参照してください。

組み込み同意チェックがある場合、追加同意チェックは不要

このような組み込み同意チェックがあるケースだと、GTMタグ自体は同意モードの発火してスクリプトが差し込まれても問題がないため、追加同意チェックの設定は「追加同意は不要」という設定にしておきます。

最後に

いかがでしたか? 個人情報保護法改正に伴って同じように Cookie 同意の対応を検討されている方の参考になれば嬉しいです。

弊社ではエンジニアを積極採用中です!

www.wantedly.com

www.wantedly.com


  1. 生存する個人に関する情報であって、個人情報、仮名加工情報及び匿名加工情報のいずれにも該当しないものと定義されています。

  2. JS だけでなく CSS も含むコードは GTM に移行するのがハードだったので、移行していません。これらは localStorage の値を直接参照して同意済みかどうか条件分岐させています。

  3. タグの追加同意チェックを設定するには、コンテナの設定で「同意の概要を有効にする」にチェックを入れる必要があります。

プログラムを消すライブラリrice-ballを作った

皆さんおはこんばんにちは、ゆいです

今回はTypeScriptでライブラリを作ったのでその話をしようと思います。

www.npmjs.com

これなあに?

rice-ballは特定のコメントパターンに基づいてコードを削除するTypeScript製のライブラリになります。

rice-ballと言う名前の由来は私の推している声優さんの高田憂希さんが演じるTokyo 7th シスターズ天堂寺ムスビと言うキャラクターのムスビと言う名前からおむすび->rice ballと言う感じで着けました。

777☆SISTERSしか勝たん

以下のようにコメント記述してnpmでインスコしたrice-ballを実行することで指定したコードブロックを削除したりファイル毎削除することができます コメントのパターンはREADMEを参照ください。

befor:

export default function example(): void {
  const scream = '高田憂希しか好きじゃない'
  /* rice-ball start example-flag */
  console.log(scream)
  /* rice-ball end example-flag */
}

after:

export default function example(): void {
  const scream = '高田憂希しか好きじゃない'
}

なんで作ったの?

※なぜコードを削除するライブラリが欲しかったのかはphp-delの過去記事で詳しく書かれているのでこちらを参照ください。

久保田さんの開発されたphp-delはvueやjs, tsなどのファイルに対応しておらず、 フロントエンドのコード削除が手作業でやる必要があったので、基本コンセプトは同じ内容でテキストファイルであれば基本的に全部サポートするライブラリを作ってしまえば良いじゃんと思い開発したと言うところです。

OSSを開発してみての感想

初めてOSSとしてnpmで配信するライブラリを開発してみてこんな事を感じました

とりあえず作ってみるでも良い

これは久保田さんもphp-delの記事でも書かれていますが、万人ウケするものではなく、自分たちのニーズに応える物でも良いという風に感じました。 規模が小さくペルソナが身内だとしても、ニーズに応えられるツールを開発すると言うのはとても刺激的で、開発意欲も湧いて楽しく開発することができました。

ちょっとした便利ツールなど小物でも実際に作ってみると言う部分は中々難しいところだと思いますが、作り始めてしまえば小さな物でもニーズに応えることはできるのだと言うのを改めて実感しました。

最後に

少しでもいいなと思ってもらえたらstarつけてください!

そして最後に、高田憂希しか好きじゃない!

以上になります。

現在採用中です!

組織拡大!急成長中スタートアップでエンジニアリングマネージャーを募集!! - 株式会社M&AクラウドのWebエンジニアの採用 - Wantedly

モブプログラミングはじめました

はじめに

こんにちは。エンジニアの津崎です。 皆さんモビングしてますか? 一人でコーディング、もしくはペアプロでしょうか。

ちなみに、この記事も初の試みとしてモブプログラミングによって作成されています(笑) モブブロギングです。

共同編集者の、やも(@yamotuki)さん、はまちゃん(@hamakou108)、ありがとうございます。この場を借りてお礼申し上げます🙇‍♂️

M&Aクラウドの開発チームのうち、僕が所属する3名構成のサブチームにてモブプログラミングを試験的に導入しています。 今日は、1ヶ月ほどモブプログラミングを経験した上での学びを共有します。

モブプログラミング

モブプログラミング(モビング、以下モブプロ)とは、3人以上のエンジニアで1つのプログラムを書き、チームで成果物を完成させる、チーム作業のテクニックです。

なぜモブプログラミングか?

開発チームではコミュニケーションコストを減らすために部分的にペアプログラミング(以下ペアプロ)を導入していましたが、ペアプロを行っているのは主に、レビューのタイミングと、作業が詰まってしまったときに限られていました。 一部ペアプロを行うことでベロシティ(スプリント内の消費ストーリーポイント)が高まる傾向にあることはわかっていたので、ペアプロの機会を増やせば、もっとベロシティが上がるのではないか? と考え、3人で同時に作業を進めるモブプログラミングを試してみることにしました。

モブプログラミング ベストプラクティス

僕たちのチームではモブプログラミングの導入にあたって、「モブプログラミング ベストプラクティス」を参考にしています。

「モブプログラミング ベストプラクティス」では、同じ部屋にメンバーが集まり、1台のPC、1台のキーボードを共有するスタイルでモブプログラミングをすることが前提になっています。

モブプログラミングでは「タイピスト」と「その他のモブ」という役割に分かれて作業を行います。タイピストは、その他のモブの指示に従ってコーディングを行います。タイピストが暴走して勝手にコーディングすることは許されません。 タイピストは、ただの入力者ではなく、その他のモブの「スマートアシスト」です。 一字一句指示していると時間がかかりすぎてしまうので、齟齬が起こらない範囲でざっくりとした粒度で指示し、作業を行います。

初めてのモビングセッションは2時間程度から始めることが推奨されています。 1つのモビングセッションを10分の小さなモビングインターバルに分割します。 それぞれのインターバルでタイピストを交代しながらモブプログラミングを進めていきます。

最後に20分ほどで締めくくりでレトロスペクティブ(振り返り)を実施し、次のモビングセッションに向けて改善できることを話し合い、モビングセッションを終えます。

実際にやってみて感じたモブプログラミングのメリット

複数人で問題解決した方がより良い答えがでる

これは体感として、話し合いの中でいいアイデアが浮かぶことが多いなと感じています。 1人でタスクを終わらせることばかり考えて実装していると、つい安直で長期の保守性の悪い手法を選択しがちです。そういった思考で作ったPull Requestでも「既に書かれてしまっているから」と多少品質が低くてもマージされてしまう、そういうことはよくあるのではないでしょうか。 一方でモブプロで方針を話し合うと、「このタスクの目的ってなんだっけ?」から始まります。その目的に叶う最も良いと思われる設計方針を複数人で話し合うことになります。場当たり的な対応なんて恥ずかしいので出づらくなります。

キーパーソンに頼らない

1人でタスクを進める前提だと、どうしても得意な人に特定のタスクが偏りがちです。 その人だけが詳しくなっていき、その他の人は情報共有を受けるだけとなり、より属人化が進んでいきます。 モブプロで進めることで、チームでタスクに当たるので特定の誰かに情報が偏ることが減ります。 プラクティスとして「ちょっと付いていけなくなってもタイピストをやればOK」というものがあります。タイピストは必然的に他の人から指示を受けて書くことになるので、知見を得られやすいポジションになります。 これにより、各人のスキルアップに繋がることになります。

ペアプロより中断しにくい

1人より2人、2人より3人で話している方が外部からの妨害は入りにくいものです。 また、3人で進めている場合には、そのうち1人ミーティングで抜けたとしてもモブプロは続いていきます。 これにより成果物の完成速度が安定します。

レビュー高速化

「レビューお願いします」「リマインドですが、こちらレビューお願いします!」 「ここ修正してください」「修正しました。確認お願いします!」「ここもお願いします」「修正しました!再度確認お願いします」・・・

モブプロを初めてからのコードレビューは爆速化しました。事前にモブプログラミングをしているため、コードレビューでは「余計なものが入っていないかな?」という確認をさっとするだけでよいのですぐに終わります。 レビューしてもらうまで待ったり、リマインドしたり(されたり)する必要もありません。

技術の共有化

コーディングのテクニックや、ツールのショートカットなど、GitHub上のプルリクエストのコードには現れないスキルを間近に見ることができます。 同じような仕事をしていると思っていた同僚がすごいショートカットを使っていた時の驚きをぜひ体感してみてください。

未知領域、苦手領域のキャッチアップが楽に

メンバー内に得意な人がいれば、リードしてもらうことで一人で進めるより理解が進みます。

実際にやってみて感じたモブプログラミングのデメリット

技術を深ぼる時間が取りづらい

1人でやっている時にはタスク完了にはそこまで影響しないけど技術的に気になったことを深掘りする時間が取りやすいです。自分がきちんと全て理解していなくてもタスクをDONEにすることができるので理解が曖昧なまま終わってしまうことがあります。 この問題には強い気持ちで「ここの技術的背景をもうちょっと深掘りたい」とチームに提案することが必要です。案外他のメンバーも曖昧になっているところで、全員の技術理解を深めることになるので積極的に提案していきましょう。

傍観者モードになってしまう

これは僕だけかもしれませんが、自分以外のモブが議論して自分以外のモブがタイピングしている状況では、気を抜くと簡単に傍観者モードになってしまい、話についていけなくなってしまいます。 適度に休憩しリセットする。タイピストを適切に回し、集中を切らさないようにするなどの対策が必要です。 (原則に則ればタイピストは 10分交代なのですが、後述の理由でなかなか難しいケースがあります)

リモートでのモブプログラミング

PhpStorm Code With Me の活用

Code With Me: JetBrains が提供する共同プログラミングサービス

僕たちチームでは、リモートでの作業が主なため、PhpStormの共同プログラミング機能、「Code With Me」を使ってモブプログラミングを行なっています。

Code With Me は、誰か1人がホストとなって、その人のPhpStormに接続する形で共同編集を行います。Googleドキュメントやスプレッドシートを複数人で触ったことがある方はイメージできるかと思いますが、利用者ごとに複数のカーソルが存在し、それぞれがコーディング作業できます。

(弊害)平行作業できてしまう

コーディングをしていると、「平行作業した方が早くない?」となり、タイピスト以外がタイピングしたくなる時があります。 並行で作業をしてしまうと、何がどのように変更されたのかわからなくなるため原則禁止ですが、僕たちは以下のルールでその他のモブがタイピングするのを許可しています。

  • ちょっとしたタイプミスや誤字脱字の修正
  • 単純な作業の分業
  • タインピングのための参考コードやURLの入力
(弊害)タイピストが偏ってしまうケースがある

PhpStorm上で完結する作業であれば、タイピストがシームレスに交代できますが、フロントエンドのマークアップ作業でコーディング→ビルド→ブラウザでの動作確認をやるケースなどでは、PhpStormのホストが作業しないと効率が悪くなってしまいます。後者のケースでは、どうしても数十分間タイピストが固定になることがあります。

(弊害)ホストのスイッチングコスト問題

PhpStorm の Code with Me は、2022/05/13現時点ではホストへの接続にやや時間がかかります。 コードはホストとなる人の環境にしかありませんので、会議でホストが抜けると、その環境で動作確認ができなくなってしまう問題があります。 その緩和策としては、テストを潤沢に書くことで手動動作確認を減らし、コード上だけで完結させることがある程度ワークしました。

また、誰かの環境である程度書いたコードを、なんらかの事情で他の人の環境で続きを書きたいケースでは、新しいホストの環境にコードをフェッチする必要があります。これが地味にめんどくさく、ホスト交代をする頻度が下がりがちです。

Slack Huddleとの併用

動作確認などのコーディング以外の作業を行う際は画面共有が必要です。 僕たちはSlackのHuddleにて通話や画面共有をおこなっています。Code With Meにも通話、画面共有機能があるのですが、僕たちは普段Slackを利用することが多いためSlackの方を利用しています。

休憩に対する考え方

複数人で作業を進めていると、どうしても話しながら休憩するタイミングを逸してしまいます。 自分が疲れていても、他の人はまだいけそうかな?など忖度してしまうとなかなか休憩しようよ、とは言いづらいものです。 これはチームが成熟していくとお互いの状態を見て休憩をうまく取っていけるようなのですが、現時点ではルールを作って対応しています。

  • 10分でタイピスト交代(ベストプラクティス準拠)
    • アレクサやスマホのタイマーをセットしてアラームをみんなに聞こえるようにします
  • 30分に一回 10分ほど休憩 (これは最近厳密ではなく、区切りのいいところで休憩しがち)
  • 2時間ぐらいごとに20分ほどの長い休憩
    • (休憩中はチャットの確認なども含む)

タスクの進捗に関して

個々人でタスクをやっている時には、3人チームでベロシティ(1週間に消化できるストーリーポイント)は10pt程度だったのですが、モブプロを始めてからは概ね8pt程度になりました。1/3まで低下することもあり得ましたが、そこまでは低下しませんでした。 それは1人で進めているときには以下のような時間があったのが減ったためだと考えてます。

  • 設計方針を決めるのに時間がかかる
  • 細かい実装でいちいち詰まる
  • プルリクエストのタイミングで前提共有をして全部ひっくり返る
  • 実装詳細の共有の時間

一方で、1人で考えている時にはなかなか取られなかったであろうシンプルな設計方針をチームで考え出せたりします。シンプルな設計に加えて、その設計を確実にそのチームメンバー全員が理解しているのも長期でのリターンとなると考えています。

おわりに

モブプログラミングを実施してみて、当初期待していたチームのベロシティ向上という期待に反してベロシティが下がってしまいましたが、 知識共有や大きなバグの防止、慎重な設計による長期的なリターンなどを考えると、ベロシティ以上の便益があると感じています。 今後、モブプロのやり方を改善していく上で、ベロシティの回復、そして向上を目指しています。

個人的には1人で黙々作業するよりも、チームで話し合いながら作業するほうが楽しいので、チームで工夫しながらより良い開発をおこなっていけるよう今後も試行錯誤を続けていきます。

採用中です

組織拡大!急成長中スタートアップでエンジニアリングマネージャーを募集!! - 株式会社M&AクラウドのWebエンジニアの採用 - Wantedly

Amazon CognitoとCloudFrontで特定のユーザのみが閲覧できる仕組みを作る

こんにちは、久保田(@kubotak_public)です

今回の記事はAmazon CognitoとCloudFrontを利用して特定のユーザのみが閲覧できる仕組みを作る(表題どおり)となります。
弊社での利用シーンとしてSchemaSpyで生成したER図(というよりドキュメント)を特定のユーザ、つまり弊社の人間のみが閲覧できる仕組みを作りたいなという動機で作成しました。
例えばフロントエンドのStorybookなども社内展開する際にはS3に置いたものをどうにかアクセス制限して提供したい・・・みたいなニーズってあると思うのですが、まさにそういう場合にうってつけではないかと思います。

Amazon Cognito

そもそもCognitoとは?という方向けに説明しますと、Auth0Firebase Authenticationに代表されるIDaaSと呼ばれるたぐいのサービスです。
認証の仕組みやユーザーの管理などを内包するサービスです。

全体像

まずは全体像を共有します。
CloudFrontを経由してS3の静的データを配信する素朴な構成の中に、CloudFrontにLambda@Edgeを紐付けて前段に認証の仕組みを挟んでおります。
また、CognitoにはGoogle認証を紐付けてGoogleアカウントによるログインができるようにしています。

① CloudFront

もう少し詳細に説明していきたいと思います。
まずはCloudFrontがS3を参照して静的データを返すという素朴な構成です。
しかし、CloudFrontのビヘイビアで関数の関連付けを行い、ビューワーリクエスでLambda関数が起動するように設定します。
このようにCloudFrontでLambda関数を紐付けるものがLambda@Edgedです。

② Lambda@Edge

CloudFrontで紐付けているLambda関数はLambda@Edgeとして利用するので以下の成約があることに注意が必要です。

  • Lambda@EdgeはバージニアリージョンのLambda関数しか設定できない
  • Lambda@Edgeは環境変数が使えない
  • Lambda@Edgeは5s以内にレスポンスを返さなければならない

さて、このLambda@EdgeではCognitoに連携して以下の処理を行います。

  1. 認証済みかどうかチェック
  2. 未認証の場合はGoogleログインを促す
  3. 認証済みの場合はリクエストを許可する

これを実装しましょう・・・といっても全然お手軽感ないですよね。安心してください。便利なライブラリがあります。

github.com

awslabsが提供してるLambda@Edge用のCognito認証ライブラリを使うと簡単に上記仕組みが実装できます。

※ライブラリのREADMEの通り

const { Authenticator } = require('cognito-at-edge');

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'us-east-1', // user pool region
  userPoolId: 'us-east-1_tyo1a1FHH', // user pool ID
  userPoolAppId: '63gcbm2jmskokurt5ku9fhejc6', // user pool app client ID
  userPoolDomain: 'domain.auth.us-east-1.amazoncognito.com', // user pool domain
});

exports.handler = async (request) => authenticator.handle(request);

先述の通り、Lambda@Edgeでは環境変数が使えないのでここではベタ書きする必要があります。

③ Cognito

続いてはCognitoでGoogle認証を追加します。 また、Google認証時に特定のドメインのみをサインアップ対象とするために一工夫します。

CognitoでGoogle認証を追加する方法はクラスメソッドさんの記事を参照ください。

dev.classmethod.jp

サインアップ時に特定の処理を挟む場合はLambda関数を指定することができます。
後述でLambda関数については紹介するのでここでは登録の方法のみ紹介です。 CognitoではこのようにイベントにフックするようにLambda関数を登録することができます。

GCP

Google認証を行うのでGCPの設定も必要ですが、これに関しては先述のクラスメソッドさんの記事に紹介がありますので割愛します。

⑤ Lambda

Cognitoのサインアップ時に登録するLambda関数を作成します。
こちらはLambda@Edgeではないので東京リージョンで作成しても問題ありません。

この関数はPythonで作成しています。

def lambda_handler(event, context):
    print(event)
    email = event["request"]["userAttributes"]["email"]
    print(email)
    domain = email.split('@')[1]
    if "example.com" == domain:
        print("OK!")
        return event
    raise Exception("bye-bye!")

とてもシンプルな実装です。
Google認証後に受け取ったメールアドレスのドメイン部分を抜き出して特定のドメインであるかどうかをチェックします。
一致した場合はそのまま受け取ったイベントを返し、そうでない場合は例外となります。
この例ではexamile.comドメインのメールユーザーのみサインアップが許可されることになります。

さいごに

いかがでしょうか?
実装自体はほぼやってないと言っても過言ではないレベルでAWSのサービスを組み合わせただけで認証機能を実現できました。
最初はER図用に作った仕組みではありましたが、開発環境のアクセス制御に関しても現在ではこの仕組で運用しています。
BASIC認証等ではなく、Cognitoによるユーザー認証になるので退職者が出た場合にも安全に運用を続けることができますね。

今回の記事はここまで!

PHPerKaigi2022イベントレポート(M&Aクラウドから6人登壇しました!)

こんにちは。エンジニアの塚原(@AkitoTsukahara)です。 先日、開催されましたPHPerKaigi2022(4/9 ~ 11)のイベントレポートになります。 弊社メンバーの発表スライド紹介に加えて、個人的に印象的だった発表をまとめさせていただきました。

PHPerKaigi2022は3日間の開催でオフラインとオンラインのハイブリット開催となっておりました。私はオンラインで参加させていただいていただきましたが、オンライン上でもオフラインに負けないぐらい盛り上がっており、カンファレンスの熱量を久しぶりに感じることができる素敵なイベントでした。

また今回のPHPerKaigiには弊社から6人のエンジニアが登壇させていただきました🎉 弊社メンバーは「全員インフルエンサー」のバリューを胸に、エンジニアであるなら発表で参加したい! そんな気持ちでプロポーザルを提出しております。 イベントが近づくにつれてSlack上では発表スライドをまだ用意できていないエンジニアが焦り始めたり、出来上がったスライドをメンバー同士でレビューしあったりと、社内でも盛り上がりを見せていました。メンバーが一生懸命作成したスライドを1つ1つご紹介させていただきます🙋‍♂️

弊社のメンバーが発表したスライド

(レギュラートークPHPコードを消すライブラリを作った

speakerdeck.com

「全員インフルエンサー」のバリューの旗振り役、久保田さんによるスライドです。 弊社のプロダクトではfeature toggleを活用していて、使われなくなった分岐をphp-delでサクッと削除できるようになりました。チームの生産性がかなりカイゼンされたおすすめのライブラリです。

github.com

登壇者からのコメント

私だけ事前録画だったんで当日涼しい顔してみんなの発表みてました。でも事前録画は結構寂しいので来年はリアル登壇したい!

(LT)Predefined Interfacesを使って便利な独自クラスを作りましょう!

speakerdeck.com

みなさんはPHPerKaigi冊子は見ましたか?あの1面いっぱいに写し出されていた我らがCTO荒井さんによるスライドです。 Predefined Interfacesについて皆さんはご存じですか?よく知らない、利用したことがないとという方はスライドをチェックしてみましょう

登壇者からのコメント

Predefined Interfacesを調べていたら、UnitEnumとかBackedEnumとかあって、早くEnum使って開発できるようにPHPのバージョンをあげようと思いましたね。

(LT)PHPスカラー型をクラスでラップして便利に使えるようにするライブラリ「Stannum」を作った話

speakerdeck.com

まだ入社して3ヶ月という事実を感じさせないくらい大活躍中の大石さんによるスライドです。 「こんなコードを書きたいんだ!」というモチベーションから自作のライブラリを作り上げた内容になっています。 個人的にはライブラリの名前からも大石さんのアイドル愛を感じました

github.com

登壇者からのコメント

初めての登壇でめちゃくちゃ緊張しました。ちなみにライブラリの名前の由来は金属のスズですが、実は私が好きなアイドル(Appare!の藍井すず)から取ったものでもあります!是非皆さんライブラリ使っていただけるとうれしいです!!

(LT)PHPの緩やかな比較の実態

speakerdeck.com

最年少でなんと20歳の國村さんによるPHPの緩やかな比較に関するスライドです。

言語仕様に関する説明はドキュメントを読めば良いのですが、最も正確なドキュメントは何でしょう。 それはソースコードです。

いやー、かっこいいですね。私はPHP自体を形成するのソースコードは読んだことなかったですし、読み解き方もスライドで紹介してくださっているので勉強になりました。気になる方はぜひスライドをチェックしてみてください。

登壇者からのコメント

初めての登壇超緊張しました。反応が見れなくて寂しかったので来年はぜひリアル登壇したい!

(LT)【Laravel】サクッとN + 1問題を見つけて倒しチャオ!

speakerdeck.com

チームのムードメーカー津崎さんによるN +1 問題に関する発表です。私が見ていた中ではLTの中で一番盛り上がっていのではないか?という位に会場が湧いた発表になっていました。チケットをお持ちの方はぜひニコニコで実際の発表内容を見ていただきたいです

登壇者からのコメント

みんなもタイムオーバーになって会場を沸かせよう!

(LT)気づいた時にリファクタしよう!Laravelのデータベースクエリを最適化するTips

speakerdeck.com

登壇者からのコメント

こちらは私のLaravelのデータベースクエリを最適化Tipsを紹介するスライドになります。ふりかえると折角のLTだったのでもう少しエンタメに振った内容にしてもよかったかな?と思っています。Laravel開発をしている時にコードレビューのヒントにもなると思うので、一読いただけると嬉しいです。

個人的に印象的だった発表

予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント

speakerdeck.com

テスト駆動開発で有名な@t_wadaさんのセッションです。 リアルタイムではt_wadaさんのセッションを始めてで、サンプルコードがPHPというなんてお得体験なんだろうと思いながら視聴していました。個人的にはPHPが型厳格な方向に進んでだ恩恵でt_wadaさんセッションが生まれたのでは?と思いましたw

堅牢な設計について分かりやすく、具体的な説明はその日からコードを書く時には意識したいものばかりでしたね。 まだご覧になっていない方にはおすすめです

チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた

speakerdeck.com

@vacchoさんによるチームビルディングの発表です。 私は先月から専任スクラムマスターをやらせていただいているので、テック寄りの発表以外にもチームで取り組むプロダクト開発に関する発表を視聴していました。チームの課題をメンバーとの議論から少しづつ見える化していくアプローチは、そうですよねーと相槌を打ちながら聞いていましたw また1o1ログを公開・非公開のバランスを取りながら、残していく気遣いは私も意識していきたいなと感じました。

最後に

いかがだったでしょうか? 今回は予定が合わなくて参加できなかったり、終わってからイベントに気づいた方でもスライド資料を読むだけでも新しい学びや気づきがあると思います。気になったセッションがあれば、どんどんスライドを読んで、そして次回はPHPerKaigiに参加しましょう! 来年もPHPerKaigiが開催予定があると実行委員長の長谷川さんがおっしゃていましたので、次回も弊社メンバーは全員プロポーザルを出して、「全員インフルエンサー」を発揮していきたいと思います!(個人的にはLTではなく通常のセッション枠の登壇を狙っていきます!💪)

また、弊社ではエンジニアの採用を行なっています。 スタートアップ企業でのWeb開発に興味のある方は、ぜひカジュアルにご応募ください🤝

www.wantedly.com

www.wantedly.com

www.wantedly.com

20%税金ルールとインフラタスクの優先度定量化の試み

f:id:yamotuki:20220415125932p:plain こんにちは。エンジニアの鈴木(@yamotuki)です。
本日はインフラタスクの優先度の定量化の試みについて書いていきたいと思います。
ここでいうインフラタスクとは以下のようなタスクが含まれます。

  • 可用性と信頼性に関わる障害対応, バグ対応
  • ベロシティとストレスに関わる業務効率化(DX: Developer eXperience)
  • セキュリティやライブラリバージョンアップなど

これらのタスクについて「何を一番優先して取り組んでいくべきか」という優先度について長く頭を悩ませていましたが、私の中で一定の答えが出たので共有いたします。

20%税金ルールについて

私はインフラタスクは概ね20%は必ず時間を割かないといけない税金のようなものだと考えています。 税金を払わなければ将来にツケを回すことになり、利息が付いて後で降ってきます。 20%のアイディアはThe DevOps ハンドブックで紹介されているものでした。 書籍では以下のように書かれています。

組織が”20%税”を支払わなければ、全てのサイクルを技術的負債の返済に充てなければならなくなるようなところまで、技術的な負債が膨らむ。 サービスがどんどん不安定になっていくため、ある時点で突然機能を追加できなくなる。全てのエンジニアがシステムの信頼性の改善や問題点の回避のために追われるようになってしまう。

今の状況が特に悪い場合には、20%を30%以上にしなければならないかもしれません。 しかし、20%よりはるかに小さい割合でなんとかなると思っているチームを見ると心配になってきますよ。

この話を根拠に、jira の「インフラ」エピック(ラベルようなもの)が付いているタスクが、全てのタスクの消化量の何%程度になっているかざっくり計算し、低くなりすぎる場合にはPdMとエンジニアが交渉するようになっています。

ただ、やりたいことというのは無限に増えていくものです。20%を闇雲にやっていけばいいわけではなく、重要なものから優先順位をつけてやっていく必要があると考えています。

優先度定量化について

定量化の導入検討

以下に定量化の試みの初期検討資料の冒頭を引用します。

これまで、インフラタスクの優先度つけは、特定の人が属人的に優先度を決めていた。
その優先度付けのプロセスはブラックボックスであり、非効率であった。

特定の指標に基づいて優先度を定量化することでバックログの並び順を変えることができれば、属人化を排除して、開発チームの誰もが納得できる優先度になるのではないか。

この記事の最初に書いたように、「インフラ」に含まれるタスクは広範囲に渡ります。 種々のタスクを並列に優先度比較できることを目指しました。

タスクの分類とスコア

インフラタスクを発生している現象ベースでまず以下のように分類してみました。

  • 障害が発生しているか
  • 業務に支障が発生しているか
  • 攻撃が容易な状態にあるか
  • ライブラリが古いか

その分類の中で、例えば「障害」であれば以下のように傾斜をつけることにしました。

  • (障害)障害が既に起こっている
  • (障害)障害がいつでも起こりうる
  • (障害)障害が起こりうる可能性は高くない
  • (障害)障害が起こりうる可能性は低い
  • (障害)障害が起こってから対応しても問題ない

業務効率化については以下のような形です。

  • (効率化)無いと特定のタスクを進められない
  • (効率化)著しく業務に支障がある
  • (効率化)蓄積すると業務に支障がある

タスクのインパク

発生している現象に対して、その現象がどの程度インパクトがあるかを考えれば優先度を決定できるのではないか、と考えました。 インパクトについては、まずは”影響範囲が広いか”が重要だと思います。しかし、業務改善に関わるタスクについては開発者のみに関わるためユーザに関わるものと同列に比較することは難しいです。そこで、「開発者への心理的影響が大きいか」という視点も入れることにしました。ストレスが下がるような改善をすれば業務効率が上がり、間接的に良い影響が広範囲に出るであろうという仮説です。

例えば「障害」であれば以下のようにインパクトの傾斜を設定しました。

  • (障害)ユーザ行動を大きく阻害する
  • (障害)ユーザ行動を稀に阻害する
  • (障害)ユーザ行動を阻害しない

業務効率化については以下のような形です。

  • (効率化)解決されると高ストレスが解消される
  • (効率化)ややストレスが解消される
  • (効率化)そこまでストレスはかかっていない

スコアリング方針

上記の”発生している現象”と”インパクト”をそれぞれスコアをつけて、掛け算したら最終スコアが出るのでは、とシンプルに考えました。初期アイディアとしてはセキュリティ領域でよく使われているCVSSスコアを参考にしましたが、正直なところ跡形もありません。

具体的にはスコアリングは以下の掛け算で行われます。

  • 障害対応、バグ対応(可用性、信頼性)
    • 障害発生状況 * 影響度
    • 例えば、メール障害であれば(障害)障害が既に起こっている * (障害)ユーザ行動を稀に阻害する
  • 業務効率化(業務ベロシティ、ストレス)
    • 業務支障度 * ストレス
    • 例えば、AIテストツールの導入に関しては (効率化)著しく業務に支障がある * (効率化)解決されると高ストレスが解消される
      • ※最終的には現時点ではコストに合わないとなり、導入は見送りました
  • セキュリティ
    • 攻撃容易性 * 影響度
  • 保守(バージョンアップ系)
    • ライブラリの古さ * 影響度

スコアリング計算実務

計算は単純な掛け算なのでスプレッドシートで行うこととしました。変更も容易です。 実際に使用しているものから公開できる部分だけを切り出し、公開用スプレッドシートを作成しました。ご興味がある方はご自身の環境にコピーし、実際にタスクについてスコアリングしてみてください。

docs.google.com

スプレットシートの内部の構造について軽く説明します。

  • 青い背景領域: 選択肢の定義とそのスコア定義
  • 黄い背景領域: 対象となるタスクリストと、選択肢を二つ選択してスコア計算する領域
    • 順番のイメージを持ってもらうために、外部に公開しても差し支えないだろうと思われるタスクのみ残してあります(出してヤバそうなのがあったらこっそり教えてね!)。
    • 選択肢は、同じタスク分類二つの軸を選択する。発生している現象軸が (障害)障害が既に起こっているなら、インパクト軸にも同じ接頭の(障害)を持つ(障害)ユーザ行動を稀に阻害するを選択する
  • 緑の背景領域: スコアリングの結果、タスクをソートした領域

※各タスクのスコアは良い間隔で分布しているわけではなく、素点数値の微調整によりほんの 0.01 点だけ差があるということも多く発生しています。この数値の絶対値に大きな意味はなく、並び順を決めるためだけに使用されています。「人間が都度判断するとしたらこういう並び順になるだろう」という並び順になるために、素点は微調整をしています。会社によって素点はかなり変わってくると思います。

運用について

以下のようなSlackワークフローを用います。エンジニアは選択肢を選んで理由を書いて投稿します。そうするとその内容がチャンネルに自動投稿され、NoCodeツールであるZapierがキャッチしてJira issueを自動生成します。

f:id:yamotuki:20220415125312p:plain
Slackワークフロー

作られた Jira Issue を、作成したエンジニアが上記スプレッドシートを使用してスコアリングします。最後に(残念ながら)手動でJiraのバックログを並び替えてもらいます。

終わりに

元々は特定のエンジニアが頭を悩ませて優先度を決めていたインフラタスクでしたが、同種のタスクは毎回同程度の優先度になることにより判断コストが劇的に下がりました。 また、各エンジニアは優先度を最初から考えるわけではなく、分類だけ考えればいいので判断は難しくありません。
バックログの並び替えまで各エンジニアにやってもらうことができるようになったので、エンジニア人数が増えてインフラタスクが増えても運用を続けることができてスケーラブルな仕組みとなりました。

採用してます!

スケーラブルなチームの仕組みを一緒に考えてくれるエンジニアリングマネージャー募集しています!

組織拡大!急成長中スタートアップでエンジニアリングマネージャーを募集!! - 株式会社M&AクラウドのWebエンジニアの採用 - Wantedly

他にもデータエンジニアと機械学習エンジニアが積極採用中職種です。