TECH PLAY

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

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

226

こんにちは!スマートキャンプ、エンジニアの関口です! 私は現在BOXILと連携させる新規アプリケーションの開発に携わっております。 このアプリケーションは、バックエンドAPIはGo 、フロントエンドはTypeScript/Next.jsで構成されています。 このプロジェクトの中で私は認証機能を担当しました。認証機能はAuth0というIDaaSを利用して実装しています。同じ様な構成で認証機能を実装したいと考えている方の理解の助けになればと思い、 このアプリケーションの構成に寄せる形でNext.jsとAuth0を連携した認証機能を実装する方法を紹介します。 Next.jsとは Auth0とは auth0-reactとは 実装 まとめ Next.jsとは Next.jsとはReactをベースとしたWebアプリケーションフレームワークです。Next.jsはVercelというホスティングサービスを提供しているVercel社が提供しています。Next.jsはページ単位でHTMLの生成方法をSSRかSSGで選択できたり、ファイルベースのルーティングを備えています。 公式サイトにもあるように本番環境に備えたあらゆる機能を提供しています。 nextjs.org Auth0とは Auth0とはWebアプリやモバイルアプリ、WebAPIなどに対して認証・認可の機能を提供する認証基盤サービス(IDaaS)です。Auth0を利用することで開発中のアプリケーションに簡単に認証機能を組み込むことができます。連携できるソーシャルログイン の種類の豊富さや、ユーザー管理、ログ管理などのAuth0のコンソール画面の扱いやすさも魅力的です。 auth0-reactとは 今回auth0-reactというSDKを採用しました。Next.jsとAuth0を連携する場合のSDKの選択肢として auth0-react と nextjs-auth0 があります。どちらのSDKを採用するか判断するために、nextjs-auth0のドキュメントを参考にしました。 github.com ドキュメントには以下の様なケースではnextjs-auth0よりもauth0-reactの方が適していると書かれています。 Next.jsでStatic HTML Exportを使用している場合 サーバーサイドレンダリング時にユーザーデータにアクセスする必要がない場合 Next.jsのAPI Routesをプロキシとして使用して外部APIを呼び出すのではなく、アクセストークンを取得してフロントエンドレイヤーから直接外部APIを呼び出したい場合 私達のアプリケーションではアクセストークンを取得してフロントエンドレイヤーから直接バックエンドのGo APIを呼び出すような設計にしていたので、auth0-reactを採用しました。 実装 Next.jsの環境構築 実装に入っていきます! 最初にNext.jsの環境構築をおこないます。 Next.jsの公式ドキュメント を参考にNext.jsのアプリケーションを作成するためのコマンドを実行します。今回は「auth0-sample」という名前でアプリケーションを作成し、TypeScriptを採用するので、コマンドにオプションを追加します。 npx create-next-app auth0-sample --ts アプリケーションの作成が成功したら、サーバーを立ち上げます。 npm run dev サーバーを立ち上げて http://localhost:3000 にアクセスしてください。すると以下の画像のような画面が表示されます。 Auth0の設定 Auth0の設定をおこないます。まずAuth0のアカウン登録の際にテナントドメインを設定します。 ApplicationsページのCreate Applicationからアプリケーションの名前とアプリケーションのタイプを選択し、アプリケーションを作成します。 アプリケーションが作成されると、Settings画面からシークレット情報の確認やログイン後のコールバックURLなどの各種設定ができるようになります。 認証機能の実装 続いてauth0-reactを利用してAuth0とNext.jsの連携をおこない、認証機能の実装をしていきます。 まずauth0-reactのinstallをおこないます。 npm install @auth0/auth0-react 続いてAuth0とNext.jsの連携をおこなうための設定を_app.tsxに記述します。 import type { AppProps } from 'next/app' import { Auth0Provider } from '@auth0/auth0-react' ; function MyApp ( { Component , pageProps } : AppProps ) { //ログイン後のリダイレクト先を指定 const redirectUri = ` ${process .env[ "NEXT_PUBLIC_BASE_URL" ] } /success` return( < Auth0Provider domain = { process .env [ "NEXT_PUBLIC_AUTH0_DOMAIN" ]} clientId = { process .env [ "NEXT_PUBLIC_AUTH0_CLIENT_ID" ]} redirectUri = { redirectUri } > < Component { ...pageProps } / > < /Auth0Provider > ) } export default MyApp リダイレクト先のURLやAuth0のドメイン、クライアントIDは環境変数で指定しています。 ログイン、ログアクト後にどのページにリダイレクトするかをAuth0のコンソール画面で設定します。ログイン後はAllowed Callback URLs、ログアウト後はAllowed Logout URLsに設定します。 認証ページ作成 続いて認証ページを作成します。 isAuthenticatedメソッドでログイン中のユーザーが存在するか判定し、存在する場合はログアウトボタンを、存在しない場合はログインボタンを表示させます。 //pages/index.tsx import styles from '../styles/Home.module.css' import { useAuth0 } from '@auth0/auth0-react' ; export default function Home () { const { isAuthenticated , loginWithRedirect , logout , user } = useAuth0 (); return ( < div className = { styles.container } > < main className = { styles.main } > < h1 className = { styles.title } > Welcome to < a href = "https://nextjs.org" > Next.js ! < /a > < /h1 > { isAuthenticated && ( < div > < p > { user?.name } < /p > < button onClick = { () => logout () } > ログアウト < /button > < /div > ) } { ! isAuthenticated && ( < div > < p > ログイン < /p > < button onClick = { () => loginWithRedirect () } > ログイン < /button > < /div > ) } < /main > < /div > ) } 次にログイン後に遷移するページを作成します。 // pages/success.tsx import { useAuth0 } from '@auth0/auth0-react' ; import styles from '../styles/Home.module.css' export default function Home () { const { isAuthenticated , loginWithRedirect , logout , user } = useAuth0 (); return ( < div className = { styles.container } > < h1 > Welcome to < a href = "https://nextjs.org" > Success Page ! < /a > < /h1 > { isAuthenticated && ( < div > < p > { user?.name } でログイン中 < /p > < button onClick = { () => logout () } > ログアウト < /button > < /div > ) } { ! isAuthenticated && ( < div > < button onClick = { () => loginWithRedirect () } > ログイン < /button > < /div > ) } < /div > ) } ログインするためにはユーザーが必要なので、Auth0のコンソール画面のUser Managementから ユーザーを作成しましょう。 以上によりログイン機能が実装できたので動作確認していきましょう。 サーバーを立ち上げて http://localhost:3000 にアクセスしてください。以下のような画面が表示され、ログインボタンを押すとAuth0のログインページにアクセスできます。 ログインページで先程作成したユーザーのメールアドレスとパスワードを入力します。 Auth0のログイン画面 ログインに成功すると先程リダイレクト先として指定したサクセスページにリダイレクトされます。 Cookiesに「auth0.is.authenticated」というキーが生成され、値に「true」が代入されます。これによってユーザーがログインしているかどうかの判断をおこなっています。 以上がNext.jsとAuth0による認証機能になります。 まとめ Next.jsアプリケーションにAuth0とSDKを利用することで簡単に認証機能が実装できることができました。 認証はアプリケーションを構築する上で欠かせない機能ですが、Auth0のようなIDaaSに頼ることでアプリケーションの肝となるロジックに開発を集中することができます。 今回のアプリケーションは以下のリポジトリにまとめました!興味のある方はご覧ください! https://github.com/daichi1998928/nextjs-auth0-sample github.com 今回の記事がNext.jsとAuth0を利用して認証機能を実装したいと考えている方の助けになれば幸いです。最後まで読んでいただきありがとうございました!
アバター
こんにちは、 BOXIL 開発に携わっている、新卒エンジニアの高砂と申します! 私はスマートキャンプにてこれまでインターンとして1年、新卒社員として1年ほど働いています。 皆さんは、普段の業務の中で「これって非効率だな」と感じた経験はありますか? 私はこれまで業務の中で非効率を見つけると、社内コミュニケーションツール「Slack」上で動くbotをGASで開発などして解決していました。 本記事では、開発した業務効率化botの内でお気に入りのものを3つほどご紹介し、その中でも特に注力した1つについて詳しくお話ししていきます! 業務効率化bot3選 来客対応サポートbot 質問精度向上bot 日報半自動生成bot 日報半自動生成botの詳細 背景 要件定義 実際の実装内容 実際の利用状況 まとめ 業務効率化bot3選 来客対応サポートbot スマートキャンプでは「RECEPTIONIST」という受付サービスを利用しています。 来客時にSlack通知する機能があり非常に便利なのですが、この通知には来客名しか表示されず、誰の来客か分からないという弱点があります。 なのでこれまでは、その日の受付担当者が来客名とマッチするGoogleカレンダー上の予定を目視で探し、来客先の社員に連絡するというフローで対応していました。 しかしそれが非効率だと思った私は、下記のように自動で対応する予定を探し、連絡してくれるbotを作りました。 詳しくはこちらの記事で説明しているので、良ければご覧ください。 tech.smartcamp.co.jp 質問精度向上bot 入社したての頃、新卒の私は「良い質問の仕方」を身につけるのに苦戦しました。 質問が曖昧だったり、過去に聞いた事があるのにまた質問してしまったりしていました。 かといってそれを質問する度に意識するのも非常に難しいです。 なので私が質問する度に、botが質問文かどうかを下記のようなロジックで判定した上で、質問文の場合は「良い質問の仕方」を意識させるような問いかけをするにしました。 var questionWords = ['ですか?', 'ますか?'] if (receivedMessage.indexOf(questionWord) !== -1) { postMessage(message) } これにより私は、質問の度に「良い質問の仕方」を毎回意識することができ、自然と良い質問ができるようになりました。 日報半自動生成bot スマートキャンプには「毎日退勤時に日報を書く」という文化があります。 その文化自体はとても良いものだと思うのですが、「日報の作成作業」にも非効率が潜んでいました。 それを解決する為に下記のようなbotを作成しました。 詳しくは次のセクションでお話ししていこうと思います。 日報半自動生成botの詳細 背景 先述した通り、スマートキャンプには「毎日退勤時に日報を書く」という文化があります。 その目的は2つあり、1つは業務報告、1つはコミュニケーション活性化です。 日報には下記のように「本日のタスク」と「雑感(ひとことコメントのようなもの)」を記載するので、2つの目的を達成できるようになっています。 なので日報を書くこと自体は重要なのですが、日報の前半の「本日のタスク」については、各自がGoogleカレンダーを見ながら手打ちで入力するという作業が発生していました。 「その作業は効率化し、雑感に時間をかけるべき」だと考えた私は、Googleカレンダーから自動で「本日のタスク」を抽出できるのでは考えました。 要件定義 前述の課題を解決するにあたって、下記要件が必要と考えました。 Googleカレンダーから予定名を抽出して一覧化してくれる事 一覧を日報の定形フォーマットに挿入してくれる事 上記を特定の時間に共有してくれる事 これらを実現してくれるbotをGASで開発してみました。 実際の実装内容 実装は下記のように行いました。6つのセクションに分けて解説していきます。 ①ーーーーーーーーーーーーーー function main() { var sheetId = PropertiesService.getScriptProperties().getProperties().sheetId var spreadsheet = SpreadsheetApp.openById(sheetId) var formSheet = spreadsheet.getSheetByName( 'form' ) var formSheetValues = formSheet.getDataRange().getValues() var idTableSheet = spreadsheet.getSheetByName( 'idTable' ) var idTableSheetValues = idTableSheet.getDataRange().getValues() ②ーーーーーーーーーーーーーー formSheetValues.slice(1).forEach( function (formSheetRow) { var calendarId = formSheetRow [ 1 ] var calendar = CalendarApp.getCalendarById(calendarId) if (calendar !== null ) { var events = calendar.getEventsForDay( new Date ()) var eventMessage = '' ③ーーーーーーーーーーーーーー events.forEach( function ( event ) { var title = event .getTitle() var status = event .getMyStatus() if (title !== '' && status != 'NO' ) { eventMessage += '・' + title + '\n' } } ) var mention = makeMention(calendarId, idTableSheetValues) var message = makeDailyReport(eventMessage, mention) postMessage(message) } } ) } ④ーーーーーーーーーーーーーー function makeMention(calendarId, idTableSheetValues) { var SLACK_ID_COLUMN = 2 var EMAIL_COLUMN = 3 var mention = ‘’ idTableSheetValues.forEach( function (idRow) { if (idRow [ EMAIL_COLUMN ] === calendarId) { mention = ‘<@‘ + idRow [ SLACK_ID_COLUMN ] + ‘> ‘ } } ) return mention } ⑤ーーーーーーーーーーーーーー function makeDailyReport(message, mention) { return ‘今日の日報フォーマットを持ってきたわん! \ n \ n’ + mention + ‘*【日報】’ + Moment.moment().format(‘YYYY年MM月DD日(dddd)’) + ‘*’ + ‘ \ n ```本日のタスク``` \ n’ + message + ‘ \ n ```雑感``` ’ } ⑥ーーーーーーーーーーーーーー function postMessage(message) { (中略) } まず①では、スプレッドシートから利用者情報を抽出しています。 アンケートで集めた日報半自動生成botの利用希望者一覧が記載されているスプレッドシートから、それぞれのメールアドレスおよびSlackのメンバーIDを引っ張ってきています。 ①ーーーーーーーーーーーーーー function main() { var sheetId = PropertiesService.getScriptProperties().getProperties().sheetId var spreadsheet = SpreadsheetApp.openById(sheetId) var formSheet = spreadsheet.getSheetByName( 'form' ) var formSheetValues = formSheet.getDataRange().getValues() var idTableSheet = spreadsheet.getSheetByName( 'idTable' ) var idTableSheetValues = idTableSheet.getDataRange().getValues() 次に②では、①のメールアドレスをもとに対応するGoogleカレンダー、およびそこに記載されている今日の全予定を取得しています。 ②ーーーーーーーーーーーーーー formSheetValues.slice(1).forEach( function (formSheetRow) { var calendarId = formSheetRow [ 1 ] var calendar = CalendarApp.getCalendarById(calendarId) if (calendar !== null ) { var events = calendar.getEventsForDay( new Date ()) var eventMessage = ‘’ そして③では、②の全予定の中で「不参加」以外の予定の予定名を取得し、変数に代入しています。 ③ーーーーーーーーーーーーーー events.forEach( function ( event ) { var title = event .getTitle() var status = event .getMyStatus() if (title !== '' && status != 'NO' ) { eventMessage += '・' + title + '\n' } } ) var mention = makeMention(calendarId, idTableSheetValues) var message = makeDailyReport(eventMessage, mention) postMessage(message) } } ) } ④では、①のメンバーIDからSlackでのメンション文言を生成しています。 ④ーーーーーーーーーーーーーー function makeMention(calendarId, idTableSheetValues) { var SLACK_ID_COLUMN = 2 var EMAIL_COLUMN = 3 var mention = ‘’ idTableSheetValues.forEach( function (idRow) { if (idRow [ EMAIL_COLUMN ] === calendarId) { mention = ‘<@‘ + idRow [ SLACK_ID_COLUMN ] + ‘> ‘ } } ) return mention } そして⑤では、③の予定名情報および④のメンション文言を用いて、botが実際に送るメッセージ内容を生成しています。 ⑤ーーーーーーーーーーーーーー function makeDailyReport(message, mention) { return ‘今日の日報フォーマットを持ってきたわん! \ n \ n’ + mention + ‘*【日報】’ + Moment.moment().format(‘YYYY年MM月DD日(dddd)’) + ‘*’ + ‘ \ n ```本日のタスク``` \ n’ + message + ‘ \ n ```雑感``` ’ } 最後に⑥では、⑤で生成したメッセージを実際に送信する処理を行っています(良くある処理なのでこちらは省略させて頂きました)。 ⑥ーーーーーーーーーーーーーー function postMessage(message) { (中略) } 以上の6つのセクションによって、今回の日報半自動生成botを実装しました。 実際の利用状況 最初は自分の為に作り始めたbotでしたが、社内公開してからクチコミで色んな方に利用して頂き、今ではなんと30人以上が利用しています!(全正社員の4割ほどにあたります) また、利用者から寄せられた声をもとに定期的にアップデートもしており、好評をいただいています。 まとめ 最近の業務ツールはAPI連携が盛んであり、特にGASでSlack用のbotを作るのは自由度が高いです。 業務ツールの移り変わりと共に非効率な作業も発生しがちなものなので、皆さんもそれをbotで解決してみてはいかがでしょうか? スマートキャンプのミッションである「テクノロジーで社会の非効率を無くす」を、引き続き社内でも体現していこうと思います!
アバター
こんにちは!スマートキャンプでWebアプリケーションエンジニアとして働いている中川です。 さて、唐突ですがみなさんは別の開発チームに異動した経験はありますか? いくつかプロダクトを抱えていたり受託開発をしている会社では割とよくある現象なので、少なくない数の方が経験されたことがあるかなと思います。 と、この書き出しで察しの良い方はお気づきかと思いますが、かくいう自分もこの度チームを異動して、6月からBOXIL開発チームで働いています。 今回の記事では、自分が新しいチームに参加することになったときにどういうキャッチアップを行っているかについてご紹介していこうと思います! また、今回の記事で前提としているのは異動のシーンですが、転職でも通ずるような内容は多いと思っています。 キャッチアップする目的を考える なにをキャッチアップしていくか考える プロダクトに慣れる ビジネスモデルを知る 使われている技術を知る ソースコードを知る データとその構造を知る アーキテクチャを知る チームに慣れる コミュニケーションスタイルに慣れる チームのイベントごとを把握する カルチャーに慣れる 自分の立ち位置・求められている役割を把握する まとめ キャッチアップする目的を考える まずはキャッチアップしていった末にどういう状態になるとよいのか、その目的について整理します。 今回は以下に挙げたことがこなせるようになることと定めました。 複雑な設計が必要ない機能を設計・実装できるようになること 実装からリリースのフローが理解できており、実践できること プロダクトのドメインと提供している機能群のなかでもコアなものは把握できていること 開発チームと滞りなくコミュニケーションができること 究極的にはどんな機能でも設計出来るようになりたいですし、ソースコードの細部まで認識している状態になれれば理想なのですが、 あくまでもキャッチアップとしては上記のようないわゆる「一人前として立ち上がっている」状態を目的とし、 だいたい3ヶ月程度を目安として実行することにしました。 なにをキャッチアップしていくか考える さて、前項でキャッチアップによってどうなりたいかについては考えたので、ここでは何をキャッチアップするかについて詰めていきます。 前項の箇条書きを抽象化して整理すると、ここには大まかに2つの軸が存在していることがわかります。 軸の1つは当然ですが技術的な要素である「プロダクト」、もう1つは見逃されがちですが日々のコミュニケーション面として「チーム」の2つです。 というところで、「プロダクト」と「チーム」についてキャッチアップ、つまり「慣れる」ためにそれぞれの要素を出していったものが以下の箇条書きになります。 プロダクトに慣れる ビジネスモデルを知る 使われている技術を知る ソースコードを知る データとその構造を知る アーキテクチャを知る チームに慣れる コミュニケーションスタイルに慣れる チームのイベントごとを把握する カルチャーに慣れる 自分の立ち位置・求められている役割を把握する それぞれの要素の下にもさらに子要素・孫要素が連なっていくとは思いますが、方針としてある程度網羅性のあるものにはなっていそうなのでこの各要素を実施していくことにしました。 ここからは、異動から2ヶ月弱経過した現在時点までに実際に各要素の打ち手として何を実行してきたかについて書いていこうと思います! プロダクトに慣れる ビジネスモデルを知る まずはそのプロダクトがどのようにしてマネタイズしているのか、そのビジネスモデルを理解しにいきます。 プロダクトを成り立たせている収益構造・参入障壁などについて、開発チームメンバーや関係する人々に教えを乞います。 これは世界観を身につけるようなもので、プロダクトに登場する人物の種類(ユーザー・アドミン等)や、何が大事とされているのか・その逆はなにか、といったプロダクトがまとう一種の雰囲気をつかみにいくようなイメージです。 また、ここで出てくる収益構造のキーとなる単語(商品・プラン・ユーザー・リード等など)は後々 「データとその構造に慣れる」の章でデータの関係性を理解するうえでも役立ってきます。 使われている技術を知る プロダクトで使われている技術要素を俯瞰します。 ここで見るポイントとしては、使われている言語やフレームワークで自身の経験がないものを炙り出すだけでなく、それが一体どれほど使われているのか・どれほどの頻度で修正が起こり得るかにも注目することです。 つまり、そのプロダクトにおいてメインとなる言語・FWであればキャッチアップの必要性は高いですが、その逆であれば優先度は下げられると考えています。 当然セキュリティ上クリティカルな部分であったりビジネスのコアロジックが書かれている場合は一読する必要があるとは思いますが、そういったロジックは概して修正頻度も低いので、一読できるレベルにまでキャッチアップできればあとは他要素のキャッチアップを優先しています。 今回の異動においてはRailsのフロントエンド部分(slim, CoffeeScript, gon, ahoyなど)が未経験だったため、そちらのキャッチアップを行いました。 また、現在プロダクトではこういったslim + CoffeeScriptの部分をVue.jsに置き換えていく動きが進行しているタイミングということに便乗して、そちらのタスクを通じてシンタックスや挙動の理解を捗らせることが出来ました。 (移行の経緯や概要については以下の記事をご覧ください) https://tech.smartcamp.co.jp/entry/frontend-improvement ソースコードを知る 普段の開発で関わることになるソースコードについてキャッチアップしていきます。 ここでキャッチアップの手段や手順といった概要に関するお話は以下の記事に詳しく、素晴らしい内容ですのでそちらに譲りたいと思います。(ありがとうございます!) ソースコードを読むための技術(チートシート) - Qiita 余談ですが、上記の記事のなかでもソースコードの処理の流れをメモしていくことがキャッチアップにおいて有用であることが説明されていますが、今回のキャッチアップにおいては、それを効率化する以下のVSCodeプラグインが非常に活躍しました。 marketplace.visualstudio.com VSCodeのエディタ上でソースコードの行単位でメモを簡単に取ることができるプラグインなのですが、「コードを読む」&「メモを取る」という2つの作業がシームレスに行えて体験が良かったので、コードリーディングのお供としてオススメです! データとその構造を知る ここではアプリケーションが持つデータとその構造をキャッチアップします。 具体的に表すと、データベースのスキーマと、そのなかの実際のレコードを見ていきます。 といっても、GUIで闇雲にaからzまで順番にテーブルを見たとしても要点を理解することは難しいです。 そのため、ここではER図を生成することでその整理を通して重要なモデルやその関連を学びます。 Railsアプリケーションにおいては、rails-erdというER図を生成するためのgemが存在しているので、こちらを使用します。 GitHub - voormedia/rails-erd: Generate Entity-Relationship Diagrams for Rails applications すでに運用されているアプリケーションで生成した図を見てみると、いくつものモデルから関連されているモデルや、逆にどこからも関連されていないようなモデルがおそらく発見出来るかと思います。 図をただ眺めるというと、実のない行為に思えてしまいますが、ここで得られる発見は実際にその後の設計や実装において精度の高いものを作るために確実に寄与してくるため、図の大小にもよりますが私の場合は1時間程度は時間をかけて、眺めたり気になるところは実際のレコードをSELECTしてきてどういった値が入っているのか確認したりしています。 アーキテクチャを知る プロダクトのアーキテクチャをキャッチアップします。 いわゆるインフラ的な要素もそうですが、連携やバッチなどのメインとなるアプリケーション外でどういった要素が動いているのかであったり、デプロイにまつわるリソースに関してもここで確認しておきます。 チームに慣れる コミュニケーションスタイルに慣れる チームに慣れるために最も不可欠なのがこのチームのコミュニケーションスタイルを把握するという点です。 チームが違えばそのコミュニケーションの形態も千差万別で、社内の別チームであってもまったく異なるという場合も多いかと思います。 かくいう弊社もそのうちのひとつです。 前チームにおいてはチャットやドキュメントを通した非同期的なコミュニケーションをチームとして推奨しており、極力同期的な場を設けずに各々の業務スタイルに一任するやり方でした。 具体的には、日々の朝会や企画のMTG、振り返りなどをZoomで行いつつも、日中は各自で業務にあたり、確認点・不明点が発生したタイミングでSlackでコミュニケーションを図るような動き方です。 これに対して、現チームでは常時接続の同期的なコミュニケーションを良しとしており、それに伴って非同期的なコミュニケーションはあまり取られない(常時接続なのでする必要性に駆られない)ような、真逆のコミュニケーションスタイルです。こちらはDiscordのボイスチャンネルに各自が常駐する形となっています。 こういったスタイルの違いは、チームメンバーの特性やプロダクトのフェーズによるものが大きいのでどちらが良いというものではないのですが、コミュニケーションスタイルに大きな違いがあるという前提を持ち、慣れる必要性があると自認することが大事だと思っています。 今回の異動でもこのコミュニケーションスタイルの違いによっていつものように仕事を進められないことに苦労しましたが、慣れる必要性があると認識をしていたおかげで焦らずに適応していく気概を持つことができました。 チームのイベントごとを把握する 定期・不定期に関わらず、チームにはイベントごとがつきものです。 1on1をしているチームであればその相手や頻度を確認しますし、スクラムをやっているチームであればリファインメントやプランニング、スプリントレビューなどの定常的なイベントのタイミングを確認します。 また、たいていのチームはそのチーム独自の施策を行っていたりするものかなと思います。 現チームでは開発チームで雑談する時間や最近の業務内容を共有する時間が隔週で取られていたりしました。 こういったイベントごとを把握し、体内時計をそのスケジュールにあわせていく動きが必要です。 カルチャーに慣れる 「郷に入っては郷に従え」という言葉にもある通り、共同体(チーム)は「何をよしとするか」の価値観がそれぞれ違うものだと思います。 上述のコミュニケーションスタイルひとつをとっても大きく異なるように、場面場面でチームがどういった選択をしているかを観察し、必要であればその選択に至った理由を深堀りすることで価値観を吸収していきます。 自分の立ち位置・求められている役割を把握する チームに慣れるという文脈とは少し逸れるのですが、チームにおける自分のポジションを把握することはその後の自分の動き方を考えていくうえで有用なためこのタイミングで確認します。 チームにおける自分の立ち位置というのは周囲との差異によって規定されることが大きいのですぐに把握することは難しいですし、主観なので精度も高くはないのですが、チームの誰が何を得意で、その集まりに対して自分のこのスキルや特性は希少性が高くて活かしやすそうだな、といった具合に考えていきます。 これは一見打算的な考えかもしれませんが、チーム内における自分のバリューを高めることはそのままチームの穴を埋めて全体のパフォーマンスを上げることにもつながるので、必要なステップだという考えです。 まとめ 今回の記事ではチーム異動に際して自分がキャッチアップしていることをご紹介しました。 異動というのは大きな環境の変化で、流されるままにタスクに取り掛かっては右往左往するということも多い(自分もそうでした)かと思いますが、このように何をやるべきか準備しておくだけでも日々の仕事に翻弄される可能性を下げられるのではないかと考えています。 今回の異動では、5年以上運用されている大規模サービスという異動先のプロダクトの特性もあってER図を眺めてデータとその構造を知ることと、メモを書きつつのコードリーディングが特に役立ちました。 最後になりますが、この記事でご紹介した内容以外でもみなさんがオススメするキャッチアップ手法があればぜひSNSなどで教えてください! それでは!
アバター
こんにちは!スマートキャンプでBALES CLOUDというSaaSを開発しているエンジニアの井上です。 本記事では、弊社のBALES CLOUD開発チームでサービスの品質を保つために導入したE2Eテスト自動化サービス mabl についてご紹介します。 mablとは 導入背景 導入前の状態 導入前のリリースフロー BALES CLOUDで発生した思わぬバグ E2Eテスト導入への課題 E2Eテスト導入するための解決すべき課題 mablを選んだ理由 1. コーディング不要でテスト作成が簡単 2. クラウド/SaaS環境なので初期構築コストがない 3. AIでテストを自動修復、自動画面分析 4. Japan Lead 藤原さんの手厚いサポート mablでテストを作る mablの構造を知る テストを作成する mabl導入により良かったこと まとめ mablとは mablはE2Eテスト自動化サービスです。 特長としては以下のような機能を備えています。 mabl Trainer というChromeの拡張機能があり、自分の操作手順を記憶させることでテストを作成できる CI/CDにも簡単に組み込むことができる クロスブラウザのテスト実施も可能 導入背景 導入前の状態 弊社のBALES CLOUD開発チームはエンジニア2人とPM1人の体制です。 リリースまでは以下のようなフローをとっており、Unitテストはしっかり書かれているためカバレッジは高い状態でした。 また、過去にデザイン崩れなどの問題も起きていたので、Visual Regression Testを追加しデザイン崩れに気付ける状態にした上で POが要求通りかテスト環境で確認してくれるので安心してリリースできる状態でした。 導入前のリリースフロー 機能開発 Unitテスト Visual Regression Test コードレビュー POの要求仕様確認テスト リリース BALES CLOUDで発生した思わぬバグ いつもどおり安心して本番リリースを実施したところ、追加した機能の影響で既存機能にバグが発生しました。 エンジニアもPMも影響範囲がその機能に及ぶとは思っていなかったため、テストから漏れていた状態でした。 開発初期であればコードサイズも小さいため、影響範囲の把握が容易でしたが 機能追加していくと、関連し合う機能が増えUnitテストだけでは担保できない範囲でバグが発生する状態になっていました。 その後、発生したバグに対する振り返りを行い、検知のためにはアプリケーションに対する手動テストを行う必要があるという結論になりました。 しかし、手動では機能の増加に伴ってテストのコストも増加していくため、E2Eテストツールを導入することを決めました。 E2Eテスト導入への課題 E2Eテストを導入する前にクリアしなければならない課題がいくつかありました。 そもそもE2EテストはUnitテストとは逆の特徴があり、以下のような課題があるため今まで手をつけられていませんでした。 そのため、これを解決しないままE2Eテストを導入することはテストのメンテナンスコストを高くする恐れがありました。 E2Eテスト導入するための解決すべき課題 壊れやすく、メンテナンスが大変 ちょっとした文言の変更等で落ちたりする そのたびに修正をしなければならなくなる 導入コストが高い E2Eテストツールは様々あり、ツールの選定や安定運用できるだけの知識の習得が大変 安定性が低い たまに発生する落ちる要因(画面のロード待ち、文言の変更)がいくつかある ピラミッド図 mablを選んだ理由 今回のツール選定では、上で挙げたE2Eテスト導入するための課題をもっとも解決してくれそうだったmablを選び導入に至りました。良いと感じた点は以下のようなところです。 1. コーディング不要でテスト作成が簡単 一番の理由は、コーディングをしなくてもテストが作成できるという点です。 mablTrainerという機能で直感的かつ簡単にテストが作成できます。 また、Flowという小さい操作単位にして他の操作と組み合わせることなどもでき 上手く設計すれば既存のFlowを組み合わせるだけでテストができるのでとても魅力的でした 2. クラウド/SaaS環境なので初期構築コストがない E2Eテストはメンテナンスコストもかかりますが、初期構築のコストも高くさくっとお試しがしづらかったりします。 その点mablはSaaSとして提供されているため、すぐにテストを試せるという点が魅力的でした 3. AIでテストを自動修復、自動画面分析 E2Eテストの難しいところで、少しの変更でテストがコケるのでメンテナンスコストがかかるという点です。 この点に関してはmabl側でAIが自動でテストを修復してくれる設定があるので、これをONにすることで文言変更などの多少の変更は自動で修復されるようになります。 4. Japan Lead 藤原さんの手厚いサポート mablの導入ではJapan Lead 藤原 大さんにサポートを受けながら導入や設計をおこないました。 mablの機能説明だけではなく、どのようにテストをすればいいか解説して頂き 導入後はテストのレビューまでしていただき、手厚いサポートに頭が上がりませんでした。 mablでテストを作る 次に、実際にテストを作っていった過程について紹介します。 mablの構造を知る mablでテストを作成する上で、操作をどの単位で組み合わせて作れるようにしていくかを設計するために、 mablの構造をエンジニアとPMで図を作成しながら認識を合わせました。 コストはかかりましたが、この後の動きはかなりスムーズになるアクションでした。 構造を話した図 テストを作成する テストを作成するにあたって、これまで一番手動テストをしているPOと何をテストするかの認識合わせをしました。 プロダクトにとって大事な機能に問題が起こらないようにPOに最終チェックをしてもらっているため mablでどのようなテストを作っていくかの認識を合わせることで今後のPOのテスト工数も減らせるのでは、という取り組みでした。 そして、その認識合わせの中で上がってきたやるべきテストを選定しmablに落とし込む作業を行いました。 mabl導入により良かったこと 現在は作成したテストをPOの要求仕様確認テストの前に走らせることで既存機能への影響がないかが担保される状態になりました。 これにより、今までPMやエンジニアが既存機能のテストに時間を使っていた部分が自動化されて、新たに追加された機能のみを手動テストすればリリースまでできる状態になりました。 また、既存機能のテストにかけていた時間を開発に使うことができるようになったのも導入した効果かなと思います。 まとめ mablの導入によりこれまでコストがかかっていた既存機能で重要な部分のテストを実現することができました。 まだmablへのテスト追加ができていない箇所も多いので、徐々に自動化していき、より開発に時間を使っていける状態にしていきたいなと思います。
アバター
スマートキャンプでエンジニアをしている瀧川です。 今回、4/26に発売になりました『ユニコーン企業のひみつ ―Spotify で学んだソフトウェアづくりと働き方』を、翻訳者様のご厚意で献本いただいたのでそちらのレビューを書かせていただこうと思います! (翻訳いただいた島田様、角谷様ありがとうございます🙏) 🦄 テック企業のみなさま、テックブログの記事の候補に一冊いかがでしょうか!!!q 🙏 » 🦄 書籍『ユニコーン企業のひみつ』を貴社テックブログでレビューしていただける企業さまを募集します https://t.co/TdfL58TwpS — Kakutani Shintaro (@kakutani) 2021年4月7日 今回の企画に手を挙げさせていただいた背景として、ちょうどここ最近組織のスケーラビリティに課題を感じ、マネジメントに関心を持っていたことがあります。 私は自分自身が俗に言うフルスタックエンジニアとして最前線でなんでもアウトプットし、その背中を見せることでメンバーも活性化し組織としても成長するとずっと考えてきました(昔ながらの職人みたいですね)。 しかし最近になってそれが逆に組織のスケールを阻害しているのでは?もっと俯瞰したときに良い方法があるのでは?と考えるようになりました。 そんなときに件のTweetを拝見し、それに対する一つのヒントとして、大きな成功を収めたSpotifyモデルは参考になるのではと思い反射的に手を挙げていました。 そういった背景の人間が読んで感じた、本書に書いていないこと、 すなわち自分たちで考える必要があること についてツラツラと書かせていただこうと思います。 様々な感想や考察を生む良書だと思いますので、すでに興味を持たれている方は、まずご自身でも手に取っていただき、読了後に再度本記事を見るとより楽しめるかなと思います! www.oreilly.co.jp どんな本なのか 書いていないこと スクワッドのミッションはどのように決定するか データサイエンティストがスクワッドで活躍する方法 スクワッド間での人の異動をどのように実現するか どうやって企業文化をアップデートしていけばよいか 終わりに どんな本なのか 名著と名高い『アジャイルサムライ−達人開発者への道−』を執筆されたJonathan Rasmussonが、自身が(元)ユニコーン企業であるSpotifyでアジャイルコーチ・エンジニアをする中で経験した、 スタートアップの良さを失うことなく組織をスケールするノウハウ(俗に言うSpotifyモデル) についてまとめている著書となっています。 Spotifyモデルとは、スクワッド、トライブ、チャプター、ギルドといった組織体系や全社で進むべき道を示すカンパニーベットといった要素を定義しそれらが協調しあうことで、自己組織化・スケーラビリティが獲得されるといったモデルになっており、それらを実現するためのヒントが作者の経験則など元にまとめられています。 *1 用語 スクワッド 共通のミッションを負った職能横断の少人数チーム トライブ スクワッドをドメインでまとめたグループ チャプター トライブ内の同じ専門性を持ったメンバーのグループ ギルド 同じ専門分野に興味のあるメンバーのグループ(トライブ内などの制限なし) カンパニーベット 会社が取り組みたい重要事項を優先度順に並べたもの 書いていないこと 書いてある内容についてはたくさんのブログなどで取り上げられているかと思うので、本記事では自身を取り巻く環境と照らし合わせたときに疑問に思ったことや、考えたことを書いていこうと思います。 本書内で取り上げられている言葉の定義についても、本記事では詳しく説明しないので、本を手にとっていただくか、Spotifyモデルについて調べていただければと思います。 スクワッドのミッションはどのように決定するか スクワッドは必ずミッションを負い、そのミッションに到達する手段について裁量を与えることが大切だと挙げられていました。 ではスクワッドのミッションとはどのように決定するのでしょうか? 本書では経営リーダーが決めると書かれていました。 一方で経営リーダーが決める指標として、カンパニーベットもあります。 この2つのレベルの指標をどのように決めるべきなのか に難しさを感じました。 またスクワッドのミッションはカンパニーベットより優先されるものだとされており、その点でもどちらに何を据えるか難しいですね。 私見ですが、 カンパニーベットとスクワッドのミッションはタイムスパンと粒度が違う のかなと感じました。 一般的には、会社でやりたいことは長期かつ粗い内容、一方でチームのミッションは短期かつ細かい内容になることが多いかと思います。 しかし本書の例などを見ると、どちらかというとカンパニーベットが短期で具体的な内容で決められている印象を受け、これはスクワッドのミッションはカンパニーベットより優先されるという原則を考えるとある程度納得感があるように感じました。 このあたりは非常に重要なテーマで、自社でもいつもモヤモヤしているところなので、引き続き調べてみようと思っています。 余談ですが本書で取り上げられていたロードマネージャーというロールが重要そうだと感じています。 データサイエンティストがスクワッドで活躍する方法 本書の一つのテーマとしてデータサイエンティストの重要性についても書かれており、スクワッドがデータからインサイトを得て意思決定するために、スクワッド内にデータサイエンティストを持つのが良いとされていました。 私を取り巻く環境に目を向けてみると、私が所属するチームは、PdM、プランナー、デザイナー、エンジニアといった職能の違うメンバーが集まった、まさにスクワッドと呼べるものになっており、日頃から施策立案から実装、リリースまでを協力しながら進めています。 そこで私が感じているのが データの定点観測・仮説検証・施策の効果測定 のやりきれていない感です。 チームがミッションを負い、精度高く試行錯誤(イテレーション)するためには、ミッション達成の定義やスピーディなデータ分析を可能にする基盤が必要だと感じています。 ではなぜ必要性を感じているのに、実際私のチームにはデータサイエンティストがチーム内にいないのか。 一概に理由は挙げられないのですが、一つの要素として、 データ分析のコストの見積もりにくさ があるのではと考えています。 例えば施策を立てるためにデータ分析をする場合、一つのデータを出して施策が決定することはなく、何度もPdMやプランナーと話しつつデータを出すことを繰り返すと思います。 その繰り返しにどれくらい時間がかかるかわからない、分析した結果なにも得られないかもしれない、そういった不確実性が安定した開発フローを阻害するイメージがあります。 その壁を破りデータサイエンティストが活躍するチームはどうすればできるか。 一つは活躍を信じてBetすること、もう一つはデータ分析基盤のイニシャルコストをしっかりとかけることだと感じています。 例えば一歩踏み込んだデータ分析基盤の工夫だと、分析する際に頻出するドメインロジックをすぐに分析に組み込めるようにしたり、新機能開発時に効果測定用のログを漏れなく埋め込むフローを整備するなどがあるかなと思います。 そういったコストをかけることで分析スピードが上がり、現状の開発フローに組み込めるようになるのではと考えています。 余談ですが、実際の取り組みとして近々でLookerの導入を進めており、それを足がかりに理想的な状態を目指しています。 スクワッド間での人の異動をどのように実現するか トライブ内(スクワッド間)での異動は積極的にすべきだとSpotifyでは考えられていたと言及されていました。 これについても納得感があり、私もミッション達成のために必要なリソースがあれば柔軟にアサインできるべきで、メンバーとしても極力ミッションに共感できるチームに移るのがいいのではと考えています。 ただこれについても実現のためには課題があると思います。 現実的に一番大きい問題は異動時のオーバーヘッドかなと考えています。 実際に弊社でも直近で定期的なチームのメンバー入れ替えにチャレンジしていますが、そこで課題として挙がるのが異動時に そのチームの開発フローに合わせるのにコストがかかる ということでした。 例えばリリースまでにどういったチェックが必要か、タスクの詳細をだれとどのように詰めていく必要があるかなどがチーム毎文化として存在するため、それを理解し合わせるのにコストがかかっていました。 (前提として弊社はどのチームもスクラムをベースとしているのですが、定常的な振り返りで改善した結果チームの文化はズレていきます) そういった状況でどのようにオーバーヘッドを抑えていくか。 ここは現在試行錯誤中ですが、本書に書かれていたチャプターに相当する枠組みと、トライブのリーダーでの振り返りが効くのではないかと考えています。 前者については弊社の現状だと、エンジニアが横軸で参加するイベントをいくつか実施しており、隔週でタスクや取り組みを共有する会、雑談会、ペアプロ会、勉強会などを通じてズレを補正するような動きをはじめています。 後者については訳者のあとがきでもSpotifyモデルの変化として取り上げられていた「TPD Trio」が近いかなと思いますが、リーダー同士で定常的に状況や環境の変化を共有し、お互いに真似できるところを探すことでメンバーの負荷が低くなるフローを模索しています。 ここについては人の入れ替わる間隔など含めて課題が多いので、向き合っていく必要があると感じています。 どうやって企業文化をアップデートしていけばよいか Spotifyモデルは2012年にアウトプットされたもので、訳者のあとがきで現在はアップデートされ新たな構成要素も存在することが書かれていました。 やはり組織の形態や企業文化には正解やゴールがなく、様々な状況に応じてアップデートしていく必要性があることに納得感がありました。 ただ、どういったプロセスを経てアップデートしていくのか、これは非常に難しいと感じました。 チームの改善であれば当事者で定常的な振り返りなどを通して、大事にすべきものと変えていくものを見極めていくことができますが、企業文化についてはどうでしょうか。 課題も抽象的になると思いますし、解決のためのアクション起こすとしても関連する人や組織が多くなる分、大きなリスクを負う必要が出てくるでしょうし一筋縄ではいかないですよね。 やはりそこを乗り越えてアップデートしていくためには、経営リーダーとしては会社としてのミッションを示す、メンバーとしては密にリーダー層とコミュニケーションを取るといった、本書でも随所に書かれていたことが最低限必要で、そこから少しずつ堅実に改善を進めるしかないのではないかなと感じています。 終わりに 今回献本いただいた『ユニコーン企業のひみつ ―Spotify で学んだソフトウェアづくりと働き方』を読ませていただき、自社の取り組みや自分の思いも含めてレビューを書かせていただきました。 著者のSpotifyでの経験を元に、スクワッド、トライブ、カンパニーベットといったキーワードを中心として、自己組織化しスケールする組織の一例を示す良書だと感じました。 余談ですが著書の中で「反直感的ベット」という言葉が出てきますが、最近プロダクトに携わる中で少しその考え方が抜けていた気がして、改めてテック企業で働くことの意義を考えさせられました。 本記事を読んで少しでも興味が湧いた方は、ぜひご自身でも手にとって読んでいただければと思います! www.oreilly.co.jp *1 : https://blog.crisp.se/wp-content/uploads/2012/11/SpotifyScaling.pdf より
アバター
またオレ何かやっちゃいました? こんにちは!!!スマートキャンプでエンジニアをしている吉永です! 自己紹介記事はこちら 前回の記事はこちら 弊社の主力サービスであるBOXILはリリースから時間が経っていることもあり、バックエンド・フロントエンドともに様々な技術的負債となる部分を抱えています。 また、その負債の中には普段の業務時間では手をつけにくいものもあるため、定期的に 薪入れ と呼ばれる開発改善日を設けています。 先月行われた薪入れはいつもよりも長い1週間の期間が設定されており、今回の記事ではその中で私が行ったフロントエンド改善の内容や、そこに至った経緯について説明します。 やったこと ビルドフロントの短縮 Reactの試験導入 BOXILのフロントエンドについて 現在のフロントエンドを改善するに至った経緯とその背景 フロントエンド負債の認識のすり合わせ 負債を話し合う会の開催 理想を話し合う会の開催 React導入に向けた動き出し React導入を決定してから行ったこと 最終的に決定したもの ビルド時間の短縮でやったこと thread-loader 他に検討した方法 thread-loaderの導入方法 npmモジュールのインストール Webpackの各種rulesを編集する Reactの導入方法 まとめ やったこと 薪入れではそれぞれで改善する目標を持つのですが、私は CIによるフロントエンドのビルド時間を短縮したい Reactを試験導入してみて、ちょっとした動作をさせてみる と言う2つの目標を設定しました。 結論としては、以下の2点が達成されました。 ビルドフロントの短縮 こちらは、ビルド時間をいかに早くできるかを有志のメンバーで競い合う形で実施され、結果として ローカルビルド時間: 5分8秒 -> 52秒 CIによるビルド時間: 8分 -> 4分 に改善されました。 Reactの試験導入 こちらは本番環境にすぐ反映させるというものではなく、まずは既存の環境で使えるかテストしてみるという意図で、Webpack周りの理解も含めて既存アーキテクチャで「Reactが動けば成功」という目標で導入してみました。 また、TSXが使えるかなどのテストも合わせて実施したため、何をやったの詳細は後述します。 BOXILのフロントエンドについて 先ほど少し説明した通り、BOXILはリリースから6年以上が経過している弊社創業間もなくから存在するプロダクトです。 そのため、時間が経つにつれて技術的な流行りもどんどん変容していき、現在はフロントエンドだけでもかなりの負債や、時代を感じさせられる技術選定が行われている箇所があります。 しかし、その負債を解消しきれずに放置してしまった事による可読性の低下や、新規メンバーの開発参入時にかかる理解コスト、採用面接時に「まだCoffeeScriptで消耗してるの?」と捉えられてしまう悲しさなど様々なデメリットが発生しています。 そのため直近では私の主導でCSSの検索性改善やBEM化などが行われましたが、今回また新たな試みとして、これまでも議論されていたものの解決できていなかったビルド時間の短縮やReactの導入テストに踏み切った次第です。 現在のフロントエンドを改善するに至った経緯とその背景 現状、BOXILのフロントエンドの採用技術は以下のようになっています。 構成は一見するとシンプルですが、 CoffeeScriptやjQueryを使って書かれている画面 AppのCoffeeScriptの中でnew Vue...とすることによりVueコンポーネントとする使い方 Vue + TypeScriptを採用したときに一時期使っていたClassベースのComponent など幅広いレガシーな負債が存在しています。 また、BOXILフロントエンドの今後の改善案として、最近ではデザイナーとの連携を強化するためのデザインシステムの導入案やテストを強化する案などもチーム内で挙がっています。 そのため、まずチーム内でどこを負債と感じ、優先して対応するべきものはどれなのかを決めたいと思いました。 また、対応する理由なども全体で共通認識を持っておくべきだと思い、フロントエンドを話す会という名目で数日に分けてミーティングを設定しました。 フロントエンド負債の認識のすり合わせ チーム内での負債の認識や、優先したい対応の確認などは今後の改善に大きく関わってくると思ったため最優先で対応することにしました。 また、負債を解消するにあたっては今後の展望を見据えたうえで技術選定を行っていきたいと考えていたため、現状の負債と今後の理想といった形の2つのミーティングをBOXIL開発チーム全体で取ることにより今後のロードマップを敷くことまでを目標にしていました。 負債を話し合う会の開催 まず行ったのは負債を話し合う会の開催です。 共同編集できるドキュメントに参加者が思う負債の内容を列挙してもらい、これらに関してどう問題なのかなどを話しました。 ほんの一例ですが、書かれていた内容を貼り付けてみました。 こう見ると、同じVueでも書き方が統一されていない(フォーマット問題)やCoffeeScriptに関する問題などが挙げられていることがわかります。 また、この一連の流れでCIで行っているbuild_front(フロントエンドをビルドするジョブ)があまりにも遅いという問題が再び課題として上がっていました。 理想を話し合う会の開催 次に理想となる環境を話し合ってみました。 前提として、理想は理想であるため、すぐすぐ解決できなくとも、「本来はこうあればいいよね」といったものも込みで話し合うことにしました。 負債の時と同じように各々が感じる理想を列挙してもらい、それに対して参加者全員で工数・解決したいことを踏まえた優先度(やりたいお気持ち度)を1~5ポイントで投票してもらい、最終的に解決したいものを決定しました。 そして、先述したReactに関しての議論も行われ、上記のポイントの高いものやCoffeeScriptなどレガシーな技術が使用されている箇所からスモールステップでReactに作り変えていこうという話になりました。 React導入に向けた動き出し Reactをえいやで導入した結果、後々負債になってしまいましたといった結末は避けたいと感じていました。 その為、これまでVue + TypeScriptの構成を選んでいた弊社開発チームがどれだけReactに学習コストを割けるのか、また現状で構成をシフトした場合に教えられるメンバーがいるのかなどの調査をすることにしました。 リスクとして、React化が途中で頓挫した場合 Vueページ Reactページ CoffeeScriptページ と言った三国志の様な状態になるのがまず懸念としてありました。 また、Webpackが整備されていないことや、この辺りの実装当時に関わっていたメンバーが少ないこともあったため、不安視する声も上がっていました。 しかし、デザインシステム導入の話がデザイナーサイドや一部のエンジニアメンバーで議論されているということもあり、折角であれば最小単位のパーツやあまり要素の多くないページをReactで作り始めることができれば、当初の狙い通り小さい走り出しができるのではないかと考えました。 また、エンジニア採用面でも今後Reactの採用は強みになりそうだという意見や、新しい技術への挑戦という意味でも採用するメリットは高いと感じました。 また、開発チーム全体でReactの導入経験が全くの0というわけではなく、近ごろ走り始めたプロダクトではReactやGoといったこれまで興味はあるものの使ってこなかった技術スタックを試しています。 そこで身につけた知見や技術を横展開できる場としても、メインプロダクトへのReact導入は良い方向に進む可能性が高いと感じました。 React導入を決定してから行ったこと Reactに知見のあるメンバーと開発リーダーを呼んだミーティングを行い、導入するとしてどうやって進めて行こう、何から始めようといった内容を話し合い決定しました。 頓挫したときに切り戻しやすい部分から改修していきたいという意見があり、やり始めるとしたら、BOXILの中でもあまりパーツ数が多い方ではない管理画面のとあるページがいいのではないかとなりました。 また、導入までのフロー案として、 既存機能や新規機能をReactにする 管理画面自体をReactプロジェクトとして引き剥がす と言った2択の意見も出ていました。 しかし、2の方はかなり改修が大掛かりになってしまう上に、現状かけられるコストとしてもそこまでの工数は割けないという判断となり、1を採用することにしました。 そして1を導入するにあたり、Reactの最小構成を作るときに何を予め準備すべきかといった会話から、最小限導入しておくものを決定しました。 また、React導入後に知見を共有したり、開発メンバーが戸惑うことなく開発できるように、周知や勉強会の実施などの意見出しなども行っていました。 そして、一旦の最終目標として Vue Classベースのもの: extendsの書き方に変えていく extendsのもの: そのまま CoffeeScriptまたはCoffeeScript + Vue 順次Reactに置き換えていく として最終的にVueとReactのファイルだけに統一され、その後Reactに順次Vueのものを置き換えていく...という流れが良いだろうと判断しました。 最終的に決定したもの この結果を踏まえて、薪入れで対応するタスクを ビルド時間の短縮 Reactの導入テスト にしました。 ビルド時間の短縮は、煩雑になってしまっているWebpack周りの処理を読み、適切な処理に書き直してあげる必要がありそうだったため、実装理解という点でもReact導入の障壁になり得ると考えられていたリスクの一つを減らせるのでは無いかと思いました。 ビルド時間の短縮でやったこと ビルド時間改善は、参加するメンバーが各々の考えた方法で高速化を試してみるというスタンスで開催しました。 今回は私が行ったthread-loaderを使った短縮を説明します。 thread-loader Webpackを使用して開発を行う場合、rulesの中にファイルの拡張子ごとにloaderを設定し処理を行わせると思います。 thread-loaderでは、そのloaderの処理を並列で実行することによってビルド時間の短縮を可能にします。 他に検討した方法 hard-source-webpack-plugin や esbuild-loader の導入も候補として検討しました。 が、esbuild-loaderにおいてはVueでも同じように使えるのか、それともViteを使っていくべきなのかという問題があった上、現状BOXILではIEユーザーがいまだに一定数いるためIEに対応していない方法では解決できないという結論になりました。 thread-loaderの導入方法 ここからはthread-loaderの導入方法について説明します。 この時参考にさせて頂いた資料はこちらです。 * esbuild導入時にも様々なアドバイスを頂きました。ありがとうございました。 qiita.com npmモジュールのインストール 以下のコマンドでインストールが可能です。 npm i -D thread-loader Webpackの各種rulesを編集する { test: / \ .ts$/, exclude: '/node_modules/' , use: [ // 各loaderの一番最初に記載する { loader: 'thread-loader' , options: { workers: 2, workerParallelJobs: 80, workerNodeArgs: [ '--max-old-space-size=512' ] , name: 'ts-loader-pool' , } } , { loader: 'babel-loader?cacheDirectory' , options: { presets: [ '@babel/preset-env' , ] , } } , { loader: 'ts-loader' , options: { appendTsSuffixTo: [ / \ .vue$/ ] , transpileOnly: hmr, happyPackMode: true } } ] } , これでthread-loaderの導入は完了です。 他にもオプションとして、thread-loaderをwarmupしておくことで、実行時の遅延を防ぐこともできますが今回は時間の関係もあり、一旦直接組み込む形で解決しました。 導入した結果、 ローカルビルド時間: 5分8秒 -> 52秒 CIによるビルド時間: 8分 -> 4分 となり、かなりのビルド時間短縮になりました。 Reactの導入方法 Reactやreact-domなどをインストールした後、各種ファイルを以下のように編集します。 ・webpack ... // tsxも読み込めるように rules: [ { test:/ \ .(ts|tsx)$/, exclude: '/node_modules/' , loader: 'ts-loader' , options: { appendTsSuffixTo: [ / \ .vue$/ ] , transpileOnly: hmr, } } , ] ... // tsxを追加 resolve: { extensions: [ '.vue' , '.js' , '.ts' , '.tsx' ] , alias: { '@' : resolve( './src/script' ) } } , ・tsconfig.json { ... " compilerOptions ": { // これを追加 " jsx ": " react " , } , ... " include ": [ // これを追加 " ./frontend/src/script/**/*.tsx " ] } そして、Vueファイルなどを読み込んでいるエントリーポイントをまとめている場所に、.tsxの形式で適当なコンポーネントを作ってslim内で読み込んでみます。すると... ちょっとわかりにくいですが、BOXILのローカル環境に、よくあるクリックすると数字が増えるコンポーネントを導入してみた例です。 このように.tsxで書かれたReactのコードがしっかりと動作していることがわかりました。 まとめ 今回目標にしていた、ビルド時間の短縮とReactの導入はどちらも達成することができました。 また、Webpack周りの処理を深く理解することが出来たため今後の開発において懸念する点を少し減らすことが出来たと考えています。 課題として hard-source-webpack-pluginとthread-loaderの連携が上手くいかなかった Webpackの理解に時間を取られすぎてしまい、jestなどの導入までには至らなかった など、今後も薪入れの時間などを使って改善しなくてはいけないと感じる点が多かったです。 さらに、Reactを導入することだけが本来の目標ではなく、最終的な目標はCoffeeScriptなどのレガシーな環境をモダンにするという内容のため、ここから更に知見を深め、チーム全体での技術レベルの向上に努めなければいけないと感じています。 レガシー環境の改善は、これまでプロダクトが歩んできた分だけ溜まってしまう物ではあると認識しているため、これからも継続して負債の返済にあたり、明日結果を出すことに期待するのではなく、一年後、二年後を見据えた開発ができる環境作りに励みたいと思いました。 この記事が同じ課題を抱えている方々の助けになれば幸いです。
アバター
スマートキャンプ、エンジニアの入山です。 弊社のBOXILは、AWSを基盤としたRailsベースのアプリケーションです。 以前のブログ でもECS移行におけるTipsを紹介しましたが、2020年10月頃よりEC2基盤からECS/Fargate基盤へのインフラ移行に取り組んでおり、2021年5月に新しい基盤が無事本番稼働を迎えました。 今回は、弊社BOXILのインフラ移行について、概要を紹介したいと思います。 BOXILのインフラについて EC2基盤の課題と移行の背景 EC2基盤のアーキテクチャ ECS/Fargate基盤のアーキテクチャ 移行による効果 Pros Cons まとめ BOXILのインフラについて BOXILは、弊社創業から程なくして誕生し、弊社と共に成長してきた主力プロダクトで、リリースから6年以上が経過しています。そんなBOXILのインフラは、プロダクト発足時からAWSのEC2基盤で構築・運用されてきました。 創業期から存在するプロダクトということもあり、初期のインフラはいわゆるアンチパターンにあたる要素を含む構成で、インフラ起因の障害も起きていたと聞いています。その後、事業やエンジニア組織の拡大に合わせて、インフラ構成やCI/CDなど様々な面で改善を繰り返し、直近ではインフラ起因の障害もなく安定稼働できる状態になっていました。 EC2基盤の課題と移行の背景 前述の通り、BOXILはインフラ構成やCI/CDなどの改善を何度か実施しています。 これらの改善によって、安定稼働に加えてCI/CDや運用面も適度に自動化されていたため、致命的な不便さや欠陥はありませんでした。 しかし、長年運用してきたEC2基盤では、やはり簡単には改修へ踏み切れない部分もあり、致命的ではないものの以下のような課題や技術的負債を抱えていました。 構成管理 OpsWorksでの煩雑な構成管理 スケーリングに手動対応が必要(最大+2台) 環境変数が適切に管理できてない サーバー障害時は手動対応が必要 CI/CD デプロイ時間が長い 約45分 / staging~productionまでの1リリース 自動ロールバックができない(要再デプロイ) デプロイ環境依存のエラー Jenkinsサーバーがたまに落ちる これらは、今までも少なからず開発効率やDX向上の妨げとなっていましたが、基本的に対応頻度の少ない事象だったこともあり、暗黙的に対応しない(できない)ものとして扱われていました。しかし、今後更に速いスピードで事業や組織の拡大を達成していく上で、将来的により大きな負債となる懸念があったため、今回のタイミングでインフラ移行を実施することにしました。 尚、今回の移行プロジェクトは、脱EC2を始めとする前述の負債解消を主目的とし、最小工数・最低限でECS/Fargateへ置き換えることを目標にしていました。 EC2基盤のアーキテクチャ BOXILはいわゆる一般的な構成のWebアプリケーションで、Web/APサーバーを中心にRDSやElastiCache、S3、ログ管理サーバーなどで構成されています。 移行前のEC2基盤のアーキテクチャは、各サーバーがEC2で構築された以下のような構成となっていました。 尚、デプロイについては、基本的に全てJenkinsのWorkflowで管理しており、デプロイサーバーから各サーバーに対して実施していました。 ECS/Fargate基盤のアーキテクチャ 移行後のECS/Fargate基盤のアーキテクチャは、移行前にEC2で構成されていた各サーバーをECS/Fargateによるコンテナ(タスク)に置き換えた構成となっています。 また、デプロイについては、デプロイサーバー(Jenkins)を廃止し、CircleCIによるDockerイメージビルドとCodeDeployによるECS/Fargateへのデプロイを組み合わせた構成となっています。 移行による効果 ECS/Fargate基盤への移行作業は、技術検証・環境構築・テスト・移行までの全工程を、私と当時内定者インターンの二人で計約半年ほどの工数で実施しました。社内にECSの前例がなく技術キャッチアップや技術検証も含んでいたため、最小工数・最低限での移行でも安全に稼働させるためにはそれなりのコストが掛かりましたが、掛かったコスト以上の効果はあったと感じています。 弊社における具体的な移行の効果としては、以下のようなものが挙げられます。 Pros 構成管理 コンテナ化&Fargate化による環境管理コスト減 サーバー管理不要 DockerによるImmutableな環境 脱OpsWorks オートスケールが容易&速い 障害時の自動復旧 パラメタストア + コンテナ定義による環境変数管理の柔軟性向上 CI/CD デプロイ時間短縮(約15分短縮) 約30分 / staging~productionまでの1リリース CodeDeployによるBlue/Greenデプロイ 即時自動ロールバックが可能 カナリアリリースなど柔軟に選択可能 脱Jenkins SlackでのChatOps (参考記事)SlackでChatOps!CodeDeployのBlue/Greenデプロイを操作する方法 コスト サーバー費用削減(約12万 / 月) コンテナ化による割当スペック最適化 オートスケールによるコスト最適化 Cons Fargate特有の制約がある カーネルパラメタ制限 CPU選択不可 コンテナログインに一工夫が必要 (参考記事)待望!Amazon ECSのコンテナにログインできるAmazon ECS Execを試してみた (参考記事)AWS Fargateで動いているコンテナにログインしたくて Systems Manager の Session Manager を使ってみた話 技術キャッチアップが必要 EC2とECS/Fargateでは設計や運用面が大きく異なる 各メンバーへの教育も必要 ECS/Fargateに関する情報が少ない 基本的には、技術的に新しく進歩した基盤への運用効率や利便性向上を目的とした移行だったため、Prosが多くかなりの効果が得られました。 Consに関しては、Fargateでサーバー管理が不要になった代わりの制約や根本的に利用する技術の変化に起因するもので、移行する上ではある程度は避けられない部分といった印象です。Fargateに関する制約については、Fargateのバージョンアップに伴って柔軟性が向上してきているので、今後に期待です。 まとめ 今回は、弊社BOXILのインフラをEC2からECS/Fargateへ移行した話の概要をご紹介しました! インフラは、移行コストやリスクの観点からなかなか手がつけにくい部分ではありますが、刷新することで中長期的な開発効率向上が期待できます。 近年の流れとして、コンテナ技術やサーバーレスアーキテクチャの発展によって、低レイヤな基盤部分をエンジニアが意識しなくてよくなってきています。 こういった流れも含めてインフラを見直すことで、様々なメリットを得られる機会に繋がるのではないでしょうか。インフラ移行の一例として、少しでも参考になれば幸いです!
アバター
スマートキャンプのプロダクトマネージャーの郷田です。 皆さんは普段の業務で、以下のように感じる場面はありませんか? - 「同じチームで働くあの人と、いつもなんだか認識がずれてるかもと感じる」 - 「一通り会議はやったものの、なんだかいまいち話しきれてないようなモヤモヤがある」 - 「あの人にはもっと注力してもらいたいことがあるのに、なかなかそこまでやってもらえない」 こういった場面に遭遇したときには、リーンコーヒーを実施されることをおすすめします! この記事では、チームのMTGで活用してみていただきたい「リーンコーヒー」を紹介します。 リーンコーヒー(Lean Coffee)とは? リーンコーヒーの進め方 準備するもの その1:トピック出しと優先順位の決定(5分~15分) その2:トピックのディスカッション(10分〜45分) 初めてのリーンコーヒーでのハマりどころ 継続するかの判断をせずに会話を続けてしまう 9分(5分+3分+1分)が経ったけどまだ会話を続けてしまう リーンコーヒーの強み 参加者の興味が強いトピックを網羅的に話せること アジェンダが決まってないMTGでも時間を有効的に使える 個人の興味とチームの興味を分けられる リーンコーヒーの弱み 1つのトピックについてより踏み込んだ会話が必要なとき 状況に合わせて、リーンコーヒーを活用する チーム内の認識をあわせる 通常の会議を補足する メンバーの指向性を把握する まとめ リーンコーヒー(Lean Coffee)とは? アジェンダの無いMTGと呼ばれている会議形式のことです。 リーンコーヒーには、MTGのアジェンダの作成からその議論までをMTG時間内で効果的に行うことができる進め方が準備されています。 詳細は、本家サイトも是非ご覧ください。 Lean Coffee | Start one in your city! ここでは、誰もが実施しやすく理解しやすいものとなるように、私が普段から活用しているアレンジした形式でのリーンコーヒーを紹介します。 リーンコーヒーの進め方 リーンコーヒーはとてもシンプルです! リーンコーヒーは状況に応じて細かい進め方を変更したり、前後に別のアクションをとったりできるのですが、ここでは一番簡単な方法を紹介します。 準備するもの まずは、リーンコーヒーをする上で必要となるものを紹介します。 オフラインMTGの場合 付箋 サインペン ドットシール タイマー(スマホでOK。音が鳴るものがおすすめ) オンラインMTGの場合 共同編集ができるホワイトボードツール(Miro、FigJam、jamboard、Googleスライド、等) この会議では、アジェンダを自分たちで決めていくので、効率化のために付箋やサインペンを利用します! Miro等のツールを使うことで準備物なく簡単に実施することができるので、オフラインのMTGでもホワイトボードツールを使うのもありだと思います! その1:トピック出しと優先順位の決定(5分~15分) ①この会議の参加者で話したいトピックを、付箋に書き出します。 書き出す時間は2分で、タイマーで測ります。 時間が長いと、一人のトピック数が増えMTG時間が長くなるので、一人あたり1〜3トピック程度考えられる時間にするとMTG進行がスムーズになります。 参加人数が5人より多い場合は、1分でも十分です ②各々が書いたトピックを参加者に紹介します。 各トピックについてディスカッションするかは後で決めるため、ここではトピックの背景・話たいことを1トピック15秒程度でサクサクと共有していくのがおすすめです。 ③すべてのトピックに対して、この参加者で話したいことにドット投票(1人3票)をします。 話したいものに投票しましょう。この後の議論を効果的にする上でとても重要なポイントです。 トピックのディスカッションをすることが重要なので、ドット投票も1分のタイマーで締め切るのがおすすめです。 ④投票数が多いものから並び替えます 同票だったものも、悩まず順番をつけましょう。リーンコーヒーでは網羅的にトピックを話せるので同票内での並び順に大きな差はないです。 ここまででトピック出しが終了です。 テキストに書くと長いのですが、実際のアクションは画像の通りなのでとても簡単なプロセスになります。 その2:トピックのディスカッション(10分〜45分) 以下画像の1トピックのライフサイクルに合わせて、優先度の高いものから1トピックずつ話していきます。 1トピックのライフサイクルをテキストで書くと以下の流れになります。 並んだ優先順位の一番上のトピックの話をします。 タイマーを5分間に設定し、制限時間内でトピックの話をします。 制限時間に達したら、会話を継続したい人はグッドサインを出します。 半数がグッドサインを出した場合、タイマーを3分で継続してトピックを話します。半数がグッドサインを出さなかった場合、次のトピックに移ります。 制限時間に達したら、会話を継続したい人はグッドサインを出します。 半数がグッドサインを出した場合、タイマーを1分で継続してトピックを話します。半数がグッドサインを出さなかった場合、次のトピックに移ります。 制限時間に達したら、次の優先順位のトピックに話を移ります。 これを、MTG時間の終わりまで繰り返します。リーンコーヒーはたったこれだけの軽量な会議形式です。 初めてのリーンコーヒーでのハマりどころ 継続するかの判断をせずに会話を続けてしまう 5分・3分・1分経過した時点で、会話をしている途中でもすぐにグッドサインをしてもらうルールがあります。 しかし、会話の切れ目を見つけられず、タイマーが鳴っても会話をしたくなってしまいズルズルと話してしまい時間を守れない場合があります。 参加者は全員ルールを厳守し、誰が話していたとしてもタイマーが鳴ったらグッドサインを出すか確認しましょう。 9分(5分+3分+1分)が経ったけどまだ会話を続けてしまう トピックの最後の1分が終わり、タイマーが鳴った後も継続してそのまま会話を続けたい場合があります。 リーンコーヒーでは複数のトピックを網羅できることが良さの一つなので、そのままトピックを変えずに会話することはあまり良くありません。 そのような状況が繰り返される場合は、9分(5分+3分+1分)を超えてトピックを継続したい場合のルールを決めておくことがおすすめです。 (リーンコーヒーとは別時間で実施する、重要なテーマなら最後5分追加する、etc...) リーンコーヒーの強み 参加者の興味が強いトピックを網羅的に話せること 進め方どおりの流れで実施できた場合、1時間のMTG時間内で話したい5トピック程度のディスカッションをすることができます。 そのため、参加者が興味のあるテーマを網羅的に話せるので、参加者間で認識を共通化したり、課題をディスカッションすることができます。 参考として、図の例では45分間で6トピックについて話せている様子です。 アジェンダが決まってないMTGでも時間を有効的に使える 急遽開かれたMTGでは悶々として終わることも多いかと思います。 リーンコーヒーでは議題決めを簡潔に行い、より詳しく話したいトピックは長く話せ、そうでないトピックはすぐ終わる仕組みとなっています。 また、進め方は簡単なため、リーンコーヒーを知らない人でもその場で教わりながら実施できるので、急遽参加することになった人でも大丈夫です。 個人の興味とチームの興味を分けられる トピックの洗い出しから優先順位を付ける工程では、MTGの参加者全員で話したいことに投票します。 そのため、全トピックに関して自身の関心事と他人の関心事の違いが優先順位をつけたタイミングで理解できます。 参加者の興味関心がわかれば、自身の振る舞いやネクストアクションの参考にすることもできます。 リーンコーヒーの弱み 1つのトピックについてより踏み込んだ会話が必要なとき 複数のトピックを網羅的に話してく進め方のため、既知の共通の課題を深く突き詰めて行くMTGなどでは逆効果となります。 もしトピックの中で短い時間では話しきれないテーマがでてきたら、必要な参加者のみで別MTGを設定するなどのアクションをとることをオススメします。 状況に合わせて、リーンコーヒーを活用する チーム内の認識をあわせる 「同じチームで働くあの人と、いつもなんだか認識がずれてるかもと感じる」 そう感じた場合、チームとして同じ方向を向いていない状況の可能性があります。 そのため、以下のようにリーンコーヒーのカスタマイズをすることをおすすめします。 トピックにテーマを決める 「チームでモヤモヤすること」などのテーマを決めることでチーム内の疑問を解消しやすくなります。 トピック出しの時間を5分間程度まで伸ばし、上がったトピックをグルーピング化する。 モヤモヤしてることを書き出すだけでも課題の自己理解になります。 トピックが多くなるので、グルーピング化することで幅広いトピックが話せるようになります。 1トピックの議論時間を、3分・2分・1分にする 1トピック最大6分になるので、より多くのトピックについてチーム内の疑問を話すことができます。 通常の会議を補足する 「一通り会議はやったものの、なんだかいまいち話しきれてないようなモヤモヤがある」 そう感じた場合、会議内ですべてのアジェンダが網羅されていない可能性があります。 そのため、以下のようにリーンコーヒーを組み込むことがおすすめです。 一番モヤモヤする会議の最後に「リーンコーヒー」を入れ込む。 会議の最後20分ぐらいをリーンコーヒーの実施時間として入れる。 直前まで会議でモヤモヤしているはずなので、一番話したいトピックを挙げやすいです。 一人が出せるトピックは1つだけにする 20分と短い時間なので多くトピックを挙げることより、重要なトピックを多く話すほうが重要です。 メンバーの指向性を把握する 「あの人にはもっと注力してもらいたいことがあるのに、なかなかそこまでやってもらえない」 そう感じた場合、相手は他のことを課題と思っているなどの、現状を正しく認識できていない可能性があります。 そのため、以下のようにリーンコーヒーと振り返りを実施することがおすすめです。 1時間のリーンコーヒーを行い最後に、Tryを決める。 この1週間でできそうなTRYを最低で1つから最大で3つまで決めることで、目の前の課題をなくしていけます。 翌週から週1でKPTをやる KPTを週1回実施することでチームの現在の課題や取り組んでいるものが明らかになり、状況を把握しやすくなります。 KPTにより週に1回のTryをまわしていくことで、目の前のTryの繰り返しにより本来解決すべき問題に目が向けれるようになります。 KPTに関しては、別の記事も参考にされてください。 tech.smartcamp.co.jp まとめ 今回はリーンコーヒーの紹介をさせていただきました。 リーンコーヒーにより多くのチームがより成果を上げられるようになれば幸いです!
アバター
こんにちは!今年の 4 月からスマートキャンプに入社し、只今新卒エンジニア研修期間中の中田です。本記事は、インターフェース定義の悩みを解決するために gRPC、Protocol Buffers を調査してみた!という内容のエントリです。 背景 gRPC とは Protocol Buffers とは 4 つの通信方式を試してみた 実装 準備 インターフェース定義 コンパイル サーバーとクライアントの実装 UnaryCall ClientStreamingCall ServerStreamingCall BidirectionalStreamingCall ドキュメント生成 学び まとめ 背景 新卒エンジニア研修では、同期のメンバーと 2 人で Go (REST API) + React/TS 構成の SPA を作っています。 このアプリの開発では、Server - Client 間でインターフェースの定義を一元化できておらず、それぞれでリクエスト/レスポンスの型を定義しているために、一方に修正が入ったタイミングでもう一方も修正する必要があり、面倒だなぁと感じていました。 また、インターフェースの定義内容をドキュメントなどで管理していないため、開発メンバー間での認識にずれが生じてしまうこともありました。 この辺りの解決策を考えていた際に、gRPC や Protocol Buffers について知り、興味を持ったので調査してみました。 gRPC とは gRPC は Google が開発した RPC のフレームワークです。 データのシリアライズとインターフェースの定義に Protocol Buffers を用い、プログラミング言語に依存しない実装で高速な通信が可能になるという特徴があります。 Protocol Buffers とは 構造化データをネットワーク経由で送信できる形へシリアライズする役割と、構造化データ定義用の IDL(Interface Depription Language)としての役割を機能として備えています。 公式では以下のように定義されています。 プロトコルバッファは、構造化されたデータをシリアル化するための、言語やプラットフォームに依存しない、Google の拡張可能なメカニズムです。 引用: https://developers.google.com/protocol-buffers/ 4 つの通信方式を試してみた ※本記事で扱っているコードは ココ に置いてあります。 gRPC では Stream を利用して、1 リクエスト内でクライアントとサーバー間でのメッセージのやりとりを複数回行うことができます。 これにより、gRPC では以下の 4 つの方式で通信ができます。 Unary RPC (シンプル RPC) Server streaming RPC (サーバーサイドストリーミング RPC) Client streaming RPC (クライアントサイドストリーミング RPC) Bidirectional streaming RPC (双方向ストリーミング RPC) 引用: https://grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle それぞれに、リクエストとレスポンスの関係が以下のように変化します。 Unary RPC - リクエスト:レスポンス = 1:1 Server streaming RPC - リクエスト:レスポンス = 1:N Client streaming RPC - リクエスト:レスポンス = N:1 Bidirectional streaming RPC - リクエスト:レスポンス = N:N 今回は上記の各通信方式を Go で実装してみます。 実装 準備 初めに、以下をインストールします。 Protocol Buffers v3 grpc-go( https://pkg.go.dev/google.golang.org/grpc ) protoc-gen-go( https://pkg.go.dev/github.com/golang/protobuf/protoc-gen-go ) > brew install protobuf > go get -u google.golang.org/grpc > go get -u github.com/golang/protobuf/protoc-gen-go インターフェース定義 次に、.proto ファイルにインターフェースの定義を書いていきます。 今回は 4 つの通信方式を試すため、service Call に対して各通信方式の service メソッドを定義しています。 ./proto/call.proto syntax = "proto3" ; package call; option go_package = "gen/pb" ; service Call { // Unary Request:Response = 1:1 rpc UnaryCall (CallRequest) returns (CallResponse) {} // ClientStreaming Request:Response = N:1 rpc ClientStreamingCall (stream CallRequest) returns (CallResponse) {} // ServerStreaming Request:Response = 1:N rpc ServerStreamingCall (ServerStreamingCallRequest) returns (stream CallResponse) {} // BidirectionalStreaming Request:Response = N:N rpc BidirectionalStreamingCall (stream CallRequest) returns (stream BidirectionalStreamingResponse) {} } message CallRequest { string name = 1 ; } message CallResponse { string message = 1 ; } message ServerStreamingCallRequest { string name = 1 ; uint32 responseCnt = 2 ; } message BidirectionalStreamingResponse { map< string , uint32 > callCounter = 1 ; } 直感的な記法で定義できて分かりやすいなという印象でした。 コンパイル 定義した.proto ファイルを protoc でコンパイルします。 > protoc --proto_path ./proto --go_out=plugins=grpc: ${APP_ROOT} call.proto ./gen/pb 以下に call.pb.go ファイルが自動生成されました。 生成された call.pb.go には以下のような内容が含まれています。 定義した message に対応する構造体 type CallRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } service メソッドを呼び出す gRPC クライアント type CallClient interface { // Unary Request:Response = 1:1 UnaryCall(ctx context.Context, in *CallRequest, opts ...grpc.CallOption) (*CallResponse, error ) // ClientStreaming Request:Response = N:1 ClientStreamingCall(ctx context.Context, opts ...grpc.CallOption) (Call_ClientStreamingCallClient, error ) // ServerStreaming Request:Response = 1:N ServerStreamingCall(ctx context.Context, in *ServerStreamingCallRequest, opts ...grpc.CallOption) (Call_ServerStreamingCallClient, error ) // BidirectionalStreaming Request:Response = N:N BidirectionalStreamingCall(ctx context.Context, opts ...grpc.CallOption) (Call_BidirectionalStreamingCallClient, error ) } type callClient struct { cc grpc.ClientConnInterface } func NewCallClient(cc grpc.ClientConnInterface) CallClient { return &callClient{cc} } func (c *callClient) UnaryCall(ctx context.Context, in *CallRequest, opts ...grpc.CallOption) (*CallResponse, error ) { out := new (CallResponse) err := c.cc.Invoke(ctx, "/call.Call/UnaryCall" , in, out, opts...) if err != nil { return nil , err } return out, nil } service メソッドを実装する gRPC サーバーのインターフェース type CallServer interface { // Unary Request:Response = 1:1 UnaryCall(context.Context, *CallRequest) (*CallResponse, error ) // ClientStreaming Request:Response = N:1 ClientStreamingCall(Call_ClientStreamingCallServer) error // ServerStreaming Request:Response = 1:N ServerStreamingCall(*ServerStreamingCallRequest, Call_ServerStreamingCallServer) error // BidirectionalStreaming Request:Response = N:N BidirectionalStreamingCall(Call_BidirectionalStreamingCallServer) error } サーバーとクライアントの実装 次に、生成したコードを用いて、サーバーとクライアントの実装をしていきます。 実装は公式に用意されている 各通信方式の実装例 を参考に進めました。 実際に書いたコードは以下になります。 ./client/cmd/main.go package main import ( "context" "grpc-lesson/gen/pb" "io" "log" "time" "google.golang.org/grpc" ) const ( address = "localhost:50051" ) func runUnaryCall(c pb.CallClient, name string ) error { log.Println( "--- Unary ---" ) in := &pb.CallRequest{Name: name} ctx, cancel := context.WithTimeout(context.Background(), time.Second* 10 ) defer cancel() res, err := c.UnaryCall(ctx, in) if err != nil { return err } log.Printf( "response: %s \n " , res.GetMessage()) return nil } func runClientStreamingCall(c pb.CallClient, names [] string ) error { log.Println( "--- ClientStreaming ---" ) ctx, cancel := context.WithTimeout(context.Background(), time.Second* 10 ) defer cancel() stream, err := c.ClientStreamingCall(ctx) if err != nil { return err } for _, name := range names { in := &pb.CallRequest{Name: name} if err := stream.Send(in); err != nil { if err == io.EOF { break } return err } time.Sleep(time.Second) } res, err := stream.CloseAndRecv() if err != nil { return err } log.Printf( "response: %s" , res.GetMessage()) return nil } func runServerStreamingCall(c pb.CallClient, name string , responseCnt uint32 ) error { log.Println( "--- ServerStreaming ---" ) in := &pb.ServerStreamingCallRequest{Name: name, ResponseCnt: responseCnt} ctx, cancel := context.WithTimeout(context.Background(), time.Second* 30 ) defer cancel() stream, err := c.ServerStreamingCall(ctx, in) if err != nil { return err } for { res, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } log.Printf( "response: %s" , res.GetMessage()) } return nil } func runBidirectionalStreamingCall(c pb.CallClient, names [] string ) error { log.Println( "--- BidirectionalStreaming ---" ) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() stream, err := c.BidirectionalStreamingCall(ctx) if err != nil { return err } done := make ( chan struct {}) go recv(done, stream) if err := send(names, stream); err != nil { return err } <- done return nil } func send(names [] string , stream pb.Call_BidirectionalStreamingCallClient) error { for _, name := range names { in := &pb.CallRequest{Name:name} if err := stream.Send(in); err != nil { return err } } if err := stream.CloseSend(); err != nil { return err } return nil } func recv(done chan struct {}, stream pb.Call_BidirectionalStreamingCallClient) { for { res, err := stream.Recv() if err == io.EOF { close (done) return } if err != nil { log.Fatalln(err) } log.Printf( "response: %v \n " , res.CallCounter) } } func main() { conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf( "did not connect %v" , err) } defer conn.Close() c := pb.NewCallClient(conn) if err = runUnaryCall(c, "John" ); err != nil { log.Fatalln(err) } names := [] string { "John" , "Paul" , "George" , "Ringo" } if err = runClientStreamingCall(c, names); err != nil { log.Fatalln(err) } if err = runServerStreamingCall(c, "John" , 10 ); err != nil { log.Fatalln(err) } names = [] string { "John" , "Paul" , "John" , "George" , "Ringo" , "Paul" , "John" , "Paul" , "George" , "John" } if err = runBidirectionalStreamingCall(c, names); err != nil { log.Fatalln(err) } } ./server/cmd/main.go package main import ( "context" "errors" "fmt" "grpc-lesson/gen/pb" "io" "log" "net" "time" "google.golang.org/grpc" ) const port = ":50051" type CallServer struct { pb.UnimplementedCallServer } func (s *CallServer) UnaryCall(ctx context.Context, in *pb.CallRequest) (*pb.CallResponse, error ) { log.Println( "--- Unary ---" ) log.Printf( "request: %s \n " , in.GetName()) resp := &pb.CallResponse{} resp.Message = fmt.Sprintf( "Hello. I'm %s" , in.GetName()) return resp, nil } func (s *CallServer) ClientStreamingCall(stream pb.Call_ClientStreamingCallServer) error { log.Println( "--- ClientStreaming ---" ) message := "Hello. We're" for { in, err := stream.Recv() if err == io.EOF { return stream.SendAndClose(&pb.CallResponse{Message: message}) } if err != nil { return err } log.Printf( "request: %s \n " , in.GetName()) message = fmt.Sprintf( "%s %s" , message, in.GetName()) } } func (s *CallServer) ServerStreamingCall(in *pb.ServerStreamingCallRequest, stream pb.Call_ServerStreamingCallServer) error { log.Println( "--- ClientStreaming ---" ) log.Printf( "request: %s \n " , in.GetName()) var message string for i := uint32 ( 1 ); i <= in.ResponseCnt; i++ { if i <= 5 { message = fmt.Sprintf( "Hello. I'm %s" , in.GetName()) } else { message = fmt.Sprintf( "I'm so tired. (%s)" , in.GetName()) } if err := stream.Send(&pb.CallResponse{Message: message}); err != nil { return err } time.Sleep(time.Second) } return nil } func (s *CallServer) BidirectionalStreamingCall(stream pb.Call_BidirectionalStreamingCallServer) error { log.Println( "--- BidirectionalStreaming ---" ) counter := make ( map [ string ] uint32 ) for { in, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err } log.Printf( "request: %s \n " , in.GetName()) counter[in.Name] += 1 res := &pb.BidirectionalStreamingResponse{CallCounter: counter} if err := stream.Send(res); err != nil { return err } } } func main() { fmt.Printf( "server is listening on port%s \n " , port) if err := set(); err != nil { log.Fatalln(err.Error()) } } func set() error { lis, err := net.Listen( "tcp" , port) if err != nil { log.Fatalln(err) } s := grpc.NewServer() pb.RegisterCallServer(s, &CallServer{}) if err := s.Serve(lis); err != nil { return errors.New( "serve is failed" ) } return nil } 各通信方式の実装を見ていきます。 UnaryCall UnaryCall はリクエスト:レスポンス=1:1 の通信です。 ソースコード ./proto/call.proto service Call { // Unary Request:Response = 1:1 rpc UnaryCall (CallRequest) returns (CallResponse) {} ... } message CallRequest { string name = 1 ; } message CallResponse { string message = 1 ; } ./client/cmd/main.go func runUnaryCall(c pb.CallClient, name string ) error { log.Println( "--- Unary ---" ) in := &pb.CallRequest{Name: name} ctx, cancel := context.WithTimeout(context.Background(), time.Second* 10 ) defer cancel() res, err := c.UnaryCall(ctx, in) if err != nil { return err } log.Printf( "response: %s \n " , res.GetMessage()) return nil } func main() { ... c := pb.NewCallClient(conn) if err = runUnaryCall(c, "John" ); err != nil { log.Fatalln(err) } ... } UnaryCall のクライアントでは、CallRequest をサーバーへ投げ CallResponse を受け取りメッセージを log に吐いています。 ./server/cmd/main.go func (s *CallServer) UnaryCall(ctx context.Context, in *pb.CallRequest) (*pb.CallResponse, error ) { log.Println( "--- Unary ---" ) log.Printf( "request: %s \n " , in.GetName()) resp := &pb.CallResponse{} resp.Message = fmt.Sprintf( "Hello. I'm %s" , in.GetName()) return resp, nil } UnaryCall のサーバーでは、CallRequest を受け取って log へ吐き、受け取った CallRequest.Name をもとに CallResponse を生成してクライアントを返しています。 実行結果 サーバー 2021/06/08 00:57:37 --- Unary --- 2021/06/08 00:57:37 request: John クライアント 2021/06/08 00:57:37 --- Unary --- 2021/06/08 00:57:37 response: Hello. I'm John ClientStreamingCall ClientStreamingCall はリクエスト:レスポンス=N:1 の通信です。 ソースコード ./proto/call.proto service Call { ... // ClientStreaming Request:Response = N:1 rpc ClientStreamingCall (stream CallRequest) returns (CallResponse) {} ... } message CallRequest { string name = 1 ; } message CallResponse { string message = 1 ; } ./client/cmd/main.go func runClientStreamingCall(c pb.CallClient, names [] string ) error { log.Println( "--- ClientStreaming ---" ) ctx, cancel := context.WithTimeout(context.Background(), time.Second* 10 ) defer cancel() stream, err := c.ClientStreamingCall(ctx) if err != nil { return err } for _, name := range names { in := &pb.CallRequest{Name: name} if err := stream.Send(in); err != nil { if err == io.EOF { break } return err } time.Sleep(time.Second) } res, err := stream.CloseAndRecv() if err != nil { return err } log.Printf( "response: %s" , res.GetMessage()) return nil } func main() { ... c := pb.NewCallClient(conn) ... names := [] string { "John" , "Paul" , "George" , "Ringo" } if err = runClientStreamingCall(c, names); err != nil { log.Fatalln(err) } ... } ClientStreamingCall のクライアントでは、1 秒おきに CallRequest をサーバーへ投げ、全て投げ終わったら 1 つのレスポンスを受け取り、受け取ったメッセージを log に吐いています。 ./server/cmd/main.go func (s *CallServer) ClientStreamingCall(stream pb.Call_ClientStreamingCallServer) error { log.Println( "--- ClientStreaming ---" ) message := "Hello. We're" for { in, err := stream.Recv() if err == io.EOF { return stream.SendAndClose(&pb.CallResponse{Message: message}) } if err != nil { return err } log.Printf( "request: %s \n " , in.GetName()) message = fmt.Sprintf( "%s %s" , message, in.GetName()) } } ClientStreamingCall のサーバーでは、CallRequest を複数回受け取って log へ吐き、受け取った複数のリクエストをもとに 1 つの CallResponse を生成して返しています。 実行結果 サーバー 2021/06/08 00:57:37 --- ClientStreaming --- 2021/06/08 00:57:37 request: John 2021/06/08 00:57:38 request: Paul 2021/06/08 00:57:39 request: George 2021/06/08 00:57:40 request: Ringo クライアント 2021/06/08 00:57:37 --- ClientStreaming --- 2021/06/08 00:57:41 response: Hello. We're John Paul George Ringo ServerStreamingCall ServerStreamingCall はリクエスト:レスポンス=1:N の通信です。 ソースコード ./proto/call.proto service Call { ... // ServerStreaming Request:Response = 1:N rpc ServerStreamingCall (ServerStreamingCallRequest) returns (stream CallResponse) {} ... } message ServerStreamingCallRequest { string name = 1 ; uint32 responseCnt = 2 ; } message CallResponse { string message = 1 ; } ./client/cmd/main.go func runServerStreamingCall(c pb.CallClient, name string , responseCnt uint32 ) error { log.Println( "--- ServerStreaming ---" ) in := &pb.ServerStreamingCallRequest{Name: name, ResponseCnt: responseCnt} ctx, cancel := context.WithTimeout(context.Background(), time.Second* 30 ) defer cancel() stream, err := c.ServerStreamingCall(ctx, in) if err != nil { return err } for { res, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } log.Printf( "response: %s" , res.GetMessage()) } return nil } func main() { ... c := pb.NewCallClient(conn) ... if err = runServerStreamingCall(c, "John" , 10 ); err != nil { log.Fatalln(err) } ... } ServerStreamingCall のクライアントでは、Name と ResponseCnt というフィールドを持つ ServerStreamingCallRequest をサーバーへ投げ、複数回の CallResponse を受け取り、受け取ったそれぞれのメッセージを log に吐いています。 ./server/cmd/main.go func (s *CallServer) ServerStreamingCall(in *pb.ServerStreamingCallRequest, stream pb.Call_ServerStreamingCallServer) error { log.Println( "--- ClientStreaming ---" ) log.Printf( "request: %s \n " , in.GetName()) var message string for i := uint32 ( 1 ); i <= in.ResponseCnt; i++ { if i <= 5 { message = fmt.Sprintf( "Hello. I'm %s" , in.GetName()) } else { message = fmt.Sprintf( "I'm so tired. (%s)" , in.GetName()) } if err := stream.Send(&pb.CallResponse{Message: message}); err != nil { return err } time.Sleep(time.Second) } return nil } ServerStreamingCall のサーバーでは、ServerStreamingCallRequest を受け取って Name を log へ吐き、受け取ったリクエストの ResponseCnt フィールドで指定された回数だけ CallResponse を生成して返しています。 実行結果 サーバー 2021/06/08 01:29:03 --- ServerStreaming --- 2021/06/08 01:29:03 request: John クライアント 2021/06/08 01:29:03 --- ServerStreaming --- 2021/06/08 01:29:03 response: Hello. I'm John 2021/06/08 01:29:04 response: Hello. I'm John 2021/06/08 01:29:05 response: Hello. I'm John 2021/06/08 01:29:06 response: Hello. I'm John 2021/06/08 01:29:07 response: Hello. I'm John 2021/06/08 01:29:08 response: I'm so tired. (John) 2021/06/08 01:29:09 response: I'm so tired. (John) 2021/06/08 01:29:10 response: I'm so tired. (John) 2021/06/08 01:29:11 response: I'm so tired. (John) BidirectionalStreamingCall BidirectionalStreamingCall はリクエスト:レスポンス=N:N の双方向の通信です。 ソースコード ./proto/call.proto service Call { ... // BidirectionalStreaming Request:Response = N:N rpc BidirectionalStreamingCall (stream CallRequest) returns (stream BidirectionalStreamingResponse) {} } message ServerStreamingCallRequest { string name = 1 ; uint32 responseCnt = 2 ; } message BidirectionalStreamingResponse { map< string , uint32 > callCounter = 1 ; } ./client/cmd/main.go func runBidirectionalStreamingCall(c pb.CallClient, names [] string ) error { log.Println( "--- BidirectionalStreaming ---" ) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() stream, err := c.BidirectionalStreamingCall(ctx) if err != nil { return err } done := make ( chan struct {}) go recv(done, stream) if err := send(names, stream); err != nil { return err } <- done return nil } func send(names [] string , stream pb.Call_BidirectionalStreamingCallClient) error { for _, name := range names { in := &pb.CallRequest{Name:name} if err := stream.Send(in); err != nil { return err } } if err := stream.CloseSend(); err != nil { return err } return nil } func recv(done chan struct {}, stream pb.Call_BidirectionalStreamingCallClient) { for { res, err := stream.Recv() if err == io.EOF { close (done) return } if err != nil { log.Fatalln(err) } log.Printf( "response: %v \n " , res.CallCounter) } } func main() { ... c := pb.NewCallClient(conn) ... names = [] string { "John" , "Paul" , "John" , "George" , "Ringo" , "Paul" , "John" , "Paul" , "George" , "John" } if err = runBidirectionalStreamingCall(c, names); err != nil { log.Fatalln(err) } ... } BidirectionalStreamingCall のクライアントでは、CallRequest を複数回サーバーへ投げ、BidirectionalCallResponse を複数回受け取り、レスポンスの CallCounter をそれぞれ log に吐いています。 ./server/cmd/main.go func (s *CallServer) BidirectionalStreamingCall(stream pb.Call_BidirectionalStreamingCallServer) error { log.Println( "--- BidirectionalStreaming ---" ) counter := make ( map [ string ] uint32 ) for { in, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err } log.Printf( "request: %s \n " , in.GetName()) counter[in.Name] += 1 res := &pb.BidirectionalStreamingResponse{CallCounter: counter} if err := stream.Send(res); err != nil { return err } } } BidirectionalStreamingCall のサーバーでは、CallRequest を複数回受けつけ、各リクエストを受け取る度に BidirectionalStreamingCallResponse の CallCounter に名前を呼ばれた数を CountUp してレスポンスとして返却しています。 実行結果 サーバー 2021/06/08 01:29:13 --- BidirectionalStreaming --- 2021/06/08 01:29:13 request: John 2021/06/08 01:29:13 request: Paul 2021/06/08 01:29:13 request: John 2021/06/08 01:29:13 request: George 2021/06/08 01:29:13 request: Ringo 2021/06/08 01:29:13 request: Paul 2021/06/08 01:29:13 request: John 2021/06/08 01:29:13 request: Paul 2021/06/08 01:29:13 request: George 2021/06/08 01:29:13 request: John クライアント 2021/06/08 01:29:13 --- BidirectionalStreaming --- 2021/06/08 01:29:13 response: map[John:1] 2021/06/08 01:29:13 response: map[John:1 Paul:1] 2021/06/08 01:29:13 response: map[John:2 Paul:1] 2021/06/08 01:29:13 response: map[George:1 John:2 Paul:1] 2021/06/08 01:29:13 response: map[George:1 John:2 Paul:1 Ringo:1] 2021/06/08 01:29:13 response: map[George:1 John:2 Paul:2 Ringo:1] 2021/06/08 01:29:13 response: map[George:1 John:3 Paul:2 Ringo:1] 2021/06/08 01:29:13 response: map[George:1 John:3 Paul:3 Ringo:1] 2021/06/08 01:29:13 response: map[George:2 John:3 Paul:3 Ringo:1] 2021/06/08 01:29:13 response: map[George:2 John:4 Paul:3 Ringo:1] ドキュメント生成 最後にインターフェース定義のドキュメントを作成します。 protoc のドキュメント生成用のプラグインをインストールします。 > go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc この protoc-gen-doc では、.proto ファイルから HTML,JSON,DocBook,Markdown のいずれかの形式でドキュメントを生成することができます。 今回は HTML 形式でドキュメントを生成します。 > protoc --doc_out=./proto/doc --doc_opt=html,index.html ./proto/*.proto ./proto/doc 以下に index.html というドキュメントファイルが生成されました。 index.html コマンド 1 発で整ったドキュメントが作られてとても便利ですね。 学び 4 つの通信方式のいずれも、protobuf で生成された情報をもとにそれほど苦労せずに実装することができました。 今回は「試してみた」記事ということで各通信方式の特徴を生かしたコードは書けていませんが、公式の例のように ServerStreaming であればサーバーからクライアントへの Push 通知機能、BidirectionalStreaming であればサーバーを介してのチャット機能など、実現したい事に応じて通信方式を使い分けると各通信方式の良さがより実感できそうだなと思いました。 また、新卒エンジニア研修で作成している REST API のアプリケーションでも、protobuf で リクエストとレスポンスの message のみ定義するような使い方で定義の一元化に取り組めそうだなと感じました。ドキュメントの生成もかなり楽で良かったです。 まとめ いかがでしたでしょうか。今回は gRPC の 4 つの通信方式を Go で試してみました。 僕はこれまで REST での開発経験がほとんどなので、他の方法での API 設計は新鮮で楽しかったです。かなりシンプルに開発できそうだったので、今後業務でも機会を見て利用していければと思います。 最後までお読みくださりありがとうございました!
アバター
こんにちは!スマートキャンプにこの春新卒エンジニアとして入社した関口です。 私は今年の2月まで内定者インターンとしてBOXILのインフラ基盤のリニューアルプロジェクトに携わらせていただいてました。 このプロジェクトでBOXILのインフラ基盤をEC2運用からECS/Fargateへ移行したのですが、今回の記事ではこのプロジェクトで学んだこと、その学びを現在の業務でどう活かしているかの紹介をしていきたいと思います。 学び1:公式ドキュメントを読むことの大切さ この学びを現在の業務でどう活かしているか 学び2:事実と想像を分けることの大切さ この学びを現在の業務でどう活かしているか 学び3:触れたことのない技術に触れると自分のエンジニアとしての世界が広がるということ この学びを現在の業務でどう活かしているか まとめ 学び1:公式ドキュメントを読むことの大切さ 学んだことの1つ目は公式ドキュメントを読むことの大切さです。 公式ドキュメントはどのような目的、場面でも役に立ちますが、今回のプロジェクトでは特に実装工程でのエラー解決に役に立つことが多かったです。 今まで実装中にエラーに遭遇した時は、解決するためにブログ記事や技術系の質問サイトを参考にすることがほとんどでした。 ブログ記事や質問サイトの記述は私にとって公式ドキュメントより理解しやすいことが多かったため、エラー解決に必要な情報を素早く得るためには有効な手段でした。 しかしブログ記事を頼りにすると、必要な情報が得られるまでいくつもの記事を読む必要があったり、結果的に長い時間をかけても必要な情報が得られないこともありました。 そんななか、一緒にプロジェクトを行っていた先輩から「ツールについて正確な情報を得るためには公式ドキュメントを読むといいよ」とアドバイスをいただきました。 そのアドバイスがきっかけで私はツールの理解を深める時やエラー解決のための情報を得る時に公式ドキュメントを読むようになりました。 公式ドキュメントを読み進めながら開発していくうちに、公式ドキュメントの情報の網羅性や正確性を実感することが増えていきました。例えば、インフラ基盤に新しく導入するツールの利用方法を調べている時や実装中のエラー解決をするためのデバックの際、ブログ記事や質問サイトなどでは見つからなかった情報が公式ドキュメントに明記されていたことが何度かありました。 これらのことから、開発に必要な情報のほとんどは公式ドキュメントを見れば掲載されていることに気が付きました。 このプロジェクトから公式ドキュメントを利用して必要な情報を探すことの大切さを学びました。 この学びを現在の業務でどう活かしているか この学びを得てから、初めて触る技術やツールを勉強したり実装方法で悩んだりした時はまず公式ドキュメントやレファレンスを見ることを意識するようになりました。 たとえば、現在私が担当しているプロジェクトではReactを利用しているのですが、私はこれまでReactを利用したことはほとんどありませんでした。 そのためプロジェクトの初期ではReact特有の機能(たとえばhooksなど)を理解し、その機能を利用して実装することに苦労することがありました。 しかしそのような時に、ブログ記事や質問サイトからの情報に頼るより公式ドキュメントを利用すると必要な情報が見つかることが多く、インフラ、フロントエンドなどの技術領域問わず公式ドキュメントを読む大切さを実感しました。 学び2:事実と想像を分けることの大切さ 学んだことの2つ目は事実と想像を分けて開発することの大切さです。プロジェクト中、事実と想像を分けて考えることができていなかったために、自分では実現できていると思っていたことが実際には実現できていなかったことがありました。具体的には以下のようなことです。 プロジェクトの終盤に新しいインフラ基盤に対する負荷テストをおこないました。 その準備として、使用するツールからロードバランサーにアクセスできるか確認する作業をおこなっていたのですが、実際にはアクセスできていないにも関わらず、アクセスできていると勘違いしてしまったことがありました。 ロードバランサーのメトリクスを確認するとアクセスされた形跡があったため負荷テストツールが問題なく動いていると勘違いをしてしまったのですが、よく確認すると負荷テストツールで指定したリクエスト数や時間などに対応したアクセスの形跡ではありませんでした。 反応しているメトリクスを見てテストツールが動作しているに違いないと自分の想像と事実を混合してしまった出来事でした。 この出来事以外にも基盤構築中でエラーに詰まった時に事実と想像を区別して考えていなかったために解決するまで長い時間を費やしてしまったことが何度かありました。 以上より事実と想像を分けて考えることの大切さを学びました。 この学びを現在の業務でどう活かしているか インフラ基盤移行のプロジェクトから事実と想像を分けることの大切さを学びましたが、この学びはインフラの領域だけでなく様々な技術領域の開発で活きています。 自分が今考えていることが根拠を元にした事実なのか、想像なのかということを意識するようになりました。 上記のことを現在のプロジェクトでも意識しています。以前までエラーに遭遇するとエラーの内容を正確に把握しないまま、エラーが発生する原因を想像で考えてしまうことがありました。 しかしこの学びを得てからは、エラーに遭遇した時にコンソール画面から、エラーの内容や種類をレファレンスやドキュメントを利用して調べ、そこからエラーが起きる原因を考えるようになりました。 以前に比べてしっかりと根拠を持ちながらデバックをおこなうことができるようになりました。 学び3:触れたことのない技術に触れると自分のエンジニアとしての世界が広がるということ 学んだことの3つ目は触れたことのない技術領域に触れることでエンジニアとしての自分の世界が広がるということです。 プロジェクトでは慣れない技術を利用することで苦労したことがたくさんありました。 プロジェクト初期はECSやFargateなどのサービスの利用方法やサービスの概要を理解することにとても時間がかかってしまいました。 また基盤構築をしている時にもエラー解決のためのデバックをどのようにすればいいかわからなかったり、エラーの原因の切り分けが難しいなど、多くの苦労がありました。 しかし、プロジェクトが終わってから振り返りをおこなうと技術的に学べたことが本当に多かったことを実感しました。 ECS/Fargateというサービスの概要や利用方法からコンテナそのものの概念、コンテナを本番運用をすることの利点を学べました。 プロジェクトが終了する頃には、自分が今まで触ったことのない技術に触れることで自分のエンジニアとしての世界が広がるということに気が付きました。 この学びを現在の業務でどう活かしているか 触れたことのない技術に触れることでエンジニアとしての自分の世界が広がることに気がついてから、業務で自分が今まで利用したことのない技術を利用する際のマインドが変わりました。 これまで、業務として初めて触る技術を利用して開発する時は、楽しみな気持ちもありつつ、しっかりとパフォーマンスをだせるのかという不安な気持ちが強くなってしまうことがありました。 しかし、この気づきを得てからは仕事で自分が触ったことのない技術を利用することに対して前向きに考えられるようになりました。以前に比べて純粋に技術に向き合うことを楽しめる様になりました。 まとめ 今回のブログではインフラ基盤移行プロジェクトを経て学んだことと、それが現在どう活きているのかについて紹介させていただきました。 内定者インターン生という立場でありながらこのようなプロジェクトに参加させていただけたおかげで、様々なことを学ぶことができました。今回のプロジェクトで学んだことをこれからの業務でも活かしていきたいです。
アバター
こんにちは、 BOXIL 開発に携わっている、新卒エンジニアの高砂と申します! 私は今年の4月で社会人2年目になったのですが、ちょうどその前後、2月~5月にかけて「 BOXILビジネステンプレート 」というサイトのリニューアルプロジェクトを企画から開発まで主導していました。 本記事では、このプロジェクトの振り返りを通じて得た、新卒目線での学びについて紹介します。 やったこと KPTを用いた振り返り Keep(良かったこと) Problem(気になること) Try(次試すこと) 誰がフォローやクオリティチェックをするかが不明瞭だった 全体への情報共有不足を感じられた 振り返りを踏まえた理想体制 前提 方針 まとめ やったこと 前述した通り、私は「BOXILビジネステンプレート」の企画と開発をほぼ一人で進めてきました。 普通だとそこまで任せる事はなかなか無いのですが、今回は下記の理由よりそのように進める事になりました。 開発をリードできる人材を増やしたい その為に、高砂に事業やユーザーの事を意識しながら企画・開発する経験をして欲しい 「BOXILビジネステンプレート」はリニューアルの規模感としては小さめなので任せやすい これらを背景に本プロジェクトはスタートし、企画・開発を経て4月頃ローンチ完了、その後も企画・開発を続け、5月中で必要最低限の機能がそろった状況となりました。 prtimes.jp KPTを用いた振り返り プロジェクトとしては一区切り付いたので、ビジネス面やデザイン面でご協力頂いた方々と共に振り返りを行いました。 振り返りは「KPT」と呼ばれるフレームワークを用いました。KPTは「Keep(良かったこと)」「Problem(気になること)」「Try(次試すこと)」の頭文字から来ており、それらをベースに振り返りを行う手法です。 詳しくは下記記事で解説しているので、よければご覧ください。 tech.smartcamp.co.jp その振り返りで実際に出てきたトピックは以下の通りでした。 Keep(良かったこと) エンジニア主導でプロジェクトを推進できた プレスリリースまでに炎上せずリリースできた 0からサイト構築する事で技術力が伸びた 企画からほぼ一人で進めたので企画チームのリソース的に助かった ユーザーインタビューや他社協業を自発的に進められて良かった Problem(気になること) 誰がフォローやクオリティチェックをするかが不明瞭だった 全体への情報共有不足を感じられた 明確に事業としての目標数値を達成できた訳ではなかった 工数見積りの精度が低かった 期限短めのレビュー依頼やデザイン依頼が多かった いつまで本プロジェクトに取り組むかが不明瞭だった Try(次試すこと) これに関しては、前述のProblemの中で特に話し合いたい2トピックを議論する形式で洗い出しました。 誰がフォローやクオリティチェックをするかが不明瞭だった 関係者全員でチェックする時間を設ける 主導する若手がどこまでの責任や期待値を持っているかを、関係者全員に事前共有する 全体への情報共有不足を感じられた ミーティングでは、進捗だけでなく意図も共有する 誰がどこまでを誰に共有する、というラインを明確に設定しておく 振り返りを踏まえた理想体制 ここからはチームではなく個人で考えた事なのですが、今回のように若手に主導を任せるプロジェクトでは、下記のような体制で進めるのが良さそうだと結論付けました。 前提 体制の目的は2つ 小さめのプロジェクトを主に若手のリソースで進める(事業的観点) 若手にプロジェクト主導を経験をしてもらう(教育的観点) 方針 若手以外のリソースを大きく割かなくても進む 認識齟齬による手戻りが少ない 関係者が適切な行動を取れるように、十分に情報共有がされている 高砂の考える理想体制 まとめ 若手にプロジェクトの主導を任せるのはなかなか勇気の要る事ですが、その分成長機会としては非常に良いものになるかと思います。 一方でマネジメントやコミュニケーションの面は経験不足かもしれないので、その点は体制や周囲の意識でフォローしていければ良いのではと思いました。 本記事が、プロジェクトの主導を任された方、もしくは任せようとしている方の参考になれば幸いです!
アバター
こんにちは!スマートキャンプで Web アプリケーションエンジニアとして働いている中川です。 突然ですが、みなさんは普段スライドを作っていますか? 私はそこまで頻度高くはないものの、全社イベントでプレゼンするためであったり、他部署交流のための自己紹介、あとは LT 会などのためにスライドを作ることがあります。 Twitter で見かけたこちらのツイートから、 Slidev という Markdown からスライドを作成できる OSS を知り、実際に使ってみたところ非常に感触がよかったので、今回の記事では Slidev の簡単な使い方や、なかはどうなっているのかなど紹介していきたいと思います! 🚀 Slidev v0.9.0 Released 🤹 Animations / Motion by @vueuse 's motion package 📰 Textual diagrams by Mermaid.js 👇 Play the demo here! https://t.co/ZdsakWBIwG pic.twitter.com/DHPDjWO05j — Slidev (@Slidevjs) 2021年5月11日 Slidev とは スライドに CSS 形式のスタイルを適用できる Web の技術で作られている Vue コンポーネントをスライドに直接埋め込むことができる その他 なかを覗いてみる どうやってスライドを生成しているのか エクスポート機能はどう実現しているのか まとめ Slidev とは 開発者のためのプレゼンテーションスライドを作成する OSS です。 公開 7 日で GitHub リポジトリにはすでに 7000 を超えるスターがついており、発起人は VueUse など Vue 界隈の OSS 活動で知られる Anthony Fu 氏です。 百聞は一見に如かずということで、以下の動画もしくは 公式サイトのトップページ にある GIF を一度見ていただくのが分かりやすいかと思いますが、簡単に言えば Markdown からプレゼンテーション用のスライドを作成できるものになっています。 youtu.be その他の特徴を簡単にまとめると... スライドに CSS 形式のスタイルを適用できる 以下のように、 <style> タグを記述することによってスライドにスタイルを適用することができます。 # This is Red < style > h1 { color : red } </ style > --- # Next slide is not affected また、この <style> タグは記述したスライドにのみ適用される scoped なものです。 反対に、あるスタイルを全スライドに適用することもでき、その場合は ./style.css もしくは {project-root}/styles/index.{css,js,ts} に記述します。 https://sli.dev/custom/directory-structure.html#style 前述した通り全スライドに適用するスタイルを記述できるほかに、テーマ機能を有しており、公開されているテーマを使用したり、そのテーマを改造したり、また自分のテーマを作成することも出来ます。 テーマ機能はプレゼンテーションスライド作成ツールにおいて標準的な機能かとは思いますが、一風変わっているのはテーマを npm 経由でインストールする点で、開発者向けのものであることが伝わってきます。 https://sli.dev/themes/use.html Web の技術で作られている 後のセクションで詳しく見ていきますが、Slidev はなかで Vite や Vue 3、Windi CSS が動いています。また、各ソースファイルや Vue コンポーネントにおける Script は TypeScript です。 Slidev はスライド形式にレイアウトされた HTML を出力し、それらスライドを操作できるビルトインのプレゼンテーションツールを提供することで、プレゼンテーションのためのスライドツールを実現しています。 ひとつひとつのスライドは HTML であるため、スライドの中で API リクエストしたり、iframe を埋め込んだり、スライド自体を共同編集することも容易です。 Vue コンポーネントをスライドに直接埋め込むことができる 強烈な機能ですが、前述した通り Slidev 自体が Vue 3 で作られていること、HTML として吐き出していることから Vue コンポーネントをスライドに埋め込むことが出来るようになっています。 これは、 {project-root}/components/*.{vue,js,ts,jsx,tsx} と配置することでスライド中に独自コンポーネントを埋め込むことが出来ます。例えば、Hoge.vue を components 配下に配置すれば、スライドからは <Hoge></Hoge> として呼び出すことが可能です。 また、組み込みコンポーネントもいくつか用意されており、Youtube.vue では <YouTube id="luoMHjh-XcQ" /> として Youtube の動画を埋め込めたり、Tweet.vue では <Tweet id="20" /> としてツイートを埋め込めたりします。(現段階ではおそらく変更が激しいので、どのような組み込みコンポーネントがあるのかは直接リポジトリの このディレクトリ を確認することが推奨されています) おそらく、スライドで動的な要素を実現するために Vue コンポーネントを埋め込むシーンが出てくるかと思いますが、こうして好きにコンポーネントを埋め込めることは非常に拡張性が高く、これまでのプレゼンテーションスライドツールには無かったものなので、例えば LT 中にコードを紹介したあとで、その次のスライドで実際に動いているコンポーネントを見せられるなど、いろいろおもしろい使い方ができそうです。(もちろんスライド作成者が Vue コンポーネントを作れることが前提にはなってしまいますが) https://sli.dev/custom/directory-structure.html?#components その他 その他にも、WebRTC を使った画面録画やカメラ投影機能や、PDF,PNG などへのエクスポート機能などが存在しており、詳細は このページ にまとめられています。 簡単な Slidev の紹介は以上です。 注意点として、2021/05/13 現在 Slidev は Public Beta であるため、公式サイトのトップに Slidev is still under heavy development. API and usages are not set in stone yet. といった表示がされている通り、直近は破壊的な変更もありえる想定で使うべきものとなっています。 なかを覗いてみる ここからは、OSS として公開されている Slidev の中を見ながら、いくつか気になった・面白い点をピックアップしていきます。 どうやってスライドを生成しているのか Slidev には独自の Markdown パーサが内蔵されています。 パッケージのルート直下に parser ディレクトリが存在しており、このなかの特に core.ts でパース処理を行っています。コンポーネント埋め込みなどある種の Markdown 拡張構文が用意されている手前、このパース処理は今後複雑性が増していきそうだなと読んでいて胃が痛くなりました。 そして、Markdown のファイルパスを投げることでパース結果を返す load 関数が fs.ts にあり、 loader.ts の handleHotUpdate 関数によって呼び出されることで、Markdown をスライドとして成形・スタイリングしてサーバーが返却する流れのようです。 (他にもテーマの書き出しや Markdown をフォーマットするためにも呼ばれている様子でした。) エクスポート機能はどう実現しているのか 前述した通り、Slidev には PDF や PNG としてスライドをエクスポート出来る機能が備わっています。 これを実現しているのが export.ts です。 マイクロソフト製の軽量なライブラリである playwright から chromium のヘッドレスブラウザを使用して、ローカルサーバー上の各スライドページにアクセスし、PDF 生成や PNG のスクリーンショット撮影を行っています。 個人的な感想ですが、なかで Vue が動いていることもあり、ビルド結果を読み込んでエクスポート先の各ファイルを生成するような方法が取られているのかと推測していたため、ヘッドレスブラウザで該当のページを表示して PDF 生成やスクリーンショットを撮影するといった E2E テスト的な手法を使ってエクスポート機能を実現しているのは驚きでした。 まとめ 駆け足でしたが今回の記事は以上になります。 今回紹介したなかでも Vue コンポーネントを埋め込める機能は色々と面白い見せ方が出来る予感がするので、これからどんなスライドが見れるようになるのか楽しみです! 個人的にも、今までプレゼン用のスライドを作るのは腰を上げるのも上げてからも大変でしたが、Slidev を使えば頑張れそうな気がしています。 また、共同作業しやすいことやバックアップ面でも Markdown で管理できることの利点は大きいかなと思います。 Slidev は npm init slidev を実行するだけで今すぐ使い始めることができるので、気になった方は一度使ってみてはいかがでしょうか。 それでは!
アバター
スマートキャンプ、エンジニアの井上です。 現在私が開発しているBALES CLOUDのインフラ基盤はEKSが採用されています。 そのEKSのバージョンは1.15でサポートが2021年5月に切れてしまうので、EKSのアップデート作業を行いました。 今回の記事ではそのときのアップデート作業の詳細やその目的、また、アップデートによって出来るようになったことなどをご紹介できればと思います。 なぜ、一気に4バージョンもアップデートしたのか? EKSのアップデートの前提情報 EKSは1バージョンずつしかあげられない クラスター更新時にKubernetesのアドオンは変更されない EKS アップデート作業でやったことの紹介 どのバージョンでも共通して行う必要のある作業 EKSクラスター アップデート nodeのAMIをEKS バージョンに合わせたAMIに変更する EKSのバージョンに合わせたKubeProxyに変更する kubectlのアップデート EKS1.16のみ行う必要のある作業 apps/v1への変更 2020年8月17日より前にデプロイされている場合はマルチアーキテクチャ対応が必要です。 CoreDNSのマニフェストのupstreamオプションが非推奨になるため削除する Amazon VPC CNI Plugin for Kubernetesの推奨バージョンへのアップデート 1.18、1.19のみ行う必要のある作業 EKS1.19まであげたことにより、できるようになったこと 1.19からKubernetesのサポート期間が1年に kubectl alpha debugの追加 StartupProbe HorizontalPodAutoscaler まとめ なぜ、一気に4バージョンもアップデートしたのか? EKSは、そのバージョンが利用可能になってから14ヶ月間サポートされます。 これだけ見ると、1バージョンあげれば1年間は安心して過ごせそうに見えます。 しかし、この話にはEKSとKubernetesのサポートの2軸があり、EKS側では下記のように書かれています。 Kubernetes バージョンに対する Kubernetes コミュニティのサポートに沿って、Amazon EKS は Kubernetes の少なくとも 4 つの本番稼働対応バージョンをいつでもサポートするよう努めています。Kubernetes の特定のマイナーバージョンのサポート終了日については、サポート終了日の最低 60 日前に発表します。Kubernetes の新しいバージョンの Amazon EKS の認定およびリリースプロセスにより、Amazon EKS の Kubernetes バージョンのサポート終了日は、Kubernetes プロジェクトがバージョンアップストリームのサポートを停止した日以降になります。 docs.aws.amazon.com このようにKubernetesがサポート終了してもEKSのバージョンはサポートが切れずに使い続けることができるケースがあります。 しかし、本家であるKubernetesがサポートしてないバージョンを使用するのはリスクが高く不安です。 このためアップデートは本家Kubernetesのサポート期限に追従する形にしていきたいと考えました。 Kubernetesでは最新3つのマイナーリリースについてリリースブランチを管理しています (2021/0426現在では1.21, 1.20, 1.19)。 EKSでの最新バージョンは1.19なので、できれば1.19までアップデートしてEKS、Kubernetesのどちらにもサポートされているバージョンとしたかったのが今回一気に4バージョンアップデートすることにした理由です。 とはいえAWSも本家KubernetesのバージョンとEKSがサポートしているバージョン間のギャップは減ってきているので、 今後はこういったことを考える必要はないかもしれません。 EKSのアップデートの前提情報 では、実際にアップデートするにあたっての注意点などをご紹介できればと思います。 EKSは1バージョンずつしかあげられない たとえば、1.15から1.17にアップデートする場合、1.15から1.16にあげて、その後1.16から1.17に上げる作業が必要になります。 クラスター更新時にKubernetesのアドオンは変更されない コンソール画面でクラスターの更新が可能ですが、更新対象にKubernetesアドオンは含まれません。 そのため下記のKubernetesアドオンは手動で更新をしていく必要があります。 Amazon VPC CNI プラグイン CoreDNS KubeProxy EKSバージョンごとに対応するアドオンのバージョン EKS アップデート作業でやったことの紹介 どのバージョンでも共通して行う必要のある作業 EKSクラスター アップデート クラスターのアップデートはコンソール画面とコマンドのどちらでも実行可能です。 このアップデートは30分ほどかかります。 nodeのAMIをEKS バージョンに合わせたAMIに変更する EKSのバージョンに合わせてAMIのバージョンも指定されています。 過去にAMIのバージョンが古いせいで問題になったケースがあるので、EKSのバージョンに合わせた最新のAMIに変更することをお勧めします。 github.com EKSのバージョンに合わせたKubeProxyに変更する EKS1.16~EKS1.19まではバージョンごとにKubeProxyの推奨バージョンが異なるため、各バージョンでアップデートする必要があります。 kubectlのアップデート kubectlもクラスターの更新時に更新されないため、バージョンごとに手動で更新する必要があります。 こちらは別ページで丁寧に書いてあるので、書かれている通り実行すれば問題ありません。 docs.aws.amazon.com EKS1.16のみ行う必要のある作業 EKS1.16はアップデートするまえの前提条件が書かれるほど、変更作業が多いアップデートになります。 手動で変更する箇所が多いため、前提条件をしっかり確認した上で進めることをおすすめします。 また、EKSの状態によっては他にも対応する必要のある工程がありますが、今回はBALES CLOUDで必要だった対応を紹介します。 docs.aws.amazon.com apps/v1への変更 1.16からDaemonSet、Deploymentは「extensions/v1beta1」や「apps/v1beta1」が廃止され「apps/v1」にする必要があります。 2020年8月17日より前にデプロイされている場合はマルチアーキテクチャ対応が必要です。 kube-proxyの設定にarm64を追加する kubectl edit -n kube-system daemonset/kube-proxy - key: "beta.kubernetes.io/arch" operator: In values: - amd64 - arm64 <-追加 CoreDNSの設定にarm64を追加する kubectl edit -n kube-system deployment/coredns - key: "beta.kubernetes.io/arch" operator: In values: - amd64 - arm64 <-追加 CoreDNSのマニフェストのupstreamオプションが非推奨になるため削除する 下記のエラーissueから手順に取り込まれたものです。 github.com 下記のコマンドを実行して upstream の文字列が含まれていたら変更が必要になります。 kubectl get configmap coredns -n kube-system -o jsonpath='{$.data.Corefile}' | grep upstream upstream の文字列が有った場合修正していきます。 kubectl edit configmap coredns -n kube-system -o yaml Corefile: | .:53 { errors health kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream <=ここを削除 fallthrough in-addr.arpa ip6.arpa } Amazon VPC CNI Plugin for Kubernetesの推奨バージョンへのアップデート 1.16からAmazon VPC CNIの推奨バージョンが変わるためアップデート対応をします。 ここはus-west-2からap-northeast-1に変換するというちょっと面倒な作業があります。 curl -o aws-k8s-cni.yaml https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/v1.7.5/config/v1.7/aws-k8s-cni.yaml sed -i -e 's/us-west-2/ap-northeast-1/' aws-k8s-cni.yaml kubectl apply -f aws-k8s-cni.yaml 1.18、1.19のみ行う必要のある作業 1.18, 1.19でのみ推奨されるCoreDNSのバージョンが異なるため、 それぞれCoreDNSのイメージを更新する必要があります。 コマンドは現在のバージョン確認で出力された内容で書き換え、 x.x.x の部分に対象のバージョンを指定して実行します。 現在のバージョンを確認する kubectl get deployment coredns --namespace kube-system -o=jsonpath='{$.spec.template.spec.containers[:1].image}' 現在のバージョン確認の際に出力された値と 602401143452.dkr.ecr.us-west-2.amazonaws.com の部分を置き換え x.x.x を対象のバージョンに指定する kubectl set image --namespace kube-system deployment.apps/coredns \ coredns=602401143452.dkr.ecr.us-west-2.amazonaws.com/eks/coredns:vx.x.x>-eksbuild.1 EKS1.19まであげたことにより、できるようになったこと EKSを1.19まであげたことにより、プロダクトのインフラ基盤が圧倒的に改善されたわけではありませんが、 バージョンをあげたことによって今後安定性の高いインフラに変化させるための手段が増えたので、それらの例を一部ご紹介できればと思います。 1.19からKubernetesのサポート期間が1年に これまでKubernetesのサポート期間は9ヶ月だったので、9ヶ月以内に次のバージョンにアップデートする必要がありましたが、1.19からはサポート期間が1年に変更されました! (サポート期間9ヶ月間のバージョンでは、期間内にアップデートすることができたユーザーが限られていたことが理由のようです。 https://kubernetes.io/blog/2020/08/26/kubernetes-release-1.19-accentuate-the-paw-sitive/#major-themes ) これで、少し余裕をもってバージョンを追従することが可能になりました。 kubectl alpha debugの追加 Kubernetes 1.16で一部の機能がalphaとして提供されました。 kubectl alpha debugは、実行中のPodにエフェメラルコンテナと呼ばれるデバック用の一時的なコンテナを後から追加するコマンドです。 これにより既存のPodに調査用のmoduleなどをインストールせずともデバッグ用のコンテナにインストールして調査などが可能になります。 StartupProbe StartupProbeは、コンテナの起動時のみに使用される新しいProbeです。 これまで起動にある程度時間がかかるコンテナではLivenessProbeやinitialDelaySecondでの調整が難しく、失敗すると強制終了されるリスクもありました。 StartupProbeは、このProbeが成功するまでLivenessProbeとReadinessProbeの2つのProbeが開始されません。 これにより起動時の調整がやりやすくなったかと思います。 HorizontalPodAutoscaler HorizontalPodAutoscalerにspec.behaviorフィールドが追加されました。 これにより1回のスケーリングでどの程度スケールしてほしいかなどが設定できるようになり、 かなり柔軟なスケールアップ/スケールダウンそれぞれの挙動を設定できるようになりました。 ※ autoscaling/v2beta2からの機能になります。 まとめ 今回はBALES CLOUDで行ったEKSアップデート作業を紹介しました。 可能な限り最新バージョンに追従していくことで開発や改善の手段を増やして、よりモダンな環境を構築していきたいと思います!
アバター
スマートキャンプでエンジニアをしている瀧川です。 みなさん、NoCode(ノーコード)やLowCode(ローコード)をご存知でしょうか? 考え方としては昔からありましたが最近国内で急速に注目されてきており、今月始めにはNoCodeツールであるAdaloを使って開発されたアプリが資金調達したことでさらに勢いが増したと感じています。 日本初!Nocodeで作成されたUnion -大学生のためのSNSがシードラウンドにて1,000万円の資金調達を実施|Anlimited株式会社のプレスリリース NoCodeの利点といえばなんと言っても、エンジニアがプログラムを書くことなくアプリケーションが作成でき、かつ公開までできるところにあると思っています。 ではすでにエンジニアが在籍しているITベンチャーにとって、NoCodeはどんな価値があるのでしょうか? そして働くエンジニアとしてどのようにNoCodeと向き合っているのか、そういったことを社内の取り組みを紹介するとともにまとめてみようと思います! NoCodeとは NoCodeと自社の向き合い方 想定している使いみち 1. 業務効率化のための社内ツール作成 2. 新規サービスのプロトタイプ作成 社内NoCode勉強会開催 エンジニアのNoCodeへの向き合い方 まとめ NoCodeとは NoCodeとは特定のサービスや機能を指すのではなく概念なので決まった定義はないのですが、私は プログラミングをはじめとした専門知識がなくとも、アプリケーションを実装できる ツールやサービスの総称だと捉えています。 (LowCodeは一部分にプログラミングを用いる) 一般的に以下のようなメリットがあります。 必要な専門知識が少ない(非エンジニアでも実装が可能) GUIで抽象化されているため少ない工数で実装ができる インフラなども隠蔽されているため気にする必要がない 具体的なサービスだと、先に取り上げたモバイルアプリ構築のための Adalo 、静的Webサイトを構築するための STUDIO 、データベースなども内包したWebアプリ構築のための Bubble などが挙げられます。 NoCodeと自社の向き合い方 そもそもなぜこのタイミングで、組織的にNoCodeに注目しているのか。 その理由は大きく分けて以下の2つになります。 NoCode黎明期での早期キャッチアップ 社内の課題解決 1つ目については、言わずもがなですが流行の移り変わりが激しいIT業界において、「なにか怪しいもの」のように思われている状態からキャッチアップすることで先行者利益を得ている事例はネットの歴史上多いと考えています。 例えば毛色は違うものの、仮想通貨は良い例だと思います。 NoCodeについては、すでに遅いかもしれませんが、ここからの成長性も考えて今からでも動くべきだと判断しました。 また2つ目については、弊社は私含めエンジニアが在籍し、日々既存サービスである BOXIL や BALES CLOUD の改修や新規サービスの実装に取り組んでいます。 ただ潤沢にエンジニアリソースがあるわけではなく、社内の業務課題やアイディアの実現に手が回っていないという実感がありました。 そこでエンジニアの担当領域を、非エンジニアに分散できる可能性があるNoCodeツールを試すことにしました。 また昨今のWeb系技術(Next.js, Vercel, Firebase等)の進歩で開発速度も向上しているものの、やはり特定の領域まではNoCodeの方が圧倒的に早いのではという思い、どこまでなにができるのかをエンジニアとして理解したいという気持ちもありました。 想定している使いみち 実際に今NoCodeの使いみちとして考えているのが以下の2つです。 業務効率化のための社内ツール作成 新規サービスのプロトタイプ作成 これらがどの程度、どのツールで実現可能かを社内で検討しています。 1. 業務効率化のための社内ツール作成 バックオフィス系や部署や役職毎に細々とした業務が社内には数多あり、ツールやSaaS導入ができてないものも多いです。 そういった場合、以下のようなアプローチをとることが多いと思います。 商用ツールやSaaSの導入検討 エンジニアに開発を相談 自分たちでSpreadsheetなど使い効率化 これらにはそれぞれいくつかの課題があると感じています。 「1. 商用ツールやSaaSの導入検討」では、そもそも業務がドメインに特化しているためサポートしているツールやSaaSが見つからないことは多いですし、「2. エンジニアに開発を相談」では、改善の効果と実装コストが見合わずリソースが割けないとなりがちです。 「3. 自分たちでSpreadsheetなど使い効率化」は良いアプローチだと思いますが、何枚ものシートが関連し、いくつもの関数が組み合わさり...と負債化しているものをエンジニアがヘルプにいくといった構図が今までも何度かあり、運用やメンテナンス性を考えると限界があると感じています。 (コピペミスで関数が壊れましたといった相談を受けることもありますね) こういった問題点をNoCodeを使うことで解消できるのではと考えています。 ドメインに特化した機能を実装できる エンジニアリソースを使わない Spreadsheetなどより機能の制限ができるのでオペレーションミスなど起きにくい ※ 後述しますが、負債化についてはNoCodeでも完全には解決せず、エンジニアが入る方がいいと考えています 2. 新規サービスのプロトタイプ作成 新規のサービスや機能を作る際には以下のようなフローになることが多いかと思います。 仮説 検証 プロトタイプ実装 検証 プロトタイプ修正 検証 ... 仮に、「仮説や検証は企画職が担当、プロトタイプは初期ではGoogleFormを利用、検証が進んだらエンジニアが再度プロトタイプ作成」のようなフローだった場合と比べ、NoCodeを使うことで早い段階でクオリティの高いプロトタイプが導入でき、検証精度の向上が期待できます。 また検証中のプロトタイプ修正も、エンジニアが入ることが減り、修正して検証のサイクルも早くなると思っています。 社内NoCode勉強会開催 ここでは実際に社内で実施している取り組みのNoCode勉強会を紹介します。 目的は前述した社内ツール作成やプロトタイプ作成を念頭に置いたNoCodeツールの調査及び習熟となります。 今年3月くらいから発足し、毎週1時間ランチを食べながらも可というルールで実施しています。 参加者については現在はプロダクト開発に直接関わるメンバーとして、プロダクトマネージャー、プランナー、デザイナー、エンジニアが任意で参加しています。 今の参加者が推進者として、セールスやメディアの部署に展開できるようになるとよいかなと感じています。 直近の内容としては、 Bubble をメインに触っており、モブプロ風にチュートリアルを実施したり、業務課題改善についてどの機能を使えば実装できそうかディスカッションしたりしています。 またNoCodeに関する事例やアイディアを集めるために、Slackチャンネルも作りわいわい盛り上がっています。 実施しているなかで参加者からは、NoCode開発合宿がしたい、部署立ち上げても面白いかもといった声があがったりと、今後社内の文化の一つになればいいなと思っています。 エンジニアのNoCodeへの向き合い方 NoCodeによりエンジニアが不要になることはない、というのはよく言及される内容なので理解されている方も多かと思います。 今回エンジニアの私がNoCode勉強会や個人でNoCodeツールを触っていく中で、現状エンジニアが主導する必要があると感じたのは以下になります。 アプリ開発に必要な概念(DB、イベント、条件分岐など)の教示 業務や要件の整理 外部のAPI連携などNoCodeツールで完結しない要件のサポート GUIで操作可能で抽象化されているとはいえ、データベースやスキーマの概念や、イベントや条件分岐、エラーハンドリングといった要素は、勉強会をするなかでも非エンジニアの方が難しいと感じているようでした。 それを更に組み合わせてアプリケーションを作成していくのはやはりそれなりの経験値が必要なので、この点はエンジニアが指導者になるほうがいいと感じました。 業務や要件の整理は、「業務効率化のための社内ツール作成」についてでも書きましたが、複雑な業務をそのままNoCodeで実装したとしても、作るコストも管理コストも高くなってしまいます。 その点についてもエンジニアが一日の長があると考えているので、サポートしていく必要があると思います。 あとは実際に実装のアイディアを考えてみるとNoCodeツールだけで完結するものは少なく、例えば自社サービスのデータを取得する必要があったり、Gmailをフックする必要があったりといった要件が挙がります。 そういった場合に、エンジニアが実装する必要があるのか、それとも他のツールと連携させるとよいのかといった判断にまずエンジニアが関わる必要があると感じています。 そして必要であればプログラミングをすることになるかなと思います。 余談ですが、エンジニア視点でもNoCodeツールを使い実装するのはどんなフレームワークで実装するよりも早いと思うので、スキルの一つとして学んでおくのがいいと感じています。 ここからさらに急速に成熟する可能性もあり、しっかりとキャッチアップしていくべきだと思います。 まとめ 弊社がNoCodeとどのように向き合っているか、実際の取組みを含めて紹介させていただきました。 当初はできないことも多いのではといった懸念もありましたが、実際に触っているとNoCodeの盛り上がりを裏付けるようなツールとしての成熟度の高さを感じていますし、これから更に事例も増えていくと思います。 ケースバイケースで必要な技術を使えるのがエンジニアの本質的な強みだと思うので、手段の一つとしてNoCodeに触れていくのは大切だと感じています。 そんな今後のNoCodeの発展がエンジニアとしてとても楽しみです!
アバター
こんにちは。この4月より21卒エンジニアとして、スマートキャンプに入社しました中田です。 スマートキャンプの21卒エンジニアは僕(中田)と関口の2人で、両者ともに入社以前から弊社で内定者インターンをしていました。 そこで、本記事では入社エントリとして21卒エンジニアの2人が、インターンでの経験の振り返りや今後の抱負についてお話しします! 中田 自己紹介 内定者インターンについて 今後の抱負 関口 自己紹介 内定者インターンについて 今後の抱負 まとめ 中田 自己紹介 こんにちは!最近、健康維持のために散歩を始めた21卒エンジニアの中田です。出身は山口県で大学は福岡県の九州工業大学に通っていました。 スマートキャンプでは昨年の11月からインターンを始め、業務では主に BOXIL の開発に携わっています。 先日、大学のあった福岡から東京に越してきました。東京は人もモノも多いですね。緊急事態宣言も解除されたので、頃合いをみて色んな街を散歩してみたいなあと思ってます。 内定者インターンについて 僕は昨年の11月から内定者インターンを始めました。インターンでは以下のような業務に取り組んできました。 (週3日勤務|福岡からリモート) - BOXIL (2020/11~2021/01) - バナー機能の開発 - サービス紹介機能の改善 - その他改善系タスク (2021/02~) - 新規機能開発 - 仕様検討 - プランニング - プロト開発 - その他 - スクラム管理用のツール開発 インターンでは、初日から実際の業務に取り組ませてもらっていました。サービスに備わっている機能やビジネスモデル、アプリケーションの構成などを早く理解したかったので、ありがたいなと思ったのを覚えています。 業務には、初めのうちは小さめの改善タスクをアサインしてもらい、徐々にしっかりめの機能改善や新規機能の開発をアサインしてもらうという流れで取り組んでいました。BOXIL開発チームのタスクは、機能や改善点単位で区切られているものが多く、特にフロントエンド/バックエンドで区切られてはいません。インターンを始める以前には、ほとんどバックエンドの開発経験しかなかったので、フロントあんまり分からなくて難しいけど、どちらも触って機能を丸々作れるのは嬉しいなーと思っていました。 最近では、大きめの新機能の開発に新卒の2人で取り組んでいます。この新機能開発では、仕様もプランニングも基本は僕たちに任せてもらっています。やりがいがありますし、良い機能にしたいです。 今後の抱負 弊社は「Small Company Big Bussiness」というVISIONを掲げていて、僕はこのVISIONが好きです。なので、ユーザーにもBOXILを通して弊社のビジョンと同様の感想を得てもらいたいと思っています。そのためにも、ユーザーが嬉しいと思ってくれるような機能をどんどん開発していきたいです。 個人としては、業務でバックエンドやフロントエンド、(少しインフラも)横断して触らせて貰ってるので、どの領域もある程度はわかるようになった上で自分の得意領域を作れれば良いなと思ってます。 関口 自己紹介 こんにちは!スマートキャンプ21卒エンジニアの関口大地です。 ガジェット集めが趣味で、最近は Keychron K6 を買いました!Keychronのキーボードはデザインも打感も最高です! 去年の4月から内定者としてインターンを始め、中田くんと同様 BOXIL の開発に携わっています。 内定者インターンについて 1年間の内定者インターンでは以下の業務を行いました。 - BOXIL 2020/04~2020/08 - BOXIL SaaS、BOXIL マガジンのスタイル修正 - BOXIL SaaSの管理者用機能実装 2020/09~2021/01 - インフラ移行プロジェクト - BOXILのインフラ基盤をEC2での運用からECSでのコンテナ運用に刷新するインフラプロジェクトに参加しました - CI/CD フロー構築 - 運用構築 2021/02~ - BOXIL SaaS向け新規機能開発 - 仕様検討 - タスクプランニング - プロト開発 内定者インターンを始めたばかりの去年の4月から8月には簡単なスタイル修正のタスクや管理者用の機能を担当していました。去年の9月からはBOXILのインフラ基盤をEC2からECSに移行するインフラプロジェクトに参加しました。 今までコンテナやECSというものをほとんど触ったことがない中でのプロジェクト参加であったため、自分に任されたタスクを自分の力だけでやりきることができなかったり、基盤の実装中にエラーがおきた際、そのエラーはどのレイヤーが原因となっておきているのか判断が難しいなど、様々な苦悩がありました。しかしプロジェクトを通してコンテナに対する理解、ECS,Fargateなどのツールに対する理解が深まりました。またエンジニアとして仕事をする上で大切にしたいことを学んだプロジェクトでした。 今年の2月からはBOXIL SaaS向けの新規機能開発を同期の中田くんと2人でおこなっています。このプロジェクトではGolangやReactなど弊社ではあまり使われてこなかった技術を採用しています。今まで私はRubyしか書いたことがなかったので静的型付けの概念などに苦戦することがありますが、触ったことのない技術を使ってプロダクト開発をする楽しさも感じています。 今後の抱負 1年間の内定者インターンを通していいものをチームで作りたいという思いが強くなりました。 様々なプロジェクトに参加させていただく中で自分の実力が足りてないことに対する苦悩や葛藤がたくさんありました。しかしスマートキャンプの皆さんの支えがありエンジニアの仕事を楽しめています。BOXILを今よりも更にいいプロダクトにしていけるように人間としてもエンジニアとしても成長していきたいです。がんばります! まとめ 本記事では入社エントリとして21卒エンジニアの2人が、インターンでの経験の振り返りや今後の抱負についてお話させていただきました。 スマートキャンプのエンジニアとして「Small Company Big Bussiness」の世界を実現していきます!
アバター
こんにちは!!! スマートキャンプでエンジニアをしている吉永です! 得意領域はフロントエンドで、スマートキャンプではBOXILというプロダクトに主に関わってます。 自己紹介記事はこちら 早速ですが、皆さんはIEでの表示崩れ対応をしたことがありますか? フロントエンド開発をしていると同じCSSが当たっているのにChromeとSafari、IEでレンダリングされるものが変わってしまう自体に遭遇することがあるため、各種ブラウザでちゃんとした挙動を調べる作業が生じることがあり、その作業でIEというブラウザの挙動を確認することをIE確認と言います。 IE自体、今後はサポートが終了されるという話も聞く上に、最近ではIEの対応を完全にやめた他社プロダクトも出始めてきました。 docs.microsoft.com しかし、現在BOXILではユーザの10%がIEからのアクセスとなっており、10%というユーザを切り捨ててしまうのはビジネス的にとてもリスクがあると考えています。なのでLPやサービストップなどの主要なページに対して、リリースなどのタイミングで定期的なIE確認を行っているのが現状です。 IE確認の方法としては、Windows機で開発していればそのまま確認することができますが、Macであればそうはいかないため仮想のWindows環境をVirtualBoxなどで別途ライセンスキーを購入し環境を構築することで確認する方法があるかと思います。 また、スマートキャンプでは入社時に好きなPCを選べますが、開発チームは基本的にMacを選ぶ方が多く、IE確認には別途、会社で用意しているWindows機で環境を立ち上げて確認していました。 しかし、やはり自前の端末で確認したいという気持ちもあり、つい最近自前で購入したM1 Macを使ってWindows環境を立ち上げてみようと思いこの記事を書くことにしました。 現在この記事の内容は、Parallels DesktopのM1 Preview版の終了に伴って使用できなくなっている可能性があります。 環境を作るにあたって Parallels Desktopの導入方法 1.Parallels Desktop for Mac with Apple M1 chipをダウンロードする 2.Windows 10 on ARM Insider Previewをダウンロードする 3.シリアルキーを使いアクティベートする 4.仮想環境が立ち上がる まとめ 環境を作るにあたって Windowsの環境をMacで作るに当たって思い浮かぶのは真っ先に思い浮かぶのはVirtualBoxでしたが、VirtualBoxはM1 Macには未対応でした。 そのため最近Preview版が公開されたParallels Desktopを使っての環境構築をします。 Parallels Desktopの導入方法 1.Parallels Desktop for Mac with Apple M1 chipをダウンロードする まず、以下のページにアクセスしGoogleアカウントなどでログイン後、Try Technical Previewのボタンを押しParallels Desktop for Mac with Apple M1 chipをダウンロードします。 www.parallels.com すると、上の画像のようなページに遷移します。 このページに記載されているActivation keyは、あとでParallels Desktopを立ち上げる際に使うので、まだタブは閉じないで下さい。 2.Windows 10 on ARM Insider Previewをダウンロードする 次に以下のページにアクセスします。 www.microsoft.com Microsoftアカウントなどでログインしていない場合は上の画像の画面に遷移するかと思いますが、ログインすると以下の画面への遷移が可能になるかと思います。 通常のintel用のイメージでは、M1 Macで仮想環境を立ち上げることができないため、ARMプロセッサに対応したPreview版のイメージをダウンロードしなくてはいけません。 Windows 10 Client ARM64 Insider Preview - Build 21286を書かれたボタンをクリックするとダウンロードが開始され、VHDXファイルが手に入ります。 3.シリアルキーを使いアクティベートする Parallels Desktopを開くと以下の画面が出てくるため、先ほどダウンロードしたVHDXファイルを指定します。 すると、Parallelsで使用するディレクトリの場所を決めたり、シリアルキーをアクティベートする画面などに遷移するため、先ほどのシリアルキーをコピペします。 進むとロード画面が出てくるためそのまま待機して下さい。 4.仮想環境が立ち上がる ロード後、Macに保存されているファイルが同期された状態でWindowsの仮想環境が立ち上がります。 実際にIEの確認をしてみます。 トップページでは大きな崩れなどは見当たらず、ちょっと安心しました。 まとめ 一瞬で環境が構築できる上に、操作してる間のモサッとした仮想環境の動作感なども特に違和感なく、サクサク動いたためとても使いやすかったです。 私の場合、今のところIE確認以外の使い道を見出せていませんが、今後使い道が出てくるといいなと期待してこの記事を締めくくりたいと思います。 ※追記: この記事を書くためにM1 Macを買ったということにして会社経費でM1 Mac代が落ちたりしないかな〜って...笑 是非ご一考頂けますと幸いです。
アバター
スマートキャンプ、エンジニアの入山です。 前回のブログ にも書きましたが、弊社では昨年末から既存のEC2からECS/Fargateへのインフラ移行作業を実施しています。 EC2からECSへ移行する上では、特に運用面が大きく変わります。利便性やメンバーへの教育コストを考慮すると、今までEC2でやっていた運用をECSでどう上手く代替するかが力の入れ所だと思います。 一ヶ月前に弊社インターンの関口が書いた以下の記事も、既存運用の置き換えやデバッグ時の利便性向上を目的とした手段の1つで、この記事を執筆した時点ではECS/Fargate上のコンテナに対するAWS公式のログイン手段はありませんでした。 tech.smartcamp.co.jp 弊社のECS移行も稼働直前の佳境を迎えている最中ですが、この度Amazon ECS Execがリリースされ、待ち望んでいたECS/Fargate上のコンテナに対するAWS公式のログイン手段がついに実現したので、早速試してみたいと思います! 概要 前提条件 Amazon ECS Execの利用手順 ECSタスクロールの作成 作成したECSタスクロールを設定したECSタスク定義の作成 enableExecuteCommandオプションを有効化したタスクの起動 ECS Execでコンテナへログイン おわりに 概要 Amazon ECS Execは、Amazon ECSで稼働するコンテナへのログインを実現する機能です。 これまでECS上で稼働するコンテナへログインする手段として以下の方法がありましたが、公式に直接コンテナへログインができるようになります。 ECS on EC2 EC2インスタンスにSSH or SSMでログインしてdockerコマンドで直接ログイン ECS on Fargate 手段なし (セッションマネージャーを用いることでログインできるが非公式) ECS Execは、AWS Systems Managerのセッションマネージャーを利用して、SSMエージェント経由でクライアントからコンテナへのアクセスを実現しています。 コンテナの中にSSMエージェントが動作している必要はありますが、エージェント経由のためSSHのポート開放などは必要なく、シンプルかつ安全にコンテナへログインすることが可能です。 詳細については、以下の記事を参照してください。今回の記事の内容も以下の情報を元に実施しました。 NEW – Using Amazon ECS Exec to access your containers on AWS Fargate and Amazon EC2 | Containers Using Amazon ECS Exec for debugging - Amazon Elastic Container Service 前提条件 Amazon ECS Execを利用するためには、対応したバージョンのECS基盤・AWS CLIを利用する必要があります。 ECS on EC2 コンテナエージェントバージョン: 1.50.2以降 ECS on Fargte プラットフォームバージョン: 1.4.0以降 AWS CLI AWS CLI v1: 1.19.28以降 AWS CLI v2: 未対応(2021/3/16時点、今後数週間でリリース予定) AWS CLIは現時点でv1にしか対応していないので、注意が必要です。 (私も普段はv2を使っていますが、今回利用するために追加でv1をインストールしました。) また、AWS CLIのSession Managerプラグインもインストールが必要です。以下の手順に従って、インストールしておきます。 (オプション) AWS CLI 用の Session Manager plugin をインストールする - AWS Systems Manager Amazon ECS Execの利用手順 ECSタスクロールの作成 ECS ExecではSSMの機能を利用するため、ECSタスクロールにSSMに関連する権限を付与する必要があります。 ECSタスクロールを作成し、以下のIAMポリシーをアタッチします。既存のタスクロールへのアタッチでも大丈夫です。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel" ], "Resource": "*" } ] } 作成したECSタスクロールを設定したECSタスク定義の作成 ECSタスク定義の タスクロール に前項で作成したECSタスクロールを設定します。ECSタスク定義の作成手順については割愛します。 enableExecuteCommandオプションを有効化したタスクの起動 ECS Execの機能は、サービスやタスク単位での有効化が必要で、 enableExecuteCommand オプションで設定します。 2021/3/16時点では、AWSマネジメントコンソールから設定や確認を行う方法は提供されておらず、以下のAWS CLIコマンドでのみ利用可能なようです。 create-service update-service start-task run-task なお、 enableExecuteCommand オプションが有効化された状態で起動したタスクでのみ利用可能であり、既存のタスクには適用できないため注意が必要です。 (update-serviceの場合は、タスクの再デプロイが必要) 今回は、 run-task コマンドによるFargateのタスク起動に enableExecuteCommand オプションを追加して試してみます。 $ aws ecs run-task \ --cluster test-cluster \ --task-definition test-task \ --count 1 \ --enable-execute-command \ --launch-type FARGATE \ --network-configuration 'awsvpcConfiguration={subnets=["subnet-xxxxxxxxxxxx"],securityGroups=["sg-xxxxxxxxxxxx"],assignPublicIp="DISABLED"}' タスク起動に成功すると以下のようなレスポンスが返ってきます。 enableExecuteCommand が true となっていれば成功です。 { "tasks": [ { "platformVersion": "1.4.0", "taskArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:task/test-cluster/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "launchType": "FARGATE", : : "enableExecuteCommand": true, } ] } ECS Execでコンテナへログイン コンテナへのログインには、 aws ecs execute-command コマンドを利用します。 SSMセッションマネージャーはAWSマネジメントコンソールからブラウザ上での操作も可能ですが、現時点でECS Execは未対応でAWS CLIからのみの操作となります。 コマンド実行の対象とするタスクやコンテナの情報を指定した上で、 --command に実行したいコマンドを記述します。今回はコンテナにログインして操作することを目標としているため、 /bin/sh を指定します。 なお、 --interactive オプションは必須となっています。 $ aws ecs execute-command \ --cluster test-cluster \ --task xxxxxxxxxxxxxxxxxxxxxxx \ --container app \ --interactive \ --command "/bin/sh" コマンドを実行するとセッションが開始され、コンテナにログインされた状態となります。 exit コマンドでシェルとセッションが終了されます。 The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-xxxxxxxxxxx # # whoami root # # exit Exiting session with sessionId: ecs-execute-command-xxxxxxxxxxx. せっかくなのでログインユーザーも確認してみた所、 root ユーザーとなっていました。 冒頭で紹介した前回記事の方法だと、 ssm-agent ユーザーでのログインだったため権限に一手間かかっていたのですが、デフォルトが root となっているのでそういった手間も不要そうです。 おわりに 今回は、Amazon ECSのコンテナにログインが可能となるAmazon ECS Execをご紹介しました! 以前からECSコンテナにログインする手段をリリースする旨の発言はあったものの、いつ出るんだろう?と首を長くして待っていた方も多いと思います。 ECSやFargateへの移行におけるハードルや不安要素の1つとなっていたコンテナへのログインが正式にサポートされたことで、世の中のコンテナ化がまた一歩進みそうな気がします。リリース当初の現時点では制約や未対応の部分も多いですが、監査ログやアクセス制御などにも対応しており、今後のアップデートに期待大です! なお、以前紹介したAWS CopilotもECS Execに対応しているようです。こちらもぜひ試してみてください! tech.smartcamp.co.jp
アバター
こんにちは、 BOXIL 開発に携わっている、新卒エンジニアの高砂と申します! 皆さんは開発に携わっているプロダクトで、開発効率の向上に取り組めていますか? 本記事では、弊社で定期的に開催している「改善Day」について紹介します! 改善Dayとは 改善Dayが始まった経緯 改善Dayの実際の進め方 相談ミーティング(通称:薪割り) 改善Day(通称:薪入れ) 改善Dayで得られた効果 改善Dayでの失敗 まとめ 改善Dayとは 改善Dayとは「開発効率向上」を目的とし、1日かけてリファクタリングや開発環境の改善等に取り組む日の事です! 弊社では「Small Company, Big Business.」、すなわち「少数精鋭」を企業理念として掲げており、開発チームとしても一人あたりの生産性を高める為に開発効率の向上は重要視しています。 そんな改善Dayは、参加するメンバーが愛着を持ちやすくなるように、社内ではBOXILのロゴ(下記画像)のモチーフであるキャンプファイヤーになぞらえて「薪入れ」と呼んでいます。 改善Dayが始まった経緯 元々BOXIL開発チームでは、プロジェクトの合間に1週間使って改善に取り組む文化がありました。 ただそれは下記のように難点がいくつかありました。 いつ開催されるのかが決まっていない 開催の度に企画チームとの調整が必要 1週間丸々開発が進まないと困る事が多い その為、「もっと良い改善フローがあるのではないか」と考え、去年の夏頃に手を上げてその企画を任せてもらったのが始まりでした(下記画像は初回開催時のドキュメント)。 改善Dayの実際の進め方 改善Dayは当日の開催に加え、その前日までに取り組むタスクの相談ミーティングを行います。 相談ミーティング(通称:薪割り) 30分〜1時間程度で、改善Dayで誰がどのタスクに取り組むかを相談するミーティングです。 このミーティングまでに改善Dayで取り組みたい内容を各自考えておいてもらった上で、その中からどのタスクに当日取り組むかを投票で決定します。 また、1日でやりきれるようにタスクのスコープを設定します。 改善Day(通称:薪入れ) 当日の定時1時間前までで、前日までに決めた取り組むタスクを進めていきます。 その時間内でのレビューやリリースを目標としているので、2回の進捗共有会を行い状況確認と不明点の相談を行います。 もし定時1時間前までにレビューやリリースが完了しなかった場合は、そこから定時までの1時間の間に行うというルールにしています。 改善Dayで得られた効果 やはり一番の効果は数多くの改善結果です。 去年11月からの取り組みだけでもたくさんの改善をしている事が見て分かるかと思います。 加えて普段の開発でも改善の意識が高まり、「ここの機能開発のついでにリファクタリングをしよう」という取り組みが多く見られるようになりました。 改善Dayでの失敗 上手くいかない事が一番多かったのは「開催日のうちにレビューやリリースを完了させる事」でした。 改善系タスクはスコープを設定しているとは言え、「ここはもう少し良くしたい」とこだわりたくなる事が多く、それ故に相互レビューやリリースに時間を割く事の優先度が下がってしまいがちです。 そこで弊社では最初は19時終了だったのを1時間早めてレビュー・リリース対応の時間を確保したり、実験的に連続2日間に増やして余裕を持ってタスクを終わらせられるようにしたりと試行錯誤中です。 まとめ このように、弊社では普段取り組みづらい「開発効率向上」に対し、定期的に時間を確保する事で取り組んできました。 プロダクトの年数やチームの人数が増えれば増えるほど開発効率は落ちてしまいがちですが、そのような時は定期的に「改善Day」を開催してみてはいかがでしょうか? 本記事が、開発効率向上に取り組まれる方の参考になれば幸いです!
アバター
こんにちは、スマートキャンプのエンジニアの井上です。 私はプロダクト開発がメインの業務ですが、それとは別にエンジニアイベントの運営をしています。 先日、PM・PO向けのイベントとして 「B2B SaaSエンジニアMeetup - SharingIssues Online #2 仮説検証」 というオンラインイベントを開催しました。 もともとオフラインで開催していたイベントをオンラインで実施した2回目のイベントでした。 私自身も非常に勉強になる内容を登壇者の方々に発表していただきました。 このエントリでは、イベントレポートとしてセッションの内容やその感想を書かせていただき、セッション内容のご紹介できればと思います! 後日、アーカイブも配信予定なので、詳しい内容は是非そちらでごらんください! 「ユーザーの声を聞く」から始める仮説検証〜PMが1ヶ月オンライン商談同席してみた〜 登壇者 概要 感想 youtube 仮説検証のスタック地獄から脱出する 登壇者 概要 感想 youtube 価値あるものをチームで届ける マネーフォワード クラウド会計Plusで実践している仮説検証プロセス 登壇者 概要 感想 youtube 実践的ジョブ理論 〜ジョブをベースに仮説を検証する〜 登壇者 概要 登壇者 感想 youtube 全体通して 「ユーザーの声を聞く」から始める仮説検証〜PMが1ヶ月オンライン商談同席してみた〜 登壇者 スマートキャンプ株式会社 BALES CLOUD事業本部 プロダクト開発部 プロダクトマネージャー 郷田祥史 概要 仮説検証について 今回話す仮説検証について チームの役割 商談同席 準備編 なぜ、商談に同席したか? 商談の選び方 商談時の役割認識 商談設計に組み込む 商談同席 実践編 商談でインタビューしてみる 顧客のタイプをまとめる プロダクトで、できること/できないこと 発見した課題 感想 PMがいかに商談に同席しユーザーヒアリングをしていくかについて 準備編では、商談に入る前の設計やどのようにセールスメンバーと役割を分けているかが実体験を元に話されています。 実践編では実際にインタビューしただけでなく、結果をどのようにまとめて課題を明らかにしていったかが解説されています。 難しいセールスとの関係構築やお互いのやることの明確化などについても説明されています。 最後にやってみて発見した課題も共有されているので商談設計においてとてもいい事例だと感じました。 youtube youtu.be 仮説検証のスタック地獄から脱出する 登壇者 株式会社フリークアウト Demand Product Div. 事業開発・プロダクトマネージャー山下健志 様 概要 今回の対象とする仮説検証について スタックしてしまう3つの状態 スティービー・ワンダー状態 欠席裁判状態 バンジージャンプ状態 導入したものOODAループ OODAループ 具体的な運用 導入のメリット 感想 大きい仮説検証をすすめる上での陥る状態の説明がとてもわかりやすく どのようなときにスタックする3つの状態に陥るのか、どのような要因があるのかそれぞれ解説されています。 また、実際に社内で行っている効果的な手法を紹介していただく非常に参考になる内容でした。 OODAループを使った仮説検証の進め方と、導入することによりスタックする3つの状態からの変化なども解説されています。 OODAループは自分も初めて聞いたのでとても勉強になりました! youtube youtu.be 価値あるものをチームで届ける マネーフォワード クラウド会計Plusで実践している仮説検証プロセス 登壇者 マネーフォワードビジネスカンパニー 経理財務ERP本部 京都開発部 マネーフォワードクラウド会計Plus プロダクトオーナー 杉浦 大貴様 概要 なぜ、仮説検証をしたいのか? ビジネスを成長させるループ 発見のプロセスで達成したいこと プロダクトを知る3つのループ ユーザーを知るループ 内省のループ チームを作るループ ループは影響し合う 3つのループとビジネスが成長するループ ループの関係性 3つのループをプロセスに落とし込む 実践している発見のプロセス 意味の熟成 ビジネスチームへの共有 感想 「価値のないものは作りたくない」という作ったあとで共有するのではなく 事前に仮説検証をチームで「価値のあるものを作る」にしていくを事例を解説されており そのために必要なプロダクトを発見する3つのループを使い仮説の質を高めていく流れを解説されています。 また、実践での活用法もわかりやすくまとめられており チームで動く仮説検証の事例としてとても参考になりました! youtube youtu.be 実践的ジョブ理論 〜ジョブをベースに仮説を検証する〜 登壇者 株式会社セールスフォース・ドットコム マーケティング本部 プロダクトマネージャー 早川 和輝様 概要 イノベーションとは イノベーションの3つの円 イノベーションに役立つ考え方 プロダクトのライフサイクル 問題とソリューションを分けて考える JOB理論を使って仮説を検証しプロダクトを考える Let's try JTBD JTBDの書き方 JTBDの仮説をつくる 1つのジョブをさらに深ぼっていく JTBDの仮説をゴールの種類で分類する ジョブパフォーマーを考える バリアを考える 代替手段を考える 完成したジョブの仮説 ユーザーインタビュー:ジョブは必ずユーザーの声から生まれる 登壇者 株式会社セールスフォース・ドットコム マーケティング本部 プロダクトマネージャー 早川 和輝 様 感想 「顧客はプロダクトを買っているのではなく、ジョブを成すためにプロダクトを雇っている」という考え方を元に 仮説検証でユーザーがどのようなジョブを求めているかについて、仮説を作る上で必要な要素はどんなものかを解説していただきました。 ジョブのゴールを機能的、感情的、社会的と分類してどのようなジョブを解決していくか ジョブを作る上でのバリア(障壁)となるものについて、ジョブの代替手段となるもはなにか?という要素から 誰が、いつ、何をしたくて、できるようになりたいことなどを仮説リストとして作るまでを紹介されています。 仮説検証における、ジョブ理論のやり方としてわかりやく解説していただきました! 自分も早川さんのnoteなどは拝見していますが、実際に解説してもらうことで勉強になることが多かったです。 youtube youtu.be 全体通して 登壇者の皆様のおかげでテーマである「仮説検証」において、とてもいいノウハウを共有する場になったのではないかなと感じています。 登壇内容以外でも参加者からの良い質問も多く、その回答も参考になるかと思いますので、アーカイブでご覧いただければありがたいです。 これからもBtoBの課題と解決方法をシェアする場所としてSharingIssuesを続けていきたいと思います。 次回のイベントは4月開催を目指しています!
アバター
こんにちは、スマートキャンプで BALES CLOUD を開発している中川です! 昨年の 9 月に Vue.js のメジャーバージョンが 3 になりかれこれ半年ほど経ちますが、みなさんのプロジェクトでは移行が進んでいますか? 私が普段開発している前述のプロダクトにも Vue.js の 2.x 系を採用しているのですが、利用している UI ライブラリが Vue3 に対応するのを待っている関係でまだ移行には至っておらず、 先んじて Lint のルールを Vue3 のものに切り替えて準備だけ進めているような状況です。 さて、Vue3 といえば Vue.js の発起人である Evan You 氏が始めた高速な開発用フロントエンドサーバーを実現する OSS である Vite が昨年話題になりました。 話題になったタイミングで私も 記事を書いた のですが、その時点ではまだ Beta ですぐに本番投入できる段階ではなさそうだったので、そのままキャッチアップがおろそかになってしまっていました。 そんななか、先日 Vite のメジャーバージョンが上がり 2.0 となり、リリースをアナウンスする記事が Evan You 氏本人から出されました! また、この 2.0 をもってめでたく Beta が外れました。 dev.to この記事では、Vite が今どのような状況にあるのか、どういった機能が追加されているのかなどを、アナウンス記事や周辺のエコシステムを参照しながら確認していこうと思います! バージョン 2.0 の新機能 フレームワーク固有のサポートをプラグインに移譲 Rollup プラグインの互換性 依存関係の事前バンドルを 10~100 倍高速化 CSS サポートの強化 SSR(Server Side Rendering)のサポート オプトインのレガシーブラウザサポート Vue 周辺のエコシステムの動きについて まとめ 参考ドキュメント バージョン 2.0 の新機能 フレームワーク固有のサポートをプラグインに移譲 これは新機能というよりも再設計に近いかと思いますが、当初話題になった通り、Vite はあくまで Vue 用(それも Vue3)のツールとして存在していました。 しかし Vite2.0 では Vite と Vue の依存は切り離され、プラグインとして個別のフレームワークやライブラリのものを使用する方向に転換されました。 現在は Vue, React, Preact, Lit Element の公式テンプレートがあり、Svelte の対応も行われている最中のようです。 以下のリポジトリから各テンプレートの中身を確認することが出来ます。 github.com Rollup プラグインの互換性 モジュールバンドラーである Rollup のプラグインの多くを Vite に対して利用することができます。 対応状況は以下のサイトから確認できますが、 incompatible となっているものは少なく、主要なプラグインは揃っている印象です。 Vite Rollup Plugins また、細かなカスタマイズを可能にするために、Vite 固有のフックやプロパティを追加することもできます。 使用する場面の例としては、開発サーバとビルド間の差異であったり、HMR(Hot Module Reloading)のカスタマイズなどがあげられていました。 依存関係の事前バンドルを 10~100 倍高速化 2.0 以前の Vite では依存関係の事前バンドルに Rollup を使用していました。 これを esbuild に変更することで高速化を実現したようです。 そもそも依存関係の事前バンドルがなぜ必要なのか?というところですが、これは Vite がネイティブの ESM を前提とした開発サーバであることから、 CommonJS から ESM への変換が必要というところと、あとは単純にバンドルすることでブラウザからのリクエスト数を減らす狙いがあります。 これは特に依存関係が重いプロジェクトにおいてインパクトが強いようです。 上記記事中では、たとえば React Material UI のような、と紹介されていましたので、Element UI や Vuetify など他の UI ライブラリでも同様に問題とされていたはずです。 CSS サポートの強化 主に URL や ESM、バンドル周りのサポートが強化されているようです。 CSS ファイル中の @import や url() でのエイリアスや、npm( node_modules )の依存関係をサポート url() で指定したパスが、どこからインポートされたかに関わらず自動的にリベースされるように コード分割された JS のチャンクが対応する CSS ファイルも出力し、リクエストされた時に JS のチャンクと並行して自動的にロードされるように SSR(Server Side Rendering)のサポート まだ experimental なものの、2.0 では SSR をサポートしています。 Vite は高速な開発サーバを実現するために、Node.js 環境で EMS ベースのソースコードを効率的に読み込んだり更新するための API を提供しています。 記事中の表現を借りると、 ほぼサーバーサイドのHMRのようなもの となっているため、SSR との親和性も高かったのではないかと推測しました。 詳しい特徴や手順は以下のドキュメントに詳しいです。 Server-Side Rendering | Vite オプトインのレガシーブラウザサポート 何度も書いてきましたが、そもそも Vite はネイティブな ESM サポートに対応しているモダンなブラウザーを対象としています。 ですが、 @vitejs/plugin-legacy プラグインを使うことでレガシーブラウザに対応させることができるようになりました。 また、このプラグインを使用することで自動的にモダン版とレガシー版という異なる 2 つのバンドルを生成し、ブラウザの機能検出に基づいて適切な方のバンドルを配信することができます。 Vue 周辺のエコシステムの動きについて ここまで Vite のバージョン 2.0 の新機能について見てきましたが、最後に Vite を取り巻く Vue 周辺のエコシステムについて箇条書きでまとめます。 プラグインとして React などを対応しているとはいえ、現段階では対応前の状況や製作者の出自を鑑みるに Vue コミュニティとの結びつきが強いように感じています。 Vue2.x プラグイン GitHub - underfin/vite-plugin-vue2: Vue2 plugin for Vite Vite 本体の Vue プラグインは Vue3 用なので、Vue 2.x 系で Vite を利用するために必要なプラグインです Vue CLI の Vite プラグイン GitHub - IndexXuan/vue-cli-plugin-vite: Use Vite Today, with vue-cli ビルド時は Vue CLI 内蔵の Webpack を利用しつつ、開発サーバは Vite を利用したいのがモチベーションのようです Vue CLI で作られた既存のプロジェクトでコードベースの変更なしに利用できると謳われています examples も作られているのである程度真だとは思いますが、依存関係が深いとき、たとえば Webpack の Loader を重ねているような環境でもうまくいくのかは気になります NuxtJS バージョン 3 での Vite サポート これは現状ではなく、NuxtJS バージョン 3 で対応予定なので将来の話ですが... https://twitter.com/debs_obrien/status/1355782424219811843 ツイート主は Vue.js Amsterdam において Nuxt の講演も行っていた Debbie O'Brien 氏 まとめ ここまでざっと Vite2.0 の概観と Vue 周辺のエコシステムの動きについて見てきました! 個人的には、前述した担当プロダクトは Vue CLI で構築したものなので Vue CLI の Vite プラグインが気になっており、開発サーバを Vite に出来ると費用対効果が高そうなので一度試してみるつもりです。 過去記事でも触れましたが、Vite の爆速さは目をみはるものがあるのでまだ試したことの無い方はこの機会に試してみることをオススメします! それでは! 参考ドキュメント https://dev.to/yyx990803/announcing-vite-2-0-2f0a https://vitejs.dev/ https://vitejs.dev/guide/
アバター