BASE株式会社のブログ - TECH PLAY

TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

611

こんにちは!アプリチームのEMをしている竜口です! 今回はリモート下でチームのコミュニケーションに課題があったので、それをどう改善していったかを紹介していきたいと思います。 初手、どうありたいかを決める やったこと いきなりxxxを始めます/やってみます!!と言ってもチームメンバーとしては意図、どうありたいかがわからないと、どう行動するべきかわからないと思います。 なので、何の為に改善して、どうありたいんだっけ?って部分を明文化しメンバーに伝えました。 実際はもっと肉付けしたものですが、下記がメンバーに伝えたものです。 1. 改善、課題解決していく 2. 開発をより早く、より確実にしていく 3. チームで開発していく 正直内容としては、特に真新しいものでもなく当たり前なものだと思いますが、今どのようなチームを目指しているのかというのを、メンバーと認識合わせました。 効果 個人的には、これが一番良かったかなと思っていて、この共通認識をもつことでメンバーも意識的に改善に向かっていってもらえたかなと思っています。 ツールに頼ってみる、tandem導入 やったこと tandem を簡単に説明すると A virtual office for remote teams を実現させるサービスで、 リモートで一緒に働く上でチーム感での会話のしやすさの実現、お互いの状態を緩く把握することで一緒のオフィスで働いてる感を醸成してくれます。 youtu.be 最初はアプリチームで導入し、その後アプリ開発に関わる他のプロジェクトメンバーも含めて導入しました。 効果 Slack or Zoomでコミュニケーションとってる時と比べ、ツールとしての会話のしやすさであったり会話できそうな状態か否かがわかりやすいのもあってコミュニケーションは増えていきました。 参考までに実際のtandemの様子ですが、各部屋に誰がいるのか分かるとこや、特定の人が緑色の場合はactive/オレンジだとinactiveという風に話しかけて良さそうな雰囲気も醸成できて話しかけるハードルや他の人の話してる雰囲気が伝わったりします。 朝会の後に意味のない時間を作る やったこと 毎日、朝会と言ってやること/困りごとを共有する場を15分程設けています。 その中で朝会後にすぐ抜ける必要のない運用にしてみました。朝会が終わってすぐ抜けてもいいし、話題がなくてもただそこにいてもいいし、何か特定の人に聞きたいことあれば聞いていいし、雑談してもいい。 効果 雑談が増えたのと、朝会でみんなの時間取るほどではないしSlackで聞くほどではない疑問とかが自然と出てきてよいです。 このルールだとtandemからの抜けにくさが出るかなと思ったのですが、お昼ご飯時なのもあり抜けやすさがあり、そこもメンバーのお気持ち的にも良かったのかなと。 作業タイムを作る やったこと ただただtandemつなぎっぱなしで作業する時間を2時間/1週間入れてみました。 ただ同じ空間で働いてる感を作りたかったので、ルールとしては強制参加だけど話したりカメラ繋いだりは強制しない形でやってみました。 効果 雑談や軽い相談も出てきてよかったです。ただずっとオンラインで繋げることによる集中力下がるというフィードバックがあったり時間や運用の仕方は、まだまだ調整が必要な部分もあります。 またtandemに Crosstalk という機能があり、同じ部屋にいる特定の人と話す時、会話に関係ない人にはその会話が小さくしか聞こえない機能があり、会話の指向性がでて話題に関係ない人のストレスにもなりにくくよかったです。 まとめ 今回は比較的導入が簡単なものをやってみて、以前よりチーム内での会話が増えて、一人で問題を抱え込むような形は改善されたかなとは思います。 以下メンバーの生の声です! - 朝会の後、いま取り組んでる課題とかに自然と移行できていい - Tandemは、いまみてるGitHubのURLとかを自動で取ってきて表示してくれる機能があって、IssueやPRの話などをしやすい - 朝回の前はあえて話すことないかなーと思っていたりするけど、意味のない時間に入ると敷居が下がって話せたりする - Tandemは、いざってとき会話するハードルが低い。ちょっと確認したいけど込み入った問題で確認したいぐらいの温度感のときに会話しやすいのが助かる ただまだ改善の余地はあって、チームとしてプロダクトを成長させる為に勉強会等を今後やっていきたいと思います。 そして!今アプリチームでは一緒にプロダクトを成長させていける方を募集しています! iOS 採用情報 / カジュアル面談 Android 採用情報 / カジュアル面談
BASE株式会社 Owners Experience Frontend チームのパンダ( @Panda_Program )です。 BASE では BASE の UI を構築するための社内コンポーネントライブラリ「BBQ」を使ってフロントエンドの開発をしています。 BBQ は Vue2 + Storybook v5 で作成されています。現在、フロントエンドの有志たちで Storybook のバージョンを最新の v6.2 にする対応をしています。 この記事では、Vue2 + Storybook v5 のコンポーネントを v6 向けに書き換える方法を紹介します。 なお、本記事ではStorybook v6 自体の機能の説明や、 main.js や preview.js の書き方といった Storybook の環境構築の方法には触れません。 Storybook コンポーネントを v5 から v6 に書き換える ここでは button-group を v5 から v6 に書き換えた例を紹介します。 まずは v5、v6 の書き方をそれぞれご覧ください。その後、変更点をそれぞれ解説をしていきます。 v5 の button-group.stories.js // bbq/stories/elements/button-group.stories.js import { action } from '@storybook/addon-actions' import { number, select, text, withKnobs } from '@storybook/addon-knobs' import { storiesOf } from '@storybook/vue' import { withInfo } from 'storybook-addon-vue-info' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' import README from '../../elements/buttonGroup/README.md' import { devices } from '../../values/Devices' // Storybook コンポーネント名 const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) buttonStories // addon-knob .addDecorator(withKnobs) // addon-info .addDecorator(withInfo) .add( 'ButtonGroup' , () => { return { components: { ButtonGroup } , // Vue Template template: ` <div : class = "'theme-'+device" > <p> <h2>デフォルト</h2> <div>アイコン+ラベル</div> <bbq-button-group :tag= "tag" :items= "iconsAndLabels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>アイコン</div> <bbq-button-group :tag= "tag" :items= "icons" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>ラベル</div> <bbq-button-group :tag= "tag" :items= "labels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> </p> <p> <h2>カスタムUI</h2> <bbq-button-group :tag= "tag" :items= "['a','b', 'c', 'd']" @change= "({index}) => change(index)" :selected= "selected" :width= "width" > <template v-slot= "{items, change}" > <button v- for = "(item, index) in items" @click= "change(index)" > {{ item }} : {{ index }} </button> </template> </bbq-button-group> </p> </div> `, // Data data() { return { device: select(`device`, devices, 'pc' ), selected: number( 'selected' , 0), width: select( 'width' , [ '' , 'full' ] ), } } , // Props props: { tag: { default : text( 'tag' , 'ul' ) } , } , // Computed computed: { icons() { return [{ icon: 'list' } , { icon: 'grid' }] } , labels() { return [{ label: 'ドラッグで並び替え' } , { label: '数値で並び替え' }] } , iconsAndLabels() { return [ { icon: 'list' , label: 'リストで並び替え' } , { icon: 'grid' , label: 'グリッドで並び替え' } , { icon: 'attentionCircle' , label: '念で並び替え' } , ] } , } , // methods methods: { change: action( 'change' ), } , } } , // Parameters { notes: README, } ) v6 の button-group.stories.js // bbq/elements/buttonGroup/button-group.stories.js import { devices } from '../../values/Devices' import ButtonGroup from './ButtonGroup' import README from './README.md' export default { // Storybook コンポーネント名 title: "V6/Elements/ButtonGroup/Vue" , // import したコンポーネントを指定 component: ButtonGroup, // parameters parameters: { notes: { README } , docs: { extractComponentDescription: ((_, { notes } ) => notes?.README) } } , argTypes: { // addon-knob の select で定義していた変数 device: { options: devices, defaultValue: devices [ 0 ] , control: { type: "select" } } , width: { options: [ "" , "full" ] , control: { type: "select" } } , // addon-action で定義していた関数 change: { action: 'changed' } } } ; const Template = (args, { argTypes } ) => ( { components: { ButtonGroup } , props: Object .keys(argTypes), template: ` <div : class = "'theme-'+device" > <div> <h2>デフォルト</h2> <div>アイコン+ラベル</div> <bbq-button-group :tag= "tag" :items= "iconsAndLabels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>アイコン</div> <bbq-button-group :tag= "tag" :items= "icons" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> <div>ラベル</div> <bbq-button-group :tag= "tag" :items= "labels" @change= "({index}) => {this.selected = index; change(index)}" :selected= "selected" :width= "width" /> </div> <div> <h2>カスタムUI</h2> <bbq-button-group :tag= "tag" :items= "['a','b', 'c', 'd']" @change= "({index}) => change(index)" :selected= "selected" :width= "width" > <template v-slot= "{items, change}" > <button v- for = "(item, index) in items" @click= "change(index)" > {{ item }} : {{ index }} </button> </template> </bbq-button-group> </div> </div> ` } ); export const Default = Template.bind( {} ) Default.args = { // Default コンポーネントに与える Props selected: 0, tag: "ul" , icons: [{ icon: 'list' } , { icon: 'grid' }] , labels: [{ label: 'ドラッグで並び替え' } , { label: '数値で並び替え' }] , iconsAndLabels: [ { icon: 'list' , label: 'リストで並び替え' } , { icon: 'grid' , label: 'グリッドで並び替え' } , { icon: 'attentionCircle' , label: '念で並び替え' } , ] , } ; なお、 devices の定義は const devices = ['pc', 'sp'] です。 Storybookv6の変更点 上記、新旧ファイルの変更点を抜粋してコードを比較します。 Storybook 上のコンポーネント名 Storybook で表示されるコンポーネント名の定義方法の変更点です。 // v5 import { storiesOf } from '@storybook/vue' const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) // v6 export default { title: "Elements/ButtonGroup" , // ... } v6 では @storybook/vue を import する必要がなくなりました。その代わりに、default export するオブジェクト内にコンポーネントのメタ情報を記述します。 表示するコンポーネントを指定 コンポーネントを指定する箇所も変更になっています。 // v5 import { storiesOf } from '@storybook/vue' import { ButtonGroup } from '../../elements/buttonGroup/button-group.vue' const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) buttonStories.add( 'ButtonGroup' , () => { return { components: { ButtonGroup } , // ... } } ) // v6 import ButtonGroup from './ButtonGroup' export default { // ... component: ButtonGroup, } v6 ではコンポーネントを default export するオブジェクトのプロパティに追加します。 parametersを定義する箇所の変更 v5 では add メソッドの第三引数だった parameters の定義箇所が、v6 では default export するオブジェクトに変更になりました。ここではマークダウンファイル README.md を v6 で読み込める書き方を紹介します。 // v5 import { storiesOf } from '@storybook/vue' import { withInfo } from 'storybook-addon-vue-info' import README from '../../elements/buttonGroup/README.md' const buttonStories = storiesOf( 'Elements/ButtonGroup' , module) buttonStories .addDecorator(withInfo) // decorator で addon-vue-info を活用している .add( 'ButtonGroup' , () => { ... } , { notes: README } ) // v6 import README from './README.md' export default { // ... parameters: { notes: { README } , docs: { extractComponentDescription: ((_, { notes } ) => notes?.README) } } , } v6 では Storybook 上の Docs タブで README.md を表示できます。このため、 @storybook/addon-notes 、 storybook-addon-vue-info は不要になります。 今回は v6 のコンポーネント内で定義しましたが、 preview.js に以下のように記述すると Storybook の全コンポーネントに parameters が追加されるため、 docs を各ファイルで記述すること避けられます( 「Migrating from notes/info addons」 )。 // preview.js import { addParameters } from '@storybook/client-api' ; addParameters( { docs: { extractComponentDescription: ((_, { notes } ) => notes?.README) } , } ); なお、今回は既存資産を活かすためにマークダウンファイルをそのまま使いましたが、 MDXを用いる方法も公式で紹介されています。 addon-knob の書き換え v5 では addon-knob を使うと Storybook 上でコンポーネントに与える値を画面上で変更できました。 v6 では addon-essentials に含まれている controls を使えば同様のことができます。 以下では knob の number 、 select 、 text 関数を書き換えています。 // v5 import { number, select, text, withKnobs } from '@storybook/addon-knobs' import { devices } from '../../values/Devices' buttonStories. add( // ... data() { return { device: select(`device`, devices, 'pc' ), selected: number( 'selected' , 0), width: select( 'width' , [ '' , 'full' ] ), } } , props: { tag: { default : text( 'tag' , 'ul' ) } , } , } ) // v6 import { devices } from '../../values/Devices' export default { // ... argTypes: { // select 関数で作成していた値 device: { options: devices, defaultValue: devices [ 0 ] , // 初期値の設定 control: { type: "select" } // この行は省略可能 } , width: { options: [ "" , "full" ] , control: { type: "select" } // この行は省略可能 } , } ; const Template = (args, { argTypes } ) => ( { ... } ); export const Default = Template.bind( {} ) Default.args = { selected: 0, // number 関数で作成していた値 tag: "ul" , // text 関数で作成していた値 } ; select 関数の代わりになる control: { type: "select" } で定義した値は、 defaultValue で初期値を設定できます。 なお、 export default の中で定義している device 、 width は以下のように Default.args で定義することも可能です。 // v6 Default.args = { device: devices, width: [ "" , "full" ] , selected: 0, tag: "ul" , } ; (参考: Dealing with complex values ) addon-actions の action 関数の書き換え addon-actions を使ったダミーのコールバック関数の定義方法も変更になりました。 // v5 import { action } from '@storybook/addon-actions' buttonStories .add( // ... () => { // ... methods: { change: action( 'changed' ), } , } } ) // v6 export default { argTypes: { change: { action: 'changed' } } } ; (参考: addon-actions ) ただし、以前のように action 関数を用いても問題なく動作するため、書き換えは必須ではありません。 コンポーネントに渡すデータの定義の変更 v5 で記述していた data, props, computed で定義していた値を Storybook の画面上で自由に変更したい場合は、 argTypes や args に集約可能です。 // v5 buttonStories .add( // ... () => { // ... // Data data() { return { device: select(`device`, devices, 'pc' ), selected: number( 'selected' , 0), width: select( 'width' , [ '' , 'full' ] ), } } , // Props props: { tag: { default : text( 'tag' , 'ul' ) } , } , // Computed computed: { icons() { return [{ icon: 'list' } , { icon: 'grid' }] } , labels() { return [{ label: 'ドラッグで並び替え' } , { label: '数値で並び替え' }] } , iconsAndLabels() { return [ { icon: 'list' , label: 'リストで並び替え' } , { icon: 'grid' , label: 'グリッドで並び替え' } , { icon: 'attentionCircle' , label: '念で並び替え' } , ] } , } , // ... // v6 export default { // ... argTypes: { // addon-knob の select で定義していた変数 device: { options: devices, control: { type: "select" } } , width: { options: [ "" , "full" ] , control: { type: "select" } } , } ; // ... export const Default = Template.bind( {} ) Default.args = { selected: 0, tag: "ul" , icons: [{ icon: 'list' } , { icon: 'grid' }] , labels: [{ label: 'ドラッグで並び替え' } , { label: '数値で並び替え' }] , iconsAndLabels: [ { icon: 'list' , label: 'リストで並び替え' } , { icon: 'grid' , label: 'グリッドで並び替え' } , { icon: 'attentionCircle' , label: '念で並び替え' } , ] , } ; ただし、data や props、computed をそのまま残すことも可能です。その場合、 props 以外は GUI 上で値を変更できません。Storybook の GUI 上で変更したい値であれば、args で記述すれば良いと思います。 BASE BBQ の.Storybook では様々なパターンがあるため、まずは v6 の書き換えを優先しています。このため、data 等で定義している値は一旦 args に集約し、コンポーネントごとの細かい調整は個別に対応する予定です。 以上のパターンで BASE の BBQ で作成された Storybook コンポーネントの大抵のケースを網羅しています。 その他、より詳しい変更点は、 Storybook 6 Migration Guide をご覧ください。 addon について v6 で利用可能な Essential addons には、今までの主要な.addon の機能がまとめられています。 Docs Controls Actions Viewport Backgrounds Toolbars & globals Storybook で開発するにあたり、 @storybook/addon-essentials は開発体験を向上させてくれるため、v6 からは必須といっても過言ではないでしょう。 ここでは、先程紹介した例で使用している addon のみ取り上げます。 addon-knob v6 では deprecated addon-essentials の controls を代わりに使う addon-info knob と同様に v6 では deprecated addon-essentials の docs を代わりに使う addon-action v7 で deprecated になる予定 v6 ではまだ使えるが、可能なら control に置き換えると次のバージョンアップがスムーズになる v6 で addon-action を使いたい場合は、以下のように記述すれば OK です。 const Template = (args, { argTypes } ) => ( { // ... template: `...`, methods: { change: action( "changed" ), } } ); 表示を確認する v6 の書き方に変更したコンポーネントを実際にStorybook で表示すると以下のようになります。 StorybookのButtonGroup ンポーネント control で値を変更して様々な props の表示ケースを確認できるようになりました。 また、「Docs」というタブをクリックすると README が表示されています。 StorybookのButtonGroupコンポーネントのDocs これで v6 への書き換えが完了しました。 Storybook v6 で向上した開発体験 v5 と異なり、v6 では以下の点で開発体験が向上しました。 コンポーネントに与えるデータを Vue の外(args, argTypes)で定義できる args として定義した値は addon-knob を使わなくても Storybook 上で値を書き換えられる 上記の例では取り上げていませんが、args を変えることでコンポーネントのバリエーションを容易に作成できる( using-args ) インストールする addon の数や、stories ファイルのボイラープレートが減った おわりに 今回は Vue2 + Storybook v5 の環境で Storybook を v6 にアップデートする詳細な方法を紹介しました。 Storybook のバージョンアップにあたり本記事の内容が参考になれば幸いです。 多くの方はお気づきだと思いますが、v5 から v6 への書き換えといってもパターンが決まっています。 このため、手順さえ分かってしまえばプログラムで機械的に置換するだけで対応できます。 この考え方をもとに、TypeScript Compiler API を使ってメタプログラミングで v5 のコンポーネントを v6 に書き換えたので、次回の記事でその方法をご紹介しようと思います。
この度は、5/29(土)にオンラインで開催された PHP カンファレンス沖縄 2021 にゴールドスポンサーとして協賛し、また 4 名のメンバーが登壇しました。 登壇者 4 名から発表内容の補足など、PHP カンファレンス沖縄 の参加レポートをお届けします! phpcon.okinawa.jp 発表内容と補足 杉浦のセッション内容について こんにちは!BASE 株式会社でバックエンドの開発をしている杉浦( yutakasugiura )です。この度、PHP カンファレンス沖縄 で、BASE のスポンサートークとして「変化する時代のエンジニアリング」について発表させていただきました。 発表内容の意図 PHP に関するカンファレンスでしたが、スポンサーセッションの発表内容は PHP 以外のことでも良いとのことでしたので、広い意味でエンジニアリングについてお話ししました。普段の業務におけるエンジニアリングとは PHP のような言語が話題の中心ですが、せっかく頂いた機会なので、より大きな視点でエンジニアリングを語ることにしました。 登壇資料 発表内容の要旨 企業活動におけるエンジニアリングはマーケットニーズに従属的です。2010 年代に CakePHP がフレームワークとして支持されたのも「便利な web サービスを早く使いたい!」というニーズが背景にありますし、これほど AWS が広まった理由も「いつアクセスが爆発的に増加するかわからない!」という課題への対処として有効だったからです。スマホの普及によって、web サービスへのニーズが 2010 年代を通じて爆発的に増加したからこそ、これらのエンジニアリングが時代の潮流となったと言えます。 一方で、注意しなければならないことは、エンジニアリング単体で物事を考えると、ニーズにそっぽを向いてしまう可能性があると言うことです。純粋な研究開発は別として、企業活動の最終目的は、顧客にサービスを提供することにあり、その手段としてエンジニアリングがあります。考えてみれば当たり前の事実ですが、当たり前だからこそ、この単純な事実を忘れやすいというのも、また真理です。 web 業界は誕生してまもないこともあり、ニーズと技術が乖離してしまうという経験値が少ない業界です。ですが、歴史を広く見渡せば、時代の変化によって、顧客ニーズとエンジニアリングが乖離してしまい、ビジネスとしては幸せにならなかった例は枚挙にいとまがありません。 今回のセッションでは、1980 年代の DRAM(ダイナミック・ランダム・アクセス・メモリー)におけるエンジニアリングの失敗例を挙げましたが、1 つの技術に精通するエンジニアほど、ニーズに乖離してしまうというのは往々にしてあります。科学技術の世界ではパラダイムシフト、ビジネスの世界ではイノベーションのジレンマがよく知られますが、専門家であっても、常識の変化を機敏に感じ取ることは容易ではないのです。 2021 年の時点で web 業界は成長産業ですし、2040 年ぐらいまで急成長が続くことは、ほぼ確実な未来です。ですが、この疑いない状況が未来永劫、永遠に続くのか?と問われれば、その回答は「その限りではない」と断言します。 だからこそ、エンジニアは細心の注意を払って「このエンジニアリングによってどんなニーズが満たされるのか?」「顧客ニーズはどのように変化していくのか?」を、常日頃から考え続けなければならないのでしょう。 感謝 技術に関するカンファレンスの登壇は初めての経験でした。貴重な体験をさせていただき、関係者の皆様に御礼申し上げます!ありがとうございました! 炭田のセッション内容について こんにちは!BASE 株式会社の炭田( @tac_tanden )です。今回の PHP カンファレンス沖縄 2021 にて「PHP で throw しない例外ハンドリング」をテーマに 30 分間発表させていただきました。 セッション内容について PHP で throw せずに例外ハンドリングを行う事例をご紹介する前に、そもそも例外とは何かという部分を整理したり、PHP 以外の言語でどのように throw しないで例外ハンドリングを行っているのかを説明させていただいました。 特に、前半の例外の概念を説明するのに苦労し、発表でもわかりにくい部分があったかと存じます。例外については様々な意見や見解があるかと思いますが、みなさまの議論や考えの整理にこの発表が少しでもお役立てれば幸いです。 カンファレンスでの発表について 今回自分は初めてのカンファレンスの発表だったのですが、発表を通して色々な方と関わることができ、とても楽しいカンファレンスでの発表体験になりました! 発表中に twitter で様々なコメントをいただいたのも、すごく嬉しかったです。今後も機会があればぜひ発表したいと思いました。この場をお借りして、運用の皆様、参加者の皆様にお礼申し上げます。ありがとうございました! セッション動画はこちらになりますので、興味のある方ぜひご覧いただけると嬉しいです! https://www.youtube.com/watch?v=kOhsJCW9YIE&t=14093s 大津のセッション内容について 2021 年 2 月から BASE 株式会社に入社しました大津( @cocoeyes02 )です。PHP カンファレンス沖縄 2021 では Git のコミットにまつわるトークをさせていただきました。 また、セッションの動画はこちらになります。 コミットへのFBをもらうとGood 最後のスライドでこのセッションは自戒が含まれていると書いていますが、それは私自身過去にコミットによる FB をたくさん受けたことがあるからです。 例えば NG 例として挙げたコミットメッセージ「一旦コミット」「PR で指摘したので反映」「バグを直した」は、全て過去に僕が書いたコミットメッセージだったりします。このコミットメッセージが何故ダメなのか FB をもらって、ようやく理解できるようになりました。 コミットを見るのは他人と言う話をしましたが、リーダブルなコミットを書くことによるメリットを受け取れたか判断するのも他人です。今回のトークの tips を参考にしつつ他人から FB をもらうと、リーダブルなコミットを書けているのか判断できるので良い思います! スタッフの皆様ありがとうございました! 最初のオープニングから、最後の懇親会まで楽しく過ごすことができました! 今年はコロナ渦ということもあって、直接沖縄の会場へ向かうことは叶いませんでしたが次回は是非現地にて登壇したいと思っています! 東口のセッション内容について はいさい!BASE BANK 株式会社の東口 ( @hgsgtk )です。当カンファレンスでは、プロダクト開発のため E2E テスト環境を整備してきた中で、泥臭くなりがちなテスト環境・テストデータの考え方と工夫について発表させていただきました。 懇親会等、PHP コミュニティの方とも楽しくコミュニケーションさせていただきました。前回の PHP カンファレンス沖縄では直接沖縄現地に伺い、PHP コミュニティの方と直接話したりソーキそばなどを堪能したり楽しい時間でしたので、また来年・再来年とコロナが落ち着いてきた際に、直接沖縄の会場に伺えればいいなと思います。 最後に 今回弊社は計 4 名のメンバーが登壇して発表する機会をいただき、とても充実した時間を過ごすことができました! また自身の発表だけでなく、多くの方々の発表を通して様々な知識にふれることができ、各々が新たな知見や視点を持ち帰って来れたと考えております。 それもひとえに PHP カンファレンス沖縄実行委員会の皆様のおかげです。心より感謝申し上げます。 それでは、来年もまた皆様にお会いできることを楽しみにしております!
BASE株式会社Data Strategyチーム兼 Data Platformチームの楊(@wyang)です。 ショッピングアプリ「BASE」では、 前回公開した記事 の通り、商品検索基盤をCloudSearchからAWS Elasticsearch Serviceへ移行しました。 この記事では、レスポンス速度改善と検索精度改善をメインにご紹介します。 1. レスポンス速度改善について 1-1. ElasticsearchのProfile API この Profile API を利用すると検索クエリを分析し、どんなクエリが発行されたのか、どのくらいの処理時間がかかっているのかなどを簡単に知ることが出来ます。 GET items/_search { "query": { "bool": { "filter": [ { "range": { "date_field": { "gte": "now-180d" } } } ] } }, "profile": "true", // profile: trueで有効になる "size": 0 } BASEではAWS Elasticsearch Serviceを使っている都合上、X-Packの Search Profiler を利用することはできませんが、こちらではより手軽にパフォーマンス分析をすることができます。 1-2. 改善された変更点 1-2-1. 時間Rangeクエリの丸め込み BASEでは長期間でログインされていないショップの制御など、検索の要所要所でdate型データに対する絞り込みをしています。 GET items/_search { "query": { "bool": { "filter": [ { "range": { "date_field": { "gte": "now-180d" } } } ] } } } しかし、 Elasticsearch公式のドキュメント )にもある通り、時刻によるフィルタリングはfilter cacheに載らないため、毎回絞り込みが行われレスポンス速度が低速になる傾向がありました。 ドキュメントのガイドのように時刻を丸め込むことにより、filterクエリがfilter cacheの対象となり、レスポンス速度が大幅に改善されました。 GET items/_search { "query": { "bool": { "filter": { "range": { "date_field": { "gte": "1600819200000" } } } } } } 1-2-2. 必要最低限のフィルターを利用、不要なドキュメントの削除 Profile APIで時間がかかるフィルターにあたりをつけた後、検索インデックスで管理する必要がないフィールドとフィルターを削除しました。この部分は、インデックス更新バッチ側で制御しており、不要なドキュメントをそもそも登録しないなどの対応で、インデックスサイズを軽量化させました。結果として、クエリの軽量化にも繋がり、レスポンス速度が改善されました。 1-2-3. search_analyzerの使用 シノニム辞書を利用した検索を実現するために、インデックス時と検索時でanalyzerを分けて管理しました。 詳しい内容は Elastic社公式の記事 にもありますが、 インデックスサイズに影響が出ない。 用語の統計全体は同じに保たれる。 同義語ルールを変更するにあたり、ドキュメントの再インデックスは必要ない。 など、大きなデメリットはなく、こちらの設計を採用しています。 2. 精度改善について 2.1. A/Bテスト運用 Firebase Remote Config による検索の A/Bテストを実施しています。アプリのアップデートを配布せずとも、Remote Configのコンソール上で設定を修正するだけで、指定の比率でテストサイズを設定できるようになりました。 2-2. A/Bテストの評価指標 実際に改善に繋がったかどうかを以下の指標で判断しています。 商品閲覧につながった検索率 商品閲覧につながった検索のうち、商品閲覧位置(表示順位)の最小値の平均値 検索結果を1件も返せなかった率 検索結果のうち上位k件の商品閲覧率(SERP@k) 商品お気に入りにつながった率 検索実行からレスポンスを返すまでの時間 閲覧率だけでなく他の評価項目(商品の多様性など)も含めました。 2-3. これまでに効果があった変更点 2-3-1. 複数語検索時の絞り込み条件の緩和 ユーザーが複数語で検索した場合、語順を考慮する検索(type: phrase)と、語順を考慮しない検索(type: best_fields)では、語順を考慮した検索のほうが成績が良いことがわかりました。ただし、一部のキーワードの結果に対してはヒット件数が0件になってしまうため、ヒット件数が一定件数以下になった場合に、条件を緩和して語順を考えないクエリで再検索をするようにしています。結果、CTR改善に繋げることができました。 2-3-2. function_score BASEのおすすめ順では、ユーザが検索したキーワードに該当するドキュメントをスコアリングする際にfunction_scoreを利用しています。function_scoreにより、ショップ情報や、商品情報の複数の要素に対して、それぞれ重みづけをしたオリジナルのスコアを設定することができます。 GET items/_search { ... "function_score": { "functions": [ { "field_value_factor": { "field": "score_field_A" // スコアに関するfield } }, { "gauss": { "date_field_B": { // 日付に関するfield "origin": "now", "scale": "180d", "decay": "0.5" } } } ], "score_mode": "multiply", "boost_mode": "multiply" } } 上記のクエリでは、score_field_Aとdate_field_Bのスコアを乗算してスコアリングをしています。 gaussを利用する場合では、origin(now:現在時刻)を基準として、scale(180d: 180日)の日付差の地点に対して、decay(0.5)の減衰をする正規分布のスコアを設定することができます。 その他 日々登録される商品データのサイズや、更新反映までに求められる時間を考慮し、 Bulkで投入するリクエストデータのサイズ refresh_interval に対する調整をそれぞれ行い、効率的にドキュメントを更新できる設定にしています。 おわり 今回は移行後におけるレスポンス速度改善と検索精度改善についてご紹介しました。AWS Elasticsearch Serviceへの移行を行うことで継続的な検索性能の改修、改善をしやすい環境を作ることができました。これからさらなる改善を行なっていきたいと思います。
こんにちは、BASE株式会社Data Strategyチームの杉です。 ショッピングアプリ「BASE」では、検索にAmazon Cloudsearchを使用していました。今回、検索基盤をAmazon Elasticsearch Service(以下、ES)に移行し、Data Strategyチームで管理をする方針にしました。 この記事では商品が更新された際などにどのように検知し、データをESにいれるようにしたかなど、基盤の部分をメインにご紹介をします。 1. 背景 検索は新しいショップに出会うきっかけを作ってくれたり、探していた商品をいち早く見つけることができることができます。 そのため、検索機能はどのECサイトなどでも見かける存在であり、活用している人も多いのではないでしょうか。 例えばショッピングアプリ「BASE」の検索はこのような画面になっています。 このようにさまざまな便利さをもっている検索機能ですが、ショッピングアプリ「BASE」では継続的な検索性能の改修、改善ができていないという問題がありました。 これらの課題に対し、今回検索基盤の移行を行うことで検索性能改善への第一歩を進めました。 2. 新基盤の移行について 今回の移行はAmazon CloudsearchからESへデータを移行するだけに見えます。 しかし、内部的には管理をData Strategyチームに移行するため、商品が更新された際に検知、データの取得や同期など全体的に新しく作り直す必要がありました。 また、実際に移行計画を進めていくと、様々な問題の壁に当たりました。 例えば Data Strategyチームの管理に変えるために参照するデータベースを変える必要がある データを全てロードし直す必要がある 商品更新時にできる限りリアルタイムに更新をしたいがどう実装するべきか 商品情報に関するテーブルがたくさんある などが挙げられます。 これらの課題が存在したため、手探りでlogstashを試したりembulkでデータ同期を試みたりもしながら、現在の基盤を作りました。 2-1. システム構成 今回は商品検索のみの移行をしましたが、検索を行う上で商品情報に関するテーブルは多くあります。 また、これらのテーブルがそれぞれ更新が起きた際にESにもデータを同期させる必要があります。 これらを考慮し、最終的に以下のシステム構成で実装を行いました。 商品情報の更新 定期的に動いているbatchがS3から前回起動情報を取得 データ更新の有無の確認 更新分のデータを取得 ESのデータを更新 S3へ更新後の情報を記録 という流れで動いています。これを短い時間で繰り返すことでリアルタイムに近い間隔でデータを更新することが実現できました。この「短い時間」は定期実行時間を何分毎と設定しているわけではなく、可能な最短時間で動かしています。 S3に入っている前回起動情報はデータ同期をしているテーブルのそれぞれの実行情報を記録しています。このような記録をし、都度取得をすることで急にDBの同期が上手くいかなくなった場合にも、自動で復旧するような仕組みになっています。 さらに、batchはデータの取得からESのデータ挿入まで全てPythonで作りました。このPythonでの実装時にいくつかの細かい設定をしました。 こちらはinsert時のコード例です。 es = Elasticsearch( ..., timeout=timeout # (1) ) retry_count = 0 while retry_count < retry_max: # (2) try : ... es.bulk(body) time.sleep( 1 ) # (3) break except BaseException : ... time.sleep( 1 ) retry_count += 1 (1) timeoutを明示的に書く timeoutを書かない場合、デフォルトの秒数となります。しかし、実際に動かしてみると稀にtimeoutになることがありました。 bulk時にはサイズで区切っていたのですが、商品情報は商品説明文などが長いことがあり、想定よりサイズが大きくなってしまうことがありました。この際にtimeoutが発生してしまっていたのですが、伸ばすことでtimeoutでのエラーはなくなりました。 (2) retry処理をいれる こちらも同様にbulk時の処理で稀に失敗することがありました。 しかし同じデータをもう一度試すと成功することも多かったため、retry処理をいれることで対応をしました。 (3) sleepをいれる 今回、商品情報が更新されたら更新分を全て更新する仕様です。こちらも頻繁に起こることではありませんが、ある時間に大量の商品情報更新がかかることもあります。 その際に短時間に何度もbulkを行うと、内部キューが溜まりすぎてしまうことがありました。上限値を超えた場合、破棄されてしまうため、データが欠損してしまう恐れがあります。 ESの設定で上限値を変更するという手段もありますが、上限値を変えても送る量が上限値を超えないという保証はなかったため、スピードを緩和させることで対応をしました。 このように、とても細かい部分の設定ではありますが、これらの処理をいれることでデータの欠損もなくスムーズにESにデータをいれるためのシステムを実装することができました。 2-2. 初期ロード 通常の商品が追加や更新された際のデータ更新はシステム構成で書いたような内容で行われています。 しかし、今回ESには商品情報がゼロの状態から始めるため、今までのデータを入れ直す必要がありました。 対応策としては、別途batchを作り初期ロード専用の作業を行いました。 初期ロードのbatchは以下のような流れでデータをいれるようにしました。 メインの商品テーブル以外は一気に全部取得 user_idもしくはitem_idごとにデータをまとめる 商品テーブルをID区切りでデータを取得し、2のデータとjoin これをIDを変えながら繰り返すことで今までのデータを全部入れました。 通常時のESのinsertはupdateもしくはdeleteを使用していますが、初期ロードではindexを使い少しでも速くなるようにしました。 2-3. 例外テーブル システム構成でも書いたように、今回商品に関するテーブルは多くあります。 テーブルに何かしらの変更に起きた際に更新をかけるという方法でうまくいかないテーブルも存在しました。 具体的には batchで計算した結果を格納するため同時刻に何十万、何百万のレコードがinsertされるテーブル 更新頻度がとても高く、約1分間に紐づくレコードが何十万、何百万存在するテーブル があります。 これらのテーブルに関しては、上の処理とは別の処理も加えてテーブル内容の同期を行なっています。 2-3-1. batch計算結果を格納しているテーブル こちらに関しては、常にinsertが走っているわけではなく、dailyやweeklyといった頻度であったことから、insert時に全てのデータを更新するのではなく、少しずつ更新をする方針にしました。 batchでの計算結果の追加 定期的に動いているbatchがS3から前回データを取得 ID区切りでデータを確認 データを取得 前回データとの差分を確認 差分発生データのみESを更新 S3へ更新後の情報を記録 大まかな流れは通常動いているシステムと同様ですが、S3に前回のデータを保存している点と更新分全てを取得するのではなく、ID区切りで他の更新の妨げにならない量に抑えている点が異なるポイントとなります。 このような工夫をすることで、他のテーブルの更新にも影響がでず、スムーズに更新をすることが可能になりました。 2-3-2. 更新頻度が高いテーブル 更新頻度が高いテーブルの問題点としては、同時刻に更新しなくてはいけないレコードが多く、best effortでの更新を行なっているとどんどん詰まっていき、全体の更新が遅くなるということがありました。 更新レコードが多く、更新も常に起こっているためbatch計算結果テーブルのようにIDで区切って少しずついれるようなこともできませんでした。 検討の対象となった更新頻度が高いテーブルでは、多くの情報が入っており、少しの更新でもレコード全体に更新がかかってしまっていました。 そのため、必要な情報のみをS3へ保存し、更新が起きた際にS3の情報と比較をし、必要な情報に更新が起きた際のみ、ESのデータも更新をする方法にしました。 S3に前回情報を保存し、毎回取得することはデータ更新の速度に影響してしまう可能性もありましたが、幸いにも本当に更新すべきデータがかなり減ったため、速度アップにつながりました。 おわり 今回は移行後の基盤についてをメインにご紹介しました。 このように実装を行い、現在のショッピングアプリ「BASE」では移行後のシステムで稼働をしています。 実際には基盤完成後にAPIのresponse timeが遅い問題の対応や、CTRの改善などを行なっています。こちらの詳細については来週ご紹介させていただきます。 移行を行うことで継続的な検索性能の改修、改善をしやすい環境を作ることができました。これからさらなる改善を行なっていきたいと思います。
はじめに こんにちは。BASEのCSEチームの秋谷です。 CSEチームは社内業務の効率化と財務の信頼性担保することを専門とするチームとして開発や社内の整備を行なっています。そんなCSEの取り組みを紹介できればと思います。 CSEについて詳しくはこちらをご覧ください devblog.thebase.in BASEショップの売上金の担保とJ-SOX対応 BASEではショップの売上を一時的にプラットフォーム側が預かっており、申請があった段階で売上金を引き出せるようになっています。 そのため、ECプラットフォームはショップに対して、以下のことを担保することが大前提です。 データが適切であること 発注、決済データの適切な記録がされていること 上記データから作成される、売上計上のデータの作成が適切であること 集計範囲、抽出がただしくおこなわれていること BASEは2019年12月に上場したこともあり、J-SOXへの対応を求められるようになりました。これにより、より厳格にショップの売上金に対してデータの透明性を担保し、またそれに対して監査を受ける義務が発生しました。 J-SOXとは 財務報告に係る内部統制報告制度の概要 J-SOXにおいては、経営者は財務報告に係る内部統制を構築する責任を有しており、その有効性を自ら評価し、外部に対してその結果を報告することが求められます。また、財務報告に係る内部統制の有効性に関する経営者の評価を外部監査人が監査することによって、その評価の適正性を確保する制度となっています。 監査人による内部統制報告書の監査 J-SOXでは、財務報告に係る内部統制の有効性に関する経営者の評価を監査人が監査することによりその適正性を確保することとされています。また、内部統制監査は、原則として財務諸表監査と同一の監査人が実施することとされ、内部統制監査報告書は財務諸表監査報告書と合わせて作成することが原則とされています。 参照: https://www.pwc.com/jp/ja/knowledge/ipo-guideline/j-sox.html ショップの売上金の検証と経理業務の負担 店舗預かり金について BASEではショップの売上金を店舗預かり金と称しています。この店舗預かり金に対して経理チームがデータを検証し、妥当性を担保しています。 当初は売上データを取得するのに時間がかかってしまったり、日々動いているトランザクションに対して常にSQLを実行しているためキャンセルなどが発生した場合に再集計すると過去の結果が変わってしまったり、Excelでデータの確認作業を行なっていたためExcelが重くなり開けなくなるなどの物理的な問題もありました。 また、売上金を担保するためにBASEシステム上のデータと各決済データとの突合しなければなりませんが、これにもかなりの時間がかかっていました。 reportシステムの構築 そこでCSEでは上記の問題を解決するために、BASEのデータ集計やレポーティングのためのシステム(以下reportシステム)を作成しました。 先述の通り、BASEでは各ショップの売り上げを一時的に預かっているため、特に以下の点について詳しくチェックをしています。 店舗預り金のBASEシステムのデータが適切に作成されているか 例:商品の代金、送料、各種手数料が正しく計算されている 店舗預かり金の構成要素に変更はないか 例:新規決済手段の追加(AmazonPayなど) 決済データの突合 例:BASEシステム上のデータとクレジットカードの決済情報が正しいか この売上データの取得やデータの突合をしやすくしたり、構成要素の変更をわかりやすくするために、日次でBASEシステムの本番DBから必要なデータをAmazon Athenaにインポートしています。 reportシステムの構成について次に詳しく説明します。 構築 reportシステム概要 reportシステムの概要はざっくりこんなかんじです。 1. BASEシステムの本番DB(Amazon Aurora)から、必要なデータをAmazon Athenaに日次で取り込む 2. Amazon Athenaに対してSQLを実行して、レポートとなるCSVを出力 reportシステムの構成 report-dataloader(図中の青枠、青矢印) レポートシステムのデータを準備する Auroraのクローン作成機能でテンポラリDBを作成 Embulkを用いて、テンポラリDBからレポートに関わるテーブル、列のデータをAthenaにロード ( civitaspo/embulk-output-s3_parquet を使用) report(図中の赤枠、赤矢印) レポートのデータを抽出する 店舗預り金に関わるデータを抽出するクエリをAthenaに発行する クエリの結果からCSVを作成する レポートシステム 各決算データの取得機能 各決済データとBASEシステム上のデータを突合するために、各決済システムからデータを取得してAthenaにインポート report-datacollector 各決済の外部のシステムからデータをダウンロードをしてきてS3に保存 決済システムによって取得方法が異なる 各種APIの利用など report-dataloader S3に保存したデータからAthenaのテーブルを作成 report-dataloader reportシステム構築による効果 reportシステムの構築によりAmazon Athenaへクエリを実行することによって、重たいクエリを実行しなくともBASEシステム上で取得していた店舗預かり金と同等のデータが取得可能になり、日次でのデータの突合もし易くなりました。 BASEにはさまざまな決済方法があるため一概には言えませんが、一番トランザクション数の多い決済で約1人/日分の工数の削減出来ています。クエリの実行時間は決済方法に関わらずほぼ同一なので、トランザクション数が多い決済ほど工数が削減されていることになります。 終わりに 上記に挙げた以外にも、BASEではreportシステムには経理業務を改善するための細かな機能を随時追加しています。 ここに挙げた例はCSEの活動の一部です。CSEは Corporate Solutions Engineering の略であり、社内業務のより良く改善できればと思っています。 CSEチームでは、エンジニア力で社内業務の効率化やJ-SOX対応をリードできるコーポレートエンジニアを募集しております。 下記のリンクから気軽にご連絡ください。 https://open.talentio.com/r/1/c/binc/homes/4380
こんにちは。BASE BANK株式会社 Dev Divisionにて、 Software Developerをしている永野 ( @glassmonkey ) です。 今回は弊社でブロンズスポンサーとして協賛しました。 PHPをメイン言語として使用しているBASE社と異なり、BASE BANK社ではGoをメイン言語として使っているので、今回は初めてBASE BANK社としてスポンサードさせていただきました。色々至らぬこともありましたが、この場を借りてお礼を申し上げます。 登壇の内容に関してですが、業務ではなく趣味で触ってるFlutterネタです。勢いでProporsalに出したら通していただいたので、趣味全開な形になりました。 GoConference2021 Springについて gocon.jp https://gocon.jp/ Go Conferenceは半年に1回行われるプログラミング言語Goに関するカンファレンスです。 今回は初のオンライン開催という記念すべき回でもありました。 本記事のGopherアイコンのライセンスは以下の通りです。 github.com The Go gopher was designed by Renee French. ( http://reneefrench.blogspot.com ) The gopher stickers was made by Takuya Ueda ( https://twitter.com/tenntenn ). Licensed under the Creative Commons 3.0 Attributions license. 感想と振り返り 発表内容について 今回は個人の趣味でハマってるFlutterネタを発表させていただきました。 私自身はGo歴1年ぐらいのまだまだ初心者ではありますが、機会をいただけてよかったです。 speakerdeck.com 発表の様子はこちらです。 youtu.be 発表についての振り返り 話のベースとして触れた gomobile が何かと不安定なので、サンプルアプリを作る際は色々とハマりました。それも登壇ネタになったかなと考えると美味しい思いはできた気がします。 その後の懇親会でも、色々Firebaseの話ができたりGoのイベントなのにモバイルアプリの開発談義ができて大変楽しい時間を過ごせました。 また、「FlutterとGoのAPIの通信する」アプリを予想されてた思われてた方も居たようで、いい意味で予想をひっくり返せたのは良かったです。改めてGoの自由さを紹介できた点は良かったとおもっております。 時間配分を少し間違えて後半駆け足になってしまったのは反省として今後の登壇には活かしたい所存です。 アドリブ気味ではあったのですが、発表時にオープンクエスチョンを投げることができたのも満足でした。 懇親会 Remoで実施されており、前半はテーブルごとで雑談、後半はみんなでわいわいクイズ大会な流れでした。 remo https://remo.co/remo-101 特に @tenntenn さんの出題した go quiz めちゃくちゃ難しかった思いです。一番印象に残ったのはgo 1.16から入るembedに関してのクイズですね。勉強になりました。 これコンパイル通るか… embedが適応されるケース。変数の中身にファイルの内容が格納される。 //go:embed hoge.json var message string ただのコメントアウト(//の後にスペースがあるのでembedではなくコメント扱いになる) // go:embed hoge.json var message string 感想と謝辞 発表時の進行に関しては事前にリハーサルや説明を @micchiebear さんを始めとした運営の皆様の厚いフォローがあったので当日はスムーズに行うことができたので発表に集中できました。運営の皆様ありがとうございました。 また、スポンサードの窓口としても関わらせていただいたのですが、運営の皆様に手厚くフォローしていただいてありがとうございました。 https://gocon.jp/ に自社のロゴが載っているとわかった瞬間感無量でした。 私自身Go歴は1年ぐらいなので全然わからないことだらけではあったのですが、 参加された方々の Go Love が伝わってきてもっと私自身頑張ろうという気持ちにさせていただきました。 特に登壇資料を業務時間を割いて作ってる中フォローしてれた同僚にも感謝です。 宣伝 今回は趣味ネタでFlutter×Goを触れさせていただきましたが、 私たちの普段の業務はGo, Python, PHPを中心に、フロントからインフラまでを一気通貫で開発しています。 また、開発だけでなく・機能をグロース・サポートまで担当します。 そんな開発スタイルに興味あるぞって方は永野( @glassmonkey )にDMを送っていただくか、 下記のリンクから気軽にご連絡ください。 open.talentio.com 最後までご覧いただきありがとうございました。
こんにちは、デザイナーの河越です。 「BASE」では今年の2月に、注文時に購入者に送られる購入完了メールをリニューアルしました! baseu.jp これまで「BASE」から購入者に送られるメールはほとんどがテキストメールでした。 各ショップが工夫してネットショップ上でブランドの世界観を表現しているにも関わらず、購入者が受け取るメールがショップの世界観とマッチしておらず購入者に違和感を与えてしまうという課題がありました。 そこで今回、「BASE」から送られているあらゆるメールをHTMLメール化する「メール改善プロジェクト」が始まりました。 まず第一弾として購入完了メールがHTMLメール化されましたが、どのような経緯でリニューアルに至ったのか、デザインで工夫した点などを振り返りたいと思います! 購入完了メールのHTMLメール化の経緯 「メール改善プロジェクト」は、デザイナー主導で始まりました。 「BASE」の体験をより良くできそうな箇所があれば、デザイナーが主体的に課題を解決していける環境があるのはBASEの良いところだなと思います☺︎ プロジェクトのキックオフが終わったら、まずは「BASE」から購入者やショップオーナーに送信されているすべてのメールをリストアップしました。 購入完了メールや発送完了メール、お問い合わせ完了メールなど、購入者に送信されているものだけで32種類ものメールがあることがわかりました。 メールを洗い出したあとには、ショップオーナーや購入者の声を一番近くで聞くCS (Customer Support) チームに、メールに関してどのような問い合わせが来るかをヒアリングしました。 その結果「 ブランドのお店で注文をしたのに"BASE"からメールがきて混乱した 」「 銀行振込決済を選択して注文したあと、どこに振り込めば良いのかわかりづらかった 」と感じている購入者がいることがわかりました。 CSチームからのヒアリングを経て 購入者の購入体験を向上させること ショップのブランドブランドの世界観を壊さないこと を念頭に、まずは購入完了メールをリニューアルすることに決めました。 完成したデザイン ショップロゴや、設定しているSNSを表示 ショップがロゴを設定している場合、メールのヘッダー部分にショップロゴが表示されるようになりました。 これにより「"BASE"ってサービスからメール来たけどなぜ?」という戸惑いをなくすことや、ショップの世界観と大幅に解離したメールをなくすことで、注文時から一貫したショップでの購入体験を購入者に提供することを意識しました。 また、フッターパーツに設定しているSNSやショッピングアプリ「BASE」への導線を配置することで、ショップの普段の活動や商品について購入者が知りやすくなりました。 購入した商品がファーストビューに表示させることで、購入直後のわくわく感も醸成できたら良いなと思っています😆 レコメンドエリアを追加 そのショップで販売されているその他の商品を画像付きで表示させることで、リピート率の向上やショップの売上増加を狙う導線を新たに取り入れました。 まとめ 購入完了メールをリニューアルして早2ヶ月。 期待通り「その他の商品」からの購入が増えています。 また、メールが見やすくなったことで社内からも好評をいただいています! これまで以上にカスタマーサポートやカスタマーサクセスの声を聞きながらデザインを作ったことや、影響範囲の大きい品質向上に関われたことで達成感が大きく、このプロジェクトにアサインされてよかったなと思います。 また、「BASE」から送られているHTMLメールがほとんどなかったため、コンポーネントを1から考えて作れる楽しさも感じれました。 購入完了メールは「BASE」を利用しているwebのネットショップからの購入でも、ショッピングアプリ「BASE」からの購入でも見られるので、購入のさいにチェックしていただけると嬉しいです😉 今回は購入完了メールをリニューアルしましたが、「BASE」から購入者に送信しているその他のメールもどんどんリニューアルしていきたいと思います。
集合写真:写真真ん中よりちょっと上にて、左胸にBASEロゴがあるパーカーを着ている大津 こんにちは。Product Dev Divisionに所属している 大津 です。 今回、PHPerKaigi 2021にコアスタッフとして参加しました。私がなぜコアスタッフとして参加したのかという点も含め、カンファレンスの裏側についてレポートしたいと思います! PHPerKaigi 2021とは PHPerKaigi 2021とは、3月26日(金)〜3月28日(日)の期間で開催されたPHP系の技術カンファレンスです。今年で4回目の開催となります。 phperkaigi.jp オフラインカンファレンスだった例年とは違い、今年はニコニコ生放送を使って進行するオンラインカンファレンス形式になりました。 グリーンバックに写っている人が見る生放送用ディスプレイ (グリーンバックに写っている人が見る生放送用ディスプレイ) 弊社もゴールドポンサーとして協賛し、スピーカーや一般聴講者として数名が参加しました。以下の記事に参加レポートが書かれていますのでそちらも是非ご覧ください! devblog.thebase.in 舞台裏ではどんなことをしたの? PHPerKaigi 2021では以下のような仕事がありました。(私が担当したのはこの中の一部です!) プロポーザルの採択 デザインの採択(ロゴ、ノベルティ(ボックス)、Tシャツ、パンフレット、公式サイトなど) ノベルティ(ボックス)の発注、発送 公式サイトのコーディング 放送時に使う動画や録画されたセッション動画の編集 広報、宣伝 生放送の進行 セッション動画を再生するなどの生放送機材操作 動画終了後のアナウンス Ask The Speakerの司会 TL(Twitter)監視、お問い合わせ対応 アンカンファレンスに参加して賑やかす twitter や discord、ニコニコ生放送でわいわいコメントする ノベルティボックスの発送やセッション動画の編集、生放送の進行はオンラインカンファレンスならではの仕事だったかもしれません。 今年のPHPerKaigiはオンラインだけど、ノベルティ、あります! ステッカーやTシャツ、スポンサーさまご提供ノベルティが入ったステキなボックスがみなさんのお手元に! ノベルティ付きチケットは2/28まで販売中! (写真はデザイナーさんによる試作品です) #phperkaigi https://t.co/bjcXHQuoD9 pic.twitter.com/33Qr5hzOea — PHPerKaigi 2021 @3/26-3/28 (@phperkaigi) 2021年2月25日 生放送と録画用の機材 一般聴講者やスピーカーの方々は終始完全オンラインでしたが、大体のスタッフはオフラインで作業していました。 物理的な受付がない分お問い合わせが増えることを予期して、TL(Twitter)監視やお問い合わせ対応の人数を増やすなどの対応もありました。 生放送の進行をするスタッフも、それぞれが聞きたいセッションを休憩時間にしたり、Ask The Speakerでスタッフも質問を投げかけてみたり、スタッフもアンカンファレンスに参加するなど、スタッフ自身が楽しむということも心がけながら良いオンラインカンファレンス体験を目指して裏で動いていました! スタッフが念(?)を送っている時の様子 コアスタッフとして参加し続ける理由 実はPHPerKaigi 2020でもコアスタッフだったので、コアスタッフは2回目となります。 私がコアスタッフとして参加し続けるモチベーションについてお話しします。 いちエンジニアとしてメリットがある コアスタッフとして参加すると、仕事を通して様々なメリットを得ることができます。 例えば、プロポーザルの採択の際に自分がコアスタッフであれば、自分が聞きたいセッションを採択しやすくすることができます。もちろんプロポーザル採択の観点は色々あるため、必ず叶うというわけではありません。ですが、観点の1つとして「スタッフがいち参加者としてこのセッションを聴いてみたいか」があるため、可能性は十分にあります。 またイベントを運営するにあたって様々な仕事をこなすことになるため、イベントの運営力が上がります。カンファレンス運営のノウハウは、規模を問わず他のイベント運営でも活かすことのできる点は多いです。自分がイベントの開催をすることになった時、とても役に立ちます。 他にも、PHPコミュニティに所属しているスタッフと仲良くなれる、スタッフはカンファレンスに無料で参加できるというようなメリットもあります。 お世話になっているコミュニティに貢献したい BASEでは、私たちが使っている技術スタックのイベント・カンファレンス・勉強会への参加を推奨しています。 その理由の一つとして、「自分たちが使っているOSSや技術情報を発信している方々へ貢献できるから」ということを弊社では挙げています。どのような行動が貢献に繋がるのでしょうか?私なりに例を挙げてみました。 聴講者として参加し、当日のイベントの様子を口頭やブログなどで伝えること スピーカーとして登壇し、自分の知見を広めること。 カンファレンススタッフとしてイベントを盛り上げること。 上記のいずれもコミュニティの活性化へと繋がるので、全て立派なコミュニティへの貢献活動だと私は考えています。その中でも私は、PHPerKaigiのカンファレンススタッフとしてイベントを盛り上げるという形で貢献することを選びました。 PHPerKaigiは、私が技術カンファレンスの中で 初めて登壇した イベントです。そのことがきっかけで他のカンファレンスでも登壇することになり、登壇を通じてエンジニアとして成長することができました。 初めて登壇したカンファレンスがPHPerKaigiで思入れがあることと、同じように誰かへ成長の機会を与える側になりたいという想いで、コアスタッフとして参加していました。 最後に 来年もPHPerKaigiが開催されるならば、コアスタッフで参加したいと思っています。参加した皆さんにとって良いカンファレンスであったのであれば幸いです! また、カンファレンススタッフだけでなくスピーカーとして登壇すること、聴講者として参加ブログを書いてイベントの様子を広めていくという形でも貢献し続けていきたいと思います!
この3ヶ月で行ったBDIの内容を紹介します こんにちは、デザイナーの渡邊です。 今回はBASEのデザインチームが行っている勉強会「BDI」の内容をご紹介したいと思います。 BDIとは? 『BDI』は「BASE Design Inspiration」の略。 2018年の秋頃から活動している、デザイナーがやりたいことを持ち寄って、 デザインに関する幅広い知見をみんなで楽しく学ぶことを目的とした任意参加の社内勉強会です。 BASEのデザイナーであれば、デザイナーだけでなく誰でも参加することができます。 Inspirationの名の通り新たなひらめきにつながる新しいトピックを取り上げることも多くあります。 過去にはBDIのロゴも制作したりしました↓ devblog.thebase.in BASEのデザイナーがどんな活動をしているのか気になっている方に読んでいただきたいのはもちろん、 社内勉強会やワークショップのネタとしてもご活用くださいね! 1月 新年!書き初めで作字を学ぼう 新年ということで、お正月らしいオンライン書き初め会を開催しました。 デザイナーだけでなくいろんな方に参加していただきたかったので、 作字のコツを解説する簡単なLTを冒頭に実施 初心者歓迎ムードを打ち出し! 手書きOK、写真を撮ってアップするだけ などなど、参加ハードルを下げる工夫をしました。 見る専の人も楽しめるように、ファシリテーターの手元のiPadをミラーリングしてリアルタイムに作字が出来上がる過程も見てもらいました。 できた作品はJamboardに貼り付け、みんなで観賞。 褒め言葉の嵐でさながらボディビル大会のようになり、かなり盛り上がりました! 身近なデザイン4大原則を分析してマスターしよう デザインの基本となる「デザイン4原則」を実際に使われている事例から分析するワークショップをしました。 デザインの基本となる「デザイン4原則」を心理学にも触れながら解説するLT 事例を見ながら4原則のどの要素が使われているかを分析 実際にかんたんなワークショップで実践 と、マジメでしっかりタメになる会でした! 丁寧なLTは初心者にもわかりやすく、デザイナー以外の方も「なにが良いデザインなのか」を判断するための大きなヒントになる内容でした。 スライド/LP/パンフレットを観察しながら4原則と照らし合わせることで、レイアウトへの理解がより深まりました。 また経験豊富なデザイナーが「なんとなく」で出来てしまっている情報の強弱も、改めて原則を学び直すことで根拠あるデザインなんだな〜と振り返ることができた会になったと思います! 2月 ノーコード系ツールを触ってみよう コーディングを必要とせずにシステム開発やアプリケーション開発ができるノーコードツールが盛り上がっているということで、BDIの時間でみんなでおさわり会を開催しました。 前半はWebflow, Wix, STUDIOの3つのノーコードツールについて簡単に解説、実践編ではSTUDIOを実際に触りながら、感想や気付きを共有しました。 複数人の同時編集が出来るのは個人的に衝撃でした...!上手く使いこなせば爆速でLPとか作れちゃうんだろうな、といろんな可能性を感じてかなり盛り上がりました。 1時間で架空ブランドのコンセプトを作ってみよう BASEを利用するショップオーナーの仕事を追体験するため、ブランディングのワークショップをやってみました。 競合調査からターゲット決め、ブランド名を作るところまで1時間の短い時間でこなすのはかなり大変でしたが、 事前にmiroに用意したテンプレートを埋めることで(途中かなり急かしたりしちゃいましたが)やり切りました...! ここで制作したブランドコンセプトたちは後のBDIでロゴ制作、ショップ開設ワークショップに活かしていきます。 3月 俺みたいになるな!しくじりLT会 某人気テレビ番組のフォーマットを拝借し、 4名のBASE社員に、仕事や私生活でのしくじりとそれを経て得た教訓をLT形式で発表してもらいました。 今回は試験的に視聴者のslackコメントをリアルタイムで画面上に流してみたのですが、結果かなり盛り上がりました! ツールは こちら を使用させていただきました。 すぐに反応が返ってくるので、リモートでも登壇者が寂しい気持ちにならずに楽しく発表できてました。(しくじりの内容は社内限定なのでお見せできないのですが、涙と笑いなしには見られない素晴らしいものでした) 90秒ドローイングをやってみよう 美術・デザイン系学校でよく行われるドローイング。シルエットや構造を観察する目が身に付くといわれていますが、社会人になってからもやっているという人は少ない...。というわけでBDIでチャレンジしてみました! ドローイングのwebサイト を利用したり、社員の写真を使いながら集中してドローイング、後半はみんなで講評をしました。人によって書き込む箇所が違ったり、立体感の出し方に気付いて短時間で画力が成長している人が現れたりとなかなかためになる1時間でした。単純に絵を書くことは楽しい!と思い出せる機会にもなりましたので、みなさんも是非挑戦してみてください! まとめ 月2回ペースで開催しているBDI。最近はありがたいことに参加者も増えて盛り上がってきました。 紹介した企画は全てオンライン上で行っています。オフィスに出社できずチーム間のコミュニケーションが少なくなってしまいがちですが、こういった勉強会はゆるっとした絡みを作るのにもとても良い機会だと感じています。 これからもためになって楽しい企画を運営メンバーを中心に日々考えていきます。4月以降もお楽しみに!
BASE株式会社 ServiceDev Payment Group 所属の田仲です。 現在はエンジニアリングマネージャーを担当していますが、以前はサーバーサイドエンジニアとして開発をしていました。その頃の経験を紹介したいと思います。 BASEのエンジニアはPM・ディレクターから要件を聞き、設計を行います。 仕様や設計を元にサーバーサイドエンジニアはサーバーサイドの実装し、フロントエンドに関してはフロントエンドエンジニアが実装しています。 少し前のプロジェクトになるのですが、「送り状データダウンロードApp」の開発プロジェクトを担当した際にサーバーサイドだけではなくフロントエンドの開発にも携わることになりました。 普段、サーバーサイドエンジニアとして開発を行っているエンジニアがフロントエンドの開発も行うことで良い経験になったという話を紹介したいと思います。 送り状データダウンロードAppについて 商品を発送する際にダンボールなどの箱に送り状を貼りますが、出荷件数が多いショップでは1件づつ送り状を印刷して貼るのは手間が掛かり限界があります。手間を解決するために配送会社の集荷を利用することや外部倉庫への委託などの方法はありますが、費用が掛かるためショップの負担になります。 そのようなショップの課題を解決するために配送業者各社が提供している送り状発行システムを利用して送り状を一括印刷できるようにするためにBASEのシステムからCSVデータを出力できるようにする機能になります。 普段の開発プロセス BASEの開発プロジェクトは、サーバーサイドエンジニアとフロントエンドエンジニアがそれぞれアサインされ協働しながら開発を進めています。 今回の送り状データダウンロードAppの開発プロジェクトでは、サーバーサイドとフロントエンドのタスクはざっくりですが下記のようになりました。 サーバーサイド 固定値の登録・更新(データのCRUD) 各配送会社システムに合わせたCSVファイルの出力処理 フロントエンド 固定値の登録・更新画面 CSV出力処理モーダル 入力値のバリデーション/エラー表示 BASEはフロントエンドはVue.js サーバーサイドはCakePHPを採用しています。 サーバーサイドの担当でいうとAPIの実装がメインになり、フロントエンドの担当でいうと画面UIの作成やサーバーサイドのAPIを利用してデータの登録/更新処理を実装することがメインになります。 フロントエンドを担当した流れ 今回のプロジェクトにアサインされる際、エンジニアマネージャーから提案を受けフロントエンド開発の担当も兼務する形でアサインされることになりました。 組織図上、エンジニアはサーバーサイドとフロントエンドで分かれていますが別職種の開発を担当してはいけないというわけではなく、開発する案件規模やスケジュールなども関係してきますが手を挙げれば前向きに検討してもらえます。 私はサーバーサイドエンジニアとしてBASEに入社しましたが前職でVue.jsの開発経験もあり、フロントエンド開発にも興味があります。個人的に思うところですが、ユーザーに直接に見える部分はフロントエンドの実装になりますし、実装したコードがブラウザ上で目に見えて表現される部分はモチベーションも維持しやすく好きです。 また、BASEではフロントエンド開発を担当したことがなかったので経験してみたかったというのもあります。 苦戦したところ フロントエンドのアーキテクチャの理解 具体的にいえば、単方向のデーターフローを意識することやBASEのコンポーネントライブラリであるBBQの使い方です。 既存のコードを読むのはもちろんのことですが、理解を進めるに辺り参考になったのはドキュメントの存在でした。 BASEではドキュメント共有を主にkibelaを利用して行っているのですが、フロントエンド開発の手順書が用意されておりコードと合わせてドキュメントを参考にすることで比較的早く理解を進めることができました。 BASEのコンポーネントライブラリであるBBQについては、UIコンポーネントのカタログとしてStoryBookが用意されており各コンポーネントの使用方法が書いてあるので、開発環境で試しながら理解を進めることができました。 担当してみて良かったところ デザインの開発経験 サーバーサイドの担当としてアサインされてもプロジェクトの初期フェーズにおいてUIデザインetcに関して話し合いをするタイミングはあるのですが、詳細な部分については実装を進めてみないと分からないということもありました。 今回はフロントエンドも担当することで実装を進めながら、デザイナーの方とFigmaやAbstractなどのツールを使用してコミュニケーションを取りながら開発していきデザインを決定していきました。BASEでのデザイン開発の進め方やツールの使い方を知ることができて良い経験になりました。 コミュニケーションコストの削減 サーバーサイドとフロントエンドの両方を担当していて追加したコードについては大体は分かっていたので、PMからの仕様相談を受けた際なども回答しやすかったです。また実装後に行うQAでのフィードバック対応も原因箇所の特定がしやすく普段よりも素早く対応できたと思います。 フロントエンドの仕組み フロントエンド側がどのように動作しているか?をフロントエンドエンジニアの方やドキュメントを読んでなんとなく理解していたのですが、実際に担当して実装することで「なんとなく理解していた」部分がコードレベルで明確にできて理解することができました。 最後に 実際にできあがった画面が下記になります。 ※ CSV出力処理も担当予定だったのですが、上記の苦戦した部分で想定以上に工数使ってしまい途中で別エンジニアにヘルプを出しました…。 担当が多かった開発でもあったのでリリース時の達成感は強く、SNSなどのユーザーからの反応もいつもより嬉しいものでした。 サーバーサイドとフロントエンドを交互に行き来するような開発で頭の中のスイッチングコストはありましたが、エンジニア同士でのコミュニケーションコストは減ったりもしていたので、開発する案件の規模に影響するものだと思いますが今回のプロジェクトでいうと分業していたとしても最終的に掛かる工数としては大きな差は無かったかなと思いました。 また、コードレビューに関してはフロントエンドエンジニアの方がレビューしてくれるので心強かったです。 BASEはサーバーサイドエンジニアとして入社してもフロントエンド開発できないというわけではなく、手を挙げれば開発することができます。 普段は担当しない領域を担当してみることで不明瞭な部分が明確にできたので、今後の工数見積もりの精度向上やコミュニケーションコストの削減にもつなげることも出来ると思います。 最後にはなりますが、BASEを一緒に支えてくれるエンジニアを絶賛募集中です!より良いプロダクトの開発の為に一緒に働きませんか? open.talentio.com open.talentio.com
この度は、3/26 (金) 〜 3/28 (日) にオンラインで開催された PHPerKaigi2021 にゴールドポンサーとして協賛し、また 1 名のメンバーが登壇しました。 今回は上記メンバーの他に一般聴講者として参加した 2 名のメンバーからの参加レポートをお届けします! 発表内容と補足(東口) BASE BANK 株式会社の東口 ( @hgsgtk )です。PHPerKaigi 2021 では次の 2 つのトークをしておりました。 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 (40 分) PHPUnit 9 時代のTest Doubleの作り方 (5 分) 1. 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 発表に用いた資料はこちらです。 発表の際には、ニコニコ生放送・Discord・Twitter での実況が行われていました。Twitter でのリアクションは以下の Togetter にまとめています。 https://togetter.com/li/1688539 資料公開後土日にも関わらずたくさん反応いただき、2021 年 3 月 28 日(日)のお昼ごろには 100以上ブックマーク いただきホットエントリー入りする反響でびっくりしました。 翌週以降も『 テスト駆動開発 』を翻訳されている和田卓人さんに直接感想をしていただいたりと、多くの方の目に触れる資料となり「頑張ってよかったな」と感慨深くなりました。 Acceptance test-driven development(ATDD: 受け入れテスト駆動開発)を切り口に、Example-driven development (ATDD, BDD, SBE 等) の歴史と実践の総まとめになっている。圧倒的な情報量の資料 / “実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 / ATDD by genba…” https://t.co/CAxmhf0L6z — Takuto Wada (@t_wada) March 31, 2021 以降、収録された発表を見ながら補足情報として入れていた話・Ask the speaker でご質問・意見交換させていただいた内容を抜粋してまとめます。 イテレーション分の受け入れテストをまとめるアイデア テストが増えた場合のテスト速度とフィードバックループについてどういうアイデアがあるかな〜という話を Ask the Speaker でしていました。その中で取り上げさせていただいたのが、「イテレーション分の受け入れテストをまとめる」アイデアです。 これは 『Specification by Example』のChapter 10. Validating frequently で紹介されているアイデアです。 A common special case of breaking long tests into smaller packs is creating the current iteration pack. This pack contains the executable specifications that are affected by the current development phase. ATDD を実践するために用いられるツールには、概ねディレクトリ分けや Tag 等で受け入れテストを分類する機能が備わっています。テスト量が増えた際には、当該イテレーションで関心を持つ受け入れテストのみを絞れるようにすると、開発中のフィードバックループの周りを維持できるかもしれません。 手動テストの扱い 手動テストも Specification に記載するという話を現場での試行錯誤の 1 つのアイデアとして紹介していました。 発表資料から引用 これについて Ask the speaker でいろいろ意見交換させていただきました。 手動テストの Specification には何を書いている? 手動でテストしたいテスト項目について記載している、自動テストでは実行がスキップされるようなイメージ スプリントレビューでデモするような動かし方を書いていたりもする 手動テスト分を毎イテレーションテスター向けのキュー追加するとかできるとよさそう 手動テストの実行結果はどうする? 現状実行結果を記録する仕組みを用意してるわけではないが、イテレーションごとの実行結果としては記録したいモチベーションはたしかに。手動テストをイテレーションごとに Issue 作るなどするアイデアがありそう おすすめの一冊『Specification by Example』 ATDD について知っている方であれば 『実践テスト駆動開発 (Object Oriented SELECTION)』 (通称 GOOS 本)の外側のフィードバックループを想起される方が多いかと思います。 個人的なおすすめとしては、GOOS 本は技術的要素へのフォーカスがメインなので、ATDD 自体の前提となるコラボレーションを重視する考え方などを知るには違う書籍で補完したほうがいいかもしれません。逆に言うと、ある程度 INPUT した上で実践書として GOOS 本を読むと、とても効果が高いと思います。 GOOS 本は 原著 が 2009 年出版ですが、ATDD や BDD のような考え方はそれよりももっと前から実践されているものです。具体的には Ward Cunningham 氏が FIT を作った 2002 年にはそれらの考え方は確認されています。その後にも、2007 年に『 Test Driven - Practical TDD and Acceptance TDD for Java Developers 』が出版されており、そこでも ATDD の考え方と実践方法について触れられています。 様々書籍がある中で個人的に「現場で実践する上で一番良かったな」と思ったのは、 『Specification by Example』 です。 www.manning.com 著者の Gojko Adzic 氏はこれ以前に 『Bridging the Communication Gap』 にて、自身の実践方法について解説しています。しかし、 『Specification by Example』 は数十の企業へのインタビューを通じた実践事例集となっており、インタビューを通じて Gojko Adzic 氏自身の考え方がアップデートされた点なども解説に含んでおり面白いです。 実際に現場で実践するにあたって困ること・気になることについて網羅的に解説されていて、大変参考になりました。 おすすめをもう一冊『Agile Testing Condensed Japanese Edition』 『Specification by Example』 は翻訳版が現在無いのですが英語に抵抗のある方であれば、全体像としてアジャイルテスト自体を抑えてから本腰を上げていただくのが良いのかなと思います。初めての一冊としてのおすすめは『 Agile Testing Condensed Japanese Edition 』です。 leanpub.com Janet GregoryとLisa Crispinによる2019年9月発行の書籍『Agile Testing Condensed』の日本語翻訳版です。アジャイルにおいてどのような考えでテストを行うべきなのか簡潔に書かれています! ソフトウェアテストを実践するに当たり、アジャイルテストという観点から現場を見直してみると、大局観をもって挑めるのでおすすめです。 2. PHPUnit 9 時代のTest Doubleの作り方 発表に用いた資料はこちらです。 PHP 発表資料中に引用したいテストコード付近で若干修正点があったので PR を出したりしておりました(当日発表 1 時間前)。 github.com Lightning Talk で Ask the Speaker の時間があるのは新鮮な体験でした。「PHPUnit のバージョンどのくらい頻繁にあげてる?」みたいな雑談を @goodoo さんとさせていただいてたり、"カンファレンスの廊下感"がありました。 スピーカーとして参加した感想 事前収録を見ながら補足する体験がよかった 事前収録良かった。収録したものを見ながら様々自分でここはこういうのが関連して〜的な補足が入れれたのが自分としては良かったのと、参加した方の感想でもそれがあって、濃密度の高い時間になったという声をいただけておりました。 事前収録のセッションを聞きながら Discord(などのチャット)で登壇者ご本人が適宜補足をしてくれた結果、 めっっっっちゃ濃厚なセッションになったな… すごかった…✨ #phperkaigi #a — うえしー (@ueshiy) March 27, 2021 オンライン + 事前収録の組み合わせならではでした。5 回くらいリテイクしてるので誰もいない部屋で一人ただ喋り続ける時間は大変だったのですが、その分当日に濃密度が上がる点がいいなと想いました。 Ask the speakerに人がたくさん オンラインになってから Ask the speaker の時間は「はてどうしたらいいかな」と困る時間を過ごすことが多かったのですが、PHPerKaigi 2021 では Discord でのボイスチャットでたくさん人がいらっしゃっていて Ask the speaker が過ごしやすかったです。 その中で得られた新たなアイデアや言語化・分析の突破口みたいなのもたくさん会話の中から出て感謝です。 トラックごとにスタッフの方々がファシリテーターとしていらっしゃるのも時間の安心感としてありがたかったです。Track A ではスタッフの @akki_megane さんや ueshiy さんが、トーク前・トーク後に会話を回してくださりとても感謝です。たまたまマイクがオンになってる気配を見て唐突に @koyhoge さんに無茶振りさせていただいたり、オフラインで休憩室でわいわい雑談する感じがあってよかったですね。 質問する身としても、「とりあえずボイスチャット入っとこ」くらいのノリで Ask the Speaker に入れたのもハードルがひくくてよかった気がします。 運営の皆様ありがとうございました 発表資料内では題材システムとして fortee を例に取らせていただきましたが、例に取るくらいめちゃめちゃ作り込まれていて、トークの自動収録システムが用意されていたりと、小並感ある感想ですが「すごいな!」と驚嘆しておりました。 ノベルティのボックスやパンフレットのクオリティも高く、自宅から参加していましたが、「カンファレンスに来た」という感覚が強い時間でした。 コミュニケーションを促進する場の設計・運営をしていただいた PHPerKaigi 2021 運営スタッフの皆様ありがとうございました! 参加レポート(小笠原) BASE に 2021 年 2 月に入社した小笠原奈々( @cureseven )です。土日ほとんどずっと聞いていたので全体を通しての感想を先にお話しします。 学生の頃からちょっとずつ各方面のカンファレンスに参加しているんですが、学生の頃は「エンジニアってどんな感じの人たちで、どんなことしてるんだろう?」の興味で行っていたので内容がさっぱりわかってなかったのですが、社会人になって、業務をしていく中で実体験を元に話が聴けるようになってきたことを実感しました。 反面、新しく聞く話も多く、まだまだ知らないことたくさんあるなあと感じました。 PHPで学ぶ、セッションの基本と応用 fortee.jp あまり体系的に学ぶ機会のない Session の概要がきちんと整理された、初心者にも優しい内容でした。 そもそも Session という言葉の使い方が曖昧で、正しくは「Cookie を使った Session 管理」。元々ステートレス(状態を持たない)なプロトコル World Wide Web を、ビジネス利用しだし、状態を持ちたくなったため生まれたのが Cookie という技術です。 Cookie は 4KB の制限があるので、Session ID というキーを引き回し、それを元にセッションストアにアクセスしデータを取得することによって状態を表現します。 最低限の知識として Cookie に属性が設定されている。 Session 管理を狙った攻撃がある。WAF ではそれを考慮しているので安全に $_SESSION を扱える。 保持された情報によってバグが起こりやすいので、Session 管理の設計は大事。本当に Session 管理すべきなのか設計を見直すことも必要かもしれない。 セッションはファイルなので、サーバの台数を増やしてスケールアウトすると参照するセッションが別れてしまうので共通のセッションストアを見るなどする ということが紹介されていました。 Cookie を使った Session の知識はかなり曖昧でした。この発表を通して用途と注意すべき事項がわかったので、今後の開発に生かすことができそうです。 PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう fortee.jp エラーと例外とは何かを比較すると以下です。 エラー 正常な処理ができない。難しそうな状況 プログラミング的に間違っている PHP が発生させるもの 例外 事前条件、事後条件が満たされない状態になる時吐くもの 契約プログラミングにおける契約的に間違っている プログラム自体が発生させ、自身でコントロールしたいもの 改めて日本語にするのは難しいですが、感覚的にエラーと例外は自分の中では区分できているかなと思います。 このセッションでは、「エラーも例外もまとめて投げる \Throwable を闇雲に使うべきではない。」「例外を投げるにしても、捕捉してそれをどう扱いたいか明確なものを投げよ」ということを主張されていたと思います。 曖昧な throw を残すことは、何が起こるか不安な状態のままにしておくことと理解しました。 \Throwable は \Error \LogicException \RuntimeException をキャッチします。 \Error \LogicException は本番では起こりえない状況なので throw せず、本番環境に乗せる前に修正すべきです。対して、 \RuntimeException は本番ではどうしても起こるものなので、捕捉して回復する余地のある場合は回復する処理を書き、どうしようもない場合は諦めるのが良いと主張されていました。 今まで正しい理解がないまま \Throwable を書いていたのでこちらのセッションを聞いて理解が進みました。想定されるエラーと例外を考えるようにします。 自前の Exception を定義し、どういうコンテキストのエラーかをエラー名を見ることによって把握できる、という話もありました。 今私が参加しているプロジェクトではちょうど自前の Exception を作っていたのでよかったです。 レイヤーに合わせた抽象度の Error にして渡すのが良い、という話があったので、DDD の考え方は例外にも当てはめるべきということが新たな発見です。意識して書くようにしたいと思いました。 そのコード、フレームワークの外でも動きますか? fortee.jp tech.quartetcom.co.jp このセッションでは、フレームワークに依存しない書き方を実践を通して紹介されました。40 分のセッションのうちに Laravel から Symfony にフレームワーク移行するデモンストレーションが入っていて驚きでした。 フレームワークから業務ドメインのコードを独立させておくことで、後方互換性を高くすると、リプレイス・リニューアルの時にまる写ししなくて済みます。 今私が参加しているプロジェクトでは保守性を高めるために DDD で開発しているので、参考になりました。 デモンストレーションの中でほとんど変更点がなく、名前空間や view の表記方法をちょっと修正したくらいのものを別のフレームワークに持ってきており、フレームワークに依存した書き方をしなければこんなに簡単に移行できるんだと改めて DDD で開発するありがたさがわかりました。 独立パッケージにするのも賢いと思い、検討したいです。 参加レポート(炭田) Service Dev Section に所属しています炭田高輝( @tac_tanden )です。自分は 2021 年 3 月 27 日(土)〜3 月 28 日(日)の 2 日間参加しました。 PHPerKaigi は去年初めて参加して今年で 2 回目の参加です。今年は新型コロナウイルス感染症の影響でニコニコ生放送でのオンライン開催でしたが、どのセッションも熱いトークばかりでオフラインに負けず劣らずとても楽しいカンファレンスでした。この場をお借りして運営スタッフ/スピーカーの方々にお礼申し上げます。ありがとうございました! 各セッションでは、日頃開発をしているだけでは出会うことが中々難しい様々な知見にふれることができ、とても勉強になりました。自分が聴講したセッションの中から特に気になったセッション 3 つのレポートをしたいと思います。 PHPでCSVを安心して扱うために fortee.jp github.com CSV への認識が 180 度変わったセッションでした。恥ずかしながらこのセッションを聞くまでは、CSV は正規化されたお手軽なデータ形式という認識でいました。 なぜこのような認識を持っていたかというと、今まで自分が携わった開発だとサービスの管理画面からデータ入稿をするのに便利だから使う程度の事しかしてこなかったからだと思います。 そのような場面で自分が扱っていた CSV データは「信頼している人が作成した信頼性の高い」という但し書きがついた CSV データだったんだなということを痛感させられました。 CSV データを処理するエンドポイントを、悪意があるユーザを含む様々なユーザが利用する場合、入力される CSV は文字コードや改行コード 1 つとっても様々なものが想定されます。また、クラッキングしようとするような悪意があるデータも含まれる可能性もあり、それらに対して全て考慮した実装にする必要があります。 セッションでは、PHP で CSV を扱う複数の手段とともに、それらの長所/短所やベストプラクティス、様々な文字コードへの対応を解説しながらどう PHP で CSV を扱うのか知ることができともて勉強になりました。次回 CSV を使った機能を開発するときに、ライブラリの利用やその内部実装を参考にさせていただきたいなと思いました。 モックの泥沼から脱却するために、あえてDBにつないでテストしている話 fortee.jp データソースから取得したデータを整形したり何らかのロジックを適用したあとにデータを保存するといった処理のテストを行う場合、データに関連する部分以外の実装を細かなメソッドに切り出して、データに依存しないようにしてテストすることで、データ入出力まわりのテストの複雑さをさけてユニットテストを記述できると思います。 しかしこの場合、細かく切り出した複数のメソッドを協調させて使うようなメソッドのテストが最後にどうしても残ってしまいます。これらのメソッドをテストする場合、どうしても実装が複雑になってしまい、またデータへも依存するのでテスト用のデータの生成するのにもコストがかかります。 場合によっては、このようなメソッドを組み合わせたメソッドのテストは行わない戦略を取る場合もあると思いますが、自分個人としてはそのような Large サイズのテストもできるだけ実装したいと思っています。 このセッションでは、上記のようなデータにも依存し、かつ処理も複雑なテストをする場合、モックを使ったテストではなく DB につないでテストする選択をした場合のメリット/デメリットについて解説されていました。 テスト時にモックはたしかに便利ではあるのですが、どうしても自作自演のテストになってしまい「作成したモックが正しいことをどう担保するのか」がすごく難しくなってしまうと実感しています。なので、発表にあったように「実データ」を実際に用意して、特にユースケースやシナリオといった役割のクラスのメソッドに対してはテストしていくことが良いのかなと改めて考えさせられました。 ちなみに、BASE では実装者がテストで所望のデータを生成できるよう、Fabricate というライブラリを利用しています。 Fabricateの記事 devblog.thebase.in devblog.thebase.in 改めて、テストの意味と役割について考えさせられたセッションでした。 作って理解するDIコンテナ fortee.jp tadsan.fanbox.cc PHPerKaigi 最終日の trackA は DI(Dependency Injection)祭りでした。 その中でも自分がすごく勉強になったのがこちらのセッションでした。依存とは何かの初歩の部分から依存にどう立ち向かうのか、最後には DI コンテナを実装するところまで詳細に解説されていました。特に、依存とはなんなのか、なぜ DI という考え方を取り入れるべきなのかを今一度自分の中で整理することができました。 自分は DI をという概念を利用して実装はしています。ですが、DI コンテナのようなライブラリは利用しておらず、発表中は恥ずかしながら理解が追いつかない部分がありましたが、セッションの後に資料を見返してなんとか理解したつもりです。また、この発表を通じて初めて PHP-DI を知ることができました。 最後に 今回弊社は登壇者含めて計 3 名のメンバーで参加させていただき、たくさんの参加者の方々や発表にふれることができ、とても充実した時間を過ごさせていただきました。 それも実行委員長である長谷川さんをはじめ、実行委員会の皆様のおかげです。心より感謝申し上げます。 それでは、来年もまた皆様にお会いできることを楽しみにしております。最後までお読みいただきありがとうございました。
こんにちは。BASE BANK 株式会社 Dev Division にて、Engineering Manager をしている東口( @hgsgtk )です。 BASE では @budougumi0617 さんが主催となって Go のコードリーディング会を行っています。昨年は『 私がGoのソースコードを読むときのTips 』にてその様子を少し紹介いただいていました。 その後も隔週の毎月 2 回ペースでコードリーディング会を継続しています。2020 年 12 月以降は前述したブログをきっかけに興味を持っていただいた @dice_zu さんや po3rin さんにもゲスト参加いただき、ゆるゆると継続しています。 当エントリではその中で話題に上がった Go 1.16 の機能についてご紹介いたします。 TL;DR 構造体フィールドタグ json オブジェクト名の中でセミコロン( ; )が許可されました たとえば json:"hoge;hoge" のように ; が JSON オブジェクト名 に含まれる場合も json.Marshal / json.Unmarshal 可能となりました JSONの仕様を定義した RFC7159 によるとセミコロンは文字種として valid であるため 社内で開催中のコードリーディング会はゲスト大歓迎です。興味がある方は身近な社員の方か @hgsgtk にご相談ください Go 1.16 の Minor changes Go 1.16 の Release Notes にて 1.15 からの変更点について見ることが出来ます。 golang.org 私は毎回 Core library の Minor changes to the library を眺めて気になる PR のコードを読んでみるのが好きです。 その中で、コードリーディング会までネタにあげさせていただいたのが encoding/json パッケージの変更点です。 Go 1.16 における Minor changes https://golang.org/doc/go1.16#encoding/json The json struct field tags understood by Marshal, Unmarshal, and related functionality now permit semicolon characters within a JSON object name for a Go struct field. どういった内容なのか、当日参加者で 15 分程度読んだ内容を皆様にご共有いたします。 encoding/json パッケージの Minor changes 構造体フィールドタグ json オブジェクト名の中でセミコロン( ; )が許可されました。具体的には次のようなサンプルコードです。 package main import ( "encoding/json" "fmt" ) func main() { encoded := [] byte ( `{";": "World!"}` ) // セミコロンがKeyのJSON Object type MyObject struct { Hello string `json:";"` } var decoded MyObject if err := json.Unmarshal(encoded, &decoded); err != nil { fmt.Println(err) return } fmt.Printf( "%+v" , decoded) // Output: {Hello:World!} } https://play.golang.org/p/3HUq4N66AK5 変更の背景 「セミコロンが JSON オブジェクト の Key にはいっているものを扱うユースケースがあまり想像つかないね」という話をコードリーディング会ではしていました。どういった背景でこの変更が行われたのでしょうか。起点となった Issue を見てみましょう。 issue: encoding/json: does not recognise semicolon as a valid field name https://github.com/golang/go/issues/39189 この変更の背景根拠となったのは JSON 仕様を規定している RFC7159 のようです。 So pretty much we check JSON spec (RFC-7159) for validity on our "bug" and it seems to us that the spec would treat a semicolon as a normal character. Go内部実装における変更点 ここで行われた内部実装の変更は次の CL から確認できます。 https://go-review.googlesource.com/c/go/+/234818 src/encoding/json/encode.go に入った修正  isValidTag というメソッドにて tag の正当性を検証するのですが、その内部での文字種ルールに ; を追加しています。 strings.ContainsRune( "!#$%&()*+-./:;<=>?@[]^_{|}~ " , c): 余談 Go 2structured tag という提案 Go コードリーディング会の雑談のなかで Go2 のプロポーザルで structured tags というものが提案されていることを知りました。 github.com 現在の構造タグのフォーマットは文字列リテラルですが、この Issue の提案およびディスカッションの結果、具体例として次のようなコードを記述するような機能提案となっています。 package mypackage import json import sqlx type MyStruct struct { // [] 内にstruct定義する Value string [json.Rules{Name: "value" }, sqlx.Name( "value" )] PrivateKey [] byte [json.Rules{Ignore: true }] } 個人的には、文字列リテラルで記述する方法と比較した際に、記述ミス時に気が付きやすい点が嬉しいと思いました。実行時エラーや無視されてしまうのではなくユーザーがスペルミスした際にコンパイルエラーになります。 Issue 内での会話は 2020 年 1 月ごろに落ち着いていますが、「そういうアイデアがあるんだな」と頭の片隅に入れておくといいかもしれません。 おわりに かんたんにですが、encoding/json の minor changes について紹介しました。Go の内部コードリーディングのはじめの一歩として身近な標準ライブラリのちょっとした変更を追ってみると、その周辺コードもちょっとしれたりしておすすめです。 社内で開催中のコードリーディング会はゲスト大歓迎です。興味がある方は身近な社員か @hgsgtk にご相談ください。 こちらも余談ですが、BASE BANK 株式会社は絶賛採用募集中です!もし、「ちょっと興味あるな〜」とおもったら、Go 1.16 の話とか現場での Go 開発についてワイワイはなしましょう! open.talentio.com
はじめに CTOの川口 ( id:dmnlk ) です。 BASEは現在140万ショップを超え、サービスの安定性・信頼性を維持することは非常に重要になっています。 その中で去年、New Relicを本格的に導入しました。 https://newrelic.com/jp/press-release/20201217 しかしNew Relicを導入しただけで安定性が獲得できるわけではありません。得られるのは可観測性のための武器であり、その使い方を適切に学ばなければ無駄になってしまいます。 今回、New Relic社のご提案もあり社内メンバー向けにオンボーディング会を開いていただけることになりました。 オンボーディングを行うことでまずNew Relic Oneのオブザーバビリティプラットフォーム で何が出来るのか、どのように使うことでBASEシステムの可観測性を得ていくことが出来るのかを開発チームが体系的に学べることを狙うこととしました。 オンボーディングに参加して SREチームのngswです。 当日の参加者は最終的には50名近くとなりました。プロダクトに直接関わるフロントエンド、サーバサイド開発陣のほかにCSE( Corporate Solutions Engineering )チームも参加する形で、大変にぎやかな会になりました。 14:00〜17:00という長丁場の中で、個人的に特に盛り上がったなと感じたところである以下を中心に当日の雰囲気をお伝えできればと思います。 「New Relicに期待することはなにか」 New Relic でできることについて NRQL / Query builder について 「New Relicに期待することはなんですか?」 期待に胸を躍らせる面々 オンボーディング開始の直前まで、Slack上ではNew Relic社のSenior Solutions Consultant 1 の清水毅様と調整を行っておりました。 清水さんにはBASEを強くサポートいただいており、 その中でご提案いただいたのが「各開発チームの代表者を1名ずつ決めて、それぞれに『New Relicに期待すること』をそれぞれ教えていただきたい」というものでした。 当日発表内容は以下になりました。 所属チーム New Relicまたは今日のオンボーディングで期待すること ペイメント New Relic Browserを組込中。 ユーザがどのような形で <BASE> を利用しているか、解明していきたいと考えていて、 他の機能もさわっていきたい CSE 内部統制を効率的かつ有効に進めていけそうな機能を、このオンボーディングの中で見つけていきたい ショップ1 (New Relicを用いることで)開発側でも監視/モニタリングを積極的に行える土壌ができることを期待している フロントエンド1 パフォーマンス改善の足がかりとしてNew Relicを活用していきたい 安定運用を自チームを含めた全開発陣でやっていくことを ショップ2 リリースした機能/APIが期待通りのパフォーマンスがでているか、ある時点から劣化していないかということを追跡したい フロントエンド2 JavaScriptのエラートラッキングや、追いきれていないパフォーマンスのボトルネックの発見を行うためのツールとして期待しており、このオンボーディングで学んでいきたい 基盤 今、アプリから呼び出されるAPIのパフォーマンス改善を行っているところで、ログ追跡がより便利に、かつ簡便になることを期待している SRE インフラ側(たとえばSRE)と開発陣での共通の話題が、たとえばNew Relicのダッシュボードを中心になっていくようなことを期待している ネイティブアプリケーション モバイルアプリから呼んでいるAPIの負荷/ボトルネックなどの、問題の発見と追跡を行いたい BASE BANK社開発陣 マイクロサービス化していく中で、3〜4サービスまたいでログを確認しなければいけないなど、運用に苦しんでいるところがある SLOなどの設定を考えていく中で、そもそものメトリクスをどうとるか(そして投げ入れるか)などのヒントをこのオンボーディングで学んでいきたい DS New Relicをチーム内でどの様に活用できていけるのか、その下地になる知識を仕入れていきたい よかったなと感じたのは、CSEチームの「内部統制に活用したい」というものでした。 この発想を自身は持ち得なかったので、共有いただけたのは個人的な収穫のひとつでありました。 もうひとつ感じたことは「開発陣はパフォーマンスに関心がないわけではなくて、関心はあるのだけれど、追跡に必要な情報へアクセスしづらいがために、結果的に後回しになってしまっている現状がありそう」というものです。これはわれわれSREチームの至っていない点であるなと反省した次第です。 このあと清水さんより New Relic の概要説明がありときには補足も入りながら、New Relicの目指すところはなにかという内容へと進んでいきました。 紙幅の都合もありますし、より正確な内容をご自身の目と耳でご確認いただきたいと考えますので、興味を持たれた方には以下のウェビナーへの参加や過去動画の閲覧をおすすめいたします。 セミナー・ウェブセミナー | New Relic(ニューレリック) Q 内部統制でどういったところにつかえるでしょうか、たとえばDB操作の監視であるとか…… A ログベースの検知であったり、そのキーワード監視ができます。加えていうとリリースプロセスが内部統制の制約をうけるので、モニタリングをQAと連携させることもひとつの重要な観点になります Q バックエンドで稼働するバッチ処理の成功可否など、きちんと検知する仕組みはできるでしょうか A Webフレームワークの機能を利用しているものであれば、組み込むことは難しくないです 補足をするとバッチ処理などは実行と終了だけでなく、処理内容とその出力成果物の整合性など、バッチ処理の価値そのものを追っていくことも大事なことなので、いくつかの観点で複合的に考える必要がでてくるかもしれません New Relic 入門(機能紹介) 「ビジュアル化の重要性を体現的に理解している」と話題になるメンバーのプライベート機 ここからは開発メンバーから代表した数人が、一人ずつZoom画面共有機能を用いてNew Relicに触れていく形になります。「モブプログラミング」でいうドライバー役のようなものを想像いただければ間違い無いかと思います。代表者を含む参加したほとんどのメンバーはNew Relicを今日触るのが初めてと言っていい状態ですから、清水さんがナビゲータ役として位置する形になります。 統合ダッシュボードとしてのNew Relic One Add More Data New Relicにデータを投入するための言語/ツールごとのセットアップ手順がチュートリアルのような形でわかる親切設計です アカウント BASEではサブアカウントを環境ごと(Prd/STG/Dev/……)に対応する形にしました Services BASEではAppNameをリポジトリ名から引用して対応させるようにしました リポジトリAを実行しているEC2は複数台構成で稼働していますが、リポジトリ=AppNameとしているためNew Relicのデータ上ではまとめて表示されるようになっています APM導入後に活用できる各機能 Summary サンプルにしたAppNameでは、MySQLのレスポンス時間が高くなっているグラフが目に付きました Databases SummaryでみつけたMySQLのレスポンス時間が高くなっていた部分を調査していきます QueryTimeが長い部分をグラフで視覚的に確認することができます Slowest query time で sort することにより Slow queries を参照することができます MySQL の Queryそのものと、PHPのStack Traceを同時に確認することができます 「EXPLAINだ」「slowlog を grep して explain して…みたいなことをここでシュッとできる」というようなコメントがSlack上で見受けられました Workloads アカウント(BASEではサブアカウント)ごとのリソース群の健全性状態を一覧できます 自動的に組み込まれていくので手間いらずで便利そうです NRQL / Query builder がやってきた 『ある期間内にどのショップがイベントを行ってリクエストが増大したかがひと目でわかる』ことに感心する一同 個人的にもっともエキサイティングな時間がやってきました。 APMで投入されたデータはNew Relic上でNRDBとして格納され、NRQL(New Relic Query Language)を利用してさまざまな解析が可能となります。 今回のオンボーディングでは以下のNRQLを用いた可視化が披露されました。 FROM Transaction SELECT avaerage(duration) ここでの duration は属性[応答時間]です このNRQLで本番環境すべてのトランザクション平均時間が得られました FROM Transaction SELECT avaerage(duration) TIMESERIES TIMESERIES のおかげで指定期間単位ごとの集計結果を時系列化したグラフが表示されました FROM Transaction SELECT percentile(duration, 99) TIMESERIES percentile(duration, 99) は duration の99パーセンタイルを意味しています TIMESERIES により同様に時系列化したグラフを返してくれました FROM Transaction SELECT percentile(duration, 99) TIMESERIES FACET name name は属性です ここでは実行されたPHPのファイル名でした FACET はグループ化を行ってくれます 個人的には FACET がとても好きです FROM Transaction SELECT percentile(duration, 99) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago Base.shop_id はBASEが埋め込んだ Custom Attribute になります。 Custom Attribute について詳しくは以下をご参照ください アプリケーション固有の属性を活用した性能分析 | Observability Platform - New Relic公式ブログ 意訳すると「直近1週間でBASEショップごとの応答時間99パーセンタイルで(処理時間がよりかかっているものの)上位30件の時系列グラフ」となるでしょうか FROM Transaction SELECT average(duration) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago こちらは先のものを平均時間で置き換えたものです FROM Transaction SELECT average(duration) * count(*) TIMESERIES FACET Base.shop_id LIMIT 30 SINCE 1 week ago 先の平均時間では、各ショップのリクエスト数の多少は考慮されていませんでしたので、 average(duration) * count(*) として考慮する形に変わりました この形にかえることで、より戦略的かつ効果的な、費用対効果の向上に直結するパフォーマンス改善のために必要な、実践的なデータが得られるようになりました ここではNRQLのパワフルさを見せつけられました。 興味を持たれた方は以下をご覧ください。 Get started | New Relic Documentation NRQL Lessonsアプリケーションを使ってNRQLをマスターしよう - New Relic公式ブログ 結びに New Relicの基本的な機能について学べたオンボーディングでした。 まだ導入当初であって即効的なものを期待したわけではなかったのですが、この記事を書いている2021年03末ごろまでで以下のような成果がありました。 特定ショップのページ表示が遅くなった原因の特定 お問い合わせの原因となった不具合がどのリリースと関連するものかの特定 今後は、SRE的観点からダッシュボードを充実させていき、様々な意思決定にNew Relicを利用していきたいと考えています。 サービスの信頼性を獲得することはSREチームだけのミッションではなくバックエンド・フロントエンドエンジニア問わず期待しています。 New Relicを始めとしたツールの支援を受けながら、積極的に改善していくエンジニアを募集しています。 open.talentio.com open.talentio.com SREチームは先日求人を見直しました。 devblog.thebase.in 清水さんのことを心のなかでは 「New Relic アニキ」と呼んでいます ↩
こんにちは!! BASE株式会社 SREチーム エンジニアリングマネージャの富塚( @tomy103rider )です。 2021年3月現在、SREチームは私含め3名で、最近私は成長するサービスを一緒に支えていって頂けるSREの仲間を求めて採用などをメインに業務を行っています。 はじめに 突然ですが、皆さんのチームの求人票は誰が作っていますか?そしてそれを読んだことはありますか? 思い出してみると元々のSREチームの求人票は私と前のマネージャが考えて作ったものでした。 その内容を改めて確認すると、 見ていただいている方に現在のSREチームの思いが伝わる内容だろうか? チームのメンバー自身も迎えたい仲間のイメージができる内容だろうか? 採用に関わるCTOにもそのイメージは共有できているだろうか? などと感じる部分が出てきたため、このタイミングで見直してアップデートすることにしました。 今回はこのときの話を紹介しようと思います。 どうやって見直す? まずは今までのようにマネージャが考えて作るのではなく、SREチームとして今どういう仲間を求めているのかを認識を合わせながら自分たちで考えて見直し、その内容を最終的にCTOとすり合わせることを今回のゴールにしました。 また、話し合いを始めるにあたって、 過去や今の求人票を検討したときの経緯を見れると良さそう 過去の求人票の歴史を見れると良さそう そもそも社内にオープンに見れる状態になっていても良さそう という意見が出て、過去の歴史を残すとなるとこれはGitHub上でやってみては?ということになり、試しに見直しの過程やアップデートをGitHub上に残してみることにしました。 見直しの過程をIssueに残す! 新たに作成したRepositoryのIssueに「やっていくぞ!」とCTOがビシッとコメントを残してスタートです。 スタート直後の様子 数回の見直しMTGをやりながら、このような雰囲気で議事録的なコメントやカジュアルなコメントを割と何でもこのIssueに残していきました。 求人票のアップデートはPull Requestベースで! 最終的に1つの求人票を作るために、見直しMTGで話し合った結果の大枠を反映した内容をまず私が作成し、 Pull Requestの様子1 これをみんなでレビューしていき、途中CTOのアドバイスを貰ったりして、まずはSREチームの中で認識があった状態にしました。 Pull Requestの様子2 そして最後にCTOにレビューしてもらい・・・ Pull Requestの様子3 無事 approvedとなったことで、今回のゴールであるCTOとの認識合わせもできました。 やってみてどうだった? 折角なので最後にチームのメンバーに今回の感想をIssueのコメントに残してもらいました。 N氏コメント A氏コメント 抜粋すると 前の内容に比べてより今求めているイメージが表現できたかも! 採用についての当事者意識を持つキッカケになった! 履歴を残したことが将来の財産になりそうな期待が! というような良い感想をもってくれたようです。 まとめ チームの規模感や状況によって今回のようなやり方が適しているかというのはあるかと思いますが、私としては見直しの過程を財産として残せたこと、みんなで新たな仲間のイメージを持てたこと、とても有意義な時間にできたのではと思いました。 そして・・・ 今回のアップデート後の求人票が open.talentio.com こちらです!!! ここまでの内容すべてが前フリだったかのようになりますが・・・ 現在SREチームでは成長するサービスを一緒に支えていって頂ける仲間を募集していますので、求人票をご覧いただいて少しでも興味を持っていただけましたら、まずはカジュアルにでもお話できればと思いますので是非ご連絡ください!!!!!
こんにちは、BASEのフロントエンドチームでエンジニアリングマネージャーをやっている松原( @simezi9 )です。 私は最近ではマネージャーとしてコードを書くことよりもチームの編成や採用などをメインに業務を行っているのですが、 そんな中でチラっと書いたコードで見事に落とし穴にハマって失敗をしたのでその共有記事です まえがき BASEのフロントエンドチームは現在15名ほど(うち業務委託5名)で運営されています。 この人数は今後もどんどん増えていく予定なのですが、目下全社的にリモートワークになっている事情も手伝ってメンバー同士の関係性が希薄になってしまう懸念を持っていました。 BASEの中では常に複数のプロジェクトが走っているのですが、それぞれのプロジェクトにフロントエンドエンジニアは2〜3名ずつ配置されています。 そんななかでアサインされた人同士がフロントエンドエンジニア同士であるにも関わらず、お互いのことをよくわからないという状態が今後おきうるというのは組織として問題があると考えpeer 1on1という制度を開始しました。 peer 1on1 peer 1on1は組織によってはシャッフルランチなどの名前で開催されてもいるようですが、要は「ランダムに選出されたメンバー二人で1on1をする」という時間です。1on1とかっこよく言っていますが、つまるところお互いを知る雑談の場を作るというものです。 現在は週1回30分の時間を取って行っています。BASEでは最近全社的な雑談の場所として Discord が用意されましたが、 Discordの気軽なボイスチャット機能との親和性も高く、それなりに盛り上がって運用されています。 (1対1で話すのは少し疲れるという話もあり、3人単位に変えたりといろいろと運用は試行錯誤しています) そしてこのpeer 1on1を始めるにあたって、メンバーをランダムに組み合わせるためのツールをGoogle Apps Scriptでパパっと書きました。 こんな感じでA列に今いるメンバーを入力しておき、メンバー生成ボタンを押すと毎回の二人組が作られていきます 発生する問題 このシートを作った当初は、「(見た目はさておき) またいっちょツールを作ってしまったぜ・・・ ( 実コード10行程度) 」などと悦に入っていたのですが、いざ運用を始めてみてしばらく回数を重ねるこんな苦情が寄せられるようになります 最初はまあ、ランダムだしこんなこともあるでしょ!ぐらいに考えていたのですが、 対象メンバーが10名を超えるような状況でこの偏りは流石におかしいということで調査を始めるとすぐに原因を突き止められました。 バグを抱えたシャッフルの実装と修正 このシートの実装は メンバーの一覧をシートから取得 その配列をシャッフル 結果列にそのまま出力 というシンプルなフローになっていました その核となるシャッフルのロジックは以下のようなコードになっていました。 const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.sort(() => Math.random() - 0.5); この実装自体は素朴なもので、ほとんどの人に直感的に理解できるものです。 Math.random の動作は 0 以上 1 未満 (0 は含むが、 1 は含まない) の範囲で浮動小数点の擬似乱数を返します というものなので、そこで得られた結果から 0.5を引けば、正の数、負の数が擬似乱数から均等に生成されることを期待できます。 あとはこれをArray.sortに渡すことで無秩序なソート操作が行えるので、配列がシャッフルされるというものです。 実際に、このコードは数回試してみると見た目上は正しく動作します 試しに検証コードをchromeのコンソールで実行してみたら以下のようになります。 const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let tmpArr; tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); //  [1, 6, 4, 7, 5, 9, 2, 8, 3] tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); // [8, 5, 1, 6, 9, 7, 2, 4, 3] tmpArr = [...arr]; tmpArr.sort(() => Math.random() - 0.5); console.log(tmpArr); //[8, 3, 1, 6, 4, 9, 2, 5, 7] それっぽく動いているような気がします。 ただこのコードは組み込みのsort関数の比較関数の結果をランダムにすればシャッフルされるという前提に立って実装されていますが、考えてみると、この想定自体が随分といい加減です。 大体のソート処理は、一定のルールに基づいて2つの要素を取り出し、その要素同士を比較関数で比較し、結果に応じて並べ替えるという操作を繰り返し行います。 そのソート処理の内部実装は処理系に依存していますが、例えばChromeのJSエンジンであるV8の実装では Timsort を利用しています。Timsortはざっくりいうと、マージソートをベースにして、いくつかのソートアルゴリズムのアイデアを取り入れて性能を改善したアルゴリズムです。 ここで問題になるのが、先程の比較関数に乱数を利用する考え方では、ソートアルゴリズムの詳細を無視して比較関数さえランダムにすればすべての要素が乱雑に配置されるという想定をしていることです。実際にはその想定は間違っていて、操作後の配列の分布は大きく偏ります。 Will it shuffle? というサイトではその偏りを視覚化して見ることができます 以下の画像はそのスクリーンショットですが、比較関数をRandomにした場合の結果の偏りが大きいことがわかります 特に先頭部分、末尾部分があまりシャッフルされていない結果になっています (なぜそうなるのかという原理的な部分は wikipediaなどでも解説 があります) ではどうすればいいのかというと、こうしたシンプルなアルゴリズムの研究はしっかり行われているのでちゃんとその知識を拝借しましょうということで、今回のツールでは フィッシャーイェーツのアルゴリズム を利用したものに変更しました。 このアルゴリズムは、とても素朴なものでランダムな順番の数列を生成してそのとおりに要素を並べるだけのものではありますが、 ソートするなどの余計な処理を挟まない分、計算量的にもとてもスマートで正しく動きます こちらのアルゴリズムでの結果も先程のWill it shuffle?のサイトに用意されており、配置の偏りが少ないことが視覚的にわかります。 他にもこのフィッシャーイェーツの改良アルゴリズムなどいくつか手法は開発されているようでしたが、今回のようなちょっとしたツールにはこれで十分そうでした。 結果 圧倒的改善 をうみました 😭 コードの見た目がシンプルだからといって、正しく動いてる保証は無く、 特にアルゴリズムを適応するような操作についてはきちんと動作を考える必要があると改めて反省をする機会になりました。 このシャッフルの操作については、最初のバギーな実装を紹介しているサイトが検索結果にも多数出てくるため結構ハマってしまいそうなケースが多そうだなと思います。 私自身もシャッフルの偏りというような内容の記事を以前に見た記憶はあったのですが、自分で実装して罠にハマっていることに気づいたのは指摘された後でした。 あまりフロントエンドとは関係のない内容ですが、これでメンバー同士の交流も円滑に行っていくことができそうです。 採用情報 | BASE, Inc. 採用情報-フロントエンドエンジニア 採用情報-Webアプリケーションエンジニア
こんにちは! BASEのInformation System(情シス)に所属している横山です。 さっそくですが、BASEでは毎月全社定例を行っています。去年から続くコロナ禍の中、以前までは当たり前のようにやっていたオフラインでの集会も難しくなっています。そしてWork From Homeの継続に伴い、全社定例はより重要なイベントになっています。 「今後すぐに在宅勤務がなくなることはないだろう。」 「月に一度の全社定例がより大事なコミュニケーションの場となるので、アップデートしたい。」 そんな想いで全社定例をより良くするために本格的な配信環境を整えて開催しました。 今回はその様子を振り返りたいと思います。 今までの全社定例では コロナ禍の今、100人以上の社員が出社し集まることは難しくなってからは、Zoomのウェビナー機能を使ってやっていました。 Zoom ウェビナーを使った全社定例 こちら特別なことは一切していないのでZoomのウェビナーライセンスのみあれば可能です。 zoom.us ウェビナーを使った全社定例は簡単に始めることができるので手軽ではありますが、 スライドの内容や話しての感情が伝わるように登壇者や司会者の顔だけでなく体も映したい 登壇者も各自のPCやマイクを使っているので人によって画質や音質にばらつきをなくしたい 長時間になると単調な映像だとつらいので、スイッチングやワイプの切り替えなどの仕掛け、より良いデザインにして、見ていて楽しい映像にしたい こういった課題がありアップデートをすることになりました。 年初会に向けて 去年の11月下旬に全社定例のアップデートの話があり、ターゲットを1月の年初会に合わせて準備をはじめました。Slackでは、#pj-broadcast_system が立ち上げり、配信に詳しい社内メンバーにアドバイスをもらいながら進めることとなりました。 12月上旬、機材が届きはじめたので、年末年始の休みに入るまでに二日ほど情シス含む関係者で集まり、機材の設置やどういう構成で配信しようかと試行錯誤しました。 試行錯誤している様子 1 試行錯誤している様子 2 最終的に、年初会はこんなイメージでやることになりました。 背景スライド、プレゼンスライド、登壇者が左下にワイプで抜かれていて、進行の際に司会者が右下に登場します。 配信イメージ みんなで構成を考えつつ、必要な機材を洗い出しました。 機材 主な機材はこちらになります。 カメラ x 1 CANON XA40 (レンタル) 今後必要になるかもしれないマニュアル設定ができ、外付けマイクや会場音声を利用できるようXLR入力に対応、レンズ管理などは大変なのでレンズ一体型のビデオカメラを探しました。業務用でありつつボタンが最小限でタッチ操作メインのXA40にしました。 最初はを購入する予定でしたが、買う前に一回レンタルして使ってみて決めようということでレンタルしたところ、利用する頻度も多くないし、このままレンタルで良さそうという結論になりました。レンタルだと一日 3,000円/台です。 三脚 x 2 E-IMAGE EK630 カメラも業務用であること、携帯性 < パン/ティルトのスムーズや安定性を優先し、それでいて安価な三脚です。元々X40を二台購入する想定だったので三脚も二台購入しています。 グリーンバック x 1 Elgato Green Screen 折りたたみ式になっているので収納ケースを開けて、下から引っ張るだけです。収納ケースがそのまま土台になるのでセットも片付けも数秒で終わります。 スイッチャー x 1 Blackmagic Design ATEM Mini Pro マルチビューモニターが欲しかったのでProを購入しました。 マイク x 2 SHURE SM58SE スイッチ付きモデルにしてます。届くまでカメラのレンタルでついてきたマイクでテストしていましたが、SHUREのマイクに変えたらノイズがほとんどなくなりクリアに聞こえるようになりました。 照明 x 2 LPL ライトバンク LB-604 L18893 なんと社内のメンバーが貸与してくれました。 上記の他に、ケーブル、変換アダプタ、コードリールなど細々した周辺機器も購入しました。 構成 結果、このような構成になりました。 最背面には背景用スライドがあり、その上にプレゼン用スライドと左下の登壇者のワイプを被せ、OBS側でクロマキー合成した司会者を被せています。 スライドと登壇者が被ると見にくかったりスライドに集中できなかったりするのであえて左下に置いてます。 司会者は登壇者が話している時はグリーンバックの前から移動して映らなくします。 配信イメージ(詳細) まとめた構成図になります。 構成図 登壇者はスライドのスピーカーノートが表示されているPCを見ながら話し、配信しているZoomに参加したPCで配信画面とチャットを確認することができます。 司会者は進行する時のみカメラに入り、登壇者が話している時はカメラに映らない場所で待機します。 XA40のカメラマンは登壇者の写り具合を確認しながらカメラを微調整します。Webカメラは固定です。 また、進行に合わせて背景用スライドを操作する人もいます。 設定 ATEM Mini Pro プレゼン用スライドをアップストリームキーで設定します。 パレット > アップストリームキー1 > DVE フィルソース:Camera 2(プレゼン用スライド) 位置:ちょうどいい位置に サイズ:ちょうどいいサイズに プレゼン用スライド(アップストリームキー) 登壇者のカメラはダウンストリームキーで設定します。 パレット > ダウンストリームキー1 > DVE フィルソース:Camera 3(登壇者カメラ) マスク:映る場所に合わせて上下左右の数値を調整 本来ダウンストリームキーはテロップやロゴなど常に映っている部分の編集に利用するようなのですが、今回はそこにカメラを設定してます。マスクした中にちゃんと映るようにカメラも調整します。 登壇者カメラ(ダウンストリームキー) OBS 次は配信PCでOBSを起動し、ATEM Mini ProとWebカメラをPCに接続します。 OBSでATEM Mini Proの映像と司会者を合成します。 OBS > ソース > + >映像キャプチャデバイス デバイス:Blackmagic Design OBS > ソース > + >映像キャプチャデバイス デバイス:HD Web Camera OBS ソース > Webカメラ > フィルタ > エフェクトフィルタ > + > クロマキー 細かい設定はデフォルトから変えていません。 クロマキー あとは二つの映像を重ねて、サイズ、位置を調整します。 Zoom 今回はウェビナーではなく、通常のミーティングに配信PCで参加します。 最初は、ビデオ設定 > OBS Virtual Camera にして配信していましたが、 画面共有 > 詳細 > 第2カメラのコンテンツ でカメラの映像を画面共有する方が画質が良くなりました。 Zoom 第2カメラのコンテンツ 当日 年末年始休暇に入る前に機材は設置し、映像や音声の確認をしました。 あとは当日を迎えるだけです。 本番前 本番1時間くらい前にOBSを操作するPCが固まり、Webカメラの映像を取り込めなくなりました。 OBS、PCを再起動しても改善せず。 調査してても間に合わないのですぐに別のPCにOBSを入れて設定しました。 本番始まってからはもう突き進むしかないのですが、配信画面でよく見ると本来のスライドとは少し色味が変わっていることに気付きました。薄いグレーを使っている箇所はほぼ見えなくなってしまいましたが、それでも何とか配信自体は止めることなく、致命的な事故もなく終えることができました。 全社定例の配信映像 課題 顔だけでなく身振り手振りも見やすいかたちで映すことができ、映像としてもイメージしていたものに近いので、概ねやりたいことはできたのですが新たな課題がたくさんあります。 リハーサルはちゃんと本番のスライドで確認する リハーサルが12月21日で本番が1月5日で年末年始を挟むので仕方ない部分もありつつ、リハーサルが早すぎて本番用スライドで確認できなかった もっと画質を良くしたい スライドの色が微妙に変わってしまう 薄いグレーは消える/見にくくなる クロマキー合成したときに腕まわりに黒いぼやぼやが映る グリーンバックに映った影? などなど、次の配信までに少しずつでも改善していきたいです。 あと設置、片付けにも時間がかかってしまうので、ゆくゆくは専用の部屋や配信スペースを用意したいですね。 まとめ 個人的には他社さんでの配信事例も見ていたので、いつかやってみたいと思っていました。 高価な機材はプライベートで触ることが難しいですし、とても楽しみでした。 実際に色々試行錯誤している時間はとても楽しく、夢中になってやっていたような気がします。 私を含めてほぼ経験がないメンバーで手探りでここまでやってきましたが、それ故に改善する点はまだまだたくさんあります。 専門性が高いのでこれからもっと勉強し、もっと良い全社定例にしていきたいと思います。 おまけ 緊急事態宣言の発令もあり、2月の全社定例は従来のようにZoomのウェビナー機能を使って実施しました。その際、こちらのSlackに投稿したメッセージがニコニコ動画風に流れるChrome拡張機能を使ってみたらとても盛り上がりました。 chrome.google.com
こんにちは。BASE BANK 株式会社 Dev Division にて、 Software Developer をしている東口( @hgsgtk )です。 BASE 株式会社では、New Relic 株式会社のプレスリリースで発表されている通りオブザーバビリティプラットフォーム「New Relic One」を導入しています。 newrelic.com 私が所属している BASE BANK 株式会社のプロダクトチームでも New Relic One を活用しています。当チームでは AWS や GCP などのインフラ構成管理に Terraform を利用しております。New Relic One での設定情報も Terraform でのコード管理をすると次のような利点が得られて便利です。 設定内容がコードとして可視化される 意図しない設定変更を切り戻したい場合に Terraform の機能で戻しやすい 当記事で紹介する内容は New Relic One 特有なものもありますが、3rd Party 製の Terraform Provider を利用する際に一般的に当てはまる内容も含みます。具体的に紹介する内容は下記となります。 TerraformのNew Relic Providerを使う 当記事で前提するアカウント体系とディレクトリ構成 main.tf での初期設定 3rd party Providerを利用したmoduleを作成する際の注意点 GitHub ActionsによるCI/CD Pipeline 複数環境はjobs.<job_id>.strategy.matrixで対応 Job outputsでのjob間の値受け渡し terraform init/validate/plan結果のPRコメント Slack通知 おわりに TerraformのNew Relic Providerを使う New Relic を Terraform 管理するための New Relic Provider が用意されています。 https://registry.terraform.io/providers/newrelic/newrelic/latest/docs 具体的な始め方は Getting Started with the New Relic Provider にて解説されています。当記事では CI/CD Pipeline の用意等工夫点があった箇所をピックアップして紹介します。 当記事で前提するアカウント体系とディレクトリ構成 BASE BANK チームでは次の 3 つのアカウントを用意して New Relic One を活用しています。 BASEBANK-production BASEBANK-staging BASEBANK-development 環境ごとに 3 つのアカウントを用意しています。Terraform のディレクトリ構成としては環境ごとにサブディレクトリを切る構成としています。 . ├── dev // BANK-development ├── modules // 全環境共通モジュール ├── prd // BANK-production └── stg // BANK-staging ディレクトリ構成の特徴としては、 環境ごとにディレクトリを分け tfstate も環境ごとに持つ module を利用し環境共通の構成定義は module 内に定義する といった点があげられます。 main.tf での初期設定 terraform init によって初期化し最初の main.tf を用意しますが、ここでは次の内容が含まれます。 required_provider として newrelic/newrelic の定義 tfstate の保管箇所の定義 New Relic Provider の設定定義 terraform { required_version = " ~> 0.14.3 " # 1 . required_providerとして`newrelic /newrelic `の定義 required_providers { newrelic = { source = " newrelic/newrelic " version = " ~> 2.14.0 " } } # 2 . tfstateの保管箇所の定義 backend " s3 " { bucket = " sample-bucket " key = " terraform.tfstate " region = " ap-northeast-1 " } } # 3 . New Relic Providerの設定定義 provider " newrelic " { account_id = var.account_id } variable " account_id " { type = string } tfstate は AWS の S3 のバケットに保管しています。BASE BANK チームでは development/staging/production の 3 つの AWS アカウントを管理しているのでそれぞれのアカウントに tfstate のバケットを用意しています。 New Relic Provider の設定定義では New Relic の API Key を必要とします。 https://registry.terraform.io/providers/newrelic/newrelic/latest/docs/guides/provider_configuration New Relic API Key は「 Getting Started with the New Relic Provider 」のガイダンスのとおりに作成します。 Terraform 実行時の参照方法は variable・環境変数の 2 つの方法がありますが、CI/CD 環境から注入しやすい点で環境変数 NEW_RELIC_API_KEY を利用しています。 ちなみに newrelic/terraform-provider-newrelic の内部実装では下記の Provider 定義でこの挙動を実現しています。 provider := &schema.Provider{ Schema: map [ string ]*schema.Schema{ // (省略) "api_key" : { Type: schema.TypeString, Optional: true , DefaultFunc: schema.EnvDefaultFunc( "NEW_RELIC_API_KEY" , nil ), Sensitive: true , }, https://github.com/newrelic/terraform-provider-newrelic/blob/8933a37ccf09137a87643a7da33648012a33c360/newrelic/provider.go#L44-L49 3rd party Providerを利用したmoduleを作成する際の注意点 Terraform の仕様は required_providers を省略した場合、 registry.terraform.io/hashicorp/ を参照することが仕様なため、 hashicorp/newrelic を使おうとしてしまいます。 Note: If you omit the source argument when requiring a provider, Terraform uses an implied source address of registry.terraform.io/hashicorp/. This is a backward compatibility feature to support the transition to Terraform 0.13; in modules that require 0.13 or later, we recommend using explicit source addresses for all providers. https://www.terraform.io/docs/configuration/provider-requirements.html#source-addresses しかし、実態があるのは newrelic/newrelic であるため、module を新規作成する際はそれぞれの module ごとに明示的に指定する必要があります。 terraform { required_providers { newrelic = { source = " newrelic/newrelic " } } required_version = " >= 0.14 " } GitHub ActionsによるCI/CD Pipeline ここからは、GitHub Action での CI/CD パイプラインを紹介します。特徴としては 2 点になります。 Pull request を作成すると自動で PR コメントに Plan 結果が記録される GitHub Pull Requestへのコメント main ブランチへマージしたタイミングで自動で Apply される GitHub Actionsでの自動Apply 全量の GitHub Actions の yml ファイルを先に掲載いたします。 name : tfapply on : push : paths-ignore : - 'docs/**' branches : - main pull_request : paths-ignore : - 'docs/**' jobs : terraform-plan-apply : name : terraform plan apply strategy : matrix : env : [ dev, stg, prd ] runs-on : ubuntu-latest defaults : run : shell : bash steps : - name : Checkout uses : actions/checkout@v2 - name : Terraform setup uses : hashicorp/setup-terraform@v1 with : terraform_version : 0.14.3 - name : Define Configuration id : config run : | if [[ $ {{ matrix.env }} = 'dev' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_DEV }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_DEV }} elif [[ $ {{ matrix.env }} = 'stg' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_STG }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_STG }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_STG }} elif [[ $ {{ matrix.env }} = 'prd' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_PRD }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_PRD }} echo ::set-output name=new-relic-api-key::${{ secrets.NEW_RELIC_API_KEY_PRD }} else echo 'unsupported matrix environment' exit 1 fi - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} aws-secret-access-key : ${{ steps.config.outputs.aws-secret-access-key }} aws-region : ap-northeast-1 - name : Terraform fmt id : fmt run : terraform fmt -recursive -check continue-on-error : false - name : Terraform init id : init run : terraform init working-directory : ./${{ matrix.env }} - name : Terraform validate id : validate run : terraform validate -no-color working-directory : ./${{ matrix.env }} - name : Terraform lint uses : reviewdog/action-tflint@master with : github_token : ${{ secrets.github_token }} reporter : github-pr-review fail_on_error : true working_directory : ./${{ matrix.env }} - name : Terraform plan run : terraform plan -no-color id : plan working-directory : ./${{ matrix.env }} env : NEW_RELIC_API_KEY : ${{ steps.config.outputs.new-relic-api-key }} # https://github.com/hashicorp/setup-terraform#usage - uses : actions/github-script@v3 if : github.event_name == 'pull_request' env : PLAN : "terraform \n ${{ steps.plan.outputs.stdout }}" with : github-token : ${{ secrets.GITHUB_TOKEN }} script : | const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }} #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` <details><summary>Show Plan</summary> \`\`\`${process.env.PLAN}\`\`\` </details> *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ matrix.env }}\`, Workflow: \`${{ github.workflow }}\`*`; github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : output }) - name : Terraform apply if : github.event_name == 'push' run : terraform apply -auto-approve working-directory : ./${{ matrix.env }} env : NEW_RELIC_API_KEY : ${{ steps.config.outputs.new-relic-api-key }} - name : Slack notification (applying success) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && success() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Success to apply terraform-newrelic, check it! SLACK_COLOR : good SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} - name : Slack notification (applying failure) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && failure() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Failed to apply terraform-newrelic, check it! SLACK_COLOR : '#bd3232' SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} この GitHub Actions の yml ファイルの前提として定義している secrets は以下です。 KEY 内容 AWS_ACCESS_KEY_ID_DEV dev 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_DEV dev 環境の AWS IAM User のアクセスシークレット AWS_ACCESS_KEY_ID_STG stg 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_STG stg 環境の AWS IAM User のアクセスシークレット AWS_ACCESS_KEY_ID_PRD prd 環境の AWS IAM User のアクセスキー AWS_SECRET_ACCESS_KEY_PRD prd 環境の AWS IAM User のアクセスシークレット NEW_RELIC_API_KEY_DEV dev 環境の New Relic API Key NEW_RELIC_API_KEY_STG stg 環境の New Relic API Key NEW_RELIC_API_KEY_PRD prd 環境の New Relic API Key SLACK_WEBHOOK Slack の incoming webhook URL AWS_ACCESS_KEY_ID や AWS_SECRET_ACCESS_KEY は、tfstate を S3 に保管しているため設定しています。これらを発行している IAM User は当該 S3 へのアクセス権限のみを付与したものとなっています。 resource " aws_iam_policy " " terraform-newrelic-ci-user-policy " { name = " terraform-newrelic-ci-user-policy " path = " / " description = " Allows users to manage terraform-newrelic state file. " policy = jsonencode ({ Version = " 2012-10-17 " Statement = [ { Sid = "" Effect = " Allow " Action = [ " s3:PutObject ", " s3:GetObject " ] Resource = [ aws_s3_bucket.terraform - state - newrelic.arn, " ${aws_s3_bucket.terraform-state-newrelic.arn}/* " ] } , ] }) } 以降、上記の GitHub Actions ファイル内での詳細を紹介していきます。 複数環境は jobs.<job_id>.strategy.matrix で対応 BASE BANK チームでは前述したとおり、development/staging/production の 3 アカウントを用意しています。それぞれの環境に対して CI/CD パイプラインが組むために GitHub Actions の syntax jobs.<job_id>.strategy.matrix を利用しています。 docs.github.com # (省略) strategy : matrix : env : [ dev, stg, prd ] # (省略) - name : Terraform init id : init run : terraform init working-directory : ./${{ matrix.env }} # (省略) directory 構成は前述したとおり dev/stg/prd とサブディレクトリを切っている構成としており各ディレクトリ配下に main.tf をおいています。 . ├── dev // BANK-development ├── modules // 全環境共通モジュール ├── prd // BANK-production └── stg // BANK-staging working-directory にて matrix で指定した環境名を指定することで dev での実行の場合は dev ディレクトリ配下となるようにしています。 jobs.<job_id>.strategy.matrix を使用した場合は dev/stg/prd へのフローは並列に実行されます。dev/stg を先に確認してから prd を実行したいニーズが強い場合はデメリットとなりますが、New Relic の設定管理ではそのデメリットは許容しうると考えこの構成としています。 Job outputsでのjob間の値受け渡し GitHub Actions では Job outputs という syntax を用いることで、job 間の値の受け渡しができます。具体的には jobs.<job_id>.outputs という syntax です。 docs.github.com この機能を用いて matrix ごとに使用する設定情報のハンドリングを行っています。 環境ごとに set-output で outputs を定義 id=config で定義した outputs を別ステップで参照 # Step1. 環境ごとにset-outputでoutputsを定義 - name : Define Configuration id : config run : | if [[ $ {{ matrix.env }} = 'dev' ]] ; then echo ::set-output name=aws-access-key-id::${{ secrets.AWS_ACCESS_KEY_ID_DEV }} echo ::set-output name=aws-secret-access-key::${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} // ... (省略) else echo 'unsupported matrix environment' exit 1 fi # Step2. id=configで定義したoutputsを別ステップで参照 - name : Configure AWS Credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} aws-secret-access-key : ${{ steps.config.outputs.aws-secret-access-key }} aws-region : ap-northeast-1 echo ::set-output name=key::value とすることで Job outputs に値を設定できます。当該 job に id を設定することで以降のステップで参照できます。 aws-access-key-id : ${{ steps.config.outputs.aws-access-key-id }} terraform init/validate/plan結果のPRコメント 毎回 PR 作成ごとに手元で plan した結果をコメントに貼るのは大変なので、自動で PR コメントに記載してくれるようにしています。当記事内では hashicorp/setup-terraform 内の usage にあるサンプルを活用しています。 github.com ここでは、terraform init/validate/plan の結果を先ほど紹介した Job outputs から取得できる標準出力を活用して、PR コメント内容を作成しています。 - uses : actions/github-script@v3 if : github.event_name == 'pull_request' env : PLAN : "terraform \n ${{ steps.plan.outputs.stdout }}" with : github-token : ${{ secrets.GITHUB_TOKEN }} script : | const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` #### Terraform Validation 🤖${{ steps.validate.outputs.stdout }} #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` <details><summary>Show Plan</summary> \`\`\`${process.env.PLAN}\`\`\` </details> *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ matrix.env }}\`, Workflow: \`${{ github.workflow }}\`*`; github.issues.createComment({ issue_number : context.issue.number, owner : context.repo.owner, repo : context.repo.repo, body : output }) 注意点としては -no-color オプションを付けておかないと通知内容が文字化けしてしまいます。 - name : Terraform plan run : terraform plan -no-color # (省略) Slack通知 rtCamp/action-slack-notify を用いて成功時・失敗時に Slack 通知を行っています。 github.com 成功時の Slack 通知はこのようになっています。 - name : Slack notification (applying success) uses : rtCamp/action-slack-notify@v2 if : ${{ github.event_name == 'push' && success() }} env : SLACK_USERNAME : (${{ matrix.env }}) terraform-newrelic Automatic Applyer SLACK_ICON : # IconのURL SLACK_MESSAGE : Success to apply terraform-newrelic, check it! SLACK_COLOR : good # https://base.slack.com/services/1633237925184?updated=1 SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK }} この設定で以下のように Slack 通知できます。 slack通知結果 おわりに New Relic One を活用する際に Terraform の初期設定を紹介いたしました。3rd Party Provider を利用する際の初期設定や GitHub Actions を用いた CI/CD Pipeline の作成事例として参考になれば幸いです。 New Relic 等を活用したオブザーバビリティの実践によるサービス品質の向上に興味のある方は、ぜひカジュアルにお話しましょう。 @hgsgtk に DM 頂いても構いません。 open.talentio.com では、また来月もこちらのブログでお会いしましょう。
社長室の米田です。 本日20:00より、「#BASEとnote 急成長サービスならではの技術課題と組織課題を語る」と題し、note CTOの今さん、BASE CTOの川口の対談イベントを開催いたします。 Zoom開催ですので、途中参加/退出等も気兼ねなくご参加いただけます。 お申込み枠にまだ余裕がありますので、ご興味のある方は下記URLよりお申し込みください。 base.connpass.com
こんにちは。UIデザイナーの野村です。 前回 に続き、デザインリサーチの取り組みについて述べたいと思います。 (デザインリサーチって何?と疑問に思った方は、前回の記事の「デザインリサーチとは」の項を参照いただけると幸いです。) 主に、社内で企画・実施したデザインリサーチワークショップについて書かせていただきます。 前置き・プロボノプロジェクトの参加(社外) 社内での活動を書く前に、その活動の下敷きとなったプロボノプロジェクトでの経験に軽く触れたいと思います。 (プロボノとは、大まかにいうと「ボランティアの社会貢献」のようなものです。詳しい意味は Wikipedia記事 などをご参照ください。) 2020年春ごろに、社外で実施されたデザインリサーチプロジェクトに参加しました。 プロジェクト概要・成果: https://preview.studio.site/live/xNWYkmXqlB 一般の方を対象に、デザインリサーチの手法に則ってインタビューや分析を行う、というプロジェクトです。 テーマは「リモートワークを行う人の、仕事や生活の実態を探ること」であり、特定のプロダクトや機能について調査するようなものではありません。 このプロボノプロジェクトでは「リモートワーク実態」をテーマとしていたわけですが、これを「ネットショップ運営実態」に置き替えて、似た流れでリサーチを行ったら、BASEサービスの開発にとって有効な知見(ユーザへのより深い共感)を得られそうだと考えました。 継続的なデザインリサーチを検討 上記プロボノプロジェクトで得た知見と、商品オプションAppプロジェクトでのリサーチ試行経験( 前回記事 参照)を踏まえ、 機能開発プロジェクトとは別の括りで定常的なリサーチプロジェクトを行う形 が有効なのでは?と考えて、それを実践する方法を検討をしました。 定常リサーチプロジェクト案 大まかに、以下のような形を想定しました。 隔月か四半期ごとくらいの頻度で、有志を募って数名でインタビュー形式のリサーチを行う。インタビュー対象者数は5名程度か、多くても10名くらいが目安。通常業務に支障が出ない頻度・労力を意識する。 大テーマを「ネットショップ運営の実態や困りごとを探る」として、BASEの利用状況に限らず多様な角度からの情報を集める。 小テーマは都度、その時々で稼働しているプロジェクトに合わせてアレンジする。(例えば、「Instagram販売Appの改善プロジェクトが動いているなら、Instagramの活用実態について特にフォーカスする」というように。) このような、インタビュー主体のリサーチプロジェクトの実施をひとまずの目標としました。 デザインリサーチワークショップ 書籍やセミナー等からある程度はリサーチインタビューのノウハウを得られますが、いきなり実ユーザへのインタビューを始めるのでなく、なんらかトレーニングをしておきたいところです。 また、私が1人でリサーチを実施して知見を蓄えても、チームとしてのデザインワーク向上には繋がりにくいように思われます。 周りから「何かよくわからないことをやってる」と思われながら続けていくのも心理的に辛いので、あらかじめ周囲からの「意義あることをしている」という共感を得られたら望ましいですね。 そんな想いから、まずは社内でワークショップを行うこととしました。 外部の専門家を講師に呼んで、トレーニングをしてもらう。 デザインチームメンバーに参加してもらい、「何をしているのか」「どんな意義があるのか」について知ってもらう。 という二点を適えるべく、企画を進めます。 幸いなことに、 書籍「デザインリサーチの教科書」 の著者である アンカーデザイン社 の木浦さんと個人的な繋がりがありましたので、ワークショップ講師をお願いし、引き受けていただけました。 参加人数は14名で、4チームに分かれて以下のような流れでユーザインタビュー実施しています。 (オンラインで完結するよう計画しています。) DAY1(3時間):インタビュー設計 宿題期間(2週間):インタビュー協力者手配とインタビュー実施 DAY2(3時間):分析・考察 【DAY1 設計】 【宿題・インタビュー手配&実施】 【DAY2 分析・考察】 ワークショップの成果物としては、デザインチャレンジ(課題発見)が10個と、課題解決案が20個程度作成されました。(以下、その一例です。) デザインチャレンジの例 課題解決案の例 インタビューを行うためのユーザ手配がなかなかに大変だったのですが、大変だった故に意義も大きかったように感じます。ある意味、ワークショップの中で最も意義ある部分だったかも知れません。 通常業務の中でユーザとの直接的な対話を行おうとしても、スケジュール的要因、費用対効果面等から、なかなか実行はしづらいものです。慣れないことを行うことへの心理的ハードルもあります。 今回「ワークショップの宿題」という形でデザイナーとユーザの直接的な交流の場を作れたことは一つの大きな成果でした。 ワークショップ実施後に参加メンバーから感想を募ったのですが、「楽しかった」「勉強になった」というコメントを多数いただいており、デザインリサーチに対してポジティブな印象を持ちつつ知見を得てもらえたようです。 「ショップオーナーになりたくなった」「ショップを開きたくなった」といった声も出ており、ユーザに対する深い共感を得られたことも窺い知れます。 ワークショップの成果と今後の継続 ワークショップの実施にて、以下の2つの目的を達しました。 - リサーチの計画から分析までの一連の流れを経験する - 社内メンバーにデザインリサーチ活動の内容と意義を伝える 次の段階としては、単発のワークショップではなく継続的なリサーチプロジェクトとしての流れを作るべく、計画・検討を進めています。 まとめ 社内でのワークショップ(および、その下敷きとなったプロボノプロジェクト体験)から、デザインリサーチの実施・進行についていくらか知見が身についてきました。まだまだ試行錯誤の必要を感じますが、規模を変えたり新たな手法を取り入れたりしつつ、リサーチ活動を継続していきたいと考えています。 また、ここまでの経験から、デザインリサーチ活動は開発プロジェクトの中に組み込むよりも、独立したプロジェクトとして行う形が弊社には向いてそうだ、という感触も得ました。 これらの知見・経験を活かせるよう、今後のプランを練っているところです。 定性リサーチ文化を根付かせるべく、いろいろ楽しく試していきたいと思います。