M&AクラウドはPHPカンファレンス2022のゴールドスポンサーです

弊社M&Aクラウドは2022年9月24・25日に開催されるPHPカンファレンス2022のゴールドスポンサーです。
phpcon.php.gr.jp PHPカンファレンスプログラミング言語PHP」を扱う日本最大の技術カンファレンスです。
弊社から4人のメンバーが登壇予定です。

fortee.jp

fortee.jp

fortee.jp

fortee.jp

今年はオンライン・オフラインのハイブリッド開催となり、2019年以来の大田区産業プラザPiOにて開催されます。
弊社メンバーも数人は会場にて参加予定ですので見かけた際にはぜひ声をかけてください!

【スタートアップ】会社フェーズ毎のデータ活用とデータ基盤の「これから」

こんにちは。エンジニアの鈴木(@yamotuki)です。
データの活用とデータ基盤の構築に関する考えを整理したいと思い、この記事を書いています。

M&Aマッチングプラットフォームとしての「M&Aクラウド」が立ち上がった4年ほど前から、現在に至るまでデータをどう活用してきたのかというのをざっくりまとめた後に、今のデータ基盤構想とその背景にある考えを書こうと思います。

弊社におけるこれまでのデータ活用に関わる意識の変化

2018年~ M&Aクラウドリリースからしばらく

M&Aクラウドプラットフォームのリリースは2018年4月です。データ分析に対して深い知見のある人は社内にもちろんゼロ。しかし、google analytics は入れてあり、mouseflow などユーザ行動が見れるサービスも入っていました。mouseflow は登録フォームのファネルでの離脱点がどこかといった分析もできます。これらを活用して、PdMとエンジニアは以下のような分析をしてサービス改善をしようとしていました。

  • アクセスの多いページはどこだろう。全てのページが最低限の機能しかない。analyticsを見てユーザ数が多いところから改善していこう。
  • 登録フォームの離脱が多い項目はどこだろう。mouseflowを見てフォームの項目を整理したりステップ分離したりしよう。
  • 迷いを生む機能はどこだろう。mouseflowのレコーディングを見て、特にユーザがストレスを抱えているところはどこだろう。

弊社の主な顧客は経営者であり、母数としては多くないのでデータ量としてはあまり多くありません。多くの改善は定性的な情報に基づき「えいや!」で改善をしていました。また、KPIなど指標に関わるデータはエンジニアが依頼を受けて都度出力する運用になっていました。

2020年~ Redashの導入

Redash を導入しました。Redash はデータソースとなるAmazon RDSやSalesforceスプレッドシートなどに直接繋がっている状態でした。プラットフォームにおける主要な指標を、まずはPdM(マーケ担当兼任)が中心となって測定して改善に繋げようという目的です。
会員登録数やアクション数などがオンデマンドで情報が取れて、ダッシュボードで一覧として見れるようになりました。これにより、重要な指標になる数値がチーム朝会などで確認できるようになり、エンジニア側でもプラットフォームの数値への関心が高まってきました。

その後、Redashは顧客サポートをするメンバーをはじめ、社内全体にも解放されました。このRedash解放により、非エンジニアでも当たり前のようにSQLを使ってデータベースを参照する人が現れました。目的は、顧客サポートのための情報整理や、データ分析をしてサービスの改善提案をすることです。

2022年 データソン実施

弊社では定期的に(本当に定期なのか怪しいですが半年または1年ごとに)全社員が参加するハッカソンが開催されています。第3回ハッカソンは「データの活用」をテーマにしたいわゆる「データソン」です。

データソンを行う背景には以下のような意図がありました。(当時の資料から引用)

データソン。空・雨・傘で表現すると

- 空: データ活用の度合いが人によって偏っている
    - e.g. Redash のクエリ作成者が社内の特定のメンバーに偏っている
    - e.g. 部署に少なくとも1人ずつ詳しいメンバーが理想的だが、(多分)そうなってない
- 雨: 社内にどのようなデータが存在するのか、データをどのように扱えば良いのか、分析すると何が分かるのかイメージできてないのでは(仮説)
    - e.g. データの構造やフラグ値の意味などをエンジニアしか知らない
- 傘: ハッカソンで実際にデータ活用の流れを体験してもらう

全社にデータ活用の意識を持ってもらい、データドリブンの意思決定をすることで会社成長を加速させていこう、ということですね。今回は「データに親しむ」を重視して分析対象は絞り、対象データはプラットフォームのデータベースにあるもののみとしました。  

データソンは前半と後半の二部構成でした。

  • 前半: 学習の時間です。全社員がそれぞれ何人かのチームに別れて「神父」と呼ばれるSQLが既に書ける人に導かれてSQLを学んで書きました。なぜ「神父」かについては、以下の実際に出された問題のスクショを見てもらうとピンと来る人も多いのではないでしょうか。
  • 後半: 応用の時間です。「与えられた仮説からデータベースから事実を集め、何かの提案をする」というお題でした。仮説はたとえば「承諾されやすい打診には何か特徴や条件があるのではないか?」のようなものです。

データソンの問題例

各チーム奮闘し、結果の発表ではそのまま実務に使えそうな分析もありました。データソンにより、データとSQLに対する苦手意識はかなり減り、(おそらくCTOの筋書き通りに)会社としてデータ活用をしていくぞ!という機運が高まりました

これからのデータ利活用

このセクションはとても”文字文字しい"です。ちょっと読むのが大変かもしれませんがお付き合いください。

解決したい課題の全体観

さて、データソンで全社のデータ活用の機運は高まったのですが、まだまだ課題は残っています。大まかな課題は以下のものだと思っています。

  • ①プラットフォームのみのデータだけを見ていては、会社を効率よく成長させられない
    • プラットフォームには、会員登録をしてくれて、その上でマッチングした企業の情報しか載っていません。弊社はプラットフォーム外でのマッチングサポートも一部していますので、プラットフォームDBのような一つのデータソースをもとに分析した結果は、会社全体の成長という面だと片手落ちです。(※「プラットフォーム外」というのは、売り手や買い手またはその両方がプラットフォームに情報を登録していないケース。メールや電話での連絡など。)
  • ② 全社員がバラバラに似たようなクエリを書いていては、効率よく正確な意思決定ができない
    • エンジニアでもちょっとした情報が無いだけで集計を間違ってしまいます。たとえば deleted_at がついているものが論理削除されており「全数を count() で取得するときには deleted_at が null のものを外しましょう」という情報を全社員が持てるでしょうか(ここまでは分かっても、もっと複雑な条件は沢山あります)。部署ごとにちょっとした理解の違いで似たようなクエリを書いています。これでは信じられる結果にならず、全社の目線を合わせてコミュニケーションできません。

これらの課題の解決方法の一つの答えは、データ基盤を構築して、よく使う指標の元になるデータをデータウェアハウス(以下DWH)やデータレイクに整理して格納しておくことだと考えています。①については、データレイクにはそれぞれ格納した後に、DWHで集約し、プラットフォーム情報もそれ以外の情報も同列な情報として触れるようにすればよさそうです。②についてはデータ取得元としてデータウェアハウスに集約して、例えばdeleted_at問題であれば削除済みユーザはそこに居ないなど整理すればある程度解決しそうです。

データにまつわる具体的な課題

マッチング支援がスケールしない

主に①の課題に関わる部分です。
売り手と買い手のマッチングは弊社社員が頭を捻って紹介するのですが、人間の頭の中の情報だと限界があります。また、レコメンドとして推奨するマッチング先をシステムとして提供する場合も、参照できるデータがなければいけません。M&Aのマッチングは転職のマッチングとある程度類似性があるのでそれで想像してもらえると分かりやすいかもしれません。いくら「いい人」(M&Aだと「いい会社」)であっても、それにマッチする会社(M&Aだと買い手)に会えなければ良い転職はできません。転職エージェントは、転職希望者には沢山のマッチする会社を教えてあげる必要があります。

このマッチング問題に対しての解決策は、データとして「現状の買収戦略や予算」「M&A経験があるか」など各種データがリストで見ることができて、簡単に検索できるようになることが一つ考えられます。そこには世間に一般に公開されている情報以外にも、弊社のメンバーが力を使って集めた鮮度と確度の高い情報が載っているべきです。M&Aの業界だといわゆる"ロングリスト"と呼ばれるものですが、これが簡単に精度高く作れれば、マッチングの質が一定改善されるのではないかということです。
現時点では、そういった集められた情報は社内のスプレッドシートや各部署が管理しているSalesforce, プラットフォームのDBなどに散らばっており、全社横断で見れる形になっていません。

これの解決策としてデータ基盤として情報がデータレイクに集められ、クレンジングされた上でDWH・データマートに乗っかってすぐ取り出せる形が良さそうです。初期的にはSalesforceに情報を集約して暫定のロングリストとして提供することになりそうですが、その次のステップとしてSalesforceを一つのデータソースとしてその他のソースの統合をしていく可能性は高そうです(※構想段階なので2022/09/05時点では社内でも諸説ありです)。

全社に関わるKPIツリー構築が属人的

主に②の課題に関わる部分です。
弊社ではKPIツリーを作ることで、KGIとそれにたどり着くためのKPIの関係を明らかにして、事業のモニタリングと戦略決定に役立てています。KPI個別については戦略に関わるのでここには詳しくは書けないのですが、このKPIツリー作りが属人的になってしまっている問題があります。KPIツリー作りは社内の各所から人力とエクセル力により収集されて実現されています。人力だと転記ミスなども起こり得ますし、部署ごとにKPIの数値集計基準が揃っていないと信頼に足るデータではなくなってしまいます。例えば(※弊社KPIがどうであるかはぼかしつつ、あくまで例えば)プラットフォームの登録数ひとつとっても、「登録延べ人数」をとるのか「削除など考慮した月末時点での登録数」を取るのかで情報が変わってきます。こういった情報が「どのデータソースから」「どういう基準で整形して取られているのか」がコードレビューするようにレビューして整理できたら、より強固な情報をもとに意思決定をすることができるようになります。技術的な大枠の話だと、データレイクからDWHとデータマートを構築するときのSQLがレビューされて、運用開始後もいつでも集計基準を参照して間違いがあれば修正できるようになっているとよさそうです。

成約までがブラックボックスになっている

これは直近で全てを明らかにするというより、特に長期で取り組んでいきたい課題になります。
マッチング後のM&Aの成約までは、関係者の情報の非対称性や思惑が交差することにより定式化するのが難しい「ブラックボックス」になっている部分です。
マッチング後から成約までのフェーズには以下のようなものがあります。

  • 初回面談
  • 2回目面談
  • 買い手による「買収意向表明」
  • 見えている情報を前提とした買い手と売り手の「大筋合意」
  • 買い手による売り手の「実態詳細調査」
  • 成約

ここのデータの問題としては、どのような登録経路や、どのようなサポートをした顧客が、最終的にどのような成約につながっているかというのがデータとして"繋がっていない"という認識です。登録数もある、マッチング数、意向表明数もある程度取れている、成約数も取れている、となっているのですが、それがそれぞれ独立した集計値になってしまっているのです。
理想状態としては、成約に関わる情報が一元管理されており、その成約はどこから入ってきた売り手買い手のマッチングで、間のフェーズで何が起こったから成約したのだろう、というのが分かる形です。各フェーズでうまく行ったケースとうまくいかなかったケースの情報が、流入経路や途中でのサポートアクションを含めて整理されていれば、分析によってさらに適切なサポートを行えそうです。

データ基盤の技術選定に関する考え方

さて、データ基盤を作ろうという気持ちが高まって、目的もある程度見えてきたとします。技術チームがどのような意図で技術選定を進めているか、整理してみたいと思います。
構想段階での構成図は以下のような形です。

データ基盤の構成図

弊社は、昨年大型の資金調達を完了し、売上が伸びてきている状態です。一定のデータは集まってきており、データにより意思決定をできるフェーズになっています。ロングリストがしっかり運用できれば大きなリターンも見込めます。一方で、「データエンジニアリング」の専門家は正社員にはまだおらず、業務委託と顧問の方の知見を借りてアプリケーションエンジニアが構築を進めております。そういう背景ですので、技術選定は以下の前提でやっています。

  • 技術的なハマりどころを減らしたい。はまっても技術情報が豊富にあるツールが良い
    • データエンジニア専門としている人がメインで手を動かすわけでは無いので、ETL特有の辛さをある程度吸収してくれるツールが良い。クラスタ管理など自分たちでしたくない。
  • 使えるようになるまでの立ち上がりを速くしたい。種々のデータソースの対応は可能な限り楽にしたい
  • 長期に渡ってデータ活用したいので、作りっぱなしではなく改善サイクルを回しやすい方が良い
  • 価値があるデータが整理できるなら、ある程度お金を払うのはOK

その前提を受けて、今使用しようと検討している技術要素は以下のものです(本職データエンジニアの方、ツッコミ歓迎です!)。

  • DWH
    • インスタンス管理の必要なRedshiftではなくて、SnowflakeやBigQueryが良い(今のところBigQueryが有力)。Redshift Serverlessもよさそうだが、一般で使えるようになったばかりでハマると辛い可能性。
  • ETL
    • trocco
      • 種々のデータソースに対応(少なくともMySQL, スプレッドシート, Salesforceなど今見えている対象に対応) => ETLの辛さの低減
      • 日本でのユースケースが多い。サポート厚そう。slackベースで聴ける。 => 専門家リソースが少なくてもカバー
  • その他ツール
    • dbt cloud
      • BigQueryでデータレイク, データウェアハウス, データマートを構築したとして、その間の更新をできるだけ楽にしたい。使われ続けるための更新大事。

最後に

前述した通り、専門性を持って正社員でデータエンジニアをやっている人はまだいません。正社員でデータエンジニアをやってくださる方を募集してます。方向性に共感してくださった方がいらっしゃればお話ししましょう!
「こいつわかっていないからいっちょ教えてやるわ」という方も歓迎です。お話ししましょう。 twitter@yamotuki の方までDMを下さってもいいですし、実際にデータ活用を旗を振っているCTO荒井とのmeetyもあります。 よろしくお願いします!

新規プロジェクトのフロントエンドにSvelteKitを採用しました

こんにちは、こんばんは、kubotakです。(@kubotak_public

今回の記事は、M&Aクラウドの新しいプロジェクトのフロントエンドにSvelteKitを採用したよ!という内容です。
なぜSvelteKitを採用したのか、他のフレームワークと検討してなぜ採用に至ったのかなどを紹介したいと思います。
SvelteKitってなに?SvelteKit気になってる!な方はぜひお付き合いください。

SvelteKitってなに?

数年前からReact、Vue.jsに次ぐUIフレームワークとして人気が出ているSvelteをベースにしたSSRやSSGが実現できるフレームワークです。
Svelteは日本では名前を聞いたことがある程度で詳しく知らない。という方が多いと思いますが、StateOfJSでは2019年から人気のあるフロントエンドフレームワークです。
また、SvelteKitは2021年で満足度・興味ともに最も注目されています。

kit.svelte.jp

なぜSvelteKitを採用したの?

お世辞にも現時点で日本で知名度があるとは言えないSvelteKitをなぜ採用に至ったかという経緯をお話したいと思います。
まず弊社ではNuxt.jsを採用したプロジェクトがあります。
シリーズでNuxt.jsの採用事例を紹介しています。

tech.macloud.jp

新たに新規プロジェクトのフロントエンドの技術選定をすることになり以下を念頭に検討しました。

  • SSRができること
  • 素早くキャッチアップできること
  • 将来性のあるもの(?)

SSRできること

SEO周りでやはり未だにSSRできるという点は捨てきれず、検索流入が重要であることからこの要件は外せませんでした。
そのため、この点についてはNuxtもしくはNext.jsが真っ先に候補に上がりました。
この時点ではSvelteKitは存在こそ知っていましたが検討のステージに上ることはありませんでした。

素早くキャッチアップできること

弊社ではNuxt.js(v2+Typescript+CompositionAPI)による開発実績があるためNuxt3を採用した場合のキャッチアップについては懸念がありませんでした。
しかし、Next.jsについてはNext.js及びReactに関して深く理解できているメンバーがほぼいなかったこと、初期導入レベルを私が試した限り、useCallbackやuseMemoなどの利用の見極めやディレクトリ設計などに知識がないため開発の初動に大きく影響があるという感触を受けました。
今回の新規プロジェクトは最初期のリリースまでの速度が重要であり、開発スピードが重要視されているためこの点に不安を大きく覚えました。

将来性のあるもの(?)

昨今の流行りを鑑みると、やはりNext.jsが優位ではないかと思います。私はそう思いました。
逆にNuxtは、3年ほど前がピークでそこから人気に陰りがある印象を持っています。恐らくNuxt3のリリースがまだ正式ではないからではないかと思っていますが、エンジニア採用などを考えるとやはりReactが強いだろうなと思います。
さらにVue.jsに馴染みのあるエンジニアでも実はVue.js v2が大半で、v3構文になれている方は少ないのかな?という印象があります。

Next.jsか、Nuxt3か・・・

先程の検討をした結果、Next.jsかNuxt3か、という選択は意外にもPros/Consどちらもあり悩ましい結果となりました。
また、個人的な気持ちはNext.jsに寄ってはいたもののViteという新興のバンドルツールにも注目していて、Nuxt3はこのViteを採用していて開発体験を高めてくれそうだという期待があり、更に悩みの種となりました。
もういっそのこともっと新しいことにチャレンジしても良いのかもしれない?という気持ちになりSvelteKitを検討のステージにあげてみました。

SvelteKitを試してみて

公式ドキュメントは有志により日本語翻訳されているのでとても親切です。翻訳されていなくても公式ドキュメントは丁寧な作りで読むことに苦労することは少ないです。
SvelteKitもNext.js/Nuxtと似たようなルーティングの仕組みで違和感なく使えます。むしろSvelteKitの方が一歩進んでいる印象で、ファイル名に応じてAPIとして振る舞うエンドポイントを作れたりします。
Nuxt3のサーバールーティングをページルーティングと併せ持っているような感じです。
やはりSSRを利用するフレームワークですのでサーバー側でやりたい処理を分離できる仕組みを持っているのは好印象です。
また、バンドルツールとしてViteにもすでに対応していて起動がとても速くて快適です。
マイナスな面としてはいくつかあります。やはりまだメジャーバージョンとして正式にリリースされているものではないのでNuxt3同様の懸念があります。むしろNuxt3より開発が活発な印象があり今でもあらゆるAPIが議論を重ねて変わっています。

それぞれを比較して

これで検討にはNext.jsとNuxt3、SvelteKitがあがりました。
全てにおいてPros/Consはあり、どのフレームワークを採用しても私達が作るアプリケーションには過不足のない機能を持っています。
そこで、以下のことを満たしたフレームワークを採用することにしました。

  • メンバーからの同意が得られること
  • ワクワクするもの

1つ目の理由は当然として、2つ目の理由はやはりモチベーションを高く持って開発したいという意図があります。
その点でSvelteKitはまだ正式にリリースされているものではなく、いち早くプロダクションで使うことになります。
SvelteKitを使っている企業といえばM&Aクラウド、と思われるように先人を切って行くことはワクワクしませんか?

採用してみて

SvelteKitの開発はとても活発で体感としては2~3週間にはAPIの変更がある印象です。Github issueやDiscordを追ったりしていれば事前に変更を予測できますし、ルーティングの大幅な変更に関しては移行ツールも用意されています。
新しいAPIや仕様変更に伴いバグも発現してきますが迅速に修正されている印象があります。幸いにもまだプロダクションに動いているわけではないので開発を続けながら問題なく最新バージョンに追従して行けている状態です。

さいごに

SvelteKitで開発してみたいという方はぜひ弊社に声をかけてください!

www.wantedly.com

PHPカンファレンス沖縄2022に弊社エンジニアが登壇します

phpcon.okinawa.jp

PHPカンファレンス沖縄2022は2022年8月27日(土)に開催されるプログラミング言語PHP」の利用者をターゲットとした技術カンファレンスです。

弊社M&Aクラウドから、以下のタイトルで2名のエンジニアが登壇します。

FeatureToggle戦略と運用方法

レギュラートーク 10分
KenjiroKubota

新機能のリリースはどのように行っていますか?
私が所属している会社ではFeatureToggleを利用した機能リリースを行っています。
FeatureToggleとは、新機能のコードを含んだままデプロイを行い、フラグ機能により新機能の有効化・無効化を制御する手法です。
FeatureToggleでリリースするメリットとデメリットについて、またどのように運用をしているのかを紹介できればと思います。

スクラムマスターを経験して得られた学びとエンジニアとしての成長

レギュラートーク 30分
AkitoTsukahara

みなさんはスクラムで開発されていますか?スクラムマスターを経験したことがありますか?
何となくスクラムマスターはエンジニアマネージャーやリーダー適性のある人だけがやるものだと思っていませんか?
確かに適性はあるかもしれませんが、スクラムマスターを経験することで、エンジニアとしてのパフォーマンスが上がるきっかけも手に入れられると自分は考えています。

スクラムマスター経験を通じて得られた学びが、現在エンジニアとしてどのように活かされているのか、ご紹介できればと思います。

▼エンジニアにも効くスクラムマスター経験の学び
・ユーザ価値をより意識する
・アンラーンの面白さ
・ファシリテーション能力
・チームの観察と声かけ

本トークを聞いて、迷っていたけどスクラムやってみようかな?スクラムマスターやってみようかなと思って貰える人が少しでも増えたら幸いです!

2名とも当日は現地沖縄での参加予定です。
会場でお会いしたらぜひ声をかけてください!

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