TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

580

この記事は、「 BASEアドベントカレンダー2019 」16日目の記事です。 devblog.thebase.in こんにちは。Owners Growthチームの宮川です。 BASEでは、ショップオーナーさんのことを「Owners(オーナーズ)」と呼んでおり、私たちオーナーズの成長を支援するチームはOwners Growthといいます(オーナーズと呼ぶことになった経緯は こちら )。 今回はOwners Growthで実際どのように支援しているか簡単にご紹介します。 目次 オーナーズとは? ネットショップ運営の構成要素 ショップ運営でやること オーナーズの成長を後押しする 情報提供手段 まとめ オーナーズとは? 一言でオーナーズと言っても、80万ショップの様々なオーナーズがいらっしゃいます。商品のカテゴリだとアパレル・インテリア・コスメ・食べ物など多岐に渡り、個人でモノづくりをされている方から、企業として運用されている場合も。 また、実店舗を持っているのか、ネットショップだけなのか。オリジナル商品なのか、セレクト商品なのかなど、ショップの特性は幅広いです。 すこし前の情報ですが、ネットショップ作成サービス「BASE」をお使いのオーナーズについての調査結果が下記に掲載されています。 「BASE」が初のオーナーズ調査を実施 – 個人・法人を問わずブランドを立ち上げる時代の流れが顕著に - https://binc.jp/press-room/news/press-release/pr_20190514 「BASE」のショップオーナーさんの中には、初めてネットショップを開設・運営される方もたくさんいらっしゃいます。また上述の調査でもあるように半分以上が個人で運営されており、ネットショップの運営を周囲に相談できる方も多くはないように思います。 Owners Growthではそんなオーナーズに、ショップ運営のパートナーとして寄り添い、ショップを継続的に運営していただくための支援ができればと思っています。 具体的には下記のような施策を通じて、ショップごとの運用プロセス最適化のサポートを行っております。 ショップの状況に応じた効果的なメール配信 ネットショップ作成サービス「BASE」のショップオーナーさんが使う管理画面におけるアドバイス機能の提供 オウンドメディア( BASE U )におけるショップ運営・販促ノウハウの紹介 ネットショップ運営の構成要素 はじめに、ネットショップの運営はどのような要素でなりたっているか確認しましょう。 訪問者数 ネットショップに来ていただいているお客様のことです。「新規」と「リピーター」にわけることができます。“一見さん”で来ていただいたお客様を、いかに“常連さん”として再訪問していただくかが重要となります。 購入率 来ていただいたお客様のうち、実際何名に購入されたかの割合です。ここは、「ショップデザイン」「商品画像」「決済方法」と他にもたくさんの要素にわけられます。 購入単価 「商品価格」「まとめ買い」といった、お客様の合計注文額となります。 したがって、ショップ運営はこれらの要素を最適化する必要があります。 ショップ運営でやること それでは、ショップの成長支援をするためには何をすれば良いか。さきほどの構成をもとにして、運営に必要な内容を一部ですがまとめてみました。 訪問者数は「集客」として、「SNS(Instagram・Twitter・YouTubeなど)」「SEO」「広告」などを活用して獲得することできます。 一方、購入率は「ショップ運営」としての「ショップデザイン」「商品画像」「決済方法」などが重要な内容となります。 ショップデザインであれば、訪問していただいたお客様にとってショッピングしやすく、楽しくワクワクするようなデザインになっているのか。また、商品画像は商品の魅力が伝わるような分かりやすい画像になっているのか。というように、各項目ごとの運営内容を検討していきます。 オーナーズの成長を後押しする 上述のように、ショップ運営の成長要素は幾つもあり、Owners Growthではこれらを適切に実施してもらうための情報を、下記のような手段で提供しています。 情報提供手段 「BASE」のショップオーナーさんが使う管理画面の「お知らせ」 メール BASE Creator アプリ(ショップ管理を行えるアプリ)のプッシュ通知 SNS(Instagram・Twitter・Facebook) 以上の4つがあり、下記のような目的で活用しております。 「管理画面」「メール」「プッシュ通知」はショップのカテゴリ・売上・「BASE」の拡張機能であるAppsの活用状況など、ショップのセグメントごとに配信内容を変更できるため、ショップのステータスにあった細かくより深い情報を送ることが可能です。そのため、実行して欲しいアクションをうながすために効果的です。ただし、この3つの中でも、セグメント条件や配信内容によって効果はさまざまあります。 一方、SNSでは配信先をしぼることはできない(Facebookは年齢や地域などで絞ることは可能)かつ、オーナーズ以外のユーザーもいるため、イベント情報や新機能リリースなどライトな情報を送るのに適しています。また、お困りごとや機能に関するフィードバックなどをコメントでいただく可能性があるので、オーナーズとのコミュニケーションの場として有効です。 実際の運用方法としてそれぞれ異なる部分はありますが、基本的には下記のような流れで進めています。 仮説立て 例えば、Instagramのショッピング機能を使ったショップの売上は訪問者数によって変わるのか、等 仮説検証のためのデータ抽出 これを、Re:dash・Google Analytics(Google スプレッドシートにアドオンを入れて連携)などの分析ツールを用いてデータを抽出 施策の立案 仮説が合っていれば、施策への落とし込みを行う この場合、「Instagramのショッピング機能を使い、訪問数が◯◯数以上のショップだと、それ以外にショップに比べると◯◯%売上が高い」というデータが見つかれば、これにもとづく施策を立案 特にデータの変化が見えなければ、別の仮説を検討 施策の実行 この仮説を実証するために、「Instagramのショッピング機能」を利用していないショップには利用を促し、すでに利用しているショップには訪問者数を上げるための施策を提供 効果検証 施策を実施後、実際に効果があったか上述の分析ツールを使って検証し、改善 ちなみに、「管理画面」「メール」「プッシュ通知」のうち、施策によらず高い開封率をあげているのが「メール」です。世間では「古いツール」「開封率が低い」というイメージが先行しがちなメールですが、オーナーズ向けの施策としては効果的な結果が出ています。 最初に、オーナーズは十人十色という内容を記載いたしましたが、本当にショップの運営状況はさまざまです。 ショップがどのような設定内容で何を登録しているかやどういう行動をしているのかなど、多様なデータと照らし合わせる定量分析はもちろん、ときには定性分析も用いながら最適な情報を適切なタイミングで提供する必要があります。 情報が決まれば適切なツールは、メールなのか管理画面内でのお知らせなのか、それともBASE Creator アプリのプッシュ通知なのか、なども決めなければいけません。 「オーナーズが何に困っているかを見つけてそれを解決したい!」という一心で日々の業務に励んでおります。 まとめ 80万ショップの支援を通じて、これまで売上がなかったショップがはじめて売上ができたり、毎月コンスタントに売上があがっていなかったショップが毎月売上が上がるようになったり、とオーナーズのショップ運営を後押しできたときはすごく嬉しいので、大変やりがいのあるお仕事だと感じています。 “ネットショップは顔が見えない”とは良く言われますが、日々膨大な定量データとアンケートなどの定性データの両面と向き合いながら、オーナーズの顔を想像して日々業務に取り組んでいます。 80万ショップの成長を支援するという、他にはないユニークな業務に少しでも興味を持ってくださった方がいれば、 採用ページ で会社の雰囲気や実際はたらいているメンバーの様子などを見ていただければと思います。 オーナーズの未来を一緒に支えていきませんか? 明日は、Platform Devマネージャーの大窪さんとData Strategy所属の杉さんです!
この記事は BASE Advent Calendar 2019 の16日目の記事です。 devblog.thebase.in エンジニアの田中( @tenkoma )です。 あなたのマシンにインストールされているPHPのバージョンは何ですか? 仮想マシンやコンテナで開発環境を作ることが増えているので、ホストOSにはPHPが入ってない・気に掛けたことがない、ということも多いかもしれません。 僕は、新しいバージョンを試すために php-build を使ってmacOSでビルド・インストールしています。(また、プロジェクト毎にバージョンの切り替えがしやすいよう direnv を使っています) 今回はphp-buildを使った複数バージョンビルドを、コードを書いて少し省力化してみたので紹介します。 多くのバージョンのPHPをそろえてみました。ただし、Catalinaでは7.0.19未満の動作が実現できていません 前提 この記事で紹介するコードは以下の環境で実行しています。 OS: macOS 10.15 Catalina 依存ライブラリのインストールはHomebrew php-buildは motemen/ghq でローカル環境にclone php-buildの導入・トラブルシュートは以下の記事が参考になります。 複数バージョンの PHP をインストールして使う - OTOBANK Engineering Blog Macのphpenv(php-build)でビルドしようとしたら出るエラーと解決まとめ - Qiita 作った理由 php-buildを使うと、自分でソースコードをダウンロードしてビルドするよりは楽に、ビルド・インストールができます。(ただし、ビルドエラー時に必要な依存ライブラリについて調査したりするので、導入時にある程度の知識や調査の時間が必要です) 例えば、以下のようなコマンドでビルド+インストールします。 $ php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / これでインストールできたらめでたいのですが、macOS をアップグレードしていくと、なぜか依存ライブラリが見つからなくなるようになってきたので、ビルドのためのオプションを付けて以下のように実行しています。(macOS Catalina 10.15.1にて実行) $ PHP_BUILD_CONFIGURE_OPTS = " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " YACC = $( brew --prefix bison ) /bin/bison PHP_BUILD_EXTRA_MAKE_ARGUMENTS =-j4 php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / さて、PHP の新しいバージョン(ポイントリリース)はだいたい1〜2ヶ月に1度リリースされているようですが、このとき、7.3と7.2と7.1の新しいバージョンがほぼ同時にリリースされるという感じなので、そのたびに以下のようなコマンドを実行することになります。 $ ghq look php-build $ git pull $ ./install.sh $ exit $ export PHP_BUILD_CONFIGURE_OPTS= " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " $ export YACC= $( brew --prefix bison ) /bin/bison $ export PHP_BUILD_EXTRA_MAKE_ARGUMENTS= -j4 $ php-build -i development 7 . 3 . 12 ~/ local /php/ 7 . 3 . 12 / $ php-build -i development 7 . 2 . 25 ~/ local /php/ 7 . 2 . 25 / $ php-build -i development 7 . 1 . 33 ~/ local /php/ 7 . 1 . 33 / php-build コマンドを打つこと自体は対して大変ではありませんが、マイナーバージョンごとの最新バージョン番号を確認するのが面倒ですし、自動化出来そうだったのでやってみました。 複数のPHPバージョンをビルドするスクリプト 以下のスクリプトを作りました。 php-build-auto.sh #!/usr/bin/env bash function usage_exit() { echo " Usage: $0 [OPTIONS] <version1> [<version2> [...]] " echo echo " Options: " echo " -h, --help " echo " --parallel num (default: CPU physical core number) " echo " --install-root-path path (default: \$ HOME/src/local/php " echo " --override " echo " --show-versions " echo exit 1 } # option defalut PARALLEL = $( sysctl -n hw.physicalcpu_max ) INSTALL_ROOT_PATH = " $HOME /local/php " OVERRIDE = false SHOW_VERSIONS = false param = () for OPT in " $@ " do case $OPT in -h | --help ) usage_exit exit 1 ;; --parallel ) PARALLEL = $2 shift 2 ;; --install-root-path ) INSTALL_ROOT_PATH = $2 shift 2 ;; --override ) OVERRIDE = true shift 1 ;; --show-versions ) SHOW_VERSIONS = true shift 1 ;; * ) if [[ -n " $1 " ]] && [[ ! " $1 " =~ ^-+ ]] ; then param+ = ( " $1 " ) shift 1 fi ;; esac done if [ -p /dev/stdin ]; then IFS =$' \n ' for line in $( cat - ) do param+ = ( " $line " ) done fi BUILD_VERSIONS = () SKIP_VERSIONS = () if [ $OVERRIDE = true ]; then BUILD_VERSIONS = $param else for VERSION in " ${param[ @ ]} " ; do if [ -e " $INSTALL_ROOT_PATH / $VERSION /bin/php " ]; then SKIP_VERSIONS+ = ($VERSION) else BUILD_VERSIONS+ = ($VERSION) fi done fi echo " skip versions: " echo " ${SKIP_VERSIONS[ @ ]} " echo " build versions: " echo " ${BUILD_VERSIONS[ @ ]} " if [ $SHOW_VERSIONS = true ]; then exit 0 fi export PHP_BUILD_CONFIGURE_OPTS= " --with-zlib-dir= $( brew --prefix zlib ) --with-bz2= $( brew --prefix bzip2 ) --with-iconv= $( brew --prefix libiconv ) --with-libedit= $( brew --prefix libedit ) --with-openssl= $( brew --prefix openssl ) --with-libxml-dir= $( brew --prefix libxml2 ) --with-curl= $( brew --prefix curl ) --without-tidy " export YACC= " $( brew --prefix bison ) /bin/bison " export " PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j $PARALLEL " echo " ${BUILD_VERSIONS[ @ ]} " | xargs -n 1 -t -I@ php-build -i development @ " $INSTALL_ROOT_PATH " /@/ 使い方ですが、引数でPHPバージョンを指定すると、まとめてビルドしてくれます。 $ ./php-build-auto.sh 7 . 0 . 33 7 . 1 . 33 7 . 3 . 12 skip versions: 7 . 0 . 33 build versions: 7 . 1 . 33 7 . 3 . 12 [ Info ] : Loaded extension plugin [ Info ] : Loaded apc Plugin. ( 以下略 ) すでにインストール済みのバージョンがあれば、ビルドはスキップされます。もし再ビルドしたい場合は --override オプションを付けます。 $ ./php-build-auto.sh --override 7 . 0 . 33 7 . 1 . 33 7 . 3 . 12 このスクリプトですが、作りはじめたときは xargs -P を使って php-build コマンドを並列実行させるのが最大の特徴でした。しかし、PHPビルド後のXdebugビルドは、同じディレクトリで実行されるので、複数のXdebugビルドを1ディレクトリで同時にやってしまい、エラーになってしまったのでその機能を削除しています。 マイナーバージョン毎の最新バージョン番号を列挙する php-build-auto.sh は、新しいポイントリリースが出たときにまとめてビルドしたいときに使います。そこで、最新のポイントリリースバージョンを列挙するスクリプトを別途作りました。 listversion.php #!/usr/bin/env php <?php declare ( strict_types = 1 ) ; /** * usage: php listversion.php [--filter stable|minor-head] [--oldest-version version] [--definitions-path path] */ class PhpVersion { const VERSION_PATTERN = '/(?P<major>\d+)\.(?P<minor>\d+)\.(?P<point>\d+)/' ; public static function getMinorVersion ( string $ version ) { preg_match ( self :: VERSION_PATTERN, $ version , $ matches ) ; return sprintf ( '%s.%s' , $ matches [ 'major' ] , $ matches [ 'minor' ]) ; } public static function isStable ( string $ version ) { return preg_match ( self :: VERSION_PATTERN, $ version ) === 1 ; } } $ argvOptions = getopt ( '' , [ 'filter:' , 'oldest-version:' , 'definitions-path:' ]) ; $ options = [ 'filter' => !empty ( $ argvOptions [ 'filter' ]) ? $ argvOptions [ 'filter' ] : 'minor-head' , 'oldest_version' => !empty ( $ argvOptions [ 'oldest-version' ]) ? $ argvOptions [ 'oldest-version' ] : '5.6.0' , 'definitions_path' => !empty ( $ argvOptions [ 'definitions-path' ]) ? $ argvOptions [ 'definitions-path' ] : '/usr/local/share/php-build/definitions/' , ] ; $ definitionsIter = new DirectoryIterator ( $ options [ 'definitions_path' ]) ; $ versions = [] ; foreach ( $ definitionsIter as $ definition ) { if ( $ definition -> isDot ()) { continue ; } $ version = $ definition -> getFilename () ; if ( ! PhpVersion :: isStable ( $ version )) { continue ; } if ( version_compare ( $ version , $ options [ 'oldest_version' ] , '<' )) { continue ; } if ( $ options [ 'filter' ] === 'minor-head' ) { $ minorVersion = PhpVersion :: getMinorVersion ( $ version ) ; if ( !isset ( $ versions [ $ minorVersion ]) || version_compare ( $ version , $ versions [ $ minorVersion ] , '>' )) { $ versions [ $ minorVersion ] = $ version ; } } else { $ versions [] = $ version ; } } echo implode ( " \n " , $ versions ) . PHP_EOL; 実行すると、各マイナーバージョン毎の最新バージョンを列挙します。 $ ./listversion.php 7 . 2 . 25 7 . 3 . 12 7 . 1 . 33 7 . 0 . 33 僕の環境だと 7.0.19 未満のバージョンはビルドエラーになったので、列挙しないようにしています。列挙したい場合は --oldest-version オプションを使います。 $ ./listversion.php --oldest-version=5.3 7 . 2 . 25 5 . 4 . 45 7 . 3 . 12 7 . 1 . 33 5 . 3 . 29 5 . 5 . 38 7 . 0 . 33 5 . 6 . 40 php-build-auto.sh は引数と標準入力の両方でビルド対象を指定できるので、 listversion.php と組み合わせて、「マイナーバージョン毎の最新バージョンをまとめてビルド」、が実現できます。 $ ./listversion.php | ./php-build-auto.sh skip versions: 7 . 1 . 33 7 . 0 . 33 build versions: 7 . 2 . 25 7 . 3 . 12 php-build -i development 7 . 2 . 25 /Users/kojitanaka/ local /php/ 7 . 2 . 25 / [ Info ] : Loaded extension plugin [ Info ] : Loaded apc Plugin. ( 以下略 ) 2つのスクリプトを作った結果、PHPのバージョンアップ時の作業は以下のように単純化できました。 $ ghq look php-build $ git pull $ ./install.sh $ exit $ cd ~/src/github.com/tenkoma/php-build-tools $ ./listversion.php | ./php-build-auto.sh 今回実装したスクリプトは tenkoma/php-build-tools にて公開しています。 まとめ 複数のPHPバージョンを手元にそろえるときに使えるphp-buildの運用を多少楽にするためにコードを書き、バージョンアップ時に考える要素を減らしました。最初やりたかった複数バージョンの並列ビルドはできていません。 また、Homebrew でライブラリをアップグレードすると、php-build でビルドしたPHPが動作しなくなることもあります。2年前は5.3〜7.1まで揃えられましたが、Catalinaではまだ5.5, 5.6 のビルドができていません。 最新のmacOSで古いPHPを動かしにくくなってきている気がするので、PHPの古いバージョンを揃えたい場合は、 phpallのDocker版を作ってみた話 - hamacoの日記 のように、Docker を使った方がいいかもしれません。 明日はPlatform Devマネージャーの大窪さんとData Strategyの杉さんです。
この記事はBASE Advent Calendar 2019の15日目の記事です。 devblog.thebase.in DataStrategyの齋藤( @pigooosuke )が担当します。 ONNXの概要 Open Neural Network Exchange(ONNX)とは、機械学習モデルを表現するフォーマット形式のことです。ONNXを活用すると、PyTorch, Tensorflow, Scikit-learnなどの各種フレームワークで学習したモデルを別のフレームワークで読み込めるようになり、学習済みモデルの管理/運用が楽になります。今回の記事では、よく利用されているLightGBMモデルからONNXへの出力方法の確認と、ONNXの推論を行う実行エンジンであるONNX Runtime上での推論速度の改善がどれほどなのかを検証していきたいと思います。 https://onnx.ai 学習モデルの用意 今回は、KaggleのTitanicデータを使用して、binary classificationの予測モデルを作成します。 Dataset: https://www.kaggle.com/c/titanic import pandas as pd from sklearn.model_selection import train_test_split import lightgbm as lgb data = pd.read_csv( "path/train.csv" ) y = data[ 'Survived' ] X = data.drop([ 'Survived' , 'PassengerId' , 'Name' , 'Ticket' , 'Cabin' ], axis= 1 ) # カテゴリー変数をbooleanに展開 # 現在、LightGBMのカテゴリー変数を直接ONNXに変換することが出来ないため category_cols= X.select_dtypes( 'O' ).columns.tolist() X = pd.get_dummies(X, columns=category_cols, drop_first= True , dtype= bool ) X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size= 0.1 , random_state= 2019 ) # training train_data = lgb.Dataset(X_train, label=y_train) valid_data = lgb.Dataset(X_valid, label=y_valid) train_params = { 'task' : 'train' , 'boosting_type' : 'gbdt' , 'objective' : 'binary' , 'num_leaves' : 28 , 'learning_rate' : 0.01 , 'verbose' : 0 , } gbm = lgb.train( train_set=train_data, params=train_params, num_boost_round= 1000 , valid_sets=[train_data, valid_data], early_stopping_rounds= 10 , verbose_eval= 10 ) # Training until validation scores don't improve for 10 rounds # [10] training's binary_logloss: 0.625936 valid_1's binary_logloss: 0.582429 # [20] training's binary_logloss: 0.588612 valid_1's binary_logloss: 0.550251 # ... # [240] training's binary_logloss: 0.331639 valid_1's binary_logloss: 0.346977 # [250] training's binary_logloss: 0.327381 valid_1's binary_logloss: 0.346515 # Early stopping, best iteration is: # [248] training's binary_logloss: 0.328348 valid_1's binary_logloss: 0.346271 かなり雑ですが、モデルの用意が出来ました。 # 型の確認 X.info() # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 891 entries, 0 to 890 # Data columns (total 8 columns): # Pclass 891 non-null int64 # Age 714 non-null float64 # SibSp 891 non-null int64 # Parch 891 non-null int64 # Fare 891 non-null float64 # Sex_male 891 non-null bool # Embarked_Q 891 non-null bool # Embarked_S 891 non-null bool # dtypes: bool(3), float64(2), int64(3) # memory usage: 37.5 KB # データの確認 X.head() # Pclass Age SibSp Parch Fare Sex_male Embarked_Q Embarked_S # 0 3 22.0 1 0 7.2500 True False True # 1 1 38.0 1 0 71.2833 False False False # 2 3 26.0 0 0 7.9250 False False True # 3 1 35.0 1 0 53.1000 False False True # 4 3 35.0 0 0 8.0500 True False True ONNX変換 ONNXに変換するためには、事前にinputの型を定義する必要があります。 用意されている型は以下の通りです。 整数型: Int32TensorType, Int64TensorType 真偽型: BooleanTensorType 浮動小数数型: FloatTensorType, DoubleTensorType 文字列型: StringTensorType 辞書型: DictionaryType 配列型: SequenceType 今回は、全てnumpyのfloat32でinputを受け付けるようにします。 この設定は活用している学習モデルなどによって変わってきます。 例えば、scikit-learnのPipelineを活用して、テキスト入力をtfidfで変換する処理などを含めてONNX化したい場合は、 inputにStringTensorTypeを設定する必要があります。 参考URL: http://onnx.ai/sklearn-onnx/auto_examples/plot_tfidfvectorizer.html#tfidfvectorizer-with-onnx LightGBMをONNXに変換するために onnxmltools が必要になるので、事前にライブラリをインストールします。 https://github.com/onnx/onnxmltools import onnxmltools from onnxmltools.convert.common.data_types import FloatTensorType, BooleanTensorType, Int32TensorType, DoubleTensorType, Int64TensorType # 入力の型定義 initial_types = [[ 'inputs' , FloatTensorType([ None , len (X.columns)])]] # LightGBM to ONNX onnx_model = onnxmltools.convert_lightgbm(gbm, initial_types=initial_types) # save onnxmltools.utils.save_model(onnx_model, "lgb.onnx" ) # モデルをvizualize可能 onnxmltools.utils.visualize_model(onnx_model) inputs は、入力のラベル名です。 入力のshapeは [None, 特徴量数] のFloatTensorを指定しています。 参考までに、LightGBMのclassifierのモデルは下図のような構成になっています。(visualize_modelで生成) 入力値を決定木を通じて、予測ラベルと予測確度を出力しています。 推論 ONNX用の実行環境として、Microsoftが出しているonnxruntimeを使います。 こちらもインストールします。 https://github.com/microsoft/onnxruntime import onnxruntime session = onnxruntime.InferenceSession( "lgb.onnx" ) # 入力のラベル名の確認 print ( "input:" ) for session_input in session.get_inputs(): print (session_input.name, session_input.shape) # 出力のラベル名の確認 print ( "output:" ) for session_output in session.get_outputs(): print (session_output.name, session_output.shape) # vizualizeした図と一致 # input: # inputs [None, 8] # output: # label [None] # probabilities [] # 推論実行 preds = session.run([ "probabilities" ], { "inputs" : X_train.values[ 0 ].astype( "float32" ).reshape( 1 , - 1 )}) print (preds) # [[{0: 0.0961046814918518, 1: 0.9038953185081482}]] # LightGBMの予測 preds = gbm.predict(X_train.values[ 0 ].reshape( 1 , - 1 )) print (preds) # array([0.90389532]) 第1引数に出力ラベル名(今回はprobabilitiesのみを出力)。 第2引数に入力ラベル名と値をセットして推論を実行します。 予測結果もLightGBMの予測とONNXの予測がちゃんと一致していました。 速度計測 # onnx %%timeit -r 30 for v in X_train.values: pred = session.run([ "probabilities" ], { "inputs" : v.astype( "float32" ).reshape( 1 , - 1 )}) # 43.3 ms ± 7.86 ms per loop (mean ± std. dev. of 30 runs, 10 loops each) # lightgbm %%timeit -r 30 for v in X_train.values: pred = gbm.predict(v.reshape( 1 , - 1 )) # 84.4 ms ± 8.96 ms per loop (mean ± std. dev. of 30 runs, 10 loops each) MacOS 10.14.6 Intel Core i5 3.1 GHz python=3.7.3 numpy=1.15.2 lightgbm=2.3.1 onnx=1.6.0 onnxconverter-common=1.6.0 onnxmltools=1.6.0 onnxruntime=1.0.0 上記の条件で計測したところ、ONNXモデルはpureなLightGBMに比べて約半分ほどの時間で推論が出来ているのが確認できました。 ONNXは途中で型変換を入れているので厳密に平等な比較とは言えませんが、それでも十分早かったです。 モデルファイルサイズ計測 import pickle with open ( "lgb.pkl" , "wb" ) as f: pickle.dump(gbm, f, protocol=pickle.HIGHEST_PROTOCOL) !du -h lgb.pkl # 740K lgb.pkl !du -h lgb.onnx # 500K lgb.onnx モデルファイルサイズに関しても、pickleでの圧縮に比べ、68%まで軽量化することが出来ました。 今回は、LightGBMでの手順を確認しましたが、 https://github.com/onnx では、各種フレームワークの対応が次々に進んでいます。 独自カスタムした計算をしていない限り対応出来ると思うので、学習モデル運用でONNXを検討してみてはいかがでしょうか。 まとめ 今回、LightGBMのモデルからONNX形式でモデル出力をする手順の紹介と、ONNX上での推論速度の検証を行いました。 ONNXを利用することで学習フレームワークに依存せず、高速な推論ができる環境を作ることが出来そうですね。 明日は基盤グループの id:tenkoma さんとOwners Growthの id:MiyaMasa です!お楽しみに!
この記事はBASE Advent Calendar 2019の15日目の記事です。 こんにちは。フロントエンドグループの加藤です。 私達は、「 Payment to the People,Power to the People. 」というミッションを掲げ、日々サービスづくりを頑張っています。 Peopleとは誰か このミッションにある、 People とは誰のことを指すのでしょうか? 自分の周りの環境を想像しても、実に多様な人がいることがわかります。 また、日々ショップオーナーさんや購入者さんからいただく様々なお問い合わせの内容を見ていると、ほんとに様々な背景を持った方々に使っていただいているんだなと思います。 Webフロントエンド開発者としては、自分の力で出来ることがあれば、出来る限り多様な使われ方に対応できるプロダクトにしていきたいという思いがあります。 何を指針とするか では、まず何をどうすればいいのでしょうか。よくわかりません。 調べると、どうやらWeb技術の標準化を行う非営利団体であるW3C(World Wide Web Consortium)が勧告しているガイドラインが存在するようです。 Web Content Accessibility Guidelines (WCAG) 2.0 また、WCAG2.0に関しては実際に対応する時に参考にできる解説書もありました。どちらも日本語化されています。大変ありがたいです。 WCAG 2.0 解説書 細かく言うと、この項目をどれだけ対応するかによってレベルA~AAAなどのレベル付けがあるようです。今回は絶対どのレベルを厳守するんだ!というよりも、自分の中で実装時に意識すべきことを掴みたい、まずは慣れたい、といった動機で始めているので、まずはAの中でも対応できそうなものからやってみたいと思います。 練習してみよう ということで、実際に弊社でも利用しているフレームワーク、Vue.jsを使って、かんたんなウェブページをよりアクセシブルにしていく素振りをしてみます。 まずは何も考えずに作っていく 作るものはなんでもよいので、「自由に投稿できる動物ずかん」をイメージして作ります。一覧画面と詳細ページ、そしてモーダルで開いて入力する画面があるとしましょう。 できました。 CodeSandboxのリンク 特筆すべき点はないですが、 モーダルとボタンをそれぞれ共通のコンポーネントとして切り出した vue-routerを利用してクライアントで一覧と詳細画面をそれぞれルーティングさせている というところで、非常に簡単ではありますが実際のフロントエンド開発でよくあるシーンを再現してみました。(今回はVue.jsやその他のライブラリの詳しい説明に関しては省きます) 課題を発見していこう とにかく動くものを作ったのですが、そもそも課題が何なのかわかっていません。 今回はWCAG2.0をバイブルとして進めていくので、一つ一つ目を通して、これは守れてないなと思ったものを地道にクリアしていくことにしましょう。 原則 1: 知覚可能 - 情報及びユーザインタフェース コンポーネントは、利用者が知覚できる方法で利用者に提示可能でなければならない。 1.1.1 非テキストコンテンツ: 利用者に提示されるすべての 非テキストコンテンツ には、同等の目的を果たす テキストによる代替 が提供されている。 ただし、次の場合は除く (!) コントロール、入力: 非テキストコンテンツが、コントロール又は利用者の入力を受け付けるものであるとき、その目的を説明する 名前 (name) を提供している。 これは、ひとまず作ったボタンコンポーネントに問題があります。 <template> <div @click="$emit('click')" class="button"> <slot></slot> </div> </template> divですね。 コントロール又は利用者の入力を受け付けるもの だと全く伝わりません。初歩的ですが、気を抜くと似たようなことはよくやってしまいます。 この項目に リンクされている解説書の項目 では、 アクセシブルなウェブコンテンツ技術の標準コントロールを使用する場合、このプロセスは簡単である。ユーザインタフェース要素が仕様に準じて使用される場合、この条件に条項は満たされる。 とされており、セマンティックなマークアップを守れば特別な工夫をせずとも条件を満たすことができるようです。以下のようにしてみました。 <template> <button @click="$emit('click')" class="button"> <slot></slot> </button> </template> よさそう。しかしこれもまだ問題があります。 これはアクセシビリティの問題ではなく、このボタンというコンポーネントは、機能としてのボタンを切り出したいのではなく、単なるプレゼンテーションとしてのボタンぽい見た目をただ切り出したいのです。ボタンの見た目を提供するのに、buttonタグとしてしか使えないと、使われる文脈によっては正しくないマークアップを強制してしまう可能性があります。 ここは、デフォルトはbuttonタグで、必要に応じてprops経由でタグを指定出来るようにしてみましょう。こういったケース(ルートエレメントを動的に変えたい)は、jsxで書くことで実現できます。詳しくは、 弊社松原のVue.js+JSX基本文法最速入門 という記事を見ていただくとよく理解できるかと思います。 export default { props: { tag: { type: String, default: "button" } }, render(h, context) { const tag = this.$props.tag; return ( <tag {...this.$attrs} class="button" onClick={() => this.$emit("click")}> {this.$slots.default} </tag> ); } }; こうすることで、以下のようにaタグをボタンにしたい場合でも対応することが可能になりました。 <custom-button tag="a" href="/hoge" /> また、実はもう一つ課題があります(インタラクティブな要素の実装は本当に大変ですね)。 よくあるケースなのですが、マウスでクリックしたあと、その要素がフォーカスされるので、フォーカスインジケータが表示されるのですが、これをポインティングデバイスによるフォーカスでは出さないようにしたいのです。 しかし、これを素朴に .button:focus{outline: none;} としてしまうと、ポインティングデバイス以外の入力によってフォーカスされた場合、視覚でそのことを伝えることができません。先程の達成基準 4.1.2 の解説にも 特に重要なユーザインタフェース コントロールの状態は、フォーカスを持つかどうかである。 とありますが、マウス以外の入力では、フォーカスされた状態をユーザーに伝えることは非常に重要そうです。 幸いにも、CSSの *:focus-visible 疑似クラスによってこれは達成できます。これは、ユーザーエージェントが要素にフォーカスを明示するべきであるとした場合にのみスタイルを適用することが出来る便利な擬似クラスです。しかしながら、まだこれは草案の段階であり、ほとんどのブラウザで実装されていませんので、今回はpolyfill( focus-visible )を導入し、該当のフォーカス時にのみ要素に適用されるdata属性に対してフォーカスのスタイルを当ててみましょう。 .button:focus { outline: none; } .button[data-focus-visible-added] { outline: 2px solid #000; } これでbuttonコンポーネントは一旦大丈夫そうです。 tableタグ captionをつける 今回、動物の一覧を並べるのにtableタグを使用しました。CSSの表現力が高まるにつれて使う機会が徐々に減ってはいますが、管理画面などで一覧性を担保しながら要素を表に並べるという用途では未だによく使うタグでもあります。 今回は以下の達成方法にある、caption要素を使用するとより何の表なのかがわかりやすいのではないかと思いました。 H39: データテーブルのキャプションとデータテーブルを関連付けるために、caption 要素を使用する | WCAG 2.0 達成方法集 行全体をクリッカブルにする <tbody> <tr :key="animal.id" v-for="animal in animals" @click="onRowClick(animal.id)"> <td>{{ animal.name }}</td> <td>{{ animal.emoji }}</td> </tr> </tbody> 行全体をクリッカブルにして、押されたらその行を詳しく見る/操作できる詳細画面が開く、ようなアプリケーションはよくあると思いますが、今回もそうしてみました。ただ、trのクリックイベントでページ遷移させていて、先ほどのようにセマンティックなマークアップでないがために、押せることもわからないし、キーボードで操作することもできません。困った。 一度、全体をクリッカブルにしたい!というところから一歩引いて、そもそもこれは何ができればいいのかを考えてみます。詳細ページに飛ばしたいんですよね。であれば、aタグでマークアップされるべきです。ただ今回はtableでマークアップしていて、trをaタグで囲うことができません。ただ、cellの中には当然aタグを置くことはできます。という発想から、以下のようにしてみました。 <table class="table"> <caption>現在登録されている動物の一覧</caption> <thead> <tr> <td>名前</td> <td>emoji</td> <td class="util-hidden">リンク</td> </tr> </thead> <tbody> <tr class="row" :key="animal.id" v-for="animal in animals" @click="onRowClick(animal.id)"> <td>{{ animal.name }}</td> <td>{{ animal.emoji }}</td> <td class="util-hidden"> <router-link :to="{name: 'detail', params: { id: animal.id }}">{{ animal.name }}の詳細を見る</router-link> </td> </tr> </tbody> </table> export default { // ... methods: { onRowClick: function(id) { this.$router.push({ name: "detail", params: { id } }); } } }; .row { cursor: pointer; } .row:hover { background-color: #ddd; } .row:focus-within { background-color: #ddd; } .util-hidden { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); } 思い切って、テキストリンクを配置した列を新たに追加しました。ただ、その列を表示上は非表示にします。( .util-hidden という名前のユーティリティクラスを付与していますが、これはアクセシビリティの対応でよく使われるCSSです。スクリーンリーダーなどの支援技術で利用してもらいたいのですが、視覚上からは非表示にしたほうが都合が良いケースで使われます。) これにより、リンクにキーボードでフォーカスすること自体は可能になりました。その際に視覚上で行全体のフォーカスを表現するため、focus-within疑似クラスを使用します。これは、内包する要素がフォーカスされていた場合にスタイルを適用することが出来る便利な擬似クラスです(これもfocus-visibleと同じように全てのブラウザで対応しているわけではないので、 focus-within-polyfill を利用しています)。 trタグにfocus-withinで内包する要素がフォーカスされている場合にスタイルを適用することにより、キーボードでも現在フォーカスしている行を視認しながら移動することが可能になりました。 最後に、マウスでクリックできることを表現するために、hover擬似クラスでカーソルをpointerに設定し、trがクリックされたら、詳細画面へのルーティングを実行するようにします。 こんな感じでしょうか…? モーダルダイアログ モーダルはWCAGを見る前から何かあるだろうな…と思いましたが非常に問題が多そうです。ただ、モーダルをアクセシブルに作る方法はある程度約束事が決まっており、少し前ですが ヤフー株式会社の福本さんによるスライド がわかりやすく非常に参考になります。 端的に言えば以下の対応になります。 マシンリーダブルなコードにするため、WAI-ARIA属性によりマークアップの情報を増やす キーボード操作に対応する フォーカストラップを実装する escキーで閉じれるようにする 開いた際に最初のインタラクティブな要素にフォーカスを移す 閉じた際に元々フォーカスしていた要素にフォーカスを戻す まるっと勢いで実装してみます。 マシンリーダブルなコードにするため、WAI-ARIA属性によりマークアップの情報を増やす <div role="dialog" aria-modal="true" :aria-labelledby="titleId" :data-show="`${show}`" @click="$emit('cancel')" class="wrapper" > </div> --- props: { titleId: String, rootId: String }, methods: { onShow: function() { this.rootId && document.getElementById(this.rootId).setAttribute('aria-hidden', true); }, onHide: function() { this.rootId && document.getElementById(this.rootId).setAttribute('aria-hidden', false); }, role属性によりダイアログであることと、aria-modal属性により現在のダイアログの下にあるウィンドウは不活性であることを支援技術に伝えます。 また、モーダルダイアログのタイトルとなる要素のidと、本文のコンテンツをラップしている要素のidをprops経由で渡します。前者はaria-labelledbyでモーダルコンテンツのタイトルを伝え、後者はモーダルを開いている際に、裏側のメインコンテンツが非表示になっていることを伝えるために使用します。 開いた際に最初のインタラクティブな要素にフォーカスを移す 今回は、props経由で最初にフォーカスすべき要素のidを受け取るシンプルな作りにしました。 props: { //... initialFocus: String }, methods: { onShow: function() { this.initialFocus && document.getElementById(this.initialFocus).focus() }, //... ---- <modal initialFocus="animal-name" :show="modalShow" @cancel="closeModal"> 閉じた際にフォーカスを戻す data: function() { return { lastActiveElement: null }; }, watch: { show: function(next) { if (next === true) { this.onShow(); } else { this.onHide(); } } }, methods: { onShow: function() { this.lastActiveElement = document.activeElement; }, onHide: function() { this.lastActiveElement && this.lastActiveElement.focus(); }, フォーカストラップを実装する フォーカストラップとは、特にモーダルな状態においてそのコンテンツ内でフォーカスをループできるようにすることで、コンテンツ内での操作性を向上させるための機能です。 今回は実装についての詳細な説明を省きたいので、 vue-focus-lock というライブラリの力を借りました。モーダルのコンテンツを包むだけで上記の機能を実現してくれます。 <template> <div :data-show="`${show}`" @click="$emit('cancel')" class="wrapper"> <div class="contents" @click.stop> <focus-lock> <slot></slot> </focus-lock> </div> </div> </template> escキーで閉じれるようにする props: { escExit: { type: Boolean, default: true } }, methods: { onShow: function() { document.addEventListener("keydown", this.checkKeyDown); }, onHide: function() { document.removeEventListener("keydown", this.checkKeyDown); }, checkKeyDown: function(event) { if ( this.escExit && (event.key === "Escape" || event.key === "Esc" || event.keyCode === 27) ) { this.$emit("cancel"); } } } キーコードを愚直に見て、エスケープキーであれば親にキャンセルイベントをemitします。 完成 完成版のCodeSandboxのリンク 振り返って 率直に言えば、普段何気なく実装している機能でも、まだまだやれることがたくさんあったというのは技術者としては少しショックではありましたし、単純に時間がとてもかかったので、普段の開発でどのようにこういった取り組みを持続的に進めていくかは、深く考える必要があると感じました。 ただ、とても良いと感じたことが3つあります。 コンテンツやサービスの中身が何であるか/どうあるべきかを深く考えるきっかけとなる 実装の手法以前に、そもそもこのコンテンツはどういったユーザーがアクセスし、どういった特性があるのかなど、アクセシビリティのガイドラインを意識しよう/セマンティックなマークアップを実現しようと思うと、それを深く考えることを避けて通れなくなるように感じました。これはとてもよいことではないでしょうか。 結果的にどんな人にとっても使いやすい機能やサービスになる より多様な使われ方に対応していく過程の中で、「これをこうすることでこういう使われ方をした際の利便性が下がる」といったトレードオフが発生したケースは今回一つもなく、これからもなさそうに思いました。それどころか、今まで対応してきた使われ方での利便性もより向上しています(例えば、モーダルダイアログを開いた際に最初のinput要素にフォーカスを当てるようにしましたが、これはどんな使われ方をされたとしてもすぐ入力できるので使いやすくなっています)。 今回はあまり触れませんでしたが、カラーコントラストや、テキストの表現の仕方など、基本的にはどのようなユーザーにとってもよりわかりやすい/使いやすいものになるような改善もまだまだたくさんありそうです。 インターネットっぽくていい どんな人がどんな使い方をしても、平等に情報やサービスにアクセスできるというのはとてもインターネットっぽい感じがあります。 最後に まだまだ課題は多くありますが、会社のミッションを実現できるサービスづくりを進めていくために、引き続きできることからやっていきたいと思います。もしご興味ある方は、まずは素振りから始めてみてはいかがでしょうか。 参考 最後に、参考となった記事を羅列になりますが以下に記します。 https://waic.jp/docs/WCAG20/Overview.html https://waic.jp/docs/UNDERSTANDING-WCAG20/Overview.html https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html https://a11yproject.com/ https://qiita.com/simezi9/items/ec9dfbb3c7af09088898 https://www.slideshare.net/techblogyahoo/scripty05 https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html 明日は基盤グループの田中さんとOwners Growthの宮川さんです。お楽しみに!
本稿は BASE Advent Calendar 2019 🎅🎄 の14日目です! こんにちは、Product Divisionの金城( @o0h_ )と申します。 10月にBASEに入社しまして、ブログ投稿は本稿が初となります。よろしくおねがいします。 さて、早速掲題の件に入らせていただきます。 PSR, PHP-FIG 「PSRはPHP-FIGが策定し運用する一連の規格であり、その19号はPHPDoc Tagsについての決め事」です。 PSRには色々なレイヤーのルールが ─時には 「本来の主旨を超えている」という批判 をもさえ巻き起こしながら─ 策定されています。 1 PHP-FIGは、元々はオートローディング活用のための取り組み 2 を契機として作られた団体です 3 。つまり、元来から「PHPの書き方」に強い関心を持っていたとも言えます。 PSRの「0番目がAutoloading Standard」という事だけに留まらず、「続く1番目はBasic Coding Standard」「2番目がCoding Style Guide」であることからも「初期にフォーカスしていた領域がどこであるか?」というのを伺い知れるのではないでしょうか。 更に言い加えると、PSR-0,2のみが現状でDeprecatedステータスに配置されています 4 。 すなわち、「より洗練した規格を後から作り、採択している」のが、AutoloadingとConding Styleに関する話題であるという事です。 5 そして、2019年12月現在で「Draft」にあるのが「PSR-5: PHPDoc Standard」と「PSR-19: PHPDoc tags」です。 ある意味で「実装のし易さ」と同じくらいに「コードを読みやすく・違和感なく」運用していく事に重きを置いていた団体が、「doccomment/tagの書き方について未だに標準化できていない」・・というのは、ちょっと面白いと思いませんか?一体どんな経緯があったのでしょうね。 そんな訳で、本稿では「PSR-19なんなのよ」という事について見ていきたいと思います。 PHPと「doc comment」 一昔前まで、PHPでdoc commentというと「いろんな方言がある」という印象を抱いた方も多いと思います。私もその1人です。 (以下、doc commentについて「PHPDoc」 6 と表記することとします。) 「phpDocumentorのそれに従おう」「いやPhpStormに合わせりゃよくない?」「Phanが」「PHPStanが」といったソレです。 もちろん、それぞれ目的が違うものであり、自分達の目的を果たすために独自の拡張的な記法なども導入していく事には必然性があります。完全に統一され得るものではないでしょう。・・とはいえ、いち利用者的な観点からすると「似たような事は同じ書き方で出来ればいいのにな」と思う訳です。まさに「相互運用性のための決まり事」があると良かろうなぁという場面で、PSRに目が向きます。 なるほど、それがPSR-5です。 しかしこのPSR-5、提案自体は数年前になされたものの、「広範に支持されるような勧告」といった地位にまでは至りませんでした。結局は「色々な書き方がある内のいち方言」のような雰囲気のままです。そうして遂には議論が停止、草案は「STATUS:ABANDONED」 7 に・・・・ なったのですが、これが再提案されて「DRAFT」化 8 、今に至ります。そういった訳で「5番」などという非常に若い番号が未だに議論されている訳ですね。 そして新生PSR-5はEditor/Working group membersとして「phpDocumentetor」「PhpStorm」「Psalm」「PHPStan」の関係者をそれぞれ迎えています。これは現実的な落とし所を見つけつつ、各ツールのサポートの観点からも実用性を伴う状況が実現できるのではないでしょうか。期待ですね! 昨年時点でのPSR-5の状況については、こちらの記事に大変よくまとまっておりました。ご参照ください。 qiita.com PSR-19 PSR-5の「復活」にあたって、一緒に提出されているのがPSR-19です。 これは、PSR-5にまとまっていた内容を2つに分けようという提案 9 に基づくものになります。 PSR-5についてはPHPDocのフォーマットを、PSR-19についてはタグの種類を決める内容になります。二者が分割された動機については「PHPdocのフォーマット(PSR-5)を一般的な内容にしつつ、タグの内容(PSR-19)は拡充しやすくする。また、将来的にはPHPDoc外でも利用可能になることを見越して」と説明されています。 The rationale behind the splitting is to make it easier to add new tag dictionary PSRs in the future and also make PHPDoc more generic so it can be used for non-docblock standard such as Doctrine-style annotations. 例として Doctrine-style annotations が挙げられていますが、例えば <?php /** * @MyAnnotation(myProperty="value") */ private $ bar ; のようなものでしょうか。 ということで、本稿は(既にその内容には見慣れているであろうPSR-5よりも)PSR-19の中身はどうなっているのかな?と興味を持って調べてみるものです。 中身を見てみる 現時点でのPSR-19のproposalはこちらから確認できます。 fig-standards/phpdoc-tags.md at 2668020622d9d9eaf11d403bc1d26664dfc3ef8e · php-fig/fig-standards · GitHub また、GitHub上での議論(PR)についてはラベルで辿ってみるのが良いでしょう。 Pull Requests · php-fig/fig-standards · GitHub 中身を見てみると、5つのセクションに分かれています。 PHPDoc standard(PSR-5)を補完するのが主目的であること MUSTやSHOULDなどの語についてはRFC 2119に準ずること (いつものやつですね) 用語の定義等はPSR-5に準ずること inheritDocの扱いについて 改変無しで全体を引き継ぐ場合は @inheritDoc を、インラインで埋め込む場合は {@inheritDoc} を用いることなど そして第5セクションが「タグ」の定義です。 現時点では以下のリストが掲載されています @api @author @copyright @deprecated @internal @link @method @package @param @property @return @see @since @throws @todo @uses @var @version このうち、 @method @param @property @return @throws @var などは、すっかりお馴染みなのではないでしょうか。クラスやメソッドの機能に関連するもので、PhpStormなどのIDEやPHPStan・Phanといった静的解析を利用している場合には欠かせません。 残る内容について、その書式や意味を掻い摘んで見ていきましょう。 独断にて、関連するタグごとに以下の4つの群にまとめながら取り上げていきます。 利用者の想定・提供範囲に関わるもの 著作者・権利に関わるもの 関連情報・参照情報に関わるもの バージョン・更新に関わるもの 利用者の想定・提供範囲に関わるもの 論理的な空間・括りと、誰に向けて提供しているものか?に関する情報です @package @package は、「論理上の区分」を示すために用いるものと説明されています。 PHPの持つ言語機能であるNamespaceと近い概念を取り扱うものであり、実際に「階層は(Namespaceと同様に) \ で区切って記述する」と規定していたり、「Namespaceと完全に一致する場合に@packageタグを利用することは推奨しない」と言及されています。 あくまで Namespaceで表現される「機能的な区分」とは別に、論理上の区分を明らかにしておきたいという場合に利用される ものになります。 @api @api は、 「primary public API of a package」に付与されるものと説明されています。 publicなメソッドの中でも、 そのパッケージ外から利用される ようなものを区別するためのものです。 @internal @internal は、@apiと対象的に「only for use within the application, library or package to which it belongs.」と説明されています。 「primary public API of a package」に付与されるものと説明されています。 このタグが規定されている動機についても説明していて、 1つには「ライブラリの作者が、 ユーザーに対して"破壊的な変更を含む可能性がある"ということを示す ため」 2つには「静的解析時に、 パッケージ外からの利用を警告できるようにする ため」 というポイントを取り上げています。 実際に、PhpStormでは違反的な利用に対して警告を出すようになっています。 著作者・権利に関わるもの 該当箇所に関する権利等に関する情報です @author, @copyright @author は、その作者や重大な改修を行った人を示すものと説明されています。 @copyright はそのコードに対する著作権と説明されています。 (特異な内容はないので、あとは割愛) 関連情報・参照情報に関わるもの 該当箇所の実装の理解を助ける情報です @link @link は関連する情報を示すものと説明されています。 ( a custom relation between the associated "Structural Element" and a website, which is identified by an absolute URI. と書かれていますが、ここは「Webサイト」というより「情報の掲載されているWebサイト」といったニュアンスで解釈するほうが意味する所に近いのかな?と思います) @linkタグを利用する場合には、絶対URIで示すように指示されています。 また、インラインでの利用も想定されています。 <?php /** * This method counts the occurrences of Foo. * * When no more Foo ( {@link http://example.com/my/bar} ) are given this * function will add one as there must always be one Foo. * * @return int Indicates the number of items. */ function count () { <...> } @see @see は関連するリソースを示すものと説明されています。 @linkはURIだったのに対し、こちらはURI以外のリソースを利用することも可能 です。例えば他のクラスやメソッドがあり、そのような要素を指す場合には「FQSEN」を利用するようにと規定されています 10 。 また、関連先のリソースとの関連性(=なぜ see なのか?)という説明を載せるべきだとされています(SHOULD)。 @link が対象リソースの説明について「してもよい」とされているのに対して( The @link tag MAY have a description )、こちらが「べき」なのは、「ドキュメント」以外のリソースも扱えるからでしょうか。 @uses @uses は利用される要素やファイル名を示すものと説明されています。 ただし、記述できるのは同じプロジェクト内のリソースに限るとされています。そのために、URI等の利用は禁止です。(システム外部のリソースに言及する場合は、 @see を利用することが出来ます) 例として、「コントローラーのメソッドに対して、描画されるView/templateファイル名を示す」といった内容が挙げられています。 これは、どういった場合に嬉しいのでしょうか? 例えば 動的な関数の呼び出しを行っている場合やマジックメソッド経由で利用しているメソッド について、@usesを用いてエディタや静的解析ツールに「実際に使っているよ!」ということを知らせることが出来ます。 余談ですが、 @used-by については、現時点ではPSR上での規定はありませんでした。 バージョン・更新に関わるもの 現行の状態や更新履歴といった情報です @version @version は、現在のバージョン情報とそのバージョンで提供された内容を示すものと説明されています。 セマンティックバージョンを利用して言及すること、説明文を加えることを推奨されています(RECOMMENDED)。 @since @since は提供が開始された(もしくは内容が現行の状態に変更された)バージョンを示すものと説明されています。 (@versionと同様に)セマンティックバージョンを利用して言及すること、説明文を加えることを推奨されています(RECOMMENDED)。 例えば「2.0.4から、第3引数が追加された」といった内容などは@sinceで示すのが良さそうです。 「提供のタイミング」「バージョニング」に関わるということで、@versionと混同することもあるかもしれませんが、 「今がどのバージョンであるか」といった情報には@sinceによって提供されるべきではない(@versionを利用する) と説明されています。 @deprecated @deprecated は廃止予定・将来のバージョンにて削除されるものを示すものと説明されています。 廃止予定とされた理由について、説明文を加えることも可能とされています(MAY)。また、代替となる手段が用意されている場合には @see タグによって示すことが推奨されています(RECOMMENDED) @todo @todo は実施される(開発・改修される)べきことを示すものと説明されています。 このタグには必ず説明を入れなければならない(MUST)とのことです。 (todoが「バージョンに関するもの」か?というと、「将来的な変更に関するもの」という意味では、この群に属するのではないでしょうか) その他の提案内容について ここに列挙したタグは既にproposalにコミットされているもので、ある程度の同意を得たものという扱いになります。 もちろん、「今はリストに含まれている」というだけであり、最終的には削除されるかもしれないし他のタグが追加される可能性もあります。(そもそもPSR19自体が採択されていないですからね) また、興味深い議論としては、PSR-5の領域ですが PR#1191にて「 no-return type」についての提案がなされています。 github.com これは、TypeScriptでいう never に相当するもので、exit()やtrigger_error()の発火により「呼び出し元に処理を戻さない」性質を説明するものです。PHPStanやPsalmでも、それぞれ @return never , @return no-return という表記によって扱われています。 PSR-19についても、いくつかのタグの追加に関するPRが作成されている状況です。 まとめ ざっくりと、現時点でPSR-19として規定されているPHPDocのタグについて見ていきました。 多くのタグは「定番化」しているもので、既存のコードに対して衝撃を与える事はないように思います。実際に、PSR自らがInterfaceの定義等において既に利用している状況が見受けられます。 PSR自体は決して「PHPを書く際に従わなければならない」ものではなく、「フレームワークやツールを作る時に準拠しておくことで、何か良いことがあると良いよね」程度のものです。そのため、実質的なデファクトスタンダード(今だとphpDocumentorになるでしょうか)が存在すれば、大きく困ることもありません。実際に、過去に議論が停滞したのも「困らなかったから」という側面はあるでしょう。 ですが、個人的には、先述の通り「PSR自体がPHPDocを利用している」こともあり、やはり明確な定義を持つべきなのではないかな?と考えています。 繰り返しになりますが、PSR-5,PSR-19はその内容がまだ確定しておらず、現在進行系で議論がなされている状況にあります。なので、ここにまとめた内容も採択時には変更されているかもしれません。 それでも、その内容や議論に興味を持つことは、今っぽいPHPを考える上で多少なり意味のあることではないかと考えます。 PSRはその性質的に「最大公約数」を示す力も持っていると捉えているからです。 皆さんもぜひ、PSR-5/PSR-19の内容を眺めながら「ドキュメント性能の高いソースコード」について想いを馳せてみるのはいかがでしょうか! ここまで読んでいただきありがとうございました。 明日は、Data Strategy Groupの齋藤とFrontend Group加藤が更新予定です!✨ 前身となる「PHP Standards Group」は「PHP本体への提案」を目指して結成されたグループです。しかし「標準化」に際しては考慮すべき点が多く困難性があったため、「より柔軟な提案をするために、言語仕様そのものではなく、その上に乗るプラクティスとして規約を求めよう」と方向転換をして今のPHP-FIGが生まれました。そのため、「広い範囲を扱う」というのは、サジ加減はあれど、FIGにおける本質的な振る舞いだと私は解釈しています。 ↩ https://wiki.php.net/rfc/splclassloader ↩ 歴史については、こちらの記事に詳しいです The Past, Present and Future of the PHP-FIG — SitePoint ↩ PSRのステータスや議論の進行については、 https://www.php-fig.org/bylaws/psr-workflow/ をご覧ください ↩ と言いつつ、「PSR-3を踏まえて簡素化した(利便性を高めた)PSR-16:Simple Cache」や「PSR-7をコアとした一連のHTTP関連Interface(15,17,18)」もあるので、「特定の領域について深める・広める」という事自体は他にも積極的に行われています。 ↩ https://github.com/php-fig/fig-standards/blob/2668020622d9d9eaf11d403bc1d26664dfc3ef8e/proposed/phpdoc.md#3-definitions ↩ GItHub上でステータスを変更されたソースはこちらです https://github.com/mcneely/fig-standards/pull/1 ↩ https://github.com/php-fig/fig-standards/pull/1078 ↩ https://groups.google.com/d/msg/php-fig/5Yd0XGd349Q/w-uTRA2nEgAJ ↩ FQSENについては、PSR-5を参照してください https://github.com/Chofoteddy/fig-standards/blob/e89924d320c269678fe3f5822c9bf9ef95db1af0/proposed/phpdoc.md#3-definitions ↩
はじめまして、Owners Marketingグループの小林です。 Owners Marketingグループは、ショップオーナーさん向けに新規機能などを常に開発しているエンジニアのチームで、最近ではネットショップ作成サービス「BASE」の拡張機能である「BASE Apps」のInstagram販売App、予約販売Appや顧客管理Appなどを開発/リリースしました。 アドベントカレンダー14日目のこちらの記事では、BASE社内の部活動のひとつ、ランニング部の活動について紹介したいと思います。 devblog.thebase.in BASEの部活動 BASE社内には多くの部活動があり、5名以上のメンバーが集まれば、部活動として認定され、会社から活動費の一部を補助する制度があります。 (ランニング部以外に社内にある部活動) ビール部/ヨーガ部/日本酒部/筋トレ部/ボードゲーム部/ボルダリング部/サウナ部/LLVM部など 社内にあるコミュニティは多種多様で気軽に参加できるため、仕事ではあまり関わりのない人とも気軽にコミュニケーションできる場として、積極的に活用されている印象です。 過去には、ボードゲーム部が、ランサーズさんと合同大会などを実施し、社外交流の場としても活用されています。 basebook.binc.jp ランニング部 ランニング部では日々の活動として、週一回、仕事後にオフィスの近く走る活動を開催しています。皇居一周をメインコースとして、その日の参加メンバーの走力や体調に合わせて、6〜13kmくらいの距離を走っています。 ランニング部に参加する前まで、日々の業務では座りっぱなしで運動不足を感じる事が多く、また「運動したい。走りたい。」と思ってもなかなか継続できず三日坊主になりがちでした。ランニング部は、雨で中止になる日もあるのですが、基本的には毎週活動しているため、継続的に運動できる環境になり運動不足が解消できると思い、参加していました。 皇居周回コース 有名な皇居ラン。 オフィスがある六本木グランドタワーから皇居に向かい、皇居の周りを1周してグランドタワーまで戻るコースです。 皇居周辺は多少のアップダウンがあり、1周5kmで信号で止まる事がなく走りやすいコースです。 桜の時期では、夜桜を楽しみながら走れます。 マラソン大会 私がランニング部に参加し始めて1年半ですが、週一回の活動以外に、休日に開催されるマラソン大会に何度か出場してきました。それまでマラソン大会に参加したことがなかったので、最初は10kmのマラソン大会に出場することにしました。 10kmという目標と他のメンバーに負けたくないという気持ちもあり、週一回の活動以外でも、仕事後や休日にも自主練習。 日々のランニングとは違いマラソン大会に出場することで、各メンバーの目標設定ができ、Slack上でもランニング部のチャンネルでは話が尽きません。 小さい規模のマラソン大会でも、ゴールするとメンバー全員に達成感があり、大会後の打ち上げでは一体感が生まれます。 ただ、大会が終わるとすぐに次の目標を設定したくなり、終了後すぐに次の大会に申込みをするを繰り返し、出場を重ねる毎に自然と距離を延ばしてきました。 以下、私が出場したマラソン大会 2018年10月21日 10km 2018年10月27日 16km 2019年01月20日 21km 2019年06月23日 30km 2019年09月28日 21km 2019年11月10日 42.195km 2019年11月17日 42.195km 2019年12月09日 42.195km 2019年11月10日に参加した横浜マラソンでは、高速道路の一部区間が一部規制となり、高速道路を走る事ができました。奥にはベイブリッジも見えてとても気持ちがよかったです。 まとめ 仕事を一生懸命するのは勿論ですが、職場のメンバーと他の事にも挑戦できる環境であることは素晴らしいと思います。 最初は運動不足解消やコミュニケーションの場として、ランニング部を活用していましたが、少しずつ小さな達成を積み重ね、いつのまにかフルマラソンを走れるくらいの走力がついていました。 何事にも当てはまると思いますが、 THERE IS NO FINISH LINE. (そこにゴールはありません)大会後の打ち上げを楽しみつつ、継続した挑戦をしていきたいと思います。 最後まで読んでいただきありがとうございました! 明日は、Frontendグループの加藤さんとData Strategyチームの齋藤さんです!お楽しみに。
この記事はBASE Advent Calendar 2019 13日目の記事です。 devblog.thebase.in こんにちは。UIデザイナーの野村です。 2019年4月にBASEに入社し、主にショップオーナーさんが使う管理画面のUIデザインに携わっています。 デザインワークの下敷きになるようなトライをいくつか行っておりまして、今回はそのあたりの話をさせていただきたいと思います。 [これまでに実施したトライ] 2度のデザイン思考ワークショップ 非デザイナー向けのデザインツール講習会 サービスデザインの勉強会で組織デザインについて学ぶ(社外) 開発プロジェクト内での実践(途中) なぜそんなトライをしてるのか、という話 受託デザインの世界からインハウスへ移行して 私はBASE入社前はデザイン制作会社に勤めたりフリーランスで働いたりしていたのですが、携わった業務はほぼ全て受託案件でした。 業務委託の形で事業会社内で勤めたことはあったものの、インハウスデザイナーとしての経験はとても浅い状態で入社して参りました。 受託での仕事と、事業会社内(インハウス)でのデザインワークには色々と違いがあります。 受託デザイナーをしていると、基本的には「お客さん(依頼者)が提示する情報をベースにモノを作る」という形をとります。 実際のところ、プロジェクト開始段階でお客さんから十分な情報を提示されることは稀でして、追加で色々と調査・検討を行うこととなります。 もらった情報を精査した上で、不足を補うためにお客さんにヒヤリングをしたり、内外への調査を提案し計画したり、技術や周辺事情についての勉強会や講習会を行ったり... などなど、良いアウトプットに到達するための施策を打っていくわけですが、とはいえ基本的には情報収拾と取りまとめはお客さん側で担当することが主で、受託デザイナーとしての自分は「 お客さんの情報収拾活動をサポートしつつ、制作に注力する 」というのが基本スタンスとなります。 そうなると、情報の深いところまではイマイチ理解が浅いまま納期優先で業務を進行することになったりするのですが、それは「 仕方ないこと 」と飲み込んできました。 インハウスデザイナーとして仕事をしていると、自ら社内で動いて事業やユーザの深い情報を掻き集めながらプロダクトを作っていくことができます。 マネージャーが取りまとめてくれた情報のみから業務を進めていくことも可能ではありますが、社内の各部署で持っている知見や体験を掻き集めて自ら消化・整理して業務に反映することで、アウトプットの質は目に見えて向上します。大変ではありますが、インハウスデザイナーとして働くにあたっての面白いポイントの1つです。 効率よく、かつ深く情報を共有するには 自ら組織内のナレッジ掻き集めようとした時、新参者は 誰がどんな知識をもっているかわからない という事態に陥りがちです。 それらはドキュメントとして蓄積されてたりしますが、全てが明文化されているわけではありません。 また、ドキュメントには文章や図の形で知識を溜めていくことはできますが、そこに各人の想いや体験(ひっくるめて ナラティブ 、と呼ぶことにします)まで込めるのは難しいです。文章書くの苦手な人も居ますし。 ナレッジは有形(文章・図)化して蓄積可能だけど、ナラティブは無形のまま流動し続けることしかできない、とでも申しましょうか。 そして、ナラティブのようなバックグラウンドを踏まえてないと、せっかくの知識もその真の意味、深い意味が抜け落ちた上っ面だけのものになってしまうことがあります。非常に勿体ない事態ですね。 そんな想いから、部署の垣根を超えて各人のナラティブをそれとなく共有できるような、そんな場を組織内に作れないものか、と考えました。 プライベートなことを掘り下げようというものでなく、仕事やプロダクトについてのナラティブを共有するのが主なので、 プロダクトの企画をテーマにしたワークショップ をするのが良いかな、という思いに至り、企画と実施をしました。他、非デザイナー向けにデザインツールの講習会をしたり、デザイン組織醸成に関する社外の勉強会に出たり、といったトライをしました。 2度のワークショップ 「デザイン思考を体験するワークショップ」という名目で、2時間程度のショートワークショップを社内で2度実施しました。2回とも、参加人数は10名前後の、小規模なものです。 第1回ワークショップ まずはやってみよう、ということで7月にパイロット版的な形で実施しました。 内容としては、私が過去に参加した1DAYワークショップを下敷きに、2時間程度の尺で収まるよう圧縮したものです。 いくらかバタついたものの、10名強の参加者には楽しんでいただけたようで、パイロット版としては成功を納めたと思っています。 その時の様子は こちら 。 第2回ワークショップ 第2回のワークショップは、流れとしては概ね第1回と同じなのですが、第1回での反省点や、いただいたフィードバックなどを踏まえ、かつ段取りのバタついた箇所を整理するなど、いくつかのアレンジをしました。 その時の様子は以下の通り↓ ワークショップ振り返り 2回のワークショップの後には、参加者からはそれなりにポジティブなフィードバックをいただけました。(「楽しかった」「普段話せない人と話せてよかった」「良い頭の運動になった」など) 90〜120分程度のワークショップをスムーズに進めるための段取りも作れたと思っています。 自身のファシリテーション力向上にもつながり、良いトライができました。 他、ナラティブ共有についてのトライ 「他部署メンバーのナラティブを得る」という主旨からは少々逸れるのですが、以下のようなトライを行いました。 非デザイナー向けの、デザインツール講習会 これは私が企画したわけではなく、デザインチームメンバーの小山が主導したものなのですが、デザイナー以外へのデザインツール講習を行いました。業務の効率化を意図しての施策ですが、デザイナーから他部署メンバーへの良いナレッジ共有・ナラティブ(デザイナーの作業姿勢とか)共有になったと考えています。 組織ナラティブに関して勉強会参加(社外) 社外で行われたサービスデザインに関する勉強会にて、組織におけるナラティブ構築に関して整理・発表したりもしました。(この勉強会の影響で、ナラティブという単語を多用するようになった私です。口には出してないけど。) 開発プロジェクトの中にナラティブ収集のフェーズを組み込んでみるトライ ここまででご紹介したのは開発プロジェクトの外側で実施してきた内容なのですが、現在は「 プロトタイプ検証のための社内ヒヤリング 」という名目で、プロジェクト初期段階で他部署から意見を収集するフェーズを作ることにトライしています。 単なる意見収集に留まらず、ナラティブ共有の一助になっている、と感じています。 この件についてはまだプロジェクト進行中につき詳しくは書けないのですが、いずれ整理してまた記事化したいと思います。 整理途中のものをチラ見せすると以下のような具合です。 トライのまとめと今後 業務の傍でいくつか施策をトライしてみました。 それぞれ多くの知見を得られましたが、一方で、拡大していく組織の中で個々のメンバーの想いや体験を収集することの難しさを感じています。デザインワークに本当に役立っているのかは、正直まだよくわかりません。 最後に挙げたデザインプロセスのトライについては、進行中の開発プロジェクトに取り込みつつ試してるため、デザインワークに対してそれなりの効果があることを実感できています。まずはこのプロセスを整理して社内で共有し、トライを重ねていきたい次第です。 ワークショップについても参加者からはポジティブな感想をいただいていますので、こちらも続けていきたい想いでいます。 ただ、これまでに実施したワークショップよりも、もう少し業務との繋がりをイメージしやすいような形のワークショップを行いたいと思っています。 具体的に何かモノ(プロトタイプ)を作って検証するような形をとれたら良いのではないかと思い、現在はプロトタイピングにフォーカスしたワークショップについて学習・研究しているところです。 なんやかんやと、 チームを超えて存在(潜在)している集合知をデザインワークに繋げることを意図したトライをしています 、というお話でした。最後までお読みいただきありがとうございます。 明日はPlatform Devの金城さんとService Devの小林さんです!
はじめに この記事はBASE Advent Calendar 2019 13日目の記事です! devblog.thebase.in こんにちは。BASE株式会社 Product Design Divison でデザインリードをしている北村 ( @naomi_kun )です。 ふだんはオーナーさま向けの管理画面やショッピングアプリの改善、新機能の開発までいろんな部分でUI設計を担当しています。直近では、既存機能のフルリニューアルプロジェクトを進めており、てんてこ舞いになりながらもとても楽しみながら開発しています。 実は、9月にデザインマーケットのテーマ作者さん向けの機能が少しアップデートされました。デザインマーケットでテーマを作成しようと思っているデザイナーさんは、ぜひ BASE Developers からデザイナー申請してみてください。 https://developers.thebase.in/ 今日は、BASEに入社して2年半を迎えたので、BASEの考える「Move Fast」をデザイナー視点で振り返りながら、いろいろ書いてみようと思います。 BASEのMove Fastという文化 入社当初からBASEには「Move Fast」という文化が浸透しており、今現在のプロダクトの開発にもその考え方が強く根付いています。自分が携わってきたプロジェクトも、まずリリースしてみよう、ファーストリリースを恐れるな、という考えが強く、チャレンジングな会社の雰囲気を感じとったのを覚えています。 前Qのはじめにも、あらためて「Move Fastを体現したプロダクト作りを正とする」ということが全社的に語られ、各プロジェクトの開発現場では、「DAY1にこだわるために何をすべきか」という議論が飛び交っています。 プロジェクトが煮詰まるあまり、当初の要件から膨れ上がったり、リリースが遅れがちになるといったプロジェクトを経験していたこともあるので、このスピード感と意思決定の方法はとても「強い」なと思いました。(強い、という表現が適切かはわからないんですが、強さを持っている会社だなと感じてます。) 日々感じていることですが、「BASE」のショップオーナーさんは本当に成長速度が早く、気づいたらとてもおおきなブランドになっている、ということも多いです。 そんなオーナーさんたちの成長速度についていくための方法として、「DAY1にこだわる」という意思決定が言語化されたことは、(私個人としては)、腑に落ちた気がします。 入社当初を振り返りながら影響されたこと 入社した当初、私はアプリの開発チームにアサインされましたが、振り返ってみると現在のBASEのMove Fastを体現するような現場だったなあと思います。 その頃のチームにはディレクターがいなかったため、開発メンバー全員がオーナーシップをもって取り組んでおり、仕様決めやUI設計を一緒に進められたこと、不具合時の対応もリアルタイムで議論していたことなど、いろんなところに首をつっこめる環境のおかげもあって、結果的にサービスへの理解がとても深まったと思っています。 また、現在のVP of Product・CTO・テックリードなど、現在のBASEの開発の根幹を担っているメンバーが集まっていたため、彼らの決定力とスピード感にも多くの影響を受けたように思います。 このスピード感を保つには、粗が出ることもあるし、完璧なデザインや実装にはならなかった部分もあるのかもしれないですが、そのときにくだした意思決定は圧倒的に正しくて(あとから状況が変わってそれが最適ではなくなることももちろんありますが)、現在のBASEはそれを積み重ねてきた結果なんだなと思っています。 あとは、「Be Hopeful」な文化もあり、気負わず自然にお互いを後押しできるような空気があったのも、Move Fastを進められる大きな要因だと思いました。 Move Fastは難しい 上に書いたようなスピード感のある開発は、入社当初の規模感だからこそ実現できていたもので、今はそれが以前に比べて難しくなっているのも事実です。あとは単純にプロジェクトで開発する影響範囲の粒度が大きく、機能改修や新機能を考えるときにはやはり慎重になってしまう場面が増えました。ショップの数も増え、社内の開発メンバーも増え、考慮すべき部分も増えた今では、いろいろなパターンを考慮するあまり、仕様が過剰になり、時間もかかってしまうのは、この規模だとある程度仕方ないと思っています。 それを削ぎ落とすための判断軸として、「DAY1にこだわる」というスローガンがあるのですが、個人によって考え方や持っている背景が違ったりするので、軸として機能させるのもなかなか難しいなあと。 BASEのデザイナーとして「Day1にこだわる」という意思決定をするためには、仕様も、ブランディングも、市場での立ち位置も含め、「BASE」への理解が深くないと行えない、という、当たり前のところに立ち返っています。 これは自省として書くのですが、いつかのMTGで、VP of Productの神宮司 が「デザイナーが不安になると、プロジェクトマネージャーも、開発メンバーもみんな不安になる」ということを話していました。 今でもその言葉が残っていて、デザイナーが慎重に・不安になってしまうのには色々な要因があると思うのですが、私自身がチャレンジングな環境のBASEに助けられてきた部分があるので、自分のいるプロジェクトでは、なるべく「すごい!」とか「めっちゃいい!」とか「はやくリリースしたい!」っていうアクションをするように意識するようになりました。 Be HopefulになれればMove Fastもついてくる、という空気を、デザイナーが持てると良いよなあと思っています。 Move Fastとは、未来のオーナーズに選ばれること 3Qのはじめに、デザインチームのミッションを皆で考える時間がありました。 「BASE」の価値をMove Fastにサービスに具現化し、未来のオーナーズに選ばれ続ける 「はじめに」でも書きましたが、「BASE」のショップオーナーさんの成長速度に追い越されないよう、「BASE」ももっと成長していく必要があり、Move Fastとは、未来のオーナーズに選ばれるための最速手段だと感じています。 おわりに 2年半を振り返りながらいろいろ書きましたが、この先の未来にも、もっと新しい構想や、やりたいことなどが山積みで、その全てが未来のオーナーズのみなさんに選ばれるための決断なんだという部分は揺るぎないので、ワクワクしています。 いま開発中の機能もはやくリリースしたい!DAY1!という気持ちのほうが大きいです。 おわり 明日は基盤グループの金城さんと 、Owners Marketingグループの誠さんです!
Product Management Groupの坂東( @7auto )です。 この記事は BASE Advent Calendar 2019 の12日目の記事になります。 devblog.thebase.in 私はアートが好きでしょっちゅうギャラリーや美術館を巡っています。 会社のお昼休みや、フレックスなのでたまに17時頃など早めに退勤していそいそとギャラリーを巡っているのですが、昼休みにぶらりと行ってみるととても良い気分転換になりますよ! みなさんは行きますか?ギャラリー。 最近ではアート思考の書籍が多数発売されたり美術館が60万人を超える動員をしていたりと、アートに関する情報を耳にすることも多くなっているように感じます。 アートが話題に上がりやすくなったことはとても嬉しいことではあるのですが、 美術館などに展示されるアーティストはトップオブトップな人たちです。 その裏には今まさに制作活動を行い切磋琢磨しているアーティストやアーティストを支えるギャラリーがあります。 大変ありがたいことに、Eコマースプラットフォーム「BASE」はたくさんのそうしたギャラリーやアーティストの方々にもご利用いただいています。 ただ、そんなギャラリーやアーティストはまだまだ一般によく知られる存在ではないのかなと感じています。 そこでよりよく知ってもらうため、本稿ではその魅力をご紹介したいと思います。 ギャラリーってなに? そもそもギャラリーとはなんでしょう。 ざっくりと言えばアーティストの作品などを展示してる場所になります! ※ギャラリーにもレンタルギャラリーとコマーシャルギャラリーがありますが、ここでは後者を指します。 作品の展示と言われて思い浮かぶのは美術館ではないでしょうか? 123億円の作品が展示されていた展示が六本木ヒルズで開催されて話題になっていましたね。 そんな美術館とギャラリーの大きな違いは収益構造です。 美術館は入場料を取るのに対し、ギャラリーは(基本)入場料を取りません。 ではどこで収益を得るのかと言うと作品の売買によって収益を得ています。 なのでそれぞれで目的が大きく違っています。 ・美術館:入場してもらう ・ギャラリー:作品を販売する 目的は作品の販売ですがギャラリーには作家を露出させて価値を育てるという側面もあると思っているので、個人的には昼休みにぶらりと見に行き価値を感じるだけでも良いのではないかと思っています。 そんな作家の価値を見出し育てていく関係性は、起業家と投資家の関係に少し似ているのかなと感じたりします。 最近ちょうど「 ほぼ日刊イトイ新聞 」さんが作家とギャラリーの関係性についての記事を書かれています。 見たことや聞いたことのある作家の名前も出ているかもしれません。 天才もしくは狂人と、その伴走者 ギャラリーやアートの世界が少し身近になったでしょうか? 何が楽しいの? 私は若手作家のいわゆる現代アートにカテゴライズされる作品を好んで見に行きます。 難解だと言われがちな現代アートですが、とにかく表現に対する多種多様なアプローチや切り口おもしろいです。 小林健太 さんや 岡田舜 さんら好きな作家を上げ始めるときりがないのですが、いろいろな目線やアプローチで作られた表現の価値とは何なのか、情報の伝達とは何なのかみたいなことを考える良いきっかけになっています。 難しく考えすぎず、何か刺激を得られるといいな、くらいの気持ちで足を運んでみてはいかがでしょうか? ギャラリーってどこにあるの? そんな楽しいギャラリーですが、どこにであるのでしょう? 個人的に数えたことがあるのですが、東京都内で500件以上、オフィスのある六本木にも50件弱くらいありました。 ビルの地下など目立たないところにあったりするので、Googleマップで「ギャラリー」などのワードで調べてみると意外と身近なところに見つかるかもしれません。 Google マップで「ギャラリー」で検索してみる ギャラリーの営業日は火曜~土曜で19時頃には閉まることが多い印象です。 作品っていくらぐらいなの? 売買が目的だとすれば気になるのは値段です。 もちろん、アーティストの知名度や作品のサイズなどによって値段も様々ですが、私がこれまで作品を見てきた感覚でいうとPCやスマホと同じくらいという印象です。 作品は流行り廃りのあるものではないのでワクワクが持続するように感じます。(個人の印象です。) そう考えると案外手に入れてみるのもいいかも、と思えるかもしれません! また今はアート売買やレンタル、分散保有などのサービスも生まれてきていて、作品の所有に対する障壁は低くなってるように感じます。 私も AND ART というサービスを通して↓のKAWSの作品1点の0.23%を所有してみました。 KAWSを(0.23%だけ)所有していると言えることは、とてもプライスレスな体験です。 購入方法や所有方法も多種多様になってきているので、自分にあったスタイルで一度購入してみてはいかがでしょうか? 何か新しい発見があるかもしれません! 最後に ギャラリーや作家に少し興味を持っていただけましたでしょうか? BASEのミッション には下記のような記載があります。 ひとりひとりに眠る、想いが、感性が、才能が。 世界中の、必要な人に届くように。 そこから生まれる、作品に、アイデアに、活動に。 正当な対価を、受け取れるように。 今回ご紹介したような切磋琢磨して作品を世に送り出している作家やギャラリーの方々をサポートしていけるよう、「BASE」というサービスを磨き続けていきます。 明日はProduct Design Divison でデザインリードの北村さんとUIデザイナーの野村さんです!
この記事はBASE Advent Calendar 2019の12日目の記事です。 devblog.thebase.in こんにちは、BASE BANKインターンの河越( @heart_breakers2 )です。 この記事では、去年と同様に「鶴岡さん観察日記」と題して、BASE株式会社 代表取締役CEOおよびBASE BANK株式会社 代表取締役CEOである鶴岡の普段の様子をカジュアルに伝えていきたいと思います。 (みなさんにもカジュアルな雰囲気で読んでいただきたいので、記事内ではあえて「鶴岡さん」と表記しています。) 私がインターンとしてBASE BANKに入社してから、1年半が経ちました。 私の席は変わらず、鶴岡さんのななめ前にあります。 いつも近くにいるからこそ発見できた、鶴岡さんの素顔や、みなさんに知ってもらいたいBASEの好きなところをまとめてみました。 この記事を読んでいただき鶴岡さんやBASEに興味を持っていただけると嬉しいです。 ※ 去年の観察日記をまだ読んでない!もう一度読みたい!というかたはこちらからどうぞ! devblog.thebase.in はじめに 「鶴岡さん」 この記事を読んでいるみなさんなら、その名前を一度は聞いたことがあるのではないでしょうか? 鶴岡さんは、80万以上ものショップが利用する、ネットショップ作成サービス「BASE」を運営するBASE株式会社の代表です。 鶴岡さんと一度でも話したことがある方だと必ず、温和でコミュニケーション能力が高くて、優しい鶴岡さんに惹かれた経験があると思います。 また、先日BASE株式会社が東京証券取引所マザーズに新規上場をしたことから、代表である鶴岡さんがどんな人なのか気になっている方も多いかもしれません。 この記事では、鶴岡さんのことが大好きで、鶴岡さんみたいになりたい〜!と思っている私が書く「鶴岡さん観察日記」を5日分、みなさんにシェアしたいと思います。 みなさんにも鶴岡さんと、BASEの魅力が伝わりますように! 3/12 (火):鶴岡さんはなんでも知ってる 鶴岡さんは世の中で起こっていることのほとんどを知っているんじゃないかな?と思うことがあります。 顔を合わせるたび聞かれるこの質問ですが、最初は「え、特にないんだけどな、、」と思っていました。 鶴岡さんはとっても忙しいはずなのに、『テラスハウス』も毎週必ず火曜0時から見るし、『グランメゾン東京』も、『オオカミちゃんには騙されない』も、YouTubeも、女子高生に人気なインフルエンサーのInstagramも、IT界隈のTwitterも、スポーツマンガも、「BASE」を利用してくださっているショップさんの情報も、全てにおいてとっても詳しいんです。 BASEの哲学は Stay Geek ですが、鶴岡さんはインターネット上で起こっていること全てに興味が止まらない、「インターネットGeek」な人だな、と思います。 ちなみに今年の春、鶴岡さんはkemioさんにハマっていて、いつも「アゲアゲ修学旅行がやばいんですよ!」とえふしんさん(BASE株式会社 取締役EVP of Development)に教えていました。 今もkemioさん著書『ウチら棺桶まで永遠のランウェイ』は鶴岡さんのデスクに大切そうに飾ってあります。 今は、鶴岡さんが知らないことを見つけると、すごく嬉しくなります。 5/1 (水):鶴岡さんの笑い方 鶴岡さんがミーティングからデスクに帰ってきた時や、ごはんの場にいる時は、周りの雰囲気がパッと明るくなるのを感じます。鶴岡さんはよく笑う人で、鶴岡さんが見えない位置にいても鶴岡さんがいるのがわかるのです。 ちなみに鶴岡さんの笑い方は結構独特で、いつも「カカカカー」と笑います。 最近は、鶴岡さんのことが大好きな神宮司さんも、ちょっと鶴岡さんの笑い方を真似している気がします。 8/8(木):「食べて〜」 鶴岡さんはよく、「BASE」で販売されている、みんなでシェアできるスイーツやフルーツを差し入れしてくれます。 【秀品】ニューピオーネ 2kgを購入しました! https://t.co/ntUQpdOhyu #BASEec @BASEec より — 鶴岡 裕太 (@0Q7) 2019年8月28日 (こういうツイートがあるともうすぐスイーツ・フルーツシェアのチャンスです) ちなみにこの日はGAZTAさんのバスクチーズケーキ。 信じられないくらい美味しくて、鶴岡さんも終始にこにこ笑顔でした! また美味しいスイーツを買ってきてくれますように。 9/23 (月):鶴岡さんは聞き上手 鶴岡さんのすごいところは、なんと言っても傾聴力ではないでしょうか? 鶴岡さんは今まで会った人の中で一番聞き上手で、話していて(こちらは)とっても楽しいのです...! この聞き上手っぷりは今までも多くの人を魅了してきたはず。 誰と話していても気づいたら聞き役に回っているし、鶴岡さんのInstagramをみる限り、友達も多そうです。 鶴岡さんは、「人に興味があるし、そこから何か学べるかもしれないから、自分のために人の話を聞いてるんだよ」とビジネスライクなことを言っていました。大人ですね。 が、そんな鶴岡さんも話が長くて飽きてくるとこんな感じになります。 すご〜〜い。 オンとオフの切り替えがわかりやすいのも魅力ですね!😂 ずっと興味を持ってもらえるように話し上手になりたいなと思います! 11/8 (金):さくさくわいわい BASEのSlackには #sakusakuwaiwai というチャンネルがあります。 #sakusakuwaiwai は、仕事終わりにメンバーとサクッとご飯に行きたくなった時に声をかけるチャンネルです。 この日は、鶴岡さんがチャンネルに声をかけていました。 多忙そうだし、社員とプライベートで過ごす時間はないのでは? 社長とごはんだなんて、社員が萎縮してしまうのでは? と思うかたもいるかもしれませんが、鶴岡さんは違います。どんなに忙しくても、2週に1度くらいのペースで社員とのごはんの時間を大切にしてくれています。 やっぱりごはんに鶴岡さんが来てくださるのは嬉しいし、聞き上手な鶴岡さんが必ず、和やかでフランクな雰囲気を作ってくれます。そんな場にいるからこそ、社員同士も楽しく、"Speak Openly"に話しやすくなっているとも感じます。 鶴岡さんに「人数が増えても忙しくなっても、今もみんなとご飯に行くってすごいですね」って言うと、「僕が寂しくて、みんなとご飯食べたいだけだよ」って言ってました。 本当に素敵な人だなって思います。 おわりに 鶴岡さん観察日記第二弾、いかがだったでしょうか? 日記を書いていて、鶴岡さんみたいになるにはまだ遠いなと感じる日々です。 この記事を通して、鶴岡さんの優しい人柄が伝われば嬉しいです。 (一番気に入ってる鶴岡さんとのツーショット) 今日は坂東さんの記事「 昼休みに行くギャラリーは楽しい 」も公開されています!ぜひ読んでみてください〜!
この記事は BASE Advent Calendar 2019 の11日目の記事です。 devblog.thebase.in はじめまして!こんにちは! BASEのCommerve Dev Group所属の白数です! サーバサイドをメインで開発しています。 私は現在、新卒2年目で18卒と呼ばれる代となります。 BASEには今年の9月に入社しました。今月で4ヶ月目です。 入社してからは、Eコマースプラットフォーム「BASE」の社内向け管理画面の新規機能を開発しています。 そんな若手エンジニアの私がどのようにしてBASEでの働き方をキャッチアップしているのかについてご紹介しようと思います! 特に私と同世代や学生の方にBASEで若手がどのようにして働いているのかを少しでも伝わると幸いです。 BASEで働く上で意識していること・実践していること 1. タスクと進捗の共有 BASE では入社時に、日々の業務でのちょっとしたお困りごとについて質問したり、オフィス生活に関する質問対応をしてもらえるメンターが付く期間が1ヶ月間用意されています。 この時に社内では timesチャンネル と呼ばれる新入社員とメンターがメインでやり取りを行うチャンネルを作るのですが、メンターの他にグループのメンバー、他グループのメンバーが参加しています。 メンター期間はこのチャンネルで日報や困っている事を書くのですが、私の場合、メンター期間後もこのチャンネルを利用し続けています。 やっていることは出勤時には、 今日やること 退勤時には 今日やったこと いわゆるタスクと進捗を共有することです。 自分のタスクの内容をグループのメンバーに共有することで、 もし作業の方針や順序が間違っていたりしたらその場で指摘をもらうことができています。 同時に自分が作業するTODOリストとしても機能しており、タスクが明確化していて「次は何をやるんだっけ...?」といった状況にならず効率が良いです。 毎週、上長と 1on1 の面談をする時間が設定されているのですが、 毎日タスクを共有していることで上長は自分が今どんな事をしているのかを把握してくれていて、1on1が設定されている理由の本質である 思っていること 困っていること などの話に100%の時間を使うことができています。 また、作業内容のログとして後日確認する事もできるので週末に振り返りを行う際、より詳細な観点で良かった事、悪かった事について考えることができていると感じます。 2. 技術的な問題で詰まったときは、とりあえず共有 私は大学生の時に長期インターンで働いていた頃はRuby、そして新卒で入社した会社ではJavaを書いていました。 現在のBASEでは PHP で開発しています。 PHPは大学時代の授業で触ったことがある程度で実務で利用するのは初めてです。 書いている言語が頻繁に変わっていることもあり、 入社2ヶ月ぐらいはBASEのサーバサイドにおける仕様面やCakePHPの細かい使い方が理解できていないこと、 PHPUnitを書いた経験がない事などが理由で頻繁に原因不明のエラーに詰まっていました。 30分ほど悩んでも一向に進まない時は、自分のtimesチャンネルに投げるようにしていました。 BASEにはPHPに詳しいエンジニアがたくさんいます。 解決策や助け舟を出してもらい、開発をしていく上で必要となる最低限の知識を爆速でキャッチアップして実装に入ることができました。 3. タスクで困ったときや、方針を決めるときは直接話す タスクを進めていて進め方などに困ることがあります。 その時は当日、遅くて翌日中には上長へ直接相談するように心がけています。 Commerce Dev Groupでは、毎日12:00からスタンディング形式で タスク内容や困っている事を共有する場として昼会を実施しています。 いろんな人の見解を聞きたいときはこの時に共有して相談するようにしています。 Slack等で話すことも可能ですが、 直接相談した方が早いのと、より細かいところまで認識を擦り合わせることができます。 4. 糖分を摂取 オフィス内にBASEの3つの行動指針のうちの1つである「Be Hopeful」が描かれた飴が置いてあります。 プログラムを書いていて糖分が欲しいな〜という時はこの飴を摂取しています。 デザインが可愛いくて美味しいです。 おわりに BASEの若手エンジニアの私が働く上で意識していることを、あくまで一例ではありますが紹介してみました。 一番は大切なのは、 常に自分の現状を共有することで多方面からアドバイスを貰いやすくなっている印象です。 BASEは若手が成長できる場があると思います。 実際私もまだ入社4ヶ月ですが、エンジニアとして大きくレベルアップしている実感を持っており、 これからもプロダクトと共に成長してBASEを盛り上げていきます。 明日はBASE BANKの河越さんとProduct Managementの坂東さんです!お楽しみにー!
この記事はBASE Advent Calendar 2019の11日目の記事です。 devblog.thebase.in こんにちは!BASEのProduct Management Groupの船坂です。 2019年9月にBASEに入社し、ようやく4ヶ月目に入ったところです。プロジェクト単位でアサインされ、ディレクションを中心に業務を行っています。 今回は、BASEに入社してからEコマースプラットフォーム「BASE」というサービスをいろいろな角度から理解するためにしてきたことをご紹介します。 カスタマーサポート研修 最初は、自主的なものではなく研修ですが、、、 BASEには入社直後にカスタマーサポート業務を体験する研修があります。 この研修には、 まずはショップオーナーとしてネットショップ作成サービス「BASE」を実際に利用してみる。 次に、よくお問い合わせいただく質問内容に対し、模擬的に回答を作成する。(その後CSのメンバーに回答を添削してもらいます。) という2つのステップがあります。 これによって、「BASE」というサービスの利用シーンを深く理解すると同時に、現時点で初めてサービスを使う方がどの部分でつまづく可能性があるのかを短期間で把握することが出来ます。 また、1. の過程で気になったことを社内ドキュメントにまとめて記載するというステップが含まれています。利用に慣れてない視点だからこそ気づくことができる新鮮なフィードバックを社内にすることができて、とても良い仕組みだと感じています。 実地見学 僕自身、IT業界に携わっていると基本的なことは画面の中で出来てしまうのでおろそかにしてしまいがちなのですが、何かを理解するときはその場所に行き、自分の目、耳、触覚など、五感で経験することが一番大事だと思っています。現地に訪問し、オーナーさんやそのショップ、買ってくださるお客様などの様子を実際に感じることで、自分たちが作るサービスの画面の向こうにいる人たちの存在を確かめます。サービス理解という意味だけでなく、普段のモチベーションに直結します。結果、アウトプットの質も段違いに高くなると思っているので、とてもおすすめです。 BASEの場合、オフラインに展開されているものとして、渋谷マルイにある常設店舗「 SHIBUYA BASE 」を始めとする実店舗や、先日行われたオーナーさん向けイベント「 BASE OWNERS DAY 2019 」などのオフラインイベントがあります。入社直後から、これらの場所には実際に足を運んでいました。 経営陣や執行役員の考えを知る 経営陣や執行役員陣がこれまでどういう考えで事業やサービスづくりと組織づくりに携わっているかをなるべく深く理解するために、いろいろな情報をチェックしました。社内に向けて発信されているメッセージや外部で話しているインタビュー記事、普段の何気ない言動やSlackへの投稿などです。どういった人たちがどういうモチベーションでプロダクトを作っているのかを理解することは、どういったサービスなのかを理解することに直結すると思っています。 ちなみに、自分の役割はそこにスムーズに乗っかりつつ、僕にしか出来ないことを全力でやって更にプロダクト成長を加速させることなのかなと思っています。 社内プロジェクト調査 BASEはプロジェクト単位でチームが組まれ、サービスの開発が進んでいきます。当然、社内全体ではどういうプロジェクトを走らせるかが入念に検討されているのですが、一個人としては、自分のプロジェクトに入ってしまうと、意外と実際の業務の中では全体が見えにくくなってしまいます。 そこで、入社したタイミングで、社内ではどういうプロジェクトが走っていて、それはだれが、なぜ今やっているのかをヒアリングしながら考えていました。この作業で、大きな流れの中で今サービスがどういうフェーズなのか、理解できた気がします。 知り合いへのヒアリング ありがたいことにEコマースプラットフォーム「BASE」はたくさんの方にご利用いただいており、自分の知り合いでもネットショップを運営してくださっている方や、ショッピングアプリ「BASE」で商品を購入したことがある方が思った以上にいらっしゃいます。 生の声を聞ける方々は僕にとってとても貴重な存在です。BASEに入社してからは、「BASE」を利用したことがある知り合いと何度か飲みに行き、なぜ使っているのか、どんなところが好きか、どこが使いにくいのか、いろいろな話を聞かせてもらいました。 こういうことを繰り返していくと、なんとなく「サービスを形成しているコミュニティ」全体の雰囲気が見えてきます。 これがわかってくると、どのような施策が効果的なのかを感覚的につかめてくるのではないかと思っています。 データ分析 とはいえもちろん感覚だけでは話は進まず、詳細かつ確実な現状把握にいちばん効くのはデータだと思っています。Google Analyticsから自社で独自に蓄えているデータまで、どんどん調べてみています。BASEでは僕の所属するProduct Management Group含め、必要な従業員はRedash等で業務に必要なデータの分析を自由に行うことができます。全体像は上記のような方法で調べて、細部はデータで確証を得ながら理解を進めている形です。 まとめ 今回この内容にしたのは、経験上、理解が進めば進むほどそのサービスは"じぶんごと"になっていき、それがサービスを作るのに一番大事なことだと考えているからです。 極論、どんなスキルよりも当事者意識こそが、サービスに本当に必要な価値提案や、小さくても確信をついた修正案を産み出すのだと僕は信じています。 最後まで読んでいただきありがとうございました! 明日は、BASE BANKの河越さんとProduct Managementの坂東さんです!楽しみ!
この記事は BASE Advent Calendar 2019 の10日目の記事です。 devblog.thebase.in お久しぶりです。 BASEビール部部長(兼Data Strategyチーム)の氏原です。 1年ちょっと前に Yahoo!の近傍探索ツールNGTを使って類似商品APIをつくる という記事を書きました。あれからだいぶ経ちましたがその間に類似商品APIはコツコツと改善を続けています。例えばファッション系以外で精度が良くないという話を前の記事にも書きましたが、画像以外に商品のタイトルや説明文の特徴量も使うようにするなどしてそれも改善されています。 そうした取り組みのなかで近傍探索に使っていた NGT を別のものに切り替えましたので、それを紹介したいと思います。 類似商品APIについて 類似商品APIはBASEのアプリで各商品の詳細ページを開いたときに表示される「関連する商品」という部分などで利用されています。 その時開いた商品と似た商品を提示することで良い商品探しに役立つようにとAPIを開発しています。 NGTについて NGT はYahoo!Japanさんが開発している近傍探索用のツールです。 NGTは任意の密ベクトルに対して事前に登録した(同次元の)ベクトルから最も距離が近いベクトルの上位数件(k件)を高速に近似k最近傍探索(k-Nearest Neighbor Search)するためのソフトウエアです。 高次元ベクトルデータ検索技術「NGT」の性能と使い方の紹介 以前はこれで画像の特徴量vectorを保存して、商品ページが開かれた際に似た画像をもつ商品を関連商品として提示していました。 NGTはとても性能がよく、1024次元の特徴量vector350万個を登録しても数十msecでレスポンスを返してくれてオンデマンドで関連商品を出すのにとても重宝してました。 問題点 ただ運用を続けていると色々と問題もでてきました。特に辛いのはindexの構築にかかる時間とindexのサイズです。 NGTは登録したvectorを全てそのまま持っています。そのため1024次元の特徴量vector350万個を登録した時点でindexのサイズは20G程になります。これがメモリに全部載るので、AWSでサーバを立てると結構なサイズのメモリを持ったインスタンスが必要になります。 また、NGTは必要なvectorを全てinsertした後にbuild_indexでindexingをする必要があります。登録したvectorをdeleteするメソッドがなく、差分更新が(おそらく)できないため、daily batchで毎度indexを作り直していました。(※私の理解不足の可能性もあります。間違ってたらごめんなさい。) faiss 上記の問題を解決するために、現在では別のツールで近傍探索を行っています。 faiss です。 faissはNGTと同じくvectorの近傍探索のためのツールでFacebookが開発しています。 NGTと比べてなにが嬉しいかと言いますと、最終的に出来上がるindexのサイズです。 faissは高速化やindexの圧縮のための仕組みが用意されていて、いろいろ試した結果indexのサイズがNGTでやっていた時の1/200くらいになりました。衝撃です。 もちろん圧縮するがゆえにある程度の精度低下は覚悟しなくてはいけないんですが、目立つほどでもありませんでした。 使い方はこんな感じです。 index作成 import faiss import numpy as np import random dim = ... # 特徴量の次元数 nlist = ... # 特徴量が何個の空間に分割されるか m = ... # 近傍探索するときに対象の空間以外に隣の空間を何個まで利用するか nbits = ... # 特徴量をここで指定したbit数で表現するように圧縮する quantizer = faiss.IndexFlatL2(dim) index = faiss.IndexIVFPQ(quantizer, dim, nlist, m, nbits) vecs = [(item_id, vec), ...] # 商品IDと特徴量のセットのリスト # 圧縮のために特徴量の分布を学習する必要がある # 実際の運用ではここは事前に学習しておいて学習済みのindexをloadする assert not index.is_trained train_data = [v for _, v in vecs if random.random() < 0.01 ] index.train(train_data) assert index.is_trained # indexへの追加はまとめてではなくこんな感じで細切れでもよいので # vecsが大きすぎて全部をメモリに乗せられない場合は # generatorで必要な分だけ読み込みながらやったりしても大丈夫 batch_size = 10000 for i in range ( 0 , len (vecs), batch_size): input_vecs = [] input_ids = [] for item_id, vec in vecs[i:i+batch_size]: input_vecs.append(vec) input_ids.append(item_id) input_vecs = np.array(input_vecs, dtype=np.float32) input_ids = np.array(input_ids, dtype=np.int64) index.add_with_ids(input_vecs, input_ids) # 保存 faiss.write_index(index, "features.index" ) indexの利用 import faiss index = faiss.read_index( "features.index" ) vec = ... # 近傍探索のターゲット vecs = np.array([vec], dtype=np.float32) D, I = index.search(vecs, 1000 ) # 近傍1000個とってくる D = D[ 0 ] I = I[ 0 ] for item_id, distance in zip (I, D): # add_with_idsでindex作ってるとここでIDが返ってくる ... wiki が充実してるので使うにあたってあまり困ることはありませんでした。 差分更新 faissで作成したindexは、(サポートしているindexなら)登録されたvectorの部分削除が可能です。 import faiss index = faiss.read_index( "features.index" ) target_id = ... # 消したいitem_id index.remove_ids(np.array([target_id]) つまりdaily batchで差分更新ができるため、index構築時間を激減させることができます。これならdailyと言わずもっと更新頻度を上げられます。 ちなみにindexのmergeとかもできるそうなので、vectorが大量にある場合はN個に分けて、N個のjobでindexを作成して後でmergeするみたいな使い方もできるようです。 まとめ faissはFacebookが使ってるだけあって1兆個のvectorを登録して動かすなんてスケールまで想定しているようです。もちろんそのためには 色々工夫 が必要なようですが。 faissを導入したことでindexの構築やAPIの運用がかなり楽になり、インフラやアルゴリズムの改善に力をいれることができるようになりました。リコメンドのシステムを作る際などで近傍探索用のツールが必要であればfaissを検討してみてはいかがでしょうか。 明日はプロダクトマネジメントグループの船坂さんとエンジニアの白数さんです。お楽しみに。
この記事は BASE Advent Calendar 2019 の10日目の記事です。 devblog.thebase.in こんにちは、はじめまして。 SREの相原です。 BASEには2019年9月に入社し、今月で4ヶ月目に突入しました。 SREでは各々の改善業務のほか、日々の問い合わせや依頼業務、トラブル対応など、突発的に発生するタスクがあります(数はとても少ない)。 これはどのような業種、職種においても言えることかと思いますが、 突発的かつ迅速に対処しなければならないタスクは、 担当が特定の人(その問題に知見がある人や、問題解決力が高い人)に偏ってしまう傾向にあります。 幸いBASEでは、各メンバーが主体的にボールを拾っていく文化があったため、大きな問題にはなっていませんでしたが、 私が入社した時点でSREメンバーが6人と増えてきたこともあり、試しに当番botを導入することになりました。 当番botについて 当番は問い合わせなどに対して窓口としての機能を持つ(必ずしも1人で解決する必要はない) 当番にはメインとサブを設定し、メイン当番が休みの時や、手が回らないときはサブが対応する 当番は週ごとに切り替わる 当番の通知はslackでおこなう こんな感じで当番をお知らせしています。 良かったこと チームとして: 対応するタスクの偏りが少なくなった もともと自主的に拾う文化があったとはいえ、それぞれの得意領域や興味はバラバラなのでどうしても対応するタスクの種類は多少偏ります。 当番botを始めることによって、この偏りが少しなくなったと感じています。 個人として: 心理的なブレーキがなくなる これは入社したばかりの私だからこそ感じる事かもしれませんが、 問い合わせや突発的に発生するトラブルは初見のものが多く、中には解決までの糸口が見当もつかないものもあります。 そういった問題に手を挙げるのは、人によっては億劫になってしまうかと思います。 しかし、当番という役割があることによって手を挙げざるを得ない状況になるので、 「手を挙げたはいいが、解決できなかったらどうしよう」という心理的なブレーキがなくなります。 このブレーキがなくなったおかげで今では臆することなく、何か問題があればとりあえず手を挙げることができるようになったと感じています。 他メンバー、他チームとのコミュニケーション機会が増える 対応する問題の中には、1人で解決できないものももちろんあるので、他のメンバーに質問することになります。 入社初期の段階で、質問を通してメンバーとのコミュニケーションを図ることができました。 他にも、他チームの方と関わる機会が増えたりと、当番botを導入することで、結果自分のためになることが多くありました。 botの 改善 同じ組み合わせの担当だとマンネリ化してくるので、ランダムに組み合わせを変えていく仕組みを入れたいなと考えています。 また、今のbotはただ当番を報告するだけのものなので、 メッセージに遊び心を入れるなどして、見た人のモチベーションが少しでも上がるような工夫ができればと思っています。 最後に 私は主体的にボールを拾っていくBASEの文化が好きなので、当番botを導入することには少し懐疑的でした。 しかし前述した通り、結果として自分にとってためになったことが多くあったなと感じています。 BASEにはMove Fastという行動指針があります。 これは「速く動くこと。多くの挑戦から多くを学ぶために、まずはやってみよう。」という意味が込められていますが、 今回の当番botを通して、あれこれ悩む前に先に行動することで得られることは多々あるという学びになりました。 明日はプロダクトマネジメントグループの船坂さんとエンジニアの白数さんです。お楽しみに!
この記事はBASE Advent Calendar 2019の9日目の記事です。 devblog.thebase.in はじめまして。BASE株式会社のtatsuと申します。 最近、業務にて guzzle を使う機会がありました。結論から述べますと guzzle のみで実現することは出来ず Amazon sqs を併用するという形で落ち着いたのですが、いくつか知見を得ることも出来たのでその事について書きたいと思います。 主に guzzle/Pool と guzzle/RetryMiddleware の話になります。 最初の壁:ResponseがどのRequestの結果なのか分からん まず並列処理を実装しました。実際のものとは違いますが流れは一緒。 $urls = [ 'https://example_base.in/1', 'https://example_base.in/2', ]; $guzzle_client = new Client(); $requests = function ($urls) use ($guzzle_client) { foreach ($urls as $url) { yield function () use ($guzzle_client, $url) { return $guzzle_client- > postAsync($url); }; } }; (new Pool($guzzle_client, $requests($urls), [ 'concurrency' = > 10, 'fulfilled' = > function ($response, $index) { // dbに保存とか }, 'rejected' = > function ($reason, $index) { // dbに保存,ログ出力とか } ]))- > promise()- > wait(); レスポンスを受け取って保存という段階で 「どのリクエストの結果を受け取っているか分からん。」となりました。 送信は request の生成順に処理されますが、結果はもちろん順不同で返ってきます。 そりゃ並列処理ですからね… この問題の解決策は簡単で、request 生成部分を少し変えるだけ。 $urls = [ 'index1' = > 'https://example_base.in/1', 'index2' = > 'https://example_base.in/2', ]; $requests = function ($urls) use ($guzzle_client) { foreach ($urls as $index = > $url) { yield $index = > function () use ($guzzle_client, $url) { return $guzzle_client- > postAsync($url); }; } }; これで pool の成否処理の第2引数が'index1','index2'といった値になります。何もしないとrequest の生成順に整数が振られますが、上記の様に任意の値を渡すことも出来ます。 ちなみに失敗時の第1引数には基本的に exception が入ってきます。 下記を参考に処理を分けてあげると良いかもしれません。 http://docs.guzzlephp.org/en/latest/quickstart.html#exceptions 二つ目の壁:Retryの度に結果を保持したい 一応の並列処理は出来た。ということで retry についても考えてみます。 guzzle では RetryMiddleware というリトライの方法を標準で用意してくれています。 // clientに設定 $handler_stack = HandlerStack::create(new CurlHandler()); $handler_stack- > push(Middleware::retry(retryDecider(), retryDelay())); $guzzle_client = new Client(['handler' = > $handler_stack]); function retryDecider() { return function ( $retries, Request $request, Response $response = null, RequestException $ exception = null ) { // 最大5回 if ($retries > = 5) { return false; } // 4xx or 5xx はリトライ if ($response && $response- > getStatusCode() > = 400) { return true; } return false; }; } function retryDelay() { return function ($retries) { return (int) pow(2, $retries - 1) * 1000; }; } 上記の例はかなりシンプルですが、リトライの判断部分と待機時間を実装して Middleware::retry() に渡してあげれば良しなにリトライしてくれます。 注意点としては、delay は単位がミリ秒です。timeout は単位が秒なのに http://docs.guzzlephp.org/en/stable/request-options.html#delay ここでまた問題が生じました。ある理由から最終的な成否だけでなく、retry 時の結果も逐一保持したいとなりました。さて困った。 pool の成否へは request がすべき動作が終わってから到達します。つまり retry 時には実行されません。そうなると retry の中で保持を行う必要があります。 retryDecider() で行いたいところですが、どのリクエストの retry なのか判別する必要があります。上記の様に request が入ってきているので、request-header 等に識別子を設定して判別する事も出来ます。ただ、こちら側の都合を request に持たせるのはどうなのかという考えが浮かび別の方法を取ることにしました。 RetryMiddleware を自作してみました。 …こう言うと凄そうに聞こえるかもしれませんが、実際は標準のものをちょっと変えただけです。 全文は長いので変更箇所の辺りだけです。 private function onFulfilled(RequestInterface $req, array $options) { return function ($value) use ($req, $options) { if (!call_user_func( $this- > decider, //$options['retries'], $options $req, $value, null )) { return $value; } return $this- > doRetry($req, $options, $value); }; } 上記と同じ事を onRejected() にも deciderに $options['retries'] (リトライ回数)では無く $options ごと渡す様にしました。 これで request-options に識別子を持たせることでリクエストを判別する事ができる様になりました。request-ooptions は key を既存のものと被らない様にすれば影響はないですし、リクエスト先にも渡ることはありません。 $options['retries'] 自体は $options に同keyが無ければ __invoke() で追加されます。つまり RetryMiddleware の外から操作する事も出来るということでもあります。 少し長くなってしまいましたが、これで pool × retry の実装が出来た! …と思っていました。この時は 三つ目の壁:PoolとRetryMiddlewareって併用出来ないの&リトライ中にプロセス死んじゃう 開発の世界で三段オチを味わうことになろうとは…。 それぞれをさっくり解説しますと PoolとRetryMiddlewareって併用出来ないの リトライするテストを書いていたら何かおかしいということで検索したところ、「asyncRequest() と RetryMiddleware を併用すると同期してしまいます」といった情報を見つけました。いやいやいや、そんなはずは…と思いつつ試してみました。 方法は簡単でリクエスト毎にリトライの待機時間を変えてみます。 request1 は[2つ目の壁]にある様に回数に応じてべき秒で増えていき、request2 は2秒固定でリトライを繰り返してみます。並列処理が非同期で行われているのであれば、4回目で順番が入れ替わりrequest2が連続するはずです。結果は…? 綺麗にrequest1とrequest2が交互に並び続けました。ワーイ さらに追い打ちをかける様にもう一つ問題が発覚します。 リトライ中にプロセス死んじゃう これは一般的ではない環境での事象ですので詳細は省きますが、あるタイミングで実行中のプロセスが強制的に終了させられてしまうという事がわかりました。終了までに多少の猶予があるのですがリトライの待機時間によっては、待機中に強制終了されてしまい終了に備える事が出来ないのです。プロセスの管理側からの操作なので内部で対策をするにも限界がりそうでした。ちなみに強制終了後すぐに再起動されます。 上記二点の問題が浮上し悩んでいた所、同僚からこんな言葉が(実際にはgitのコメント)…。 「リトライをAmazon sqsに任せちゃうのは?」 …なるほど? 結果的にこれが自分がとった解決方法となりました。 sqs に関しての説明は長くなってしまうので「便利なメッセージキューイングサービス」とだけ言わせて頂きます。その sqs の機能の一つで遅延機能があります。 https://docs.aws.amazon.com/ja_jp/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-delay-queues.html これと実装してきた guzzle を組み合わせる事で上手くいきそうです。 リトライの判断と待機時間の算出はそのまま使い、失敗してリトライが必要な時に待機時間を持たせて sqs に投げる様にします。 待機自体は sqs でするので強制終了の猶予時間以上の処理は無いので、終了に備える事が出来ます。 イレギュラー中のイレギュラーとはいえ起こり得る事に対処出来て良かった。 まとめ この方法を採った大きな要因として、そもそも作っていたものが sqs からキューを受け取ってリクエストを送るというものだったという事があります。なので sqs の導入コストはほぼ0でした。 そもそも使っていたなら気づけよとも思いますが、こういう時って離れて視野を広くするのがなかなか難しかったりします。 そんな時に第三者の視点から意見を貰えるのは本当に有り難いですよね。同僚の言葉が無ければ時間を掛けて guzzle で頑張っていたと思います。それも不正解では無いと思いますが、今回は sqs との連携を選んで良かったと感じています。また guzzle で色々やったからこそ、それほど苦労せずに連携させられたという事もあるはずです。 長くなってしまいましたが、業務上の課題は積極的にオープンにすると良さそうというお話でした(guzzleどこいった)。 明日は id:beerbierbear と id:yk4o4 の記事です。お楽しみに!
この記事はBASE Advent Calendar 2019 9日目の記事です。 devblog.thebase.in こんにちは、BASE株式会社 ランニング部部長の元木です。 フルマラソンのサブスリー達成を目指して日々トレーニングに励む傍ら、Owners Marketingというチームでサーバーサイドエンジニアもやっております。 前書き 弊社が提供するネットショップ作成サービス「BASE」(以下「WEB」)とショッピングアプリ「BASE」(以下「アプリ」)では、 Amazon CloudSearch (以下「CloudSearch」)を利用して商品の検索機能を提供しております。 当記事では、 CloudSearch のインデックスを更新する処理を、どのようにバッチからワーカーに置き換えたのかをご紹介させていただきます。 この記事は、「 BASE Advent Calendar 2019 」の9日目の記事です。 バッチの何が問題だったのか 商品データは、ショップオーナーさんや購入者のアクションによって刻々と変化していきます。 BASEのシステムは、その商品データの変化を捉えて CloudSearch のインデックスを更新していくこととなります。 CloudSearch のインデックスを更新するのは比較的時間がかかる処理であることと、 CloudSearch には 「更新の頻度が10秒に1回を超えると、スロットリングが発生する場合がある」 という制限があるため、BASE ではバッチプログラムを用いて、非同期でこの処理を実行しておりました。 しかし、バッチ処理には インデクシングが必要な商品数は日々増えていくが、処理をスケールできない DBへの負荷が高い という問題がありました。 試験的に、アプリ側には Amazon SQS (以下「SQS」)をポーリングして CloudSearch のインデックスを更新するワーカーが導入されておりましたが、一部のアクション(商品の登録・更新・削除)しかカバーしておりませんでした。 以下が、切り替え前のシステム構成です。 弊社CTOの  @dmnlk と話した結果、これを以下のような構成に変更することが決まりました。 SQS の前段に Amazon SNS (以下「SNS」)を配置する。 WEB用の SQS を新たに追加する- アプリ側、WEB側ともに、バッチをワーカーに完全に置き換える 以下が、目標とするシステム構成です。 ちなみにこの時の私の Amazon SNS に対する理解度は、こんな感じでした。 次からは、どのような段取りでシステム構成を切り替えていったのかをご説明します。 バッチからワーカーへ 手順1 : Amazon SNS の配置   まず、SQS の前段に SNS を配置し、商品データが更新された際の通知先を SQS から SNS に切り替えました。 アプリケーションの変更点としては、インデックスの更新が必要になるアクションが発生した際の通知先を SQS から SNS に切り替えるだけです。 手順2 : WEB用のワーカーとSQSの配置 次に、WEB用のワーカーと SQS を配置し、ポーリングを開始しました。 まだ、一部のアクションしか SNS に通知されない状況ですので、引き続きワーカーとバッチを並行稼動させています。 WEB用のワーカーを実装する際には、バッチとワーカーが同じ動作をすることを保証するために バッチのテストコードを書く(それまでは無かった) バッチのテストコードとワーカーのテストコードとでデータプロバイダーを共通化し、入力と出力が一致することを保証する といった工夫も行ないました。 SNS と WEB用の SQS を連携させる際も、いきなり全てのイベントを通知させるのではなく、 SNS のメッセージ・フィルタ機能 を利用して通知するイベントを徐々に増やしていくようにしました。 手順3 : Amazon SNS に通知するアクションを増やしていく 次に、SNSに通知するアクションを徐々に増やしていき、最終的に CloudSearch のインデックスを更新する必要のある全てのアクションが SNS に通知されるようにしました。 ここでも、一気に全アクションを通知するよう変更するのではなく、段階的に通知するアクションを増やしていくようにしました。 実は、手順2, 3 を実施していく過程で何度かWEB用のワーカーに不具合が見つかり、改修を行なっております。 しかし、バッチとの並行稼動を続けていたことと、ワーカーの仕事量を少しずつ増やしていくようにしていたため、大きな問題に発展することはありませんでした。 手順4 : バッチの退役 全てのアクションが SNS に通知されるようになったところでバッチを退役させ、すべての作業が完了しました。 まとめ サービスが成長していくにつれて、以前は問題なかったシステムから問題が生じるようになることは往々にしてありえます。 一方で、システム構成の変更は比較的リスクが高い上に(何か新しい機能を提供するわけではないため)地味な作業であるため、ついつい、問題が大きくなるまで放置してしまいがちです。 私としては、作業の段取りを工夫することでリスクを最小限に抑えつつ、サービスの成長に応えられるシステムを構築し続けていきたいと考えております。
この記事はBASE dvent Calendar 2019 8日目の記事です。 devblog.thebase.in こんにちは、はじめまして。 2019年8月に入社したデザイナーの石井です。 入社してからEコマースプラットフォーム「BASE」のWebやアプリ、グラフィックなど、様々なデザインを制作してきました。 各プロダクトのデザイン制作時、UIを作成する際にチームのコミュニケーション、共通理解、制約条件をそろえてデザインしていくことが大変重要です。 それらをより活発に、より洗練されたものにするために最近ではデザインシステムの構築に励んでいます。 今回はショッピングアプリ「BASE」においてのデザイン言語システムの初期の段階(β Version)についてお話をさせていただければと思います。 デザイン言語システムの必要性 僕は過去数年間ほどWebとモバイルのアプリケーションの構築と設計を続けてきました。エンジニアやディレクターなど様々なメンバーと協力してプロダクトを作る経験をしてきた中で、より効率的にプロダクトを成長させるための設計システムを構築する方法を学んできました。 デザイン言語システムに求められる要素はたくさんあります。タイポグラフィ、レイアウトとグリッド、色、アイコン、コンポーネント、コーディング規約から、音声とトーン、スタイルガイド、ドキュメントなど多岐にわたります。 そして各デザイナーの考えをデザインシステム上で他のメンバーに適切に共有し、一貫性を保てるように設計する必要があります。そして、チーム全体でデザインシステムを改善していくことで、より良いプロダクト作りを支えていく基盤となります。 デザイン言語システムがあることで、個別のUIを考える時間を短縮し、どのようなユーザー体験を作るかを考えることに集中できます。また、デザイナー間の共通認識ができることで、複数のデザイナーが同じプロダクトの制作に関わる際にも一貫したユーザー体験の提供することにつながります。 ショッピングアプリ「BASE」のデザイン体制 僕が入社するまで、ショッピングアプリ「BASE」の担当デザイナーは1人でした。その時点ではデザイン言語システムはありませんでした。 入社後、僕もアプリのデザインを担当することになり、また今後新たに入社してくるデザイナーがアプリを担当する可能性も鑑み、デザイン言語システムの構築を始めました。 どうやって構築していったか デザイン言語システム構築を開始する前に、基本的なスタイルガイドを作成しました。ここで、タイポグラフィ、色、アイコン、情報アーキテクチャを大まかに定義しました。 こちらは、タイポグラフィーの定義、カラーの使用パターンと洗い出し、スペーシングの確立後のスタイルガイドです。 さらにiOSおよびAndroid共通のアイコンのガイドラインを確立したものがこちらです。 標準化されたコンポーネントの定義 従来、多くのスタイルガイドにおいて、コンポーネントをアトミックコンポーネントとして定義し、それを使用してより複雑な分子を構築されているのを見てきました。 理論的には、これは首尾一貫した柔軟なシステムを作成するためにうまく機能します。 しかし実際には、これらの再利用可能な原子がさまざまな方法で使用され、あらゆる種類の分子を作成できることがよくあります。これは一貫性のあるデザイン言語システムの維持を妨げ、一貫したユーザー体験の創出を阻害してしまう可能性があります。 これらのコンポーネントを作成する際、ライブラリと呼ばれるマスターファイルにそれらを集約しました。またマスターファイルの更新は僕自身のみで行えるようにしています。 ライブラリを構築している間に、個々のコンポーネントを類似のアイテムを含むアートボードに整理し始めました。 これらのアートボードは、一般的なカテゴリ別にナビゲーション、マーキー、コンテンツ、画像等に分類しました。 これらのコンポーネントをiOSおよびAndroid用に1セット作成しました。タブレット用のコンポーネントはモバイルのコンポーネントとほぼ同じなので、技術的なレベルでは、コードは2つの異なるスタイルがあれば済みます。 デザイナーは、共通のコンポーネントを使用して画面を設計することができ、iOSやAndroidだけでなくさまざまな画面サイズに簡単に適応させることができるようになります。 このコンポーネントスタイルガイドはまだ議論が始まったばかりでお見せできるものはありませんが、今までチーム内で共通のコンポーネントの共通理解や作業方法の一貫性がなかったのでそこをシステム化に現在取り組んでいます。 おわりに アプリのデザイン言語システムの発展はまだまだこれからです。引き続きMove Fastなプロダクト作りの基盤の整備に努めていきます。 明日はエンジニアの齋藤さんと元木さんです!
この記事はBASE Advent Calendar 2019の8日目の記事です。 devblog.thebase.in エンジニアの右京です! みなさん! Storybook は使っていますか?BASE では UIコンポーネントの社内展開 はもちろん、日々の業務の中でもサンプルの実装を共有したりするために Storybook が使われています。BASEではこれを「特定のリポジトリにコードをコミットすると、自動的に社内向けサーバーへデプロイされる仕組み (ようするに社内 GitHub Pages ですね)」を利用して社内共有しているのですが、毎度のセットアップが大変なので Gtihub Actions を使ってお手軽に設定できるようにしてみたよ、という内容です。 github.co.jp TL;DR 社内用向けドキュメントサーバーへのデプロイを他のリポジトリから使いやすいように Action 化して配信するようにしました プライベートリポジトリにある Action は直接参照することができないので Personal access token と npx を使います CircleCI から GitHub Actions に変更することで様々なイベントにフックすることができ、柔軟性のあるデプロイが可能になりました デプロイ(コミット)の Action 化 これまでこのサーバーを利用したい時は、利用側の CircleCI で専用のリポジトリを clone して commit を作って push して...といった処理を用意する必要がありました。利用するプロジェクトが増えていく中、この利用法のスケールのしにくさに問題を感じていたため、GitHub Actions でスパっといけないか?と思ったのが今回のスタート地点です。 このエントリでは今後はこのリポジトリのことを「static-pages-repo」と呼ぶことにします。 まず、前提として static-pages-repo は master に push されると public ディレクトリ以下が自動で社内向けのサーバーへデプロイされるようになっています。 └── public ├── webservice1/master/... ├── webservice1/hogefuga/... ├── webservice2/master/... ├── ... └── index.html // デプロイされているURLのリストが入っています 今回作る Action では、上記の構成に沿って以下のフローを実行します この配置に合わせて利用側から成果物をコピー コピーした成果物をコミット アクセスしやすいように public/index.html を更新 リモートへ push これを行うためには3つの引数が必要そうです。 public の配置を決定するためのプロジェクト名(prefix) public の配置を決定するためのブランチ名 成果物(コピー元)のパスの指定 つまり Action としてはこんな感じに利用できるのがよさそうです。 - uses : static-pages-repo with : project : webservice1 branch : feature/hogefuga source : dist 早速実装に...と行きたいところですが、気をつけるべき点が2つあります。 プライベートリポジトリのアクションは直接実行することはできない Workflow 内で他のリポジトリを扱う場合は secrets.GITHUB_TOKEN では権限不足 それぞれ見ていきます。 プライベートリポジトリのアクションは直接実行することはできない パブリックなリポジトリの場合は リポジトリ名やパスを指定することで 直接他のリポジトリから Action を参照して、利用することできます。ですが、プライベートリポジトリの場合はこの方法は使えないため、回避方法を考える必要があります。 static-pages-repo を clone...? と思いましたが、それだとあまり現状と変わらない...ので多少見栄えがよくなりそうだと npx で Action をインストールできるようにしてみることにしました。 一度 npx 経由で Action をコピーしてしまうことで、 自身のリポジトリ内の Action として実行します。 - run : npx https://github.com/baseinc/static-pages-repo install - uses : ./.github/actions/static-pages-repo Workflow 内で他のリポジトリを扱う場合は secrets.GITHUB_TOKEN では権限不足 GitHub Actions を使ったことがある人は知っているかもしれないのですが、元々 Workflow で使える変数として secrets.GITHUB_TOKEN が用意されています。通常であれば、このトークンを使えばプライベートリポジトリでも GitHub API を呼び出したり、リポジトリに Git での書き込みができるのですが、残念ながらこのトークンではリポジトリを跨いだ操作を行うことはできません。 これは代わりに Personal access token (以下PAT) を使用することで解決します。 actions/checkout にもこれに関する記述 があるので参考にしてみてください。 実際に Action を作る では、以上の2つの注意点に気をつけながら実際に Action を作っていきます。 Action の開発方法には Docker コンテナ版 と JavaScript 版 があり、今回は Git での操作がメインになりそうだなという理由でシンプルに実装できそうなDockerコンテナ版を選択しました。 公式ドキュメントにそってまず Dockerfile を作ります。alpine でもよいですが、 public/index.html を Node.js で作成しているため、 node:12 を選択しています。 ファイルの置き方ですが、これらを直接 Actions が実行するわけではないので(コピー元)、安直に action というディレクトリを作って配置することにしました。 FROM node:12 COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] 次に action.yml を作ります。action.yml は引数や出力、実行方法を設定するためのファイルです。 name : 'static-pages-repo' description : 'deploy static pages' inputs : # ここに追加したキーが引数となります pat : description : 'Actionで必要になるPAT' required : true project : description : 'ディレクトリ名のprefix' required : true branch : description : 'ディレクトリ名' required : false default : 'master' source : description : '配信したいコンテンツへのパス' required : true outputs : url : description : 'デプロイ先となるURL' runs : using : 'docker' image : 'Dockerfile' args : - ${{ inputs.pat }} - ${{ inputs.project }} - ${{ inputs.branch }} - ${{ inputs.source }} 最後に entrypoint.sh を作って実際に動かすスクリプトを書いていきます。PAT を受け取って、static-pages-repoを操作していきます。 #!/bin/sh -l # 作業用のリポジトリをクローンします、PAT($1)を付加していることに気をつけてください git clone https:// $1 @github.com/baseinc/static-pages-repo.git .github/tmp/repo # project/branchなディレクトリを作って成果物をsourceからコピーしてきます mkdir -p .github/tmp/repo/public/ $2 / $3 cp -rT $4 .github/tmp/repo/public/ $2 / $3 cd .github/tmp/repo # 配置されたファイルへのリンクを含む public/index.html を生成します npm ci npm run build-index # 最終的に差分が生まれていれば git commit して push します git add . git config user.name actions git config user.email actions@binc.jp if git commit --dry-run > /dev/null ; then git commit -m " Update $2 / $3 " git push origin HEAD fi git push https:// $1 @github.com/baseinc/static-pages-repo.git master # このようにして値を出力しておくと、次の step でこれを元に検証したりすることができます echo " ::set-output name=url::https://static-pages-repo.com/ $2 / $3 / " Action を配信する npx スクリプトを作る npx にあまり馴染みのない方もいるかもしれませんが、簡単にいえば「インストールせずに一度だけ使えるパッケージ」という感じでしょうか。 package.json を作って以下のようなスクリプトで Action をローカルにコピーしています。 #!/usr/bin/env node const path = require( 'path' ) const fs = require( 'fs-extra' ) const src = path.resolve(__dirname, '../action' ) const out = path.resolve(process.cwd(), '.github/actions' ) ;(async() => { await fs.ensureDir(out) await fs.copy(src, `$ { out } / static -pages-repot`) } )() これを上のものと合わせると、最終的には他のリポジトリからこのような step で static-pages-repo デプロイすることができるようになります。 - run : npx https://${{secrets.PAT}}@github.com/baseinc/static-pages-repo install - uses : ./.github/actions/static-pages-repo with : pat : ${{ secrets.PAT }} project : webservice source : dist 便利そうに見えませんか?実際に PAT を設定するのは、利用側であることに気をつけてください。 これで static-pages-repo 側は完成です! ここまでのディレクトリ構造はこのようになっています。 ├── action │ ├── Dockerfile │ ├── action.yml │ └── entrypoint.sh ├── bin │ └── install.js // npx で実行されるスクリプト ├── webroot │ ├── webservice1/master/... │ ├── webservice1/hogefuga/... │ ├── webservice2/master/... │ ├── ... │ └── index.html ├── README.md ├── package-lock.json └── package.json 実際に Action を使ってデプロイする これで準備が整ったので、あとは各リポジトリに導入するだけです! 今回は、実際に BASE で運用しているパターンから3つをご紹介します。 master に push されたらデプロイ Pull Request が作られたらデプロイ Pull Request に特定のコメントがついたらデプロイ master に push されたらデプロイ 一番簡単でわかりやすい、馴染みのある感じだと思います。 ほとんどのリポジトリは、master にマージされたときに更新を実行するようにしています。 name : deploy master on : push : branches : - master jobs : build : runs-on : ubuntu-latest steps : - uses : actions/checkout@v1 - uses : actions/setup-node@v1 with : node-version : '12.x' - uses : actions/cache@v1 with : path : node_modules key : ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} restore-keys : | ${{ runner.os }}-node- - run : yarn install --frozen-lockfile - run : yarn storybook -o dist - run : npx https://${{secrets.PAT}}@github.com/baseinc/static-pages-repo install - uses : ./.github/actions/static-pages-repo with : pat : ${{ secrets.PAT }} project : webservice source : dist Pull Request が作られたらデプロイ UIコンポーネントのような基本的に Storyboard がセットになるものはこれを設定しています。 Pull Request が Open されると同時にデプロイされるため、レビューの際にはスムーズに Storybook を確認することができます。 name : open pull-request on : pull_request : types : [ opened, reopened ] jobs : build : runs-on : ubuntu-latest steps : - uses : actions/checkout@v1 - uses : actions/setup-node@v1 with : node-version : '12.x' - run : yarn install --frozen-lockfile - run : yarn storybook -o dist - run : npx https://${{secrets.PAT}}@github.com/baseinc/static-pages-repo install - id : deploy uses : ./.github/actions/static-pages-repo with : pat : ${{ secrets.PAT }} project : webservice branch : ${{ github.event.pull_request.head.ref }} # 実行時の event を受け取って使うことができるため、ここから branch 名を取得して決定しています source : dist - if : "steps.deploy.outputs.url" # 前の step が成功したときに発行される url があったら、PRのコメントにURLを書き込みます env : URL : ${{ steps.deploy.outputs.url }} API_ENDPOINT : ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} # ここは同じリポジトリ内なので GITHUB_TOKEN でアクセスできます run : | curl -X POST -H "Authorization: token ${GITHUB_TOKEN}" -i ${API_ENDPOINT} -d "`printf '{ \" body \" : \" deploy to %s \" }' ${URL}`" Pull Request に特定のコメントがついたらデプロイ 必ずしも Storybook が必要ではないアプリケーションのリポジトリでは、Pull Request にコマンドコメントを書くことで任意のタイミングでデプロイが行えるようにしています。 これには issue_comment というイベントを使うのですが、これは特定ブランチには紐づかないイベントで、デフォルトブランチを起点に実行されるため少し複雑です。 name : on comment pull request on : issue_comment : types : [ created ] jobs : build : runs-on : ubuntu-latest steps : - id : check # コメントに deploy-storybook の文字列が含まれていて、pull_requrest なら実行します if : "contains(github.event.comment.body, 'deploy-storybook') && github.event.issue.pull_request" env : API_ENDPOINT : ${{ github.event.issue.pull_request.url }} GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} run : | # pull_request のデータを API 経由で取得して、ブランチ名を特定します branch=`curl -X GET -H "Authorization: token ${GITHUB_TOKEN}" ${API_ENDPOINT} | jq -r '.head.ref' ` echo ::set-output name=branch::$branch # このようにすることで id(=check) に紐づく outputs を step からも書き出すことができます # これ以降は branch が特定できていたら続行します - if : "steps.check.outputs.branch" uses : actions/checkout@v1 with : ref : ${{ steps.check.outputs.branch }} - if : "steps.check.outputs.branch" uses : actions/setup-node@v1 with : node-version : '12.x' - if : "steps.check.outputs.branch" run : | yarn install --frozen-lockfile yarn storybook -o dist npx https://${{secrets.PAT}}@github.com/baseinc/static-pages-repo install - id : deploy if : "steps.check.outputs.branch" uses : ./.github/actions/static-pages-repo with : pat : ${{ secrets.PAT }} project : webservice branch : ${{ steps.check.outputs.branch }} # 前の step で特定した branch 名を使用します source : dist - if : "steps.deploy.outputs.url" env : URL : ${{ steps.deploy.outputs.url }} API_ENDPOINT : ${{ github.event.issue.comments_url }} GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} run : | curl -X POST -H "Authorization: token ${GITHUB_TOKEN}" -i ${API_ENDPOINT} -d "`printf '{ \" body \" : \" deploy to %s \" }' ${URL}`" 一つ気をつけるべき点として、 job 自体にも if を設定でき もっと簡潔にかけそうなのですが、if が通らなかった場合に job が 0個となり Workflow 自体が失敗したことになってしまいます。 また、実際にはこれらと「 Pull Request が close された際にディレクトリを削除する Action 」をあわせて運用しています。 めでたしめでたし Storybook を Move Fast にデプロイできるようになったことで、 Speak Openlyな開発 にまた一歩近くことができたと思います。 お気づきの方もいると思いますが、単なる静的ファイルホスティングなので Storybook 以外にももちろん使うことができます! 明日は id:kmotoki と id:tatsuta2 です!
はじめに この記事はBASE Advent Calendar 2019の7日目の記事です。 devblog.thebase.in こんにちは、BASE投資部部長の菊地です!2019年は多くのBASE社員のマネーリテラシーを高めることができて、なかなか満足のいく一年となりました! さて、2019年業務の方がどうだったか振り返ってみると1月にエンジニアリングマネージャー(以下EM)に就任し、7月からは約20名のエンジニアが所属するService Devというセクションのマネージャーに就任しました。カレンダーを振り返ってみると今年は1on1を400回、採用面接は70回ほど行ったりとマネージャー業にどっぷりと浸かった一年となりました。 巷ではピープルマネジメントに費やす時間が増え、コードを書く時間が減ることが多いEMに対してネガティブな印象を持つエンジニアが多いようです。 しかし私はこの一年間EMとしての役割を果たすことでやりがいを感じることがたくさんありました。みなさんにEMって楽しそうだなと思って頂けたら嬉しいと思い、そのいくつかを紹介させて頂こうと筆を取りました。 BASEにおけるEMの役割 昨年、私の上司である@fshinが「 エンジニアとしてワクワクし続けるためのエンジニアリングマネージャという役割分担 」という記事を書いてEMの役割について言語化しています。 この記事にあるようにBASEのEMに求められる役割を一言で言うと 「事業、プロダクトに貢献しながら、チームのエンジニアの活躍にコミットすることで、メンバーの評価を上げること」 ということになります。 会社によってはEMが技術をリードする責任も求められる場合もあるようですが、BASEではエンジニアの上位職としてEMの他に技術でリードするテックリード(以下TL)という役職が存在するのでEMはピープルマネジメントだったりに専念することができます。(逆に言うとTLはピープルマネジメントを求められないので、技術に専念することができます) 私がやりがいを感じるとき さてここからは私がEMをやっていてやりがいを感じる瞬間を、それにまつわる取り組みなどを交えながら紹介させて頂きます。 チームメンバーを輝かせられたとき EMの仕事をマネジメントというとなんだか堅苦しい印象ですが、より強いエンジニア組織を作る、メンバーの成長を支援して市場価値を高めるという側面ではプロデューサーと呼んだ方が個人的にはしっくりきます。もっと輝けるのではと思った人をプロデュースして、それまで以上の輝きを放つようになったときに私はEMとしてのやりがいを感じます。 例えば黙々とコードを書いていてあまり目立たっていないと感じていたチームメンバーが、私が背中を押すことでグングンと成長し今では社内の誰からも信頼され頼られる存在に成長した人がいます。これぞEM冥利に尽きる瞬間です。私は彼の社内でのプレゼンスを上げるために次のような働きかけをしました。 アイコン重要 BASEではSlack, G Suite(GmailやGoogle カレンダー等), Asana, Kibelaなどなど多くのツールを使用しています。社員数が100人を超え社内の認知を得ることが難しいフェーズになっているので、アイコンにこだわりを持つことは社内で認知されるために非常に重要だと考えていました。 しかし当時彼はアイコンを何も設定していませんでした。そこで私は「アイコンは必ず設定しよう」、「各ツールでアイコンを統一しよう」、「アイコンはキャッチーなものにしよう」とアドバイスをしました。その結果今では社内のほとんどの人が彼のアイコンを見れば彼のことを想起できるようになり認知度が飛躍的に高まりました。 他部署の人との関わり重要 日常的にカスタマーサポートチーム等の他部署からお問い合わせ調査依頼がやってきます。こういう依頼があった際はなるべく拾いにいくように伝えました。理由はお問い合わせの調査をすることでBASEのサービスやシステムの理解が深まりますし、他部署のメンバーからの信頼を得ることができるからです。 最初の頃は積極的には拾いにいっていなかったので、私が拾って彼に振っていたのですが、いつしか抵抗がなくなったのか今では自ら進んで拾いに行ってくれるようになりました。自分からタスクを取りにいくような働き方になった結果、多くの人から頼られる存在になりました。 理想としているチームに近づいているのが目に見えたとき 突然ですが、 wevox というサービスをご存知でしょうか。wevoxとは「定期的なアンケートを実施することで、社員の仕事や会社とのエンゲージメントを数値化しよう」というサービスです。 BASEの場合は3ヶ月に一度全社員からアンケートをとって、「上司との関係性は10点中9点で世の平均よりも良好です」といったようにたくさんの項目が個人レベルで数値化されマネージャーに共有されます。(匿名と実名での回答が設定でき、BASEでは現在実名で回答する形式をとっています) 私は「明るく、楽しく、健康的」なチームを目指しているので、wevoxでそういった項目の数値が高かったり前回よりも改善されていると自分の日々の取り組みの成果が現れ、目指しているチームに近づいているのだなと喜びを感じます。 メンバーと頻度多くコミュニケーションを取ることでお互い何でも言い合える関係を築くことがEMにとって何よりも大事だと考えているので、そのためにいくつかの取り組みを行なっています。 1on1 もはや定番になっているかもしれませんが、1on1がEMにとって最も重要な業務であると考え、全ての業務より優先して全メンバーと毎週30分行なっています。主に1on1のバイブルとなっている『 ヤフーの1on1 』に書かれている下記のようなことを実践しています。 この本には1on1の目的は「メンバーの才能と情熱を解き放つこと」と書かれていますが、まずはシンプルに雑談でもいいからとにかく毎週30分行うということに専念しました。 定番の質問 自分が部下として1on1をしていた時を思い返しても「何かお困りごとはありますか?」といきなり聞かれてもなかなか思いつかないものでした。ですので「以下の質問を毎回聞くので何か考えてきてください」とチームメンバーに伝えるようにしています。これらの質問も『ヤフーの1on1』を参考にしたものです。 今日は何の話をしましょうか? 何かお困りごとはありますか? 私(EM)にできることはありますか? 毎回聞くからねと宣言することによって、1on1の時にメンバーが議題を用意してくれるということが増え、1on1の充実度が上がったように思います。 予習 1on1を行うにあたって1人につき15分ほど予習を行なっています。チームメンバーには毎週末に週報を書いてもらっているので週報を確認して一週間にどのようなことをしていたのか把握したり、タスクの進捗は順調そうか確認したり、これまでの1on1の議事録を見返したりすることで話したい内容をある程度箇条書きして臨むようにしています。そういった準備をすることでチームメンバーが今困っていることなどに気づきやすくなるといった効果があると感じています。 議事録 毎週10人近くのメンバーと1on1を行なっていると、誰が何を言っていたのか分からなくなることがあります。また自分が誰に何を言っていたのかも分からなくなることがあります。そのようでは信頼を積み重ねることは難しいと感じ、毎回議事録をとって、終わったら議事録の内容をSlackでDMするようにしています。信頼を積み重ねるにはとても大事だと認識しているのですがけっこうサボりがちなので反省しています、2020年はサボらないで頑張りたい。 ニチレイ(日例) 毎日12時からスタンディング形式でチームメンバーが集まって今やっていることや困っていることを5分ほどの時間を使って共有しあう時間をとっています。議論したり雑談をしたりとチーム内のコミュニケーションを毎日作れるのでニチレイの重要度はとても高いと考えています。 Unipos Unipos というのは社内のメンバー間で感謝を送り合うサービスです。 感謝することも、感謝されることもやりがいを持って業務に取り組むためにはとても重要であると考えているのでチームのメンバーには感謝のハードルを極力低く設定して、たくさん感謝を送り合うように伝えています。 チームメンバーを採用できたとき BASEではスクラム採用に取り組んでいて、エンジニアの採用にはEMがフルコミット(スカウト、カジュアル面談、書類選考、面接、オファー面談、採用会食など盛り沢山)しています。BASEのスクラム採用について興味のある方は弊社採用マネージャーの 米田が登場している記事 をご参照ください。 さて、採用活動がEMにとって最重要タスクであることは言うまでもありませんが、一方でなかなか成果に結びつきにくいものです。100人カジュアル面談を実施して2,3人入社して頂ければ御の字といったところではないでしょうか。 BASEから内定をお出ししている方は他社でも複数内定が出ていることがほとんどです。複数内定が出ている中からBASEを選んで頂けるように毎度毎度全社総動員でアトラクトをしています。そうやって最終的にBASEを選んで頂けた際にはみんなで喜びます。この瞬間がEMとしてやりがいを感じる瞬間です。 採用活動に携わるようになって良かったと思う点をいくつか紹介します。 視座が上がった 自分が面接官を担当するということはBASEのサービスであったり、ビジネスモデル、社内環境のことなどを魅力的に語れる必要があります。 また候補者からは以下のような質問をはじめ、様々な質問をされます。これらに自信を持って答えられなければ候補者に不安を与えてしまうことになり、BASEを選んでもらうことは難しくなります。 BASEはどんな会社ですか なぜBASEに転職したのですか 今抱えてる組織的な課題はなんですか 評価の方法を教えてください などなど 採用活動に携わるようになってから、プレイヤーの時と比べて一段高い視座を持てるようになったと感じています。 技術力が高まった 先日 「エンジニアリングマネジャーになってから技術力が伸びるパターン」 という記事が話題になりましたが共感を持って拝見しました。 候補者と面談をするにあたって職務経歴書に書いてある知らない用語を調べたりすることで、決して深くはありませんがプレイヤーの時には得られなかった広い知識が身についたと感じています。 相場が分かった スカウト等の採用活動をWantedly, LAPRAS, 転職ドラフト, Green, Findyなどたくさんのサービスを利用して毎日のように転職顕在層/潜在層のエンジニアをチェックしています。この経験を通じてスキルだったり会社だったり年収の相関が大体分かるようになり、今後の自分のキャリア設計に参考になると感じています。 最後に この一年間EMとして様々な経験をさせて頂きました。つらいなーと思うこともたくさんあった気がしますが、一年を振り返ってみるとつらかったことは思い出せずやりがいを感じたことばかりが思い出され不思議なものです。まだまだEMとして期待に応えられず不甲斐ないことが多くありますが、優秀なチームメンバーに支えられ何とか会社の成長にコミットしてこれたと思っています。2019年はチームメンバーに感謝の毎日でした。 2020年は「BASE」をより良いサービスに成長させられるように、エンジニア組織をまだまだ成長させていきます!2020年が今からとても楽しみです! 明日は、テックリードの右京さんとデザイナーの石井さんです!お楽しみに!
この記事は、「 BASEアドベントカレンダー2019 」7日目の記事です。 devblog.thebase.in BASE株式会社で採用をやっているaipon( @AiYoneda )です。 今回は、Slack Workflow Builderを使ったワークフロー自動化についてご紹介します。 プログラミング知識がなくとも、簡単に作成することができるので、まだ試したことのないという方はぜひお試しあれ! BASEでのSlack Workflow Builderの活用方法 人事関連のワークフローについて、労務回りはSmartHR、採用はHERPを使っていますが、利用サービスで吸収できない細かなワークフローについては主にSlack Workflow BuilderもしくはGoogleフォームを利用しています。 Slack Workflow BuilderとGoogleフォームは、下記のような使い分けをしています。 回答を一覧で見る必要がない・見返す頻度が少ない(Slackで回答が流れても困らない)、回答内容をもとにシート上で計算等を行う必要がない→Slack Workflow Builder 回答を一覧で見たり、見返す頻度が多い(Slackで回答が流れると困る)、回答内容をもとにシート上で計算を行う必要がある→Googleフォーム 回答をスプレッドシートで一覧で見たいもの、計算式を組んだりVLOOKUPなどあれこれしたいものに関してはGoogleフォームで回答してもらい、回答があれば特定のチャンネルで通知を飛ばしています。(Slackへの通知の飛ばし方は こちらの記事 をご参考にされてください。) 人事関連では、主に下記でSlack Workflow Builderを利用しています。 採用会食費用の補助申請 社内懇親会費用の補助申請 オフィスアンケート(社内向けサービスについての意見収集) 相談窓口(健康相談、働き方相談など) 人事制度やリファラル採用など、全社に関わる施策を実施する際は運用方法が全社に浸透するかがポイントになりますが、Slack Workflow Builderはフォームへの回答、回答の閲覧はSlack上でシームレスに行うことができ、全社の利用の障壁が低くなるというメリットがあります。 Workflowの作り方 ここからは具体的な利用方法についてのご紹介です。 今回は、採用会食費用補助の申請ワークフローを作る例とします。 採用会食補助のワークフローは下記です。 利用者(申請者)が補助申請 採用チームが内容を確認 問題なければ採用チームが稟議申請 稟議決裁がおりれば、採用チームが稟議を利用者(申請者)に共有 利用者(申請者)が会食実施 利用者(申請者)が経費精算 1から2のワークフローをこれまでGoogleフォームで行っていたのですが、URLが見つからなくなったり、Slackから遷移するのが少し面倒ではないかと懸念していたので、Slack Workflow Builderがリリースされたのを機に移行してみました。 1から2のワークフローをSlack Workflow Builderで実現する際のワークフローを細かく分解すると下記になります。 利用者(申請者)が補助申請 利用者(申請者)のDMに回答内容が送信 上記と同タイミングで、採用チームのチャンネルに回答内容を含むカスタマイズされたメッセージが届く 採用チームの担当者が内容を確認 それでは実際に作っていきます。 ワークフロービルダーを起動 メニューの一番上のワークスペース名をクリックすると、「ワークフロービルダー」があります。 これをクリック。 これまで作成したワークフローが表示されます。 ワークフローを新規作成 先ほどの画像の、右上の「作成ボタン」をクリック。 ワークフローに名前を付けます。 今回の例では、「採用会食費用補助申請フォーム」と入力します。 ワークフローの開始を設定できます。 今回は社内の誰かが申請しようと思ったときにワークフローを開始したいので、「アクションメニュー」を選択。 ワークフローを開始するチャンネルを選択します。プライベートチャンネルにも設定できます。 今回は #general とします。 「短い名前を追加」は、ワークフローを開始する際にチャンネルで表示される名称です。 利用する人が分かりやすい名称に設定するのがよいでしょう。 アクションメニューを設定するとこのように表示されます。 ワークフローの概要を設定したら具体的なステップを設定していきます。 最下部の「ステップを追加」からステップの追加を行うことができます。 ステップには「メッセージの送信」と「フォームを作成する」の2種類を設定することができます。 今回の場合、採用会食を実施する日時や参加するメンバー等の内容を申請者に入力してほしいので、フォームの作成を行います。 質問と回答の形式を設定してみました。 回答のタイプは入力形式のものやリスト形式、Slackのユーザー名やチャンネル名を選択する形式もあります。 上記はすべて「短い回答」でフォームを作成しています。 また回答者に回答内容を送っておきたいので、「提出された回答をチャンネルまたはDMで他のメンバーに送信する」にチェックを入れ、送信先を該当のワークフローをクリックしたユーザーに設定します。 ここまでで下記が完了しました。 利用者(申請者)が補助申請 利用者(申請者)のDMに回答内容が送信 あとは下記のステップです! 上記と同タイミングで、採用チームのチャンネルに回答内容を含むカスタマイズされたメッセージが届く 採用チームの担当者が内容を確認 特定のDMやチャンネルにメッセージを送信するステップを作成します。 冒頭と同じように、ワークフローの概要から「ステップを追加」をクリックします。 今度は「メッセージを送信」をクリックします。 回答の送信先チャンネルの指定と、メッセージを作ります。 メッセージには変数を入れられるので、それぞれの項目の回答内容や、フォームを提出したメンバーをメッセージに加えることができます。 Previewを見ながらメッセージを作成できるのもいいですね! メッセージができたら保存します。 ワークフローの作成はここまでです。 完成したら、ワークフローの概要から「公開する」をクリックしましょう! 稲妻アイコンをクリックしてフォームが意図した通りにできていれば完成です! 採用チームのチャンネルにはこのように通知が飛んできます。 おわりに 今回は誰でもできるワークフロー自動化の例をご紹介しました。 バックオフィスのメンバーが新しい制度や取り組みをする際に業務フローに手間がかかることが懸念の一つとしてあがることがあるかと思います。 こういった便利なツールを使うことで、制度起案者・運用者の心理的障壁がなくなるといいなーと思っています。 明日は、デザイナーの石井さんとテックリードの右京さんです!お楽しみに!