TECH PLAY

スマートキャンプ株式会社

スマートキャンプ株式会社 の技術ブログ

226

はじめに なぜViteに移行したか 導入方針 開発環境に導入 vite側の作業 詰まったところ vite自体に付属するmanifestオプションを使用すると、manifest.jsonの形式が大幅に変わってしまう 同じスタイルを複数のエントリーポイントで読み込むとファイル名が変わってしまう Rails側の作業 ビルドの設定 Staging、Pre環境へのデプロイ検証 リリース 結果 今後 最後に はじめに こんにちは!スマートキャンプ開発エンジニアの林(ぱずー)です。 BOXIL SaaSのフロントエンドは歴史的経緯からjQuery、CoffeeScript、Vue、Reactが混在した環境で開発しています。 今回はReactで作られているページにViteを導入したので、経緯やハマったことについて書こうと思います。 BOXIL SaaSにReactを導入した記事はこちら なぜViteに移行したか webpackはビルドが遅く、本番環境のビルドもファイル数が少ないのにも関わらず、3〜4分ほどかかっていたり、開発環境でもコードを変更するごとに30秒ほど待たされるためスピード感を持って開発できる状態ではありませんでした。 また、移行先としてTurbopackも検討しましたが、Railsと合わせて使う場合においては、 ドキュメントの充実度や日本語の情報も多いことから、Viteの方が移行しやすいと感じたため、選択しました。 ちなみに過去に導入を検討したこともあったのですが、当時はInternet Explorer対応が必須だったため見送りました。 ようやくInternet Explorerがサポート外となったので(とはいっても去年ですが)、晴れて導入できるようになりました。 導入方針 開発環境に導入 ビルドの設定 Staging、Pre環境へのデプロイ検証 リリース 開発環境に導入 BOXIL SaaSのReactはRails上のhtml上でReactを読み込んでいるため、デフォルト設定では動きません。 そのため、 Backend Integration に従って導入しました。 vite側の作業 この場合、読み込む必要があるファイルをオブジェクト形式でrollupOptions.inputに記述していくのですが、そのままどんどん追記していくとかなり設定ファイルの見通しが悪くなってしまいます。 そこで、glob.syncを使用して、手動で読み込むファイルを追加する手間を解消したいと考えました。 webpack時代はこんなコードが何行もあり、ファイルを追加するごとに書き足していくやり方でした...。 const entries = { hoge: "./hoge.tsx" , ...この後何行にもわたるエントリーポイントの記述 } ただ、現状すべてのファイルがルートディレクトリ直下にフラットに配置されており、glob.syncで自動で取得するには無視するディレクトリをignoreオプションで設定しなければなりませんでした。 const srcFileKeys = glob.sync ( "**/*.+(js|ts|tsx)" , { cwd: srcDir , ignore: [ '.eslintrc.js' , 'vite.config.ts' ] } ); それを解消するために、entriesディレクトリにすべてのエントリーポイントを集約することでglob.syncを手軽に使用できるようにしました。 const convertToEntryKey = ( key: string , ext: ".js" | ".css" ) => { return key.replace ( /^entries\// , "" ) .replace ( /\.[^/ . ] +$/ , ext ); } ; const entries: { [ key: string ] : string } = {} ; const srcDir = "./" ; const srcFileKeys = glob.sync ( "entries/**/*.+(js|ts|tsx)" , { cwd: srcDir , } ); const srcStyleKeys = glob.sync ( "styles/**/*.+(scss|css)" , { cwd: srcDir , } ); for ( const key of srcFileKeys ) { const srcFilepath = path.join ( srcDir , key ); this .entries [ convertToEntryKey ( key , ".js" ) ] = srcFilepath ; } for ( const key of srcStyleKeys ) { const srcFilepath = path.join ( srcDir , key ); this .entries [ convertToEntryKey ( key , ".css" ) ] = srcFilepath ; } export default defineConfig ( { build: { rollupOptions: { input: entries , ...other } , } , } ); これでrailsのhtml側でファイル名を指定するとjsを読み込めるようにしています。 例: vite_javascript_tag('hoge') 詰まったところ vite自体に付属するmanifestオプションを使用すると、manifest.jsonの形式が大幅に変わってしまう 解決策として、 rollup-plugin-output-manifest を導入し、今までと変わらないやり方でmanifest.jsonを出力できるようにしました。 import outputManifest from "rollup-plugin-output-manifest" ; export default defineConfig ( { appType: "custom" , plugins: [ outputManifest ( { nameWithExt: false , } ), ...other ] , } ) before: " _hoge-4dc41c9a.js ": { " file ": " javascripts/hoge-4dc41c9a.js ", " imports ": [ " _styled-components.browser.esm-0475c222.js ", " _colors-52758e30.js ", " entries/hoge.tsx " ] } , after: { " hoge.js ": " /bundles/javascripts/hoge-0403de2a.js " , } 同じスタイルを複数のエントリーポイントで読み込むとファイル名が変わってしまう swiper を複数のページで使用する必要があったのですが、 読み込んだ回数によってmanifest.jsonに吐き出されるファイル名が変わってしまうことが判明しました。 1回だけ読み込んだ場合: スタイルを読み込んだ場所のファイル名で吐き出される。 " hoge.css ": " http : //localhost:3000//bundles/stylesheets/hoge.css" 2回以上読み込んだ場合: なぜかswiper.cssとして吐き出される。 " swiper.css ": " http : //localhost:3000//bundles/stylesheets/swiper.css" この状態だと、rails側でスタイルを読み込むときに指定するファイルが読み込む回数によって変わってしまいます。 解決策としてoutputManifestのmapオプションで出力するmanifest.jsonのキーを手動でマッピングしてあげることで、glob.syncで読み込んだファイルのみ出力するように変更しました。 これで、manifest.jsonに記載するファイルをglob.syncで読み込んだファイルに限定できるので、読み込み回数によってファイル名が変わることはなくなりました。 import outputManifest from "rollup-plugin-output-manifest" ; const getEntryName = ( name: string | undefined ) => { if ( ! name ) return "" ; const entryPaths = Object .keys ( entries ); const entryPath = entryPaths.find (( entryPath ) => entryPath.includes ( name )); if ( ! entryPath ) { throw new Error ( `entryPath is not found \ntarget: ${ name } \nentries: \n ${ entryPaths.join( "\n" ) } ` ); } return entryPath ; } export default defineConfig ( { appType: "custom" , plugins: [ outputManifest ( { nameWithExt: false , map: ( chunk: Bundle ) => { const name = getEntryName ( chunk.name ); return { ...chunk , name , } ; } } ), ...other ] , } ) Rails側の作業 Rails側でも上記のmanifest.jsonを読み込むための作業をしました。 viteをRuby上で動かすにあたって Vite Ruby を使用することも検討しましたが、 ライブラリを導入することによる依存の発生や、vite以外の選択肢が今後出てきたときに乗り換えやすいようにVite Rubyの 実装 を参考に必要な部分だけ使用する形で独自実装することにしました。 すべてのコードの記載は割愛しますが、webpack時代の処理に比べてScriptタグをmoduleで読み込む点と、開発サーバーと接続する設定が入っている点が大きく異なってます。 def vite_react_refresh_tag react_refresh_preamble&.html_safe if dev_server_running? end def vite_client_tag ( crossorigin : ' anonymous ' , **options) javascript_include_tag(vite_dev_host( ' @vite/client ' ), type : ' module ' , extname : false , crossorigin : crossorigin, **options) if dev_server_running? end def vite_javascript_tag (entry, type : ' module ' , skip_preload_tags : false , crossorigin : ' anonymous ' , **options) # MEMO: 開発中の場合は開発サーバからjsを取得する return javascript_include_tag(vite_dev_host( " entries/ #{ entry }" ), crossorigin : crossorigin, type : type, extname : false , **options) if dev_server_running? files = vite_manifest.fetch( "#{ entry } .js " ) tags = javascript_include_tag(*files, crossorigin : crossorigin, type : type, extname : false , defer : true , **options) # modulepreloadタグを生成してモジュールを先読みする tags << vite_preload_tag(*files, crossorigin : crossorigin, **options) unless skip_preload_tags tags end ビルドの設定 本番環境ではCIでビルドされたファイルをS3に配置しているため、manifest.jsonで参照されるURLをS3に向けてあげる必要があります。 そこでrollup-plugin-output-manifestにpublicPathのオプションを追加し、環境に合わせてURLを変更できるようにしました。 export default defineConfig ( { plugins: [ outputManifest ( { publicPath: ` ${ process .env.PRECOMPILED_ASSETS_HOST ? process .env.PRECOMPILED_ASSETS_HOST : "" } /bundles/react/` , nameWithExt: false , } ), ] , ...otherConfig } ); こうすることで、以下のようにURL付きのmanifest.jsonが生成されるので、読み込む先を変更できます。 " hoge.js ": " https://domain/bundles/react/javascripts/hoge.js-350806ce.js ", 詰まったところとして、swiper(7.4.1)を使用しているページがビルドがうまくいかない問題が発生したので、aliasを書いてあげることで解消できました。 エラー文: [commonjs--resolver] Missing "./swiper-bundle.min.css" specifier in "swiper" package error during build: Error: Missing "./swiper-bundle.min.css" specifier in "swiper" package export default defineConfig ( { resolve: { alias: { swiper: path.resolve ( __dirname , "node_modules/swiper" ), } , } , ...otherConfig } ); 現時点ではswiperでしか発生していませんが、他のライブラリでも発生する可能性もあるので、注意が必要です。 その他、CicleCIでメモリの問題で頻繁に落ちてしまう問題が発生したため、ジョブを分けることで回避しました。(ちなみにビルドに20秒ってだいぶ早いですね) Staging、Pre環境へのデプロイ検証 試行錯誤しつつも、開発環境での動作は問題ない状態になったので、staging環境にデプロイしたところ、JavaScriptの取得リクエストで404エラーが大量発生しました。 ドキュメントを追ってみると、デフォルトでは build.modulePreload オプションが有効となっており、自動でpreloadするためのjsを読み込んでいた事が原因でした。 preloadタグの生成はRails側で行なっているため、設定を無効化することで解消できました。 export default defineConfig ( { ...otherConfig build: { modulePreload: false , // デフォルトではpolyfill: trueとなっていて、preloadするためのpollyfillが自動注入されてしまう } , } ); リリース 現状、残念ながらフロントエンドのテストがほとんど書かれていないため、開発チームで手作業で頑張りました。 具体的には、BOXIL SaaSの主要ページシナリオを作成し、Vite導入に影響する箇所を確認していくことで、リリース後のバグが少しでも発生しないように努めました。 結果、幸いにも本番リリース後も特に大きな問題は起こらなかったので、無事に完遂できました。 結果 ローカル環境でのコード反映: 30秒 → 1秒未満 これは例えば、10行のコードを変更した場合、webpackだと反映に300秒かかっていたのが、Viteだったら10秒で反映できることになります。 ちなみに、webpack時代はコードの反映が遅すぎて、コードの保存を極力まとめて行なっていたのですが、Vite導入によって臆することなく、コード保存するショートカットキーを多用できるようになったので個人的には数字以上の開発体験の向上している実感があります。 ビルド時間: 49秒 → 7秒 現状コード量が少ないことや、構成上、Railsのアプリケーションのビルドが速くならないと結果的にデプロイ速度は向上しないため、そこまで重視していない項目でしたが、こちらも大幅に改善していました。 今後BOXIL SaaSにおいてReactのコードがメインになってくる(していく)ことを考えると、 ビルド速度はボトルネックになりうる部分なので、早めに改善できてよかったです。 今後 冒頭にも説明したとおり、歴史的経緯からBOXIL SaaSのフロントエンドはカオスな状態なのですが、 ここ一年でyarn workspaces(v3)導入によるモノレポ化と、今回の記事で取り上げたVite導入をおこなってきました。 直近あったプロジェクトではReactを使用してスピード感を持って開発できているので、 これまでの地道な改善による効果が一定数あったのかなと感じています。 今後の個人的な展望としては直近のVitest導入に合わせて、継続的にテストを書いていける環境・方針の策定を進めて更なる開発体験の向上を推進していくことや、 統一されておらずページごとにバラバラになっているコンポーネントをデザイナーさんと協力して整理していきたいと考えています。 最後に 長い歴史を持つプロダクトの負を改善していくにあたって、工数の兼ね合いから諦めなければいけない部分も出てくる事がありますが、 少ない工数で劇的な変化をもたらすこともできることをViteを導入してみて感じました。 今後もこのような改善を継続的に行なって開発効率を上げて、さらなるプロダクトの価値向上に貢献していきたいです!
アバター
はじめに 対象読者 主なキーワード 共通ID基盤プロジェクトについて なぜプロジェクトを開始したのか? 共通ID基盤構築の要件 共通ID基盤の技術選定 認証基盤に関連する技術群 どの技術を使うべきか? アーキテクチャの検討 隠れたサービス要件の発覚 サービスに求められる要件について OIDCで必須のOPへのリダイレクト OIDCとサービス要件の不一致 別の方法を探る 1. 主力プロダクトをOPとする案 2. サービスのドメインを統合する 3. サービスをコードレベルで統合する 終わりに はじめに スマートキャンプ株式会社京都開発拠点では、自社開発プロダクトであるSaaSマッチングプラットフォーム「 BOXIL SaaS 」とオンラインイベントサービス「 BOXIL EVENT CLOUD 」の間でアカウントを共有する共通ID基盤の開発を進めています。 この記事では、その開発過程でOpenID Connect(OIDC)の採用を検討した結果と得られた知見について解説します。 具体的には、共通ID基盤の開発過程を時系列で紹介し、OIDC採用の検討過程、ビジネス要件との折り合いをつけることが難しかったこと、およびOIDCを使わないケースも含めたシングルサインオンの代替案についても触れます。 対象読者 ID基盤を作る際に、OIDCを導入しようか迷っている人 かつOIDCの基本的なことは理解している人 主なキーワード BOXIL SaaS : SaaSマッチングサービス。主力プロダクト。 BOXIL EVENT CLOUD : ビジネスのための情報共有の場としてのオンラインイベントプラットフォーム。 OpenID Connect(OIDC) : 認証基盤としての最有力候補技術。 シングルサインオン(SSO): 一つのID(アカウント情報)を入力するだけで、連携する他のサービスにもログインできる仕組み。 OpenID Provider(OP):OIDCにおけるユーザー認証のサービスを提供するサーバーまたはプラットフォーム Relying Party(RP):OIDCにおけるユーザーの認証情報を利用するサービスやアプリケーション 共通ID基盤プロジェクトについて なぜプロジェクトを開始したのか? プロジェクトの対象サービスがBOXIL SaaSというサービスと、BOXIL EVENT CLOUDというサービスであることは冒頭にお伝えしました。 BOXIL SaaSとBOXIL EVENT CLOUDとは、別々のサービスではありますが、片方はSaaSを導入したい/提供したい企業向けのマッチング、もう片方はイベント開催を通してビジネス上の知見・つながりを提供しています。 ですのでターゲットとする顧客としてはある程度重なる部分もあります。 現状ではそれぞれ独自の認証システムを持っているので、ユーザーは各サービスで別々に登録する必要があり、サービス間のデータ連携もありません。一方のサービスに登録しているユーザーに対して、そのデータを活用したもう一方のサービスで便利な機能を提供するなどの施策を簡単に提案できない状況でした。 このような課題を解消するため、共通ID基盤の導入により、一度登録すれば複数のサービスでアカウントを共有できるようにするプロジェクトがスタートしました。 ここで現状におけるサービスの簡単な構成図をお見せしておきます。 それぞれのサービスがユーザー情報を独立して持ち、認証も別々に行なっていることがわかります。 認証の仕組み(Before) では現状を踏まえたうえで、要件をまとめていく次のステップに移ります。 共通ID基盤構築の要件 チームでは、ビジネスサイドのメンバーとも話を進め、共通基盤に求められる具体的な要件をまとめていきました。 要件をかいつまんで説明すると下記のような内容になります。 ドメインの異なる2サービス(BOXIL SaaS、BOXIL EVENT CLOUD)でIDを統合して利用できるようにする もともとユーザー属性の近いサービスであるため、IDの一元化によりサービス間での相互送客を狙いたい 会員情報の二重入力や更新の手間を省き、サービスのUXを向上したい 各サービスのデータを紐づけたうえで、データ分析やマーケティング活動に活用したい 属性の近い別のサービスを将来的に連携する際のID連携の基盤を作る 新規サービスにおいても、既存サービスのユーザーを低コストで連携できるようにしたい 本プロジェクトでは上記の要件を満たすべく、まずは関連する技術選定から始めていきました。 共通ID基盤の技術選定 認証基盤に関連する技術群 今回のように異なるドメインのサービス間でのSSOを実現する際に、関連技術をいくつかピックアップし検討していきました。 以下に主要な検討技術をピックアップして簡単に特徴を説明します。 OIDC OAuth2.0を拡張し、認証にも利用できるようにしたプロトコル IDトークンを用いて認証をして、UserInfoエンドポイントからユーザーのプロフィール情報を取得する 異なるドメインのサービス間でのSSOが実現できる データのやり取りはJSON形式で行なう OAuth2.0 認可のためのプロトコル(認証ではない) この仕様で認証を賄うには独自に拡張を行なう必要があるが、リソースサーバーから提供されるプロフィールAPIを用いての認証の方法には脆弱性があり、拡張の際にはセキュリティなどのリスクに特に気をつける必要がある データのやり取りはJSON形式で行なう SAML 異なるドメインのサービス間でSSOを実現できるプロトコル SAMLは独自に定義された認証規格であり、OIDCがOAuth 2.0に基づいているのとは対照的 SAMLはXML形式でデータをやり取りする 独自構築 規格には属さないものの、完全に独自で認証システムを設計する選択肢も存在します。この方法は、特定の要件に対してもっとも柔軟に対応できる点が強みです。しかし、OIDCなどセキュリティも考慮された規格とは異なり、セキュリティリスクも自分たちで考慮の上仕様を決める必要があるため、その点で難易度が高いと言えます。 さらに、この手法は既存のライブラリや参考資料が少ないため、構築と運用のコストが他の方法よりも高くなる可能性があります。 どの技術を使うべきか? チームでは、先にまとめた既存規格の技術、独自認証などの特徴と、求められる要件を考慮したうえで検討した結果、今回の共通ID基盤プロジェクトにおいては第一候補としてOIDCを採用することになりました。 選定の際に考慮した点については下記の通りです。 社内のスキルセットとのマッチング OIDCはOAuth2.0の拡張であり、基本的なフローはOAuth2.0とほぼ同じであること 既存プロダクトではOAuth2.0の導入実績があり、社内で知見がある程度蓄積されていること 低い複雑性 SAMLよりもプロトコルがシンプルで、仕様自体の見通しが良いこと 走り出しから継続して検討をしながら進めていくような今回のプロジェクトには、シンプルであることがプラスで働くこと 将来的に新しいサービス連携することを考慮したときに、スピーディに連携できそうなこと セキュリティの考慮 IDトークンの署名・暗号化や、 PKCE (Proof Key for Code Exchange)など、多くのセキュリティ機能とベストプラクティスが組み込まれており、必要十分であること ライブラリの充実 標準化され多く利用されている規格であるため、既存のライブラリやツールのサポートが豊富で、設定例やドキュメントも充実していること 基盤のベースの技術としてOIDCを採用したので、それを前提としてシステム構成を考えていきます。 アーキテクチャの検討 次にOIDCを前提として、共通ID基盤を入れたときのサービス全体のアーキテクチャを検討しました。下記に簡略化した構成図を掲載します。 現状のシステム構成図は「 なぜプロジェクトを開始したのか? 」の章を参照してください。大きく変わったところは下記です。 認証を担う機能を共通ID基盤として新たに構築する BOXIL SaaS、BOXIL EVENT CLOUDでそれぞれ持っていた認証の実装を、共通ID基盤のOIDC経由で行なう実装に置き換える。 各サービス上にあるユーザー情報のうち、認証に使う情報を中心に共通ID基盤に移す。(それ以外の多くのサービス固有のユーザー情報は据え置き) 認証の仕組み(After) OIDCの観点だと、BOXIL SaaS、BOXIL EVENT CLOUDの各サービスはそれぞれRPとなり、新しく作る共通ID基盤はOPとして振る舞う形になります。 隠れたサービス要件の発覚 チームでは、作ったアーキテクチャの想定を元に、実際にOIDCを使ったときに具体的にどのような画面遷移になるだろう?実際のUXはどうなるだろう?といったユーザー観点での調査を重ねて、ビジネスサイドとも話を進めていきました。 サービスに求められる要件について ところで、BOXIL SaaSでは、さまざまな施策を通じて会員の獲得効率(CVR)を最大限に高める戦略を採っています。たとえば、資料請求フォームの入力後に会員登録処理と並行してログインができるよう実装したり、EFO(Entry Form Optimization)によるUXの最適化に重点を置いています。 今回のプロジェクトでも、会員登録の手間を減らしてUXを向上したり、サービス間での相互送客を実現するなどの目的がありますが、実際問題としてはCVRを損なわないという前提条件下で考慮される必要があります。 ただ、この要件は事前に明文化できていたわけではなく、チーム間で明確に認識の形成ができずにプロジェクトが進行して、徐々に懸念が強く浮き彫りになってきたという感じでした。 OIDCで必須のOPへのリダイレクト OIDCの仕様の中にはいくつかの認証フローが定義されていますが、特に今回採用を予定していた認可コードフローにおいては、"OPへのリダイレクト"という仕様があります。 下記のシーケンス図をご覧ください。 シーケンス図 認証の開始時にRPであるBOXIL SaaSから、OPである共通ID基盤にリダイレクトされ、認証が完了した後にOPからRPに認可コード付きでリダイレクトされて戻ってくる流れがあることがわかります。 このリダイレクトはOIDCの仕様上重要で、RPとOP間の疎結合性を保つことで、安全に認可コードをRPに渡すことを実現しています。 OIDCとサービス要件の不一致 さて、前述したように、BOXIL SaaSとしては会員獲得におけるCVRに影響を与える変更は極力避けたい事情がありました。プロジェクトチームでは、OIDCのリダイレクト仕様がBOXIL SaaSのCVRにどのような影響を与えるかを慎重に考察しました。 結果、リダイレクトを入れることで会員登録やリード獲得のプロセスに影響が生じ、ユーザーが外部の認可エンドポイントへ移動することで、会員獲得の動線が分断されて、CVRやEFOによるユーザー動線の最適化にも悪影響を及ぼす懸念が出てきました。 それを避けるために、なんとかリダイレクトしつつも極力CVRに影響を与えない次善策を含め、調査と検討を重ねたのですが、決定的な策が提案できず、最終的には独立した共通ID基盤経由でOIDC認証をする構成を採用しない決断をしました。 高い自由度のUXを確保するために、OIDCのリダイレクト要件は今回のプロジェクトには合わないと判断したのです。 別の方法を探る では、前述した「リダイレクトしたくない」というビジネス要件と、「リダイレクトが必要」というOIDCの仕様を受けて、どういった意思決定をすべきでしょうか。 私たちが現状調査・検討してきた案をいくつかご紹介します。 BOXIL SaaSをOPとする案 サービスのドメインを統合する サービスをコードレベルで統合する なお、この記事を執筆時点では、目下、案の検討を進めている途中のため、どれを採用したのかは言及しません。どの案もUI/UXへの影響や、開発・保守運用工数への影響などなどトレードオフがあり影響度も大きいため、慎重に検討を進めていく必要があります。 また考えるうえでは、短期的なメリット、中長期的なメリットなど時系列も含めて議論が必要と考えています。 以下にそれぞれを簡単に紹介します。 1. 主力プロダクトをOPとする案 認証の仕組み(BOXIL SaaSをOPとする案) この案はOIDCの認証を管理するサーバー(マイクロサービス)を新規で開発・運用するのではなく、考慮すべきビジネス要件が多いBOXIL SaaSをOPとして振る舞わせ、BOXIL EVENT CLOUDはRPとしてそのエンドポイントを利用する形です。 そうすることでBOXIL SaaSでは従来の会員登録のフローを変更する必要がなくなるため、ネックだったリダイレクトの影響に対する懸念は考えなくて良くなります。 デメリットとしては、認証機能自体のBOXIL SaaSへの依存が強くなってしまうため、連携するサービスの負荷でBOXIL SaaSも影響を受けてしまったり、例えばBOXIL SaaSがクローズする際に連携サービスが影響を受ける可能性などがでてきてしまいます。 また、この案は一度しか使えず、今後連携するサービスでは通常のOIDCによるリダイレクトが強制される形になります。 以降の案はOIDCを使わない案です。 2. サービスのドメインを統合する 一方のサービスのコードベースを、もう片方のドメインに丸ごと移すやり方です。 認証の仕組み(サービスのドメインを統合する案) 例えば、BOXIL SaaSは現在 boxil.jp 、BOXIL EVENT CLOUDは boxil-event-cloud.jp でホスティングしていますが、これを boxil.jp 、 event.boxil.jp に変更すれば、サードパーティクッキーの制限がなくなります。 片方でログインして、もう片方のサービスとログインセッションを共有することも技術的に可能になります。 3. サービスをコードレベルで統合する これはできるケースが限られると思いますが、もし連携するサービスのビジネスドメインや会員属性が近く、数が多くない場合は、サービス自体を一つのコードベースに統合する案も考えられそうです。 認証の仕組み(アプリケーションをコードレベルで統合する) 当然ながら自由度は非常に高いですが、引越しする側のサービスについてゼロベースに近い開発が必要になるため、完了までにかかるコストや、統合にあたっての双方のデータスキーマの見直しなど、多岐にわたる検討が必要になります。 終わりに 本プロジェクトの開発過程では、技術選定だけでなく、ビジネス要件との整合性も重要であることが明らかになりました。OIDCは多くの場合に有効な技術ですが、それありきで進めるのではなくUXとビジネス要件をしっかりと両立させ、最適な技術選定と実装を広い視野で追求していく必要があることを今回学ぶことができました。 この記事が皆さまの参考になれば幸いです。
アバター
BOXIL SaaSの開発チームについて Notionでプロダクトバックログの管理をやってみた結果 尋常じゃない量のプロパティ Sprint番号 PointとPlanning Point 親タスクとサブタスク ドキュメントのリレーション 実装の効果 所感 メリット ドキュメントツールとタスク管理ツールが同じだって?! 自由性 新しい機能が増えていくのが楽しい デメリット あくまでドキュメントツール 多機能すぎる 自由すぎる まとめ こんにちは!! BOXIL SaaSのエンジニア兼テックブログチーム平社員をしているブラーバです。今週は弊社テックブログチームのスクラム月間(勝手に言ってる)ということで、プロダクトバックログの管理をNotionで行っているお話をしようかと思います。 もともと、弊社では社内のドキュメントを他のドキュメントツールで管理していましたが、以下のような理由でNotionへの移行チャレンジをしています。 同時編集を手軽に行いたい 柔軟にドキュメントの階層を設定したい そのドキュメントの作成者がオーナーのような感覚になり、同じようなドキュメントが乱立してしまう そして、同時にNotionはタスク管理ツールとしてもさまざまな機能や テンプレート が存在するため、BOXIL SaaS開発チームではプロダクトバックログの管理まで試みました。 このNotionチャレンジから半年以上経ちましたが、さまざまな試行錯誤をしてきました。 今回はその試行錯誤の中身をこのような方々に共有できればなと思います。(ここ半年くらいの試行錯誤をアウトプットしたかっただけなんてことはない) スクラムをある程度知っていて、Notionを社内ドキュメントとして利用している方 Notionをタスク管理ツールとして使えるか不安な方 すでにNotionでプロダクトバックログの管理をしていて、困っている方 BOXIL SaaSの開発チームについて まずはBOXIL SaaSの開発チーム(以降、開発チーム)がどのようにスクラム開発をしているかを説明しようかと思います。 (スクラムなんか余裕で知ってるZE☆という方々や早く本題に入らんかい!!という方々はぜひ本題の Notionでプロダクトバックログの管理をやってみた結果 に進んでください!!) 開発チームの人数は現在10人弱です。 スプリントの期間は2週間であり、以下のスクラムイベントが存在します。 スプリントプランニング デイリースクラム リファインメント スプリントレビュー レトロスペクティブ(振り返り) そして以下図の流れでアイテムが生成され、リファインメントでアイテムの理解をし、スプリントプランニングで2週間でこなせそうなレベルまでタスクを細分化しています。 開発手順 各部門から上がってきた機能改善や施策等をPdMの方々が管理しているデータベースで管理されています。 それらのアイテムをリファインメントを通じて見積もりをし、優先度順にNotionのテーブルビューとして存在する プロダクトバックログのテーブルビュー にアイテムが追加されます。 そしてスプリントプランニングにて プロダクトバックログのテーブルビュー から次のスプリントで達成できそうなアイテムを スプリントバックログのテーブルビュー に移動し、スプリントが始まるとそれらを着手するという流れです。 プロダクトバックログとスプリントバックログのデータベースが同じであるため、それぞれのテーブルビューに表示される条件にアイテムを変更すればスムーズにアイテムを移動させることができるようにしています。 さて今回はプロダクトバックログとスプリントバックログのテーブルビューに表示されているアイテムのデータベースについて半年強(一年弱と言っても過言ではない..?)の試行錯誤を書いていきます! Notionでプロダクトバックログの管理をやってみた結果 ここまで順に読んでくださった皆さん、読み飛ばしてきた皆さん、ここまで長々と書いてきましたがここから本題です。 前述のプロダクトバックログとスプリントバックログのテーブルビュー、そしてアイテムの中身をそれぞれ説明したいと思います。(早く説明せい) プロダクトバックログのテーブルビュー こちらはプロダクトバックログのテーブルビューです。(流石にテストで作成したアイテム以外は載せたら怒られそうだったのでマスキングしてます、怒られるのヤダ...) スプリントプランニング時には優先度の高い順からスプリントで達成可能なレベルにまでタスクを細分化するため、タスクがどうしても階層構造になります。そのため、Notionの サブアイテム という機能を利用し階層構造を表現しています。 タスクデータベースでサブタスクを使用する方法 また、上のキャプチャでは表示されていませんがフィーチャーやエピックなどの大きめの粒度のタスクも同様のデータベースで管理することがあるため、その際は 開発カテゴリ というマルチセレクトでラベル付けをしたりします。(そのラベルだけを表示するテーブルビューなどを作れたりする) ここからチームのベロシティを考えつつ、スプリントで達成可能なアイテムをスプリントバックログに入れていきます。 スプリントバックログのテーブルビュー そしてこちらがスプリントバックログのテーブルビューです。 Sprint番号 というプロパティを入力するだけでプロダクトバックログからスプリントバックログに移動できるようにしたかったため、スプリントバックログのテーブルビューのフィルターに Sprint番号が入力されているか という条件を入れています。 また、このテーブルビューはNotionの グループ という機能を利用し Sprint番号 でグループ化しています。 完了したスプリントはグループ横の三点リーダーから非表示をしています。(アーカイブみたいな感じ) Notion 2.13:データベースのグループ化・サブグループ化 そしてNotionはもちろんドキュメントツールなので、スクラムにおいての 生きているドキュメント として活用しています。 以下がテストで作成したアイテムの中身です。 アイテムの本文 開発チームではスプリントプランニング時にアイテムの完成の定義を決めています。 そのために必要な情報である 背景 や ユーザーストーリー 、 受け入れ条件 などをアイテムの本文に書くようにしています。(執筆時にちょうどお腹が痛かったわけでは断じてない) これらの項目はNotionの データベーステンプレート という機能を利用し、アイテム作成時に自動で挿入されるように設定をしています。 リファインメントに持ってくる前にこれらの項目を埋めるだけで、リファインメントがスムーズに行えるため、非常に便利です。 データベーステンプレート このようにNotionでプロダクトバックログのアイテムの管理をしています。 次の章では、このデータベースに次々と追加されていったプロパティの紹介をしていきます。(たぶんここがミソ..?) 尋常じゃない量のプロパティ 使っていないプロパティも存在していますが現在36のプロパティがあります。(絶対ありすぎるので棚卸しを今、誓いました) その中でもこのプロパティの運用はうまくいっているな、というものを紹介しようと思います。 Sprint番号 前述の通り、 スプリントバックログのテーブルビュー に追加するときに設定しているプロパティです。 Sprint番号 もともとこのプロパティは日付になっており、スプリントの期間が入っていましたが管理が大変ということからセレクトになりました。 しかしながらどうしてもスプリントを跨いでしまうタスクがあり、現在は マルチセレクト に落ち着いています。 おかげでNotionの グループ という機能を使いつつ、開発チームが見るテーブルビューのメンテナンスがとても楽になりました。 また、このプロパティで簡単にどんなタスクを行っていたかのテーブルビューを作れたりするのもとてもいいところです。 自分の消化したタスク群 PointとPlanning Point 開発チームではリファインメント時に工数をストーリーポイントで見積もっており、その際に Planning Point を入力します。 pointとplanning point そして実際に着手しアイテムが完成したタイミングで、着手した人たちの体感に基づいて Point を入力するようにします。 これはリファインメントやスプリントプランニングの精度の振り返りに利用するための分析用に入力をしています。 このプロパティ導入時はベロシティが不安定で開発スケジュールがうまく立てることができておらず、その対策として追加されました。 これにより振り返りで分析ができたり、実際に消化できるベロシティがどのくらいなのかが大体把握できる様になりました。 (今はブレても±1ptなのであまり振り返ることも少なくなった) 親タスクとサブタスク このプロパティも少し出てきましたが、親子関係を表すために追加しました。(親タスクなのに子タスクじゃなくてサブタスクなのは目を瞑ってください) 親タスクとサブタスク 小さなバグ改修から1ヶ月以上かかる施策などアイテムの大きさはバラバラです。開発チームでは2週間のスプリントで終わらせる大きさにまでアイテムを細分化しますが、その親子関係を表現するために利用しています。 また、Notionのアップデートによりテーブルビューでも親子関係を表示できるようになったときは、感動しましたね。(ただのNotionヘビーユーザー) プロダクトバックログのテーブルビュー(再掲) また、親タスクのテンプレートはタスクを細分化する前提で作られています。そのアイテムを親タスクにしているアイテムを表示するテーブルビューがテンプレートに入っており、わざわざ親タスクを設定しなくてもテーブルビューの +新規 からサブタスクを作成できます。 親タスクのテーブルビュー 親タスクには背景やユーザーストーリーは必要ですが、サブタスクには必要ないことが多々あります。 そのため、サブタスクで利用するテンプレートでは現状と、そのタスクで何をしたいかなどだけを書けるようにしています。 Notionはテンプレートを複数用意できるため、非常に便利ですね! サブタスクのテンプレート ドキュメントのリレーション 開発チームではできるだけスプリントプランニングでどのように着手するかの認識の共有しており、手戻りを防いでいます。 しかしながら複雑なロジックや仕様理解が必要なアイテムも存在します。 そのような場合は 社内の他のチーム のようにDesign Documentを作成することにしています。 Design Docs このリレーションも親タスクのテンプレートに追加しており、 +新規 からドキュメントの作成ができるようにしてあります。(これめっちゃ便利というのを伝えたい...) 実装の効果 レトロスペクティブや期末の振り返りなどでよく出てくる話題として「自分たちがどのくらい価値提供したかわからない」というものがあると思います。 そんな意見をPdMの方が吸い上げてくださり 実装の効果 というリレーションが生まれました。 実装の効果 これはアイテムのオーナーなどが実際にどのくらい効果があったのかを記載してくださる、別のデータベースへのリレーションです。 このデータベースにもテンプレートが用意されており、開発業務とあまり関わりのない人でも記入しやすいようになっています。(この記事を書いているときに初めてテンプレート見たなんて言えない) 実装の効果テンプレート (だんだん楽しくなってきた...) 所感 ここからは、1メンバーとして他のタスク管理ツールからNotionにチャレンジしてみて感じた所感を述べたいと思います。(一応移行チャレンジからずっと試行錯誤してきたメンバーの一人のはず...) メリット ドキュメントツールとタスク管理ツールが同じだって?! 何と言っても一番のメリットはドキュメントツールとタスク管理ツールが同じことです。 今まではタスク管理ツールにドキュメントにリンクを貼ったりPRにアイテムのリンクとドキュメントのリンクの二つを載せていたりしましたが、一つに集約していることでオーバーヘッドがかなり減ったと思います。 またアイテム自体がドキュメントにもなり得るため、スクラムにおける 生きているドキュメント としての振る舞いがストレスなくできます。(アイテムの中に書きすぎて別途ドキュメントに起こすときにコピペしたり同期ブロックとしてペーストしたり...) このメリットが個人的に一番大きいなと思います。 自由性 開発チームでは パーソナル部屋 というものを作っており、その中ではそれぞれの思い思いのNotionの使い方をしています。 Tipsや業務知識をドキュメントの仮置き場として、ひたすらページを作っている人やたくさんテーブルビューを作っている人もいます。 私はNotion上でも個人のタスク管理をしており、プロダクトバックログのデータベースに一方的なリレーションを貼ったり、次のデイリースクラムで話すことをメモったりしています。 ぶらーば部屋 (ToDoを早く消化しろ自分...) このようにカスタマイズ性に優れていたり、データベースが参照しやすかったりするため私達エンジニアとも相性が良いのかなと思います。 新しい機能が増えていくのが楽しい Notionチャレンジ当初は親子関係を表す機能や依存関係を表す機能などはなかった(たぶん)ですが、どんどん新しい機能が追加されることによって、個人的にNotionの沼にハマっていきました。 (新しいもの好きとしてはたまらんです) デメリット あくまでドキュメントツール さっきと言ってることが真逆な気がしますが、Notionはやはりあくまでドキュメントツールです。 そのため、 Asana や Jira でできる◯◯◯ができない!!みたいなことはあります。 私達のNotionの使い方が悪い説もかなりありますが、親子関係や依存関係が複雑になりすぎて管理しづらくなってしまうアイテムがあったり、半期の振り返りのときにわざわざAPIを叩いたりと手間がやはりかかってしまいます。 多機能すぎる とはいえ、Notionは機能が豊富だと思います。そのおかげで痒いところに手が届いたりするんですが、私達のチームにとって必要な機能と必要でない機能の取捨選択が難しいなと感じるときもあります。 便利ということと管理が煩雑になるということはトレードオフなのかなと思います。(頑張って管理します) 自由すぎる 管理が難しいという文脈で同じではありますが、やはり自由なことがデメリットになるときもあります。 ここ半年でプロダクトバックログのアイテムが突然消えていたり(フィルターにかからない条件になっていた)、ロックをかけていないテーブルビューのプロパティが勝手に変わっていたり(あとでロックをかけた)しました。 他にも、誰が追加したか分からないプロパティがあったり(データベース自体にもロックをかけたが誰が使ってるか分からないから消すに消せない)と、管理が難しいなという印象があります。 しかしながらこれはしっかりとデータベースを管理するオーナーを決め、使い方をまとめたドキュメントなどが整備されていれば解決する問題なのかもなと思います。(ドキュメントって大事..!!) まとめ 弊社ではドキュメントツールをNotionにしようという動きがあり、タスク管理もできるんじゃないか?という淡い期待を寄せて半年強ほどNotionチャレンジをしてきました。そのメリットとしてはやはりドキュメントとタスク管理が一つのツールに集約されていることだな(当たり前)と執筆しながら感じました。おかげで価値のあるドキュメントが作れたり、オーバーヘッドがかなり減ったりと開発チームの生産性が爆上がりしています。 しかしながら多機能が故、管理が難しく、しっかりとしたルール整備や取捨選択が必要だと思います。 まだNotionチャレンジは1年生ですので、これからももっとアップデートしつつ、このような形でアウトプットできたらなと思います!!
アバター
スプリントプランニングとは 最近のBOXIL SaaS開発について 先に結論 施策 1.ポモドーロ・プランニング ポモドーロ・テクニックとは やってみた感想 おまけ(ChatGPTのプロンプト) 2.ファシリテーター・書記の順番交代制 ルーレット 3.内職を我慢する 4.おやつを食べる まとめ スマートキャンプでBOXIL SaaSのエンジニアをやっております永井です。 猛暑のみぎりでございますが、皆さまいかがお過ごしでしょうか。 今回はスクラム開発におけるスプリントプランニングに関してブログを書きました。 というのも正直スプリントプランニングって結構大変じゃないですか? そこで今回は最近のスプリントプランニング状況や、チームの取り組みについて共有しようと思います。 スプリントプランニングとは そもそもスプリントプランニングとは何かを軽くおさらいします。 スクラムガイドには以下のように書かれています。 スプリントプランニングはスプリントの起点であり、ここではスプリントで実⾏する作業の計 画を⽴てる。結果としてできる計画は、スクラムチーム全体の共同作業によって作成される。 https://scrumguides.org/docs/scrumguide/v2020/2020-Scrum-Guide-Japanese.pdf ゴールの策定など多くの要素がありますが、第一にすることは「次のスプリントで何するかしっかり皆で計画を立てようぜ!」と私は認識しています。解釈違いの方ごめんなさい。 現在BOXIL SaaSチームでは、開発チームが中心となり、次のスプリントでは対象のタスクをどう進めるかを詳細に詰めていく作業をしています。 開発タスクにおいては既存仕様の共通認識を取ったうえで、実現の方法を話し合います。調査タスクでは何をどこまで調査すれば良いかを話し合います。 ここでは「どうやってタスクを進めるか、皆で話し合ってるんだな」くらいに思ってもらえればOKです。 最近のBOXIL SaaS開発について BOXIL SaaSチームでは、ここ数ヶ月の間に業務委託の方や新卒の加入が相次ぎ一気にメンバーが増えました。 また人数も増え、大きめのタスクも増えたため、作業時間を多めに確保するためにスプリント期間を1週間から2週間に変更しました。 スクラムガイドによると、1ヶ月のスプリントでは、プランニングにかける時間は最大8時間とされています。したがって、今回は2週間のスプリントに対して、プランニング時間を半分の最大4時間に設定し、定期的に確保して開催しています。 また弊社ではリモートワークを中心としているため、スプリントプランニングもオンラインで行っています。 これらをまとめたものが下記です。 スプリントは2週間 プランニングにかける時間は最大で4時間 つまり4時間で2週間分のタスクのプランニングを行なう。 最近JOINしたメンバーが多め もし参考にされる場合はこれらも頭の片隅に置きつつ読み進めると良いかもしれません。 先に結論 こういった話はスクラムに限らず一発逆転ホームランのような手は無いです。また「スプリントプランニングが楽勝に進む最強のメソッド!」みたいな怪しささえ感じるものもありません。あったら教えてください、本当に。取り入れたい。 以前も「ホームランは狙わず地道に改善しました」といった内容の記事を書いていますので気になる方はご参照ください。 tech.smartcamp.co.jp そんな中でも楽しく参加できたり、継続することで良くなっていくような施策を振り返りの中で考え、実行に移しているので、いくつかご紹介できればと思います。 施策 1.ポモドーロ・プランニング いきなりですが「ポモドーロ・プランニング」は造語です、すいません。 名前の通りポモドーロ・テクニックを用いてプランニングをしよう、というものです。 ポモドーロ・テクニックとは ポモドーロ・テクニックは、25分の作業と5分の休憩を繰り返す作業管理法です。4回に1度、長めの休憩を取ります。作業と休憩を明確に分けることで、集中力を維持できるというものです。 ポモドーロ・テクニックは、個人の勉強や作業などで使用されるのが一般的ですが、我々はそれをプランニングに適用しています。とはいっても、25分プランニングして5分休憩を繰り返し、4回に1度長めに休むだけです。特別なことはしていません。 やってみた感想 プランニングは長時間話し続けて疲弊することが多いため、明確な休憩の時間があると疲労が抑えられているように思います。個人的には目を閉じてボーッとすることが多いのですが、その間に頭も少し整理されるので効率があがっているように感じています。 また、ファシリテーターも定期的に交代をしているため良い区切りになっています。 あとは切れ目があることで「この25分で話しきりたい」という意識が働くのですが、これには一長一短があります。 良い点としては、重要なポイントを見極める意識が働くことです。また、それにより話が脱線しにくくなると感じています。 悪い点としては、急いでしまうことで考慮漏れができてしまう点があげられます。少しだけ脱線することで新しく考慮すべき内容が見えてくる事もあるので、脱線=悪という訳でも無いように思います。 おまけ(ChatGPTのプロンプト) スケジュールを考えるのが面倒なときはChatGPTに以下のプロンプトを投げるとよしなに組んでくれます。ご活用ください。 ◯時から◯時まで以下のフローに従ってスケジュールを組んでください - 25分間スプリントプランニングを行い、5分間休憩を1セット - これを繰り返し4セットに1回15分の休憩 - スプリントプランニングには◯さん・◯さん・◯さんをランダムかつ均等にファシリテーターとして割り振る 2.ファシリテーター・書記の順番交代制 今プランニングでは議論を推進する、いわゆるファシリテーター役を定期的に交代しながら進めています。これは特定の人がずっと中心になって話し続けるといったプランニングの属人化を防ぐと言った狙いがあります。あとついでにタスクのチケットに加筆する人も順番に交代しています。 ルーレット 担当はルーレットで決めています。以前は指名もしていましたが毎度指名するのも大変なので、最近はランダムに身を任せています(笑) プランニングで議論をしている中での良い雰囲気づくりに一役買っているように思います。 デメリットはルーレットにも時間がかかることです(本末転倒)。ちょっと項目が多いですね。 最近のルーレット。名前以外は答え合わせが始まるが、それはそれで良し。 個人的には「人志松本のすべらない話」で出てきた、あのサイコロ位のノリで決められると良いと思います。☆が出たら「最近出てないし◯◯さんやってみますかー」みたいな。 3.内職を我慢する 急に当たり前な施策ですね(笑) 「そりゃそうだろ!」と言われそうですが、オンラインだと案外難しいと感じています。 プランニング中に開発タスクをやっちゃう、位の明確な内職だと分かりやすいし意識的に避けやすいと思います。 しかし、議論の中で分からないことを調べてそのまま脱線してしまったり、Slackから「スッコココ」と聞こえ、返事の間に話に置いていかれるといったケースは案外多いのではないでしょうか。 なるべく手を動かさないようにして議論に集中しようという話なんですが、そうしてると本当に疲れます。そのための定期的な休憩ですね。 また、プランニング中はファシリテーターと書記以外は気になったことはコメントをどんどん残していくように取り決めています。オンラインMTGツールはチャット機能も付いているので、話すだけでなくそこにも書き込むことで議論を積極的に行います。 オンラインだと話す人が一人に限られてしまったり、オフラインだとできる「小さめの声で隣の人に確認する」といったことができなかったりするため、こういったところでも補っているように思います。 4.おやつを食べる もはや施策なのかと言われそうですが施策です(断言)。 集中はしつつも緊張しすぎないように適度におやつでもつつきながら議論しています。 オンラインMTGなのでマイクの切り忘れなどで咀嚼音ASMRをメンバーにお届けしないように気をつけるのがポイントです。グミとかチョコとかおすすめです。 まとめ 結局のところ、本質的に良いプランニングのためにすることは、メンバー全員がより仕様に詳しくなり、意思決定も滞りなく行えるようになることではないかと考えています。 それに対して今回あげた施策は直接は影響しない、いわば付け焼き刃のような施策と言えるかもしれません。 ですが、現状の中で最善のプランニングを行い、手戻りのない開発を進めることで、未来のより良いプランニングにつながると考えています。今回紹介したこれらの施策もそれには一役買っていると思っています。 うまくプランニングができているかは開発が上手く回っているかの指標の一つになると思っているので、より良いプランニングを目指してこれからも振り返りつつブラッシュアップしていければと思っています。
アバター
ご挨拶 はじめまして! 2023年4月よりスマートキャンプに23卒として入社しました小宮です。 社内ではリーブスと呼ばれています。学生時代のインターンでもジェネシスと呼ばれていたので、なんかカタカナ系のあだ名が多いです。 自分について文章を書くのは苦手ですが、とりあえず書いていきたいと思います。 自己紹介 出身地は東京の蒲田で、東京の住みたくない街ランキングではいつも上位を守っています。 ネットの口コミを見ていたら、「昼間はスラム街のような雰囲気」と書かれていて、笑ってしまいました。 ですが交通の便も良く、自分的には住みやすい街だと思っています。 趣味は筋トレ(ダイエット)とサウナで、仕事が終わった後は基本的にそのどちらかに行っています。 学生時代 大学では文系の学部に通っていたため、エンジニアが何をする職業なのかも知らず、複数のサークルに入って典型的な大学生をやっていました。 当時、学習塾でアルバイトをしていたのですが、同僚のアルバイトから「生徒のテスト管理が面倒」という声をよく聞くようになりました。 そこで簡単にPCで管理できる方法はないかと考え、新型コロナウイルスの影響で大学がリモート授業で時間を持て余していたということもあり、プログラミングの基礎を勉強しました。 プログラミングの基礎を勉強することは楽しかったですが、同時に独学では作れないと感じました。 そのため当時働いていたアルバイト先の社長にお金を出してもらい、プログラミングスクールに通い、無事にアルバイト先にツールの導入できました。 作ってみて周りからのフィードバックも嬉しかったのですが、0から1を作る楽しさを知ることができたのが収穫だったと思います。 どんなインターンに行っていたか 技術力を身につけるためには実務経験が必要だと考え、アルバイト先でツールを導入した後にインターンを探し始めました。 当時の技術力と使用言語で求人を絞っていくと、あまり求人数は多くなかったように思います。そんな中、不動産系のSaaS企業でのインターンを決めました。 当時、僕はPHPをフレームワークを使わずに書いていました。なので会社でどんなフレームワークを使うのかワクワクしながらインターンを始めると、 聞いたこともない(Laravelしか知らないだけ)Zend Frameworkというフレームワークを使っていて、少し落ち込んだ記憶があります。 実際にインターンを始めると、クライアントがいるサービスを作ることになったので、自分の書くコードに責任感を抱くようになりました。 そして、SaaS企業であったため、毎週のようにアップデートしていく過程で運用を意識したコーディングなども学び、独学では学べない箇所も知ることができたのは良かったと思いました。 就活 インターンでの実務経験を経て、エンジニア職で就職活動を始めました。 SaaSであったり新規事業に挑戦している会社を中心に受けていきました。 ただ就活のやり方を全く知らなかったので、優秀なインターンの友人に何度も就活のやり方を教わった記憶があります。 何とか第一志望だった会社から内定をいただくことができましたが、大学を卒業する直前に一身上の都合で内定を辞退してしまい、崖っぷちになりました。 新卒の募集はもう終わっていたため、中途採用で探していたのですが、実務経験3年の壁を越えることができずに断念しました。 そこで、大学を卒業するか留年するか悩んだのですが、当時は大学にいること(就職留年)のメリットがあまりないと思い、卒業を決断しました。 フリーター生活 学生という肩書きを失い、フリーターになったのですが、思いのほか楽しかったです。 今までの敷かれたレールの上から急に外れて、謎の解放感に包まれた記憶がありますw 就職活動をしながら、暇だったので興味があったブロックチェーン系のスタートアップでアルバイトをしたり、旅行をしたりなど、 人生でフリーターを経験してよかったなと思いました(何を言っているのだろう)。 この期間の就活中に、スマートキャンプとご縁があり、今に至ります。 スマートキャンプへの入社理由 スマートキャンプに興味を持ったきっかけは、私が選考を受ける程に関心があったSaaS業界に特化したサービスや事業を行っていたからです。 また、同社では新規事業への前向きな姿勢が強く、選考を受けてみようと思いました。 選考を進める中で、選考に関わっていただいた方々の人柄が良く、社員の方々のnoteやテックブログを見ると、 同じ方向性を持っていてVISIONに共感している人が多く、個人としても組織としても成長していけるのではないかと感じました。 以上の理由からスマートキャンプへの入社を決めました。 入社してみて 内定者インターン スマートキャンプの内定者インターンは半年間程行いました。 最初は京都にある開発拠点に在籍し、スマートキャンプの技術を実践的な形で学びました。 私は東京にいたため、リモートで参加させてもらっていましたが、最初は少し不安でした。 リモートで働いたことがなく、自宅の環境も最悪で(最初はパイプ椅子に座っていました)、対面でのコミュニケーションが取れないことから関係性を築くことができるのかと思ってました。 しかし、実際に働き始めてみると、GatherやSlackなどで細かいコミュニケーションが取れることがわかり、一週間ぐらいでリモートワークは最高だと思いました。 また、スマートキャンプではフルリモートで働くエンジニアの方も多く、コミュニケーションを取る手段がしっかり整備されている印象を受けました。 インターンを行った所感としては、今まで触れたことのないテストやインフラ周りなどをたくさん学ぶことができ、 これまでのエンジニアのアルバイトでは得られなかった視点からの学びが多かったなと思います。 研修について 4月に正社員となり、最初に行ったのはマネーフォワードと合同の研修でした。 期間は約2ヶ月で、前半はビジネスと合同で社会人スキルを身につけるような研修で、後半はエンジニアの研修になりました。 前半のビジネスと合同の研修では、FFSワークショップ(Five Factors & Stress)やロジカルシンキング研修などを行いました。 FFSワークショップでは、自分の性格やタイプなどを数値化していただき、自分が思っていた自分の性格とは違う発見や相性の良い性格などを知ることができました。 また、ロジカルシンキング研修では、事実と解釈の違いや情報を整理する方法などを学び、今後の社会人生活で活用できる内容だったと思います。 ビジネス研修全体を通して、グループワークが多く、同期の数もかなりいたので、たくさんの人と話す機会をいただくことができました。 エンジニア研修では、3人のグループで約3週間で図書館の管理システムを作りました。 基本的にはリモートで作業して、週に1回程度オフラインで進捗を確認していました。 使用技術から設計まで、自分たちで決めていくので、今までの業務などでは経験できない範囲を経験できました。 自分のチームは3人チームでしたが、配属後の使用技術が異なるため、技術選定が難しかったです。結果として、Next.js、Kotlin、GraphQLという、自分がすべて初めて触る技術スタックになりました。 ただ、新しい技術を学ぶこと自体に抵抗感はなかったので、チームの仲間と通話を繋げながら、作業してわからないところがあればすぐに聞くことができたので、苦しむことなく作業できました。 他のチームとは異なる機能を実装したくて、ChatGPTにおすすめの本をレコメンドする機能なども作っていただいたのですが、 最終的には間に合わなかったため、自分たちのリソースと向き合うことの大切さなども学ぶことができました。 最終日には発表会があり、他のチームのプロダクトを見て、マネーフォワードのレベルの高さを感じました。完成度やデザインが高いだけではなく、英語で発表しているグループもいて驚きました。 英語の方が情報量が多い点や、オフショア開発などを考えると英語でのコミュニケーションは必須なので、今後の課題として英語力を上げていきたいと思っています。 なので最近はDuolingoというサービスを使って、1日10分だけ英語の勉強をしていますw。 研修を通じて、たくさんの同期に出会うことができたことは、良かったと感じました。 研修が始まる前から上長に何よりも友達をたくさん作るように言われていたので、プライベートで旅行に行ったり飲みに行ったりできる同期ができたのは、今回の研修の一番の収穫だったと思います。 初めてスクラムをやってみて  研修が終わった後は、BALES CLOUDというインサイドセールス特化のSaaS事業部に配属となり、スクラム開発をすることになりました。 スクラム開発を始めた当初は、スクラムという単語を聞いたことあるなぐらいのレベルで、頭の中には屈強なラガーマンがたくさんいました。 なのでまず最初に行ったことは、スクラム開発をする目的とスクラムイベントの理解から始めました。 具体的には、ドキュメントを読んだり、YouTubeで解説動画を見たり、OJT担当の方に質問したりなどをして理解を深めていきました。 ある程度流れが掴めてきたら、積極的にファシリなどに手を挙げて全体像を理解していくようにしました。 スクラム開発をやってみて初めに思ったことは、ミーティング(スクラムイベント)が異様に多いことです。 学生時代もエンジニアのアルバイトは行っていましたが、タスクを渡されて黙々と開発するだけだったので、こんなにミーティングをする必要性があるのかと疑問を思ったこともありました。 ただミーティングがしっかり行われるからこそ、細かい仕様変更などに柔軟に対応でき、手戻りすることが減っているなと感じました。なので生産性は学生の頃に比べると明らかに上がっていると思います。 またBALES CLOUDでは朝会(デイリースクラム)を非同期化するなど、同期的に行わなくても問題がないミーティングなどを 非同期化していく取り組み をしています。 非同期化することで、ミーティングの時間を減らし、連続的な開発時間を確保する取り組みも素晴らしいと思いました。 スクラム初心者が理解を深めるうえで、一番大事なことはしっかりタスクの完了率を追うことだと思いました。 タスクの完了率を追うことで、1スプリント内でタスクを終わらせる意識が生まれ、タスクの正しいポイント付けや考慮もれをリファイメントで話したり、 リリースまでの進捗状況を常に意識することになり、スクラム開発の目的やメリットを一番感じれるようになるのではないかと思います。 入社してからの目標 長期的な目標としては、新規事業などに携わって、プレイヤーではなくマネージャー的な立場になりたいと考えています。 そのため、エンジニアリングスキルを伸ばすことはもちろん、ビジネススキルも身につけ、個人の成長と事業の成長の両方を考えられる人材になることが必要だと思っています。 短期的な目標であれば、現在所属しているBALES CLOUDはまだまだ発展途上で、多くの人々の負担を軽減できるサービスだと考えているので、 機能を充実させ多くの人々に価値を提供できるように努めたいと思います。 まだまだ駆け出しですが、誰よりも経験を積んで成長していきたいと思っています。 まとめ ここまで読んでいただきありがとうございました!これからコミットして結果を出していこうと思いますので、よろしくお願いします!
アバター
マリ緒のあいさつ はじめまして,4月よりスマートキャンプに23卒として入社しました那須野です。 社内では マリ緒 と呼ばれていますがもはや面影すら残ってないですね。あまつさえ最近は「マリ緒っち」や「マリリン」という派生形で呼ばれるようになってきたのでもう訳がわかりません。 今回は自分語りする機会を頂けたので、思う存分語りたいと思います。 地方勢だけど上京してエンジニアとして働きたい人 スマートキャンプに興味がある人 就活で色々迷っている人 高専生 このような方々の目に留まり、参考にしていただければ幸いです。どうぞよろしくお願いします;) 自己紹介 自己紹介です。つらつらと書きます。 出身は岩手県で、地元の工業系高専に通っていました。 携帯の電波すら圏外になるような辺境の地に住んでいました。光回線が開通したのも最近のことです。(やばいね) 現在二十歳で、高専を卒業してそのまま新卒入社した形になります。 ねことゲームが好きです。 下の写真は実家で飼ってるねこちゃんです。かわいいね。 学生時代のおはなし 高専、知ってますか? 高専、知ってますか? 5年制の教育機関で専門技術を16歳からたくさん学べちゃいます。校則はだいぶ緩く、部活は任意だったり、授業は15時で終わったりと自分の時間をたくさん確保できます。 最近は某呪術マンガの影響で多少認知度は上がっていると思いますがそれでもたまに「コウセン...???」みたいな反応をされることがあります。 ちょっと悲しいです。 IT?なにそれ美味しいの? 学生時代からWeb開発一筋だったかというとそういう訳でもなく吹奏楽部で楽器吹いてたり、アニメ見たりと割とITとは無関係な学校生活を送っていました。(そもそもド田舎出身でPCをまともに触る機会がなかった) きっかけ そんな自分がなぜエンジニアになろうと思ったのか、きっかけはコロナ禍のリモート授業にあります。 新型コロナウイルスが猛威を振るっていた2020年中頃、自分の通っていた高専もついにリモートでの授業を開始しました。そして、学校側も学生側も慣れないリモート授業のなかで友達からこのような話をされました。 「課題出すの...忘れるくね...????」 ...確かに。ハッとしました。自分の通っていた高専は比較的年配の教師が多く、慣れないリモート授業で課題の周知、回収が曖昧になっていました。 そこで、授業で習った覚えたてのPythonと当時どっぷり沼にハマっていたTwitter(今も)を利用して、課題通知Botなるものを作成することにしました。 下の写真は実際にBotが投稿したツイートです。(アイコンは自分で描いた。ブラックサンダー美味しいよね。) このBotをクラスメイトに使ってもらい、彼らから放たれた次の言葉が自分がエンジニアになりたいと思ったきっかけになりました。 「これめっちゃいいじゃん」 この一言がめっっっっっちゃ嬉しいんですよね。なにせ発案から実装まで、全部自分1人でやったことがみんなに評価されたんですよ!自分を全肯定してくれてるんですよ!嬉しくない訳ないじゃないですか! この時の嬉しさが忘れられず、テクノロジーで人をたくさん喜ばせたいと思いエンジニアを志すようになりました。 道が決まった その後、友人の影響でWeb技術を触り始め、Webアプリ開発を通してじっくり沼にハマっていきました。 そうしてなんやかんやしているうちに就活の時期になり、自分は将来Webで飯を食っていきたいと思い, Webエンジニア志望で就職活動をすることにしました。 それだけではなく、将来は東京で働くんだい!っていう強い想いもありました。なぜかは知らないんですけど、田舎の若者は都会に強い憧れを持ってるんですよね。異常な程に。 (でも実際新卒でWebエンジニアを目指すとなると企業自体がそもそも少ないので厳しい話ではあった) 就活のおはなし ポートフォリオ頑張った 就活のおはなしです。 Webエンジニア志望で就活を始めることにした自分. とは言っても一体何から始めたらいいんでしょう... 自分はインターンを終えると同時に内定かっさらってきたつよつよな友人に相談しました。そして「何かしらの成果物を用意しろ」とありがたいお言葉を仰せつかったので自分はまずポートフォリオの作成に取り掛かることにしました。 さて、何を作ろうか。 う〜ん何も思い浮かびません。ToDoリスト?メモ帳?Twitterクローン?否、インパクトが薄い。なぜそれを作ろうと思ったのかをしっかり説明できるようなものを作りたいと思いました。 そうして悩んでいるときに何とはなしに友人に「なんか困ってることない?」と聞いてみました。すると「小2の弟がいるんだけど九九を覚えられなくてねぇ」とのこと。 これだ。マリ緒に電流走る──! さて、解決するテーマは決まった。これをどう解決していこうか。自分は「 共感覚 」という言葉をテレビで耳にしました。以下 Wikipedia からの引用です。 共感覚(きょうかんかく、シナスタジア、英: synesthesia, 羅: synæsthesia)は、ある1つの刺激に対して、通常の感覚だけでなく 異なる種類の感覚も自動的に生じる知覚現象をいう。 例えば、共感覚を持つ人には文字に色を感じたり、音に色を感じたり、味や匂いに、色や形を感じたりする。複数の共感覚を持つ人もいれば、1種類しか持たない人もいる。共感覚には多様なタイプがあり、これまでに150種類以上の共感覚が確認されている。 ...これだ。マリ緒に電流走る──!(2回目) 失礼しました。ということで共感覚に着目し、色と数字を連想させてあげることができれば覚えやすくなるんじゃないかと考え、 KUKU と題しまして「九九を視覚的に覚えることができる教育向けWebアプリ」を開発することにしました。名前については何も聞かないでください。お願いします。 長くなるので詳細は割愛しますが実際に開発したものは画像の通りです。だっさいですね。(当時はイケてると思ってたんだけどなぁ) でも自分でデザインしたアイコン(↓)だけは今でも気に入っています.(KとU、教える人と教わる人を組み合わせてる) デザインはともかく企業さんが技術力を判断する一番の材料になるのはこのポートフォリオの内容だと思ったので当時の自分なりにめちゃくちゃ頑張りました。 今振り返ってみてもこのポートフォリオ開発のマインドは間違っていなかったなぁと思います。 ライバルは大卒、 院卒 自分1人ですべて行なうのは効率が悪いと思ったので就活エージェントさんの力をお借りし、自己分析や就活対策をしてもらいました。 その後、自分は就活マッチングイベントに参加するようになり、そこで僕は今まで見えていなかった現実を突きつけられることになります... 他の参加者、全員歳上なんですよね、大卒とか院卒とか。それに対して自分は当時19歳、まして自分はWebを触りはじめてまだ一年ちょっとだったのでめっちゃ緊張したし不安でした。 選考の結果も奮わず、落ち込み、焦り、イベントを重ねていくごとに自信を失くしていきました。 頑張った結果 そんなこんなでメンブレしていたんですがどうしても 東京に行きたかった Webエンジニアになりたかったので頑張りました。諦めずに。 今思うとよくあそこまで頑張れたな〜って思うんですが、頑張れた秘訣はちゃんと休憩の時間を取れていたからだと思います。 自分はかなりのゲーマーなんですが、どんなに面接やイベントが重なって忙しくても好きなゲームをやる時間は絶対に確保していました。絶対にです。 ネガティブな事象が起きた後もそれを続けると生産性も落ちるし、何より心がしんどくなっちゃうんですよね。ネガティブに毒されたドロドロした心は自分が向き合うべきことまでドロドロ(?)させてしまい掴みようのないものになってしまいます。 だからこそ好きなことに取り組む時間を毎日必ず設け、習慣化することで毎日心に栄養を補給してあげる必要があるんじゃないかなと思います。(習慣化してあげることで「俺はこんなことをやってていいのか...;;」っていう罪悪感も薄れますし) このように自分の心と向き合いながらめげずに頑張り続けた結果... スマートキャンプと出会い、働かせていただくことになりました。 その後(インターン) 内定をいただいた後、スマートキャンプでインターンをすることになりました。 大学とは違い、高専は必修の科目が多く就活が終わった後もそこまで時間に余裕があるわけではありませんでした。長期休暇中は週4で参加できていたのですがそれ以外は中々参加できませんでした。幸い単位に余裕があったので必修ではない科目を休んで参加することにしましたが、それでも週2回が限界でした。つらい。 今これを読んでいる高専生、大学生の皆さん。単位だけは余裕を持たせておきましょう。⚪︎にます。 そんなわけであまり長い期間インターンに参加できていたわけではありませんでしたがスマートキャンプの業務の雰囲気、文化などを感じるには十分すぎる時間だったと思います。 インターンはフルリモートで行ない、オンボーディングWebアプリを開発したり、実際にプロジェクトにジョインしてタスクをこなしたりとフルリモートでも十分だと思えるほど濃密な内容でした。(オフラインでできるのが一番いいんだけども地方勢にはつらい) 完全に個人で行なっていたポートフォリオ作成とは違い、インターンでは学んだことがたくさんありました。他の人に見てもらうことを意識したコード、レビュアーが見やすいようなPR作成、進捗報告などのアウトプットのわかりやすい伝え方、実際の運用を考慮してのセキュリティ面、CI/CD、etc.多いですよね。個人開発では中々学べないことだと思います。忙しい中時間を削って参加した甲斐があったってもんです。実際、今インターンで学んだことを活かせてますし。 インターンにはもちろんオフラインで参加している方もいますが、インターンの内容としては共通のものに取り組みました。田舎人は差別されるとかそういうことはないです。安心してください。 また、オンラインオフィスツールの Gather を使用しているため、オンラインでもオフラインのような感覚で話しかけることができます。下の写真は実際のチャットだけだとどうしても解決できないこともあるため、Gatherの存在はかなり大きかったです。 下の写真は実際に集まって雑談している様子です。わちゃわちゃしてて楽しいです。 インターンに参加している人の技術レベルはまちまちでしたが、それぞれに対応した内容のタスクや課題を用意してくれるため、苦しみすぎることはありませんでしたが正直、焦りはありました。でも頑張るしかないんです。その人も同じ道を通ってきたはずだから。 今振り返ると、インターンで実際の業務の流れや使用している技術を体験できていたことが今の自分にとって大きなアドバンテージになっているなぁと思います。 入社理由 スマートキャンプと出会ったのは逆オファー形式のマッチングイベントでした。 前述した課題通知botを作成してから作業の自動化などの効率の改善が好きになっていた自分にスマートキャンプのMISSIONである 「 テクノロジーで社会の非効率を無くす 」 がぶち刺さりました。 嬉しいことにオファーをいただきまして、そのイベント内で一次面接を通過できました。その後もオンラインランチや面接を重ね、次回最終面接というところまでたどり着くことができました。 そしてそのタイミングで就業体験として本社にお招きいただき、実際にメンバーの雰囲気や企業文化を肌で感じることができました。たくさんの選考を受けてきましたが選考途中で就業体験として会社にお招きいただいたのはスマートキャンプだけでした。 このお誘いを受けた時点でスマートキャンプは技術力云々の前に その人がどんな人物なのか をしっかり見て、丁寧に選んでいるんだと感じました。 就業体験を終えたときにはそれは確信に変わっており、同時に「 絶対にここで働きたい 」と思っていました。 最終面接では代表の林さんとオンラインで行ないました。嬉しいことにその場で内定をいただき、思わず「マジっすか」と漏れてしまいました。普段の言葉遣いが終わってるとこういう大事な場面でボロが出るもんですね。内定取り消されなくて本当によかったです。 その後も何度か本社にお招きいただいて社員の皆さんと交流する機会があったのですが皆さん本当に優しくて入社してからシバき倒されるんじゃないかと不安になるほどでした。 もちろんそんなことはなくて本当にみんな優しいんですよ。マジで。(あまあまゆるゆるという意味ではないです) これも明確な採用基準の下、その 人物 を見て採用しているからなのかなと思います。 入社してから 合同研修 ここからは入社してからのことを書こうかなと。 スマートキャンプは 株式会社マネーフォワード のグループ会社なんですが研修はマネフォと合同で行なわれました。 自分は結構な人見知りなので見知らぬ大勢の人間(23卒の同期は70人近くいた)と話すのは正直大変だったのですがなんとかやりきりました。 お陰で話すことへの苦手意識が少し薄れてきたかもしれないと思う今日この頃です。 さて、気になっている方も多いかもしれないので研修の内容についても簡単に書いていきたいと思います。 エンジニアの研修は大きく分けて2つに分けられます。1つ目は新卒全員が参加する全社研修、2つ目が新卒のエンジニアのみが参加するエンジニア研修です。 全社研修はビジネスマナーやロジカルシンキング(MECEとかSWOTとかPDCAとかロジックツリーとか、わからない人は調べてみてね)など、社会人として、マネーフォワードグループの一員として大切なことを学びました。そんな全体研修でいいな〜と思ったことが1つありまして、懇親会が定期的に開催されるんですよね。「えぇ〜〜ただでさえ疲れてるのに夜も拘束されるの...」と思った方、いると思います。そうそこの貴方です。自分も正直最初は同じ気持ちでした。だって人見知りだし、夜はゲームしたいじゃないですか。でもね、一度参加したら考えを改めざるを得ませんでした。 実は懇親会に参加するのは同期だけではなく、スマートキャンプ、マネフォの経営陣や各部署のリーダーなどもたくさん参加します。このような方々と歓談する機会は後にも先にも中々ありません。そういった方々の経験談や価値観、考え方を聞くことができるのは本当に貴重な機会だと思います。もちろんまだ話したことのない同期とも お酒の力を借りて 話すことができて、とても楽しく、有意義な時間を過ごすことができました。このような体験を通して、自分のなかでの懇親会の捉え方が変わっていきました。正直、自分から話しかけることに対しての苦手意識はまだ払拭しきれていませんが...頑張っていきたいです。 エンジニア研修では2~3人のグループでPBL(課題解決型学習)を行ないました。内容としては以下の通りです。 - 仕様書のみが与えられ、その仕様書を元に1つのアプリケーションを開発する - 技術選定はメンバーのトレーナー達が各々の技術スタックを考慮して決定する - 期間は2週間で週1回の進捗報告会以外はグループで自由に開発を行なっていく オンラインでも「ちょっといいですか」を実現できるよう作業中は常にVCを繋いたり、ふざけるところは適度にふざけたりと心理的安全性を高めることがチーム開発においてはかなり重要になってくることがわかりました。適度にふざけたいい例がありまして、自分達のチームでは猫好きが2人、カワウソ好きが1人だったのでチーム名をKawausoにしました。弊チームではマイノリティを尊重します。 チームで1つの共通認識みたいなキーワードがあってもいいと思います。まあ当然自分達のチームは「Kawauso」なんですが。例えばアプリケーションのアイコンをカワウソにしたり、モックデータをカワウソ関連にしたりとそのキーワードを見るときにクスッと笑えるようにするとこれもチームの心理的安全性向上につながるのかな〜と思います。 最終日には制作物の発表会があり、そこで各チームが開発したアプリケーションの解説、デモを見ることができます。さすがマネーフォワードと言ったところでしょうか、皆つよつよなんですね。自分では絶対に思い付かないような実装をしているチームもあって、ただただすごいな〜と感服するばかりでした。 2週間という期間は想像以上にあっという間でめちゃめちゃ楽しかったですし、同年代の人たちとチームで開発することはあまりなかったのでとても新鮮でした。 またやりたい。 チームへジョイン 研修が終わった後は BOXIL SaaS というスマートキャンプのメインプロダクトのチームにジョインしました。 1スプリント2週間のスクラム開発を導入しており、スプリント末に行なわれるスプリントレビュー(やったこと報告会みたいなやつ)では他部署の方々も参加し、ゆる〜くチャットが賑わいます。YouTubeとかニコニコみたいにコメントが流れていくのが自分は好きです。 また新卒メンバーのオンボーディングも充実していて、エンジニアとしてのメンター(トレーナーと呼ばれています)と社会人としてのメンターがいまして、1on1などを通じて手厚いサポートをしてくれます。本当にありがたい... 勤務体系について、自分は入社する前「毎日出社するぞ!!」と意気込んでいたのですが朝の満員電車に耐えられず入社三か月にしてフルリモート状態になっています(都会怖い)。 ですがコミュニケーション量はMTG、ペアプロ、モブプロなどを通じ、出社していたころとほぼ変わっていません。実際に秋田からフルリモートで勤務している方もいらっしゃいますし地方の方でも働きやすいと思います。 記事 も書かれているので見てみてください. ちなみに自分が担当したタスクがプロダクトの価値向上(高評価や、売上向上など)に貢献できたときの嬉しさはマジたまらんです。これからも頑張ります。 これからのおはなし これからについて語ります。 20歳ということもあり自分はまだまだ未熟です。青いバナナくらい未熟。 タスクをサポートなしでこなせるようになることは当たり前ですしこの手の記事では書きがちのことなので省きます。それ以外について、ここではお話ししたいと思います。 スマートキャンプでは社内の非効率を無くすようなツールが有志によって日々開発されています。例えば日報のフォーマットを自動作成してくれるツールとか自分が今月どれくらいフレックスできるかを確認できるツールとか...いっぱいあります。 これの何がすごいって社内のプロジェクトとして開発されたわけではなく、社員が自ら非効率を見つけ自主的に開発しているところなんですよ。 もともとスマートキャンプに興味を持った理由がMISSIONである 「 テクノロジーで社会の非効率を無くす 」 なので、それを実際に体現していることに深く感銘を受けました。僕も作りたい。 と、いうことでこれからは社内の非効率を無くすようなツールを作ることにチャレンジしてみたいと思います。 かくしてWebで飯を食っていくという夢が叶ったわけですが、これで満足できるわけもありません。周りよりも経験が少ないと卑下するのではなく、自分には周りよりも時間があるんだと、そう捉えてポジティブに生きていきたいと思います。 さいごに 長くなってしまいすみませんmm 言葉を書くのに不慣れで怪文書みたいになってしまいましたがここまで読んでくださって本当にありがとうございます。 この記事が少しでも皆さんの参考になれば幸いです。
アバター
※タイトルとアイキャッチはAIに考えてもらいました。 はじめに こんにちは。VPoEの米元です。 スマートキャンプでは2023年3月に「リモートHQ」というサービスを導入しました。 リモートHQは、在宅勤務の環境を始めとしたリモートワーク支援のためのサービスです。 hq-hq.co.jp 本稿では、スマートキャンプの働き方とその課題、リモートHQの紹介、導入後の効果、メンバーの声について紹介したいと思います。 対象読者 リモートワークでの在宅環境に課題がある方 社員のリモートワーク環境を整えたいエンジニアのマネジメント職または人事の方 導入の背景 スマートキャンプ開発組織の働き方 当社では創業間もない時期から週1回のリモートワークデーを採用していましたが、コロナ禍をきっかけに全社的にリモートワーク中心の働き方に移行しました。 その後、今年に入りコロナ禍の状況が変わってきたことで週1日以上の出社を基本としていますが、生産性を最大化するために最適な出社頻度を組織や職種ごとに選択する形をとっています。 開発組織においては月1回以上の出社頻度を目安にしていますが、人によっては出社が中心のメンバーもいますし、関東近郊以外にお住まいのフルリモートのメンバーや週2回程度の出社を自主的に行っている 京都開発拠点 のメンバーなど、人や部門によって多様な働き方をしています。 全体でフルリモートに統一しない理由としては、対面でのコミュニケーションも大事にしたいという思いがあるからですが、その辺りの背景は本記事の趣旨からは外れますので省略します。気になる方はこちらの 京都開発拠点立ち上げ時のnote をご覧ください! 在宅環境における課題 前述したような多様な働き方をしている中で、全員に対して生産性の高い作業環境を提供することは簡単なことではありません。以前のように全員が出社する前提であればオフィスのデスクやモニタを始めとする備品を整備すれば済む話ですが、在宅での作業環境はメンバー個人の住環境や家族構成によってさまざまな制約があり、それに伴って必要な機材や金額も異なります。 また、リモートワークが長期間続くと運動不足になってしまったり、自宅の椅子が合わず腰を痛めたりといった健康面にも懸念がありましたが、これらに対しても一律のリモートワーク手当の支給のような対応ではカバーしきれません。 一方で個別の状況に合わせて金額や椅子・モニタなどの支給物をカスタマイズする方法も考えられますが、手間や資産管理の観点から現実的ではありません。 特にスマートキャンプでは人によっては自分で一定のレベルまで在宅環境を整えているメンバーもおり、公平性の観点からも既存の制度でのカバーが難しいと考えていました。 このような状況の中、グループ会社の方から紹介していただいたのが「リモートHQ」でした。 リモートHQとは 「リモートHQ」は在宅の作業環境を整えるためのサービスです。 リモートHQを導入すると、あらかじめ設定したポイントに応じて 社員自身が リモートHQのサイトから椅子・モニタ・キーボードといったさまざまな商品を選んでレンタルできます。 また、事前に自宅の作業環境の不満点や写真を送り、それを元に 専門のコンシェルジュと相談しながら 自分にとって最適な作業環境を作ることができます。 ラインナップを一部紹介するとFlexiSpotの昇降デスク・エルゴヒューマンのチェア・HHKBなどのデスク周りの商品や、ルームランナー・スマート睡眠パッド・空気清浄機などの健康増進関連など非常に幅広いラインナップから商品を選ぶことができます。 リモートHQの資料から抜粋 さらに、既存のラインナップに無い商品についてもリクエストを出すことができ、リモートHQが承認したものは新たにラインナップに追加されます。 商品のレンタルや返却に関するやりとりなど手間のかかる部分はリモートHQ側で対応してもらえますし、リモート手当てのような制度とは違って 所得税や社会保険料がかからない ため導入企業側で増える工数はほとんどありません。 弊社ではまだ利用していませんが、電気やネットの代金を非課税で法人負担にできるオプションもあるなど、在宅勤務やその補助で課題になりやすい点が非常にうまくカバーされたサービスだと感じています。 導入の効果 それでは実際に導入してどうだったのか、まずはサービス導入後のアンケートの結果から定量的なデータを見ていきましょう。 満足度 サービスの利用満足度は100%でした。(とても満足している: 60%、どちらかと言えば満足している: 40%) レンタルした商品自体の満足度だけでなくコンシェルジュ相談を始めとしたユーザーサポートや、欲しい商品が無かった場合でもリクエストが可能な点などサービス全体の総合的な評価もこの満足度に繋がっているようです。 生産性向上の実感 90%近くのメンバーが生産性の向上を実感しているとのことでした。 すでに自分で在宅環境を整えていたメンバーも一定数いたため、組織全体としてどれくらい効果があるかは未知数だったため、この結果は(良い意味で)少し意外でした。 また、生産性向上の実感値としては平均で19%増となっていました。 これはあくまで実感値ではあるので、仮に5〜10%程度の生産性が向上したと保守的に見積もっても十分な経済効果が出ていると判断しています。 健康面への影響実感 健康面に関しては30%程度のメンバーが変化を実感したようです。 満足度や生産性向上への影響と比較すると低い値になりましたが、アンケートの実施がサービスの利用開始直後だったため健康面の効果が出るまでの期間が短かったことや、椅子やその他の健康面に関わる商品をレンタルしたメンバーが多くなかったことも原因だと思われます。 メンバーの声 それでは実際に利用したメンバーの声も聞いてみましょう。今回は4人のメンバーにインタビューをしてみました。 ※括弧内はインタビューイのニックネームです。 1人目(ピーターさん) — サービス導入前の課題や気になっていたことがあれば教えてください もともとそこまで課題感は無かったのですが、デスク環境が暗かったのでその点を改善できたらいいなというくらいでした。 — サービスを利用してみてどうでしたか もともとの課題だった暗さに対してはコンシェルジュの方に大きめのデスクライトをおすすめしてもらいました。 また、自分では当初考えていなかった点もいくつか提案してもらいました。 例えば「モニターアームを使ってみたらどうか」という提案や、当時はMacをデスクにそのまま置いてキーボードとしても利用していたため「Macもモニターにした方が目が疲れにくいですよ」と提案もしてもらいました。 その結果、モニターはモニターアームを利用し、Macはスタンドを使ってモニターと高さを揃えました。さらにこの形にするにあたってキーボードが必要になったので、試しにMistelの分割キーボードにしてみました。また、トラックパッドも合わせてレンタルしました。 また、余ったポイントでトレーニングベンチを借りたのですが、思っていたより軽いものが届いてしまいこれに関しては失敗でした(笑) 総合的にはデスク周りは全体的に改善されましたし、提案していただいたデスクライトも自分では探せなかったと思うので非常に満足しています。 失敗したベンチも返却できますし、レンタルだからこそ気軽にトライできたので学びになりました。 2人目(職人さん) — サービス導入前の課題や気になっていたことがあれば教えてください もともと在宅の環境は必要なものが揃っていて何を借りようか迷う部分がありました。 デスクはFlexiSpotの昇降デスクを持っていて、椅子もあったのであまり借りるものは無いかもしれないなと思っていました。 — サービスを利用してみてどうでしたか デスク周りはキーボードをREALFORCE for Macに変えただけなのですが、他にエアロバイクと「 KENSUI -kaku- 」という懸垂マシンをレンタルしました。 自分は身体を動かすことが好きなのですが、エアロバイクがあれば外が雨でも有酸素運動ができますし、懸垂マシンはコンパクトな作りになっているのでデスクのすぐ横に立てていて仕事の合間にいつでも懸垂ができるようになりました。 おかげで日常生活を豊かに過ごせるようになったと感じています。 特に懸垂マシンに関しては以前から気になっていた商品だったのですが、値段が4万円くらいするので自分で買うには少しハードルが高いです。 もともとのリモートHQのラインナップに無かったのでダメもとでリクエスト申請してみたら承認されて嬉しかったです(笑) 実際に届いてみたらサイズも用途も自分にドンピシャでハマって、非常に満足しています。 自分はリモートHQは健康面に重点を置いて使っていきたいと思っています。 省スペースな懸垂マシン(正面) 省スペースな懸垂マシン(横) 3人目(クマノミさん) — サービス導入前の課題や気になっていたことがあれば教えてください 私の場合は机や椅子などはすでに揃っていたためそこまで課題感はありませんでした。強いて言えばサブで使っていたモニタをもっと大きいものにしたい気持ちはありました。 — サービスを利用してみてどうでしたか 一点集中でポイントを利用し、自分では絶対に買わないであろう39.7インチ5K2Kの曲面型ウルトラワイドモニタを借りました。 私はデータアナリストなので普段からデータ抽出業務をするも多いのですが、画面の幅が広いのでその業務が圧倒的に楽になりました。生産性が上がったと思います。 解像度も以前利用していたものよりも圧倒的に良いので、目が疲れにくくなりました。 — 今後レンタルしてみたいものはありますか もしモニタが気に入らなかったら返却しようと思っていましたが、結構気に入ってしまいました。なので当面は残りのポイントで小物のレンタルをしようと思います。 特に電源タップなどは消耗品だと思っているものの、実際に買い換えようと思うとまだイケる!と思って買わなかったりするので電源タップとか借りてみたいです。 また、1年に1回くらいは使いたいときがあるシュレッダーやプリンター等も低ポイントで借りられるのですが、そういった一時的にしか使わないものや置き場所に困りそうなものを借りると思います。 あとは気軽に試して気に入らなかったら返却できるのは大きいですね。自分で購入したものは多少気に入らない点があったとしても我慢してそのまま使いがちですが、リモートHQだと躊躇なく返却して他の商品に変えられるので特に値段が高いものを試すハードルが下がりました。 4人目(ブラーバさん) — サービス導入前の課題や気になっていたことがあれば教えてください 気になっていたことは2つあって、1つはデスクです。 手軽に高さが調節できなかったため腰が痛くなりがちでした。また、天板が小さくご飯を食べながら作業がしづらかった点にも不満を感じていました。 2つ目はモニターです。サイズが小さかったことと、スピーカーが無かったためイヤホンが必須だったことに不便さを感じていました。 いずれも学生時代に買った安いものをそのまま利用していました。そのため自宅ではあまり作業する気にならず、満員電車に揺られながらも出社することが多かったです。 出社した際に少し残業してから帰宅するとだいたい21時過ぎになっていて、勉強に使う時間があまりとれない日々が続いていましたね。 — サービスを利用してみてどうでしたか デスクはFlexiSpotの昇降デスクにしました。 高さが自分で手軽に設定できるので腰の痛みが少なくなりましたし、天板が大きくなったのでご飯食べながら作業したり、よく使うものもテーブルの上に置けるようになりました。 また、モニターアームを導入したことでさらにデスク上のスペースが確保できました。 モニターの方は31インチのものにしました。モニターから音が出るようになったので自分が喋るときだけイヤホンするようにしています。 これらのおかげで非常に快適になったため、リモートにする日がかなり増えました。(400%増) それによって満員電車に乗ることが少なくなったことや勉強するための時間が増えたのも良かったです。 今後はキーボードを試してみたいと思っています。 私は今まで自宅の作業環境の整備にそこまで興味が強いわけではなかったのですが、会社が提供してくれるサービスで改善できたことが非常に良かったです。 自分と同じように新卒で今まで自宅の環境を整えていなかった人からすると、家賃補助くらいありがたい福利厚生だと思います。エンジニア以外にも使ってもらいたいです。 マネジメントの視点から見たリモートHQ 最後に導入者側であるマネジメント(私)の視点も書いてみようと思います。 高いコストパフォーマンス 導入者として一番気になる費用対効果ですが、前述の「導入効果」で記載した生産性向上の効果に加えて、中長期的には健康面での効果もあるのではないかと考えています。 業務に関する効果だけでなく「生活の質(QOL)が上がった」と話してくれるメンバーもいるなど利用者の満足度も非常に高く、定量的にも定性的にも当初の想定以上の効果が出ていると感じています。 また、利用された分だけ費用が発生する仕組みなので、もし社員にほとんど利用されなかったとしてもサービスの利用料が無駄になることはありません。 運用に関しても負担はかなり少なく、手当や物品の支給と違って社内の手続きや資産管理が発生しないためバックオフィス側のメンバーの手をわずらわせることもありません。 サービスの利用料に対しての効果が高いため非常にコストパフォーマンスが高く、そのうえで無駄な手間やコストが発生するリスクが低いので、導入者にとってもありがたいサービスだと言えます。 考えられたユーザー体験 初回の商品を発送する際にメッセージカードを同封してくれたり、商品を発送する際の箱を利用企業のオリジナルデザインに変更できます。 例えば新入社員が入社したその日に、自分が選んだモニタやキーボードなどがメッセージカードと共に送られてくる・・・という体験を想像すると、それだけで会社へのエンゲージメントが高まりそうですね。 単に物を届けるだけでなく受け取る利用者側の体験まで考え抜かれた設計になっており、サービスを作る人間としても見習いたいと思っています。 喜んでもらったことに喜ぶ私 さまざまな変化に対応できる柔軟性 例えば結婚や出産などのライフステージの変化、昇進や異動などによる業務の変化があった場合、作業環境の見直しやカスタマイズが必要になる場合もあると思います。 ただ個人でそれらを行なうことはコストや労力の面で負担が大きく、見直したとしても微調整にとどまることが多いかもしれません。 一方でエンジニアとしては自分の作業環境をチューニングし続け、常に最高の生産性を求めるべきだとも考えています。 リモートHQを利用することによって、これらのハードルが下がり、さまざまな変化に柔軟に対応できるため、社員の皆さんが長期的に高い生産性を発揮し続けてくれることに繋がるのではないかと考えています。 まとめ スマートキャンプにエンジニアとして入社するともれなくリモートHQが利用できます。 この記事を読んだ方が在宅環境を整えて生産性を高めることに少しでも興味を持っていただけたら幸いです。 また先方のメディアにも弊社の記事を掲載していただいていますので、宜しければこちらもご覧ください。 hq-hq.co.jp スマートキャンプではこれからもエンジニアが働きやすい環境作りに積極的に投資していきます。 ※PR記事のような内容になってしまいましたが、これは記事広告ではありません、念のため。
アバター
はじめまして! BALES CLOUD エンジニアのてぃが(光永)と申します。 今回、BALES CLOUDとZoom Phoneの連携をすることになりました。 調査・実装等々行いましたので、この件についてお話ししたいと思います。 前置き Zoom Phoneとは? BALES CLOUDとは? インサイドセールスとは? なにをするのか? BALES CLOUDとZoom Phoneを連携するということ Zoom Phone連携の構成 なにをやったのか? Zoom Phoneの調査 Zoom Phone Smart Embed公式リリースが「今年」 OAuthを使うための作業が多い つくっていく Zoom Phone Smart Embedの開発の流れ 1. なにはともあれ動くものをつくる 2. 既存のUIと似せてみる 3. デザイン・UIを調整してみる 4. 機能の構成決定! 設計へ 5. 設計ができた! 反映する 6. 完成まで駆け抜ける プロトタイピングのなにがよかったのか? まとめ プレスリリース 前置き そもそもなんのことを言っているのか? とお思いでしょうから、まずは前提説明です。 Zoom Phoneとは? 皆さんご存じのZoomが提供しているプロダクトの一つに Zoom Phone があります。 クラウド VoIP 電話サービス Zoom Phone は、あらゆる規模のビジネスに向けた、機能豊富なクラウド電話システムです。 BALES CLOUDとは? 一方、私の所属するチームでは BALES CLOUD というサービスを開発しています。 インサイドセールス時代に最適な営業DXツール インサイドセールスを効率的、かつ高度に行うためのツールです。 インサイドセールスとは? 上述の通り、BALES CLOUDはインサイドセールス向けのツールです。 インサイドセールスについて、 弊社記事 から引用すると以下の通りです。 見込み顧客に対してメールや電話、Web会議ツールなどを活用しながら非対面で営業活動を行なう内勤型の営業体制です。 インサイドセールスは営業活動全体の効率化が目的です。 つまり、インサイドセールスでは「メールや電話、Web会議ツールなどを利用した営業活動」を「効率的」に行なうことが求められます。 こちらを補助するのがBALES CLOUDというわけですね。 また、BALES CLOUDの説明に「DXツール」ともあるとおり、インサイドセールスの行動を「データにする」こともBALES CLOUDの重要な機能です。 なにをするのか? さて、そんなBALES CLOUDとZoom Phoneを連携するとはどういうことでしょうか。 BALES CLOUDとZoom Phoneを連携するということ Zoom Phoneは「クラウド電話システム」であり、インサイドセールスの「電話を活用した営業活動」、いわゆる「架電業務」に活用できます。 ...と、ここまでの情報を踏まえつつ、いきなりですが、BALES CLOUDのZoom Phone連携の要求は以下の通りです。 ワンクリックで通話が開始できる 通話の記録が(ほぼ)自動で取れる 通話内容の録音・再生ができる BALES CLOUDの提供したい「業務の効率化」という価値においては、「ワンクリックでの通話」や「自動での記録」は特に外せない機能です。 Zoom Phone連携の構成 BALES CLOUDでは、前述の要求を満たすためにZoomの以下の機能を利用することにしました。 Zoom Phone Smart Embed 提供されているZoom PhoneのUIをWebアプリに組み込んで使うことができる Zoom Phone API Zoom Phoneの情報にアクセスするためのAPI Zoom OAuth Zoom APIを使うためのOAuth(2.0) 通話機能をZoom Phone Smart Embedで実現し、 録音などの情報取得をZoom Phone API×Zoom OAuthで実現する構成です。 なお、Zoom Phone Smart EmbedのUIはこんなかんじです。 なにをやったのか? いよいよ、エンジニアの具体的な行動の話をしていきましょう。 Zoom Phone連携は以下のような流れで開発していきました。 Zoom Phoneの調査 Zoom Phone Smart Embedのプロトタイピング 各種設計 実装 Zoom Phoneの調査 まずは、Zoom Phone連携のための情報を収集しました。 Zoom公式には下記さまざまなドキュメントやサポートが用意されています。 Zoom Support (英語Supportページ) Zoom サポート (日本語Supportページ) Zoom Developer Platform > Document Zoom Developer Platform > Document > API > Zoom Phone API Zoom Developer Forum Zoom Developer Support これらの文書を読み込み、時には実際に手を動かして検証してみつつ、要件を実現するための情報を集めました。 ある程度はエンジニアならお馴染み「ドキュメントに書いてあるとおりやれ!」のやつなのですが、苦労した点がいくつかありましたので少し紹介してみますと...。 Zoom Phone Smart Embed公式リリースが「今年」 Zoom Phone Smart Embedについて調べていたてぃがさん(私)は、情報がやたらと少ないなあ、ということに気がつきました。 そこで更なる情報を求めてdeveloper communityの投稿などを漁っていたところ、以下の コメント を見つけました。 Thank you for your patience. We have released Zoom Phone smart embed. To know more, please visit: https://support.zoom.us/hc/en-us/articles/11630110788877 その時、このコメントの投稿日時には燦然と輝く『14d』の文字がありました。 「このコメントの投稿は14日前だよ」の意味です。 そうです、Zoom Phone Smart Embedは、公式リリースされたばかりだったのです。 しかしBALES CLOUDチームでは、臆さず、この新しい技術の採用を決断したのでした。 OAuthを使うための作業が多い Zoom OAuthを自社サービスで利用するためには、自社用のOAuthアプリをZoom Marketplaceのアプリとしてリリースする必要があります。 それに伴って、申請等々、の作業が発生しました。 詳しくは、以下の公式ドキュメントを眺めて見てください。 Zoom App Submission なかなかの物量の作業が指示されています。 申請作業を受け持ってくれた弊チームの...調律者?(役割名迷子)井上さん(a.k.a.師匠)には頭が下がる思いです。 (感謝の意を込めて師匠さんの記事を貼っておきます。よければこちらもご覧ください。→ BALES CLOUD TEAMのMTG非同期化への取り組み ) つくっていく 情報をあらかた集めたので、今度は、設計や仕様について考えて作っていくフェーズです。 ここで、「ユーザーの使い勝手に大きく関わるUIについては、見えるものを元に検討したい!」と弊チームPO兼PdMから依頼がありました。 (BALES CLOUDの売りはUIの良さです。(隙自慢)) そこで、UIを担うZoom Phone Smart Embedの実装については、プロトタイピングで行なっていくこととしました。 OAuthやAPIの実装はすこしわきに置いておき、ここではこの件についてお話ししたいと思います。 Zoom Phone Smart Embedの開発の流れ Zoom Phone Smart Embedについては、大きく以下のような流れで開発を進めました。 なにはともあれ動くものをつくる 既存のUIと似せてみる デザイン・UIを調整してみる 機能の構成決定! 設計へ 設計ができた! 反映する 完成まで駆け抜ける プロトタイピングは、1〜3のステップで行いました。 プロトタイピングで行ったことは以下のような感じです。 「検証したい内容を確認するためのもの」を作る 1をPdMと確認する PdMやチームと一緒に次のステップ(検証したい内容)を決める 最初からステップがすべて決まっているのではなく、ステップ1からはじめて、順々に次にやることを意思決定していく形です。 各ステップでの具体的な作業内容についても、簡単にご説明します。 1. なにはともあれ動くものをつくる Zoom Phone Smart EmbedがBALES CLOUD上で表示され、通話ができるところまでを作ってみることにしました。 とりあえずBALES CLOUDにZoom Phone Smart Embedを埋め込んで、動く様子を観察してみます。 また、Zoom Phone Smart Embedでは発着信・通話開始・終話などなど、リアルタイムに色々なイベントが取れるようなので、それらもとりあえず捕捉して様子を見てみます。 BALES CLOUDのフロントエンドはVue.js/JavaScript/TypeScriptですので、ここの実装は完全にZoom Phone Smart Embedの「ドキュメントの通り」です。 2. 既存のUIと似せてみる BALES CLOUDには、すでに連携しているCTIサービスがあります。 今度はそのUIに挙動を似せてみることにしました。 Vueでcomponentを作りこみ、既存機能に埋め込んでいきます。 3. デザイン・UIを調整してみる 動かしているといくつか気になる箇所を発見。 ちょちょいと調整します。 4. 機能の構成決定! 設計へ これまでに収集した情報と、プロトタイプを参照しつつ、チームで話し合ってZoomで利用する機能などの構成を決定しました。 構成については前述の通りです。 構成も決まったので、PdMが具体的な設計を開始します。 とはいえ、プロトタイプによってある程度は機能のイメージができています。 なので、ゼロからの設計ではなく、懸念点などを洗い出すためにさらに詳細な要件を具体的に書き出してもらう形です。 5. 設計ができた! 反映する Zoom Phone Smart Embedを利用した機能の最初のステップについて、PdMの設計がおわりました。 出来上がった要件に合わせて、足りない機能の実装や違っている挙動の実装変更を行なっていきます。 6. 完成まで駆け抜ける あとは設計が進むたびに「ものをつくる」と「確認」を何度か繰り返し...できあがりです! プロトタイピングのなにがよかったのか? ここからはプロトタイピングをやってみた感想です。 これは、「めっちゃアジャイルしている!」の一言に尽きると思います。 ...と言ってしまうと流石に不親切なので(笑)もう少し細かくしゃべってみます。 まず、動くものをとにかく作って提供できたという点がよかったです。 これによってどんどんものができてくるスピード感と、ワクワク感が得られました。 目に見える形ですぐに価値提供ができることで、「Zoom Phone連携、すぐできちゃうんじゃない!?」というポジティブなモチベーションが持続しました。 小さく作業を切って順番に進めていくことで、作業を進めるたびに着実に完成系に近づいていく、というところもよかったです。 また、常に目に見えるものがあることで開発者・PdMともに確認がしやすく、意思決定がスムーズに素早く行えて快適でした。 合わせて、認識違いによる手戻りが発生しづらいという利点もあります。 特に「3. デザイン・UIを調整してみる」のステップでは、「現状このように不都合があります」という確認、「このように修正できます」という提案をブラウザ上で実際の画面を操作しながら行えて意思疎通がスムーズでした。 つまり、「アジャイルしている!」...言い換えると、「スピード感のある価値提供ができている!」ということになるでしょうか。 以下のような、アジャイルを考えるときに3万回見る図の体現でもあったと思います。 例の図っぽいやつ(※illustration byてぃが) まとめ Zoom Phone連携の話はここまでです。 新しい技術を積極的に採用できた アジャイルでものづくりを進められた ことにより、 BALES CLOUDはスピード感を持って開発をしている! という事実をあらためて実感できた案件でした。 また、もしBALES CLOUD、またはZoom Phoneの連携機能に興味を持って頂けましたら、 こちらの公式ページ より資料請求・お問い合わせいただけます! それでは〜〜〜! プレスリリース 本記事で紹介されているBALES CLOUDとZoom Phoneとの連携はこちらでも紹介されています! prtimes.jp
アバター
はじめに BOXIL SaaSのChatGPTプラグインとは システム概要 開発にあたっての主な意思決定項目 開発の進め方 開発者申請 法務周りの対応 インフラ構成 カテゴリ検索APIの開発 ChatGPTプラグインのここがすごい3選 プラグインの使用を促してくれる 用意したAPI同士の連携ができる 申請から承認まで最短1日!? さいごに はじめに こんにちは。スマートキャンプでエンジニアをしている佐々木(社内ではピーターと呼ばれています)です。 2023年6月20日のプレスリリースの通り、スマートキャンプの新たな取り組みとして2023年6月15日にChatGPTプラグインの提供を開始しました。ChatGPTプラグインを提供するのは、SaaSおよびITサービス比較サイトとしては国内初です。 prtimes.jp 私を含めエンジニアの正社員2名、インターン生1名、そしてプロダクトオーナー(PO)1名の合計4名で開発を進めました。 短期間と言える約3週間で、開発開始からリリースまでを無事に達成できました。 具体的な役割としては、私とインターン生がAPIの開発やChatGPTのレスポンスのチューニングを担当しました。一方、もう一名のエンジニアは申請関連の調査やインフラの整備、さらに、POは法務関連の調整やQAとして予想される質問のリストを作成する役割をお願いしました。 この記事では、開発の舞台裏や得られた知見などを紹介します。 BOXIL SaaSのChatGPTプラグインとは 今回開発したプラグインのイメージは次の画像のようになっています。 BOXIL SaaSのChatGPTプラグインでは、ChatGPTに対して業務を効率化したい点について相談すると、その業務の効率化に役立つサービスの一覧が見られるページを提供します。また資料ダウンロードのリンクからBOXIL SaaSに移動することで、サービスごとの詳細な資料を取得できます。 実は、カテゴリの一覧ページを検索することは、 BOXIL SaaS からもできます。しかし、SaaS製品のカテゴリは年々複雑性を増している中、自分が求める製品のカテゴリがどれなのか分からないといった声が寄せられています。ChatGPTを使うことで、例のように「タイムカードを切るのが面倒だ」という潜在的な悩みがどのカテゴリに属するのか判断する手助けが行えます。 システム概要 実はChatGPTプラグインとして申請に必要なのはドメインだけです。 ただし、そのドメインから次の情報にアクセスできる必要があります。 プラグインの説明が書かれたマニフェスト(ドメイン配下の決められたパスに配置) OpenAPI形式で記述されたAPI定義書(マニフェストにURLを記載) ChatGPTから叩きたいAPI(API定義書にエンドポイントを記載) これにより、ChatGPTはマニフェストやAPI定義書にアクセスし、何をするプラグインなのか、どういう仕様のAPIが置いてあり、どこにAPIを叩きに行けばいいのかを把握できます。 そしてChatGPTがユーザーとの会話の中でAPIを叩くべきだと判断したときに、APIが叩かれるという仕組みです。 詳細は公式ドキュメントに書かれています。気になる方はご参照ください。 platform.openai.com また、OpenAI公式からはクイックスタートとしてToDoリストのChatGPTプラグインのリポジトリが公開されています。 github.com 今回はChatGPTに叩いてもらうAPIとして次のようなものを作成しました。 リクエスト { " カテゴリ名 ": " 会計 " } レスポンス { " カテゴリ一覧ページ ": { " リンク ": " <https://boxil.jp/sc-cloud_accounting> ", " カテゴリ名 ": " 会計ソフト(財務会計) ", " 概要 ": " 会計ソフトでは、会計業務に精通した方から知識が乏しく不安だという方まで、会計(買掛金台帳・売掛金台帳・賃金台帳・試算表・決算資料など)に関わる業務の効率的を実現し、生産性を飛躍的に向上させます。 ", " 資料ダウンロードページへのリンク ": " <https://boxil.jp/downloads/confirm/?type=category&ids%5B%5D=114> " } , " 検索条件 ": { " カテゴリ名 ": " 会計 " } } これにより、ChatGPTはカテゴリ名を入れたリクエストをAPIに投げることで、カテゴリ一覧ページへのリンクや資料ダウンロードページへのリンクをプラグインの利用者に提供できるようになります。 開発にあたっての主な意思決定項目 開発の進め方 ChatGPTプラグインの開発事例は国内でも数件あるのですが、自分たちにのケースに応用できるか不明で、そもそもやりたいことができそうなのか検証するという意味合いが強かったため、エンジニア主導で開発を進めました。 そのため、チームでの意思決定を高速に行うために、デザインドキュメントは必ず書き必要な人にレビューを依頼することで法務との調整やインフラのすり合わせ、申請周りの整備、APIの仕様決めなどをテンポ良く行ないました。 3週間で書いたドキュメントは約50本くらいになります。 先日デザインドキュメントについての記事も執筆したので良かったらご覧ください。 tech.smartcamp.co.jp 開発者申請 開発を進めるにあたって、最初にChatGPTプラグインの開発者申請をする必要があります。 開発者申請が通ったChatGPT Plusのアカウントがなければ、ローカルのAPIや申請前のAPIを登録してChatGPTから叩けるか動作検証することはできないからです。(2023年6月現在) 私たちも、本プロジェクトが始まる際に急いで申請をしましたが、承認されるまでに16日かかりました。(5/30にChatGPT Plugin waitlistに登録、6/16にChatGPT Plugins Developer Accessが承認) そのため、承認が下りるまでの期間は私の個人的なアカウントで動作確認を行なっていました。 もし開発に乗り出したい方は早めに申請するといいでしょう。 法務周りの対応 法務としては、主にBOXIL SaaSで利用されている情報をChatGPTに提供した際に、個人情報の第三者提供等にあたるかが一番の懸念となりました。 そのため、BOXIL SaaSから口コミ情報などを提供する場合には個人情報をマスクすることにし、現段階では個人情報は提供しないことにしました。 また、マニフェストの legal_info_url にはBOXIL SaaSの利用規約を入れることにしました。 インフラ構成 今回は1日でも早く世に出したかったので、BOXIL SaaSで元々運用しているAPIサーバーにChatGPT用のエンドポイントを実装することにしました。ChatGPTプラグイン機能はOpenAPI形式で記述したエンドポイント以外にはアクセスしないため、このような設計でも問題ないと判断しました。 今後アクセス数が伸びればBOXIL SaaS本体とは切り離すことになるかもしれません。 カテゴリ検索APIの開発 リリースできる最低限の機能として BOXIL SaaS で使われている検索機能をベースに、カテゴリ一覧ページへのリンクが取得できるAPIを作成しました。 今後、一覧ページへのリンクではなく具体的なサービスをChatGPTから提供できるようにする可能性もありますが、今回は見送りました。 私たちは今回のプロジェクトのために結成された急拵えのチームで、普段はBOXIL SaaSの開発とはあまり関わりがありません。 そのため、BOXIL SaaSの開発チームには、実装方針のアドバイスをいただいたり、コードレビューを手伝っていただいたりしました。 その時に、やはり便利だったのはデザインドキュメントです。 これにより、コードレビューを迅速に進め、滞りなく開発を進めることができました。 ChatGPTプラグインのここがすごい3選 ここでは実際に調査・実装する中で分かったことについて紹介します。 プラグインの使用を促してくれる プラグインを有効にしたら、必ずしもそのAPIをいきなり叩けるような質問を投げかける必要性はありません。 下記画像のように、純粋に困っていることを相談するように話しかけても自然な流れでAPIの利用を促してくれます。 これは私たちがこのChatGPTに求めていた「ユーザーの潜在的な悩みを吸い上げ、具体的なSaaSサービスに紐づける」ことにおいて大きなメリットだなと感じました。 用意したAPI同士の連携ができる これは実際に実験して分かったのですが、特定のAPIエンドポイントから取得した情報を、別のAPIエンドポイントへの入力として使うことができました。 これにより何が嬉しいのかといいますと、検索のマッチ度の判断をChatGPTに移譲できます。 下記の例では、最初に「タイムカード」というキーワードでAPIが叩かれましたが、該当するカテゴリが見つからなかったために、カテゴリ一覧をChatGPTが取得し、その中から関連の高い「勤怠管理システム」というキーワードで再度APIを叩き直しています。 このようにユーザーの知りたい内容と合致したカテゴリがどれかという判断をAPI内で行わずにChatGPTに移譲できます。 今回、カテゴリ検索のAPIとしてBOXIL SaaS内部の検索ロジックを流用したのですが、カテゴリ検索の部分はデータベースから部分一致するカテゴリを検索するようなロジックになっており、検索ワードが長いと検索に引っかかりませんでした。 そのため、カテゴリ一覧を別のAPIエンドポイントとして用意し、そちらから検索ワードを取得してもらった上で、ChatGPTにどの検索ワードで調べるかの判断を任せるような設計が有効でした。 申請から承認まで最短1日!? たまたま運が良かったからかもしれませんが、申請した次の日には承認されました。 参考になるかは分かりませんが、公式ドキュメントには明記されてないものの申請するにあたって注意したのは次です。 マニフェストは英語で書く マニフェストの description_for_human には Japan という単語を入れる(当該プラグインは国内利用を想定しているため) 申請項目のプロンプト例は英語で書き、やりとりを繰り返さなくても一度目の返答でAPIが叩かれる さいごに スマートキャンプでは「Small Company, Big Business.」というVisionを掲げています。 新卒でもこのような新しい技術を使ったサービス開発を機会をもらえたり、少人数チームが故の機動力の高さでプロジェクトを進めていけるのが魅力だと思います。 今後も新たな技術を活用し、よりユーザーにとってぴったりなサービスを快適に探せるよう取り組んでいきます。 最後まで読んでいただきありがとうございました!
アバター
はじめに ドキュメントを残さないといけないことはなんとなくわかる。 なのでNotionなりkibelaなり社内で使うツールにちょこちょこドキュメントを残していたりもする。 だけどさ、残したドキュメント見られてます?使われてます? 本当に大事なことは自分が理解できるドキュメントではなく、読者が理解できるドキュメントを残すことなんじゃないか・・・!? ちなみにタイトルはbingが考えてくれました。 執筆のきっかけ 業務でAmazonPersonalizeを利用した機械学習、推論の作業を引き継ぐこととなった。 事前知識なんてない。全くない。 開発環境の閲覧権限のあるアカウントすら持ってなかった。 そんな中でデータPMの方が引き継ぎドキュメントを用意してくれた。 それをみてポチポチしていけば作業を完遂できた。しかも迷うことなく。 今まで自分が触れてきたドキュメントだと迷ったり、調べないといけなかったり・・・。 挙げ句の果てには違っているので自分で検証して実装変更しないといけなかったり。 聞ける相手がいるならまだいいが、退職などで聞くことができなくなることもある。 もしも自分が引き継ぎ用にドキュメントを残したとして、こんなに綺麗に引き継げるものなんだろうか?僕はこれまで何も考えず、クソみたいなドキュメントを量産するマシンだった。 自分の課題の言語化と同時に、記事にしちゃおうと思った次第です。 引き継ぎドキュメント10ヵ条 個人的にどこを意識すると良さそうかを10項目考えました。 本当はもっとポイントはありそうだし、気をつけなければならない優先順位などもあるのかもしれない。キリがないので10項目に絞らせて欲しい。 ここからは実際にドキュメントを残す感じで書いていきます! 1. 対象読者の明確化 前提条件を明記し対象の読者かどうか早期に判断できるようにする。 これは何のドキュメントで何をして欲しいのかを短くまとめる。 (例)前提事項 ・ このドキュメントはAmazonPersonalizeを用いてレコメンド用のCSVを作成するための手順書です ・ レコメンドロジックの利用方法   ・ 説明リンク ・ AmazonPersonalizeの概要説明   ・ 説明リンク ・ レコメンドの種類   ・ 説明リンク 非対象者の読者が見た場合に読まなくて良い判断を早くさせることが可能。 意味を感じ取れない作業は苦痛にしかならないため、何のために何に利用されるのかを明確化する。 2. とにかく簡潔に 難しい表現、伝わりづらいものはキャプチャを入れつつ、必要な情報だけをまとめる。 その際に複数項目を並べる際には表組みも利用する。 下記は各レコメンドロジックのインプットとアウトプットデータのディレクトリ説明の表です。            学習用データ 推論入力用データ 推論出力結果データ サービスレコメンド用 fulldata/training/ fulldata/predict_input/ fulldata/predict_output/ カテゴリレコメンド用 category_recommend/training/ category_recommend/predict_input/ category_recommend/predict_output/ 表組にすることでMECEに表記可能 視覚的に理解しやすくする 3. 表現方法を意識 用語の説明を書いていく際には下記を意識し、検索性の向上させる。 本質をついた名前をつける 社内の用語に合わせる 表記の揺れをなくす (例) ユーザー/ユーザ 売り上げ/売上 4. 肯定し断言する xxxした方が良い という表現は必須項目であれば xxxする という表現にする。 条件付きで実行した方が良いものに関しては yyyの場合はxxxする と実行条件を明記する。 yyyしない という否定表現ではなく xxxする という肯定表現を使う。 yyyに注意する などは yyyを実行しxxxする のようにすべき行動で記載する。 (例) 誤: 条件A**でない**場合には、項目Xを選択する。 正: 条件B、Cの場合、項目Xを選択する。 肯定系で漏れなく書くことで具体的に作業をイメージ可能。 多少文章が長くなる可能性もあるが、迷うことがなくなる。 5. 手順を具体化する 入力不要、選択不要も含めて、通るであろう手順に関わることはすべてもれなく具体的に記載する。 (ポイント) ・ 別ページ参照などと本文中に書かない ・ 後から質問されないであろう粒度まで具体的にする ・ 後任者が行うであろう動作を考えたうえで説明する ・ 何かを入力していく項目画面であれば入力値を明記する ・ 注意点があれば本文中には残さずコメントに残す ・ 円滑に実行させることだけにフォーカスする ドキュメントの読者がどのような動きをしているかを想定したうえで記載していくとスムーズに作業を完遂できる。 6. 節目で確認可能にする 途中で確認できるのであれば、確認タイミングを挟む。 その場合は 実行ボタンを押下したか?(yes/no) のような聞き方ではなく、押下後に起きているであろう事象を明記しておくことで確認可能になる。 (例)実際のドキュメントより抜粋 ・ category-to-categoryレコメンドのレシピを作成(モデル学習)する。 ・ Solutions and recipesの設定画面を開く。 ・ 作成したjobが「create in progress」になっている事を確認する。 7. 道の一本化 タスク1~3の処理を実行した後にタスク4を実行しなければならない場合、下記の図のような処理を想像していた。 ただドキュメントに落とし込む場合にはもっとシンプルで良い。 とにかく道の一本化させる。 途中で道が分かれるのであれば別ドキュメントに分けるなど工夫する。 8. 資料は最新の状態で書くこと 過去にまとめた情報からピックアップして作らずに最新の状態を元に書く。 現状の仕様に合わせた書き方でなければ読者が勘違いする可能性は多々ある。 9. 資料は後任者と確認すること 引き継ぎ後に質問が来て場合作業が止まった経験がある。自分にとっても相手にとっても時間のロスにつながる。 相手がどの程度理解したかを 実際に試しながら 確認を実施することで質問が来ない状況を作る。 試運転に勝るものはない。通して実施した際にドキュメントの不備も見つかる。 試運転で問題がなければ安心して業務を任せることが可能。 後の時間のロスをなくすことが可能。 疑問点があればドキュメントをアップデートを実施。 10. ドキュメントのオーナーを移譲すること 前任者のさらに前の前前任者からの秘伝のドキュメントをもらった経験が何度かある。 間違いを修正して良いのか悩んでしまいドキュメントを更新しないことも多々あった。 ドキュメントのオーナーを移譲しメンテナンスを後任者にまかせることで最新性を保つ。 まとめ 引き継ぎドキュメントは、後任者への円滑な業務引継ぎに欠かせません。 あらかじめ引継ぎすることを考え資料を作成することで、担当業務をふりかえる機会にもなり課題を発見し、業務改善にもつなげることができます。 自分の業務を移譲することで、新しい業務、次のステップへと円滑に進めることができ視野も広がります。 使われるドキュメントを書いていきましょう! 雑感 究極とか言っておきながら書いてみたら普通のことしか書いてない。 ただしっかりと考えたうえで日常的に実施していかないと 普通 はできないのかもしれない。 ドキュメントを書くことは大事。だが一番大事なのは 読者への気配り だと悟りました。
アバター
はじめに プロダクトの概要と開発背景 BOXIL EVENT CLOUD について イベクラの開発背景 最初の課題 Design Documentとは GoogleのDesign Doc イベクラチームにおけるDesign Document フォーマット Design Documentがチームにもたらすもの 手戻りが発生しない 問題認識の差が埋まる 考えが整理される 応用が効く Design Documentのポイント7選 問題背景を書くことに99%の労力を割く(つもりで書く) 読者が知っているであろうことから始める 過去のPRを読みに行く PRから辿れるようにする 議論の足跡を残しておく 実装後はDesign Documentを更新しない タスクと紐付ける 導入による心理的な変化 チームへの発信がしやすい タスクを進めるときに迷いがない 自信を持ってPRが出せる 今後の課題 最後に はじめに こんにちは! 2022年度の新卒として入社して早一年が経ちます、ピーターこと佐々木です。 私の配属先のプロダクトであるBOXIL EVENT CLOUDは、様々な背景(後述)から自分含め3人いる開発メンバーの誰一人既存のコードがどのような思想のもとで書かれたものか分かっていない状態からスタートしました。 表から見るとそんなに規模の大きくない1つのサービスに見えるのですが、アプリケーションが2つに分かれており、DBも別々に管理していたりと、全体像を把握するだけでも一苦労に見えました。 その上、ソースコード自体も詳細に分かれすぎていて、複雑に入り組んでいます。いわゆるスパゲッティコード状態です。 そんな状態のプロダクトとの付き合いももうじき1年になります。 そこで今回は、どのようにしてBOXIL EVENT CLOUDにチームとして立ち向かっているのかについて紹介いたします。 プロダクトの概要と開発背景 BOXIL EVENT CLOUD について スマートキャンプでは、主にSaaSを導入したい企業様向けに、それぞれの企業様のプロダクトの紹介の場を設けることで、潜在顧客様との接点を作る手助けをさせていただいております。 オンライン展示会を開催するプラットフォームとして、そのような接点の1つとしての役割を求めらているのが、私たちが開発している「BOXIL EVENT CLOUD(ボクシル イベント クラウド)」(以下、イベクラと言います)です。 イベクラの開発背景 イベクラは、もともとベトナムでのオフショア開発から始まったプロダクトで、スマートキャンプのエンジニアはあまり関わってこなかったプロダクトでした。 しかしながら、開発効率を上げるために約1年前にオフショア開発から社内開発に切り替えました。そうして生まれたのが、私たちイベクラチームです。 ですが、ある問題が浮上します。それは引き継ぎの際にあまり整理されたドキュメントなどが得られなかったことです。その結果、私たちは中身を全く知らないプロダクトをパッと渡されたところからメンテナンスを始めるという状況に等しいところから、開発を始めなければなりませんでした。 最初の課題 そのため、すでにある大量のコードがどのような思想で書かれたものなのか不明だったのが最初の課題でした。 例えば、 このGemはどういう目的で、いつ入れられたものなのか、今後も必要なのか 今あるこの機能は一見不要そうなのだが、どういう経緯で作られたものなのか システムが複数リポジトリ、複数AWS環境に跨っているが分かれている必要はあるのか 大量にあるテストコードが本当に意味のあるコードになっているのか などです。 そして、実際に調査する中で求められているビジネス要件に対して、1+1を計算するのに量子コンピュータを持ち出しているような、過度な道具や手段を用いているところが多々見つかりました。 いわゆる「ハンマーしか持っていなければすべてが釘のように見える」状態で作られたプロダクトと言えるやもしれません。 そして出会ったのが、今回ご紹介したいDesign Documentです。 Design Documentとは GoogleのDesign Doc 一般的にはGoogleのDesign Docが有名で、それをアレンジしたものとして、私たちのチームでは活用しています。 これは、ソフトウェアの設計を定義するためのものであり、開発者自身がコーディングタスクに着手する前に作成し、主に実装方針と設計がその際に考慮したトレードオフと共に書かれているものです。 詳細は、こちらをご覧ください。 Design Docs at Google イベクラチームにおけるDesign Document このDesign DocをDesign Documentとして、イベクラチームでは「仕様のレビューを行なう文章」として捉えています。 ※前提として私たちはスクラム開発で業務を進めています。 Pull Requestを作成するとき、次のような問題背景を書くと思います。 何が問題なのか / なぜこの変更が必要なのか 今の仕様・設計はどうなっているのか 変更の目的/歴史的背景 設計上のトレードオフ これらをPRを出す前・コードを書く前に書いておいて、その内容をチームでレビューしてからコードを書き始めましょうというものです。 これにより、PRを出すときには、レビュアーはすでにその変更の背景を理解しているため、実装のレビューに集中できます。 GitHubのIssueとして書いて、PRにリンクするのも1つの手だと思いますが、私たちはNotionで行っています。レビューを行なうのに、それは特定の行、単語にコメントが書ける方が適していると考えているからです。 フォーマット また、フォーマットは次のようにしています。 タイトル/著者/作成日時/最終更新日時 概要 問題背景 提案 Sign off(承認) この他にも項目を追加することもありますが、これが基本です。 また、このDesign Documentにはレビューしやすい以外にもメリットがあります。 Design Documentがチームにもたらすもの 手戻りが発生しない コードを書いてPRを出してから、「その設計どうなの?」と仕様の検討に戻ることがありません。なぜなら、その認識を合わせるのが、Design Documentの役割だからです。 そのため、PRがマージされるまでの時間もとても短いです。 「後からこのPRでこの修正も入れた方がいいんじゃない?」ということもありません。 もし後から問題が見つかった場合は、別のDesign Documentとして切り出され、以前のドキュメントをリンクするようにします。 問題認識の差が埋まる イベクラ開発初期は、自分がタスクにアサインされた箇所しか内部的にどのような仕様になっているかが把握できていませんでした。そのためレビューするのもなんだかとても難しかった印象があります。しかし、Design Documentを書き、あらかじめ整理されたドキュメントを読むことで新しく開発に参画した人でも問題の背景がわかり、レビューがしやすくなります。 また、チームで設計のレビューを行なうので、技術に長けている人の知見が活かしやすくなります。逆にプロダクトに詳しくなかったり、知見があまりなかったりしても自信を持ってPRを作成できます。 考えが整理される チームメンバーに変更の背景を伝えようと文書化する過程で、思考が整理され無駄のない修正になります。 おっちょこちょいな開発者は、この修正で動くからいいだろうとたくさんコードを書いてPRを出してから、よくよく調べたら今実装したクラスはすでに実装されていて、それを使えばいいだけだったということに気づくことがあるかもしれません。 逆に、一見不要そうな機能でも実は使っていることもあります。このクラス使ってなさそうだから削除してしまえとPRを作ったら、実は使っていたなんてことは山ほどあります。 そのようなことは、コードを書き始める前に問題背景を明らかにし、現在のコードになっている歴史的背景をまとめておけば起きないはずです。 応用が効く Design Documentの考え方は、レビューのためだけに使うのも勿体無いです。 リモートで働く上で、議論を進めるのにもかなり便利だなと感じています。 周りにある全ての議題・問題に対して、Design Documentを書くことで非同期的に議論を進めることができ、会議時間が減らせると思います。 Design Documentのポイント7選 実際に運用する中で、分かってきた7つのポイントについて紹介します。 問題背景を書くことに99%の労力を割く(つもりで書く) フォーマット に書いたような構成でDesign Documentを書いていますが、このドキュメントを書くのに最も意味があるのは問題背景の部分です。 人によって何を問題と捉えているかは、実は違うことがよくあります。 「問題だと思っていたことが仕様でした」ということや「別の問題を解決したから今のコードになっている」ということはたくさんあります。 イベクラで例を挙げると、ジョブキューで行っている動画の視聴記録の保存処理があります。この処理は負荷が高く、他のプロセスを圧迫する可能性があるため独立させていますが、その背景を知らないと管理のしやすさを優先してまとめようという話になるかもしれません。 したがって、自分達が使っているアーキテクチャはどのようなもので、かつ歴史的経緯はどうなっているのか、という背景を明らかにすることが重要です。そして、「確かにそれが問題だね」という共通認識を持つことが一番大事です。 これを疎かにすると、間違った方向にプロダクトを進めてしまう可能性もあります。 そのため、1行のバグ修正でも背景が共有できてなかったら書きます。 Design Documentを書くのは、手間だと感じるかもしれませんが、マージされるまでの時間の短縮になるので結果的に効率は速くなると考えています。 こちら とは思想が真逆ですが、今のプロダクトの状況的にこの判断が今はベターだと思っています。 読者が知っているであろうことから始める 現在の仕様を知らない、未来の開発チームが見ても話がわかるようにDesign Documentは書いておく必要があると思っています。 そのために、現在のチーム内で周知の事実であってもなるべく省略はせず、今新しく入った人が見ても問題背景が分かるように説明しておくことが大事かなと思います。 未来の開発者がコードを見て、なんでこういう仕様になっているのだろうと疑問に思ったときに、そのコードが書かれた時にプロダクトが抱えていた問題が正確にわかれば、どう修正していけばいいかも自ずと見えてくるはずです。 過去のPRを読みに行く 問題背景を調査するために、過去のPRを遡ることが重要でした。 git blame は欠かせません。 今からやろうとしている修正が、実は過去に一度失敗した方法だったなんてこともここで分かります。 この機能、このライブラリ、この変数、使ってなさそうだな、消しちゃえーっとやってしまってから、実は使ってましたなんてことが山ほどありました。 しかし、ちゃんと事前に git blame してPRを見に行くと、周辺の修正が一緒に見られるので、どの機能を作るために必要だったのかが一瞬でわかります。 この過去の経緯をしっかり把握しておくことが、スパゲッティを解くのには欠かせないと感じました。 もう過去のPRを読み、問題背景に書かずには私はPRを作れません。 PRから辿れるようにする 新しくプロジェクトに関わってきた人が、なんかこのコード読みにくいな、と思った時に、そのコードが書かれた経緯が遡れることはとても重要だと思っています。 これは私たちが実際にその状況に置かれて痛感しました。 背景を知っていれば「あ、このコードは一時的な解決策として書いたものだったけど、後でリファクタしようと思っていたんだな」とか「この機能は今後使われる見込みがないから、一時的にこの状態なんだな」と判断できます。 昔は、こういうビジネス要件があったから、作られた機能だったが、今の要件に照らすと入らないというのも自信を持って言えます。 しかし、過去のPRに変更背景が記載されていないことも多々あり、化石から恐竜の姿を想像するように、当時の背景を推測しかできないこともありました。 そういうことが起こらないように、これから作るPRには、Design Documentのリンクを貼っておきたいです。 議論の足跡を残しておく Design Document上では議論をします。 そのため、行ごとに指摘がしづらいGitHubのIssue機能は使わず、Notionでドキュメントを書くようにしています。 また、ドキュメント内で、このような感じで合意を取っています。 チーム間、メンバー間でのすり合わせが大事なので、誰と議論したのかを残しておくことが大事です。 [x] Aさん [x] Bさん [ ] Cさん 実装後はDesign Documentを更新しない Design Documentに詳細に問題背景を書くこと、これはそのドキュメントで果たすべき責任範囲を定義するのとほぼ同義です。 そのため、実装後にDesign Documentを修正し、後からこの修正も入れようと考えるのは、よくないと感じます。 もし、仕様が変わって新しいことをするときは、新しいDesign Documentとして、何が問題だと思ったのかを明らかにするとともに、歴史的経緯として前のドキュメントのリンクを入れておくといいと思います。 タスクと紐付ける 現在私たちは、バックログの管理をNotionで行なっています。 そして、チケットをすべてDesign Document形式で書く運用にしています。 そうすることで、管理がしやすいと思っています。 導入による心理的な変化 業務への関わり方が不慣れだった私目線での良かった点をあげます。 チームへの発信がしやすい Design Documentは書くだけならタダなので、もっとここをこうした方がいいんじゃないか?という提案をチームに発信しやすくなりました。 これにより、なんかちょっともやっとするところがあるんだよな、でも話に行って作業の邪魔をしてしまうのも悪いし…という時に、サラッとドキュメントを書いて、見ていただくようにするとお互いの好きなタイミングで議論が進められるので、気兼ねなく相談できるなと感じています。 タスクを進めるときに迷いがない どうやってタスクを進めようかという迷いがなくなりました。 最初の頃は、何をどこから調べればいいのか、どういう形に持っていったら理想なのかと頭を悩ませていましたが、今は違います。 歴史的背景を明らかにして、何が問題なのかを整理すれば自ずと解決策は分かるということに気づきました。 自信を持ってPRが出せる 事前にチームと問題を共有し、自分が何をするかをすり合わせているので、後からそもそも問題解決方法がいけてないという指摘を受けることがないからです。 今後の課題 イベクラでは、全てのタスクが調査の側面を持っていて、現在の仕様確認と歴史的背景を一度整理するところから入らないことには話が進みません。 そのため、プランニングでポイントをつけることも一切できていないです。 対して別サービスの BOXIL SaaS 開発チームでは、プランニングの段階で何をどのように修正するかの精度が高いです。 誰かしらが現在の仕様について知っていて、その人を中心にみんなで話し合いながら、どういうふうに実装していくかの方針までをプランニングで決めていました。 Figmaでどういう風にメソッドが生えているかを図を使ってわかりやすくして、問題背景の共有が同期的に行える状態にあります。 そのため、誰が実装しても同じくらいの時間で終わるみたいな見積もりが成立すると思っています。 しかし、イベクラチームもDesign Documentを書き続けていくことで、歴史的背景がドキュメントとして蓄積され、いずれは誰もが仕様を理解している状態に来るのではないかと思っています。 そうなった時に、やっと私たちのスクラム開発がスタートするのです。 最後に まだまだチームとしてどのように開発を行なっていくのが効率的かは模索中です。 今後もチームでの効果的な業務の進め方の改良を続け、小さなチームでも大きな成果を出せるということを証明していきます。 ここまで読んでいただきありがとうございました!
アバター
Ruby のロゴについて 自己紹介 Ruby 3への移行 脱Refile 過去の先駆者 開戦 問題その1 画像のURLがS3のエンドポイントになっている問題 問題その2 移行対象のレコードが大量問題 問題その3 画像が荒くなる問題 幾多の障害を乗り越え その他gemの更新 ついにRuby3へアップデート 1番の影響 Ruby 3へのバージョンアップを終えて 最後に 自己紹介 2023年1月1日付け入社の はかまた です。 BOXILカンパニープロダクト本部配属でBOXIL SaaSの開発エンジニアとして働いています。 スマートキャンプはニックネーム文化があり、私は「職人(しょくにん)」になりました。 (GitHubのアカウント名を寿司職人にしていたらそうなった・・・) 最初は職人と呼ばれることに若干の抵抗がありましたが、不思議なもので今ではもう呼ばれ慣れています。 職人に見合った仕事を全うできるように日々奮闘中です。 私は秋田県からフルリモートで働いています。 最初は戸惑いながらも徐々に環境を整えたり、他の社員の方々とたくさんコミュニケーションをとってようやく慣れてきた気がします。 スマートキャンプは社員同士のコミュニケーションが本当に活発です。 あたたかく受け入れてくださった皆さまには本当に感謝しています。 Ruby 3への移行 タイトルが「Ruby 2.7に飽きたから秋田からRuby 3移行した話」となっていますが、 もちろん私1人で対応したわけではなく、BOXIL SaaS開発メンバー全員で対応しました。 (ただ私自身が秋田での勤務のため、飽きたと秋田をかけたかっただけです・・・) BOXIL SaaSはこれまでRuby 2.7を使用していましたが、 2023年3月31日でEOLを迎えるため、Ruby 3に移行する必要がありました。 しかし、Ruby 3にバージョンアップするには依存ライブラリのバージョンも上げていく作業が必要でした。 脱Refile BOXIL SaaSでは資料や画像のアップロードにRefileというgemが使われていました。 しかしRefileはもうメンテナンスがされておらず、Ruby 3に対応していません。 https://github.com/refile/refile 最終コミットが3年ほど前になっています。 そのため、Refileを剥がし、Shrineというgemに移行する必要がありました。 過去の先駆者 幸いなこと?に脱Refile、Shrineへの移行の作業はすでにGitHub上のPRにありました。 そのPRはRefileとShrineのデータを同期するところまでは完了しているようでしたが、それ以降音沙汰がなさそうな状況でした。 PRは2020年に作られていたようです。 何があってこのPRが放置されてしまったのか、既存の開発メンバーに経緯を確認してみたところ、 もともとこのPRは開発体験向上のためのタスクとして、取り組んでいたそうです。 ですが、当時取り組んでいた別のタスクの方が優先度が高く、その作業をしているうちに月日が流れ、開発メンバーも入れ替わりました。 その結果、当時対応していた開発メンバーもいなくなり、対応し難い状況から放置されてしまったということらしいです。 私たちはこれらの蓋を開けていくことになりました。 開戦 BOXIL SaaSはSaaSを導入したいユーザーとSaaSを提供しているベンダーをつなぐリボンモデルのプロダクトです。 そのため、SaaSのサービスロゴやSaaSに関する資料や画像のデータが多く存在します。 BOXIL SaaSのどこにRefileが使われているのか確認していくと サービスのロゴ サービスの資料 サービスのスクリーンショット ホワイトペーパー ライト会員用資料 会社のロゴ プロフィール画像 のアップロードと表示に使われていることがわかりました。 ありがたいことに Shrineの公式サイト には Refile移行のためのドキュメントが整備されています。 これを元に移行を進めていくことになりました。 Shrineは1つのモデルに対して、画像をアップロードするアップローダー(実体はClass)を作成します。 そこにMIMEタイプのバリデーションや頻繁に使用される画像サイズをあらかじめ定義できます。 移行にあたり、それらを修正する必要がありましたが、その作業自体は難しくないものでした。 ですが、問題はここからでした。 問題その1 画像のURLがS3のエンドポイントになっている問題 アップロードしたファイルはS3に保存されるような実装になっています。 画面からファイルのURLを確認するとS3のエンドポイントになっており、直接ダウンロードができてしまうという状況でした。 BOXIL SaaSはCloudflareを通し、画像を最適化しつつ表示しているので、S3から直接画像を取得してしまうと最適化も機能しません。 この問題はShrineのプラグインである Download Endpoint を使用して解決しました。 具体的には以下のような設定を追加します。 # config/initializers/shrine.rb Shrine .plugin :download_endpoint , prefix : " attachments/files/images " # config/routes.rb (Rails) Rails .application.routes.draw do # ... mount Shrine .download_endpoint => " /attachments/files/images " end これで、ファイルのエンドポイントのホストはboxil.jpになり、ファイル自体へのURLはハッシュ化された状態になりました。 実際にBOXIL SaaSのサービスロゴの画像のimgタグを確認してみると以下のようになっています。 < img alt = "BOXIL" class = "service-logo-image" loading= "lazy" src = "/attachments/files/images/eyJpZCI6IjBjMGE1MzRmMWQyYjY1NjQ0Y2EyOWFkZjVjZDNhNzViLnBuZyIsInN0b3JhZ2UiOiJzZXJ2aWNlX2xvZ28iLCJtZXRhZGF0YSI6eyJmaWxlbmFtZSI6ImltYWdlX3Byb2Nlc3NpbmcyMDIzMDQxMC0xMjQtNHRtbjdpLnBuZyIsInNpemUiOjkyMjUsIm1pbWVfdHlwZSI6bnVsbH19" > この対応をすることで、S3のエンドポイントは公開されることがなくなりました。 問題その2 移行対象のレコードが大量問題 BOXIL SaaSでは実際のサービスを紹介しているページがあります。 そこにはサービス画面を紹介するためのスクリーンショットが表示されています。 サービス画面のスクリーンショットは9000件のレコードがあり、このレコードをShrine用のデータに変換しつつ、元画像から各画面表示用に最適化された画像を生成する必要がありました。 変換と画像の生成処理をバッチを作成して単純に実行したところ、実行結果が戻ってきませんでした(念の為、5〜6時間くらいは待った)。 のちのち確認したところ、件数が大量すぎて処理がロールバックされており、変換もされていませんでした。 これはidを指定した範囲で絞り込み、少しずつ変換してあげるようにしたところ、無事最後まで変換できました。 問題その3 画像が荒くなる問題 画像を単純に表示すると、画質が荒くなったり、画像が大きすぎてはみ出てしまう問題がありました。 これは縦横比を固定したまま指定された画像サイズにするメソッドresize_to_limitやCSSを駆使してなんとか解決しました。 https://www.rubydoc.info/gems/carrierwave/CarrierWave%2FMiniMagick:resize_to_limit 幾多の障害を乗り越え 無事にRefileからShrineへの移行が完了しました。 その他gemの更新 Ruby 3に対応していなかったバージョンのgemも順次アップデートを実施しました。 unicorn simple_form rubocop image_processing mini_magick ddtrace bugsnag faraday faker slack-api ※自前実装して削除 slack-notifier ※自前実装して削除 こうして見ると細かいGemの更新が全然できておらず、プロダクトとしても不健全な状況でした。 この機会にアップデートできて良かったと思います。 ついにRuby3へアップデート BOXIL SaaSはAWS ECS上で起動しています。 Dockerfileの内容を更新し、BOXIL SaaSはついにRuby 3へとバージョンアップしました。 もちろんすんなりバージョンアップできたわけではありませんでした。 CIを実行するとエラーが発生し、それら1つ1つを修正する必要がありました。 1番の影響 Ruby 3へのバージョンアップをするときに1番影響があった変更点はキーワード引数の仕様が変わったことです。 https://www.ruby-lang.org/ja/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ Ruby 2.7では警告で済まされていたものが、Ruby 3ではエラーとなってしまいます。 例えば以下のようなコードです。 def example ( arg : 1 ) p arg end # Ruby 2.7: 引数のHashは自動でキーワード引数に変換される example({ arg : 100 }) # Ruby 3.0: ArgumentErrorとなる # example({ arg: 100 })はNG example( arg : 100 ) この変更に影響されたのかFakerもキーワード引数を受け取るような仕様に変更がされており、それも変更する必要がありました。 例えば以下のようなコードです。 # Ruby 3.0: ArgumentErrorとなる Faker :: Number .number( 4 ) # キーワード引数として引数を受け取るようになった Faker :: Number .number( digits : 4 ) このパターンとなっているようなコードを一つずつ修正していき、やっとのことでCIが通るようになりました。 そしてようやくRuby 3へのバージョンアップができました。 Ruby 3へのバージョンアップを終えて 今思うとかなり地味で大変な作業だったと思いますが、私はこの手の作業の経験が薄かったので、とても充実した時間を過ごせたと思っています。 システムを定期的にバージョンアップをしていくには CI/CDを導入して、自動テストができている テストコードがある程度、網羅されている ということがマストだなと思い知らされました。 これが実現できていない状況でのバージョンアップは工数もかかりますし、工数のほとんどがテストに持っていかれてしまうので作業自体も楽しいものではなくなってしまいます。 理想としては100%全パターンを網羅したテストコードを書くべきだと思います。 しかし、ほとんどの場面では現実的ではないと思います。 スピードを求めてサービスを展開したいという場合はテストコードを書く時間すらも惜しいということもあると思います。 さまざまな経緯があってCI/CDの環境やテストコードがないというシステムもあると思います。 しかし、テストコードを書かないことによってどのようなリスクがあるのかを把握しておくことは必要だと思います。 今回のようなバージョンアップ作業でも大いにテストコードが役に立っていました。 自分が入社した時点でもある程度のテストコードが整備されていたので、そのおかげでバージョンアップ作業もスムーズに進めることができました。 システムの将来を見据えた開発をしていくのであれば、完璧なテストコードではなくとも、ある程度のテストコードを整備していくことはマストだと思います。 最後に 今回得られた知見はドキュメントにまとめて、チームの皆さんにも共有し、定期的にBOXIL SaaSのバージョンアップをしていきたいと思います。 もっとBOXIL SaaSを理解して、より良いシステムにしていきたい! 最後まで読んでいただき、ありがとうございました!
アバター
はじめまして! 2023年1月付でスマートキャンプ株式会社に中途入社した松下大祐です。 京都にオフィスを構える 京都開発部 に所属し、ソフトウェアエンジニアとして働いています。 今回は私の入社エントリとして、スマートキャンプへの入社理由や仕事内容について説明したいと思います。 自己紹介 職務経歴 スマートキャンプに入社した理由 社会に大きな影響を与えるプロダクトを開発したい 将来的なキャリアを自分の中で見つけたい 企業理念への共感 技術スタックについて 京都で働くことについて 京都開発拠点について 京都開発拠点とは 出社について 取り組んできた仕事 BOXIL SaaSの機能開発 共通ID基盤の開発 インターン生のマネジメント 終わりに 自己紹介 まずは、自己紹介をさせてください。 私は、1992年生まれの30歳です。 簡単な経歴は下記のようになります。 岡山県で生まれる 高校までを岡山県で過ごす 京都で大学生活を過ごす 東京のソフトウェア開発企業に入社し、6年半ほど東京で働く スマートキャンプに転職し、京都開発部で働く 岡山県の田園風景の中ですくすくと育ちました。 京都府にある京都大学の総合人間学部という学部に入学しました。 学生時代にはみなさん、どんなあだ名で呼ばれていましたか? 私は大学の食堂でぱふぇを一度に3つ食べたことから、「ぱふぇ」というあだ名を友人に呼ばれていました。 スマートキャンプにもあだ名文化があり、大学時代のあだ名を採用してもらい「ぱふぇ」と呼ばれています。 大学では、総合人間学部という学部の 認知情報学系 に所属し、認知言語学という言語学の一分野を専攻しました。 卒論では「ラーメンに関する言語学的表現」を題材として書いており、ウケを狙いたかった年頃だったんだろうと思います。 上記のように厳密な専攻は情報系ではなかったのですが、単位として認められるので情報系の授業も受講していました。 SchemeというLispの方言を授業で学習しましたが、これは初めて触れるプログラムとしてはなかなか難しい部類に入るものだったと思います。 しかし、私にとってはコードを書くことが非常に面白く感じられ、これを仕事にできると楽しいだろうなだと考え、ソフトウェアエンジニアになりました。 職務経歴 職務経歴については下記です。 1社目: パッケージソフトウェアの開発会社 2社目: 国内大手メッセージングアプリ会社のグループ会社 3社目: スマートキャンプ(現職) 1社目は、インターンに参加したという縁からパッケージソフトウェアの開発会社に新卒入社しました。 Javaでのバックエンドロジックの実装や、スマートフォン向けのセキュアブラウザの保守開発をひたすら行なっていました。 システム会社ではよくある構造ですが、開発部署はただ開発をし続け、導入や運用は別の部署が受け持つという構造です。 よりユーザーと関わりを持ってフィードバックループを回して良いソフトウェアやサービスを作りたいという気持ちが強くなり、2社目の企業に転職しました。 2社目は、国内大手のメッセージングアプリ開発会社のグループ会社で働いていました。 社内で非エンジニアが利用するLP作成ツールの開発、エンジニア向けイベントのシステム開発、サービス協業社様向けのCMSの開発など色々な経験を積みました。 そのグループ会社は、親会社が運営している多くのサービスを裏から支えることをミッションとしています。 しかし、数年働く中で、裏から支えるのではなく自分の手でサービス・プロダクトそのものを作って、社会に大きな影響を与えたいという気持ちが強くなり、転職活動を開始しました。 スマートキャンプに入社した理由 次に私がスマートキャンプに入社した理由について説明したいと思います。 主な理由については下記になります。 社会に大きな影響を与えるプロダクトを開発したい 将来的なキャリアを自分の中で見つけたい 企業理念への共感 社会に大きな影響を与えるプロダクトを開発したい 前項でも触れたとおり、自分の手でサービス・プロダクトを開発して、社会に大きな影響を与えたいという気持ちが自分の中にはあります。 前職入社時点では、社内のサービスを裏から支えることで間接的に社会に影響を与えることができると考えていました。 しかし、実際に働く中で間接的ではなくより直接的にサービス・プロダクトそのものを作っていきたいという気持ちが強くなっていきました。 転職活動の中で、スマートキャンプでは新規のプロダクト開発へのチャレンジを加速させていきたいという思いがあり、それを京都開発拠点で推進していきたいと聞きました。 実現のためにはまだまだ決まっていないことも多く、自分が介在する余地が大きいと感じ、そこに関わっていきたいと強く感じました。 将来的なキャリアを自分の中で見つけたい また、前職で働いている時点では、ソフトウェアエンジニアとしての自分のキャリアについて迷っている部分が大きかったです。 アプリケーションの設計をすることが好きなので、ソフトウェアアーキテクトになろうかな…?となんとなく考えていました。 しかし、その他のたとえばマネジメントといった業務の経験があるわけではなく、自分が何を楽しいと感じるかはあまりわかっていない状態でした。 スマートキャンプの京都開発拠点では、人数も少なく、インターン生も多く採用しており、マネジメントの分野も経験できるという話を聞きました。 また、マネジメント以外の分野も、平たく言えば何でもやる機会があるという話も聞いて、チャレンジしてみようという気持ちになりました。 企業理念への共感 上記のような理由で、スマートキャンプに興味を持ったうえで、選考に進むにあたって、カジュアル面談で聞いた企業理念にも共感しました。 スマートキャンプの MISSION は「テクノロジーで社会の非効率をなくす」というものであり、社会の非効率をなくすというのはまさに自分がやりたい、社会に大きな影響を与えることだと感じています。 技術スタックについて 前述のようにスマートキャンプは私にとって魅力的に感じられたものの、技術スタックがあまり噛み合っていないという問題もありました。 たとえば、自分は言語の面ではJavaやKotlinの経験が多いのに対して、スマートキャンプでは主にRubyを利用しています。 インフラ構成などもオンプレミスやプライベートクラウド上での構築が多く、スマートキャンプで主にAWSのようなパブリッククラウドを利用しています。 ごく基本的な部分のキャッチアップは当然自分で行なう必要がありましたが、応用的な部分や社内の文脈に依存した部分はドキュメントが多く存在しており、あまりキャッチアップは苦になりませんでした。 スマートキャンプではNotionにドキュメントを書く文化が強くあり、その文化に助けられたと言えます。 京都で働くことについて 転職活動時に声をかけてもらったときは、スマートキャンプの京都開発拠点での採用という形で、京都に転居して働くことになるという前提がありました。 私は、大学時代を京都で過ごしており、京都に関しては郷愁を感じており、魅力的に感じました。 声をかけてもらうまでに、具体的に京都に戻りたいという希望が自分の中にあったわけではありませんでした。 しかし、前職ではフルリモートで働いており、東京都心で暮らす必要性はあまり感じていませんでした。 都会で働いていた人が、その他の地方に引っ越しすることを、○ターンと表現することがあります。 一度実家がある都市を離れた後に、実家がある都市に戻るUターン、縁がない都市に行くIターンといったものもありますが、故郷の岡山にやや近い京都に引っ越したことから私の場合はJターンに分類されるようです。 郷愁を感じていた京都に戻るという面以外でも、実家の岡山により近い場所に転居するというのは、高齢になってきている両親に会いやすくなるという面でも好ましかったです。 学生生活ぶりに京都に引っ越してあらためて感じたのは、東京よりも街自体がコンパクトで便利であるということです。 スマートキャンプでは家賃補助制度があり、オフィスの1.5km圏内に住むことで、家賃補助があります。 京都開発拠点のオフィスは京都の繁華街の中にあるため、私も繁華街近くに転居したため、生活も非常に便利になりました。 京都開発拠点について ここまでは、私の転職理由について説明してきました。 次は、スマートキャンプや京都開発拠点について説明していきたいと思います。 京都開発拠点とは 京都開発拠点は、フルリモートワークではなく一定頻度で出社するのを前提として作られた拠点です。 出社を前提とするのは下記のような理由からです。 新規事業などのコミュニケーションを多く必要とする業務をスピード感を持って実施する 京都という学生が多い都市でインターン生とのコミュニケーションを多くとる 京都拠点でのインターン生は現在3名が働いています。 全員関西の大学に所属している学生です。 学生でありながら非常に優秀なメンバーたちで、彼らと働くことができるのは刺激的です。 出社について 京都開発拠点では曜日を合わせて現在週2回の出社するようにしています。 インターン生の稼働日もそれに合わせて設定し、コミュニケーションをとるようにしています。 社会一般では、感染症の関係で出社に関してハードルを感じている人も多いと思いますが、私個人としてはたとえばミーティングといった特定の業務は、出社を行なって対面で話した方が効率が良いと感じています。 また、週3回はリモートワークをするような取り決めになっています。 リモートワークの方がより集中しやすい環境を整えやすいということあり、コードを書くといった比較的自分の中で完結する作業に関してはこちらの方が効率が良いと感じています。 極端に出社とリモートワークのどちらに振り切るのではなく、ハイブリッドな働き方にするのは、個人的には肌に合っていると思っています。 取り組んできた仕事 次に私が入社してから取り組んできた仕事について紹介します。 BOXIL SaaSの改修 共通ID基盤の開発 インターン生のマネジメント BOXIL SaaSの機能開発 BOXIL SaaS はスマートキャンプのプロダクトで、SaaSを比較できるサービスです。 BOXIL SaaSのメインの開発メンバーは、京都開発拠点以外に所属しているメンバーです。 しかし、後述する共通ID基盤をBOXIL SaaSから利用するという観点から、キャッチアップを行なう必要があり、その一環でBOXIL SaaSそのものの機能開発を行いました。 エンジニアとしては実際にコードを読んで開発することで、ドメイン知識の理解が大きく進みました。 BOXIL SaaSでは、社内全体でスクラムのスプリントレビューを実施しており、自分の開発した機能が多くの人に褒められたのも嬉しかったです。 また、ビジネスメンバーと開発メンバーの距離が近いのは、スマートキャンプの良いところだと思いますし、個人的にも大好きです! 共通ID基盤の開発 共通ID基盤は、現在京都開発拠点でメインで開発しているプロダクトです。 スマートキャンプで現在運営しているサービスでは、サービスそれぞれでユーザー情報を保持しており、複数のサービスを同一のユーザーが利用する場合でもそれぞれアカウントを作成する必要があります。 複数のサービスを利用するユーザーにとってこういった状況は不便なので、共通してログインができる基盤を作ろうとしています。 まずは、BOXIL SaaSのユーザーが共通ID基盤を通じてログインができるように開発を進めています。 いきなり認証部分を切り出すのではなく、データベースを内部的に分割して読み替えを進めていくといった感じで、段階的に実装を行なっている状態です。 ユーザーの利便性を向上させるために実装しているものなので、できるだけユーザーにとって特別なアクションが必要にならない形を意識しています。 認証はWebアプリケーションにとって非常に重要な部分であり、それを切り出すという作業は非常に難易度が高いですが、だからこそやりがいがあります。 インターン生のマネジメント 前述の通り、京都開発拠点では学生のインターン生を3名採用しており、インターン生のマネジメントも業務として存在しています。 学生個々人がインターンを通して実現したいことと、現状やってもらっている業務がマッチしているかどうかのすり合わせや、日々の困ったことを1on1で拾い上げるといったことを行なっています。 業務経験が少ない学生だからこそ、業務ではこうやるといった知識を伝えていくといったことも意識しています。 「学生という業務経験の少ない相手だからより丁寧な伝え方をした方がよいな」というように相手の特性を考えながら自分の行動を変えるといった工夫をしながら取り組んでいます。 ソフトウェア開発とはまた違う難しさですが、スキルを新しく身につけている感じがして非常に楽しいです! 終わりに 以上、この記事では入社エントリとして、私がスマートキャンプに入社した理由や、現在の仕事内容について説明しました。 入社から3ヶ月が経過しましたが、入社前に期待していたことからのギャップはありません。 共通ID基盤の開発などではキャッチアップ的な要素を含むタスクも多い状態でしたが、これからはがっつりコミットして成果を出していきたいと考えています。 頑張っていきます!
アバター
はじめに ざっくりしたシステム構成の紹介 全体の構造 設計のポイント コーディング規約 上の階層を見に行かない 変数名は全体でユニークにする 変数のデフォルト値は設定しない main, outputs, variables 以外のファイルを原則置かない ポリシードキュメントはJSONファイルのまま管理する 変数で処理を変える仕組みを極力使わない 値のハードコードをためらわない コードが冗長であることをためらわない 残っている課題 AWSアカウント単位でしか用意しないものの扱い ECSのタスク定義の扱い 最後に はじめに はじめまして。スマートキャンプのおにまるです。 2022年10月に入社し、SRE兼インフラエンジニアとして働いています。 今回は、あるプロダクトの再スタートにあたって新しく作った、AWSのTerraformについてお話したいと思います。 再スタートにあたってアプリケーションが大きく変わるため、インフラも再構築する必要がありました。 もともとのインフラもTerraformで管理されていたのですが、アプリケーションの変更にあわせてインフラも大きく変えなければならず、Terraformのコードも大改修が必要だと分かりました。しかしあまりにも変更する箇所が多く、その範囲も広かったので、いっそ書き直したほうがよい、ということになったのです。 汎用性の高い設計にすれば、新しいプロダクトを立ち上げるときに使い回しが効くのでは、という目論みもありました。 しかし……いやあ、大変でした……。 ゼロから作る、という判断は間違っていなかったと思います。しかし汎用性の高いものを作る、という判断は間違っていたかもしれない、と途中で弱気になるぐらいトライ&エラーを繰り返すことになったのでした。 今回はそんなトライ&エラーの結果を紹介します。 これが少しでもみなさんのお役に立てば幸いです。 ざっくりしたシステム構成の紹介 これまでのアプリケーションは、かなり複雑な構成になっていました。表向きは1つに見えるのですが、GitHubのリポジトリが2つ、AWSの環境も2つあり、裏でこの両方を繋げて動作していたのです。これが開発する際の大きな障壁になっていました。 見た目や動作を大きく変えないまま統合する、というのが再スタートの目的の1つです。壮大なリファクタリングと言えるかもしれませんが、応答速度を改善する、信頼性を上げる、といったこの先の改善業務をするためにも避けて通れない道でした。 全体の構造 まず最初に、全体のディレクトリ構成をお見せします: . ├── environments │ ├── _common │ ├── lt │ ├── mig │ ├── prod │ └── stg ├── modules │ ├── acm │ ├── alb │ ├── ec2 │ ├── ecr │ ├── ecs │ ├── es │ ├── iam │ │ └── policy-documents │ ├── kms │ ├── rds │ ├── redis │ ├── s3 │ ├── ses │ ├── sg │ └── vpc ├── scripts ├── tmp └── utils ちなみに、過去のものはこういった感じでした: ├── terraform │ ├── modules │ │ ├── (app_1) │ │ │ ├── cdn │ │ │ ├── chatbot │ │ │ ├── datadog │ │ │ ├── ecs │ │ │ ├── efs │ │ │ ├── es │ │ │ ├── instances │ │ │ ├── kinesis-firehose │ │ │ ├── load_balancers │ │ │ ├── networks │ │ │ ├── rds │ │ │ ├── redis │ │ │ ├── s3 │ │ │ ├── ses │ │ │ └── site │ │ └── (app_2) │ │ ├── cdn │ │ ├── ecs │ │ ├── efs │ │ ├── instances │ │ ├── load_balancers │ │ ├── networks │ │ ├── rds │ │ ├── redis │ │ ├── s3 │ │ └── site │ └── workspaces │ ├── (app_1)-prod │ ├── (app_1)-test │ ├── (app_2)-prod │ ├── (app_2)-test │ └── tf-constant └── terraform-aws-ecs ├── cluster └── service_load_balancing ご覧のようにモジュールが app_1 と app_2 で分けて管理されていて、重複しているものもありました。 ECSの部分が別ディレクトリにあるのも気になります。どうしてこういう形になったのかは、すでに退職された方が書いたものなので、はっきりとは分かりません。 app_1 と app_2 が別々に管理されていたのが原因かもしれません。 次に、新しく作った方の environments ディレクトリの中身について説明したいと思います。 ファイルはこういった構成になっています: environments ├── README.md ├── _common │ ├── main.tf │ └── variables.tf ├── lt │ ├── locals.tf │ ├── main.tf -> ../_common/main.tf │ └── variables.tf -> ../_common/variables.tf ├── mig │ ├── locals.tf │ ├── main.tf -> ../_common/main.tf │ └── variables.tf -> ../_common/variables.tf ├── prod │ ├── locals.tf │ ├── main.tf -> ../_common/main.tf │ └── variables.tf -> ../_common/variables.tf └── stg └── locals.tf ご覧のように、 environments ディレクトリ以下に環境ごとの設定を置くディレクトリがあり、その中に locals.tf と main.tf 、 variables.tf があります。このうち main.tf と variables.tf は、どの環境でも同じということを明示するために _common のファイルへのシンボリックリンクにしています。つまり、環境ごとに違うのは locals.tf だけです。 共通の値を使う場合でも、 main.tf に値をハードコードすることは避けています。これは、環境ごとに値を変えることになっても対処しやすいようにする、という理由もありますが、社外秘情報を locals.tf に集めることで、これ以外の部分は誰に見られても大丈夫なようにしたい、と思ったからです。 設計のポイント 「はじめに」で書いたように、汎用的に使えるものを作るのが目的の1つです。どうすれば実現できるか、と考えたとき、読みやすいコードを書くのが一番大事なのでは、という考えに至りました。 汎用的に使えるといっても、完全にそのまま使えるわけではありません。違いを埋めるために書き換えが必要ですが、そうしやすいかどうか、そこが汎用的に使える、使ってもらえるときのポイントになると思ったのです。 そこで、 読む人にとって、見なければならない場所を減らす 読む人にとって、考えさせる場所を減らす そのために書く人は(すごく)がんばる を念頭にコーディング規約を作りました。 コーディング規約 上の階層を見に行かない モジュールの呼び出しやファイルの読み込みのときに、親ディレクトリを参照しないようにしました。こうすることでモジュールが使っているものはすべてそのディレクトリと、そのサブディレクトリにあることが保証され、ファイルの中を見ることなく、見なければならない範囲が明らかになります。 ただし環境別の main.tf は別です。 environments/prod/main.tf は、 ../../modules 以下のディレクトリを参照します。これ以外の例外を作りません。 (全体のディレクトリ構造は、ページの上部で説明しています) 変数名は全体でユニークにする 変数名は、どのファイルの中でも同じものを意味するようにしました。たとえばvpcモジュールの id という変数はVPCのIDなのは明らかですが、あえて vpc_id とします。こうしておくと、VPCのIDがどこでセットされ、どこで参照されているか、全文検索すれば一目でわかるようになります。 これだけだと、それほど必要ないと思われるかもしれません。しかし、たとえばログ出力のバケット名が入る変数だと効果があります。ECS用のALBなら log_bucket_alb_ecs 、CloudFrontなら log_bucket_cloudfront といった名前にしておくと、 log_bucket という変数を見て「中身は何だろう?」と調べるような状況にならずに済みます。 この延長で、すべてをユニークにしておくと、今後いつか役に立つ……かもしれません。 (変数の名前のつけ方はよく議論になります。すべての場面で優れているものはない、ということですが、Terraformに関してはわかりやすく、長い名前をつけることに利があるように感じます) 変数のデフォルト値は設定しない 一般に、プログラミング言語では変数にデフォルト値を代入(初期化)するものです。しかしTerraformでは、ロジックが書かれている main.tf とは別の variables.tf で変数が定義されています。 デフォルト値を使う可能性があると、変数に何が入っているか知りたいときは variables.tf を確認しなければなりません。しかし使わないと決まっていれば、 variables.tf を見る必要はありません。 main, outputs, variables 以外のファイルを原則置かない ファイルが少なく、それぞれの役目がはっきりしていれば、見なければならない場所が減ります。 モジュールは modules 以下にあり、こういったファイル構成になっています: modules ├── acm │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── alb │ ├── main.tf │ ├── outputs.tf │ └── variables.tf : (省略) : ├── sg │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── vpc ├── main.tf ├── outputs.tf └── variables.tf 1つのモジュールのロジックはすべて main.tf にあり、出力は outputs.tf 、入力は variables.tf にある、と決まっていれば、ロジックを知りたいときは main.tf だけ見れば済む、というのが見る前から把握できます。 また、2つ前で書いたように、変数名は全体でユニークです。 main.tf に出てくる変数がどう使われているかは、 outputs.tf や variables.tf を見なくても分かるということです。 locals.tf は、原則として置きません。ローカル変数を使わざるを得ない場合は仕方ありませんが、その場合でも変数に何が入っているかはっきりわかるように書きます。 ポリシードキュメントはJSONファイルのまま管理する Terraformを理解できる人より、JSONを理解できる人の方が多いからです。 ポリシードキュメントを変更することは多くありません。しかし、変更しなければならないときTerraformを使えない人でも編集できれば、より早く変更を反映させることができます。 templatefile という関数が用意されているのは、それを見越してのことではないかと思います。 変数で処理を変える仕組みを極力使わない 使わない resource を count = 0 にして使わないようにする方法があります。 たとえば本番環境とステージング環境、どちらか片方でしか使わない resource を、もう片方ではそもそも作らないようにする、といった使い方をすることが多いようです。 これはとても便利で、使わない理由はありません。ですが resource に渡すものによって違うものを作る、違う動作をする、ということだと話は別です。渡される変数がどういう値なのか、モジュールを呼び出している側を意識しなければ大事に至る可能性があるからです。 値のハードコードをためらわない 値をハードコードするのは悪だと、プログラマーは教わってきたと思います。私もそうですし、普通のコードを書くときはハードコードを避けています。 しかし今回のTerraformでは、状況によってはハードコードするようにしました。 具体的には、以下の条件をすべて満たす場合です。 環境ごとで値が変わらない 普段の運用で変更する可能性が(ほぼ)ない 他のプロダクトで使うときでも変更しない可能性が高い たとえばALBでは、 ターゲットグループの、ヘルスチェック対象のパスやステータスコード SSLポリシー リスナーのポート番号 などがその一例です。 これらを環境ごとの locals.tf で定義すると膨大な数になってしまいます。前の項目で書いたように、 variables.tf を意識せずに済むよう、変数のデフォルト値を使わないようにしましたが、その代わりに main.tf の中で(ハードコードして)定義しているわけです。設定を変えることが滅多にないなら問題にならないでしょう。 Webアプリケーションの場合、似たようなインフラ構成になることが多いと思います。新しくWebアプリケーションを作るときは、設計段階でインフラを考慮してもらえればいいと考えています。(もちろん、無理して寄せなくてもいいことをちゃんと伝えたうえで、です) コードが冗長であることをためらわない 参照する場所を減らすということは、それだけ共通化をしないということです。どうしても冗長になるところが出てきますが、ある程度は仕方ないと割り切ります。 これは、読む人に、ほんの少し違うコードを同じものと思わせてしまう危険があります。それでもTerraformでは見る場所を減らす方が有効だと、私は考えています。そういった箇所にはコメントを書くことで、危険をかなり抑えることができるからです。 残っている課題 AWSアカウント単位でしか用意しないものの扱い ECRは、環境毎に分けてもいいですが、別にそうしなくても問題ありません。Dockerイメージのキャッシュを活かす点では共有した方がいいような気がします。しかしTerraformでは、環境をまたいで共有するのはそれなりに面倒ですし、完全に分かれていた方が見かけもスマートな気もします。 今のところ環境別に分けていますが、今後どうすべきか、まだ悩んでいます。 ECSのタスク定義の扱い ECSのタスク定義をTerrafromとアプリケーションのどちらで管理するか、という部分はいまだに悩んでいます。コンテナインスタンスの管理という点では、Terraformで扱うのが普通のような気がします。しかしアプリケーションの動作を変えるため、よく変更するものでもあるので、そのたびにapplyするのは、あまり合理的ではないようにも感じます。 今はすべてTerraformで管理するようにしていますが、頻繁に変更する部分だけはアプリケーションで管理し、全体のベースとなるものはTerraformで管理する、というのを試してみようと思っています。ただ、それがベストかどうかは自信が持てないので、この先も試行錯誤が続くでしょう。 最後に Terraformで管理しているインフラは、手でいじってしまうと管理下に戻すのが大変だ、と感じている方は多いと思います。ですから運用中に手でいじってしまいたくなる要素を、Terraformでどれだけ簡単に変えられるか、と考えながら設計するのがとても大事だと感じました。 見通しがよく、シンプルで、他人がメンテナンスしやすいものが理想的、というのは一般的なプログラミング言語と変わりありません。 しかしTerraformはインフラを扱う分、いろいろな部分で「これはなんなんだ…!」と驚かされる、理不尽にも思える制約に振り回されます。変数やモジュールの呼び出しなどはその一例で、制約の理由が分からないままだと力業で解決しがちで、結果として読みにくいコードになってしまいます。そうならないよう、コーディング規約をきちんと作る、読む人が楽になるよう書く人が面倒をする、ということを普段より強く意識する必要があると感じました。 理想を目指すのは大変です。しかし逆に言うと腕の見せ所でもありますし、やり甲斐を感じられる部分でもあります。 今回の記事が、少しでも皆さんの参考になれば幸いです。フィードバックも大歓迎です!
アバター
はじめまして。ビジネス向けのSaaS比較サイト『 BOXIL SaaS 』のエンジニアをしていますJinJin(三浦)です。昨年12月にSESをメインで行っている企業からスマートキャンプに転職しました。 この度、テックブログの執筆を担当させていただけることになりましたので、競技プログラミングの成績をアピールポイントにして転職活動を行なった経験や、スマートキャンプに入社してみて気付いたことなどをメインに、入社エントリを書いていこうと思います。 スマートキャンプのエンジニアポジションに興味がある方だけでなく、競技プログラミングに興味がある方や、SES企業・SIerからWeb系への転職を考えている方にとって参考になるような記事にできればと思っております。 転職活動について きっかけ スマートキャンプを見つけた経緯 どうしてスマートキャンプにしたか 入社してみて カルチャー面 業務面 これから 転職活動について はじめに、転職をしようと思ったきっかけやスマートキャンプを選んだ経緯について書かせていただきます。 きっかけ もともと愛知県にあるSESをメインで行っている企業に務めており、自動車メーカーの関連企業に客先常駐しながらアプリケーション・エンジニアとして働いていました。そこでは主に自社フレームワークを用いた社内向けの経理システムの開発に携わっていました。 入社6年目に差し掛かる頃に自分の中で心境の変化があり、「よりモダンな環境・手法で開発をしてみたい」、「自分のスキルやキャッチアップ力が他の環境で通用するのか試してみたい」と思うようになり、その頃から業務を続ける傍らひっそりと転職活動を始めました。 スマートキャンプを見つけた経緯 趣味で競技プログラミングを行っていたため、そのスキルをアピールできないかと、求人サイトは『 paiza 』をメインで利用しました。競技プログラミングのコンテストで有名なAtCoderの行っている転職サービスも選択肢としてあったのですが、求人の多さや企業へのアプローチのしやすさから、paizaをメインで利用しました。 出典:プログラミングスキルチェック | ITエンジニア専門の転職サイト【paiza転職】 https://paiza.jp/challenges paizaでは求人を出している企業へアピールできる材料として、レーティングシステムが用意されています。その数値は、サイト内で用意されているプログラミングの課題を解くことで上げることができました。 もともと競技プログラミングの入門書として有名な『 蟻本 』で勉強していたり、AtCoderの週末のコンテストに継続的に参加して水色のレーティングを獲得していたりしたこともあり、本格的にして開始して約半月で最高のSランクの中でも上位の赤色のレートを取ることができました。 レートの推移。 そして、「どうせだったら、上位のランクを募集条件にしているところから探そう」と思い、条件指定して検索をかけた際に目に留まったのがスマートキャンプでした。 どうしてスマートキャンプにしたか カジュアル面談から選考を始めてもらい、数回の面接とリファレンスチェックがあった後に、内定をいただくことができ、その数日後に行われた内定者向けのWeb面談にてEMの入山さんと会話する機会がありました。 その場で、前職とはカルチャーも必要なスキルセットもギャップがあるけども、それを踏まえたうえで三浦さんでしたらキャップアップできると思うので、挑戦して欲しいという気持ちを伝えてもらえたのが、入社する決め手になりました。 入社してみて カルチャー面 利用するSaaSが多い SaaS比較サイトを運用しているだけあってか、社内で利用しているSaaSの種類も多いです。入社した初日には、多くのツールの利用登録を行なった記憶があります。(社内チャットツール、バーチャルオフィスツール、勤怠管理ツール、経費精算ツール、人事評価・社員名簿ツール、ナレッジ共有ツール、短文メッセージツール、など) 実際にそれぞれ有用に用いられており、Missionとして掲げる「テクノロジーで社会の非効率をなくす」を体現しているように思えました。 BOXIL SaaSエンジニアの雑談チャンネル『# boxil_dev_心』は心の癒し。 キャンプスペースでの開発朝会中のスクリーンショット。Gatherについての記事は こちら 。 Microsoft製品をあまり使わない 前職では「ドキュメントといえばとりあえずExcel」という文化でしたが、スマートキャンプに入ってからは、NotionでMarkdown形式で記述することが多く、スプレッドシートを使う場合でもGoogle Spreadsheetを利用するようになりました。 地味ですが自分の中では結構大きな変化でした。 また、開発PCもWindowsではなくMacを利用するようになりました。(Visual Studio Codeは愛用しています。) 支給されたMacBook あだ名文化 スマートキャンプではあだ名をつける文化があり、本名で呼ぶよりもメンバー同士の距離感を縮めやすく、ありがたいと感じています。(あだ名に慣れすぎて本名が思い出せなくなるのはスマートキャンプあるあるだと勝手に思っています。) ちなみに自分は、下の名前を音読みすると『ジン』となることから『JinJin(ジンジン)』と呼ばれています。 みんなコミュ力高い 心理的安全性を保てるようにしつつも、フランクに接していただける方が多いように感じます。自分も見習いたいと思う面が多いです。 業務面 Ruby on Rails 前職ではJava(Struts系)をメインを使っていたため、スマートキャンプに入社が決まってから初めてRuby on Railsに触りました。 フレームワークの方でいい感じにやってくれるが故に、どこがどう動いているのかわからなくて初めの頃は戸惑うことが多かったです。最近になってようやく勘所がついてきました。(と思いたい。。) GitHub Git・GitHubでの開発を本格的に行ったのもスマートキャンプに入社してからになります。前職では資産管理ツールとしてずっとSVNを用いていたため、ようやくデファクトスタンダードな資産管理ツールで開発ができる嬉しさがありました。 また、前職ではコードレビューの指摘事項はExcelに記述して共有していたのですが、GitHubのページ上でソースコードの具体的な箇所にレビューコメントを残すことができるようになり、レビューがGitHub上で完結するようになったのもありがたかったです。 また、『 GitLens 』(VSCodeの拡張機能)を用いて、ソースコードから過去のPull Requestを簡単に辿ることができ、書かれた背景などを簡単に調べられるのも便利でした。 アジャイル開発 BOXIL Magazineを運営しているメディアチームやセールスチームから次々と開発要望が上がってきて、それを、Sprintのタスクとして次々積んでいき消化する。それが、早いサイクルで回っていくのが楽しいです。 毎週金曜日にあるSprint Reviewでは、エンジニアチームの行った開発の成果を発表する場があり、その場で反応がいただけたりしてモチベーション向上にも繋がります。 これから チームやプロダクトに対してリスペクトがあるので、今以上にドメイン知識やシステム構成について理解して、機能追加や開発改善などをゴリゴリにできるようになっていきたいと思ってます。
アバター
vue3-migration-improve-frontend はじめまして!  BALES CLOUD エンジニアのえーす(井上)です。この度、BALES CLOUDで長年使ってきたVue2から卒業し、Vue3を導入した状態でリリースできました。今日はこれについてお話できればと思います。 やったこと なぜVue3移行をしたか TypeScriptサポート 各ライブラリが古い Vue2のEOLが近い 具体的なVue3移行ステップ Vuetify卒業 Vue3導入 Vue3完全移行 移行にあたって問題だったこと ライブラリのアップグレード Vuetify卒業 ElementUI -> ElementPlus 巨大PRによるレビュー負荷 チーム体制 マイグレーションビルドと他ライブラリの相性 よかったこと 課題感 これから 2024/04/03 編集部追記 やったこと Vue2を卒業し、Vue3を導入 依存している各ライブラリのバージョンアップグレード 適宜コードの書き直し Vue3導入と書きましたが、より詳細には マイグレーションビルド を使ってのVue3導入ができている状態です。つまりマイグレーションビルドによってVue2とVue3が共存しているような状態です(Vueのバージョンとしては3系です)。まだ完全にVue3へ移行できたわけではないのでやることは残っていますが、現状までに多くの知見を得られたので記事としてまとめることにしました。 なぜVue3移行をしたか BALES CLOUDではVueを使っています。バージョンは2.6.10となっていて、このバージョンは4年前から更新されていませんでした。4年前というと体感的にも相当古いと思います。 古いとはいえ動いているならそのままでステイするのもまた正義だとは思うのですが、BALES CLOUDではVue3移行を決断しました。 以下、理由です。 TypeScriptサポート フレームワークとしてVue2を使い続けていることによって、やはり辛いと思う部分が多くなってきました。その最大のものが「型」です。 やはりVue3といえばより良いTypeScriptサポートがされるようになった点が非常に大きな進歩だと思います。 Vue2でもVue.extendを使ってTypeScriptを利用できますが、型推論がうまく動かなくて明示的に指定する必要があったり、Vueインスタンスの型があまり使えなかったりしていました。「ああ、Vue2とTypeScirptの相性って微妙なんだなあ」と思うことが多く、積極的に型を使おうと言う気になれませんでした。 そのせいもあり、BALES CLOUDのVueコンポーネントでも、全体として型があまり使われていませんでした。 もっと型を活用しようと考えたのは、フロントエンドでバグがよく発生していた時期があったからです。型がないから起きたバグというわけではないのですが、むしろバグの原因となったものから短期的な改善を目指すのではなく、長期的に品質を上げ、結果として効率よく開発ができる状態を目指すことにしました。そこから型を活用しようという考えに至り、そのための環境があるVue3へ移行することにしました。 このあたり短期的な視点でなく長期的な視点で改善することを決断できるのがBALES CLOUDチームの良いところかなと思います。 各ライブラリが古い Vue以外にも、Vueに依存する各ライブラリも非常に古い状態でした。例えばElementUI(UIライブラリ)、vue-i18nなどコンポーネントで使っているものや、vue-cliやsass-loaderなどのdevDependenciesなどです。 ライブラリを古いまま放置しておくと、そのうちEOLとなってセキュリティリスクにもなったりしますし、開発者体験としてもよくないので、刷新をしたいと考えました。 Vue2のEOLが近い 皆さん Vue2のEOL (End of Life) が今年末 までであることはご存じでしたでしょうか。EOLとなるからといって、 セキュリティのサポートやブラウザの互換性サポートは引き続きされる ようですので、Vue2でステイするという判断も妥当だと思います。が、それも遅かれ早かれ終了するとは思うので、この機会に移行することにしました。 具体的なVue3移行ステップ 以下が具体的なVue3移行ステップです。 Vuetify卒業 Vue3導入 Vue3完全移行 一つずつ詳しく説明します。 Vuetify卒業 当時、BALES CLOUDではUIフレームワークを2種類利用していました。ElementUIとVuetifyです。 Vue3移行するにあたって最大の問題となったのがVuetifyでした。現在は VuetifyはVue3に対応しております が、移行を決めた当時はVue3にまだ対応しておらず、Vue3へ移行するのに障害となっていました。 ElementUIについてはVue3対応版としてElementPlusがリリースされていました。かつ、BALES CLOUD全体を見たときに、Vuetifyを利用している割合がElementUIに比べて低かったということもあって、Vuetifyを使うのを辞めてElementPlusに一本化することに決めました。 ElementPlusに一本化する前段のプロセスとして、まずVuetifyを依存ライブラリから抜いて、ElementUIに統一したものをリリースすることで、のちのVue3導入での変更量を減らすようにしました。 Vue3導入 Vue3導入フェーズでは、ゴールを下記に定めました。 Vueのバージョンが3系であること vue-compat(マイグレーションビルド)を利用して、Vue2の機能も使える状態であること つまり、マイグレーションビルドを使った状態でのリリースを目標としました。 これはVue3で使えなくなる機能まですべて修正するより、まずマイグレーションビルドを使った状態(つまりVue2の機能もある程度使える状態)でリリースすることで、コード修正量を減らし、ビッグバンリリースとなることを避けるためです。 こうすることで、このフェーズでやるべきことは下記に絞ることができました。 Vueのアップグレード マイグレーションビルドの導入 Vue3 + マイグレーションビルド環境では動かないライブラリのアップグレードとコード修正 ElementUI, vue-router, Vuex etc. マイグレーションビルドで互換性がないVue2の機能 への対応 Vue3完全移行 ここでのゴールは下記のとおりです。 Vueのバージョンが3系であること マイグレーションビルドが削除されていること 主にやることは下記の通りです。 マイグレーションビルドが出しているWarningへの対処 マイグレーションビルドを抜いたことによって動かなくなる部分への対処 ここまでやって初めて「Vue3へ移行した」ことが言えると思います。 ちなみに「マイグレーションビルドですでにVue3にバージョンアップしているのだから、無理に抜く必要なくね?」と思いましたが、 マイグレーションビルドは将来のマイナーバージョンでリリースされなくなる ので、やっぱり必要です。 移行にあたって問題だったこと ライブラリのアップグレード 当然ですが、Vueに依存するライブラリはほとんどすべてアップグレードしました。以下、一部ですが紹介します。実際にはもっとあります。 @vue-compat の導入 マイグレーションビルド element-plus の導入 element-uiはVue2までのため vue-i18n を9系にアップグレード vuex を4系に vue-router を4系に @vue/cli-service を5系に sass-loader を10系に core-js を3系に @vue/cli~ をすべて5系に vue/cli-service のアップグレード(webpack5)に合わせるため @vue/compiler-sfc の導入 @vue-compat のため node-polyfill-webpack-plugin の導入 webpack5系がnodeのpolyfillを自前でやってくれなくなったので devDependenciesはとりあえず動いていればよいだろうと判断しました。その他は一つずつ公式のBreaking Changesを読み解き、自プロダクトのコードが移行対象でないかを確認しました。Breaking Changesに未記載だったり、単純に確認漏れもあるので、なかなか一筋縄ではいきませんでした。 Vuetify卒業 前述の通りBALES CLOUDはElementUIとVuetifyの両方に依存していますが、このうちVuetifyを抜くとなるとシステムのUIが一部変わることにならざるを得ません。例えば一番顕著だったのがページネーションのデザインの変更でした。こちらは常にユーザーに見えているものなので、デザイン変更の影響が大きいです。 このようなデザイン変更に伴ってPdMを合意を取るべく、変更予定のコンポーネントを一つずつ洗い出して、Figmaに起こしました。それを確認してもらいつつ、かつ実際の環境でも触れるようにして、PdMとデザイン変更の合意形成を行いました。 一つずつUIの対照リストを作成 ElementUI -> ElementPlus ElementUIからElementPlusへの移行は、一言でいうと苦労の連続でした。おそらくここがVue3移行において一番つらかったと思います。 まず Breaking Changes にも載っていないような本当に些細な変更がところどころあったことが大きかったです。例えばDatePickerなどでのBlurイベントで渡ってくるものが コンポーネントインスタンス から FocusEvent になっていたりなどです。もともとこの動きに依存していたので結構困りました。 このような些細な変更に対しては、一つずつコンポーネントを見て動きをチェックする他ないです。かつ問題があったとしても元の仕様を再現するには非常に難しくなっているものも多く、かなり工数がかかりました。 地味にウッとなるのが GitHubのissue などを漁っていると高頻度で中国語が出てくることです。Elementに中国企業のスポンサーが入っていることが大きいと思います。ページ翻訳すればいいと思うのですが、やっぱりさっと英語で読めるのと比べると少し手間だったり、開発者としても疎外感のようなものを覚えますね。 ElementPlusのissueは半分ぐらい中国語で記載されている 巨大PRによるレビュー負荷 この手の移行はインクリメントとしてリリースできないので、どうしても巨大なPRとならざるを得ません。巨大なPRはそれだけでリスクですし、そもそもエンジニアのレビュー負担も大きいです。 レビュー負荷を下げるべく行ったのは、まずPRの分割です。出てきたバグに対して一つずつアイテム化し、それに対してトピックブランチに向けてPRを作成しました。これによって変更内容と変更理由が一つずつ明確になります。特に移行による変更は複線的なので、一つのPRでまとめてしまうと、どの変更がどのような意図で行われているのか全体像が掴みづらいところがあります。修正を分割することで、そのあたりの負荷を減らすことができたと思います。 チーム体制 Vue3移行は、通常の機能開発をストップせずに行いました。プロダクトロードマップとして達成するべき機能開発があるなかで、開発改善に全リソースを割くだけの余力はないからです。 そのためチームの体制をあらためて考える必要がありました。そして最終的にVue3移行の実行責任者として私が就き、主にVue3移行の主開発やその他開発体制を整える仕事(各ドキュメントやマイルストーンの作成など)を行いました。 このような体制にしたことで、責任範囲が明確になり、自分としては動きやすかったように思います。プロジェクトをドライブする人が一人いることで、「いつまで経っても移行が進まない」みたいな状態を作らなかった点も良かったかなと思います(規模が大きめの開発改善だとあるあるですよね)。 マイグレーションビルドと他ライブラリの相性 マイグレーションビルドは便利なのですが、これをそのまま他のライブラリのVue3対応版で動かすとちゃんと動かないケースが何回かありました。 例えばElementPlusはマイグレーションビルド下だとDatePickerなどの日時入力系コンポーネントにvalueが入らない問題がありました。 最終的な原因としては、Vue3のAPIを利用しているのにマイグレーションビルドによってVue2でコンパイルされていたことでした。これは compatConfig というオプションをDatePicker系が依存しているElementPlusのコンポーネントに指定することで解決しました。 import { CommonPicker } from 'element-plus' CommonPicker . compatConfig = { MODE : 3 } マイグレーションビルドを経由せず一気に移行すれば起きなかった問題ではありますが、今回の場合は仕方ないかなと思います。ちなみにマイグレーションビルド + ElementUIで動かないかとも探ったのですが、それはそれでエラーが出て全く動かず、丸一日試して駄目だったので素直にElementPlusに移行することにしました。 よかったこと Vue3を導入してみて、あらためて実感したのが型サポートの充実です。 もともと「VueとTypeScriptの相性微妙じゃね?」みたいな空気がVue2では漂っていたと思うのですが、アップグレードして実際にVue3を使ってみると、VueとTypeScriptの相性が微妙という感想がなくなりました。コンポーネントが持っているpropsに対する型も効きますし、綺麗に型推論が使えていたり、 this の型が充実していたり、とにかく型をフルに使える環境が整っています。 充実のthis これによって、より安全に、より高速に開発できる環境が整ったように思います。この環境だけでも導入した価値があるように思います。 課題感 BALES CLOUDはElementPlusに大きく依存したUIなのですが、Vue3移行をしていて、あらためて「依存していること」自体に課題感を覚えました。 BALES CLOUDはその特徴として第一に「UIが使いやすい」ことを挙げています。これは顧客からもよくフィードバックをいただきますし、開発チームとしても非常に大事にしている価値です。 そのようなプロダクトの価値の根幹を成すUIが、完全にライブラリに依存していることで、移行時に不都合がありました。例えば「ElementUIからElementPlus移行すると、ElementPlusの仕様上、元の挙動にはどうしてもできない」というような事象がありました。 一時はリポジトリをフォークしてくるというようなアイデアも出たのですが、それはそれでアップグレード時に手間だったりして大変だという話にもなりました。最終的には、顧客の体験を大きく損なうことはないと判断し、仕様を少し変えて提供するという形になりました。 ですがこれからも同じような事象が発生する可能性があることを考えると、「UIが使いやすい」ということを標榜するプロダクトである以上、いずれ向き合う必要がある課題ではないかとBALES CLOUDチームでは考えています。 これから 今の状態としては上記で言うところの「Vue3導入(マイグレーションビルド込みでのリリース)」が終わっている状態で、まだマイグレーションビルドからの卒業ができていません。このあたりはこれからまだBALES CLOUDでやっていくところだと思います。ただVue3へのアップグレード自体は終えられたので、大きな山は超えたと言えると思います。 ここからマイグレーションビルドを抜いてVue3に完全対応するまで、BALES CLOUDチームは走っていきたいと思います。 2024/04/03 編集部追記 本記事の続きである マイグレーションビルドからの卒業 についての記事が公開されています。 tech.smartcamp.co.jp
アバター
こんにちは!スマートキャンプエンジニアの中田(のっち)です。 みなさんWebブラウザには何を使われてますか? Chrome, Firefox, SafariにEdgeなど多くの選択肢があるWebブラウザですが、私は2ヶ月ほど前に長らく使ってきたChromeから移行し、現在は Arc というbeta版が公開されている新しいWebブラウザを使ってみています。 この Arc がとても便利で楽しいものだったので、本記事ではそんな Arc の紹介 (※普及活動) をしていきます! Arcとは 概要 インストール方法 ※ 公式から情報を辿りたい方向けの補足 特徴 機能 1. タブ/ブックマークの管理 2. Note / Easel機能 3. Boosts機能 実装の方向性 WebのOS的な立ち位置のアプリ 推し機能 Note / Easelのキャプチャ機能 まとめ 参考サイト Arcとは 概要 Arcは The Browser Company という、創立2019年、New Yorkが拠点の企業で開発されているそうです。 企業サイトの Values ページが個性的なので興味のある方はぜひご一読ください。 arc.net ArcはブラウザエンジンにChromiumを使用したWebブラウザです。 現在はbeta版のみ公開されており、OSはmacOSにのみ対応しています。 (2023年中にはWindowsOSやモバイルのOSもサポート予定とのこと) インストール方法 現在はbeta版公開のみのため、アプリを即時インストールできない仕様になっており、インストールには以下の手順を踏む必要があります。 登録ページ から情報を入力 待機リストに入る 1~2週間ほどで招待メールが届く インストール また、上記以外のフローとして、すでに導入済みのユーザーからの招待を受ける方法もあります。 導入済みのユーザーの招待機能で招待リンクを発行してもらう 招待リンクからインストール ※ 公式から情報を辿りたい方向けの補足 執筆に際して、Arcの公式サイトを再訪してみたものの、全体を通して情報が少量かつ抽象的で、具体的に「何ができるWebブラウザなのか」がサイト内の情報からは掴みづらい印象でした。 Arcの具体的な機能に関しては、Arcをインストールした後、アプリ内のヘルプから説明ページにアクセスできる導線になっており、企業サイトに記載のある価値観と照らし合わせて考えると、「まずは感覚的に使ってみてね!」という方針での設計なのかもしれません。 (私も説明を流し目に待機リストに飛び込んだ一人ですが、実際に使ってみて困ることは少なかった気がします) また、 YouTube にて公式が公開している解説動画も豊富にあるので、使うより先に情報が欲しい方は本ブログと共にそちらもチェックしてみるのをオススメします。 特徴 Arcが他のWebブラウザと異なる点を「機能」と「実装の方向性」の2つの観点で挙げてみます。 機能 1. タブ/ブックマークの管理 Arc利用時の全体画面 Arcを利用する際、アプリのコントロールは基本的に上図左のサイドバーから行います。 サイドバーについて サイドバーは大きく3つのセクションに分かれています。 各セクションはざっくり以下のような使い分けができます。 ①: Favorites よく使うタブを置いておくセクションです (噂によるとSpotify等特定のアプリを置いておくと連動していい感じのアニメーションが出たりするそう) ②: Pinned tab Favoritesほどではないがよく使うなというタブを置いておくセクションです (Chromeで言うブックマークにはこのセクションが一番近そうです) ③: Today tab Temporaryなタブが置かれるセクションです (デフォルトの設定だと、12時間が経過するとこのセクションの内容はリセットされます) ①と②の特徴として、これらはブックマークでありながらタブのようにも機能します。 最初にアプリを開くときにはブックマークとして機能し、その後はサイドバー上の同位置に固定されたアプリのタブとして使えます。 これにより特定アプリのタブへの出戻りがしやすく、同アプリのタブが複数乱立するような状況が生まれづらくなっています。 これはChromeのブックマーク機能と大きく異なる点だと感じました。 ①と②の使い分けが曖昧ですが、本記事では紹介しないSpace機能利用時の差分など、他にも若干の差分があるはずです。 が、私もまだ使い分け方を模索中なのと、正しいことが言える自信がないので曖昧のままに留めます。 個人的には③のタブが一定時間でリセットされる仕様も気に入ってます。 仕事を終えて次の日の朝、昨日のタブの残骸に脅かされる心配がなく快適です! 2. Note / Easel機能 Note機能 Noteはマークダウン形式で書けるメモ機能です。 ちょっとしたメモに別アプリを起動しなくても良いのは便利かもしれません。 Easel機能 Easelは画像を編集したり、ダッシュボードを作ることのできる機能です。 ダッシュボードとしての使い方は後ほどご紹介します! 3. Boosts機能 Boosts機能 Boostsは任意のWebサイトにカスタムCSS,JSを適用できる機能です。 (特定のサイトor全サイトで適用のスコープを選択できます) エンジニア心をくすぐられる機能ですし、Chrome拡張のように作成 -> 申請のような過程を踏む必要なく、サクッと作ったスクリプトを適用できるのは嬉しいですね。 ※ 中身がChromiumなのでChrome拡張も利用可能です。 Arcの機能に見えるような何かを自分の環境にだけ配置してオレオレArcを作ってみたりも楽しそうです。 実装の方向性 The Browser Company CEO(Josh Miller)への インタビュー記事 を読むと、以下の1点においてArcの特徴的な実装の方向性がうかがえます。 WebのOS的な立ち位置のアプリ Arcはまさにこれを目指しているのです! Meta-OS Layer の新しいパラダイムを、グローバルノートのようなメタレイヤーのツールで実現することです。 ネイティブアプリケーションからブラウザベースのアプリケーションにワークフローを自然に移行させるような体験を提供するため、Meta-OS Layerの下にすべてのアプリケーションを少しずつ統合しているのだそうです。 (DeepLによる翻訳) ※ 引用: https://www.makingproductsense.com/i/67773152/enter-arc 上記の通り、ArcはWebのOS的な立ち位置を目指しているそうです。 Note, Easelのような機能も本来OSにも搭載されている機能に思えますし、書類や画像などPCのストレージに保存されているコンテンツへのアクセスがFinderを開かずにArc上からできるのもWebのOSを目指しての機能なのかも知れません。 Arc上で直アクセス可能 また、Arcはアプリをブラウザベースで管理するApp Launcherのようにも利用しやすく、その点もWebのOS的な思想が反映されていると言えそうです。 例えばChromeをお使いのみなさんは、一度は以下のような経験をされたことがあるのではないでしょうか? アプリをChromeでブックマークから開いて使用 アプリを離れて別の作業をする しばらくして、さっき開いていたアプリを開きたくなる タブが乱立しておりどれが目当てのアプリタブか分からなくなる "機能"セクションでも紹介したとおり、Arcはタブとブックマークの管理方法に特徴があり、1度開いたアプリタブを見失いづらい仕様になっています。 Arcを使うと以下のような体験に改善されそうです。 アプリをArcのFavoriteから開いて使用 アプリを離れて別の作業をする しばらくして、さっき開いていたアプリを開きたくなる Favoriteからアプリを開く (もう迷わない!) 個人的に、これまではなるべくデスクトップアプリを入れるようにしており、その理由は上記のタブ問題が主でした。 タブ問題の改善されたブラウザは、アプリウィンドウの数も増えづらく、各アプリへのアクセスもしやすいため、App Launcherに向いているなと感じました。 実際私は、Arcを使い始めてから2ヶ月ほどが経過した今、これまでデスクトップから開いていたほとんどのアプリにはWebブラウザ(Arc)からアクセスするようになりました。 推し機能 最後に、Arcの個人的な推し機能を1つご紹介します。 Note / Easelのキャプチャ機能 「Easelはダッシュボードとして使える」と先述しましたが、このキャプチャ機能を利用することでダッシュボード化が可能です。 キャプチャ機能紹介 上図は私物の「朝ボード」ですが、配置されている各コンテンツはキャプチャ機能を利用したもので、特定のWebページの特定の位置のコンテンツが投影されているような状態になっています。 (正確にはコンテンツをホバーすると同期ボタンが表示され、実行すると最新の状態になる仕様) 色んなサイトから手軽に情報を集めて自分用のダッシュボードを作れるのは相当便利です。 また、作成したEaselはWebに公開可能なので、複数人での共用ダッシュボードとしても利用できそうです。 この機能について興味本位で少し実装を探ってみると、キャプチャされたコンテンツはHTML上ではimgタグの要素となっていました。 Webページとページ内での位置情報をArc内部で持ちコンテンツと紐付けておき、同期の実行時に最新のキャプチャを撮影して置き換えるような実装なのかもしれません。 (同期はArc上でのみ可能かつArc上でEaselに対してはChromeのDevツールが禁止だったため、同期処理の内容についてはあくまでも想像です) イチオシの機能なので、Arcを使われている/今後使われる方はぜひ試してみてください! まとめ いかがでしたでしょうか? 簡単にですが、WebのOSを目指す新しい体験のWebブラウザ Arc についてご紹介しました。 本記事では触れていないArcの魅力も多分にあると思うので、記事を通して少しでも興味を持たれた方はぜひ一度使ってみてください。 個人的に用法をユーザーで拡張できるようなツールが好きなので、Arcはツボでした。 直近にも高頻度でアップデートが来ており、今後リリースされる機能も楽しみです。 (betaでこのクオリティ...!と驚くばかりです) 最後までお読みくださりありがとうございました。それではまた! 参考サイト https://thebrowser.company/ https://arc.net/ https://www.youtube.com/@TheBrowserCompany/featured https://www.makingproductsense.com/p/why-everyone-is-losing-their-minds https://www.makingproductsense.com/p/the-future-of-arc-with-josh-miller
アバター
挨拶 こんにちは!私はBOXIL SaaS開発エンジニアのハヤシ(ぱずー)です。 前回、私がスマートキャンプで成長したエピソードを紹介しましたが、今回はエンジニア採用サイトのリニューアルに携わったので、それについて紹介します。 最後まで読んでいただけると嬉しいです! 今回、リニューアルした採用サイトです。こちらも見ていただけると嬉しいです! https://engineer.smartcamp.co.jp/ 想定読者 これから採用サイトをリニューアルしたい人 エンジニアでスマートキャンプに興味がある人 目次 挨拶 想定読者 目次 TL;DR なぜ採用サイトをリニューアルしたか どう実装したか 開発にあたっての方針 1. モダンな技術を使う 2. 独自実装を極力避ける 3. シンプルな構成を目指す 4. 最初から完璧を目指さない 技術構成 スタイリングにCSS in JSではなくCSS Modulesを使用した理由 実装で工夫したところ 作業しやすいディレクトリ構成にする メリット デメリット コンポーネントのテンプレートを作成するスクリプトを組んで開発効率を上げる next-seoを使ってSEO対策でラクをする headタグ内にAAを置く ハマったところ アニメーションなんもわからん CSSを極力使わない とにかく実装イメージをつける 最下部にスクロールするときに画面がチラついてしまう 開発してみての感想 最後に TL;DR アニメーションの実装経験があまりなかったため、なんもわからん状態だったけど気合いで乗り切った 採用サイトをリニューアルすることでエンジニア組織の方向性が定まった。 なぜ採用サイトをリニューアルしたか 主に以下の理由で採用サイトがリニューアルするモチベーションになったと認識しています。 エンジニア採用サイトの更新が2020年で止まっており、単純に情報を更新したい テクノロジーを活用する姿勢を求職者に見せたい また、リニューアルにあたっての想いを企画担当者からいただきましたので、掲載します。 スマートキャンプはIPOを目指すことになり、そのためにもよりテクノロジーを活用していく必要性があります。 そこで私たちの思いに共感し、それを推進したいと言っていただけるようなエンジニアを広く募りたくリニューアルを開始しました。 これから弊社に必要なのは、AI技術の進歩を始めテクノロジーが劇的に進歩している中で、経験したことのない未知の領域でもおもしろさを感じ飛び込んでいく力がある人だと考えています。 なので採用ページを見ていただいた方に「スマートキャンプがテクノロジーに投資をする意思があること」と「ワクワクやおもしろさを大切にしていること」が伝えたいということでなかなかない趣向にしてほしいとデザインも依頼し、自分たちエンジニアで開発・運用していくことに決めました。 ※IPOを目指すことについての想いは以下の記事をご覧ください。 https://note.com/smartcamp_tent/n/nc9ad8887a5af どう実装したか さまざまな想いが詰まった採用サイトを実装することになり、実装を担当する自分も技術的チャレンジしながら開発できる!とワクワクしていました。 企画担当者から「斬新なデザインを希望」とのメッセージ通りに、今まで見たことのないようなデザインが上がってきました。 ↑一番最初にデザインを見たときのslack上でのやりとり。 複雑なアニメーションだったので、実装しなくて済む方法も模索する道はありましたが、せっかくならばチャレンジしてみようと自力で実装することを決めました。 今まで凝ったアニメーション作成の経験はなかったので、かなりチャレンジングな開発でしたが、その中で何を考えて実装したのかをご紹介します。 開発にあたっての方針 技術選定は以下を念頭において進めていきました。 1. モダンな技術を使う 完成させることだけ目的であれば、HTML/CSS、素のJavaScript(jQuery)などでも実装は可能でしたが、今回は技術的にチャレンジしたいという思いもあり、モダンな技術を用いて実装することに決めました。 2. 独自実装を極力避ける ライブラリを最大限活用して実装する方針にしました。 3. シンプルな構成を目指す 今後、チームで採用サイトを運用していくにあたり、JavaScriptに不慣れなメンバーがコードに触れる可能性があるため、極力わかりやすい技術を採用することを目指しました。 4. 最初から完璧を目指さない 最初から完璧な状態でリリースすることはせず、まずはリリースを先行させて、コードのリファクタは徐々に行なっていく方針にしました。 技術構成 上記を念頭に以下のような技術を使うことにしました。 フレームワーク React.js (Next.js v13 + SG) 理由:今までフロントエンドはVue.jsで書くことが多かった弊社でも、最近のプロジェクトでは、React.jsを使用した実績が増えてきており、知見が溜まっていたため、React.jsを採用しました。 TypesScript 理由:言わずもがなですね。 状態管理 jotai 理由: 特に理由はないですが、状態管理には jotai を採用しました。 Recoil とAPIが似ているのですが、更にシンプルにしたような感じで特に不満なく使えています。 スタイリング CSS Modules + scss 理由: 詳しく書きたいので この項 で説明します。 アニメーション framer-motion イイ感じのスクロールはframer-motionをラップしたパッケージであるscroller-motionを使用。 理由: 最初はcssだけで作ろうと思ったのですが、全く自信がなかったのもあり、良い感じにアニメーションを作れるライブラリを探していたところ、framer-motionを見つけ採用しました。 例えば、アニメーションを使用して要素を表示・非表示したい場合は以下のようにすることで簡単に実現できます。 <motion.div initial= {{ opacity: 0 }} // 初期表示は要素は非表示 animate= {{ opacity: 1 }} // アニメーションすると要素が表示される exit= {{ opacity: 0 }} //要素が非表示になるときに要素を見えなくする /> ホスティング Cloudflare Pages 弊社のドメイン管理はCloudflareを使用しており、親和性があると感じたため。 また、Node.jsv18は執筆時点では未対応だったため、v17.9.1で動かしてます。 GitHubと簡単に連携できて、Preview環境をすぐに作れるので成果物をスムーズにチェックしてもらえたのでかなり良かったです。 スタイリングにCSS in JSではなくCSS Modulesを使用した理由 CSS Modulesを採用した主な理由は以下になります。 チームで採用サイトを更新していくことを考えると、JavaScriptを詳しく知らなくてもスタイリングできるほうがよさそう。 結局、CSS in JSは、CSSで実現できる以上のことはできないため、あえて採用するモチベーションがないと個人的に感じた。 Tailwind CSSも検討したが、独自に書き方を覚える必要があるので、今回は避けた。 また、Next.jsはCSS Modulesをビルトインサポートしていて、特別な設定が不要なため、そのこともCSS Modulesを選択した理由となりました。 CSS Modulesについては、Next.js v13でも継続してサポートしていることから、意外と今後も使われ続けるのではないかと思っています。 実装で工夫したところ 作業しやすいディレクトリ構成にする フロントエンド開発では一般的にAtomic Designなどを用いてディレクトリを構成することが多いと思いますが、コンポーネントを分類する手間が個人的に負担だと感じていたため、今回はAtomic Designの採用を見送りました。 そこで、ページ固有のものか、共通のものかを明確に分類できるようなディレクトリ構造を採用することに決定しました。 以下のような構成にして、作業しやすい構成を目指しました。 app/ ├── assets/ # 画像、フォント、アイコンなどのアセットを格納する ├── components/ # 共通で使用するコンポーネントを格納する ├── constants/ # 全ページ共通で使用する定数を格納する ├── features/ # ページや特定の機能に関するコンポーネントやhooksを格納する │ └── [page名] │ ├── components/ # 特定のページでのみ使うコンポーネントを格納する │ ├── hooks/ # 特定のページでのみ使うhooksを格納する │ ├── constants/ # 特定のページ単位でのみ使う定数を格納する │ ├── index.tsx # エントリーポイント │ └── style.module.scss # index.tsxで使用するスタイルファイル ├── hooks/ # 全ページ共通で使用するhooksを格納する ├── pages/ # 各ページを表すcomponentを格納する ├── utils/ # すべてのユーティリティ関数を格納する └── styles/ # すべてのCSSを格納する ポイントとしては、featuresというディレクトリを作ってページごとにコンポーネントやhooksを配置したことです。 こうすることで、どこでそのコンポーネントが使われるかが明確になり、コードの見通しが良くなることを期待しました。 実際にこの構成にしてみて、以下のようなメリットがありました。 メリット コンポーネントを分ける負担の軽減 Atomic DesignであったMoleculesやOrganismsとかの括りがなくなったので、確実にコンポーネントを分類する際の工数は減ったように思えます。 フォルダを行ったり来たりすることが少なくなった。 ページ単位でcomponentやhooksがまとまってて参照しやすくなり「あれこのhooksどこだっけな」みたいなのがなくなりました。 デメリット コンポーネントを分ける負担は相変わらず存在する Atomic Designよりは負担は減ったのですが、それが共通・ページ固有のコンポーネントなのかを考えながら作業しないといけないのは変わらず存在します。 ディレクトリ一式(componentsとかhooksとか)を都度作らないといけない また、featuresをどの粒度で切るかを考えるのはかなり重要だと思いました。 今回は採用サイト作成だったので、ページ単位でfeaturesディレクトリを作成しましたが、業務アプリケーションの開発では、同じようなコンポーネントが複数のページで使用される可能性があります。 その場合はページ単位ではなく、ドメイン単位(ユーザーとか)でのfeaturesディレクトリを分割した方がより適切な構成になると思っています。 コンポーネントのテンプレートを作成するスクリプトを組んで開発効率を上げる 上記のディレクトリ構成に従って、コンポーネントを量産していく際に、対応するindex.tsxとstyle.module.scssを自動生成できるスクリプトを作成することで、効率化を図りました。 import fs from 'fs' ; function capitalizeFirstLetter ( string : string ) { return string .charAt ( 0 ) .toUpperCase () + string .slice ( 1 ); } function lowerCaseFirstLetter ( string : string ) { return string .charAt ( 0 ) .toLowerCase () + string .slice ( 1 ); } const component = ( name: string ) => `import style from './style.module.scss'; export const ${ capitalizeFirstLetter(name) } = () => { return (<div className={style. ${ lowerCaseFirstLetter(name) } }></div>) } ` ; const style = ( name: string ) => `. ${ lowerCaseFirstLetter(name) } { } ` ; const handler = () => { const filePath = process .argv [ 2 ] const componentPath = ` ${process .cwd() } /src/ ${ filePath } ` ; const fileName = filePath.split ( "/" ) .slice ( -1 ) [ 0 ] // フォルダがない場合は再帰的に作成する if ( ! fs.existsSync ( componentPath )) { fs.mkdirSync ( componentPath , { recursive: true } ); } fs.writeFileSync ( ` ${ componentPath } /index.tsx` , component ( fileName )); fs.writeFileSync ( ` ${ componentPath } /style.module.scss` , style ( fileName )); } ; handler (); npm run create:component components/Image npm scriptsに登録して、こんな感じで実行してあげることですぐにコンポーネントの雛形を作れるようになったので、意外と開発体験が上がってよかったです。 next-seoを使ってSEO対策でラクをする next-seoという素晴らしいライブラリを利用することで、複雑になりがちなSEO周りを簡単に設定できました。 やったことはシンプルで、プロジェクトルートにnext-seo.config.tsを作成し、必要な設定を記述するだけです。 今回は必要最低限の設定に留めましたが、ページ毎に使用する情報を変更するなど、他にももっと細かい設定があるので、詳しくはこちらをチェックしてください。 next-seo next-seo.config.ts const seoConfig = { // メタタイトル defaultTitle: "スマートキャンプ株式会社 エンジニア採用" , // メタディスクリプション description: "スマートキャンプ株式会社のエンジニア採用ページです。スマートキャンプはエンジニアが活躍して社会の非効率をなくすテックカンパニーを目指しており、そのための取り組みやチーム体制、採用情報などを公開しています。" , // faviconの設定 additionalLinkTags: [ { rel: "icon" , href: "favicon.ico" , } , ] , // OGPの設定 openGraph: { type : "website" , locale: "ja_JP" , url: "https://www.example.com/page" , site_name: "スマートキャンプ株式会社 エンジニア採用" , description: "スマートキャンプ株式会社のエンジニア採用ページです。スマートキャンプはエンジニアが活躍して社会の非効率をなくすテックカンパニーを目指しており、そのための取り組みやチーム体制、採用情報などを公開しています。" , images: [ { url: "/ogp.png" , width: 1200 , height: 630 , alt: "Og Image Alt" , } , ] , } , } ; export default seoConfig ; _app.tsx import { DefaultSeo } from 'next-seo' ; export default function App ( { Component , pageProps } : AppProps ) { const SeoContent = () => { return < DefaultSeo { ...seoConfig } / > } return ( < Provider > < SeoContent/ > < Component { ...pageProps } / > < /Provider > ) } headタグ内にAAを置く エンジニア採用サイトらしいことをやりたいなと思い、headタグ内にアスキーアートを設置することにしました。 これも↓の感じで簡単にできたので、やってみると面白いかもしれません。 const logo = `<!-- .=+: -**##+. :+*##*=: :**+ :**+ :. .******+= -********* :+*##*=. - +**: ***. .******+: -****####=. =##*==###- :###= *##+ *= .##*===###.:==+##*=== =##*==###. -*. +##* =###. .##*==+##= -******######= ##* :##+ :####: +###+ -**. .##+ -##= :##+ *## -##- .**= +###= :####. .##+ +## :+*******########- ###. :####* -####+ .***+ .##+ :##= :##+ *## =***. +####: .#####. .##+ +##. :+*********##########- :###*+-. :##+*#=.##+##+ =****: .##*---*##. :##+ *## .****+ +##+#* +#**##. .##*..:*#* .+****###****###########*: :-+*##*. :##+:##*#*.##+ :****** .###*###= :##+ *## +*****: +##.*#*##:+##. :#######+. .=*****####****#############+: -##= :##+ +###:.##+ +**+***- .##+ .##+ :##+ *## :***+*** +##.:###= +##. :##+::. .-+**######****############*=: .++= :##+ :##+ .##= .##+ :***.-*** .##+ -##- :##+ *## -++: ***=.***- *##. +#* +##. :##+ ::::::*****#########+-. *##:.:+##- :##+ :- .##+ ***= ***= .##+ *##. :##+ +##-.:*##: -***. -*** +##. .-. +##. :##+ +****#####*=: .+#####*= :##+ .##+ -***. :***..##+ :##+ :##+ .+#####*: .***- ***= +##. +##. :##+ :=+*#*+- .::. ... ... .... .... ... ... ... .::. ... .... ... ... ... :- -->` const RawHtml = ( { html = "" } ) => ( < script dangerouslySetInnerHTML = {{ __html: `</script> ${ html } <script>` }} / > ); //_app.tsx内のHeadに入れてあげる < Head > < RawHtml html = { logo } / > < /Head > こんな感じで表示されます。 ハマったところ アニメーションなんもわからん アニメーションを作った経験がほとんどなかったため、どうやって実装したらいいのか全くわかりませんでした。 そこで以下の二つの方針で実装を進めることにしました。 CSSを極力使わない とにかく実装イメージをつける CSSを極力使わない CSSでアニメーションの実装を調べていたら時間がかかるので、ライブラリに頼りまくって実装する方向に決めました。 今回は、framer-motionを使用しましたが、CSSをあまり書かなくていいのと、コンポーネントにどうアニメーションするかの設定を書くことができるので、直感的に使うことができたので、作業が非常に捗りました。 とにかく実装イメージをつける 幸いにもアニメーションのイメージ動画は頂いていたので、まずとにかくアニメーションを繰り返し見て、流れを整理してから実装していきました。 以下のような整理をすると、実際にコードに落とし込んだときも、どの部分の実装しているのが明確になり、スムーズに進めることができました。 画面が表示される タイトルが落ちる タイトルと背景がぶつかる ぶつかった衝撃でタイトルが弾むと同時に背景が傾く 傾斜によってタイトルが滑る マウスを上にスクロールすると背景が上昇し、傾斜が逆になり、同時にタイトルが上に吹き飛ぶ かっこいいメッセージが表示される 特に難しかったのが、複数の要素を同時に動かして連動しているように見せることです。 ②では、タイトルをバウンドさせながら傾斜を滑らせる処理がはいるのですが、ここにかなり苦戦しました。(なーんだ簡単じゃんと思われた方は、ぜひ自分の師匠になってください...!) 具体的な例を挙げると、framer-motionにはuseAnimationControlsというhooksが提供されていて、任意のタイミングでアニメーションを発火させることができます。 そのオプションにどれぐらい要素を落とすかを設定する値をハードコードするなど、かなり強引な実装で回避しました。 titleControl.start ( { x: "calc(50% - 50vw)" , y: [ null , y - animationOptions.bounce , y , y , y ] , // animationOptionsはディスプレイに応じてハードコードしてしまっている rotate: [ null , 0 , animationOptions.rotate ] , transition: { duration: 1.4 , delay: 3 , ease: [ 1 , 1.6 , 0.28 , 0.71 ] , x: { delay: 3 , duration: 0.7 } , rotate: { delay: 2.65 , duration: 1 , } , } , } ); 本当は、どのディスプレイでも同じ表示になるようにしたかったのですが、複雑な計算が必要になりそう & 実装期間との兼ね合いもあり、今回は見送りました。(すぐ分かる方いたら教えて欲しいです...!お待ちしています。) 最下部にスクロールするときに画面がチラついてしまう 特徴としてトップページは最下部から上に向かってスクロールするような仕組みになっています。 そのため、画面ロード時にJavaScriptを使用して最下部まで移動する必要があったのですが、どうしても一瞬移動前のパーツが見えてしまう問題がありました。 そこで、ローディング画面を2秒間見せ、その間に最下部に移動することで違和感を与えないように変更しました。 開発してみての感想 複雑なアニメーションを実装したのは初めてだったので、試行錯誤しながらどう実装していくかを考えるのは非常に勉強になり、純粋に楽しかったです。一部強引な実装をしてしまった部分があったので、そこは反省しつつも、全体的には技術的なチャレンジもできたので、良いものが出来上がったのかなと思っています。 ただ、作って終わりではないので、リファクタを継続しながら、もっともっとエンジニアドリブンで開発していける組織を目指していきたいなと開発してみて思いました。 ちなみに↓のセクションでは、表示するメッセージを各メンバーに考えてもらったのですが、皆が向かっていきたい方向やビジョンが垣間見えて凄くお気に入りです。 最後に ここまで読んでくださってありがとうございました! 今回は、更新が止まっていた弊社の採用サイトをチームで更新していくことを考えつつリニューアルしました。 複雑なアニメーションを実現が特に難しかったですが、試行錯誤した結果、 斬新なデザイン を実現できました。 そして、採用サイトをリニューアルする意義とは何かを考えてみると、あらためて会社の方向性、会社の制度、伝えたいメッセージを考えるきっかけになって、組織の方向性がより明確に定まることなのかなと思いました。 弊社はMISSONに「テクノロジーで社会の非効率を無くす」を掲げていますが、 そういった意味合いでもエンジニアが中心となって採用サイトをリニューアルするという取り組みに携われたことは非常に良かったと個人的には思いました! これからもその想いの実現に向けて頑張っていきたいと思います! 今後は以下のアップデートを予定しているので、引き続きチェックしていただけると嬉しいです。 新たに対談ページを追加 アニメーションを追加してもっとリッチにする 新メンバーの画像とメッセージを追加する
アバター
概要 スマートキャンプでエンジニアをしている佐々木です。 本記事では、自然言語処理モデルを用いて新規サービスを作れないか試行錯誤した話をしようと思います。 今回は精度の良い検索はうまく実装できませんでしたが、機械学習モデルをインフラで動かす流れは学ぶことができました。 実際に実装したコード例ともに紹介します。 概要 経緯 検索の仕組み MLモデルのトレンド 採用した文章の類似度計算のアルゴリズム 類似度計算モデルのデプロイ 実際のコードの紹介 モデルの取得とデプロイ 必要なライブラリのimport 事前学習済みモデルの取得 モデルの保存とS3へのアップロード エンドポイントへのモデルのデプロイ 通常のインスタンスの場合 サーバーレスの場合 エンドポイントの動作確認 エンドポイントでの推論処理 ライブラリのimport model_fn input_fn predict_fn output_fn 学び SageMaker(ML)に何でも押し付けない 料金 最後に 経緯 スマートキャンプでは毎年プロダクトチーム合宿をしています。( 前回の様子 ) その機会を活用し、普段の業務ではあまり触れない技術の探索をしました。 その中で、自然言語処理モデルを用いた検索システムの開発を試みた話をします。 なぜ、このシステム開発をすることにしたのか、理由は2つあります。 最新の自然言語処理(NLP)の動向にキャッチアップするきっかけが欲しかった 個人で実験するには、学習にお金がかかるので試行錯誤しにくい 1について、まず、個人的に学習したい分野であったことが大きいです。また、実務でも利用できる段階にあるという話は聞いていたので、会社としてもNLP周りの知見があれば使い所はありそうだと感じました。 2について、MLを行なううえでネックになるのが大体計算リソースとデータ量ですが、その片方のコストがこの機会なら浮くと思ったことが理由です(膨大なコストがかかるのはNGですが、ある程度はOKです)。 検索の仕組み 通常、検索エンジンを構築するには次のような手順を踏む必要があります。 インデックスを作成する: Webサイトから文章を抽出し、検索用のインデックスを作成する クローラを作成する: インターネット上のさまざまなWebサイトを巡回し、新しいコンテンツを収集するプログラムを作成する ランキングアルゴリズムを実装する: インデックスを元に、検索クエリに対して適切な結果を返すためのアルゴリズムを実装する 今回は、1の検索用のインデックスに関しては、slackなどのチャットツールやKibelaなどの社内ドキュメントツールから作成しました。 また、2のクローラは作成せず、定期的にAPIを介して定期的に検索対象の文章を取得するバッチ処理を作成しました。 今回、主に話したいのは3のランキングアルゴリズムについてです。 検索クエリと検索対象の文章の類似度を計算することで、検索クエリに対する適切な結果を含む文章を探し出すことができます。 そして、その2文章間の類似度計算の前段階としてSentenceBERTの日本語事前学習モデルを用いました。 ここについて次から詳しく説明します。 MLモデルのトレンド 自然言語処理(NLP)の分野では、BERTやGPTといったTransformerモデルがここ数年のトレンドとなっています。 先々月にはOpenAIがChatGPTを発表し、弊社でもよくこういう会話をしてみたという投稿が盛んに行われていました。(私は現在でもこちらを活用しています) こちらもTransformerモデルであるGPTを改良したものであることが知られています。 このようなモデルを1から作成するには、膨大なデータの用意と豊富な計算資源と運用費が必要になるため、資金力に余裕がある一握りの企業や大学でしか行なうことができません。 しかし、それらの組織が作成し公開してくれた事前学習モデルをfine-tuningすることによって、個別のタスクに応用し、資源の少ない組織でも扱うことができます。 採用した文章の類似度計算のアルゴリズム その中でもSentenceBERTを用いることが文章の類似度を計算するのに適しています。 SentenceBERTは、文章を入力として受け取り、その文章を特徴量ベクトルに変換するTransformerモデルです。これは先のBERTと呼ばれる大規模な言語モデルを拡張して作られており、文章のsemantics(意味的な情報)を正確に捉えることができます。 このように文章を「意味的な情報を含んだ特徴量ベクトル」に変換するというのが肝で、このベクトルがどのくらい似ているか調べることにより文章の類似度計算ができます。それ以外にも文章のタグ付けや、文章の分類なども行えます。 よく使われる2つのベクトル間の類似度計算にコサイン類似度というものがあります。 2つのベクトルが完全一致する場合、1になり、完全に異なる場合に-1になります。 具体的には、次の式を使います。 ここで、 、 は2つのベクトルを表します。 は内積を表す記号です。 は、ベクトル の大きさを表します。 も同様です。 すなわち類似度計算の流れは次のようになります。 検索対象の文章を「意味的な情報を含んだ特徴量ベクトル」に変換する 検索クエリを「意味的な情報を含んだ特徴量ベクトル」に変換する 2つのベクトルの類似度を計算する それらを類似度が高いもの順に並べる こちらを元に、検索におけるランキングアルゴリズムを実装し、デプロイしてみました。 次にデプロイについて話します。 類似度計算モデルのデプロイ デプロイには、Amazon SageMakerを用いました。Amazon SageMakerはAmazon Web Services(AWS)が提供する機械学習を手軽に行えるサービスです。機械学習では統計的な分析や予測モデルの構築、訓練、デプロイなどさまざまな作業が必要になりますが、それらをすべて管理できます。 Amazon SageMakerにはユースケースに応じていくつかに機能が分かれています。例えば、ビジネスアナリスト用のSageMaker Canvasというノーコードインターフェースや、機械学習エンジニア用のSageMaker MLOpsといった機能があります。その中で、今回はAmazon SageMaker Studioというデータサイエンティスト向けのIDEを活用しました。 こちらを用いることで、データの前処理からGBDT系のモデルやPyTorchやTensorFlowを用いたディープラーニング系のモデルの開発からデプロイまで一気通貫して行えます。 今回はその中でも次の機能を利用しました。 Notebookインスタンス Jupyter NotebookやJupyter Labが活用できます。 エンドポイント エンドポイント用のMLインスタンスです。作成したMLモデルをこちらへデプロイしAPIとして推論結果を取得できます。 1について、データ前処理や、モデルの保存や、推論用のインスタンスを作成するのに用いました。こちらからAmazon S3バケットを作成したり、作成したMLエンドポイントへのアクセスのデモを行なうこともできます。 2について、今回は、推論結果として、文章の類似度ランキング情報を取得できるようにしました。(※あとで詳しく紹介しますが、検索として、MLモデルの中で類似度計算をするのは速度の面で好ましくありません。) また、エンドポイント機能には、サーバーレスモードと常駐モードがあります。サーバーレスモードでは、エンドポイントへのアクセスに応じて推論用インスタンスが立てられるため、料金を安く抑えることができます。そのため、開発段階では、サーバーレスモードを活用し、デモとして行なう際には常駐インスタンスを用いました。 このように、SageMakerではデータの前処理、可視化などを行なうインスタンスと、学習インスタンス、そして、推論をするインスタンスの3つを必要に応じて扱えるようになっています。 すなわち、「学習の際は、GPUを積んだ高性能なインスタンスを短時間使用し、推論の際は、サーバーレス、もしくは低予算のインスタンスを起動させ、開発用のノートブックインスタンスは必要なときのみ起動する」のように使い分けられます。 用いた構成例 実際のコードの紹介 モデルの取得とデプロイ 実際には、学習を挟む必要がありますが、デプロイを行なうだけなら次のようにできます。 必要なライブラリのimport # import libraries import boto3, re, sys, math, json, os, sagemaker, urllib.request from sagemaker import get_execution_role import numpy as np import pandas as pd import matplotlib.pyplot as plt from IPython.display import Image from IPython.display import display from tqdm import tqdm !pip install transformers[torch] sentencepiece !pip install fugashi ipadic from transformers import BertJapaneseTokenizer, BertModel import torch 事前学習済みモデルの取得 MODEL_NAME = "sonoisa/sentence-bert-base-ja-mean-tokens-v2" 今回使用させていただいたモデルはこちらで詳しく紹介されています。 【日本語モデル付き】2020年に自然言語処理をする人にお勧めしたい文ベクトルモデル tokenizer = BertJapaneseTokenizer.from_pretrained(MODEL_NAME) model = BertModel.from_pretrained(MODEL_NAME) モデルの保存とS3へのアップロード SAVED_MODEL_DIR = 'transformer' os.makedirs(SAVED_MODEL_DIR, exist_ok= True ) tokenizer.save_pretrained(SAVED_MODEL_DIR) model.save_pretrained(SAVED_MODEL_DIR) #zip the model in tar.gz format !cd transformer && tar czvf ../model.tar.gz * sagemaker_session = sagemaker.Session() role = get_execution_role() #Upload the model to S3 inputs = sagemaker_session.upload_data(path= 'model.tar.gz' , key_prefix= 'model' ) inputs ※ このinputsの値にはS3上のモデルへのパスが入っています[1]。 エンドポイントへのモデルのデプロイ from sagemaker.pytorch import PyTorch, PyTorchModel from sagemaker.predictor import Predictor class StringPredictor (Predictor): def __init__ (self, endpoint_name, sagemaker_session): super (StringPredictor, self).__init__(endpoint_name, sagemaker_session, content_type= 'text/plain' ) こちらは、エンドポイントへの入力を単一の文字列にしたかったため作成しました。デフォルトではエンドポイントへ渡すデータは、特徴量が入った配列です。 pytorch_model = PyTorchModel( model_data = 's3://xxxxx/model/model.tar.gz' , role=role, entry_point = 'inference.py' , source_dir = './code' , py_version = 'py3' , framework_version = '1.7.1' , predictor_cls=StringPredictor ) ※ model_data へは[1]の値を入れてください。 ※ inference.py はデプロイされたエンドポイントへのアクセス時に実行される関数が集まったファイルです。のちに紹介します[2]。 通常のインスタンスの場合 インスタンスタイプは こちら から適宜必要なものを選択してください。 アクセスが多いと自動でスケールしてくれるようです。 predictor = pytorch_model.deploy( instance_type= 'xxx' , initial_instance_count= 1 , endpoint_name = 'ml-endpoint' , ) 注意点として、デプロイに応じて最低1時間分の料金が請求されるので注意してください。(高額インスタンスで無闇矢鱈にデプロイして削除するを繰り返すと、実際の稼働時間が少なくてもたくさんの請求がきます。) なので、開発時は次のサーバーレスでのデプロイが特におすすめです。 サーバーレスの場合 from sagemaker.serverless import ServerlessInferenceConfig serverless_config = ServerlessInferenceConfig( memory_size_in_mb= 5120 , max_concurrency= 5 ) predictor = pytorch_model.deploy( serverless_inference_config=serverless_config, initial_instance_count= 1 , endpoint_name = 'ml-endpoint' ) エンドポイントの動作確認 実際にはLambdaからエンドポイントを叩いてレスポンスを取得していますが、ノートブックインスタンスから同様にエンドポイントを叩く際のコードを紹介します。 import json import boto3 client = boto3.client( 'sagemaker-runtime' ) ENDPOINT_NAME = 'ml-endpoint' payload = "検索Query" response = client.invoke_endpoint( EndpointName=ENDPOINT_NAME, ContentType= 'text/plain' , Accept= 'application/json' , Body=payload ) result = json.loads(response[ 'Body' ].read().decode()) print (result) [{'articles': [{'score': 0.5378279958997847, 'note_id': 'xxxxxxxxxxx', 'title': '記事のタイトル', 'text': '記事本文', 'timestamp': '2022-04-15T09:45:35.633Z'},...], 'score_sum': 1.6076672689827232, 'profile': {'username': '執筆者の名前', 'dm_url': 'slackのDMのURL', 'role': '役職', 'division': '部署'}, 'slack_id': 'xxxxxxxxx', 'score_ave': 0.5358890896609078},... ] ※ 実は今回は単純な検索ではなくて、関連する記事をたくさん書いている人をレコメンドするシステムにしているので、1レコードに対して複数の記事が紐づくような構成になっています。 エンドポイントでの推論処理 エンドポイントへのモデルのデプロイ の[2]で触れた inference.py について説明します。 こちらのファイルでは少なくとも次の関数が必要です。 model_fn(model_dir)—モデルを Amazon SageMaker PyTorch モデルサーバーに読み込みます。 input_fn(request_body,request_content_type)—データを推論のオブジェクトに逆シリアル化します。 predict_fn(input_object,model)—逆シリアル化されたデータは、読み込まれたモデルに対して推論を実行します。 output_fn(prediction,content_type)—応答コンテンツタイプに従って推論をシリアル化します。 引用元: Amazon SageMaker を使用した fastai モデルの構築、トレーニング、およびデプロイ 実装例を紹介します。 ライブラリのimport import argparse import logging import sagemaker_containers import requests import boto3 import os import io import time from shutil import unpack_archive from collections import defaultdict import simplejson as json from scipy.spatial import distance import pandas as pd from tqdm import tqdm import torch from transformers import BertJapaneseTokenizer, BertModel logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) model_fn def model_fn (model_dir): logger.info( 'START model_fn' ) device = torch.device( "cuda" if torch.cuda.is_available() else "cpu" ) tokenizer = BertJapaneseTokenizer.from_pretrained(model_dir) nlp_model = BertModel.from_pretrained(model_dir) nlp_model.eval() nlp_model.to(device) model = { 'model' : nlp_model, 'tokenizer' : tokenizer} logger.info( 'END model_fn' ) return model input_fn def input_fn (request_body, content_type= 'text/plain' ): logger.info( 'START input_fn' ) try : data = [request_body.decode( 'utf-8' )] return data except : raise Exception ( 'Requested unsupported ContentType in content_type: {}' .format(content_type)) predict_fn こちらの predict_fn 内でやっていることだけざっくり紹介すると、下記の流れになります。 S3から検索対象の文章の意味ベクトルをダウンロード・解凍 検索クエリとの意味ベクトルのコサイン類似度を計算 ランキング形式に整形 また、この部分に関しては実際に用いたものではなく、単純に関連度順の記事が10件得られるように改変してます。 def mean_pooling (model_output, attention_mask): token_embeddings = model_output[ 0 ] # last_hidden_state input_mask_expanded = attention_mask.unsqueeze( - 1 ).expand(token_embeddings.size()).float() sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1 ) sum_mask = torch.clamp(input_mask_expanded.sum( 1 ), min = 1e-9 ) return sum_embeddings / sum_mask def embed_tformer (model, tokenizer, sentences): encoded_input = tokenizer( sentences, padding= "max_length" , truncation= True , max_length= 256 , return_tensors= 'pt' ) device = torch.device( "cuda" if torch.cuda.is_available() else "cpu" ) encoded_input.to(device) with torch.no_grad(): model_output = model(**encoded_input) sentence_embeddings = mean_pooling( model_output, encoded_input[ 'attention_mask' ]) return sentence_embeddings def get_result (df, query_embedding, sentence_embeddings_path): cols = [ df.note_id, df.text, ] result = search_documents(query_embedding, cols, sentence_embeddings_path, batch_size= 4 ) return result def search_documents (query_embedding, cols, sentence_embeddings_path, batch_size= 4 ): note_id, sentences = cols # メモリに負荷がかかるので、バッチごとに意味ベクトルを読み込み、 # 類似度計算した結果だけを配列に持っておきます s_scores = [] for idx, batch_path in tqdm( enumerate (sentence_embeddings_path)): sentence_embeddings = torch.load(batch_path).detach().numpy() # 検索クエリとそれぞれの文書に対するcos類似度を一度に計算 cos_sim = 1 - distance.cdist([query_embedding], sentence_embeddings, metric= "cosine" )[ 0 ] # ここの実装はあまり良くありません(参考程度に) s_scores.extend( zip (sentences[idx*batch_size:(idx+ 1 )*batch_size], cos_sim, range (idx*batch_size, (idx+ 1 )*batch_size))) sorted_score = sorted (s_scores, key= lambda x: x[ 1 ], reverse= True ) n_docs = note_id.shape[ 0 ] upper_10 = n_docs// 10 # 例としてスコアの高いもの10件を返すことにします ret_val = [] for s, score, i in sorted_score[:upper_10]: ret_dict = { 'score' : score, 'note_id' : note_id[i], } ret_val.append(ret_dict) return ret_val def read_csv (s3_client, bucket, path): s3_object = s3_client.get_object(Bucket=bucket, Key=path) csv_data = io.BytesIO(s3_object[ 'Body' ].read()) csv_data.seek( 0 ) df = pd.read_csv(csv_data, encoding= 'utf8' ) return def predict_fn (input_object, model): logger.info( 'START predict_fn' ) start_time = time.time() sentence_embeddings = embed_tformer(model[ 'model' ], model[ 'tokenizer' ], input_object) # 文章に紐づいた情報が入ったテーブルの取得 s3_client = boto3.client( 's3' ) data_bucket = 'bucket-name' supplementary_table_path = 'sagemaker/data/supplementary-table.csv' supplementary_table = read_csv(s3_client, data_bucket, supplementary_table_path) # S3から圧縮された検索対象の意味ベクトルが入ったフォルダを取得(後述しますが、これはアンチパターンだと思います) s3 = boto3.resource( 's3' ) bucket = s3.Bucket(data_bucket) semantic_tensor_gz_path = 'sagemaker/data/semantic_tensor.tar.gz' bucket.download_file(semantic_tensor_gz_path, '/tmp/semantic_tensor.tar.gz' ) unpack_archive(filename= '/tmp/semantic_tensor.tar.gz' , extract_dir= '/tmp/semantic_tensor' , format = 'gztar' ) # tmp以下に配置した検索対象文章の意味ベクトルファイルのパス sentence_embeddings_path = sorted ( map ( lambda x: os.path.join( '/tmp/semantic_tensor/tensor' , x), os.listdir( '/tmp/semantic_tensor/tensor' ))) responce = get_result(supplementary_table, sentence_embeddings[ 0 ].tolist(), sentence_embeddings_path) print ( "--- Inference time: %s seconds ---" % (time.time() - start_time)) return response output_fn def output_fn (prediction, accept= 'application/json' ): logger.info( 'START output_fn' ) logger.info(f "accept: {accept}" ) if accept == 'application/json' : output = json.dumps(prediction, ignore_nan= True ) return output raise Exception ( 'Requested unsupported ContentType in Accept: {}' .format(accept)) 学び SageMaker(ML)に何でも押し付けない これは、そもそも検索エンジンの仕組みを全くわかっていなかったというお恥ずかしい話でもあるのですが、今回のこの類似度検索の手法はbi-encoderといい、文章の意味ベクトルを推論結果として返し、その類似度の比較は別で行なうことによって、高速に検索ができるようになっています。 しかしながら、実装の時間も限られており、Amazon ElasticSearch KNN indexなどの別のアーキテクチャを導入し、類似度計算を他で行なう実装ができなかったため、推論内で類似度検索を無理くり実装するという愚行を犯してしまいました。 また、検索対象の文章のデータの持ち方も問題がありました。最初は、1文1文の意味ベクトルを1つずつファイルにして、S3にあげようとしていましたが、それだと取得に時間もかかるしお金もかかるということで、すべての検索対象をtar.gzでまとめ、推論インスタンスの中で解凍することで動かしています。 料金 推論に関しては、サーバーレスなエンドポイントがあって、試すにはちょうどいいなと感じました。 ただ、やはり学習にはそれなりにかかるので、オンプレGPU環境があれば、それを使ってモデルだけ引っ張ってこれるようにしてみるのも実験段階ではアリかもしれません。 最後に 今回のタスクは、私が合宿という機会にチームに提案して受け入れられたものですが、スマートキャンプではテックカンパニー化が現在推進されており、通常業務と並行して新しい技術の開発やビジネスモデルの創出に力を入れています。 気になった方は、ぜひ一度カジュアル面談にお越しください! smartcamp.co.jp
アバター
どうもこんにちは。22卒としてスマートキャンプに入社したbraavaです。 普段は BOXIL SaaS というtoB向けのSaaS比較サイトの機能開発・運用をしています。 近頃はM-1グランプリやワールドカップで多少寝不足気味でしたが、今回はそんな自分が社内で利用されている日報ツールを改善したお話です。 本稿の対象読者は下記になります。 Google APIを叩くSlack botの開発をAPI開発だけで収めたい人 日々入力する日報を改善したい人 ある程度、OAuth認可フローについて理解している人 (M-1グランプリとワールドカップが好きな人) 日報ツールって?? フロントなしでGoogleの認可を終わらせるSlack botのAPI 課題 どうやって解決したか?? 軽くインフラをご紹介します 弊社の日報ツール(別名 日報太郎2.0) 課題は解決できたか?? 日報太郎2.0の課題 反響 今後の展望 まとめ 日報ツールって?? 弊社では日報を #daily_report というパブリックチャンネルで共有する文化があります。 この文化は Note にもまとめられており、社員数が大きく増えた今でも存在します。 その日報に当日の予定を乗せるために、Googleカレンダーから自動で予定を取得する 日報半自動生成bot という日報ツールが生まれました。 そしてこのツールのメンテナンスを自分が引き継ぐことになったのですが、しばらくして以下の課題が見えてきました。 日報ツールの管理者のカレンダーに利用者全員のカレンダーを登録する必要がある GASの知識があまりなく、メンテナンスできる自信がない 自分の日報フォーマットが埋もれてしまう この日報ツールは管理者アカウントと連携されていたため、100人以上のGoogleカレンダーを下記のように登録する必要がありました。 また、GASに対する知識があまりなく管理者として引き継いだ後、保守運用をできる自信がなかったため、このbotを興味のあるGolangに書き換えようと考えました。 加えて、実際に日報ツールを利用しているユーザーに軽くインタビューをしたところ 同じチャンネルに同時刻にたくさんのフォーマットが送られるため自分の日報フォーマットが埋もれてしまう という課題があることにも気づきました。 しかしながら、よくあるGoogle APIを叩くSlackのbotのように、OAuthによる認可フローのフロントエンド実装が億劫でした。 なぜフロントエンドの実装が必要かというと、Googleの認可処理とSlackアカウントへのログインを行い、両者の紐付けをする必要があるためです。(詳しくは後述します) ただそれだけのためにフロントエンドの実装をするのは面倒ですよね?(だって楽したいですよね) そのため、下記のような要件で開発をしました。 上記3つの課題を解決する API開発 だけ する 先に フロントなしでGoogleの認可を終わらせるSlack botのAPI を解説したうえで、日報ツールを書き換えた結果についてお伝えできればなと思います!! フロントなしでGoogleの認可を終わらせるSlack botのAPI 課題 前述の「管理者のカレンダーに利用者全員のカレンダーを登録する必要がある」というのは利用者のGoogleカレンダーを管理者のGoogleアカウントの権限で閲覧していたためです。 そのため実際に管理者のカレンダーから利用者のカレンダーを登録する必要がありました。 これを解決するために利用者側でGoogleカレンダーのAPIの認可をしてもらい、利用者ごとに生成されたtokenを使ってGoogle APIを叩きます。 tokenを取得+Slack上のユーザーと紐付けて保存するためには、フロントを用意し下記のような処理を書く必要があります。 Slackのアカウントでログイン Googleの認可する tokenを取得し、保存する しかしながら前述の通りバックエンドの実装だけで済ませたかった(決して楽がしたいわけではない)ため後述の解決策でフロントを作らずバックエンドの実装だけで要件を満たすようなbotを作成しました。 どうやって解決したか?? 解決案を簡単に述べると ユーザーに頑張ってもらう です。 認可フローの実装は下記です。 ユーザーにブラウザ上でGoogleの認可をしてもらう 認可のCallbackで設定されているエンドポイントでcodeを受け取る 受け取ったcodeを使ってAPIを叩く 受け取ったtokenをブラウザ上で認識しているユーザーと紐付けた形で保存する 今回はフロントを作らないため 4 ができません。 そのため 生成されたcodeをSlack経由で送ってもらう ということをユーザーに頑張ってもらいました。 API側でやることはstateの検証とJSON形式でcodeを返却するだけになり、その表示されたcodeをSlackで送ってもらいます。 図で説明するとこのようになります。 Slack上でGoogleの認可URLを発行する ブラウザに移動してGoogleカレンダーの利用許可(認可)をする Callbackで設定したエンドポイントではstateの検証だけ行い、codeをそのままブラウザに出力する ブラウザに出力されたcodeをSlack経由で送ってもらう Slack上のユーザーとGoogleで認可されたユーザーの紐付けができ、DBに保存する 上記のフローによってGoogleカレンダーに入っている予定を取得できます!! またGolangのgoogle-apiを叩くライブラリは、AccessTokenの有効期限が切れていた場合でも、RefreshTokenを利用してAccessTokenを良い感じに再取得してくれるので、そこの実装をする必要がありませんでした。 このように ユーザーに頑張ってもらう ことで開発コストを抑え(決して楽がしたいわけではない)つつ、Google APIを叩くSlack botを実装しました。 軽くインフラをご紹介します 本節では前述のフローで実装したSlack botのインフラを見てもらいます。 弊社ではインフラ周りはAWSを利用して構築しているため、本botもAWSを利用しました。 AWS Lambda API Gateway DynamoDB CloudWatch ServerlessFramework SlackやGoogle APIなども含んだインフラの構成図はこのような感じです。 個人的にServerlessFrameworkの楽さをすごく実感しました。 現状デプロイはlocalから行っていますが時間があるときにGitHub Actionsからmainブランチへのpushを検知して自動でデプロイするようにしたいなと思っています。 弊社の日報ツール(別名 日報太郎2.0) 前述のbot は弊社で お茶太郎(日報フォーマット) という名前で利用されていました。 今回の改修にあたり 日報太郎2.0 といういかにもSaaSっぽい名前に解明しています。(小話) 課題は解決できたか?? 実際に管理者でもある自分の負担はかなり減り、bot自体のエラー(Google APIが叩けない等)の調査以外は日報太郎2.0を触る機会は減りました。 また認可フローをしっかりと踏むことで若干グレーの運用をしていた既存の日報ツールから、利用者がしっかりと許可したうえでカレンダーを取得することになったのでbot自体の質も上がったのではないかなと思います。 自分の日報フォーマットが埋もれてしまう という課題についても、アプリとのDMや各々好きなチャンネルで、設定されているコマンドを打つことで日報フォーマットが送られてくるようになったため埋もれる心配もなくなりました。 そしてGASからGolangに書き換えたためbotの拡張意欲も生まれ、実際に新しい機能の開発も現在進めています!! 日報太郎2.0の課題 弊社はtoB向けのSaaS比較サイトやIS向けのSaaSを提供していることもありSlack botなどのツールに慣れている社員が多いため ユーザーに頑張ってもらう という選択ができました。 しかしながら、連携の際の手順を全社員が見れるようなドキュメントに起こしても、連携方法に関して質問が来ることはたまにあります。 そのためあまりにも手順に関して質問が来る場合などは他の打開策を探す必要があるでしょう。 反響 自分が所属している開発メンバーに実際に日報太郎2.0に関してインタビューをしました。 (ポジティブなことを言ってくださいなんて言っていない) 以下インタビュー内容です。 日報太郎2.0になったことによって自分のフォーマットが埋もれなくなった。 フォーマットを探す=>日報を編集する、という流れだったのがフォーマットを探す工程がなくなったため日報を書く意欲が劇的に改善された。 勝手にフォーマットを送る仕様から、コマンド経由になったことでフォーマットを欲しいときに取れるようになったため、フレックスするときにも関係なくフォーマットを取得できた。(以前まではフォーマットが送られる時間より前に退勤する場合はフォーマットを利用できなかった。) 自動通知設定ができるようになると以前の仕様も踏襲できてよいのではないか。 今後の展望 今回 ユーザーに頑張ってもらう ということでSlackのコマンドの引数で認可のコールバックで返ってくるcodeを送ってもらっていました。しかしcodeをコマンド経由で送る際のUXが良くないなと感じており、実際にその周辺で質問も来るため、Slackのモーダルを開いてcodeを入力する形式に変更したいなと思っています。 また、弊社では #daily_report というパブリックチャンネルに全社員が日報を送っています。そのため業務で関わらない人の日報が見れるというメリットがありますが、逆に見たい人の日報が埋もれてしまうというデメリットもあります。 そのデメリット解消のためSlackのEvent APIを利用したフォロー機能を現在開発中です。投稿したユーザーをフォローしている場合、任意のチャンネルに通知してくれる機能です。 まとめ 前任者から日報botを引き継いだ際に認可フローについての課題を ユーザーに頑張ってもらう ことでフロントなしの実装で解消し、Googleカレンダーの閲覧権限もユーザーに許可してもらうことでbot自体の質向上をしました。 恐らく既存の日報botがなければそもそも改善しようとすら思わず、作ったとしても社内で普及するのに苦労していたと思います。そのため前任者と利用してくれる社内のユーザーには頭が上がらないです!!
アバター