スタイル・エッジ技術ブログ

士業集客支援/コンサルティングのスタイル・エッジのエンジニアによるブログです。

GitHub Copilot Businessをチームに導入してみた

はじめに

こんにちは。システム事業部イネイブリングチームに所属しているOrochiです。
イネイブリングチーム*1は、開発チームの開発生産性向上の活動や技術支援などを行っています。
その取組みの一環として、GitHub Copilot Businessを導入しました。
現在は、開発チームメンバーであれば申請することで誰でも使える状態になっています。

Created by DALL-E

この記事では、GitHub Copilot Businessを導入した背景や注意点、導入後の効果などについて紹介します。

GitHub Copilotとは?

この記事を読んでくださっている方は既にご存知かと思いますが、GitHub Copilotは、OpenAIのCodexモデルを利用したコード補完ツールで、プログラミング中に次に書くべきコードを提案してくれます。 docs.github.com docs.github.com

Visual Studio Codeなどのエディタに拡張機能を導入することで利用可能です。
また、GitHub CLIを使用するとコマンドライン上でも動作します。

Copilot Chatという機能もあり、こちらを使用することで特定のファイルに記載されている処理の解説などをCopilotにお願いすることができます。
例えば、私はOSSのフレームワーク理解のために、内部処理の解説をさせています。

Laravelの内部処理を聞いている画像

導入の背景

弊社の開発チームがGitHub Copilotを導入するに至った背景には、以下の社内課題とニーズがありました。

エンジニアの不足

会社の成長と共に、開発チームのプロジェクト数が増える一方で、エンジニアの増員が追いつかない状態です。
それに対して、イネイブリングチームとしては効率化に関する各種施策を打ち出していましたが、開発チームの協力が不可欠でありながらも、日々の業務に追われて協力の余裕がない状況が続いていました。

記述量の多いコード

最近は、従来のプロダクトの反省を踏まえて、スパゲッティコードを避けるための適切なファイル分割と責務の分離を徹底しユニットテストを必ず書くようになりました。
しかし、それに伴ってコードの記述量が増加し、物量的に開発スピードが出なくなるといった問題が発生していました。

導入を決定した理由

これらの課題に対処するため、GitHub Copilotの導入を決定しました。その理由は以下の通りです。

生産性向上の効果が期待できた

他社の技術ブログなどで情報収集をし、
「繰り返しの作業を迅速にできるようになった」
「開発効率が上がった」
という記事が多かったため、コードの記述スピードや正確性を向上させ、反復的な作業やテストコードの自動生成に効果が期待でき、組織の開発生産性の向上に繋がると考えました。

既存のGitHubアカウントの活用

弊社では現在ソースコードの管理をオンプレミスのGitLabで行っていますが、イネイブリングチームの施策として、CI/CDの拡張性などを加味してGitHubへ移行する計画がありました。
そのため、一部では既にGitHubを利用しており、サブスクリプションも進めていたのでスムーズに導入できました。

チームメンバーの興味

現場からもCopilotを使用したいという話は上がっていて、その後押しもあり意思決定は比較的スムーズにできました。上司の理解があったのも大きかったです。

注意点とデメリット

社内のコードの漏洩やライセンス侵害などの可能性

GitHub Copilotを利用する際に、社内のコードがGitHub Copilotの学習に利用され、外部に漏れるリスクが懸念されました。また、OSSのコードを参考にコード補完を行うため、脆弱性を埋め込んだり、ライセンス侵害をしたりといったリスクも考えられます。

弊社は、公開コードに一致する提案を無効に設定しているため、OSSのコードをGitHub Copilotが提案しないようにしています。*2
このため、ライセンス侵害の可能性は低く、リスク許容としています。
脆弱性に関しては、複数人でコードレビューすることでセーフティネットを張っています。

若手メンバーの成長を妨げる可能性がある

AIサービス全般に言えることですが、今は「考える」ということをAIに任せられるため、経験年数の浅い若手メンバーが最初からGitHub Copilotなどを使用してコーディングをすると、AIなしではコードが書けない人材になってしまうことが懸念されました。

こちらは、コードレビューでコードの意図を問うようにしたり、ペアプロを通して教育していくことで、何も考えずにGitHub Copilotから提案されたコードを採用するのではなく、自分で書いたコードとして判断する癖をつけるように対応しています。

導入後の効果とメリット

コーディング効率の向上

GitHub Copilotの導入により、特にテストコードや反復的な作業において、コーディング効率が大幅に向上しました。具体的には、次のような効果がありました。

テストコードの自動生成

同じようなテストコードを何度も書く手間が省け、効率が向上しました。
また、ある程度コメントでどういったテストを書いてほしいかを示すことで、全く新しいテストコードも生成可能になりました。

プロダクトコードの自動生成

プロダクトコードもGitHub Copilotに提案してもらえるようになり、こちらも作業効率が向上しました。
適切なファイル分割と責務の分離を徹底していたため、Copilotから提示されたコードは比較的正確なものであることが多いです。

フレームワークやライブラリの理解の補助

弊社のアプリケーション開発では主にLaravelを使用し、中には設計思想に応じた拡張を加えているものもあります。GitHub Copilotによって、Laravelや拡張部分の理解が進み、実装のスピードが向上しました。

開発チームのフィードバック

GitHub Copilot導入後、他社の技術ブログやSPACEフレームワーク*3を参考に開発チームにアンケートを実施し、以下のようなフィードバックを得ました。

GitHub Copilotは何をする時に使用しますか?

一日の仕事の中で、コーディングに費やす時間はどれくらいですか?

GitHub Copilotを使用することでより生産的に仕事ができるようになりましたか?

GitHub Copilotを使用することでより早く繰り返しの作業を完了できるようになりましたか?

GitHub Copilotを使用することでより早くタスクを完了できるようになりましたか?

GitHub Copilotを使用すると繰り返し作業に費やす精神的な負担が軽減されましたか?

1日あたり、GitHub Copilotを使用することでおおよそどれくらいの時間を節約できましたか?

その他の結果

GitHub Copilotを使用するとコーディングのストレスが軽減されますか?

GitHub Copilotを使うことで仕事に充実感を感じられるようになりましたか?

GitHub Copilotを使うことでより満足度の高い仕事ができるようになりましたか?

GitHub Copilotを使用することで作業に集中できる状態に入りやすくなりましたか?

GitHub Copilotを使用することで検索にかかる時間が短縮されましたか?

GitHub Copilotについて使い方やここが便利!など共有したいことがあれば教えてください。

  • 同じようなことを何回も記述することが減った
  • 別の箇所に書いてある内容を読み取って書いてくれるので、リファクタなど一箇所変えれば他のところも読み取って書き換えてくれる
  • コード補完がとにかく便利
  • 人が書いたコードを読み解くときにGitHub Copilotに説明してもらえるのも便利
  • ショートカット設定しておくと、キーボードから手を離さなずにできてめちゃくちゃ便利

その他の意見

  • フレームワークのソースコードを読み取るのが楽になった
  • 三項演算子やスプレッド演算子などの名称を調べるときに便利。スプレッド演算子はgoogleで検索しても「...」などでは引っ掛からなかった
  • 共有というほどではないですが、コードを説明してくれることと書いているときに補完してくれることがありがたい
  • コードの候補がでるので、時短になる
  • Seederやテスト用コードの自動作成・自動修正などは正確に作成してくれる

GitHub Copilotについてここが使いづらい....など共有したいことがあれば教えてください。

  • 長いコードを書いてくれる時は少し時間がかかるので、もう少し早くなったらいいなと思う
  • コード補完を一度に複数行生成されちゃうと、一部だけacceptするようなことができません。全部acceptしてから直すみたいな手間があるのはちょっと使いづらいかも
  • 設問にあった「繰り返し作業」をさせる発想がなかったので、試してみたい
  • 便利な使い方集や、自分はこうやって使っている、という情報共有できる場があると良さそうだなと感じた
  • コード以外のことを調べることができない

導入後アンケートまとめ

全体的に肯定的な意見が多く、生産性の向上や繰り返し作業のスピードアップなどが実感としてある回答が多くありました。
導入前の解決したい問題にアプローチすることができたので、導入して効果が出ているということが開発者のアンケートでもみることができました。
一方で、長めのコード生成に関してはまだ課題感を感じているメンバーもいるため、改善を期待すると共に、GitHub Copilotを扱う上でのテクニックやナレッジについても、見つけ次第随時共有していこうと思います。

おわりに

GitHub Copilotの導入によって、開発チームのプロジェクトによってはベロシティが1.5倍になったという報告もあり、導入効果は目に見えて出ていると感じます。
導入フローは整備しましたが、まだ使用していないメンバーもいるので、よりGitHub Copilotを利用・活用できるように働きかけ、利用率の増加とさらなる開発生産性の向上を目指し、よりよいアウトカムをだせるように活動を続けていきます。

スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。
もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください! recruit.styleedge-labo.co.jp

参考資料

*1:チームトポロジーという組織設計のフレームワークにおける、チームタイプの一つ。

*2:公開コードに一致する提案を有効または無効にする設定について

*3:Satisfaction and well being/Performance/Activity/Communication and collaboration/Efficiency and flow の頭文字をとったもの。開発者の生産性を測るフレームワークの一つ。

Aurora Serverless v2を導入してみた

はじめに

こんにちは。システム事業部のgakiです。新卒入社3年目にして初めての技術ブログ執筆です。 私事ではありますが、最近AWS Certified Solutions Architect - Professionalに合格したこともあり、最近は特定の言語やプロダクトに偏ることなくAWS周りの相談に乗ったり、新規案件のインフラ設計なども行っています。

さて、今回のテーマはAurora Serverless v2についてです。基本的な仕様や導入のきっかけについて書いていきます。

generated by DALL-E3

Aurora Serverless v2とは何か

AuroraはAmazon RDSのサービスの1つで、高可用なデータベースを提供しています。 Aurora Serverless v2はAuroraの中でも比較的新しいサービスで、負荷状況に合わせてDBのスペックを自動でスケールアップ・ダウンする機能が備わっています。

スペックを表す単位としてACUというものがあり、1ACUに対して約2GiB のメモリと対応する CPU、ネットワークが付与されます。 詳細は公式ドキュメントをご覧ください。

docs.aws.amazon.com

Aurora Serverless v2 導入のきっかけ

弊社のあるプロダクトではデータベースにAuroraを使用しています。しかし、プロダクトの規模が大きくなるにつれて、高負荷のSQLが特定の時間帯に多く実行されることによりCPU使用率が100%に張り付き、レスポンス性能が低下するといったことに悩まされていました。 サーバレス導入前は、下記のような構成で運用していました。

システム側での検索や帳票出力など、高負荷な読み取りSQLはリーダーインスタンスに振り分けることで負荷分散していました。しかし負荷はデータ量の増加に伴い上昇していき、SQLのチューニングは定期的に行っていたものの対処しきれず、DBインスタンスのサイズを上げて何とか負荷に対応していました。高負荷時のレスポンス性能を改善するため、スパイクアクセスに対して優れたスケーリング性能を発揮するデータベースである、Aurora Serverless v2を採用しました。

導入手順

Auroraでは、1つのクラスターの中でプロビジョンドインスタンスとサーバレスインスタンスを共存させることができるため、段階的な導入が可能でした。 下記の簡単な手順でクラスターにサーバレスインスタンスを追加することができます。

  • Auroraクラスターのメニューから、「アクション」を選択
  • DBインスタンスクラスに「Serverless v2」を選択

また、既存のプロビジョンドインスタンスをサーバレスインスタンスに変更することも可能です。

置き換え方法

今回は、本番環境で稼働しているリードレプリカのみサーバレスに置き換える方針で進めることになり、具体的な作業は下記の流れで行いました。

  1. 置き換え前 リーダーインスタンスと同数のサーバレスインスタンスを仕込んでおく
  2. 置き換え中 カスタムエンドポイントの宛先DBをレプリカ(プロビジョンド)→レプリカ(サーバレス)に変更

障害時は、カスタムエンドポイントの宛先DBをレプリカ(プロビジョンド)に戻すことで復旧が可能です。

今回Aurora Serverless v2を導入するプロダクトではリードレプリカへの接続はカスタムエンドポイントを使用していたため、置き換え作業はカスタムエンドポイントのターゲットインスタンスの変更で対応しました。

移行後に感じたメリット/デメリット

メリット

パフォーマンスの向上

導入前後で比較すると、高負荷時のレスポンス速度がかなり改善しました。導入前はデータを大量に出力する際にタイムアウトが発生することもありましたが、Aurora Serverless v2を導入して以降、タイムアウトは一度も発生していません。

メンテナンスコストの削減

現在このプロダクトで運用しているAurora Serverless v2は、以前のDBの1/8~4倍のスケーリング幅で柔軟に稼働しています。そのため、毎日のように来ていたDBのCPUアラートが止まり、高頻度の調査・メンテナンス作業から解放されました。

高速なスケーリング

実用性を評価する上で一番気になるところといえば、スケーリングの速度かと思います。

最大ACU使用時のCPU使用率と、サーバレスインスタンスのACUの推移を下記の通り比較してみました。 (Aurora Serverless v2ではCPUUtilizationの値がその時点でのACU数に対する使用率となるため、数式を利用し最大ACU使用時に対応する値に換算しています)

この図から、スケールアップは負荷の上昇の1分以内には開始しており、スケールダウンは比較的ゆっくりと行われるということが分かるかと思います。

一方でAuroraは、負荷上昇に対応する別機能として、Aurora Auto Scalingというものも提供しています。
こちらはスケールアウトの方式で、一時的にインスタンスを増設し負荷に対応するというものです。 しかし、増設インスタンスのプロビジョニング時間が追加でかかるため、負荷上昇への反応速度は10分ほど遅れます。 以前、同プロダクトにてAurora Auto Scalingを導入していましたが、増設インスタンスの立ち上がりが遅く、実際に稼働し始める頃には負荷は減少傾向になっているといったことが多くありました。

docs.aws.amazon.com

以上から、現状のAmazon RDSが提供するスケーリングの仕組みの中では、Aurora Serverless v2が最も負荷の増減に流動的に対応できるサービスであると考えられます。

ただ、スケーリングの速度が常に高速かと言われると、そうでは無いようです。 下記公式ドキュメントによると、スケーリングのスピードはその時点でのACU数が多い方が速くなる、との記載がありました。 docs.aws.amazon.com

現在、同プロダクトのAurora Serverless v2は4ACU~32ACUで設定しておりますが、プロダクトの要件として1分以内のスケーリングで速度面は十分なため、性能面の問題は無いと感じています。 ACUの範囲については、負荷テストなどを実施した上で良い落とし所を見つけていただければと思います。

デメリット

持続的な負荷がある場合はコスト効率を上げにくい

Aurora Serverless v2は使用量による従量課金です。そのため、持続的な負荷がある場合は料金が高くなる可能性があるため注意が必要です。
というのもAurora Serverless v2は、その負荷に相当するプロビジョンドDBインスタンスを利用した場合と比較すると高コストであるためです。

東京リージョンの料金で汎用インスタンスであるdb.m5.largeと、それに相当する4ACUのAurora Serverless v2で料金を比較してみました。

1時間あたりの料金1
db.m5.large USD 0.235
Aurora Serverless v2 (4ACU) USD 0.8

このように、持続的な負荷がある場合はAurora Serverless v2が原因でコストパフォーマンスがあまり向上しなかったり、かえって高コストになってしまう可能性があります。 Aurora Serverless v2 を導入する際は、持続的な負荷が原因で料金がかさまないよう注意が必要です。

移行の教訓とベストプラクティス

負荷状況がAurora Serverless v2のユースケースとマッチするか確認しよう

お話ししてきた通り、Aurora Serverless v2がハマるのは常時低負荷&突発的な高負荷といった状況になります。

例えば下記のような場合は相性が良いかと思います

  • 常時負荷は低いが、1日数回の高負荷なバッチ処理を行うため泣く泣く高スペックのデータベースを稼働させている
  • 検証環境やデータ分析など短い使用時間のために、ほぼアイドル状態のデータベースを稼働させている

このような状況ですとAurora Serverless v2を導入することで、必要な時は必要なスペックで稼働させることができ、一方で大きなコスト削減も見込めます。

負荷が無い時間帯は、最低ACUを下げてコストダウンを図ろう

夜間や休日などにシステムがあまり利用されない場合は、その時間帯だけ最低ACUを下げることでコストの削減が可能です。

下記のように、1コマンドでデータベースの設定値を更新できるため、EventBridgeと組み合わせるなどしてACU調整の自動化も容易に行うことができます。 docs.aws.amazon.com

便利さに依存せず、継続的なSQLのチューニングを

Aurora Serverless v2を導入することで、使用量に応じた従量課金となります。そのため負荷の高いSQLを発行していると、その分だけ料金がかさみます。

導入前になるべくSQLのチューニングを済ませておく

負荷が高くなる要因は事前に取り除いておくと、性能面、コスト面において適切なスケール幅を設定することが可能です。

導入後も負荷軽減が可能なところは改善を続け、スケーリング幅の見直しも行う

改善できそうなSQLがあればチューニングをし、適切なスケーリング幅を設定することで、コスト効率を高められます。
負荷の高いSQLを効率的に検出するために、Performance Insightsの利用を検討してみても良いかもしれません。 docs.aws.amazon.com

おわりに

以上、Aurora Serverless v2導入について書いてみました。品質の維持やメンテナンスコスト削減の面では大きな強みを持っているサービスですが、負荷状況によっては割高になってしまうため状況によって適切な判断が必要となります。

導入の方法は柔軟に決めることができるため、簡単に試すことができるのが大きな強みだと感じています。DBのコスト削減を目指していたり、バースト的な負荷によりレスポンスに懸念があるような状況でしたら、ぜひ導入を検討してみてください。


スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。 もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください!

recruit.styleedge-labo.co.jp


  1. 2024.5.29現在

読書でチームビルディング!?社内輪読会の魅力と実践レポート

はじめに

こんにちは!システム事業部のChunです。弊社技術ブログの運営委員をしており、社内の魅力を発信するため、ネタ探しの日々を送っています。

そんな私が社内チャットでネタを探していたある日、ふと画面をスクロールする指が止まりました。目に入ってきたのは「輪読会」というワード。 どうやらプロダクトチームの有志が同じ本を読み、その内容について意見を交わしあっているという情報を入手しました。

これは面白そう!さっそく実際に会を主催している社員にインタビューしました! 今回はインタビューを通して得られた輪読会のリアルな実態をご紹介いたします!

generated by DALL-E3

なぜ彼らは輪読会を行っているのか?それはどのように行われているのか?

私はその謎を解明すべくAmazonの奥地へと向かった──────

新進気鋭の主催者メンバー!輪読会やってみたレポート!

インタビューした方

1人目

ごっちゃんさん(新卒2年目)

RaKKaR(医業向け顧客管理システム)のバックエンドエンジニア

2人目

みのみのさん(新卒4年目)

LeadU⁺アルファ(士業向け顧客管理システム)のプロダクトオーナー

───さっそくですが、 お二人が輪読会の実施に至った経緯について教えてください!

ごっちゃん

チームで振り返り会を実施した際に、メンバー間で技術的な知見が偏っているよね。との意見が出たことがきっかけでした。

知識の差を埋めるために施行錯誤した結果、今持っている知識のアウトプットだけではなく、新しい知識のインプットもできる輪読会をやってみよう!となり、実際に開催してみました。

みのみの

ごっちゃんたちRaKKaRチームが輪読会をしていることを知ってやってみようと思いました。

もともと私は開発メンバーに技術以外のことも磨いていってほしいなという思いを持っていたんです。システム事業部が掲げる「技術がないエンジニアになるな、技術しかないエンジニアになるな」という言葉に共感していて、どうすればチームビルディングのことなどをみんなに伝えられるかなと考えていました。

そんな時、RaKKaRチームが輪読会をしていることを知り、これなら実現できそう!と思いメンバーに提案したところ、賛同の声が多かったので実施に至りました。

───チームの知識水準を高めることが目的で始まったのですね。続いて、選んだ書籍とその選定理由について教えてください!

ごっちゃん

実施し始めたころは、まずは輪読会に慣れようということで、読みやすいビジネス書を中心に選出していました。ちょうど慣れてきたころに大規模なリファクタを行う予定があったので、「リーダブルコード」、「良いコード/悪いコードで学ぶ設計入門」を読み合わせて備えよう!という理由で選んでいます。その後もマネジメントに関する本など多岐にわたるジャンルで行っています。

みのみの

アルファチームでは「SCRUM BOOT CAMP THE BOOK」という本を選びました。

スピード感が求められるプロジェクトのため、各タスクに使っても良い時間や優先順位を開発メンバーにも意識してもらう必要がありました。そこでスクラム開発をチームに導入しようと思い、私が読んだ中で一番分かりやすく体系的に説明しているこの本を選びました。

───実際に輪読会を開催してみてメリットはありましたか?

ごっちゃん

プロダクトのソースコードについてチームのメンバーみんなで気軽に話し合う機会を作ることができました!

技術書の輪読を行った際は、本の内容と業務をリンクさせて話すことがよくありました。例えば、輪読会中に「リファクタするときはこういった観点も大事だね」や「あのメソッドの書き方は良くないね」といった話で盛り上がったり、コードレビューの際に書籍の内容を引用して説明を行ったりするようになりました。

みのみの

私たちのチームでも、輪読会は日々の業務を振り返る良い機会になっています。本の内容を具体的な業務に置き換えて意見を交わすことで内容の理解度も深まっているなと感じています。

あとはメンバーの学習意欲をかきたてられたことですね! 読もうと思ってつい積んでしまっている本って誰しもあるじゃないですか(笑)

みんなで読もう!という流れができたので、そういった本を読む機会が作れたなと思います。

輪読会で使用したFigJam

───なるほど、業務での経験を踏まえて議論できるのは、社内で行うからこそですね!では、輪読会を実施する上で意識していることはありますか?

ごっちゃん

輪読会への参加ハードルを下げることですね。任意参加でROM専でもOK!という形式をとっています。

また、輪読会当日の朝にどこまで本を読めたのかアンケートをとっています。この結果をもとに議論の範囲を決定しているため、本を読み切ることができなかったメンバーも罪悪感を持つことなく参加してくれています。

本を読むことを義務にしてしまうと、精神的な負担になりますし、読み終わることが目的になって内容が頭に入りにくいですよね。参加者にとって無理のないペースで本を読み進められるように輪読会を開催することが重要だと思います。

みのみの

その場の議論を大事にすることです!

輪読会の進行をファシリテーションが上手なメンバーに任せています。同じ人が話しすぎたり、一度も話さない人がでないよう進めてもらい、参加メンバー全員で話ができるようにしています。

また、メンバーの出社日が揃うタイミングで会議室に集まって実施しています。 座席もメンバーの性格を考えて話しやすい位置に座ってもらっていますね。

よく話す人とよく話す人の間に挟まると、話すことが苦手な人でも話しやすいみたいです!

───ごっちゃんさん、みのみのさん、本日はありがとうございました!最後に、輪読会の将来的な展望や、今後取り組みたいことがあれば教えてください!

ごっちゃん

チームを越えた輪読会もやってみたいですね!全く別の業務をしているメンバー同士で本の内容について意見を交換するのも新しい発見がありそうです。

また、今後チームに入ってくる後輩に向けた輪読会も実施したいです。どんな本を読めばいいのかだけでなく、先輩がどんなことを考えているのかを伝えられる機会になりそうです!

みのみの

輪読会を通して知識の標準化をすすめ、チーム全体のスキルアップを目指したいです。

また、本やセミナーで紹介されるノウハウをそのまま自分たちのプロダクトに適用できるとは一概には言えないと思っています。正解がないプロダクト開発の中でもチームの中で自分たちで話し合って共通認識を持ち、目指していく方向を揃えられる機会にしていきたいです!

おわりに

以上、輪読会やってみたレポートでした!

インタビューする前は、輪読会ってなんだろう、一人で読むのと何が違うの?と思っていましたが、その実態はスキルアップやチームビルディングにつながる素敵な取り組みでした!

スタイル・エッジには輪読会をはじめとして、社員自身がスキルアップやチームビルディングのために「やってみよう!」と思ったことを実施できる文化があります。

今後も、どんなメンバー発信の取り組みが行われるか楽しみです!

スタイル・エッジでは、一緒に働く仲間を絶賛大募集しています。 もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください!

recruit.styleedge.co.jp

【モダン開発 #4】モダン開発で工夫した8個のこと

generated by DALL-E3

はじめに

モダン開発連載もいよいよ最終回。わたくしneueがお届けいたします。

番外編の冒頭でも触れていた内容にはなりますが、設計思想とフレームワークの良いとこ取りをしようとすると、必然的に両方に即したアプローチが求められます。中には思想同士が競合する部分もあり、折衷案を出すことすら困難で取捨選択を迫られることもあります。

そういったアーキテクチャの不和を、開発者が極力意識せずにフレームワーク利用の延長線ぐらいの感覚でプロダクト自体の設計・実装を進めることができ、量産されていくコードも自然に一貫性を保たれるような開発者インターフェイスを提供するには、水面下で多くの工夫を凝らす必要がありました。

まだ改善の余地がある状態ではありますが、本記事ではモダン開発の下地に取り入れた工夫をご紹介いたします。似た立場で開発基盤の設計と格闘している方にとって、何かしらの参考になれば幸いです。

※本記事で扱われている言語はPHP、フレームワークはLaravelです

具体例の紹介

各概念における抽象クラスの作成

量産対象となる、フレームワークであらかじめ用意されているクラス(Controller, FormRequestなど)および、独自に追加したクラス(Entity, ValueObjectなど)ほぼ全てに、独自の抽象クラスを作成しました。

一貫して抽象クラスを継承させておくことで、以下のようなメリットを得ることができます。

  • あらかじめ全体共通のふるまいを定義できる
  • 後から全体共通のふるまいが必要になった際に、具象クラスの修正が不要
  • 各具象クラス及び生成されたインスタンスが何者であるかを外部から判定しやすくする
<?php
// 最初の時点は何のふるまいが存在していなくても…
abstract class BaseEntity {}

// あらかじめ継承しておけば、具象クラスの継承先を後から全て書き換える必要がなくなる
class User extends BaseEntity {}
<?php
// ユーザークラスがエンティティなのかを調べることができる
if (is_a(User::class, BaseEntity::class, true)) {}

// ユーザーインスタンスがエンティティのインスタンスなのかを調べることができる
$user = User::create(~~~);
if (is_a($user, BaseEntity::class)) {}

継承先からは継承元のふるまいを一部変更することはできても、打ち消しをすることはできないため、全体ではないが一部で共有されるふるまいは別の形で実現します。

  • 下位概念が対象範囲のケースは、上位概念を継承した下位概念の抽象クラスを作成し、そちらに定義
  • 個別の判断が必要なケースは、トレイトに定義
<?php
abstract class BaseEntity {
    // エンティティとしての共通のふるまい
}

abstract class BaseRootEntity extends BaseEntity {
    // 集約ルートエンティティとしての共通のふるまい
}

abstract class BaseLocalEntity extends BaseEntity {
    // ローカルエンティティとしての共通のふるまい
}
<?php
class User extends BaseModel
{
    // ふるまいをトレイトとして抽出し、対象となる具象クラス側でのみ参照する
    use SoftDeletesTrait;
    ︙
}

量産対象となる具象クラスの記述量を減らす

レイヤーを分割したことで、情報の受け渡しにおいて同じような情報を複数回記述する必要が出てきました。

例えば、レイヤー間でDTOに詰めたり取り出したりする工程であったり、同クラスの静的メソッド間での持ち回し(例:エンティティのファクトリーメソッドに渡ってきた引数一式をバリデーションやコンストラクターに渡す)など、個数が多くなりがちな属性周りにはこの問題が付いて回ります。

情報の一部を選択して渡すケースではさておき、全て(または一定条件に一致するもの)を渡すケースについては、列挙することが人為的ミスの温床にもなりかねません。
もっと簡素な書き方で実現できないかと頭を捻り続け、結果的には黒魔術に手を染めることになりました。

LaravelDataの活用

レイヤー間については、番外編で紹介したLaravelDataを基盤としたDTOによって解決しています。

ユーザー入力からユースケースへの情報受け渡しに記載した通り、独自の入力用メソッドさえ定義してしまえば、あらゆる形式(リクエストオブジェクトやJSON文字列など)から、スキーマとして定めたデータ構造に当てはめることができ、受け渡し先が要求する形式で取り出すことが可能です。

入出力ペアごとにDTOの抽象クラスを作り、入力と取り出し補助に関するロジックはそちらにカプセル化しました。

連想配列と引数のアンパックの活用

PHPではメソッドの呼び出し時に、配列やTraversableなオブジェクトを引数リストにアンパックできます。その際、添字配列の代わりに連想配列を渡すと、キー名と合致するパラメーターに対して引数が展開される仕様になっています。

<?php
function sum(int $foo, int $bar, int $baz): int {
    return $foo * $bar + $baz;
}

// 要素の順番の引数として渡されるので結果は`5`
sum(...[1, 2, 3]);

// 要素の順番は関係なく同じパラメータ名の引数として渡されるので結果は`7`
sum(...['baz' => 1, 'bar' => 2,'foo' => 3]);

// パラメータ名として存在しないものが含まれているので
// Error: Unknown named parameter $foobar
sum(...['foo' => 3, 'bar' => 2, 'baz' => 1, 'foobar' => 1]);

同クラス内でほぼ同じパラメーターを持つメソッドへの引数の受け渡しについては、わざわざデータオブジェクトを介在させる必要もないため、上記仕様の活用を起点に対応方法を考えました。

ちなみに、引数の単純な添字配列についてはfunc_get_args()で取得でき、呼び出し元と呼び出し先のパラメーターの数や順序が完全一致していれば、そのまま渡すだけで済んでしまう話ではあるのですが、多くのケースでは一部のみの受け渡しや、値を書き換えてから渡す必要がありました。

下記の課題を解決すれば採用が現実的であると判断し、PHPの機能を調査しました。

  • 自身に渡された引数とパラメーター名を使って連想配列を作れること
  • 自身に渡されなかった(省略された)引数は、デフォルト値に置き換わること
  • 呼び出し先メソッドのパラメーターに存在するキーのみに対象をフィルタリングできること(エラー防止)
  • 手動で一部の値の上書き・引数の追加ができること

リフレクションによる内部情報の利用(黒魔術)

リフレクションを使用することで、クラスや関数など多くの内部情報(型やプロパティ、コメントさえ)にアクセスできるようになるため、課題解決に必要だった以下の情報が入手できます。

  • 呼び出し元メソッドのパラメーター(名前、初期値)
  • 呼び出し先メソッドのパラメーター(指定した名前のものが存在するか)

以下の工程を踏むことで、目的のデータが作成可能になりました。

  1. 自身に渡された引数配列とパラメーター名配列をarray_combineして連想配列を作成
  2. 省略された引数はパラメーターの初期値で置き換え
  3. 呼び出し先メソッドのパラメーターに存在するキーの項目だけに絞り込み
  4. 上書き・項目追加用の連想配列をマージ

ただし、抽象クラス側に定義したこのメソッドを呼び出す際、引数と呼び出し元メソッドの情報を渡す必要が出てきてしまいました。

<?php
class User
{
    public static function create(〜〜〜): User
    {
        $arguments = func_get_args();

        // 引数配列と呼び出し元メソッド名を渡している(複数メソッドから呼び出したい場合)
        static::validateFromArguments($arguments, 'create');

        // 引数配列と値上書き用の連想配列を渡している(一部の値を上書きしたい場合)
        static::createInstanceFromArguments($arguments, [/* 上書き用連想配列 */]);
    }
}

バックトレースによる呼び出し元情報の参照(暗黒魔術)

PHPにはdebug_backtraceという、バックトレースを生成するエラー処理のための関数があります。
指定したスタックフレーム数まで、呼び出し元のメソッド・関数に関する情報を参照できます。

こちらを利用することで、以下の情報が入手できます。

  • 呼び出し元のクラス名・メソッド名
  • 呼び出し元のメソッドに渡された引数

これらの情報を利用することで、呼び出し元で引数配列を先に取得する必要もなくなり、呼び出し元のメソッドが複数(N:1)のケースにおいて、どのメソッドから呼び出したかという文字列情報も不要になりました。

<?php
static::validateFromArguments();

static::createInstanceFromArguments([/* 上書き用連想配列 */]);

使用にあたっては、スタックフレーム数の指定や、不必要な情報の参照を無効化するオプションを指定しないと、メモリの負荷が高くなってしまうため注意が必要です。

実際の使用にあたって

正直なところ、内部情報や実行時のトレース情報を利用することは、関数の純粋さが大きく失われてしまい抵抗感はあります。 ただし、Laravel内部でも使用されていることや、実装者視点で得られる恩恵があまりにも大きいことから、今回は用途限定で採用することにしました。

また、渡していないデータが暗黙的に参照されること自体も本来アンチパターンであり、コードを読んだだけでは挙動が想像できないため、ささやかな情報としてメソッド名にFromArgumentsといった接尾辞を含めることにしました。

ビジネスルール検証におけるValidatorの活用

DDDにおいて、ドメインオブジェクトが持つビジネスルールは、原則クラス内にカプセル化します。
インスタンスの作成や更新前に各属性に関するルールチェック、後に複数属性にまたがる整合性チェックを遅延バリデーションの形で行い、ルール違反が見つかった場合には例外をスローするといったアプローチが一般的かと思います。

従来パターンにおける課題

属性数が多くなったりビジネスルールが複雑になるにつれて、検証ロジックのコード量・分岐数の増加を招きます。
中でも、属性のルールチェックに関してはルール自体が汎用性が高いものであることが多いにも拘らず、通常の分岐を用いると行数が増加していってしまい、ルールが把握しづらくなってしまいます。

<?php
class User
{
    // 各属性のセッターでバリデートするとしたら…
    private function setAge(int $age): void
    {
        $errors = [];
        if (strlen($age) > 3) {
            $errors[] = 'age.maxDigits';
        }
        if ($age < 0) {
          $errors[] = 'age.positiveInteger';       
        }
        if ($age > 120) {
            $errors[] = 'age.max';
        }
        if (count($errors)) {
            throw new DomainValidateError($errors);
        }
        $this->age = $age;
    }
}

Validatorを用いる前提で宣言的に記述する方式

各属性に関するルールチェックはLaravelのValidatorに依存させる形で実装して、ルールはFormRequestのように宣言的に記述する方式を採用しました。
ルールを、配列を返す静的メソッドに切り出すことで、各属性の一般的なビジネスルールが一目瞭然になりました。

<?php
class BaseEntity
{
    protected static function validate(): bool
    {
        // create, updateXxxなどの呼び出し元に応じて、
        // 全ての属性または一部属性をValidatorを使ってバリデート
    }
}

class User extends BaseEntity
{
    // 型や必須判定は、ファクトリーメソッドのパラメーターの型宣言で担保されているため、こちらには含まない
    private static function rules(): array
    {
        return [
            'firstName' => ['max:80'],
            'lastName'  => ['max:80'],
            'age'       => ['positive_integer', 'max_digits:3', 'max:120'],
        ];
    }
}

オブジェクトが入れ子になっている場合の責務の所在

各オブジェクトで直接検証を行うのはプリミティブ型の属性に限定し、オブジェクト型が指定された属性の検証は、当該オブジェクト側の責務として、再帰的に検証される作りにしています。

例えば、年齢がAgeといった値オブジェクトとして実装されているのであれば、ルールの定義・検証はいずれもUserでなくAge側で行います。

<?php
class User extends BaseEntity
{
    // クラスのプロパティ(コンストラクターのパラメーターから昇格)で、インスタンスが持つ各属性の型を定義
    private function __construct(
        protected string $firstName,
        protected Age $age,
    ) {}

    // 一方ファクトリーメソッドのパラメーターは、ユースケースから渡されるプリミティブ値を受け入れる
    public static function create(
      string $firstName,
      int $age,
    ): User
    {
        // クラスのプロパティがプリミティブ型の属性のみチェック
        static::validate();
        // クラスのプロパティが固有オブジェクト型の属性は変換
        // ※内部的には当該クラスのファクトリーメソッドが呼び出され、ここと同様の判定・生成処理が行われる
        $convertedValues = static::convertObjects(['age' => $age]);

        // 引数の値とオブジェクトに変換された値を用いてインスタンスを作成
        $user = static::createInstance($convertedValues);
        // 作成されたインスタンスの整合性をチェック
        $user->validateIntegrity();

        // ルール違反がなければインスタンスが返却される
        return $user;
    }

    private static function rules(): array
    {
        return [
            // ユーザーの属性ルールとして定義するのはプリミティブ型である名前のみ
            'firstName' => ['max:80'],
        ];
    }
}

class Age extends BaseValueObject {
    public readonly int $value;

    private function __construct(int $age)
    {
        $this->value = $age;
    }

    public static function create(int $age): Age
    {
        // オブジェクト型の属性や整合性チェックを持たない末端のオブジェクトだとシンプルに
        static::validate();
        return static::createInstance();
    }

    private static function rules(): array
    {
        return [
            // 年齢として扱う数値に関するルールは、年齢オブジェクトのルールとして定義
            'age' => ['positive_integer', 'max_digits:3', 'max:120'],
        ];
    }
}

その他ポイント

Validatorを用いると、起点となるユーザーインターフェイス層で違反を扱う際に下記のようなメリットもあります。

  • 特別な工夫なしに各属性のルール違反をひとまとめにできる(ErrorBag
  • メッセージのフォーマットとして、Laravelのメッセージファイルを利用できる

また、サンプルコードにしれっと紛れ込んでいますが、Laravel標準のバリデーションルールには存在しないもので、ビジネスルールとして頻出するものについては拡張しています。(例:正の整数、カタカナのみなど)

今回触れなかった整合性チェックについては、クロージャ形式で混在させることも不可能ではないものの、視認性が高くはなく再利用性も低いため、従来のValidatorを使用しない記述方式で遅延バリデート*1することにしました。

フロントエンドバリデーションとの数値ルール共有

ユーザー体験向上のためには、即座に応答できるフロントエンド時点でも入力バリデーションを行う必要があります。

ビジネスルールとユーザー入力における制限が必ずしも一致するとは限りませんが、多くは連動しています。
ドメイン層とユーザーインターフェイス層で、同一の根拠を持つバリデーションルールを別々に保持することは、差異発生のリスクがあるため、ドメイン層にのみ情報を持ち、そちらに依存させることが望ましいでしょう。

ルールセットとしてフロントエンドと同期するには、TypeScriptの型やZodなどのスキーマの形式で取得可能にするのが1つの理想ではあります。
現時点では、個別で取り出したいケースがある点も考慮し、多重管理リスクの大きい数値系ルールのみを扱いやすい形式で切り出し、Controllerから取得可能にしました。

<?php
class User
{
    // 属性/内容別に数値形式で参照できるようにする
    public static function numericLimits(): array
    {
        return [
            'firstName' => [
                'max' => 80,
            ],
            'lastName' => [
                'max' => 80,
            ],
        ];
    }

    // 数値系ルールはバリデート時に自動変換する仕組みを作り、rulesには記述しない
    private static function rules(): array
    {
        return [
            'age' => ['positive_integer'],
        ];
    }
}

数値系ルールを構造化したオブジェクトをViewに渡すことで、属性値などとして個別参照が可能です。

<x-form-input
  type="tel"

  :maxlength="$numericLimits->user->phoneNumber->max"
/>

Eloquent Modelとの共存

Laravelのコアといっても過言ではないEloquent Modelは、包括的な概念でありつつ、Laravelが提供する多くの機能との関連性を持っています。 レイヤーを越境しやすい性質を持っていることから、DDDやレイヤードアーキテクチャといった設計思想における責務分離の考え方と、最も競合しやすい概念ではないでしょうか。

ORMとして同品質の概念を再開発する自信と時間はさすがにないため、使用自体は許容しつつRepositoryQueryServiceから情報を返す際に、ドメインオブジェクトに変換してから返却するルールにすることで、インフラストラクチャ層から外へはModelを原則露出させない方式を選択しています。

<?php
class UserRepository extends BaseRepository
{
    // クエリメソッドからModelをそのまま返却しない
    public function find(int $id): ?User
    {
        return UserModel::find($id)?->convertEntity();
    }
}

class UserModel extends BaseModel
{
    public function convertEntity(): User
    {
        // Modelのインスタンスを元にEntityのインスタンスを再構築
        return User::reconstruct(
            id:   $this->id,
            name: $this->name,
            ︙
        );
    }
}

Modelの責務としては

  • データベース操作の抽象化
  • データベース上とシステム上で扱うデータ形式の相互変換
  • リレーション先のデータ取得

に留めて、各層からは直接依存させないことで、あくまでORMとしての役割を担ってもらっています。

ユーティリティクラスなどのレイヤー外概念

プログラムで取り扱う概念の中には、問題領域・解決領域いずれも直接の関係はなく、開発そのものや非機能要件にまつわる課題を解決するための概念が存在します。

これらはいずれかのレイヤーに所属させても矛盾が発生することがあるため、切り離して考えることにしました。
(正確には、この領域から各レイヤーへの依存は許可せず、この領域に対する依存については制約を設けない)

情報・関数

システムによって変動することのない、再利用性の高い補助的な手続き(例:文字列加工、年号変換)については、ユーティリティクラスとして外部化し、Laravelのヘルパー同様、エイリアスとして登録することでどこからでも参照できるようにしました。

具体的な手続きに名前を付けて外部化することで、メインロジックをクリーンに保てるのと同時に、本来求められているふるまいが際立つことで、処理のアウトラインを追う目的でのコードリーディング負荷は大きく下げられます。

より良い手段が見つけられた際に仕組みの交換が容易になることもメリットに感じました。

<?php
// 処理全てをその場で書いてしまうと本筋が読みにくい
$originalAttributes = $request->attributes();
$replacedKeys = array_map('\Str::camel', array_keys($originalAttributes));
$attributes = array_combine($replacedKeys, $originalAttributes);

// keyをキャメルケースに変換した属性の配列が欲しいということが分かる
$attributes = \ArrayHelper::replaceKeys($request->attributes(), '\Str::camel');

内製パッケージ

今回のシステムに合わせて開発したものの、システムを選ばずに導入ができそうな機能(例:操作ログ)や、外部サービスとの連携の仕組み(例:Amazon TimestreamのORM)などについては、関連ファイルをあえて一纏めにした状態で管理しています。

異なるシステムが持つ要求の差異を吸収できるほどの汎用化を終えたら、社内向けのパッケージレジストリ上に管理を移行することを前提として、あらかじめ関連コードが分散しないよう関心事にフォーカスしたディレクトリ構成となっています。

また、動作に必要な実装をシステム側で管理する場合は、必ずインターフェイスを提供することで、規格を分かりやすく実行前から基準を満たしているかを確認できるようにしています。

App\Support\Packages\OperationalLogger
├── OperationalLogger.php
├── OperationalLoggerRepositoryInterface.php
├── Http
│   └── Middleware
│       └── RecordOperationalLogs.php
└── Providers
    └── OperationalLoggerServiceProvider.php

文字列フォーマット・設定値

前述の2つとは性質が異なりますが、同じくレイヤー外で扱う情報はあります。

画面に表示する各種メッセージの文字列フォーマット、データベースで管理するほどではない挙動制御のための設定値については、Laravelの設計に則ってlang, config配下に独自の定義を追加することで、一元管理による一貫性の維持とハードコーディング防止に努めています。

<?php
// 引数に使用する値を設定から参照
formatDate($date, config('date.formate.datetime'));

class ItemNotFoundException extends Exception
{
    public function __construct($itemName, $code = 0, $previous = null)
    {
        // 例外のメッセージを動的に生成
        parent::__construct(trans('exception.item_not_found', ['item' => $itemName]), $code, $previous);
    }
}

課題として、config()で返却される値について型の保証が標準では難しいため、エイリアス関数を用意してそちらで返却時の型情報を補完するか、各設定にあるPHPDocの情報を参照してヒントを与えるような、PHPStanの拡張機能を自作する必要性を感じています。

Make系コンソールコマンドの充実

DDD・レイヤードアーキテクチャに則って開発していることもあり、従来のLaravelに比べて開発時に扱う概念(クラス)の種類は多くなっています。

独自クラス(エンティティ、目的別DTOなど)は、Web上に情報が掲載されていないため、

  • 必要プロパティの示唆
  • 定義するメソッドのサンプル
  • 取り扱いにあたっての注意点

といった情報を目に留まる場所に記述して、正しく扱えるようにする必要があるのと、
既存クラスについても、

  • 名前空間の変更
  • 継承元の抽象クラスの独自化
  • コメントの日本語化・PHPDocにおける型定義の詳細化

といった調整箇所が存在します。

Laravelには、各種クラスのファイルを生成するためのArtisanコマンドが整備されており、その中でファイルの雛形(スタブという)も管理されているため、仕組みに乗る形でパターン別の対応を行いました。

  • 既存クラス
    • 静的文字列の変更のみ
    • 引数やオプションの拡張、名前空間の変更、動的文字列の変更が必要
      • スタブファイルの編集
      • 対象のMakeCommandを継承したコマンドを作成・処理の上書き
  • 新規クラス

また、他コマンドからのコマンド呼び出しを活用することで、特定のクラスと実質セットで作成することになるインターフェイスやユニットテストを同時作成するようなオプションを拡張し、作業工程の短縮を図っています。

例:

# User集約内にRelatedPersonというローカルエンティティとそれに紐づく
# エンティティコレクション(エンティティのリストをラップしたクラス)を生成
php artisan make:entity User/RelatedPerson -lc

# Actionとそれに紐づく入力用DTO、ユニットテストを生成
php artisan make:action User/Get -dt

今回は作成しませんでしたが、フローチャート次第で使用スタブが変わるようなケースでは、オプションの組み合わせで判断するのではなく、対話式のインターフェイスにしてみると無効な組み合わせパターンを防ぐことができそうです。

仕組みが整っていない場合にはよくある、類似ファイルを複製して調整を掛ける運用では、記述の過不足が発生するリスクがあるため、スタブファイルをバージョン管理し、基本ルールに変更が生じた場合はそちらにも修正を加える運用が安全です。

静的解析・リントツールの整備

コード品質の維持には、一貫性を守るためのルール整備が必要です。
しかし、ルールを人力だけで徹底しようとすると、かえって作業効率が低下したりレビュー工数が肥大化したりすることが予想されるため、可能な限りルール違反は静的解析で検出できるようにしています。

PHPStanの拡張機能は、Laravel組み込みクラスへの対応としてlarastanを、レイヤードアーキテクチャにおける正しくない方向への依存発生、所定の抽象クラスを継承していない具象クラスの検出に、phpatを活用しています。

また、独自クラスにおける必須指定などの開発時に遵守すべきルール・制約についてもこの層でカバーします。
専用のPHPStan拡張機能を作成することで、実行時に検証を挟まずとも早期検出ができるため、ロジック側に防御的なコードが氾濫することを防止できます。

エディター・CI上でチェックを通過してからレビューに進むフローにすることで、レビュアーの負荷も低下します。

今後の計画

記述量への対策

記述量が多い部分については、まだ全てが解消には至っておらずボトルネックとなっています。
しかし、レイヤーを分割し然るべき粒度で概念を分けるとなると、元の状態に比べてある程度増えることは当然とも感じます。

記述量を減らすといったアプローチだけではなく、1つのクラスの内容を元に関連クラスの内容を生成するジェネレーター・コンバーターの開発であったり、PHPDocの型情報とパラメーターなどの重複部分についてはAIの力を借りて自動補完できるようにするなど、まだ試せていないアプローチもこれからトライしていこうと思います。

自動テスト関連の強化・ハードル低下

自動テストに関しては、前回の記事にもある通り、依存の中心に関するユニットテストに留まっており万全とは言えない状況です。

ユニットテストだけではカバーができていない箇所について、統合テストやE2Eテストを導入できるよう、テスト実装の共通処理を抽象化するなどして実装ハードルの低下を目指しつつ、カバレッジについても基準等を検討していければと考えています。

DevContainerの導入

環境構築の時間を短縮しつつ、設定が近い状態でチーム開発ができるようにするため、ローカル環境の動作に必要なDocker Composeの定義や、VS Codeの設定ファイル雛形・推奨拡張機能の定義をバージョン管理対象に含めて運用しています。
しかし、実際に0から環境構築を行い開発に着手できるようになるまで、一部工程については手順書が必要な状況です。

DevContainer化することで、コマンド一発でエディターのセットアップも含めた開発環境を立ち上げられるようにし、最終的にはGitHub Codespacesを活用して、レビュアーは自身のローカルを汚さずともレビュイーの作業環境をチェックできるような状態を作れるようにしたいと思います。

おわりに

断片的な紹介ではあるものの、気付けばなかなかのボリュームになっていました。
まだ改善の余地が多い熟成度なので、今後も開発現場からのフィードバックに耳をしっかりと向けながら、思考と選択を繰り返していこうと思います。

また、冒頭でもお伝えしましたが、モダン開発連載は今回で終了となります。
振り返ってみると想像以上にチームのキャパシティを超えた挑戦にはなり、一部は方針転換をすることにはなりましたが、プロジェクト開始前に比べて設計に対する捉え方・考え方が大きく成長したメンバーが複数名いたことは、弊社にとっても大きな実りになったと感じています。

仕組みの構築という支援的なポジションではありましたが、その一助となれたことは、個人的に良い経験となりました。
限られたリソースの中でも、こういった投資的な取り組みが持続できる組織でありたいと切に思います。

一緒に探求してみたい方、もっと良い方法がある…と左手が疼いている方、ぜひ一度お話だけでも聞きにきていただけたら幸いです。 recruit.styleedge.co.jp

*1:CHECKS パターン・ランゲージにおけるバリデート手法の一つ。複雑性が高い検証は極力後回しにするという考え方。

【モダン開発 #3】TDDに挑戦してみた!

はじめに

こんにちは!スタイル・エッジの ike です。 過去何回かに渡って記事を連載していましたが、モダン開発#2までを執筆していた SHISO さんからバトンパスを受けて、今回は実装者としてプロジェクトに参画した視点から執筆します!

弊社ではある業務システムの開発プロジェクトにて、テスト駆動開発(TDD)に挑戦しました。しかし、結果的にはチームメンバーの経験の少なさ故の難しさがあり、途中でユニットテストの導入を最優先とした方針に転換することになりました。
そこで、TDDに挑戦した経緯や所感、途中で方針転換することになってしまった理由、その中で得られた恩恵・気付きを振り返っていきます!

TDD の挑戦に至った経緯

弊社で保守開発を行なっているあるプロダクトでは、改修時に生じたバグの解消に時間を費やしてしまい、顧客満足度の向上に直接つながるような改善に時間を割けないという問題がありました。

このプロダクトは業務ロジックが非常に複雑なのにも関わらずユニットテストがなかったり、コード設計自体が綺麗とは言い難いという状況になっており、無自覚でバグを埋め込みやすい状態になってしまっていました。また、仕様を詳細に知っているのが一握りのメンバーに限られてしまい、改修後の動作検証で考慮漏れによる手戻りなどもありました。

この経験から、新規プロダクトでは以下のようなTDDのメリットを享受しようと考えました。

  • 黄金の回転*1に含まれるリファクタリングにより、必然的にコード品質が担保される
  • 自然と高凝集・低結合で多くの部品で構成された設計を行うようになる
  • 小さな単位でテストと実装を繰り返すことで、実装時の認知負荷を軽減できる
  • 達成すべきことが明確になった状態で実装に取り組むことができる
  • 複雑な仕様がテストコードという形で確実に残る

いざ挑戦してみて感じたギャップと方針転換

上記のようなメリットを享受するためにTDDに挑戦はしてみたものの、チーム内にテスト実装自体の経験者が少なかったことや、実装工数とコードレビュー工数が想定以上に重かったことなどから、徐々にプロジェクトとしての余裕がなくなっていきました。これにより次第にテストファーストで書くことが難しくなり、TDDの黄金の回転もうまく行えなくなっていきました。

プロダクトのリリース期限との兼ね合いから、開発手法自体は従来と大きく変えずに「主要部分にユニットテストを導入すること」に照準を合わせることになりました。

ユニットテスト導入による恩恵

コードの可読性・機能拡張性が高まった

ユニットテストの実装を行うためには、テスタブルなコードにする必要があるため、自ずとクラスやモジュールの粒度のばらつきがなくなり、高凝集・低結合なコードになっていきました。また、各メソッドに単一の責務のみを持たせるようになることで、コードの複雑さを取り除くことができました。これらにより変更時の影響範囲が把握しやすく、後から機能を追加することも容易になりました。

重要な業務ロジックの変更に怯えることがなくなった

時間の兼ね合いから、テスト対象は限定する必要がありましたが、依存関係の中心になるドメインオブジェクトやユースケース、広範囲で再利用されるユーティリティに関しては必須でテストを行うようにしました。これにより中核部分のコード品質が高くなり、重要な業務ロジック部分の実装に対しての不安が無くなりました。

テスト実装を必須とした箇所
  • ドメインオブジェクト
    対象:Entity, ValueObject, DomainService, CollectionObject, 振る舞いを持つEnum
    理由:ドメインの概念やルールを表し、業務ロジックを表現するための重要な部分であるため

  • ユースケース
    対象:Actions
    理由:ドメインオブジェクトの操作の組み合わせでユースケースやサービスを組み立てる層として重要であり、経年的に煩雑化しやすい部分であるため

  • 広範囲で再利用されるユーティリティ
    対象:Helpers, Packages, Traits
    理由:多くのクラスで呼び出される共通部品で、影響範囲が広いため

テストコードの存在により新規参入者の仕様理解が早くなった

プロジェクトを進める中でメンバーの増員がありましたが、テストコードの存在により、新規参入者が実装に入るための仕様の認識合わせは、補足程度で済むようになりました。

テストコードを読みやすく、意図を把握しやすくするために実施した取り組みの中から4つを紹介します。

テストのメソッド名とテストパターン名は日本語を使用する

メソッド名・テストパターン名を、慣れ親しんでいる日本語で表現することで、曖昧さを排除しつつテスト内容を一目で判断できるようにしました。

<?php

/**
 * @test
 * @dataProvider dp_条件に違反する値を設定してcreateすると、例外が発生する
 * @param string $attributeName
 * @param string $invalidValue
 * @return void
 */
public function 条件に違反する値を設定してcreateすると、例外が発生する(string $attributeName, string $invalidValue): void
{
    $this->createParameters[$attributeName] = $invalidValue;

    $this->expectException(DomainValidationException::class);

    EventDate::create(...$this->createParameters);
}

/**
 * @return array
 */
public function dp_条件に違反する値を設定してcreateすると、例外が発生する(): array
{
    return [
        '日時がYYYY/MM/DD hh:mm:ss形式以外' => ['date', '2000/01/01'],
    ];
}
テストのメソッド内ではGiven-When-Then構文を使用する

Given、When、Then の3つのブロックに分けて書くことで、どのような条件か、何を実行するのか、期待される結果は何かを明確にしました。

<?php

/**
* @test
* @return void
*/
public function createに値を渡すと、渡した値でインスタンスが生成される(): void
{
    // Given(前提条件)
    $id   = 'ID';
    $name = '氏名';

    // When(操作)
    $inquiry = Inquiry::create(
        id:   $id,
        name: $name,
    );

    // Then(期待する結果)
    $this->assertSame($id, $inquiry->id);
    $this->assertSame($name, $inquiry->name);
}
同種のパターン系のテストはData Providersを使用する

同じアサーションで複数パターンの値を検証したい場合にData Providersを使用することで、テストメソッドの記述量を減らし、想定されるパターンが一目で分かるようにしました。

<?php

    /**
     * @test
     * @dataProvider dp_条件に違反する値を設定してcreateすると、例外が発生する
     * @param string $attributeName
     * @param string $invalidValue
     * @return void
     */
    public function 条件に違反する値を設定してcreateすると、例外が発生する(string $attributeName, string $invalidValue): void
    {
        // Given(検証値のみ上書き)
        $this->parameters[$attributeName] = $invalidValue;

        // When
        $this->expectException(DomainValidationException::class);

        // Then
        Inquiry::create(...$this->parameters);
    }

    /**
     * @return array
     */
    public function dp_条件に違反する値を設定してcreateすると、例外が発生する(): array
    {
        return [
            '氏名が21文字以上' => ['name', 'テスト氏名テスト氏名テスト氏名テスト氏名テ'],
            '電話番号が12文字以上' => ['phoneNumber', '123456789012'],
            '電話番号の形式でない' => ['phoneNumber', 'phone111111'],
            '対応日時が日時の形式でない' => ['createdAt', '2022-02-03'],
        ];
    }
モックに対しスタブメソッドを定義する場合は、expectsまたはallowsを目的別に使い分ける

スタブメソッドの作成時は以下の基準を設けました。expectsとallowsの使用基準を明示的に分けることで、メソッド内で最もテストしたい事柄を明示的に示すようにしました。

メソッド 基準
expects - 定義するメソッドが必ず呼び出されていることを確認したい場合
- テストを行う上で重要な条件となるメソッドとして明示したい場合
allows - テストを通すためだけに戻り値を定義したい場合
- 必ず呼び出されるかを問わない場合

今後の展望

TDDに挑戦しようとして惜しくも断念しましたが、結果としてユニットテストの実装によりコード品質が向上したことで、今後の保守運用も容易になると確信しています。
その一方で、テスト実装とコードレビューにかなりの工数が掛かってしまったことは課題に感じています。今回得た知見を土台にしつつ、AIも活用することで、実装者とレビュワーの負担を減らしていきたいと考えています。

よりテストを強化していくにあたって、機能単位でのFeatureテスト、UI層におけるE2Eテストなどを行なった後、最終的にTDDに再挑戦することが目標です。これらの取り組みを実施して開発負荷を軽減することで、顧客満足度の向上に、より多くのリソースを割けるようにしていこうと思います!

スタイル・エッジでは、モダンな開発にも積極的にチャレンジしています!
ご興味ある方はぜひ採用サイト ↓をぜひご覧ください ♪
recruit.styleedge.co.jp

*1:以下のようなサイクルを回すことで「動作するきれいなコード」を目指すTDDの進め方。
1.次の目標を考える
2.その目標を示すテストを書く(テストファースト)
3.そのテストを実行して失敗させる(レッド)
4.目的のコードを書く
5.2で書いたテストを成功させる(グリーン)
6.テストが通る状態のままコードのリファクタリングを行う
7.1~6を繰り返す

【モダン開発 番外編】気が利くDTO!データオブジェクトライブラリ「Laravel-data」を導入してみた

generated by DALL-E3

はじめに

こんにちは。前回の記事ではコード標準化に勤しんでいたneueです。

連載中のモダン開発を行う際、自身はプロジェクトの本メンバーではないものの、
処理の共通化や骨組みの構築などを通して、開発支援に近い立場で参画していました。

モダン開発を実現するにあたり、設計思想の遵守に必要なロジックを愚直にコードで表現していると、早い段階で手続きの多重化やコード量の増加と向き合うことになるのではないかと思います。

その設計思想に寄り添って作られたフレームワークがあれば話は別ですが、実際のところは既存のフレームワークとの共存方法を探りつつ、フレームワークがもたらす利便性や開発体験を、ある程度損なわない仕組みを自前で用意することになるのではないでしょうか。

今回はモダン開発の番外編として裏側の一部を切り取り、データクラス(主にDTO)の実装に使用したLaravel-dataというライブラリについて、紹介いたします。

DTO(Data Transfer Object)とは

DTOとは、プログラミングにおけるデザインパターンの一種で、関連するデータがまとまっており、書き込み/読み取りが可能なオブジェクトのことを指します。
主に、プログラム間やレイヤー間のデータの受け渡しに用いられます。

DTOを介することで、以下のようなメリットを享受することができ、
レイヤードアーキテクチャに則った今回の設計でも活用しました。

  • 受け渡し先に必要なデータ構造にすることで、責任の範囲を明確化できる
  • メソッドのパラメーターをシンプルに保てることで、定義の重複や改修時の不備を避けられる
  • DTOの生成時点で型チェックやバリデーションを行うことができる
    • 利用側は必須・任意パラメーターの情報を前提とした実装にすることができる
  • (濫用は避けるべきではあるが)再利用が可能になる

Laravel-dataとは

Laravel-dataは、Spatieが提供する以下のような機能を持つデータクラスを作成するためのライブラリです。
DTOをはじめとしたデータクラスの実装にあたり、あると便利な機能が揃っている印象でした。

  • データオブジェクトを自動的にリソースに変換 (Laravel APIリソースなど)
  • データオブジェクトの遅延プロパティのみ後から変換
  • リクエストデータからデータオブジェクトを自動的に作成・検証
  • データオブジェクト内のプロパティのバリデーションルールを自動的に解決
  • 任意の型からデータオブジェクトを作成
  • データオブジェクトの作成時に自動的にバリデーションを実行
  • データオブジェクトからTypeScript定義を生成(フロントエンド利用向け)
  • データオブジェクトをEloquentモデルのプロパティとして保存

Introduction | laravel-data | Spatie

使用方法

導入はいたってシンプルで、基本的にはLaravel-dataが提供するDataの子クラスを作成するだけです。

<?php
use Spatie\LaravelData\Data;

class PostData extends Data
{
    public function __construct(
        public string $title,
        public string $body,
        public ?int $authorId,
    ) {}
}

作成した子クラスにアトリビュートを定義することで、値の検証や変換の機能を付与することができ、データオブジェクト生成用のメソッドを追加することで、読み手に優しく防御的なデータクラスが表現できます。

<?php
use Spatie\LaravelData\Attributes\Computed;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use App\Models\Author;
use App\Models\Post;

// 生成元オブジェクト上でスネークケースのプロパティ名をキャメルケースに変換
#[MapInputName(SnakeCaseMapper::class)]
class PostData extends Data
{
    // 計算済みプロパティはコンストラクターのプロパティに含めずに定義
    #[Computed]
    public ?string $authorName;

    public function __construct(
        // バリデーションルールを定義
        #[Min(20), Max(50)]
        public string $title,
        public string $body,
        public ?int $authorId,
    ) {
        // パラメーターに渡された値を元にした計算をコンストラクター内で行いプロパティの値を初期化
        $this->authorName = is_int($authorId) ? Author::first($authorId)?->name : null;
    }
    
    // Postインスタンスからのデータオブジェクト生成用メソッド
    // ※Eloquent Modelは標準で生成メソッドが用意されているため、互換性があれば定義不要
    public static function fromPost(Post $post): self {
        return new self($post->title, $post->body, $post->author?->id);
    }
}

実際にデータオブジェクトを生成する際は、fromメソッドを用いるのが一般的です。
他言語でいうところのオーバーロードに近い使用感で、引数の数や型を元に、実際に処理に使うメソッドが自動的に選択されます。

<?php
PostData::from(['title' => 'title', 'body' => 'body']);
PostData::from('{"title":"title","body":"title"}');
PostData::from(Post::first(1));

その他の機能についても、公式ドキュメントにてサンプルコード込みで簡潔に紹介されており、比較的学習コストは低いため、この記事を読んで気になった方はぜひ一度ご確認ください。

使用にあたって工夫したこと

基本的には標準機能を活用しつつも、実際の使用にあたってはいくつか検討や調整が必要になりました。

データ種別ごとの親クラスを作成

具象データクラスは、目的ごとの共通のふるまい(生成ロジック、変換に必要な処理など)を一元化するため、Dataを直接継承せず、目的別の抽象データクラスを経由させました。

<?php
abstract class BaseActionInputDto extends Data {
    // Actionへの入力用DTOで使用する共通のふるまいをメソッドとして定義
}

// 上記の抽象クラスを継承することで、Dataの提供する機能+目的別の追加機能が使用可能
class UserCreateActionInputDto extends BaseActionInputDto {}
class PostEditActionInputDto extends BaseActionInputDto {}

アトリビュートにまつわる注意点

Dataの作り上、アトリビュートは実行時のクラスに定義したものだけが読み取られます。
つまり、マッピングなどのルールが共通事項であっても抽象クラス側に書くことができません。
定義先が分散しない利点はありますが、個別の特性か共通の特性かが分かりにくくなるので、やや惜しさを感じます。

<?php
#[MapInputName(SnakeCaseMapper::class)] // 効かない
abstract class BaseActionInputDto extends Data {}

#[MapInputName(SnakeCaseMapper::class)] // 効く
class UserCreateActionInputDto extends BaseActionInputDto {}

共通の抽象クラス

余談にはなりますが、データクラスに限らず同種クラスの単位では共通の抽象クラスを経由させておくことで、静的解析やメタ的な判定がしやすくなるという副次的なメリットもあります。
その際は、phpatなどを用いて継承のルールが徹底される状態にしておくと信用度も上がります。

<?php
$isEntity = is_a($user, BaseEntity::class);

自動キャスト

Laravel-dataにはキャストという機能があり、入出力間で型を変換することが可能です。

主にプリミティブ値(文字列、数値など)として入力されたものを、DateTimeEnumなど扱いやすいオブジェクトに変換する目的で使用されることが多く、独自のキャストクラスを追加することもできます。

Enum

BackedEnum用のキャストクラスは標準で用意されていましたが、システム上ではPureEnumが多かったこともあり要件を満たせませんでした。

標準のキャストクラスを無効化したうえで、共通の親クラスであるUnitEnum用のキャストクラスを作成することで、値だけでなくインスタンス名からもEnumキャストできるようにしました。

配列の要素

配列における各要素の型は、PHPDoc上では明示できるものの、現在のPHPの型宣言では表現することができません。 ゆえに型宣言を元にしてキャストすることも難しいのですが、要素の型を情報として付与するアトリビュートを作成し、配列に自動適用するキャストクラスを作成することで実現しました。

<?php
private function __construct(
    // int配列にしたいとき
    #[ArrayOf('int')]
    public array $counts,
    // 特定のEnum配列にしたいとき
    #[ArrayOf(ClientType::class)]
    public array $clientTypes,
) {}

任意プロパティの省略

Laravel-dataには、オプショナルプロパティという機能があり、一部のプロパティが省略可能なデータオブジェクトを生成できます。

基本的にはユースケースの単位で専用のDTOを用意しているので常に必要なものではありませんが、例えば同一の更新処理であっても、一部のプロパティが操作したユーザーの権限によって送信されないケースなどで重宝します。

nullといった値に置き換えて判別する方法と比べて「そもそも情報を送信しない場合」と「送信はするが値が空の場合」の区別が可能になり、型宣言を見るだけでどのパターンが許容されているプロパティなのかを把握しやすくなります。

<?php
class UserDto extends BaseDto
{
    public function __construct(
        // 空の値が許容されるプロパティ
        public ?string $nullableProperty,
        // 未送信が許容されるが空の値は許可されないプロパティ
        public string|Optional $optionalProperty,
        // 未送信も空の値も許容されるプロパティ
        public string|Optional|null $optionalNullableProperty,
    ) {}
}

遅延格納型の計算値プロパティ

パラメーターで渡された値を元にした計算値プロパティは、コンストラクター内で初期化を行うことで実現可能です。

ただし、常に利用されない値など利用されるタイミングで初期化処理が走ればよいものも多かったため、抽象クラス側のマジックメソッド(__get, __isset)と、具象クラス側のアクセサーメソッド(getXxxxAttribute)を定義することで実現しました。

<?php
abstract class BaseDto extends Data
{
    public function __get(string $name): mixed
    {
        // $this->get{$name}Attributeメソッドの存在確認+実行結果の返却
    }
}

class UserDto extends BaseDto
{
    protected function getFullNameAttribute(): string
    {
        return $this->lastName . ' ' . $this->firstName;
    }
}

echo $user->fullName; // 'Yamada Taro'

※アクセサー用にgetXxxxAttributeといった名前のメソッド名を定義する方式は、Laravel8以前のEloquent Modelから着想を得ていますが、9以降は実装方式が変更されているので、今後見直す予定です。

主な使用箇所

続いて、実際のシステムではどのような場面で使用したのかをご紹介します。

ユーザー入力からユースケースへの情報受け渡し

DTOの使用例としては、ユーザーインターフェイス層とアプリケーション層間のデータ連携があります。
ユーザーが入力した情報を元に一度DTOを生成して、ユースケースに受け渡すようなシーンです。

基底のFormRequestに、内部に持つリクエストオブジェクトを用いてDTOを生成するメソッドを定義しておくことで、コントローラー側は入出力とオブジェクト間の中継役に徹することができます。

コントローラー上の流れ

  1. 対象画面用のFormRequestがDIされ、入力値のバリデーションが行われる
  2. バリデーションを通過した際に、リクエストオブジェクトから対象ユースケース用のDTOを生成
  3. 対象ユースケースの呼び出し時の引数としてDTOを渡す
<?php
class UpdateController extends BaseController
{
    public function __invoke(UpdateRequest $request, UpdateUserAction $updateUserAction): RedirectResponse
    {
        // DTO作成・ユースケース実行時などのエラーハンドリングは省略
        $updateUserAction($request->toData(UpdateUserActionInputDto::class));
        
        return redirect()->route('users.show', ['id' => $request->id]);
    }
}

ドメインオブジェクトの情報をビュー向けに加工

もう1つの使用例は、ドメインオブジェクトの情報をビューで扱う際に使用する独自のデータオブジェクトです。

クエリのユースケースにはドメインオブジェクト(エンティティなど)を返すものがありますが、対象のビューにとっては不要な情報を含んでいたり、外部オブジェクトへの参照がID形式などの情報の過不足に加え、連載でも触れられていた不都合な点があるため直接は使用できません。

ビューの責務としては渡されたデータを過度な加工なく表示する程度に留めておきたく、かといってコントローラー上で細かい加工処理まで受け持ってしまうと全体の流れの見通しが悪くなりがちです。

加工内容や具体的な方法については、いくつかのパターンに分類できることもあり、ビューで必要な情報・加工方式を定義するだけで希望形式のデータオブジェクトを返却するデータクラスを作成することになりました。

コントローラー上の流れ

  1. ユースケースを用いて対象のエンティティを取得
  2. エンティティ(と情報突合用の別オブジェクトのコレクション)からデータオブジェクトを生成
  3. view()の第2引数(配列)の一要素としてデータオブジェクトを渡す
<?php
class EditController extends BaseController
{
    public function __invoke(string $id, GetUserAction $getUserAction, GetAllDepartmentsAction $getAllDepartmentsAction): View
    {
        // 部署のエンティティコレクションを(再利用するのであらかじめ)取得
        $departments = $getAllDepartmentsAction();
        return view('pages.users.edit', [
            'user' => UserVoo::from(
                // 対象のエンティティ(ユースケース経由で取得)
                $getUserAction($id),
                // ユーザー内の所属部署がID参照のため、部署情報との突合用に部署のエンティティコレクションを渡す
                ['department' => $departments],
            ),
            // 部署のエンティティコレクションからデータオブジェクト配列に変換(collectionFromは独自実装)
            'departments' => DepartmentVoo::collectionFrom($departments),
        ]);
    }
}

データクラス上の処理

利用側をシンプルに保つ反面、裏側の処理としてはデータクラスの定義を走査して、対象のドメインオブジェクトの情報を加工しつつ当てはめたものをデータオブジェクトとして返す、泥臭い作りになりました。

なお、突合時に必要な外部コレクションの取得を内部に持たせることも技術的には可能でしたが、ユーティリティ的概念からユースケースへの暗黙的な依存が生まれるリスクも感じたため、今回はコントローラー上で取得したものを引数として渡す実装にしました。

  • データの絞り込み
    • 入力されたエンティティ・値オブジェクトから、データクラスに定義したプロパティの情報のみを取得
  • データの構造化
  • データの加工
  • データの拡張
    • 定義での表現が困難な項目や計算値は、アクセサーメソッドを定義することで実現

おわりに

ライブラリの力も借りつつ、データの挙動を制御する処理を基底側に閉じ込めることで、実際の開発時に多く目に触れるであろう具象クラスは、概念の所有する情報が際立つオブジェクトに近づけることができました。

もちろん良いことばかりではなく、与えられている責務がデータクラスの範疇を超えているという見方や、ブラックボックス化やパフォーマンスなどの懸念がないわけではありません。

設計思想であらかじめ定めた遵守すべきラインの中で、本来の目的・運用時のコスト・開発者の属性・準備期間なども加味して今回の最適解を導き出したつもりですが、状況が変化したタイミングでより良い作りにアップデートすること(できるような作りを意識すること)が前提になるのではないかと思います。

長くなりましたが、スタイル・エッジでは、こういった縁の下のパワータイプなエンジニアもガッツリ募集中です!一緒に足場を組み立ててみたいと思いましたら、ぜひ採用サイトをご覧ください。 recruit.styleedge.co.jp

Google Workspace ユーザー会に登壇しました!

はじめに

皆さんこんにちは。スタイル・エッジのはるです🌞

とあるご縁をいただき、2023年10月27日、渋谷にあるGoogle Japan のオフィスにて開催されたUSEN Smart Works社主催の Google Workspace ユーザー会に登壇してきました!
今回はユーザー会に登壇するに至った背景をはじめ、私たちが当日語ったこと、そして感じたことを思いのままに書いていきたいと思います。

Google Workspace ユーザー会で登壇することになった経緯

ユーザー会とは

そもそもユーザー会とは、ある企業の製品やサービスを利用するユーザー同士や、サービスを提供する企業とユーザーが情報交換などを行う場のことを指します。

今回は Google Workspace を提供する企業・利用するユーザー企業が集まって開催されました。
プログラムの1つに Google Workspace 活用企業事例の枠があり、今回は弊社が話をする機会をいただきました。

Google Workspace の積極活用

弊社で Google Workspace の利用を開始したのは2020年から。
Google Workspace Enterprise Plus という最上位のプランを利用しています。クラウド環境の拡充、セキュリティ強化を図り活用を開始しました。

Google ドライブGoogle ドキュメントGoogle カレンダー ・ Gmail など豊富なサービスが利用できますが、 最近特に力を入れているのがノーコードツールである AppSheet の活用です。

AppSheet とは

AppSheet は Google Cloud のノーコードツールであり、有料版の Google Workspace ライセンスを保有していれば、AppSheet Core(有料版)を自由に使用できます。 (2023年12月現在)
Google Cloud のノーコードツールということもあり、 Google Workspace との連携も容易な点が強みです。
有料版のライセンスも使用でき拡張性が高い点も考慮すると、
Google Workspace ユーザーにとって AppSheet は数多あるノーコードツールの中でも有力な候補になるのではないでしょうか。

弊社では今年に入ってから力を入れて AppSheet を活用し始めました。

ITガバナンスの強化

AppSheet の活用と併せて、弊社では Google Workspace をITガバナンスに活用しようと努めています。
というのも、弊社で契約している Enterprise Plus は何といってもセキュリティ機能が売り!
活用しない手はないですね👏

セキュリティ機能が充実の Enterprise エディション

Google Workspace のプランは様々ありますが、エディションという括りで捉えると違いが分かりやすいです。
公式では以下のように各エディションを説明しています。

Google Workspace の各エディションの比較

Google Workspace には、個人、中小企業、大規模組織、学校向けのサブスクリプション オプションがあります。

  • Business - ユーザー数が 1~300 人のビジネス向けの生産性向上スイート
  • Education - 所定の要件を満たす教育機関向けの割引されたスイート
  • Enterprise - 無制限のユーザーに対する優れたセキュリティと高度な制御が追加で利用可能
  • Essentials - Gmail を使用しない生産性向上ツールとコラボレーション ツール
    support.google.com

公式の説明からも、Enterprise エディションはセキュリティ・制御機能に力を入れていることが一目瞭然です!
弊社がITガバナンスに【 Google Workspace Enterprise Plus 】を生かしたい所以がここにあります👍

具体的には「DLP」や「セキュリティセンター」といった機能に注目していますが、
詳細な話はまた別のブログ記事で書けたら面白いかも?と思っています!

ユーザー会で話したこと

前置きが長くなってしまいましたが、、 いよいよユーザー会に登壇した時の話です!

ザックリと今回話したトピックを紹介します。
今回の登壇に向けて、弊社のデザイナーと協力して登壇資料を作成しました!
エンジニア×デザイナーという職域の枠を越えたコラボレーションができるのは強みだなぁとしみじみと感じました...👏 (おかげで資料に対する反響も沢山いただきました🌟)

というわけで、当日使用したスライドも交えてお伝えします!
ざっくりと、以下3つの軸で話をしました!

Google ドライブ 利用ルールの整備

Google Workspace の中でも利用者が特に多いであろう、 Google ドライブ
とっても便利なサービスである一方、データのガバナンスをどのように行っていけばよいのか悩むケースも多いのではないでしょうか?
ユーザーにとっての使いやすさ、データ管理のバランスを考慮した弊社の運用ルールを紹介しました。

AppSheetの活用

先述したAppSheetの活用について紹介しました。
Google Workspaceの力を更に引き出す」をコンセプトに、AppSheetとGoogle Workspaceをセットで活用するメリットをメインに話しました。
※Style Workspace という表記は弊社内部で呼称しているサービス名です。
 Google公式の製品名ではないため悪しからず...笑

ITガバナンスの強化

最後にセキュリティ機能の活用。
ここはGoogle Workspaceの管理者が最も手を出しづらい領域なのではないでしょうか...?
非常に強力な機能が備えられている一方でどのように使ったらいいかわからない... 私たちもそうでした。
今回は、「弊社のセキュリティルールをシステマチックに制御する」という観点で行っている施策について話しました。

ユーザー会に参加して感じたこと

Google Workspace ユーザー会での登壇は弊社にとっても非常に学びのある機会となりました。

自社オリジナルの視点に気が付けた

日々目の前の業務に必死になっていると、悲しくも自分の立ち位置が客観的に見えなくなるものです...
今回はユーザー会登壇にあたり、何を話したらユーザー会に参加される皆さんに価値を提供できるのか色々と考えました。

その中で、「セキュリティという足場固め」と「先端技術活用による攻め」の両立といった私たちが大切にしているスタンスや、それをどうやってツールを通して実現しようとしているのか振り返るきっかけになりました。

Google Workspace・AppSheet を活用した攻めるために守る、守るために攻める姿勢を引き続き貫いていきます。

他社でも似た悩みを抱えており、相談できる場が貴重だと感じた

当日は他社様のシステム管理者の方も沢山参加されており、皆さんが抱えていらっしゃる悩みを交換する場もありました。
私たちが悩んでいることはみんなも悩んでいるんだと気が付き、仲間は沢山いるなという気持ちになりました笑

と同時に、私たちが工夫している運用を発信していくことは多くの悩みを解決することに繋がるという実感も得ました。
実際に、 Google ドライブ を組織として管理する上で工夫している弊社の運用が勉強になったとの声もいただき嬉しく思いました。

既存ツールの活用もエンジニアリングであると感じた

ユーザー会登壇にあたり弊社の Google Workspace の運用を振り返り、当日は Google Cloud から直接 Google Workspace の活用方法をレクチャーしていただく機会を得ました。
その中で、新規でシステムを開発するばかりでなく状況に応じて既存のツールを使いこなすのもエンジニアリングであるなぁと感じました。

自力でやらなくてもいいことが増えている昨今、システムを分かったうえで既存サービスに上手く任せていく。
そんな力を引き続き養っていきたいと思います。

最後に

今後も自社オリジナルの運用を強化し、サービスの活用を推進していきます。
面白い施策はまた発信できればと思いますので乞うご期待です!笑

SaaS乱立時代の波を一緒に乗りこなさんとする方、攻めのバックオフィスを実現していきたい!という熱い想いのある方、そんな仲間をお待ちしています! recruit.styleedge.co.jp