TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

587

はじめに こんにちは、Data Strategy所属の岡です。グループ会社BASE BANKで分析/モデリングなども兼務しています。 テキストデータを特徴量にもつ不均衡データ分類問題をDNNで解きたくなった際、下記の論文を参考にしたのでその内容を紹介します。 https://users.cs.fiu.edu/~chens/PDF/ISM15.pdf 不均衡データ分類問題ってなに? 何かしらのカテゴリを機械学習などで分類予測しようとする際、カテゴリごとのデータ件数に偏りがある、特に正例のデータが極端に少ないケースで予測精度が上がりにくい、という問題をこのように呼んでいます。 例: 不正決済と正常な注文、不正商品と健全な商品、がん患者と正常な患者 普通はどうやって対処するの? ベースとなるアプローチは下記3つにまとめられます。 アプローチ 内容 デメリット アンダーサンプリング 多数派データをランダムに減らして少数派データと均一にする 多数派データの多くを捨てるため、情報損失が生じうる オーバーサンプリング 少数派を増やし、多数派データと均一にする 例: SMOTE, ADASYN 少数派データの水増しになるため、過学習が懸念される 損失関数のカスタマイズ 損失関数に対して多数派データのコストを少数派データとの割合に応じて割り引くなど あまりデメリットについて明言されてるケースは少ないと思いますが、これも過学習になりうるという印象 個人的には、アンダーサンプリングの情報損失という弱点をカバーしている under sampling + bagging をよく使用しています。DNNでこれと似たようなアプローチができないか調べていたところ、冒頭の論文を発見しました。 ミニバッチでunder samplingするアプローチ 今回紹介する論文では、ミニバッチ作成時にラベルごとのサンプルサイズを合わせる方法を提案しています。 この処理フローは下図のようにまとめられます。 まず、多数派データ(P個)のみをバッチサイズ(ここではN個)分に分割してミニバッチ作成します。ここでミニバッチ1つあたりのサイズは(P / N)個です。 続いて少数派データ(Q個)から(P / N)個を重複がないようにランダムサンプリングし、最初に作った1個目のミニバッチのデータに混ぜます。このサンプリングを続いて2,3,4...N個目のミニバッチごとに繰り返し、ラベルの比率が均等なN個のミニバッチが作られます。 注意点として、ミニバッチ1個あたりに対して少数派データ(Q個)から(P / N)個を選び出すときは非復元抽出となるため、ミニバッチ単体で見ればデータの重複はありません。ただし、別のミニバッチを作るときには同じようにQ個から(P / N)個選び出すことになるので、バッチ全体としては少数派データの重複を許しつつサンプリングされています。 実装例 PyTorchでの実装例を示します。図示したミニバッチ作成部分だけ簡略化したコードだと下記のようになります。 class BinaryBalancedSampler : def __init__ (self, features, labels, n_samples): self.features = features self.labels = labels label_counts = np.bincount(labels) major_label = label_counts.argmax() minor_label = label_counts.argmin() self.major_indices = np.where(labels == major_label)[ 0 ] self.minor_indices = np.where(labels == minor_label)[ 0 ] np.random.shuffle(self.major_indices) np.random.shuffle(self.minor_indices) self.used_indices = 0 self.count = 0 self.n_samples = n_samples self.batch_size = self.n_samples * 2 def __iter__ (self): self.count = 0 while self.count + self.batch_size < len (self.major_indices): # 多数派データ(major_indices)からは順番に選び出し # 少数派データ(minor_indices)からはランダムに選び出す操作を繰り返す indices = self.major_indices[self.used_indices:self.used_indices + self.n_samples].tolist()\ + np.random.choice(self.minor_indices, self.n_samples, replace= False ).tolist() yield torch.tensor(self.features[indices]), torch.tensor(self.labels[indices]) self.used_indices += self.n_samples self.count += self.n_samples * 2 コメントでも触れられている↓の箇所のコードが今回の肝となる操作です。 indices = self.major_indices[self.used_indices:self.used_indices + self.n_samples].tolist()\ + np.random.choice(self.minor_indices, self.n_samples, replace= False ).tolist() 多数派データのインデックス(major_indices)からは順番に選び出し、少数派データのインデックス(minor_indices)からはランダムに選び出す操作をしています。それぞれのラベルから n_samples ずつ取り出して結合しています。 この操作は後でイテレータとして用いるので、 def __iter__(self) で定義しています。 yield の出力はたとえば下記のようになります。 test_iter = BinaryBalancedSampler(features=train_features, labels=train_labels, n_samples= 50 ) for i in test_iter: feature, label = i print (feature.shape) print (label.shape) print (np.bincount(label)) break > torch.Size([ 100 , 29 ]) > torch.Size([ 100 ]) > [ 50 50 ] 1イテレーションあたりに特徴量データと対応するラベルを返しています。ここでは n_samples=50 としたので2ラベル分で100個のデータが生成されています。ラベルの分布も [50 50] と均一になっているので、想定通りのミニバッチが作成できていそうです。 実験 不均衡データを普通に学習させた時と、均一になったミニバッチで学習させた場合の精度の差を見てみます。 学習結果の全容は こちらのgit に載せたので、ここでは簡単に説明していきます。 前処理 サンプルデータセットにはtensorflowの下記チュートリアルと同じものを用いました。 https://www.tensorflow.org/tutorials/structured_data/imbalanced_data こちらも不均衡データをどのように分類するかという内容になっています。 import pandas as pd from IPython.display import display raw_df = pd.read_csv( 'https://storage.googleapis.com/download.tensorflow.org/data/creditcard.csv' ) display(raw_df.head()) 読み込んだデータは上記のような中身になっていて、 Class 列が分類対象のラベルです。 このラベルの分布が偏っていることを確認します。 import numpy as np neg, pos = np.bincount(raw_df[ 'Class' ]) total = neg + pos print ( 'Examples: \n Total: {} \n Positive: {} ({:.2f}% of total) \n ' .format( total, pos, 100 * pos / total)) > Examples: > Total: 284807 > Positive: 492 ( 0.17 % of total) 1のラベルが全体の0.17%しかないという偏り具合で、かなりの不均衡データになっています。 続いてチュートリアルと同じように前処理し、訓練データとテストデータに分割します。 from sklearn.model_selection import train_test_split # Cleaning cleaned_df = raw_df.copy() # `Time` カラムは不要 cleaned_df.pop( 'Time' ) # `Amount` カラムは数値のレンジが広すぎるので対数化 eps = 0.001 cleaned_df[ 'LogAmount' ] = np.log(cleaned_df.pop( 'Amount' ) + eps) # split train and test. train_df, test_df = train_test_split(cleaned_df, test_size= 0.2 , random_state= 0 ) # split label and feature train_labels = np.array(train_df.pop( 'Class' )) test_labels = np.array(test_df.pop( 'Class' )) train_features = np.array(train_df) test_features = np.array(test_df) 使用するネットワークはかなりシンプルにしました。 import torch import torch.nn as nn import torch.nn.functional as F class SimpleNet (nn.Module): def __init__ (self): super (SimpleNet, self).__init__() self.fc1 = nn.Linear( 29 , 32 ) self.fc2 = nn.Linear( 32 , 16 ) self.fc3 = nn.Linear( 16 , 1 ) def forward (self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = torch.sigmoid(self.fc3(x)) return x 入力される特徴量が29ユニット、最終的に予測するラベルは0 or 1なので出力は1ユニットにしています。 その他、損失関数とオプティマイザーは下記を用いました。 import torch.optim as optim criterion = nn.BCELoss() # BCELoss: Binary Crossentropy optimizer = optim.Adam(simple_net.parameters(), lr= 1e-3 ) 普通に学習させた結果 学習部分のコードは下記のようになっています。 PyTorch特有の Dataset, DataLoader を使ってて見慣れない部分があるかもしれませんが、単純にミニバッチサイズは1000、エポック数は2で訓練させただけの普段通りの処理です。読み飛ばして構いません。 from torch.utils.data import Dataset, DataLoader class MyDataset (Dataset): def __init__ (self, feature, label): self.feature = feature self.label = label self.data_num = feature.shape[ 0 ] def __len__ (self): return self.data_num def __getitem__ (self, idx): out_feature = self.feature[idx] out_label = self.label[idx] return out_feature, out_label BATCH_SIZE = 1000 trainset = MyDataset(feature=train_features, label=train_labels) trainloader = DataLoader(trainset, batch_size=BATCH_SIZE, shuffle= True ) testset = MyDataset(feature=test_features, label=test_labels) testloader = DataLoader(testset, shuffle= False ) # training for epoch in range ( 2 ): running_loss = 0.0 for i, data in enumerate (trainloader, 0 ): inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = simple_net(inputs.float()) loss = criterion(outputs, labels.view(- 1 , 1 ).float()) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 100 == 99 : # print every 100 mini-batches print ( '[%d, %5d] loss: %.3f' % (epoch + 1 , i + 1 , running_loss / 100 )) running_loss = 0.0 print ( 'Finished Training' ) 次に学習し終えたネットワークでテストデータのラベルを予想させます。 pred = [] Y = [] for x, y in testloader: with torch.no_grad(): output = simple_net(x.float()) pred += [ 1 if output > 0.5 else 0 ] Y += [ int (l) for l in y] テストデータの予測結果をconfusion matrixで可視化すると下記のようになりました。 全データをラベル0(多数派)と予測していて、うまい具合に不均衡データの罠にハマっています。 ミニバッチでunder samplingして予測した結果 実装例のコードで書いた BinaryBalancedSampler を用いて学習させます。 少数派ラベルのサイズを考慮して、ここではミニバッチサイズは100、エポック数は2にしています。 balanced_net = SimpleNet() balanced_loader = BinaryBalancedSampler(features=train_features, labels=train_labels, n_samples= 50 ) criterion = nn.BCELoss() # BCELoss: Binary Crossentropy optimizer = optim.Adam(balanced_net.parameters(), lr= 1e-3 ) # training for epoch in range ( 2 ): running_loss = 0.0 for i, data in enumerate (balanced_loader, 0 ): inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = balanced_net(inputs.float()) loss = criterion(outputs, labels.view(- 1 , 1 ).float()) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 1000 == 999 : # print every 1000 mini-batches print ( '[%d, %5d] loss: %.3f' % (epoch + 1 , i + 1 , running_loss / 1000 )) running_loss = 0.0 print ( 'Finished Training' ) このネットワークでテストデータを予測させると下記のようになりました。 多数派ラベルの巻き込みも少なく(187 サンプル)、少数派ラベルの9割弱(88サンプル)を当てることができました。 実験のまとめ サンプルに使った不均衡ラベルのデータセットについては、ミニバッチでunder samplingするアプローチは有効そうです。 感想 この方法はミニバッチごとには均一なデータセットが得られますが、バッチ全体で見れば少数派データを複数回ピックアップするので、ある意味オーバーサンプリングとなり過学習の心配があります。early stoppingなどを組み合わせる必要がありそうです。 (あと発表が2015年とやや古い...) とはいえ実装はとても簡単なため、実務でサクッと試せる点が個人的に気に入っています。
こんにちは。バックエンドエンジニアの岡本です。 ネットショップ作成サービス「BASE」の新規機能開発や既存機能の改修・運用を担当するShop Groupに所属しています。 今回は私が入社後初めてアサインされたプロジェクトであるメールマガジンApp(※)のアップデートを通して経験したこと・考えたことをバックエンドエンジニアの視点から振り返っていこうと思います。 ※ネットショップ作成サービス「BASE」の拡張機能であるBASE Appsの機能の一つ プロジェクトメンバー構成 ・プロジェクトマネージャー ・デザイナー ・フロントエンドエンジニア ・バックエンドエンジニア(私) メールマガジンAppアップデートの場合、上記4つの役割に対して1人ずつメンバーがアサインされました。1人ずつと言ってもそれぞれの担当を全て1人でやるということではなく、入社間もないメンバーにはメンターがアサインされるなどフォロー体制が整っています。設計レベルからメンターや所属グループメンバーにアドバイスをもらいつつ、プロジェクトの主担当として作業を進めて行きます。 プロジェクトの流れ プロジェクトの流れとしては大きく下記の7つのフェーズに分けられます。 キックオフミーティング 設計・見積もり 実装 QAテスト リリース KPT(振り返り) プロジェクトお疲れ様会 1. キックオフミーティング プロジェクトの始まりにまずプロジェクトマネージャー(以下、PM)からプロジェクトの方向性・意味・想定仕様についての説明があります。PMが事前に用意したドキュメントをベースにざっと説明を行い、その後プロジェクトメンバー全員で不明点の確認や仕様の妥当性を議論していきます。 もちろん1度のミーティングでは仕様が確定しないことがほとんどなので、都度メンバー間でコミュニケーションを取りながら議論を深めていきます。 2. 設計・見積もり 大方の仕様が確定したタイミングでエンジニアは設計と見積もりを行います。BASEでは基本的にフロントエンドとバックエンドで実装担当が分かれているので、APIのレスポンス形式やバリデーションロジックについてそれぞれ意見を出し合い実装の方向性を決めていきます。 実装の方向性が決まった後、次は見積もりも含めた設計ドキュメントの作成に取り掛かっていきます。APIドキュメントについては API blueprint を利用し、その他バリデーションやDB関連の設計資料等は Kibela 記事にまとめていきました。設計ドキュメントが完成したタイミングで所属グループのメンバーにレビューしてもらいます。レビュー後のフィードバック対応が完了したらいよいよ実装フェーズに進みます。 3. 実装 実装フェーズは、 API実装 プルリクエストを作成し所属グループメンバーにレビュー依頼 フィードバック対応 レビュアーからLGTMを貰いレビュー完了 という作業をひたすら繰り返していきます。設計においても同様ですが実装フェーズにおいても所属グループメンバーからAPI単位で都度レビューしてもらえる環境があり、所属グループが支えてくれる感覚が実感としてありとても心強かったです。 4. QAテスト 開発が一通り完了し、プロジェクトはいよいよ佳境に入ります。QAテストはプロジェクトメンバー全員で分担し、それぞれが最終的なチェックを責任を持って行っていきます。比較的大きなデザインの課題が発見されたりするなど多少の問題は生じましたが、それぞれのメンバーがMove Fastに対応することで大きな滞りなくQAテストを完了させることができました。不具合の修正が完了したら後はリリース予定日を待つのみです。 5. リリース そして遂に2月3日(月)に本番環境へリリースし、特に大きなトラブルはなく無事に完遂することができました。リリース後は自分自身が関わったサービスを通してユーザー(オーナーズ)の活動を支えることができたという実感があり、エンジニア冥利に尽きるいい仕事だなとしみじみ感じました。 6. KPTでプロジェクトを振り返り 余韻に浸りたいところですがリリースが完了してもまだプロジェクトは終わりません。今回のプロジェクトから学びを得るべくKPTを行いました。内容としてはKPT(Keep・Problem・Try)ごとに一定時間(20分くらい)で意見を出し合い、最終的に皆で共有し今後のプロジェクトに生かせるようにドキュメントにまとめました。 <メールマガジンAppアップデートのKPT> Keep スケジュール通りリリースを完遂できたこと メンバー間でスムーズなコミュニケーションが取れたこと Problem QAテストで比較的大きい仕様の修正が入ってしまったこと Try 仕様のマスタドキュメント更新を全てのメンバーが責任を持って都度行う QAテストの段階で大きい修正が入らないようにできるだけ早い段階でテスト環境に動くソースをデプロイし、全てのプロジェクトメンバーが現状の実装状況を把握できるようにする 7. プロジェクトお疲れさま会 BASEには懇親会費用の補助制度があり、プロジェクト完遂後の打ち上げにもこの制度を利用することができます。プロジェクトの打ち上げは社内でも恒例となっており、今回は鉄板ステーキ屋に食事に行きました。入社後初プロジェクトをやり切った達成感からかはたまた高級店だった為か、お肉がとても美味しかったです。 プロジェクトを通して特に印象深かった事 改めてコミュニケーション大事 開発において手戻りを無くす為にエンジニア間のコミュニケーションは大事ということはもう周知の事実だと思いますが、やはり1番大事だなとプロジェクトを通し改めて感じました。コミュニケーションを密にとることの効用は何も認識齟齬だけではなく、信頼関係の構築という意味においてもプロジェクトの成功にとって避けては通れないものだと思います。 上記を意識してコミュニケーションを取った結果、今回のプロジェクトマネージャーからは下記の発言があり、プロジェクトを通して一定の信頼関係が構築できたと感じています。 またプロジェクトを通して弊社のSpeak Openlyという行動指針は何度も私の背中をそっと押してくれました。 「ここで確認するのはちょっと余計かな?」「この仕様少し疑問が残るけどどうしよう。」などと思った際、この行動指針を思い出すことで発言できる機会がいくつもありました。そして私のその発言に対してプロジェクトのメンバーはいつも真摯にHRTの精神を持って向き合ってくれました。 <HRTの精神> 謙虚(Humility) 世界の中心は君ではない。君は全知全能ではないし、絶対に正しいわけでもない。常に自分を改善しよう。 尊敬(Respect) 一緒に働く人のことを心から思いやろう。相手を一人の人間として扱い、その能力や功績を高く評価しよう。 信頼(Trust) 自分以外の人は有能であり、正しいことをすると信じよう。そうすれば仕事を自分以外の誰かに任せることができる。 出典: Team Geek ―Googleのギークたちはいかにしてチームを作るのか オーナーシップを持ってプロダクトを開発できるエンジニアは幸せ BASEには役割に関係なくプロダクトをより良いものにする為に仕様やデザインに対して積極的に意見することができる環境があります。今回のプロジェクトでの最も大きい個人的な気づきとして、エンジニアがオーナーシップを持って開発に取り組むことの楽しさがありました。前職では受託開発を中心に担当していた私からすると、この自由でかつ責任感が伴う開発の現場はとてもスリリングで心地良いものでした。 サービスを自分自身の課題として捉えることは開発に対しての強いモチベーションにもなり得ると私は思います。そして何よりエンジニア・PM・デザイナーなどの役割を超えて1つのチームとして同じ課題を解決していく過程はとても楽しく幸せなものです。 最後に BASEを一緒に支えてくれるエンジニアを絶賛募集中です!より良いプロダクトの開発の為に一緒に働きませんか? open.talentio.com
Native Application Groupの 大木 です。BASEでは、購入者向けのショッピングアプリ「 BASE 」、「 BASEライブ 」、ショップオーナー向けのショップ運営管理アプリ「BASE Creator」の3つのスマホアプリをリリースしております。今回は、その中の一つBASE Creatorを、React Nativeで置き換え、リリースしてみての話を、お伝え出来ればと思います。 課題と動機 https://help.thebase.in/hc/ja/articles/206417201-BASE-Creator- とはなんですか- BASE Creatorは、基本的にはWebViewで画面を表示するいわゆるガワネイティブアプリというものです。Webアプリとの違いは何処にあるかといいますと、商品が売れたり、購入者からメッセージが届くと、Push通知でお知らせ出来る機能があるところです。 アプリを運用していて出て来た課題としては、下記の点です。 現状、問い合わせによる不具合報告に対応するために、iOS/Androidのコードベースそれぞれを確認する必要がある スマホアプリは合計6つあり、チームメンバーも少数のため、WebViewベースのこのアプリは極力共通化したい。 これらの課題を解決するために、クロスプラットフォーム開発フレームワークの検討をしました。 React Native か Flutter か 開発プラットフォームとして上がったのは、React NativeとFlutterでした。どちらを選んでも問題は無さそうですが、今回はReact Nativeを採用しました。 重要視するポイント 既存技術を流用出来るか(社内、一般的なNative/Web技術含め) クロスプラットフォーム開発のノウハウが蓄積されているか 特に重要視していないポイント 統一されたガイドラインのようなものがある 新しい技術で開発 Flutterの方が後発で、挑戦してみたい気持ちもありました。しかし、Reactやnpmの技術を流用出来て、場合によってはWebのフロントエンドエンジニアでも対応できる可能性があるため、React Nativeを選択しました。 置き換えてみてどうなったか 結論から言うと、色々な良い影響をもたらすことが出来ました。 アプリケーションロジックを共通化出来た 最大の課題であった、 iOS/Androidそれぞれのコードベースを一つにする ことが出来、アプリケーションロジックを全てTypeScriptで記述することに成功しました。ネイティブ側のObjective-CやJavaのコードは、起動時に必要なコードやFirebase SDK用の初期化時コードくらいです。 別々のスキルセットを持つエンジニアとのコミュニケーションが増えた React Nativeで開発するとなると様々なスキルセットを必要とするため、Pull-Requestベースでレビューをもらいながら開発していきました。 メンテナンス可能なReact Nativeアプリを開発し続けるための構成を模索し構築できた Expoで管理するJavaScript/TypeScriptのみ動作させる環境を含むワークフローは"Managed workflow"と呼ばれます。対して、それを使わずNativeのIDE/SDKを使い、自分たちでReact Nativeの環境を整えるのを、Expoでは"Bare workflow"と読んでます。今回はFirebaseのNative SDKを含んだReact Nativeのモジュールを使うため、最終的にはこの"Bare workflow"でリリースする必要がありました。ただ、Expoの"Managed workflow"も素早く動作確認できる検証用環境として捨てがたいです。 幸いなことに、2019年はReact Native Re-Architecture実践の年で様々な改善をしていました。その動きに呼応するようにExpoもSDKに同梱されていたモジュールを分割するようになり、Expoベースの"Managed workflow"を使わなくても、Expoの便利モジュールを使えるようになりました。 両方の動作環境を一つのプロジェクトで共存させるために、 monorepo という仕組みを導入しました。この仕組みは、 Babel や React でも採用されています。 リポジトリのディレクトリ構成は、最終的に次のようになりました。 . ├── @types │   ├── metro-config │   └── react-web-vector-icons ├── docker │   └── android ├── packages │   ├── app │   ├── cli │   ├── components │   ├── core │   ├── expo-starter │   ├── functions │   ├── native-starter │   ├── platform │   ├── rn-metro-configurator │   └── tsconfig-paths-transformer └── tools ├── bin └── docker-bin また、分割したモジュールをどのように使うかの簡易的な概念図は次の通りです。 図の native-starter というモジュールが、通常のネイティブのIDE/SDKの設定と実装が必要な"Bare workflow"で動作させるためのエントリポイントとなっております。このモジュールからDebugビルドのアプリを起動したり、ストアリリースのためにReleaseビルドを作成したりします。また、 expo-starter というモジュールが、検証用にExpoの"Managed workflow"で動作させるためのエントリポイントとなっています。 monorepo構成で、React Nativeアプリを動作させるためには、色々なテクニックがいるのですが、長くなりそうなため、機会があれば別途解説記事を書ければと思っています。 Storybookの導入 React Nativeのコンポーネントを作成するに当たって、アプリケーションの特定の状態でしか発生しないUIの状態があり、その状況を再現したUIを素早く実装するためにStorybookを導入しました。 CodePushの導入 CodePush は、ユーザー端末に直接アプリの更新を配信できるMicrosoftのApp Centerのサービスです。リリース後、特定の古いOSでアプリが動作しない不具合が発生した時に、これを導入していたおかげで緊急アップデートをすることができました。 iOS 12.1以下で動作しないメソッドを使ってしまったため、CodePushで修正アップデート iOS 12.2以上でないと、 Object.fromEntires というメソッドは使えません。次のように修正し、 リリースした当日 に緊急アップデートで対処することができました。 @@ -42,14 +42,23 @@ export class FirebaseEventTracker implements EventTracker { } eventParams<T extends { [x: string]: string | number } = {}>(params: T) { - return Object.fromEntries( - Object.entries(params).map(([k, v]) => { - if (typeof v === 'string' && v.length > 0) { - return [k, v.substr(0, 100)]; - } - return [k, v]; - }) - ); + const values = Object.entries(params).map(([k, v]) => { + if (typeof v === 'string' && v.length > 0) { + return [k, v.substr(0, 100)]; + } + return [k, v]; + }); + + if (Object.fromEntries) { + return Object.fromEntries(values); + } + return values.reduce<{ + [x: string]: string | number; + [x: number]: string | number; + }>((acc, [k, v]) => { + acc[k] = v; + return acc; + }, {}); } React Native環境で利用できるJavaScript/npmの機能はできる限り試した これはチーム開発関係なく、個人的な興味になるのですが、ブラウザ環境でもNode環境でもないReact Native環境でどの程度最新技術をサポートしているのか、限界はどこかということを知っておきたいと考え、色々な検証をしました。 TypeScriptのDecorators導入 JavaのAnnotationのようなものが使いたくなり、TypeScriptの Decorators を導入しました。API Clientで、pathを定義したり、HTTP MethodがGETなのかPOSTなのかを定義したりで、次のような感じで使っております。 export default class StableVersionRequestService { private requestBuilder: RequestBuilder ; constructor( requestBuilder: RequestBuilder ) { this .requestBuilder = requestBuilder ; } @post @path ( '/path/to/check-api' ) checkVersion ( parameters: ServiceRequest < Data >) { // [...] IoCコンテナ(スプラッシュ画面の例) 複数のReact Native動作環境に対応するのであれば、当然使えるモジュールも違ってくることがありうると思いました。実際、アプリのスプラッシュ画面表示は、Expoの"Managed workflow"と"Bare workflow"とでは、利用できるモジュール異なることもあります。そこで、I/Fと実装を紐付けるためにIoCコンテナを導入しました。 まず、実装すべき共通のインタフェースを定義します。 export default interface Splash { preventAutoHide () : void ; hide () : void ; } export const SplashSymbol = Symbol. for( 'Splash' ); expo-starter では、Expoの SplashScreen が使えるので、次のように実装します。 import { SplashScreen } from 'expo' ; import { Splash } from '@universal-webcreator/app' ; export const splash: Splash = { preventAutoHide: () => SplashScreen.preventAutoHide (), hide: () => SplashScreen.hide (), } ; native-starter では、 react-native-splash-screen を使うように実装します。 import SplashScreen from 'react-native-splash-screen' ; import { Splash } from '@universal-webcreator/app' ; export const splash: Splash = { preventAutoHide: () => { // ネイティブ側のコード(Objective-C, Java)でスプラッシュを表示するため何もしない } , hide: () => SplashScreen.hide (), } ; そして、IoC Containerを使い、I/Fと実装を紐づけます。 export const dependencies = new ContainerModule ( bind => { // [...] bind < Splash >( SplashSymbol ) .toConstantValue ( splash ); // [...] } ); その他 その他、試して導入した機能は、主に次の通りです。 TypeScript 3.7 Redux/Redux-Saga React Hooks TypeScript Compiler API TypeScript Generics React Native開発の一年 この1年でReact Nativeならではの辛さや問題にも遭遇しました。 React Native環境構築 (2019年3月) 他のタスクがひと段落したため、本格的に環境構築を開始しました。公式ドキュメントを参考にしながら、ほとんどつまづくことなく開発することができました。 Jestを使ったスナップショットテスト を導入したり、CircleCIでlintやスペルチェックを回して、少しづつ環境を整えていました。 この時に使用した技術は次の通りです。 TypeScript v3.3.3333 Expo v32.0.5 Node v11.10.1 Jest v24.1.0 WebViewベースのアプリを開発するのに致命的な不具合を発見(2019年3月後半) 初回起動画面の実装やある程度開発環境も整え、要のWebViewを使った実装を行っていこうと考え、React Native本体の WebViewコンポーネント を使って動作確認しました。予想に反して、このコンポーネントには深刻な不具合があり、それにより、プロジェクトの存続すら怪しくなっていました。 完成度が低いReact Native本体のコンポーネントが、Communityに移管され難を逃れる 開発開始直後は、React Native関連の状況を理解していなかったのですが、2018年の Lean Core という提案により、WebViewの開発は、React Native Communityに移管されることに決定していたようです。 今のReact Native動作環境は、Expoの"Managed workflow"を利用しているため、すぐには移管されたWebViewは使えません。そのため、この環境を諦めるかどうかの選択に迫られました。そんな時、 Expo Developers のSlackにJoinしてみると、次のExpoのアップデートで、 React Native Community版のWebView に差し替えるという話があったため、他の開発をしながら、リリースを待つことにしました。 Expoのアップデートを待つ間に、他の機能を開発(2019年4月-2019年6月) 要のWebViewは不具合で開発を進められなかったため、他の機能を開発していくことにしました。待っている間に実装していた昨日は次の通りです。 IoCコンテナの導入 Core/Platformモデル・ユースケースの定義 Redux/Redux-Saga導入 CI用Node環境の整備 APIクライアント実装 文言の管理のため、i18next導入 TypeScriptのpathsによるモジュールパス解決に試行錯誤 一番最後のモジュールパス解決は試行錯誤していましたが、調べてみても、React Nativeのバージョンが古い時の方法であったり、パス解決の仕組みがよく分かっておらず、この時は断念しました。後日、最終的にMetroやBabelのモジュールパス解決の方法を組み合わせることで解決することができ、monorepo構成に移行するまでうまく動作しておりました。 Expo v33リリース!(2019年6月) 待てど暮らせど次期バージョンアップデートのアナウンスがなく、Expo DevelopersのSlackを定期的に覗く日々でした。様々な人が次のリリースはいつ?のような発言をしていた印象です。 いつものようにSlackをのぞいていると、いくつかのIssueを片付けたらリリースできるよと発言がありました。GitHubのリポジトリを確認してみると、すでにExpo v33があり、インストール可能な状態になっていました。 公式アナウンス を待ちきれず試してみると、普通に動くWebViewと対面することができました。 未知との遭遇(2019年6月) 基本的には動作していたWebViewですが、2つほど問題がありました。 target=_blank の挙動に準拠せず、勝手に同一WebViewで開いてしまう。 React Native側からWebViewでJavaScriptを実行しても実行結果を受け取れない。 これらの挙動のため、HTMLの何処に target=_blank のリンクが存在するか自分で確かめなければなりません。WebViewのJavaScript環境は、ユーザーの端末に依存するため、出来るだけレガシーなJavaScriptを書いてWebViewで動くようにしました。(ブラウザをアップデートしていないユーザーは古い機能しか利用できない)。また、BASEの管理画面は、 Vue + CakePHPで構築 されており、機能内のRoutingはVue Routerによる画面遷移で react-native-webview では検知できません。 そこで、 JavaScript : コールバックがないならDOMの変更を監視する を参考に、MutationObserverを使ったDOM変更監視で、Vue Routerでの画面遷移時に、 target=_blank のリンク取得するようにしました。 目的のリンクは取得できるようにはなりました。しかし、WebViewでJavaScriptを実行しても、実行結果を返すことはありません。一応 window.ReactNativeWebView.postMessage を使うことで解決が可能です。しかし、 window.ReactNativeWebView となっているので、 window オブジェクトにいつこれが設定されるかが問題で、ユーザーがリンクをタップするのが早いか、React Native側に実行結果を送信するのが早いかわからないという問題には、完全に対処できませんでした。 Expoの"Managed workflow"と"Bare worflow"を共存させる試みの失敗(2019年6月-7月) 結論から言うとこの試みは失敗しました。最初次の構成にすればうまくいくと考えました。 共通の部分は"Managed workflow"ベースで実装 それぞれの実行環境で必要な部分は共通のinterfaceを定義し、それに準拠させる実装をする 上の定義と実装を、ejsのテンプレートにし、コードジェネレーターやスクリプトで切り替える しばらく進めてみたのですが、ふと冷静に考えてみて、今後のReact Nativeのアップデートに追従していくためには、こんな複雑な仕組みメンテナンスできないと思い、考え直すことにしました。 monorepo構成の導入(2019年7月) しばらく色々試して迷走していましたが、1つのプロジェクトで複数のモジュールを管理できるような仕組みがあれば良いのではと考え、調査したところmonorepoについて知りました。monorepoを実現するツールの一つである Lerna を導入し、試してみました。この構成は現在も使っており導入してよかったと思いました。 実行環境の共存も、エントリポイントとなるモジュールを分割することにより別々に実行できるようになりました。 実行環境共通で使えないモジュールの解決は、共通interfaceを定義しそれぞれの実行環境ごとに実装したものを、IoCコンテナを通じてアクセスすることによって利用できるようになりました。この辺は 前述のIoCコンテナ(スプラッシュ画面の例) に例を載せています monorepo構成への完全移行とネイティブIDE/SDKの設定(2019年7月-8月) monorepoへ移行してからも、TypeScriptのpathsを使ったモジュール解決を使っていましたが、エディター上でモジュールをインポートしようとすると、ローカルPCのフルパスでインポートしたり、importの重複が発生したり何かと問題が発生していました。 pathsを使ってはいたのですが、そもそも機能ごとにグループ化し擬似モジュールのように扱っていたに過ぎません。モジュールのように扱っていたのであれば、monorepoの中でモジュールとして分割した方が管理しやすくなると考え、pathsで管理していたディレクトリーをそのままモジュールとして切り出すことにしました。これによってモジュールのインポート問題は解決することができました。 monorepoでJavaScriptレイヤーの問題は解決することはできましたが、ネイティブ側の設定はnpmのエコシステムに自動的に対応している訳ではありません。最終的に解決はできましたが、様々な問題が発生しました。それに関しては長くなるためここでは省略します。 毎回アプリを起動してUI実装するの面倒なためStorybookを導入(2019年8月-9月) 基本機能はほぼ出来上がっていましたが、次はUIの微調整が待っていました。毎回アプリを削除して、目的の画面まで遷移して特定の状態にして確認という作業を、繰り返していたため、フラストレーションが溜まっていました。 そこで、UIの状態を固定で確認できれば、アプリの実行環境で確認しなくてもいいのでは?と考えStoryboardを導入しました。結果的に、ブラウザ上で素早く確認できるようになりました。ちなみに、Storybook for React Nativeは使っておらず、Storybook for Reactをベースに react-native-web を利用しました。 リリースビルドをCircleCIで成功させるのに四苦八苦(2019年9月-11月) アプリのテスト配信には、Microsoftの App Center を選びました。React Nativeアプリの場合、CodePushを使えるのが採用した大きな理由です。 さて、配信サービスが決まったので、CircleCIでリリースビルドを作成してみるとなかなか上手くいきません。しかも一回のビルド時間が40分以上かかり確認にも時間がかかります。ここをクリアしないとリリースもできません。 絶対にローカルPCでリリースビルドを作るという作業はしたくありません。 この作業では、次のような問題が発生しました。 Androidビルドで、Javaメモリーエラーが発生する ビルドは作成できたが、署名が異なるため、端末にインストールできない アイコンフォント対応ライブラリで、リリースビルドのみアイコンが表示されない ビルド時間が長い またこの時期、iOS 13へのアップデート対応で、他アプリの対応もしなくてはならず、なかなか時間が取れませんでしたが、一つ一つ解決していきました。 QA(2019年11月後半) iOS 13へのアップデートもひと段落し、やっとリリース準備に漕ぎ着けました。- Done is better than perfect - のようにまずは終わらせろの気持ちでいた私ですが、全体を通しての動作確認はしておりません。そう、QAをしなければなりません。 QAフェースでも色々問題が発生しました。これは主にAndroidの知識不足が原因でのことです。 Androidのバックボタンでホームに戻って、アプリを再度開くと、React Nativeのコンポーネントが再生成されるのに、UI周りの再初期化できてない 不要な権限をAndroidManifestで消せてない また、QAを行なっていると、利用しているライブラリに関しても色々と気づくこともあり、修正Pull-Requestを送ったりしました。 use lane_context for android mapping text [expo-web-browser] [ios] Fixed SFSafariViewController presentation style リリース(2019年11月末) まだまだ、改善するところはあったのですが、紆余曲折を経て、リリースすることができました。 2019/11/27 Play StoreでAndroid版リリース 2019/12/2 App StoreでiOS版リリース リリース後の色々(2019年12月-) リリース後、Play StoreやApp Storeに再申請するほどの深刻な不具合が発生しなかったことにまずは安心しました。ただし、ユーザーに影響のあるものや運用開発環境の考慮漏れなど、いくつかの問題は発生してしまいました。確認不足やAndroidの知識不足に起因するものが多かったです。いくつか上げると次のようなことです。 古いOSで動作しないJavaScriptの機能を使ってしまっていた Android Crashlyticsの設定ミス Android CodePushの設定ミス まとめ 今回のプロジェクトでは、本当に多種多様な技術に触れ、大変なことも多かったのですが、様々な人の助言もありなんとか終わらせることができました。また、長期的な運用やチームでの開発を考えた実装を深く考えられた良い機会でした。React Nativeだからネイティブのことを考えなくてよい訳ではなく、ネイティブ起因の様々な問題が発生しました。その時には同じチームのAndroidエンジニアにも度々助けられました。 今、React NativeやExpoを取り巻く環境は、2019年の一年間で大幅に変化しそして改善しています。先日、 Shopifyの記事 にReact Nativeのことが書かれていました。2020年の今なら、React Nativeを採用するのも悪くない選択肢かなと思います。
この度は、2/9(日)~2/11(火)に練馬区の産業プラザ Coconeri ホールにて開催された PHPerKaigi2020 にプラチナスポンサーとして協賛し、また4名のメンバーが登壇しました。 今回は上記メンバーの他に一般聴講者として参加した4名のメンバーからの参加レポートをお届けします! 参加レポート(栗田) 概要 Service Dev Sectionに所属しています 栗田 です。 2020年2月9日(日)〜2月11日(火・祝)に開催されたPHPerKaigi 2020に聴講者として参加しました。 この記事は2日目の 2月10日(月)の参加レポートとなります。 当日は弊社から2名のトークセッションがあったりと、自分の知り合いのトークセッションがあり 盛りだくさんでした。 セッションを聞いたり、おかしをもぐもぐしたり、参加者と交流したり 茶会に参加したりなど充実した1日を過ごしました! 本文 練馬で行われた会場に到着 ここで受付です。 色々な会社のノベルティや自分専用にカスタマイズされたトレーディングカードもいただきました。 まずはじめにオープニングトークに参加しました! カンファレンスに対する想いが心に残りました。 今回弊社はPlatinumスポンサーとして協賛しております。 自分の会社のロゴが大勢の場の前に出るのは純粋に嬉しいですね。 同僚の @o0h_ さんのトークセッションに参加!! 続いて同僚の @budougumi0617 さんのトークセッションに参加!! 弊社から連続のトークセッションになり勢いを感じました。 ひと休憩して、ランチセッションではお弁当をいただき 午後は↓などに参加しました。 レンサバけもの道 磯野ー、MySQLのロック競合を表示しようぜー もっと気軽にOSSにPRを出そう! 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? LT... etc などを聞きました。様々なスタイルのセッションがありどれも為になるお話でした。 詳細はリンクよりご確認ください。 最後に茶会に参加して参加者と更に交流を深めました。 ※ GMOインターネット株式会社さまからのご提供でした。 茶会にはボドゲなどの交流グッズなどもあり 様々なスタイルで交流する事ができました。 名刺代わりに交換をしたトレーディングカードも大分集まりました! 最後に 自分はPHPカンファレンスの参加経験はありましたが、PHPerKaigiは初の参加でした。 一言で感想をいうとアットホームな温かい場だと感じました。 ・初心者、上級者、老若男女 誰でも楽しめる場 ・コミュニケーションの場 ・1年に1回、ともだちと会う場 とオープニングセッションで紹介がありましたが、まさにそのとおりだと感じ コミュニティの暖かさを感じました。 自己紹介がしやすいようにトレーディングカード風の名刺をいただいたり スタッフの方がフレンドリーに話しかけてくれたり いたる所で参加者同士で気軽に交流を行われたり PHPerトークンを探しだす、PHPerチャレンジイベントなどもあり 初めての参加でも参加しやすいカンファレンスだなと感じたのと コミュニティの熱量を感じた1日でした。 参加レポート(長澤) SREに所属していますngswです。得意な領域はインフラ側で、前職でPHPも触ってきました。 今回はインフラ側への言及があった以下の3トークについて感想を書きたいと思います。 レンサバけもの道 by uzulla | トーク | PHPerKaigi 2020 #phperkaigi - fortee.jp 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? by 小山健一郎 | トーク | PHPerKaigi 2020 #phperkaigi - fortee.jp PHPシステムをコンテナで動かすための取り組みのすべて by きくもと | トーク | PHPerKaigi 2020 #phperkaigi - fortee.jp レンサバけもの道 uzullaさん (@uzulla) / Twitter レンサバけもの道 - Speaker Deck 僕は以前ホスティングサーバを提供する側にいたことがありますので「これは他人事ではない」というトークでした。 レンサバ == 共用サーバの旨味である「アップデート追従などのマネジメント」と、その反面にある「使い勝手に関わってくる各種制約条件をどう打破するか」という内容です。SSHから始まり最新のバイナリ利用のためのビルドのtipsなど、自由度の高いセルフマネージドなVPSを渡り歩いて来たからこそ語ることのできる内容が盛りだくさんで聴き応えがありました。このトークの背骨となる「モダンを諦めない」「レンサバでもここまでできるんです」という主張はとてもエンジニアしていて好感が持てました。 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? k1LoWさん (@k1LoW) / Twitter 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? / PHPerKaigi 2020 - Speaker Deck 「既存プロダクトにアサインされたとき、どうすれば開発参加の初動オーバーヘッドを小さくできるか」という主旨のトークでした。 どのようなサービスか どのようなアーキテクチャか どのようにデータを保持するか(=どのようなテーブル設計か) どのような開発環境か(本番環境・開発環境) どのようなコードか 開発 https://speakerdeck.com/k1low/phperkaigi-2020?slide=28 「上記の6つのレイヤの情報が揃えば、あとは本人のスキルにあわせて成果が出せるよね」ということでした。 本質に近いもの(発表では上述した数値の小さい事柄のもの)ほど広い理解を要求される点も共感できる内容でした。 トークの最中「われわれのようなインフラ側の人間だとどういう情報があれば初動オーバーヘッドを小さくできるのかなあ」と考えていました。 個人的な経験では「どのようなサービスか」ということにはあまり頓着しなかった思い出があります。自分が一番はじめにほしいなと思う情報は サービスに利用されるFQDN一覧 サービスが利用するクラウドサービスのエンドポイント一覧 インスタンスの構築手順(コード化されたものも含む)、ないしは相当のものが存在しなければ踏み台へのログイン情報 だと考えています。少なくともこれだけあれば、あとは稼働しているデーモンとポートの関係などはログインして調べてしまえばいいじゃないかと考えてしまう質です。 ただこれがまさにオーバーヘッドなのですよね。そういった意味ではk1Lowさんの発表は高みを目指されていて凄みを感じました。またこの「初動オーバーヘッドを小さくする」ことに成功すれば「採用計画もより柔軟にできるのではないか」という発展性も感じました。われわれの業界はただでさえ即戦力を求めがちなのですから、「単なる一トークではない業界への提言ととってもいいのかもしれない」という気持ちを持ちました。 PHPシステムをコンテナで動かすための取り組みのすべて kikumotoさん (@takakiku) / Twitter PHPシステムをコンテナで動かすための取組みのすべて - Speaker Deck さまざまなツールを駆使してコンテナ化を実現されたその手法と、実際の運用に基づく含蓄のあるアドバイスが詰まっていて、 AWS移行を目指す中、オンプレミスでどこまで移行コストを減らす形にできるかという工夫に満ちたトークでした。 個人的に一番興奮してしまったのは「LVS」の話題が上がったところで、そこでテンションが上がりすぎてしまった結果、最後に変な質問を投げかけてしまったのでその点は個人的な反省点です。 ただLVS氏はものすごく賢くて、当該レイヤでやってほしいこと、これできたら嬉しいなってことをほんとに全部実現してくれるツールなので、テンション上がってしまったことは許していただければ幸いです。 参加レポート(炭田) 概要 Service Dev Sectionに所属しています@tac_tandenです。PHPで3年ほどWebアプリケーションの開発をしています。 自分は2020年2月9日(日)〜2月11日(火・祝)の3日間に参加しました。 カンファレンス初心者の自分にとって、一般聴講者としての参加であっても、最初は入っていけるのか正直不安でした。 しかし、PHPerKaigi 2020は運営のスタッフの方や参加者のみなさんはとてもオープンで、親切で、すごく楽しく素晴らしい経験になりました。 また、今回のPHPerKaigiでは、弊社から4名登壇されていて、登壇がより身近になりました(自分もいつかカンファレンスで登壇してみたいと考えています。) 今回の記事では、アプリケーションエンジニアの自分にとって印象に残った以下の2つのトークについて感想を書きたいと思います。 Deep Module in PHP もっと気軽にOSSにPRを出そう! 本文 Deep Module in PHP いとしょさん (@itosho) / Twitter Deep Module in PHP - Speaker Deck アプリケーションの実装をしていて、実装期間や仕様、依存関係などの制約がありつつも、もっと良いコードを書けないかなと日々試行錯誤されている方も多いと思います。 自分もその一人で、その中で「そもそも良いコードってなんだろう」と自問自答して色々調べたりすることも多いのですが、itoshoさんの発表ではその「良いコード」の一つの指針として、「Deep Module」という考え方があるということを紹介されていました。 発表の中で、良いコード=複雑ではないコードと定義して、多くの機能を提供しながらインターフェースがシンプルなモジュール(=Deep Module)を理想とするという指針を提示されていました。また、実際にDeepなModuleを実装するためのポイントや具体例もあり、とても参考になりました。 設計としての理想を追い求めるよりは、より現実に即した適用しやすい指針なのではないかと個人的には感じます。 Deep Moduleという視点も持って、会社のアプリケーションコードを読む&開発してみたいと思います。 もっと気軽にOSSにPRを出そう! DQNEOさん (@DQNEO) / Twitter もっと気軽にOSSにPRを出そう! - Speaker Deck 日々会社や個人で開発をしていて、OSSにお世話にならない場面はほとんどないと思います。 しかし、OSSへのコミットやPRを出すとなると、自分としては途端にハードルが高くなる気がしていました。 発表の中で、DQNEOさんもハードルの高さを指摘されていましたが、以下の4つの視点を持つことでPRを出しやすくなるとご紹介されていました。 1. 作者の関心ゾーンの外側を見る 2. 使っていなくてもコントリビュート 3. 業務で得たノウハウを横展開 4. ダメ元でも送ってみる スキルアップなどももちろんですが、OSSに日頃お世話になっている「恩返し」として、自分でも微力ながら気軽にPRだしてみようと思います。 また、発表セッションのあとの懇親会でも、DQNEOさんには面白いお話をたくさん聞かせていただきました! ありがとうございました! 発表を聴く中で感じたこと 登壇に関して、人脈を作ったり市場価値を上げるなどいうよりも、単純にすごく楽しそうだし、沢山のエンジニアの皆さんとつながることができるのは、素晴らしい体験なんじゃないかなと感じました。 なにより、登壇されている皆さんが、発表をとても楽しんでいるのが印象的でした。 途中で「なにかネタはないだろうか」と色々頭を巡らせていましたが、皆さんの発表を聴く中で、こうすれば発表できるトピックが作れるのではないかと考えました。 OSSにPRをだしてみる 会社での経験・知識を一般化して横展開できないか考える 自分のサービス開発を運用する 技術ブログを書く また、登壇された皆さんの発表がすごくわかりやすく、楽しめたのですが、聴講者目線で印象に残った登壇テクニック?をまとめてみました 発表に入る前に、発表と関連する経歴や自己紹介があると、なんでこんなことをしているか、なんでこんな発表をしているかがわかりやすくて内容が入ってきやすくなる。 他の発表との関連にも言及してみる。他の発表の知識が使えてより理解しやすくなる気がする。 聴講者にフィードバックが欲しいところを発表の途中途中で伝えてもらえると、発表者にフィードバックしやすい。 発表の中で聴講者に手を上げてもらうような質問をちょいちょい入れていくと、ライブ感がでる。 今後、発表する機会があったらぜひ実践してみたいと思います! 参加レポート (tenkoma) 基盤グループ所属の田中(@tenkoma)です。 3回目のPHPerKaigi参加となります。 参加したセッションの感想とPHPer Code Golf についてレポートします。 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? 知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか? / PHPerKaigi 2020 - Speaker Deck セッションを聞いた理由 自分が途中からプロジェクトに参加するときにどんな情報をあつめればいいのか、最近考えたことがない気がしたから 新しくjoinしてくるメンバーをサポートするための良い方法が知りたかったから でした。そんな疑問を考えるヒントが得られるよいセッションでした。 今だからこそ振り返る register_globals 今だからこそ振り返る register_globals / PHPerKaigi 2020 - Speaker Deck セッションを聞いた理由 最近のPHPって型(カタ)すぎませんか!?(型を使いすぎている、とか思っているわけではありません) 「例えば、PHPを避ける」なんて言われていた時代に、少しの時間でも戻ってみたかった 僕がPHPを使い始めた頃は古くてPHP4.3 を使っていたくらいの時期で、 register_globals = Off が既定値でした。 register_globals = On のアプリケーションを扱ったことはありませんし、 register_globals = On はセキュリティ上問題があるのでOffにするように社内情報共有されていました。 そのためこのセッションは、仕事に生かすかどうかも期待せず、ほぼ息抜きとして聴いたセッションでしたが、PHPの思想に触れることができる味わい深いセッションでした。 PHPer Code Golf by Pixiv 前回は全然参加できなかったな、今回はがんばってみようと思い、 PHPerチャレンジ でコードを探し入力していました。 しかし、1日目にPHPer Code Golf が始まってからは、そちらへの参加がメインになってしまいました。3問ありましたが、正解後もずっとコードを縮めることに注力した結果、表彰いただくくとができました。 PHPer code golf 賞ゲットしました!!pixivさん、楽しい問題を用意していただいてありがとうございます! #PHPerKaigi pic.twitter.com/1v6zlRG7rE — /; SameSite=Tenkoma; Secure (@tenkoma) February 11, 2020 正解するだけで無く、スコアを改善するためには途中のコードをどこかに保管しながらになるのですが、社内の #times_tenkoma にコードスニペットを投稿しながら改善していました。 1日目、帰宅後にベッドの上でFizzBuzzを改善していたときのログ 2日目、クロージング(17:10〜)の直前まで、上級編のコードを改善していたときのログ PHPerチャレンジは11位という結果に終わり残念でしたが、こちらで結果が残せて良かったです。 PHPerKaigi はセッションやIRTはもちろん、懇親会、PHPerチャレンジなど、他の企画も力が入っていて楽しめました。 賞を頂いたときのコードについては個人のブログで ⛳ PHPer Code Golf by pixiv(PHPerKaigi 2020) 上級編の回答について解説 の記事を書きましたので、そちらもご覧ください。 登壇資料 めもりー @m3m0r7 金城 @o0h_ 清水 @budougumi0617 東口 @hgsgtk 謝辞 今回弊社は登壇者含めて計9名のメンバーで参加させていただきまして、その中にはカンファレンス初参加のメンバーもございました。それぞれに刺激を受けたり、持ち帰れるものを得たりして大変実りのある時間を過ごさせていただきました。 それも実行委員長である 長谷川さん をはじめ、実行委員会の皆様のおかげです。心より御礼申し上げます。来年の開催は未定とのことですが、心より楽しみにしております。誠にありがとうございました!
こんにちは、BASE BANK株式会社 Dev Divisionにて、Software Developerをしている東口です。先日、2020年2月16日に開催された、 Object-Oriented Conference にスピーカーとして参加してきました。このカンファレンスには、BASEがブロンズスポンサーとして協賛しています。 非常に高度な内容がタイムスケジュールに並んだ濃密なカンファレンスで、オブジェクト指向や設計話にどっぷり浸かる一日になりました。 Object-Oriented Conferenceとは お茶の水女子大学で開催されたこのカンファレンスは、オブジェクト指向をテーマにした技術カンファレンスです。 ooc.dev Object-Oriented Conference はオブジェクト指向をテーマに、あれこれ共有したり、セッションを聴講することで、みなさんの知見を深めるためのイベントです。 冒頭に書いたとおり、BASEは、ブロンズスポンサーとして協賛しました。 発表内容:デザインパターンの使い方を パタン・ランゲージとの比較から考える 冒頭に上げていますが、この内容の課題感は自分自身が感じる「デザインパターンに対するもやもや感」でした。 GoF(Gang of Four)のデザインパターンは、我々が日々行う設計行為において基礎的な要素を占めています。ですが、一方で「デザインパターンをなんとなく知っている」状態から「デザインパターンを正しく使う」状態へ向かうには、大きな壁があると感じています。 正しく使うためには、その対象を知らないといけないですが、その切り口として、「デザインパターンの前」を見てみようという内容です。それが、パタン・ランゲージであり、この発表はパタン・ランゲージを理解し、その違いを理解することで、デザインパターンとの付き合い方を整理しようとしています。 発表後、ありがたいことにさまざまフィードバックを頂きました。 人は解決策をすぐに求めがちだけど、コンテキスト・問題を正しく理解してどうやって自分が解決策を導くのか、が大事なんだよなぁ #ooc_2020 #ooc_e — isop (@isop) February 16, 2020 『オブジェクト指向のこころ』をまた読み返したくなる素敵なトークだった #ooc_2020 #ooc_e — null | undefined | (@cheezenaan) February 16, 2020 動画で見たのですが、すごく良いセッションでした!問題提起から、数々の文献にあたり、最後に東口さんなりの結論を導きだしたのがとても良かったです。おつかれさまでした :) — Masashi Shinbara (@shin1x1) February 16, 2020 裏話をすると、発表時間を15分〜20分で、パタン・ランゲージとデザイン・パターンを比較するという性質上、パタン・ランゲージとその発想の背景にあるクリストファー・アレグザンダーのデザインに対する考え方を、ぎゅっと短い時間でまとめ上げることが必須で、その整理に非常に工面し、最後の最後まで時間をさきました。 10分でわかるアレグザンダー感がある。いいな。 #ooc_2020 #ooc_e — きょん@アジャイルコーチ、システムアーキテクト (@kyon_mm) February 16, 2020 「クリストファー・アレグザンダーの考え方に精通されている」と僕が認識している @kyon_mm さんにこう言っていただけたので、なんとかうまくいったようで安堵しています。 カンファレンスでアウトプットすることの醍醐味の一つですが、聴講頂いた方のコメントから、さらに関連する知識もいただけました。 アレグザンダーの『ノート』におけるデザインの3段階のモデルとアラン・ケイの"Doing with Images makes Symbols” のスローガンの心理学的な根拠が両方ともジェロム・ブルーナーの子供の認知機能の発達の過程という発見がそういえば最近あったことを思い出した。 https://t.co/HLbiHkOzE4 — Akira Motomura (@akira_motomura) February 16, 2020 とてもよかった ところでパターンが先になくとも原則に従ってリファクタリングする中で自然にデザインパターンが現れてくることを、Uncle Bobはアジャイルソフトウェア開発の奥義の中で「コードがデザインパターンに回帰していく」と表現していました。 #ooc_2020 #ooc_e https://t.co/FjFKWMHb19 — takasek (@takasek) February 16, 2020 発表したことによって、関連する新しい情報をもらえるのは本当にありがたいことです。この他にもたくさんのフィードバックを頂きました。貴重なお時間をいただけたこと、この場を借りて御礼申し上げます。 まとめ Object-Oriented Conferenceは今年初開催のカンファレンスでした。オンラインでの視聴者込みで1000人超えの来場者数で、5トラックある非常に大規模なカンファレンスにもかかわらず、非常に楽しく1日過ごさせていただきました。主催者の @hirodragon112 さんを始め、運営スタッフのみなさま誠にありがとうございました。
2020 年 3 月 28 日に開催される YAPC::Kyoto に BASE 基盤グループのエンジニアである めもりー が登壇します。 YAPC とは YAPCはYet Another Perl Conferenceの略で、Perlを軸としたITに関わる全ての人のためのカンファレンスです。 Perlだけにとどまらない技術者たちが、好きな技術の話をし交流するカンファレンスで、技術者であれば誰でも楽しめるお祭りです! 弊社で使用している技術スタックではないカンファレンスでの登壇は数多くなく、珍しい試みとなります。 セッションについて 「Perl 初心者の私が YAPC のために Perl で JVM を実装してみた」 2020 年 3 月 28 日 登壇者名: めもりー 10:50 開始 ルーム1 トーク内容 みなさんは JVM (Java Virtual Machine) を実装したことをありますか?私はあります。 ただ、それは Perl ではなく PHP と JavaScript で、です。 YAPC のために今まで使ったことのない言語である Perl を使って JVM を実装してみようと思います。 本トークでは、 Perl でどうやって実装したか、苦労した点は何か、そして Hello World を出力するところまでデモンストレーションできればと思っています。 最後に 下記から YAPC への参加申し込みが可能です。 yapcjapan.org
こんにちは。BASE株式会社でProduct Management Groupのマネージャーをしている山田です。 12/1から始まった「BASEアドベントカレンダー2019」はお楽しみいただけましたか?私の記事が最後の記事となります。 今年は約50名のメンバーが執筆に参加しました。エンジニアだけでなくデザイナーやProduct Managementのメンバーも筆を執っています。個性豊かな記事が集まっているので、ぜひご覧になってみてください。 devblog.thebase.in さて、私が最後の記事「閉会式」を担当するということで、簡単に自己紹介をさせていただきます。私は2019年4月に入社し、BASEは3社目となります。簡単な経歴は、新卒でワークスアプリケーションズ > リクルート > BASEとなります。1社目はエンジニアとして、2社目ではUI/UX、プロダクトマネジメントを担当しておりました。 入社して半年がたち改めて振り返ってみると めちゃ楽しかったなー というところなのですが、なぜBASEでのプロダクト作りが楽しいのかをさくっと振り返ってみます。 オーナーズが圧倒的に素敵 BASEではショップを開いてくださっている方をオーナーズと呼んでいます thebase.in オーナーさん達は作っている商品だけでなく生き方も素敵で、自分たちが作っているプロダクトのユーザーをむちゃくちゃ好きになりながらプロダクトを作れることって楽しく尊いことだなと思いながらプロダクトを作っています。 社内でも「それってオーナーズのためになってるんだっけ?」とプロダクト作りをする上での主語が自分たちでなく、オーナーズになっていることも、当たり前のように根付いて、じんわりいいなーと日々感じています。 変化 「BASE」というプロダクトは、インターネットが当たり前にある世界となり、新たな生き方/働き方ができる人が増えたことで、成長しているプロダクトだなと思っています。その世の中の変化の先っちょを肌で感じながらプロダクトを作っています。 先っちょであるが故に、会社が成長し、プロダクトが成長が成長し、そして上場したり、競争環境が変わったりと色々な変化がすごいスピードで起きています。 その変化に追いつくためには、自分達自身も変化し続ける必要があり、それはなかなか出来ない体験で、大変なことも含めて日々めちゃくちゃ楽しいです。 BASEには明文化された行動指針があります。 Be Hopeful:楽観的でいること。期待した未来は実現すると信じて、勇気ある選択をしよう。 Move Fast:速く動くこと。多くの挑戦から多くを学ぶために、まずはやってみよう。 Speak Openly:率直に話すこと。より良い結論を得るために、その場で意思を伝えよう。 入社当初は文化が浸透していていいな、と思っていたくらいだったのですが、改めてこの変化の激しい環境に適応していくためには、この行動指針はすごく良いなと最近より実感するようになりました。 Move Fastかっこいい 終わりに そんな感じで、BASEでのプロダクト作りを楽しんでいます。 私たちが目指してる世界に僕らはまだ圧倒的にたどり着いていません。 まだまだプロダクトも僕らも世の中も変わり続けて、ただその変化の先っちょいい続けるために突き進んでいければと思います。 こんな環境でプロダクト作りしたい仲間を絶賛募集中です! binc.jp これでBASEアドベントカレンダー2019は閉会です。読んでくださったみなさんありがとうございました!
  アドベントカレンダー最終日です!BASEで代表を務めている鶴岡です。宜しくお願いします。 いつもこの時期になると、ありがたい事にメディアさんから来年はどんな年になると思いますか?といったアンケートを頂くことがあります。 そもそもこういった質問にお答えすること自体あまり得意ではないのですが、一番悩む質問が、今年読んだ本の中で一番良かった本を教えてください。というものです。 完全に僕の課題なのですが、僕は本を読むのが苦手で、いまいち周りの起業家のようなカッコいい答えが思い浮かばないのです… そう話すと、周りの人たちからどうやって情報をインプットしているのかとよく聞かれます。 ある時、自分でも確かになと思い、よくよく考えた結果、僕は周りのコミュニティに助けられて学ばせてもらっているのだと気がつきました。 僕は本当にコミュニティに恵まれています。 まずコミュニティに恵まれた第一歩はインターネットでした。インターネットに本当に多くの価値観を学びました。CAMPFIREでインターンというキッカケをくれたのもインターネットです。インターネットさいこう! その後は、CAMPFIREでインターンをしていた都合上、この業界に来た時からインターネットが好きで好きでたまらないスター達に可愛がっていただき、気がつかぬうちに、完全にインターネットが好きになって作る側に回りたいと思っていたし。 ある時からは、気がつかぬうちにプロダクトを作るコードを書くのが好きな人たちに囲まれて、一晩中コードを書いたりプロダクトの未来を考えるのが好きになっていたし。 ある時からは、投資家に囲まれ、ファイナンスの方法を学び、企業や組織の成長のさせ方に興味が持てていたし。 ある時からは、初めには出会えないような先輩経営者に会えるようになり、大きな夢を見る大切さや、1秒1秒の大切さを実感できていたし。 僕としては初めにCAMPFIREに行こうと意思決定して以降、ジェットコースターにただ乗っているような感覚で各コミュニティにここまで育てていただきました。   上記に関しては気がつかぬうちに入っていたコミュニティなのですが、昨年くらいにこのコミュニティという存在の大切さに気がつき、今年からは意図して自分が身につけたい思考や能力が身につきそうなコミュニティにも入るようにしました。 例えば、ネットショップ作成サービス「BASE」のユーザーさん達です。 正直なところ今まではユーザーであるショップオーナーさんにはあまり会わないようにしていました。それは中途半端にお会いしてしまうと、仮にそのユーザーさんがBASEのサービスに不満を持たれて、別のサービスを使いたいと思ったときに、僕への情だったり気を遣われて、自由に次の選択をすることがやりづらくなるのではないか。それなのに僕自身は満足に相談にも乗ることができないのではないかと思っていたからです。 去年くらいからは日常業務の大部分を他のメンバーに移譲できていたので、今年は凄く多くのショップオーナーさまに会わせていただいて、本当に多くの刺激をもらいました。 毎日のようにお会いさせていただいている中で、ものづくりの大変さ、楽しさなどなど生きていく上で大切な価値観を本当に多く教えていただきました。 We are Ownersというメッセージを出させていただいたのもこれがキッカケです。 https://thebase.in/weareowners ここで出会った一部の方々とは友達としてプライベートでの交友もできました。 友達の中には、服を作っている人、音楽を作っている人、写真を撮っている人、映像を作っている人、自分という商品を作っている人。 第一線で頑張っている人もいれば、まだまだこれからだと焦っている人もいて様々だけれど、そんな友達と話していたり、成果物を見せてもらったりしていると、自分の無力感や思考の浅さを実感できて、最近では一番成長意欲を刺激してもらっているコミュニティな気がします。 いかなるジャンルであっても、どんな人であっても、悩んでない人などいないんだなと実感できるし、なんと言っても仕事が楽しそうで好きなのが伝わってくるので、僕自身の生きる活力になっています。自慢の友達です。 コミュニティが人生にもたらす影響に気がついて、新たなところに自ら足を伸ばして本当に良かったなと思います。 起業して以来初めて地元の友達にも会うようにもなりました。 完全になんとなく、自分への課題として、起業してから会っていなかったのですが、ある意味新たなコミュニティとして7年ぶりに会いました。 みんな様々な人生を送っていて、ただただ飲みながらこの7年間の話をしているだけで死ぬほど楽しかったです。毎日会っていたような感覚すらありました。 本当に友達は大切ですね。 しょうもない話ができる大切さも30歳を目前してようやく理解しました。 成長というものを前提に置いたとすると、個人的にはコミュニティには2つの性質があると思います。 成長するための知識や劣等感を得れて頑張る意欲を貰えるコミュニティ。 もう1つが、安心してなんでも話すことができるホームコミュニティ。 1つ目に関しては恐らくどんどん変わっていくもので、自分がチヤホヤされだして、成長意欲を削がれてしまうように感じたら次に行く合図かもしれません。 ここまで何が言いたいのかよく分からないですが、まとめとしては、どんなコミュニティにいるのか、そこでどんな人たちとどんな時間を一緒に過ごしたかが自分の人生に影響を与えるという事です。 もしも今、毎日をいまいちパッとしないと思って過ごしていたり、いまいち学びたいこと学べてないなと思っていたら、適切なコミュニティを見つけて飛び込んでみることをお勧めします。 僕は、ずっと知識や意欲をもらってばかりだったので、最近はそういう刺激を提供できる側にもなりたいなと思っています。 いろんな世界やいろんな価値観を知りたいのでぜひお友達になってください。 https://twitter.com/0Q7 本を読めるようにも頑張ります!! それでは良いお年を。
はじめまして。BASE株式会社 取締役COOの山村です! アドベントカレンダーはエンジニアじゃなくても書いていこう!というムードに乗せられてブログを書くことになりました。 2019年のBASEを振り返ると、CTOの交代や、東証マザーズへの上場、「BASE OWNERS DAY」を初開催するするなど、会社のステージも変わり、我々、経営陣にとっても、大幅なレベルアップする必要を感じた1年でした。 選んだのはコミュニケーションのスキル 専門領域を持たない自分が、これからもBASEの成長に貢献し続けるためにと考えて選んだのは、コーチングの技術でした。10数年前に出会って、そこから独学で少し学んだりしてはいましたが、1on1はBASEのカルチャーとして定着もしてきましたし、ここ最近、mentoやcotreeのように、コーチングを提供するスタートアップが出てきたり、身近でも話題に出ることが増えていたので、この機会に、あらためてスクールに通って学んでみることにしました。 無事にプロコーチになり 夏前から通い始めて、週末ごとにみっちり学び、秋には全過程を修了、そのまま GCSコーチ の認定試験にも合格しました。せっかく学んだ知識なので、簡単に使えるコーチングの基本を少しシェアしたいと思います! コーチングとは? 自身が考えるコーチングの定義は、「目標達成に向けて、対話によって対象者を勇気づけ、気付きを引き出し、自発的行動を促すコミュニケーションスキル、思考法」としています。コーチはアドバイスやコンサルティングを行うのではなく、あくまでも「答えは相手の中にある」と信じて、それを引き出す手伝いをします。 目標を与えられるのではなく、正解のない未来に向かって、自らビジョンを掲げ、ミッションを設定し、ゴールに向かっていくスタートアップとは、非常に相性のよいアプローチになります! 基本のストラクチャー 理想の状態を描いて達成すべきゴールを明確にする ゴールと現状の差を把握する 達成までのプロセスを設定する 行動する これを繰り返すのがコーチングの基本構造。理想の状態を描くことからスタートするのは、BASEの掲げる「Be Hopeful」という行動指針にも通じる考え方です。 セルフコーチングのススメ 質問や対話によって、目標達成の手助けをするのがコーチの役割なのですが、コーチを雇うのはハードルが高いという方には、自身で行うセルフコーチングがオススメです。これだけでも、かなり思考が整理されます。 例えば、「来年の夏休みをどう過ごすか」というテーマで、コーチングのストラクチャーを使って考えてみると、以下のようになります! テーマ:「来年の夏休みをどう過ごすか」 <目標設定> Q:どういう夏休みが理想ですか? A:いつもより長めに休みをとって、家族でハワイ島に渡って、のんびり過ごしたいな <現状把握> Q:その状態を「10」としたとき、今は何点ですか? A:「2」くらい Q:今つけた点数の中身は何ですか?(今出来ていることは何ですか?) A:家族の賛同や、会社の理解は得られそう   以前にも行ったことがあり、段取りのイメージがついている <現状と目標との乖離/プロセス設計> Q:これから目標達成に向かって、必要なことは何ですか? A:予定の確認・スケジュールの調整   航空券や宿の手配   不在時の業務調整   等々 <優先順位付け> Q:そのために何から始めますか? A:予定の確認・スケジュールの調整から   <行動を促す> Q:今日できることは何ですか? A:まずは家族に提案して日程の相談をはじめてみる! こうやって、自ら導き出した一歩目を踏み出してみる、これを定期的に繰り返すことで、着実に目標に近づくことができます。質問に対して自分自身で考えてみることによって、思考が整理され、行動が促される、というコーチングの雰囲気を感じていただけたでしょうか? 最後に この数週間は、BASEの未来を描くことにほとんどの時間を使っています。2019年も残り数日となりました。まだまだ力不足を実感する日々ですが、掲げるビジョンの実現に向けて頑張っていきます! 「Payment to the People,Power to the People.」 我々はまだまだ小さいチームです。自ら目標を掲げ、前に進んでいけるリーダーをたくさん募集しています!お気軽にお声かけください! https://binc.jp/jobs
この記事はBASE Advent Calendar 2019の25日目の記事です。 devblog.thebase.in BASE株式会社でCTOをやっている川口 ( @dmnlk ) です。 2019年7月1日にCTOに就任させていただきました。 そこからちょうど半年経った今、エンジニアが40名近い会社で自分がCTOとして感じたことや変化、その先への考えを綴りたいと思います。 BASEでCTOになった経緯については下記にて取り上げていただきました。 そもそもこういう媒体に自分が出るようになった、というのも目に見える変化だと感じています。 type.jp careerhack.en-japan.com 自分のキャリアについて登壇させていただく機会もいただきました。 今までの自分ではこういう機会は訪れなかったと思うので、立場やタイミングというのは得てしてこういうものがあるのだなぁと感じています。 devblog.thebase.in CTOに就任してからの最初の3ヶ月について 就任したその日から技術の責任者としての自覚を得られていたかというと正直そんなことはまったくありませんでした。 弊社では3ヶ月ごとに開発スケジュール組むことが多いのですが、その前にやっていた開発の残りがあったのでそのまま継続して行っていました。小さな不具合や改善なども自分で行ってしまっていました。 CTOを引き継ぐ際に前任CTOの藤川より「開発現場のリーダーが就き、技術的な意思決定を担う」とCTOの定義をされていたので自分が開発現場で日々の開発をしていても問題はないと考えていたこともあり、特に自分の考え方をアップデートせずリードエンジニアの思考のまま日々を過ごしている状態でした。 しかしこれは、現場のリーダーではありますがCTOとしての振る舞いとしてはあまりよくなかったと今は感じています。 もちろん、全くコードを書かないつもりはありません。 ですが、CTOとしてはメンバーの成長とプロダクトの成長を第一に考え次のプロダクトの非連続的な成長を迎えるために技術投資を行っていくことが重要です。 その次の3ヶ月 就任し3ヶ月も経った頃、ふと「このままでいいんだろうか」という気持ちになりました。 採用も進み、優秀なメンバーも増えています。 そんな折、コネヒトよりパネルディスカッションの機会をいただきました。 その時の模様は記事になっています。 type.jp ここで初めて他社のCTOという存在に直接関わりました。 特にコネヒトのCTOである伊藤さん( @itosho )は同じようなタイミングでCTOを引き継いだこともあり親近感を感じましたが、自分より遥かに組織を前に進めようと動いているなという焦りを感じました。 クックパッドの成田さん( @mirakui )も組織やプロダクトに向き合いながらもISUCONで優勝しており、自分より遥かに高い場所にいました。 このまま漫然と自分がやるべきでもないコードを書き(メンバーがやるべき雑務というわけではありません)立場だけCTOになっていていいのか、引き継いだ意味は本当にあったんだろうか?と自分がやるべき事を見直し始めました。 今では80万以上のショップ様に利用して頂いている「BASE」というプロダクトですが技術的な課題は非常に多くあります。 利用しているウェブフレームワークのEOL、モノリシックなアプリケーションにより開発速度の低下、耐障害性や負荷問題、データが均質化されていないことによる分析のしづらさ、枚挙に暇がありません。 非連続的な成長を成し遂げるための技術投資も行わなければなりません。 もちろんプロダクト自体のクオリティを高め、プロダクトを使って方々により良い体験を届けるための開発もしていくことも必要です。 これら全てをBASEの課題として認識し、一つ一つクリアにしていき解消するための方針を立て優秀なメンバーの能力を最大限に活かしながら前に進めていくことがCTOに求められている責任であると認識を改めることにしました。 今現在とこれから 現在のプロダクトに直接関わるようなコードは書かずたまに障害対応用の小さなコード群を書くくらいになりました。 代わりに現状のBASEを見つめ直し、課題に対する優先度を見つけたりその課題を解消できるような技術選定や検証を行うことが増えました。 採用に関しても、現状困っている部分に対してコミットしてくれるようなメンバーという視点だけでなく未来のBASEの問題に取り組んでくれるようなメンバーか?といった視点でも選考するようになりました。 考えてるだけでコード書けないのでは?と思われるのも厳しいので、もう少しなにかできないかというのは悩みです。 未来のBASEを作るアーキテクチャというものに悩んでいますが、それがこのままモノリシックなのかマイクロサービスアーキテクチャなのかモジュラーモノリスなのかといった辺りは非常に難しくまだまだ結論が出ません。 メンバーと1on1をするようにもなりました。これはある種の技術選定と同義だなと考えています。普段開発をしているチームのエンジニア達がどのような課題感を持っていたり志向性を持っているかで、次のアサインを決めていくのが重要なので日々の対話を疎かにすべきでないと思っています。人数が多いので大変ですが... 2020年は今のBASEのシステムに大きい変化を行っていく予定なので今から楽しみです。技術力とプロダクト理解、それぞれが求められるような変化なので僕もコード書いてコミットしていきたいです。 CTOになって良かった?後悔している? たまにこの事を知人には聞かれます。 正直な話、BASEじゃなかったらやりたくないなと思うくらいには大変だなと思います。 全然ゴールが見えないし、何をもってして自分がやりきったと言えるかがまだまだ不明ですし自分が決めた選択の重みが今までとは比べようもなく大きいと感じます。 ただ自分がやりたいインターネットだなと思っているプロダクトとチームが目の前にある中で大きな裁量を持って仕事できることは人生で数少ないので今やれることを精一杯やっていきたいです。 最後に この記事で上げたようなBASEにおける課題について共に考えて手を動かして「BASE」というプロダクトをもっと最高にしていく仲間を募集しています。 募集は下記にありますが、まずは僕とカジュアルにオフィスで話をしましょう。 binc.jp
BASE株式会社で執行役員VP of Productとしてプロダクトマネジメントをしている @7jin16 です。 プロダクトマネジメントについて書こうと思いましたが、世の中にたくさん記事もありますし、キャリアハックさんに記事を書いていただいたのでこちらをご覧ください🙏🏻 careerhack.en-japan.com プロダクトマネジメントに関わるまで 2013年頃、以前から知り合いだった代表の鶴岡から仕事の誘いのメッセージをもらって、カスタマーサポートのメンバーとしてBASEで働き始めたのが入社のきっかけです。 ※2013年の東京都の最低賃金は時給869円です。 カスタマーサポートとして入社しましたが、入社後すぐに年末キャンペーンのページをコーディングする機会をもらい、それ以降「BASE」本体の機能の画面設計や実装に関わることになり、コードを書いたりしながら数年かけてプロダクトマネージャーと呼ばれるような役割に移っていきました。 スーパーヒーローたちに憧れて BASEに入社する前からインターネットプロダクトが大好きだったのもあり、そこに全力を注げる環境もあるので、入社から今も変わらず毎日ほんとうにプロダクト作りが楽しいです。 インターネットプロダクトが好きな理由は、マーク・ザッカーバーグがかっこいいし、クリス・コックスもかっこいい。ショーン・パーカーもかっこいいし、エヴァン・シュピーゲルも、デビッド・カープも、ジャック・ドーシーもコリソン兄弟も、ビズ・ストーンも、アレクシス・オハニアンもかっこいいからです。ぼくにとってのスーパーヒーローです。 The Social Network | Netflix とかリスニング力が低くて英単語が聞き取れなくても音でどの場面か大体分かるくらい観ています。めちゃくちゃミーハーですね。いつか thebase.in の the を取りたいですね。 人々に熱烈に愛され続ける偉大なプロダクトをつくった偉人たちに憧れ「いつか自分も人々に熱烈に愛され続ける偉大なプロダクトを作りたい!」と思い始めて、10年弱たちますが、今もなお憧れ続けています。 プロダクト作りをしていると毎日いろいろな問題に直面します。そのたびに「まったく同じ状況下でもマーク・ザッカーバーグたちならもっと上手くやれるのかな。もっとユーザーのために頑張らないと。」と気が引き締まります。 入社当初から自分の何かが目に見えて成長したという実感は薄く、入社後に自身の1番成長した点を聞かれても回答に困りますが、毎日ユーザーに向き合い、プロダクト作りをしていたら役割が変わり、さらに楽しくなり、プロダクト作りに熱中する。そんな感じのループなのかもしれません。 毎日ユーザーに向き合い、熱烈に愛され続けるプロダクトを目指して作っていれば、いつの間にか自分自身も成長すると信じてプロダクトを作っています。ユーザーやプロダクト作りに関わるメンバーが増えれば増えるほど、スーパーヒーローたちを凄まじいと感じますが、背中すら見えないのでとても悔しいです。 今後もスーパーヒーローたちに憧れる心を忘れることなく、ユーザーから熱烈に愛され続けるプロダクトを目指します! プロダクト作りをしたい方は DM か、 こちら へ!! twitter.com 明日はCEOの鶴岡、COOの山村、CTOの川口が記事を投稿する予定ですのでお楽しみに〜〜〜!
こんにちは!基盤グループのめもりー ( @m3m0r7 ) です。最近発売された MacBookPro 16 inch を個人的に買ってご満悦です。 この記事は、 BASE アドベントカレンダー24日目 の1つ目です。 みなさんは普段プログラミングをするのはどういうときでしょうか。仕事やプライベート、そもそもプログラミングそのものが趣味…などいろんなシーンがあるかなと思います。 私は会社ではプロダクトのためのコードを書いていますが、プライベートでは本来プロダクトで触らない、そう例えば PHP で JVM を実装したり、 PHP そのものを JVM 言語にしていたり、それ以外にも自宅のペットの監視システムを作ったりなどなどしています。 さて、早速ですがこれをご覧ください。 .hello-world { property 1 : value 1 ; property 2 : value 2 ; } これを見てあなたはどう感じましたか?「CSS だ!」と感じましたか、それとも「どうやったらパースできるんだろう」と感じましたか? 今日のアドベントカレンダーはプロダクトとは少し離れたお話で、PHP で CSS をパースしてみようと思います。 CSS は Selector Level 3 からいろんな書式が扱えるようになりました。 とはいえ、これをすべてパースするのはとても時間がかかります。簡単なセレクタとプロパティをパースしてプログラム側で扱いやすいようなところまでをゴールとしてやってみようと思います。 この要件としては .hello-world をセレクターとして書き出したい。 property1, property2 を .hello-world のプロパティ一覧として扱いたい。 といったところでしょうか。 これをコードで表すには概ね下記のようにする必要があるかと思います。 { までをセレクターとして扱う { , } の間の値をプロパティ文字列として扱う、言い換えると { を読み取った位置から } までをプロパティ文字列として扱います。 プロパティ文字列から : までをプロパティ名、 ; までを値として扱い、終わるまで繰り返す。 まずは、 { や } , : , ; までを読み込む、つまり任意のトークンまでの文字列を取得する関数を定義します。 function readTo(string $text, string $delimiterToken, int & $i): string { $length = mb_strlen($text); $string = ''; // 指定したトークンまで読みすすめる while (($char = mb_substr($text, $i, 1)) && $char !== $delimiterToken && $i < $length) { $string .= $char; $i++; } return $string; } 上記のような形にします。この関数は、 指定したトークンまで読みすすめる、または CSS の終端までの間の文字列を返す ためのものです。 例えばこれを readTo($css, '{', $i); 上記のように呼び出すと、 { までの文字列を取得します。これを使って { と } までのそれぞれの文字列を読み込みます。 $selectors = []; $length = mb_strlen($css); for ($i = 0; $i < $length; $i++) { $statement = [ 'selector' => '', 'properties' = > [], ]; // { まで読みすすめる $statement['selector'] = trim(readTo($css, '{', $i)); // この時点だとまだ { なので、一つすすめる。 $i++; $statement['properties'] = trim(readTo($css, '}', $i)); $selectors[] = $statement; } これを出力すると array(1) { [0]= > array(2) { ["selector"]= > string(12) ".hello-world" ["properties"]= > string(41) "property1: value1; property2: value2;" } } 上記のようになります。次に与えられたプロパティ文字列をパースするために下記の関数を定義します。 function parseProperties(string $propertyString): array { $length = mb_strlen($propertyString); $properties = []; for ($i = 0; $i < $length; $i++) { $ name = trim(readTo($propertyString, ':' , $i)); $i++; $ value = trim(readTo($propertyString, ';' , $i)); if ($ name = == '' ) { continue; } $properties[$ name ][] = $value; } return $properties; } これも上記と原理は同じで、 : までと ; までの文字列をそれぞれ取得し、それを配列に入れています。 また、名前がない箇所はプロパティとして考えないようにもしています。 これを $statement['properties'] = trim(readTo($css, '}', $i)); に通してあげて、 $selectors = []; $length = mb_strlen($css); for ($i = 0; $i < $length; $i++) { $statement = [ 'selector' => '', 'properties' = > [], ]; // { まで読みすすめる $statement['selector'] = trim(readTo($css, '{', $i)); // この時点だとまだ { なので、一つすすめる。 $i++; $statement['properties'] = parseProperties(trim(readTo($css, '}', $i))); $selectors[] = $statement; } 上記のようにして出力すると、下記のように取得できます。 array(1) { [0]= > array(2) { ["selector"]= > string(12) ".hello-world" ["properties"]= > array(2) { ["property1"]= > array(1) { [0]= > string(6) "value1" } ["property2"]= > array(1) { [0]= > string(6) "value2" } } } } CSS のパースができました。上のコードをまとめると下記のようになるかと思います。 <?php $ css = <<< EOS . hello - world { property1 : value1; property2 : value2; } EOS; $ selectors = [] ; function readTo ( string $ text , string $ delimiterToken , int &$ i ) : string { $ length = mb_strlen ( $ text ) ; $ string = '' ; // 指定したトークンまで読みすすめる while (( $ char = mb_substr ( $ text , $ i , 1 )) && $ char !== $ delimiterToken && $ i < $ length ) { $ string .= $ char ; $ i ++ ; } return $ string ; } function parseProperties ( string $ propertyString ) : array { $ length = mb_strlen ( $ propertyString ) ; $ properties = [] ; for ( $ i = 0 ; $ i < $ length ; $ i ++ ) { $ name = trim ( readTo ( $ propertyString , ':' , $ i )) ; $ i ++ ; $ value = trim ( readTo ( $ propertyString , ';' , $ i )) ; if ( $ name === '' ) { continue ; } $ properties [ $ name ][] = $ value ; } return $ properties ; } $ length = mb_strlen ( $ css ) ; for ( $ i = 0 ; $ i < $ length ; $ i ++ ) { $ statement = [ 'selector' => '' , 'properties' => [] , ] ; // { まで読みすすめる $ statement [ 'selector' ] = trim ( readTo ( $ css , '{' , $ i )) ; // この時点だとまだ { なので、一つすすめる。 $ i ++ ; $ statement [ 'properties' ] = parseProperties ( readTo ( $ css , '}' , $ i ) ) ; $ selectors [] = $ statement ; } var_dump ( $ selectors ) ; 本来は media クエリのような { , } などのネストにも対応させたり、セレクタの優先度があったり複雑になるのですが、ブログの 1 記事に収められなくなってしまうため、今回は簡単にパースするまでのお話でした。
こんにちは。基盤グループのめもりー ( @m3m0r7 ) です。 この記事は、 BASE アドベントカレンダー24日目 の 2 つ目です。 みなさんは、いざ登壇をするとなった時の準備に何を意識されていますか。例えば「本番で失敗しないように練習をたくさんする」だとか「場数をこなす」とか「伝わらないと困るからとりあえず資料に盛り込んどく」などなどあるかと思います。 私の初めての登壇は 2019 年の 1 月 26 日の PHP カンファレンス仙台が初めてで、多くの方に「え?本当ですか?信じられない」と言われたりもしました。今年はいろんな場所で、幸いにも登壇させていただける機会をいただき、初めての登壇回数は 15 回、登壇時間 270 分までにものぼりました。 とはいえ、私自身、いろんな方の登壇を見させていただいてもっと精進しなければならないと痛感しており、日々どうしていくべきか試行錯誤しています。 そんな中で、登壇についての心構えや資料作成といったベストプラクティスのような解説を書かれている文献がそう多くないと主観ではありますが感じてきていました。そのためどう資料を構成したり、登壇までに何をしたらいいのかがわからず、多くの方は登壇自体のハードルが高いと感じてしまうのではないかと思っています。私も実際そうでした。 私自身エンジニアとしてはひよこですが、今まで1日に何個も企画資料や開発資料、外部へのアウトプットサービスを通じて得た経験を元に社内のドキュメントに「資料作成のすゝめ」を投稿したところ、ブログに公開してほしいと嬉しいお声がけを頂いたので、ブログ向けにリライトしたものを公開したいと思います。 資料の作成 相手に「何を伝えたいのか」を自分自身が完璧に理解する 資料の作成には自分自身の納得と理解が必要だと感じています。私はなにを伝えたいのか?という自問自答を繰り返していきます。そして、発表タイトルから話すゴールを決定します。例えば私の場合であれば「PHP で JVM を実装して、 HelloWorld を出力してみる」という内容であれば「Hello World を出力する」というのをゴールとして置きます。そして、ゴールとは別に、伝えたいことを設定します。 「JVM の実装はそこまで難しくない」というのがこの発表での伝えたいことでした。 JVM を Go, R や Ruby などで実装してくれる方々が出てきてくださってとてもうれしく感じました。私自身は登壇の前にはこの 2 つを設定するようにしています。 Go製自作JVM、MITライセンスつけてオープンソースにしました。 main.goの1ファイルのみで700行程度です。 めもりーさんの発表資料とJVM公式仕様書のみを見て作りました。 https://t.co/I8f80A70s2 — ドキュネオ (@DQNEO) September 13, 2019 書いた。みんなも好きな言語でJava VM作ると良いよ。 Java VM 自作 方法 https://t.co/BcV8bDv7tG — igjit (@igjit) December 19, 2019 "RubyでJVMを実装してみる" の発表資料です Google Slides: https://t.co/3liwmqFrZ4 … #heiseirubykaigi #heiseirubykaigiB (ハッシュタグつけ忘れてたので再ツイート) — Daiki Miura (@daikiii5555) December 14, 2019 いろんな方にご興味を持ってもらえたのがスピーカーとしてとてもうれしく思っています。 アジェンダの構成 資料におけるアジェンダは重要な役割を担っています。オーディエンスがどういったトークになるのかを頭の中で流れを理解できる唯一の情報源になるからだと考えています。 起承転結で書くと言われてもそこまでのイメージが湧きづらく筆がなかなか進まないこともあり、スタートから作っていくことを私は辞めました。 発表タイトルからプロフィール、そして話したいことを順番に作っていくとなると予め頭の中に何を話したいのか順序立てられており、スライドのイメージが既に湧いていないと難しいなという結論です。 したがって、私が資料を作る際には上にも書いてあるようにゴールと伝えたいことの 2 つ定義をした後、ゴールからスタートまでの大きな章を 4 〜 6 つほどに分割します。 そして、スタートは「なぜしたのか?」というところに落ち着くようにします。 例ですが、PHP で JVM を実装するという話では主に下記のように章分けをしました。 ▲ゴール Hello World を出力するまで JVM のオペレーションコードの話 PHP と JVM の互換性の話 JVM を実装するとは? なんでこんなことをしたのか ▼スタート そして、章分けをした後それぞれの章が一つの発表内容だと考え、それぞれの章をさらに章分けをします。 ▲ゴール Hello World を出力するまで 必要なもの 使うもの JVM 実装までの大まかな流れ オペランドスタックの説明 オペレーションコードを知る オペレーションコードの処理を実装する JVM のオペレーションコードの話 オペレーションコードとはなにか? Constant Pool とは? PHP と JVM の互換性の話 制約 それでも実装するのか? JVM を実装するとは? コンパイルされた class ファイルを読んでいくこと コンパイルされた class ファイルとは? JVM の実装は難しくない なんでこんなことをしたのか 刺激がほしかった みんなやってないことをしたかった PHP という言語そのものの可能性 ▼スタート 上記のように章に分割できないまで繰り返していきます。そうすると必然と細かい箇所で話したい内容が明確になったり前後のスライドで辻褄が合わないから変えようといった具体的な改善点が見いだせます。 これを何度も繰り返していき、資料のストーリーを完成させます。 最後にストーリーを埋めていく 最後にスライドのストーリーを埋めていきます。これはアジェンダの次にとても大事なことです。 立てられた章のタイトルから話が脱線しないようにその内容に関係する方法や術、内容だけを書いていきます。そうすることにより、オーディエンスが聞いた時にコンテキストのスイッチが発生せずに済み、すんなり内容が入りやすくなるためだと考えているからです。 資料の存在意義 資料の存在意義で最初に思い浮かぶのはなんでしょうか。「自分のセッションを聴きに来てくれた方に伝えたいことがあるから」というのが理由の一つではないでしょうか。 私はもう 2 つ理由があると思っています。 当日セッションを迷いに迷って見に来れなかった人に向けての資料 自分自身が資料を暗記するという辛さから逃げるための術 当日セッションを迷いに迷って見に来れなかった人に向けての資料 当日魅力的なセッションがたくさんあります。自分のセッションが見れなかった人が資料を公開した際に見る可能性があります。その資料がワンラインだけだと、伝えたいことが意図と反して伝わっていたり、そもそも伝わららない可能性があります。せっかく興味を持ってもらえているので、それは避けたいです。 自分自身が資料を暗記するという辛さから逃げるための術 そして、もう 1 つが自分自身が暗記をしなくて済むようにするためでもあります。登壇当日、緊張しないとは限らないため予防線の一つとして、話したいことを資料に粗く書くことにより、頭の中が真っ白になったりしても資料に書いてある文字を読み上げるだけで済む逃げ道ができます。緊張が解れたら、書いてあること以外に自分が伝えたかった内容を話せばよいと私は考えています。今ではほとんど緊張しないものの、一番はじめの登壇である PHP カンファレンス仙台の際はとても緊張していましたが、この予防をしておいたため、伝えたいことを伝えれたと思っています。 そして、粗く書くというのは、見れに来れなかった人や資料を読み返した人でも、ある程度は伝えたいことのニュアンスが、すんなり入って来るのではないかと思います。 資料は熟成させる 資料は普段は登壇 1 ヶ月前くらい、遅くても 3 日前に作り終わるように心がけています。この理由は 発表内容を忘れるため です。発表内容を忘れることにより、発表数日前に読み返した時に理解できるのか?を見ます。つまり資料を熟成をさせます。熟成した資料は誤字脱字、そして公の場で公開するのに適切であるのかを冷静な判断で見ることができます。例えば深夜の 26 時に勢いで書いたコードを翌日見直すと「なんだこれ!!」となる経験は誰にでもあると思います。資料も同じで、その時できたと思っていても数日立つと前後のスライドで話が噛み合っていなかったり、唐突に現れる専門用語だったりが浮き彫りになることもあります。そのため熟成という過程はコードにおいても資料においても大事であると考えます。 登壇の準備における心構え 「意義」のあるトーク練習をする 「意義」のあるトークとは何か?それは伝えたいことを伝えきることだと私は考えています。 そのためには実際のトークにかかる時間を綿密に計測し、時には伝えたいことを減らす、または増やすということが必要です。 慣れてくると発表時間と自分のトークに対してどれほどの資料の枚数が必要なのか自然とわかるようになってきます。 登壇前日に行うべきこと デモがある場合は事前に動くかテストをし、そのあとは発表のことは忘れて酒を飲んで楽しみます。 登壇本番 自分はピエロだと思いこむ 登壇当日私は自分自身のことを「ピエロ」だと思いこむようにしています。これは誰かにそう言われたからではなく、自分自身の緊張を解す意味でもそう思うようにしています。ピエロであるからには、オーディエンスの皆様に楽しんでもらって帰ってもらいたいという気持ちを持つようにします。 楽しんでもらうわけですから、誰か一人でも傷つけたり不快な思いにならないように最善の注意を払います。 画面に映された資料は凝視してもいい 緊張をするとどうしても資料を凝視しがちです。それが良くないと全く思ってなくて、逆に凝視してもいいのではないかと思っています。楽しくなってもらえて、更に伝えたいことを伝えれることができれば、それ以上のことは慣れてからでも問題ないと強く思っています。 ここまで書いてきましたが… ここまで書いていて私自身もできていないことが多くあります。私も迷ったらこの記事自体を見返すかもしれません。それでも完璧にやる必要はなく、あくまで一つの手段だと思っていただけると幸いです。これよりももっと良い方法を自分たちで作っていければ、もっとよりコミュニティ活動及び貢献が活発になるのではないかと考えています。
2019 アドベントカレンダー23日目 この記事は BASE Advent Calendar 2019 の23日目の記事です。 devblog.thebase.in こんにちは、11月よりBASE BANK株式会社に入社し、Dev Divisionに所属している清水( @budougumi0617 )です。 23日目の本記事では、レイヤードアーキテクチャを採用している上で頻出するであろうボイラープレートの悩みを共有します。 そして、Go言語(以下Go)でコードを自動生成するためのツールを作った話と、利用した text/template パッケージなどの公式パッケージの概要に触れます。 自動生成時に利用するテンプレートはGoの text/template パッケージを利用しますが、テンプレートファイルは自由に変更できるので、将来的にはGo以外のコードも生成する予定です。 BASE BANKにおけるアーキテクチャ構成 私が所属するBASE BANK株式会社では、「 YELL BANK(エールバンク) 」という即時に資金調達ができる金融サービスを提供しています。 この「YELL BANK(エールバンク)」というサービスはGo製のサーバとPython製のサーバを組み合わせたバックエンド構成で提供されています。 Go製のサーバに関しても、Python製のサーバに関してもウェブアプリケーションフレームワーク(WAF)を用いていないため、実装はレイヤードアーキテクチャに則ったコード、ディレクトリ構成となっています。 Go製のサーバのpackage構成(ディレクトリ構成)と各々のパッケージの実装内容についてはBASE BANK同僚の東口さん( @hgsgtk )の登壇資料をご参照ください。 このようなレイヤードアーキテクチャによる設計アプローチは、言語やWAF特有な構成に依存しないため、Goで実装されていても、Pythonで実装されていてもディレクトリ構成自体はほぼ同じです。 また、適切に分割された各コンポーネントのコードが担う責務はシンプルな単一責務であるため、プロダクトに途中から参加したような私でも比較的簡単にコードを理解することができました。 ただ、レイヤードアーキテクチャやクリーンアーキテクチャというアプローチを取ったとき問題になるのが、似たような構造のコードを大量に生成する必要が発生することです。 レイヤードアーキテクチャとボイラープレート レイヤードアーキテクチャやクリーンアーキテクチャに基づいて実装を行なっていくと、ひとつのユースケースに対して大量のファイルを作成する必要が発生します。 たとえば、「ユーザーを登録する」というような機能を作成する場合、我々のプロダクトでは、最低でも7ファイルを作成する必要があります。 ./ ├── domain │   ├── model/user.go │   └── repository/user.go │  ├── infrastructure/datastore/user.go │ ├── interfaces/controller │      ├── create_user_controller.go │      └── create_user_controller_test.go │ └── service ├── create_user_service.go └── create_user_service_test.go クリーンアーキテクチャやレイヤードアーキテクチャに詳しい方ならば、なんとなくファイル内容は想像できるかと思いますが、一部を記載するとこんなカタチです。 // domain/model/user.go type UserID int type User struct { ID UserID // has some fields... } // domain/repository/user.go package repository type UserGetter interface { Get(model.UserID) (model.User, error ) } type UserSaver interface { Save(model.User) (mode.UserID, error ) } // infrastructure/datastore/user.go package datastore type UserStore struct { db *sql.DB } func (*UserStore) Get(id model.UserID) (model.User, error ) { /* do anything... */ } func (*UserStore) Save(u model.User) (model.UserID, error ) { /* do anything... */ } // service/create_user_service.go package service type UserRegisterService interface { Run(UserRegisterInput) (UserRegisterResult, error ) } func NewUserRegisterService( /* some args... */ ) UserRegisterService { return &userRegisterService{ /* fill fields... */ } } type userRegisterService struct {} // service/create_user_service_test.go package service func TestUserRegisterService_Run(t *testing.T) { tests := [] struct { /* test conditions */ }{ { /* test case 1 */ }, { /* test case 2 */ }, } for _, tt := range tests { t.Run(tt.name, func (t *testing.T) { // Arrange, Act, Assert... }) } } // interfaces/controller/create_user_controller.go package controller type UserRegisterController struct { Service service.UserRegisterService } func NewUserRegisterController(urs service.UserRegisterService) UserRegisterController { return UserRegisterController{ Service: urs, } } func (c *UserRegisterController) Handler(w http.ResponseWriter, r *http.Request) { // Handle Request by UserRegisterService } このように、ひとつのリクエストを処理するために複数の実装をする必要があります。 上記ではユーザー登録用のコードを例にしましたが、ユーザー削除を行う際には( model コードを除いて)上記の例のように同じ構成のファイルの組み合わせを「新たに」実装します。ここで、新たに作るユーザー削除用のコード群は各構造体の引数やメソッドの処理内容は違えど、ファイルを構成する要素はユーザー登録時とほとんど同じになります。 以上の実装方針に則ると、ひとつのレイヤー(ディレクトリ)の中には上記のようなコードのファイルが大量に増えていきます。 ./ └── service ├── create_user_service.go ├── create_user_service_test.go ├── read_user_service.go ├── read_user_service_test.go ├── update_user_service.go ├── update_user_service_test.go ├── delete_user_service.go ├── delete_user_service_test.go ├── create_company_service.go …… 同様の設計アプローチを取られている方ならば、同様の悩みをもったことがあるのではないでしょうか。 ほぼ似た構造体をつくることになるので、私は作成済みのエンドポイントのファイルをコピーして再利用可能な部分だけ残してから実装を行なっていました。エンジニアの三大美徳(怠惰(怠慢)、短気、傲慢)に反してますね。 rails generate コマンドや cake bake コマンドのようなWAFのコード自動生成機能を使えるならばスケルトンコードをすぐに生成できるでしょう。 ということで、今回は与えた「モデル名」と「アクション名」、「テンプレートファイル」に基づいて各レイヤーのボイラープレートを自動生成するためのCLIツールを作りました。 レイヤードアーキテクチャ用ボイラープレートの自動生成ツール lgen 作成したCLIツールは以下になります。 レイヤードアーキテクチャの実装はプロダクトやチームによっても微妙に差異があります。 そこでこのツールは以下のような特徴を持ちます。 実行オプションに渡された モデル名 と アクション名 を使ってコードを自動生成する 生成するコードは指定されたディレクトリ配下にあるファイルを読み込んで決める ディレクトリ構成もコピーすることで、各プロダクトのレイヤー構成に対応する テンプレートやディレクトリ構成を自由に設定できるので、様々なレイヤー構造に柔軟に対応できる ざっくり使い方を書くと、以下のようになります。 まず最初に、後述する text/template パッケージを使って アクション名 と モデル名 を変数としたこのようなボイラープレートの雛形を書いておきます。 // templates/usercases/get_user_usecase.go package usecase type {{ .Action | title}}{{ .Model | title }}Input struct {} type {{ .Action | title}}{{ .Model | title }}Result struct {} type {{ .Action | title}}{{ .Model | title }}Usecase interface { Run({{ .Action | title}}{{ .Model | title }}Input) ({{ .Action | title}}{{ .Model | title }}Result, error ) } func New{{ .Action | title}}{{ .Model | title }}Usecase() {{ .Action | title}}{{ .Model | title }}Usecase { return &{{ .Action }}{{ .Model | title }}Usecase{ } } type {{ .Action }}{{ .Model | title }}Usecase struct {} func (u *{{ .Action }}{{ .Model | title }}Usecase) Run( in {{ .Action | title}}{{ .Model | title }}Input, ) ({{ .Action | title}}{{ .Model | title }}Result, error ){ // Need to implement usercase logic return {{ .Action | title}}{{ .Model | title }}Result{ // Need to build result } } 次に、自分のプロダクトのレイヤー(ディレクトリ)構造に合わせてそれぞれのファイルを配置します。 $ tree templates templates ├── repositories │   ├── repository.go │   └── repository_test.go ├── controllers │   ├── controller.go │   └── controller_test.go └── usercases    ├── usecase.go └── usecase_test.go そして モデル名 と アクション名 などを指定してツールを実行します。 次の例では、 GetUser 操作に関するコードを templates ディレクトリ配下のレイヤー構造とテンプレートを使って、 myproduct ディレクトリ配下に自動生成します。 $ lgen -action get -model user -template ./templates -dist myproduct 実行すると、以下のように templates ディレクトリと同じディレクトリ構造の場所にファイルが生成されます。 $ tree myproduct myproduct ├── repositories │   ├── get_user_repository.go │   └── get_user_repository_test.go ├── controllers │   ├── get_user_controller.go │   └── get_user_controller_test.go └── usercases    ├── get_user_usecase.go └── get_user_usecase_test.go 最初に書いたボイラープレートの中身は、 モデル名 と アクション名 が展開されて以下のようなコードが生成されました。 // myproduct/usercases/get_user_usecase.go package usecase type GetUserInput struct {} type GetUserResult struct {} type GetUserUsecase interface { Run(GetUserInput) (GetUserResult, error ) } func NewGetUserUsecase() GetUserUsecase { return &getUserUsecase{} } type getUserUsecase struct {} func (u *getUserUsecase) Run( in GetUserInput, ) (GetUserResult, error ) { // Need to implement usercase logic return GetUserResult{ // Need to build result } } サンプルではテンプレートを数個しか用意しませんでしたが、これに加えて他のレイヤーのコード、対になるテストコードもテンプレートさえ用意すれば全て自動生成することができます。 この結果、いままで新しいロジックを書くときにコピペと置換を繰り返していた時間を削減できます。 これで2020年はロジックに集中してコードを書けそうです。 ツールだけでの紹介では終わってしまうので味気ないので、利用しているGoの標準パッケージについて紹介しておきます。 text/templateパッケージ Goにおいて、コードの自動生成ツールをつくるのならば、 text/template パッケージ 1 を使うことになるでしょう 2 。このパッケージは名前の通りテンプレートエンジンの機能を提供しています。 あまりこのパッケージを直接利用することはないかもしれませんが、Go製のOSSでそのまま text/template パッケージの仕組みが使われていることも多いです。 例えば、KubernetesエコシステムのひとつでGoで作られているHelmのChartの書き方も text/template パッケージそのままです。 https://helm.sh/docs/topics/chart_template_guide/ よって、直接利用することはなくとも text/template パッケージの記法を覚えて損はないと思います。 text/template パッケージの使い方はGoDocの該当パッケージの説明を読むのが一番です。 ざっくりと使い方を書くと次のようになります。 // https://play.golang.org/p/-bAjX-K1_TT buf := bytes.Buffer {} tmpl := ` username: {{ .Name }} email: {{ .Email }} ` params := struct { Name string Email string }{ Name: "John Doe" , Email: "john.doe@exampl.com" , } if err := template.Must(template.New( "samples" ).Parse(tmpl)).Execute(&buf, params); err != nil { panic (err) } fmt.Printf( "%s \n " , buf.Bytes()) // username: John Doe // email: john.doe@example.com buf は変数などを展開したあとの最終結果を格納するための変数です。 tmpl がテンプレート文字列です。 {{ .Name}} などは「 Execute メソッドの引数で受け取った構造体の Name フィールドをここに出力する」という意味になります。 params は tmpl テンプレートに埋め込むための構造体です。 if 文では samples という名前で tmpl テンプレート文字列からテンプレートを作り、 params 構造体を使ってテンプレートを展開する。という処理をしています。 ツールの紹介で記載したテンプレートでは、 {{ .Model | title }} というような記述をしていました。これは変数の値を出力前に関数にパイプして加工することができる機能です。 利用関数は type FuncMap map[string]interface{} が実体である template.FuncMap にマッピングしたあと、 Execute メソッドを呼ぶ前に Parse メソッドを使うことでテンプレート内で利用することが可能になります。 今回自作したツールの場合は strings パッケージの strings.Title 関数を title として登録しています。 // templates/usercases/get_user_usecase.go var fmap = template.FuncMap{ "title" : strings.Title, } if err := template.Must(template.New(sp).Funcs(fmap).Parse(dtmpl)).Execute(&buf, l.params); err != nil { return err } もちろん自作関数を登録しておくことも可能です。 文字列長を返す len などは既定で登録済みだったりします。 その他、 if 文や range などの制御構文もテンプレート内で利用することができます。詳細は text/template パッケージのGoDoc冒頭をご覧ください。 ちなみに、GoLandを利用している場合は、テンプレートファイルに {{- /*gotype: package/import/path.type_name*/ -}} といったコメントを入れておきます。 そうすることで、テンプレートファイル中でも構造体のフィールドに対する補完が有効になります。 path/filepathパッケージとosパッケージを使ったファイルの処理 今回自作したツールでは、コードの自動生成をする上で、 指定されたディレクトリにあるテンプレートファイル群 を利用します。 Goで「あるディレクトリの配下にあるディレクトリ、ファイルを使った処理」を書くには、 path/filepath.Walk 関数を使います。 他にも相対パスを絶対パスに変換するなどの処理があるのですが、ファイル・ディレクトリ処理に関しては @mattn さんがまとめてくださっている記事を見ればほぼ完結するので、ここでは説明を省略させていただきます。 今回は使っていないのですが、(上記記事が公開されたあとにリリースされた)Go1.12やGo1.13で、実行ユーザーのホームディレクトリを返す os.UserConfigDir 関数や os.UserHomeDir 関数も追加されています。 おわりに 今回はレイヤードアーキテクチャを使ってGoの実装をする際の悩みの共有をし、課題解決のためにコードを自動生成するコマンドラインツールを実装した話を共有しました。 みなさんのプロジェクトでどのようにコードのボイラープレート問題を解決しているのかも聞けたら幸いです。 また、コマンドラインツールを作る上で役に立つ text/template や file/filepath パッケージの紹介をしました。 Goでコマンドラインツールを作ればクロスプラットフォーム対応が簡単にできます。みなさんもぜひ自作コマンドラインツールを作ってみてください。 また、「こういう機能があるなら lgen を使ってもいいんだけどなあ」みたいな要望があれば issue を立ててもらうか、Twitterで 私にメンション を飛ばしてもらえると嬉しいです。 明日は、VP of Productの神宮司さんと、基盤グループのめもり〜さんです。 参考 最後に今回の記事・ツールの作成時に参照した情報を再掲しておきます。 https://github.com/budougumi0617/lgen 私が愛した怠惰・短気・傲慢 - BASE開発チームブログ The Chart Template Developer’s Guide - helm.sh text/template上で動く計算機を作る #golang - Qiita Go templates made easy | GoLand Blog Package template - The Go Programming Language Package filepath - The Go Programming Language Package os - The Go Programming Language Big Sky :: Golang で物理ファイルの操作に path/filepath でなく path を使うと爆発します。 今回は触れませんが、HTML生成用のテンプレートエンジンとしては html/template パッケージもあります。 ↩ Goでコードの自動生成というと、 gogenerate もありますが、今回は用途が違うので触れません。 ↩
この記事は BASE Advent Calendar 2019 の22日目の記事です。 devblog.thebase.in こんにちは、Data Strategyチームの id:tawamura です。BASEには今年の8月に入社し、今月で5ヶ月目になります。 DSチームでは、ネットショップ作成サービス「BASE」のデータを集計し、機械学習など様々な利活用を行なっています。主に挙げられるのはおすすめ商品のレコメンデーション、商品のカテゴリ・属性推定、不正利用の検知などです。 弊社では、DSチームが作成したこれらの学習モデルを、DS側で立てたAPIサーバを介して利用してもらう形を取り入れています。ALBを通したAPIプロキシサーバが、各推論サーバ(ECSインスタンスやLambda)にリクエストを投げるようにしています。 基本的にDSチームの立てたAPIのレスポンスは即時で返されています。しかし、今回私が作成した推論APIは、実行にかなりの時間がかかってしまうものでした。リクエストで受け取る単一のIDについての結果を返すために、推論で必要な素性を都度RDSから収集しているのが直接の原因です。しかもリクエスト内容によって素性となる対象データ量が異なるため、実行時間が数秒〜数十秒まで変動してしまいます。 幸いこのAPIは元々リアルタイム性が求められるものではなかったので、ひとまず実行時間を受け入れつつ、この重い処理をどうにかして扱えるように色々と試行錯誤した、という話を紹介できればと思います。 ちなみに、この話は本番環境への導入前の開発環境での話となります。 初期案. リクエストに対して計算し、そのままレスポンスを返す (CDNやVPC、セキュリティグループなどは省略しています) まず最初に、APIプロキシサーバを介してリクエストを投げそのまま計算結果レスポンスを返す、従来通りの構成を検討しました。他のDS APIも多くはこの形を採用していました(今はLambdaに移行しつつあります)。最初の段階では実行に時間がかかるものがあるとわかっていなかったため、この形で試しています。 推論サーバにはECSインスタンスを使用しており、aiohttpを使用してリクエストに対し推論結果を返すWebサーバを立てています。 この場合、実行時間が数十秒かかる際に、APIプロキシサーバから事前に設定されたタイムアウトエラーが返されます。これ自体は正しい挙動です。レスポンスが返ってくるまで数十秒も待つという仕様は、リクエストを投げるサービス側にとっても良くありません。 案1. 推論部分を分離、SNS→SQS→Lambdaで推論 そのため、このAPIが扱っている「リクエストを受け取る、レスポンスを返す」「リクエストについての素性データ抽出、推論」という機能を別々のAPIとして分離することにしました。 また、この頃チーム内でAPIサーバとしてECSではなくLambdaを使用するようにしていったこともあり、Lambdaでのリクエスト処理に移行することにしました。 まず、サービス側から叩く リクエストAPI としてのLambdaの挙動は、リクエストについての計算済み結果があれば即時返す(今回は保存場所にDynamoDBを使用)、無い場合は「計算開始」または「計算中」のステータスを即時返すというものに限定します。これにより、タイムアウトなくレスポンスを返すという部分についてはクリアできます。 そして、「計算開始」または「計算中」となった場合は、同時に計算イベントを投げ、別で計算させるようにします。今回この計算イベントの投げ先にSNSを指定しました。 SNSに投げられたイベントは、サブスクライブしているSQSを経て、メッセージとして「リクエストについての素性データ抽出、推論」を行う 計算API としてのLambdaに到達し計算されます。 ここでの計算結果がDynamoDBに格納され、次回以降リクエストAPIを叩かれた場合に、即時に計算結果が返却されることになります。さらに、計算が終了したことがわかるように、計算終了時に終了イベントをSNSに投げています。ここでは、DynamoDBで計算が完了したレコードに注視し、イベントを投げていますが、計算終了時に直接SNSにイベントを投げても同じかと思います。 計算APIのタイムアウトについてですが、Lambdaはデフォルトでタイムアウトの時間を最大値900秒に設定することができます。そこまで時間がかかることはほぼあり得ないと思われるので、今回のユースケースの場合、全てのリクエストについて理論上計算可能となります。今回はとりあえず120秒としました。 その間、SQSに溜められた計算はLambdaの処理を待つ形になります。 エラーログとして、10回以上処理が失敗(10回以上受信)したものはSQSのデッドレターキューに流すようにします。 設定として、計算APIの起動Lambda数は2、可視性タイムアウトはLambdaのタイムアウトに合わせて120秒にしました(可視性タイムアウトは、メッセージ受信時にSQSの該当メッセージを見えなくし、別のワーカーによって重複して呼ばれることを避けるためのものです)。 わかりやすさのため計算API Lambdaに「×2」と表記していますが、実際は他のECSなども複数サーバーが動いています。 多くの計算イベントが意図せずデッドレターキューへ😖 これでうまくいくと思っていたのですが、実際に数百程度の計算イベントを投げてみると半分くらいのイベントがデッドレターキューに入ってしまっていることがわかりました。このデッドレターキューに流れてくるものの想定は、計算APIでの処理が10回失敗してしまったものだと思っていたので、ここがあまりにも多いのはちょっとおかしいです。一部の計算イベントについて時間がかかることで、一部のイベントがLambdaを占有してしまうことは想像がつくのですが、それでもSQSで処理を待つことで最終的には全ての計算が行われることを期待していました。 調査の結果、計算APIのLambdaでログをとっていたものの、そこでは一切エラーログは出ていないことがわかりました。つまり、Lambdaで一度も処理されることなく、デッドレターキューに入っているものがほとんどだということです。 おそらく勘違いをしていたのですが、SQSから受け取って計算APIのLambdaでの処理が失敗される度に、受信回数が1回増えるのではなく、「SQSから受け取ったが処理できるLambdaがいないのでSQSに返却された場合も受信回数が1回増える」なのではないか、ということがわかりました。 似たような事例が別の方からも挙げられていました。 medium.com 対策として考えられるのは、Lambdaの同時実行数を増やす、可視性タイムアウトを伸ばしてみる、などかと思います。 ただ、大きな前提問題としてこの計算APIはRDSへの接続を行なって動作しています。Lambdaの同時実行数を増やす場合、RDSへの接続数も比例して増えてしまいます。この結果、RDBMSの最大同時接続数を超えてしまう場合、他のDS APIのパフォーマンスに影響を及ぼすため、できれば避けたいものでした。可視性タイムアウトも根本的な解決にはなりそうにないとのことで、検討を見送りました。 実際に、弊社でサポートしていただいているAWSの方に意見を伺った際に、Lambdaの同時実行数が少ない場合に同様の現象が起きるということが確認されたようです。 案2. 推論部分を分離、SNS→SQS→ECS workerで推論🎉 AWSの方との話をした際に、別途こちらで考えてみた構成案を話させていただいており、現状はその案をとっていただくと良さそう、という回答をいただきました。 SQS→Lambdaに繋ぐのではなく、SQSのメッセージをECSインスタンスが取りに行って同期的に処理するというものです。 案1の問題は、LambdaがSQSにメッセージを取りに行く回数が実行時間に対して多く、処理する前にメッセージの受信回数が上限を超え、デッドレターキューに流れてしまうという点でした。では、メッセージの受信動作をこちらで制御し、計算中はメッセージを新たに取りに行かないようにしよう、というのがこの案のモチベーションです。 ECSのインスタンスとして2つのworkerを立てて、それぞれが同じSQSからメッセージを取って、順次計算し結果を格納する、格納し終わったらまたメッセージがあるか見に行く、という動作をし続けます。こちらも各メッセージを処理する際のタイムアウトの時間は十分長くとるようにします。 この方法により、時間は多少かかりつつも問題なく全リクエストをさばけるようになりました。 ・・というか、この案はどうやらLambdaがSQSをソースで扱う前の、従来のSQSのポーリングの仕組みになるようです。 振り返ってみると、APIがそれぞれRDSへ接続をしており、実行時間もかかる、というのがそもそもLambdaとの相性としてあまりよくなかったかなと思います。 まとめ 今回は時間のかかる推論APIをどうにかして処理させるために試行錯誤した話を紹介しました。 もちろん、推論の速度をあげるべきと言う指摘はもっともです。ただ、今回の場合は即時に結果が返る必要のない条件下での推論でもあったため、速度の追求よりも導入の実現性にフォーカスして対応を進めておりました。 AWS上のアーキテクチャを活用して上記のようなシステムを構築しましたが、自分自身AWSに触れるのはこの会社にきてからが初めてでした。ですが、チームのメンバーやテックリードの方々の手厚いサポートにより、ここまで理解と実装を進めることができました。 ちなみに私が作成したAPIはとある「不正検知API」です。 BASEではユーザのみなさまが安心してサービスをお使いいただける環境を提供するため、多方面からの協力も得ながら不正利用の検知と対応に尽力しております。 これからもネットショップ作成サービス「BASE」をどうぞよろしくお願いいたします。 明日はProduct Managementの山田さんとBASE BANKの清水さんです。お楽しみに。
この記事はBASE Advent Calendar 2019 22日目の記事です。 devblog.thebase.in こんにちは。最近はCorporate Engineeringをやっている山根 ( @fumikony )です。すこし前まで、即時に資金調達ができる金融サービス「 YELL BANK(エールバンク) 」のインフラまわりに関わっていました。 今回は「YELL BANK」のインフラにおけるTerraform運用について紹介します。 目次 目次 概要 実行する場所について レビューのやり方について プルリクエスト作成からterraform applyまでのワークフロー 詳細 tfnotifyについて AWSアカウントとTerraformディレクトリ構成について CodeBuildについて 実行例 今後の発展など おわりに 参考リンク 概要 「YELL BANK」のインフラはAWS上に構築していて、Terraformによるコード管理を行っています。Terraformのコード(tfファイル)はGitHubで管理し、変更を加えたいときはプルリクエストを作成します。普通ですね。 さて、Terraformをプルリクエストベースで運用する場合、いくつか考えることが出てきます。具体的には 実行する場所 レビューのやりかた プルリクエスト作成から terraform apply までのワークフロー です。 実行する場所について 「YELL BANK」では、 terraform plan をAWS CodeBuild上で実行し、 terraform apply はTerraform実行用のEC2インスタンス上で実行しています。 「YELL BANK」の開発では主にCircleCIを使っているのでTerraformもCircleCI上で実行することも考えました。しかし、CircleCIにあまり強いIAMの権限を付けることがためらわれたので、CodeBuildを選択しました。 レビューのやり方について tfファイルの変更をプルリクエストにすると、その差分としては当然、tfファイルのみが出てきます。しかしながらレビューの際には terraform plan の結果を確認したいものです。 はじめのころはプルリクエストに terraform plan の結果を(手で)貼ってレビューしたりしていたのですが、ここにCodeBuildと tfnotify を導入しました。 terraform plan の結果がプルリクエストのコメントとして(自動的に)返ってくるようになり、レビューがやりやすくなりました。 プルリクエスト作成からterraform applyまでのワークフロー 現在のところ、「YELL BANK」のTerraform変更のフローは以下のようなものになっています。 ブランチを切ってtfを編集 git commit , git push プルリクエストを作成 terraform plan の結果がプルリクエストのコメントとして返ってくる レビュー マージ Terraform実行サーバ上で terraform apply 詳細 以下、上でのべた構成の各部分について、詳しく説明していきます。 tfnotifyについて tfnotify はメルカリさんがOSSとして公開しているツールで、 teffaform plan や terraform apply の出力をGitHubやSlackなどに通知するためのものです。CIの中で使う想定で作られています。 以下に設定ファイルの例を示します。これはCodeBuildからtfnotifyを使用して、 terraform plan の結果をGitHubに通知するための設定です。 .tfnotify.yaml --- ci : codebuild notifier : github : token : $GITHUB_TOKEN repository : owner : "orgname" name : "reponame" terraform : plan : template : | {{ .Title }} for Production <sup>[CI link]( {{ .Link }} )</sup> {{ .Message }} {{ if .Result }} <pre><code> {{ .Result }} </pre></code> {{ end }} <details><summary>Details (Click me)</summary> <pre><code> {{ .Body }} </pre></code> </details> これを terraform コマンドを実行するディレクトリに置いておくことで、 template に記載した内容のコメントがプルリクエストに通知されます。 {{ .Title }} などはtfnotifyが埋めてくれます。詳細はtfnotifyのREADMEをご覧ください。 AWSアカウントとTerraformディレクトリ構成について 「YELL BANK」のインフラには本番(prd), ステージング(stg), 開発(dev)の環境ごとにAWSアカウントがあります。このうちprdとstgをTerraform管理下に置いています。 Terraformのディレクトリ構成としては、 - 環境ごとに別のディレクトリを作成 - リソースの種類ごとにtfファイルを作成 という方針をとっています。 具体的には以下のようなディレクトリ構成になっています。 . ├── .gitignore ├── README.md ├── aws-basebank-prd │ └── bb-prd │ ├── .tfnotify.yaml │ ├── README.md │ ├── cloudfront.tf ... │ └── vpc.tf ├── aws-basebank-stg │ └── bb-stg │ ├── .tfnotify.yaml │ ├── README.md │ ├── cloudfront.tf ... │ └── vpc.tf ├── bin │ └── dir_is_changed └── buildspec.yml buildspec.yml がCodeBuildの設定ファイル、 .tfnotify.yaml がtfnotifyの設定ファイルです。 CodeBuildについて AWS CodeBuild は、AWSのCIです。 CodeBuildを実行のトリガとしてはGitHubのプルリクエストの作成と更新を使います。 また、prd,stgそれぞれのAWSアカウントにおいてCodeBuildを設定しています。 これは実際に使用している buildspec.yml です。 buildspec.yml phases : install : runtime-versions : golang : 1.12 commands : - git clone https://github.com/tfutils/tfenv.git ~/.tfenv - ln -s ~/.tfenv/bin/* /usr/local/bin - tfenv install 0.12.6 - wget https://github.com/mercari/tfnotify/releases/download/v0.3.1/tfnotify_v0.3.1_linux_amd64.tar.gz - tar xzf tfnotify_v0.3.1_linux_amd64.tar.gz - cp tfnotify_v0.3.1_linux_amd64/tfnotify /usr/local/bin/ - cp bin/dir_is_changed /usr/local/bin/ build : commands : # ref. https://blog.hatappi.me/entry/2018/10/08/232625 - | if dir_is_changed $TERRAFORM_DIR; then cd $TERRAFORM_DIR terraform init -no-color terraform plan -no-color | tfnotify plan fi ここで行っているちょっとした工夫として、 dir_is_changed と $TERRAFORM_DIR があります。 $TERRAFORM_DIR はCodeBuildで設定している環境変数で、 terraform plan を実行するディレクトリを指定します。 また dir_is_changed は以下のようなシェルスクリプトで、引数にあたえたディレクトリ以下に変更があったかどうかを判定します。 dir_is_changed #!/bin/bash # ref. https://blog.hatappi.me/entry/2018/10/08/232625 DIFF_FILES = ( `git diff origin/master --name-only --relative= ${1} ` ) if [ ${#DIFF_FILES[ @ ]} -eq 0 ]; then exit 1 else exit 0 fi これによって、prdかstg、変更があった方だけでplanを実行するという振る舞いを実現しています。CodeBuild自体はプルリクエストの作成・更新をトリガとして常に両方の環境で動くのですが、変更が無いほうでは何もせずに終了します。 ちなみに、この方法では一つのプルリクエストでprdとstgの両方を変更するとうまく動かないため、別々のプルリクエストにしておく必要があります。 せっかくなのでCodeBuildの設定画面のスクリーンショットも貼っておきます。よかったら参考にしてください。 実行例 下図は、CodeBuild上で terraform plan が実行され、その出力が tfnotify 経由でプルリクエストコメントとして通知されている様子です。 Details (Click me) というところをクリックすると、びろ〜んと伸びて terraform plan の出力が出てきます。 今後の発展など 今のところはCodeBuildで実行するのは terraform plan までにとどめており、 terraform apply はサーバ上で手動実行しています。apply までやってしまいたい気持ちはありましたが、 GitHubでプルリクエストをマージしただけで本番インフラが変更できてしまうのはちょっと怖い 手動でAWSリソースを作ったあとで terraform import して辻褄をあわせるようなケースが、たまに有る というのがあり、いったんplanまでにしました。 上については、applyの前に人間による承認をはさみたいのでCodePiplelineにするのがよいだろうか?🤔 と思っていますが未検証です。 下については、そういうことをなるべくしないというのと、それようの場所をそれはそれで用意した上で、普段はCIに任せるのが良いかなあと思っています。 おわりに 若干とりとめがなくなりましたが、「YELL BANK」のインフラにおけるTerraform運用について紹介しました。 tfnotifyはかなり便利で、オススメできるツールだと思います。 最後に、同じようなジャンルのツールを紹介しておきます。まだ詳しく調べていないのですが、面白そうなので機会を見てさわっておきたいところです。CI+tfnotifyの構成を検討する場合は、これらも検討に入れておくといいと思います。 Atlantis これはGitHubのプルリクエストコメントにコマンドを書くと裏でterraformを実行してくれる感じのツールです。 Terraform Cloud Terraform開発元のHashiCorp自身が運営しているSaaSです。 さて明日は、BASE BANKの清水さんとProduct Managementの山田さんです。お楽しみに。 参考リンク https://tech.mercari.com/entry/2018/04/09/110000 https://medium.com/mixi-developers/terraform-on-aws-codebuild-44dda951fead https://github.com/mercari/tfnotify https://blog.hatappi.me/entry/2018/10/08/232625
やあ id:chris-x86-64 a.k.a クリスです。BASE株式会社100%子会社のPAY株式会社でセキュリティエンジニアをやっています。 新卒でここで勤めはじめて3年半が経ちました。わたしは大学在学中に畑を開墾し、大学を卒業する直前くらいにはこのまま農家になるんじゃないかと噂されていたものです。 卒業・就職した後も当該の畑を続け、社内でも農家転身やすでに本業が農家なんじゃないかなどと噂されていましたが、なんと後輩たちが卒業しさらに就職に伴い東京へ吸い込まれすっかりスーツとパソコンの人にトランスフォームするなど、27歳にして過疎化と農業の高齢化に直面し、畑は丸4年でサービス終了となってしまいました。それが今年3月のことです。 さて、畑を失ってしまったわたしですが、ほとんど同時に次の生きがいを見つけてしまいました。 それは、野生化――もとい、キャンプ。 以下は、セキュリティエンジニアというコッテコテのコンピュータギイクなわたしが、そのまるで対極に位置するアクティビティである野生化とのへんてこりんな関係について触れ、テクノロジーの世界に生きる者が母なる自然に回帰して考えたことなどを綴ろうと思います。 どうしてキャンプするんですか ぜんぜんわからない。俺たちは雰囲気でキャンプをやっている。 実はキャンプについて考え始めたのは今よりもだいぶ前、2013年のことでした。 いつものように漫然とTwitterを眺めていたら、 Thomas Backlund さんという起業家の特集記事が目に留まりました。なんでも彼は起業して自らコードを書くエンジニアであると同時に、アパートを引き払って自らの意志でホームレスとなりスウェーデンの森の中で暮らすという、ぶっ飛んだギーク特有の強烈な二面性 1 を持ち、それに惚れ込んだわたしもエンジニアとして、またホモ・サピエンスとして、強く生き抜いていきたい、そう感じさせてくれました。目標達成のために浮世のすべてを捨てて野生に還る生き様を、わたしも体験すべきだと考え始めました。 そうは言いましても当時わたしは学生。すぐさまキャンプ道具を揃えてどこでもないどこかへ脱出したい気持ちだけ高ぶらせるも、大人の機動力(要するに資金力)は持ち合わせがなく、大学が森だの原野だの評されていることをいいことに、ここで暮らしていれば実質ビッグフットだと自分をごまかし続けました。 結局決心がついたのは、あの記事を読んでから5年後。言わずもがな例のアニメが後押しとなりました。キャンプへの思いを長きにわたり燻ぶらせていたところに、あの気象レーダーみたいなでっかいお団子ヘアのキャラクターがAmazon Prime Videoに現れたのです。 今やるしかない。 脳天に雷が落ちて30分もしないうちに、わたしのAmazon.co.jpアカウントには7件ほどの注文履歴が並び、翌日にはわたしの社内Slackのアイコンが山梨銘菓になりました。これが2018年7月のこと。翌月ソロキャンデビューを果たしました。 (本当のところ)どうしてキャンプするんですか 実際にキャンプしてみると、健全な自分が取り戻せるように感じられます。 五感を取り戻す はじめに聴覚――どんなに静かなオフィスでも人間がいる限り音がします。人間の音がやたらめったら苦手であるわたしは、チャンスのある限り余計な音がしない空間に隠遁しないことには生活がままなりません。実際のところそれがわたしが今もつくば市に住み続けている理由の主たるものですが、それでも小川のせせらぎや木々のざわめきにはちょっと遠いです。そこで、川辺や森の中などのキャンプサイトにテントを張って大の字になってみると……川、木々、大小の鳥、シカ、ノウサギ、クマ(これはちょっと困る)など、我々と共に暮らしている友の声がします。耳をすませる自由がそこにはあります。わたしは常にこの「耳をすませる自由」を求めています。 次に視覚――職場も通勤時間も自由時間もブルーライトまみれ。人類を未来へ導く存在として崇められてきた「光」ですら牙をむく21世紀ですが、森の木の葉に反射された太陽光だけは今も昔も変わりません。太古から人間を包み込んできた光に回帰すると、視覚が解放されます。山の上のほうのキャンプサイトなら、数十km、数百km先の景色が見えることなどもあり、上方向なら250万光年離れたアンドロメダ星雲も見えます。360°前後左右上下ヒトヒトヒトヒトヒトアンドヒトな都心とはわけが違います。 次に嗅覚――日常的に人間や機械類の排ガスを浴びているので、たまには光合成する木々の真横で寝泊まりして呼吸器の浄化を図ります。キャンプ場でする匂いといえば、木の葉、小川、焚き火、露のついた石、肉……原始時代にもきっと同じ匂いがしていたと思います。鉄と油と汗だけが人類文明ではないことを噛みしめると、なんだか安心します。 次に触覚――風が気持ちいい(または寒い)、焚き火が暖かい、温泉でとろけそうになる、新雪を踏んだときの「くわっ」という沈み込み……これらはみんな肌の感覚です。 そして満を持して、味覚――これは自由を取り戻しているというより、 外ごはん効果で3倍おいしい(例の山梨のでかい団子談)飯が食えるという、幸せ要素になります。焚き火で焼いた肉、ちょっと焦げた米、やっぱり焦げたツナホットサンド、お隣キャンパーさんのカレー、焼いたマシュマロ、約束された勝利のコーヒー。もちろんウォッカも欠かせません。焚き火にはステンレスのフラスコに入れたウォッカが合います。 人生を取り戻す わたしはエンジニアでありながら電源のひとつ存在しない空間でも息をすることができる、これだけで強く生きている実感がわいてきます。酸素にも並ぶ生命線であるインターネットコネクティビティの有無によらず生存できる、両生類みたいなものを目指しています。 キーボードをカタカタやって生計を立てているエンジニアにこそ、電源だのWi-Fiだのがきれいさっぱり存在しない空間を生き抜いて、アナログでローテクな生活に慣れ親しむことに大きな意義があると考えています。我々の生活は高度な分業化のおかげでいろいろな道のプロがいて、ほかのプロと専門分野を分け合って全体で巨大文明を作ったうえに成り立っていますが、本当は一人ひとりが自分だけの生命を紡げるはずなのです。 以前からわたしは自分の文明を持ちたいと考えており、そのためには自分ひとりでできることを可能な限り増やす必要がありますが、その基礎がサバイバルスキルというわけです。このご時世なのでそこら辺の公園は軒並み焚き火禁止ですし、野生に還ろうにもほぼすべての国土には所有者がいて、勝手に住み着いて狩猟採集生活を営むと罰されます。そんな時代において人類が初めてエネルギーを手にした様子をそっくりそのまま再現できる場所がキャンプで、わたしはそれに救いを見出しています。 気持ち キャンプの話で社員と雑談していたら飛び出してきた疑問がこちら: 「キャンプ中は何を考えるんですか?」 これにはわたしなりに答えがあります。キャンプ中は考え事が無いことが健康の指標だと考えています。 調子がいいときは、憂いが無いので考え事が無く、ただ焚き火を眺め、ごっつい肉を食らい、暗くなって気温が下がるのを肌で感じるのみです。逆に調子が悪いときは、焚き火を見つめながら頭の中をいろんな心配事が渦巻く傾向にあります。 こんなふうにキャンプ中に考え事があるかないかは、自己の精神の健康状態の理解の助けになります。 ところで精神状態にかかわらず、河原に座って川の音に集中すると、ただのひとつも考え事をしない、最も落ち着いた自分を体験することができるように思えます。これがわたしにとってのアウトドア――精神統一の場です。 余談ですが、キャンプを始めたての頃、川の音に全身全霊で集中していたとき、涙したことがあります。精神統一を試みて逆にありとあらゆる感情に圧倒される現象は、初めてヨガを体験した人にも起こることと話にきいています。 キャンプ場の決め方 さて、これだけの目的を持ってキャンプに臨むには、相当な下調べが必要になることがあります。 ここは21世紀らしく、キャンプ場検索・予約サイトを使っています。最もよく使うのは「なっぷ」です。 候補の絞り方はだいたい次のとおり: より空いているところ 人間の生活音から可能な限り距離を取ることが目的 予約状況をなっぷで確認して、空きが多いことが確認できたら有力候補 オフシーズン、冬季可 雪上キャンプは人が少ないうえ、雪に騒音が吸収されるので非常に静かで心地よい 周辺環境や立地 川があると天然のホワイトノイズがあるのでリラックス効果大 標高が高いところは空気が澄んでいて気温が低めで、リフレッシュ効果大、星も見えやすい 猪苗代湖モビレージ 2019年1月 これまでに行って良かったキャンプ場には、猪苗代湖モビレージ(福島県)、小国白い森オートキャンプ場(山形県)、洞爺水辺の里財田キャンプ場(北海道)を挙げます。とくに猪苗代湖モビレージは通年営業で積雪期も営業しているので、雪上キャンプが楽しめます。 なお、そういったキャンプ場は往々にして辺鄙なところにあって、マイカー無しにたどり着くことは極めて困難です。やっぱり20世紀21世紀の人類文明や資本主義経済の囚人であることに変わりありませんでした。これからも人々の資本主義経済を支え守ってゆきます。どうぞPAY.JPをよろしくお願いいたします。ありがとうございました。 次回予告 明日12/22は、Data Strategyチームの粟村さんと、CSE Groupの山根さんです!お楽しみに! ここでは「二面性」は褒め言葉として使いました。 ↩
この記事はBASE Advent Calendar 2019の21日目の記事です。 devblog.thebase.in こんにちは。Owners Growthチーム所属の大木( @y_abcinema )です。 まず「Owners Growth」という言葉。聞き慣れない方がほとんどではないのでしょうか。 これはBASE内のチームの名称で、『「Owners(オーナーズ)」と呼んでいる「BASE」のショップオーナーさんの「Growth(成長)」を支援したい』という思いで、最適な支援が届くよう戦略を立てて、各施策を実行しています。 他社では見かけないチーム名で、私はとっても気に入っています! Owners Growthについては、16日目の記事「 80万ショップの成長を支援するOwners Growthチームの取組とは? 」もぜひご覧ください。 今日はその中でも、SNSで実施している取組についてフォーカスしてお話しいたします。 オーナーズに想いが届く「場」ってどこだろう? Owners Growthチームは2018年1月に誕生しました。 私がチームにジョインするに当たり気が付いたのは「ショップオーナー(以下「オーナーズ」)にオンライン上で成長支援できる場の数は、思っているほど多くはない」ということです。 こちらから届けられる成長支援のアドバイス提供場所は、現在は主に下記の3つになります。 ショップオーナーさんが使う「BASE」の管理画面に表示される「お知らせ」 メールマガジン オウンドメディア BASE U この3箇所は双方向のコミュニケーションをする場ではないため、どうしてもショップオーナーさんの反応が見えづらくなってしまいます。 BASEの公式SNSを活用すれば「アドバイス提供場所+ コミュニケーションを図る場 」としてコミュニティの構築と活性化にも繋がる思い、先の3箇所に加えSNSの運用に力を入れ始めました。 これなら一方通行のラブレターではなく、オーナーズと相思相愛になれるかも! BASEにおける3SNSのセグメント 現在BASEでは Twitter , Instagram , Facebook の3つのSNSを公式アカウントとして運用しています。 大枠のターゲットは「BASE」というプロダクトと同じく「ネットショップを運営している方」「プラットフォームを通じて素敵なショップ/商品に出会いたい方」ですが、その中でも各SNSごとにターゲットが少しずつ異なります。 Twitterは親近感と速さを重視 オーナーズの中には、 ショップを一人で運営されている方も多い です。 一人で運営をしていると「困った時は誰に相談すれば良いんだろう」「成長を高め合える仲間が欲しい」という壁もあるのではないでしょうか。 Twitterでは「BASEがオーナーズの成長を高め合える“仲間”として寄り添う」というミッションを掲げ、いち早く新機能のリリース情報や、ショップ運営に有益な記事を届ける場として活用しています。 KPIとして大事にしているのは「いいね」や「RT数」よりも「リンクのクリック数」です。 Twitterのタイムライン上では「いいね」を押した場合、他のユーザーに「いいね」を押したことが伝わるため、押しにくい……というオーナーズも多いと思います。 「本当にオーナーズが求めている情報は何か」を探るため、「いいね」の数だけに惑わされずリンクのクリック数の向上をKPIに掲げています。 Instagramはオリジナル写真を使った連載を開始 現在、ショッピングアプリ「BASE」では 特集コンテンツが自動で生成される仕組み を導入しています。これによってアプリのユーザーさんごとにおすすめのショップや商品をレコメンドできるようになりました。 以前はフィード投稿にておすすめのショップ/アイテムを投稿していましたが、特集コンテンツの自動化にともない、「オーナーズの成長を促せるような投稿」の連載も開始しました。 ハッシュタグ「 #BASEアドバイス 」を付けた投稿では、商品写真の撮り方や画像の加工方法、Instagramで有効的なアプリケーションなどをご紹介しています。 連載開始後、「#BASEアドバイス」と称した投稿のインプレッションを見てみると、他の投稿の4倍近くのコレクション数が。 Twitter同様「いいね」の数よりも、コレクション数を参考に投稿を作っています。 また、オーナーズのSNSを見たところ、3つのSNSのなかで最も利用ユーザーが多いのがInstagramです。 他のSNSは運用しておらず、Instagramのみ運用しているオーナーズも多いため、BASEとの架け橋になれるよう心がけています! Facebookは「日常に寄り添う」が合言葉 先にご紹介した2つのSNSと少し変わり、Facebookではプロダクトが素敵なショップ/アイテムやイベント情報を中心にご紹介しています。 理由としてはFacebookページを持っているオーナーズでも、「ショップのアカウントからBASEのページを閲覧する」のではなく「個人アカウントからBASEのページを閲覧する」傾向があるため、ショップの成長支援アドバイスを提供しつつも、日常を共有する/見る場の妨げにならないようにするためです。 またInstagramのように「検索ツール」や「ショッピング目的」ではなく、近況報告やビジネスの場として使っている人もいるため、日常に溶け込むような投稿を目指しています。 ちなみにショップ/アイテム紹介の場合はストーリー性があり、読んだときにパッと制作の裏側が思い描くことができ、思わずシェアしたくなる投稿が人気の傾向です。 最近では OIOI BASE MARKET で販売中の商品の紹介や、期間限定のポップアップショップなど休日に足を運びたくなるイベント情報もお届けしています。 終わりに なかなか表に出てくることがないOwners Growthチームの取組や熱い想い、伝わりましたでしょうか!今回私からはSNSを通じた、ショップへの成長支援を中心にご紹介しました。 BASEの行動指針のひとつである「Move Fast」を基に、チームでは柔軟な姿勢で、変動するネットショップの傾向やトレンドに対応できるよう励んでいます。 日々感じるのは、熱血で和気あいあいとしながらも「みんな心からオーナーズが好きなんだなぁ」ということ。その一言に尽きます。 現在は3つのSNSを中心に運用していますが、「オーナーズにとってより最適な成長支援になる場」を目指し、オンライン/オフラインを問わず、どんどん新しい場所(面)も増やしていく所存です。 明日は、Data Strategyの粟村さんとCorporate Engineeringをやっている山根さんです。楽しみー!
この記事はBASE Advent Calendar 2019の20日目の記事です。 devblog.thebase.in こんにちは、ふーです! みなさん、絵文字つかっていますか? 絵文字は、チームのコミュニケーションを円滑にしてくれる素敵な表現ですよね。Slack等で普段使われている方も多いのではないでしょうか。 ちなみに、最近GitHubで絵文字を簡単に挿入できるChrome Extensionをつくりました。 BASEでは、Pull Requestを活用して、コードレビューが行われているので、私自身はそういった場面で活用しています!活用してくれているメンバーもいるようでとても嬉しく思っています。ご興味のある方は下記リンクからインストールできるので、もしよければ、活用してみてください! Emoji Palette さて、そんな素敵なパワーのある絵文字ですが、今回は「絵文字」を中心にBASEのカルチャーを感じてみたいと思います! BASEの絵文字カルチャー BASEでは、業務におけるコミュニケーションのプラットフォームとしてSlackを活用しています。 Speak Openly という行動指針にもある通り、様々な会話が活発に行われています。そんな普段の会話で活用されている絵文字から、わたしの独断と偏見でBASEのカルチャーが伝わるような絵文字たちを紹介します! 行動指針系絵文字 BASEには、以下のような行動指針があります。 「Be Hopeful」 楽観的でいること。期待した未来は実現すると信じて、勇気ある選択をしよう。 「Move Fast」 速く動くこと。多くの挑戦から多くを学ぶために、まずはやってみよう。 「Speak Openly」 率直に話すこと。より良い結論を得るために、その場で意思を伝えよう。 これらが絵文字となって、普段の会話や、Slackの絵文字としてアクションされています。 みんな、この指針に共感と愛情を持ってお仕事をしているんだなあ。というのがこの絵文字文化から伝わります。 ちなみに、こんな感じの派生系もあります! また哲学である Stay Geek から生まれた絵文字もあります! さくわい これは、気軽にさくっとご飯いきましょー!というさくわい文化を表す絵文字たちです!#sakusakuwaiwai というさくっと食事に行きたいときに投稿するChannelから生まれた絵文字です。 BASEでは、行きたいひとたちが行きたいときにさくっと食事に行く場面をよく見かけます! 「やりたい!」という気持ちを大切にしてみんなでやる!そんな文化も感じますね。 ダサいぞ これは、プロダクトにおけるイケてないところをシェアする #ダサいぞ というChannelから生まれた絵文字です。このダサいぞ絵文字、アクションするとダサいぞChannelにシェアされる設定になっているようなので、使いどころは少し注意が必要ですね! さいごに Slack絵文字を中心として、BASEのカルチャーを感じてみました! みんなが行動指針に共感と愛情をもっていることであったり、やりたい!という気持ちを大切にする姿勢であったり、プロダクトを大切にしているカルチャーを私は感じました。 BASEのSlackには、 #twitter というまさにTwitterのように雑談をするChannelなどもあって、「インターネットっぽさ」も感じるなあ。とも思っています。 明日はOwners Growthチームの大木さんと、PAYのセキュリティエンジニアのクリスさんです。お楽しみに!
この記事はBASE Advent Calendar 2019の20日目の記事です。 devblog.thebase.in DataStrategyの岡が担当します。 Prophet is 何? ProphetはFacebook社製の時系列予測ライブラリです。RとPythonから利用でき下記gitで公開されています。 https://github.com/facebook/prophet 分析者仲間の間で「時系列予測ならまずこれを使っとけ」と言われるくらい高精度らしいのですが、私自身がイマイチ理論を把握してない & ググってもさらっとした解説の日本語ドキュメントしかない印象です。 Prophetの元となる論文は下記にて公開されています。 https://peerj.com/preprints/3190.pdf 冒頭だけ読むと、時系列分析の知見のない人でもドメイン知識を組み込みながら予測ができるようなツールを目指して開発されたようです。論文タイトルが「Forecasting at Scale」となっていて「Scale」というのはここでは「誰でも使える、スケールしやすい予測ツール」みたいな意味で使われています。 論文に書かれているProphetのモデル式をしっかり理解できるように丁寧になぞっていこう、というのがこの記事の趣旨です。 モデル式の概要 時系列データは、トレンド + 季節要因 + ノイズ などの複数の要素から成り立っていて、これらの要素に分解することで理論値の予測ができる、という考え方があります。 Prophetでは、時系列は下記のような構成要素をもつと捉えています。 さらに、時系列はこれらの要素の和と捉え下記のようなモデル式を組み立てています。 このモデル式を理解するため、誤差項以外の3要素を一つずつ見ていきます。 1. : トレンド関数 まず ですが ロジスティック非線形トレンド 線形トレンド の2種類があります。1のほうから読み解いてみます。 1-1. ロジスティック非線形トレンド ごく単純化すると下記のように表されます。 これはロジスティック曲線をベースに作られているそうなので比べてみます。 Prophetのトレンド関数(1)式と比べると、 となり の中身がシンプルになっています。 この関数の動きを把握するため、 を動かすとき がどのようになるか図示してみます。 のとき のとき となり出力 の下限と上限が決まっています。この基本となる式(2)に一つずつ の3要素を継ぎ足してトレンド関数の形に近づけていきます。 生物の個体数やプログラムのバグ発見数など、初めは少ない → 途中は多くなる → その後また少なくなる という流れを持つ現象はロジスティック曲線で説明されることがあります。 生物の個体数などには何かしら上限数があると考えられ、トレンド関数では をつけることでこの上限を設定しています。 トレンド関数にならって、ロジスティック関数(式(2))の分子を に変えてみます。 この の値を1, 2, 3,...と変化させると先ほどの曲線は下図のように変化します。 のmax値が の値に等しくなり、これで上限を設定できるようになりました。 続いて を加えてみましょう。 を固定した状態で、 の値を-1, 0.5, 1,...と変化させると曲線は下図のように変化します。 が大きいほど曲線が急になり、小さいほど緩やかになるのが見て取れます。 の時はもはや減少関数となり、 は「傾き」のようなものだと解釈できます。 生物の個体数でいうなら、成長スピードの早い群はより早く上限に達し、成長の遅い群は上限に達するのも遅い、といった知見をパラメタ で表現できます。 続いて、 を追加していきます。 これで冒頭のトレンド関数(式(1))と同様の式になりました( と読み替えてみてください)。これも を変化させながら曲線の動きを見てみましょう。 は の値をダイレクトに減算する式になっているので単に曲線が左右に動くだけですね。言い換えると同じ でも の値を上下させる働きを持っています。線形回帰でいうところの切片のようなものとイメージして下さい。 以上で下記の式の説明は終わりです。 ただ、これはあくまでトレンド関数を単純化した式です。 論文ではさらに、 も も一定ではなく時間によって変化するものと捉え、 と展開しています。 は各 時点で異なる上限を設定できるようになった、というだけの話なので説明はこれくらいに留め、 のほうをじっくり見ていきます。 は転換期のようなものを迎えた場合かなり異なったレートに変化するはず、というニュアンスを数式で表現したいとします。仮にそのような転換点がS個あったとして、そのときの 時点を と表します。また、その における成長率を調節する変更率として、 を定義します。 に対応してS個分あるので というベクトル(慣例にならってベクトルは太字で表記)も定義しておきます。 ある時間 における成長率は基本レート と 時点までに出現した変更率 の総和として下記の式で表されます。 論文ではさらに の部分を扱いやすいベクトルで表記するため、 というS個分の0または1の要素から成るベクトルを用意し、その各要素を と定義しています。例えば全部で10時点の があって、そのうち3回の変更点 が3,5,7番目に起きたとすると の内訳は下記のようになります。 結局 は各変更点 を迎えるごとにひとつずつ変更点のフラグが立つだけのベクトルと言えます。これと先ほど定義した変更率 のベクトル、 を組み合わせると式(3)は、 と変形でき、「一定ではなく時間によって変化する成長率」を表現することができました。 ここまでの式をトレンド関数に反映してみましょう。 これでt軸の転換点ごとに曲線の傾きが ぶん変化していれば良いわけですが、実はこのままだと曲線の変化がなだらかになりません。 この成長率の変化によってどのような曲線が描かれるか可視化してみましょう。 import numpy as np from matplotlib import pyplot as plt T = np.linspace(- 6 , 6 , 100 ) S = np.array([- 3 , 1 , 3 ]) # -3, 1, 3の時点で成長率に変化が起きる delta = np.array([ 0.1 , 0.3 , - 0.6 ]) # 各S時点での成長率の変更率 def logistic_trend (T, S, delta, k= 1 , C= 1 , m= 0 ): a = np.vstack([np.where(S < t, 1 , 0 ) for t in T]) y = C / ( 1 + np.exp(-(k + (a * delta).sum(axis= 1 )) * (T - m))) return y out = logistic_trend(T, S, delta, k= 0.1 , m= 0 ) plt.plot(T, out) plt.xlabel( "t" , fontsize= 16 ) plt.ylabel( "y" , fontsize= 16 ) # plot change point ymax = out.max() ymin = out.min() plt.vlines( S, ymin=ymin, ymax=ymax, linestyle= 'dashed' , color= 'gray' , label= 'change point' ) plt.legend() plt.show() 変化点ごとに曲線が切れてしまってます。 さきほど は「傾き」のようなもので、 は「切片」のようなものだと説明しました。 のようなシンプルな一次関数と同じように考えて欲しいのですが、傾きaは  のようにxの領域によって変化し切片bは一定で0として可視化すると、さきほどと同じように途切れた直線になってしまいます。 T1 = np.linspace(- 6 , - 2 , 30 ) T2 = np.linspace(- 2 , 6 , 70 ) def sample_linear (T, a= 1 ): y = a * T return y out1 = sample_linear(T1, a= 1 ) out2 = sample_linear(T2, a=- 1 ) plt.plot( np.hstack([T1, T2]), np.hstack([out1, out2]), ) plt.xlabel( "t" , fontsize= 16 ) plt.ylabel( "y" , fontsize= 16 ) plt.show() この場合は切片bを調節することで連続した直線が得られますが、話をトレンド関数に戻して考えるとオフセット項mを調整すれば連続した曲線が得られそうです。 転換点S個だけ調節する値が必要なので と同じく というベクトルを用意し、その内訳を下記のように定義します。 これを用いて式(4)のオフセット項mを調整するとトレンド関数は最終的に下記のようになります。 この修正を先ほどのコードに加えると下記のようにグラフ化できます。 T = np.linspace(- 6 , 6 , 100 ) S = np.array([- 3 , 1 , 3 ]) delta = np.array([ 0.1 , 0.3 , - 0.6 ]) def logistic_trend (T, S, delta, k= 1 , C= 1 , m= 0 ): a = np.vstack([np.where(S < t, 1 , 0 ) for t in T]) gamma = np.zeros(S.shape) for j in range ( 0 , gamma.shape[ 0 ]): gamma[j] = (S[j] - m - gamma[:j].sum()) * ( 1 - ((k + delta[:j].sum()) / (k + delta[:j + 1 ].sum()))) y = C / ( 1 + np.exp(-(k + (a * delta).sum(axis= 1 )) * (T - (m + (a * gamma).sum(axis= 1 ))))) return y out = logistic_trend(T, S, delta, k= 0.1 , m= 0 ) plt.plot(T, out) plt.xlabel( "t" , fontsize= 16 ) plt.ylabel( "y" , fontsize= 16 ) # plot change point ymax = out.max() ymin = out.min() plt.vlines( S, ymin=ymin, ymax=ymax, linestyle= 'dashed' , color= 'gray' , label= 'change point' ) plt.legend() plt.show() これで連続した曲線が得られました。 1-2. 線形トレンド 線形トレンドは下記の式で表されます。 式(5)と見比べると、expの中身が出てきて成長率の部分はロジスティックの時と同様の式です。 オフセット項の部分だけ異なっていて、ここでは という定義の ベクトルで連続した直線が得られるよう調整されています。 参考までにコードを書くと下記のようになります。 T = np.linspace(- 6 , 6 , 100 ) S = np.array([- 3 , 1 , 3 ]) delta = np.array([ 0.1 , 0.3 , - 0.6 ]) def linear_trend (T, S, delta, k= 1 , m= 0 ): a = np.vstack([np.where(S < t, 1 , 0 ) for t in T]) gamma = -S * delta y = (k + (a * delta).sum(axis= 1 )) * T + (m + (a * gamma).sum(axis= 1 )) return y out = linear_trend(T, S, delta, k= 0.1 , m= 0 ) plt.plot(T, out) plt.xlabel( "t" , fontsize= 16 ) plt.ylabel( "y" , fontsize= 16 ) # plot change point ymax = out.max() ymin = out.min() plt.vlines( S, ymin=ymin, ymax=ymax, linestyle= 'dashed' , color= 'gray' , label= 'change point' ) plt.legend() plt.show() 1-3. 変化点の自動検出 変化点 はユーザー自身で設定することもできますが、スパース推定のようなことをして自動検出も可能です。 論文によると変化点 の上限数を多めにとり、各点の変更率 に対し という事前分布を仮定すれば良いとあります。 はラプラス分布のことですが、その形状を確認してみましょう。 正規分布よりも0付近の値が出現しやすくなっている、という特徴がありそうです。 さらに の部分を変化させると下記のような分布が得られます。 が0に近づくにつれ、ほとんど0の値しか出現しない(スパースな)分布になっていることが見て取れます。 ここまでの話をまとめると、、、変更率 にラプラス分布を仮定すると、多めの変更点をとっておいても変更率はほぼ0になり、かつ稀に大きな変更率が発生するという事象を再現することができます。また、変更率の大きさそのものは を小さく調整することで抑えることが可能になります。 1-4. トレンド関数の予測 ここまで扱ってきた変更率 ですが、実際の予測の際にはどのような値を取ると良いでしょうか。論文を読むと、時系列データを予測する際に変更率 を下記のようにシミュレーションさせるとあります。 まずラプラス分布のスケール を決定するためベイズ推定で事後分布を得るか、そうでなければ最尤推定的に解いて を分布のスケールとします。この場合の は過去に出現した変更率 の絶対値の平均です。 過去の時系列の長さが 個、そのうち成長率の変更のあった時点が 個と定義したので、変更点の発生確率は 、発生しない確率は と言えます。 これらを踏まえ、論文では将来の について下記のように定義しています。 左の式ですが、 は全称記号なので より大きい任意の つまり未来に起きるすべての変更点について、という意味になります。右の式は、 は with probability の略なので の確率で の確率で は の分布に従う乱数 という意味になります。 まとめると、未来の変更率 を求めるには、まず の確率で が0になるかラプラス分布に従う乱数となるかが決まり、ラプラス分布に従う場合は その分布の乱数が変更率 となる...以上のプロセスを予測したい時点ぶん繰り返すことになります。 この一連のシミュレーションをコードで書くと下記のようになります。トレンド関数にはロジスティックのほうを用いています。 class LogisticTrendEstimator : def fit (self, T, S, delta, k= 1 , C= 1 , m= 0 ): self._T = T self._S = S self._delta = delta self._k = k self._C = C self._s_freq = len (S) / len (T) self._mu_delta = np.abs(delta).mean() self._y, self._gamma = self._logistic_trend(T, S, delta, k, C, m) def _logistic_trend (self, T, S, delta, k= 1 , C= 1 , m= 0 ): a = np.vstack([np.where(S < t, 1 , 0 ) for t in T]) gamma = np.zeros(S.shape) for j in range ( 0 , gamma.shape[ 0 ]): gamma[j] = (S[j] - m - gamma[:j].sum()) * ( 1 - ((k + delta[:j].sum()) / (k + delta[:j + 1 ].sum()))) y = C / ( 1 + np.exp(-(k + (a * delta).sum(axis= 1 )) * (T - (m + (a * gamma).sum(axis= 1 ))))) return y, gamma def forecast (self, length= 10 , seed= None ): np.random.seed(seed=seed) # generate future change point, and its change rate occurrence = np.random.binomial(n= 1 , p=self._s_freq, size=length) generated_s = np.where(occurrence == 1 )[ 0 ] + self._T.max() generated_delta = np.random.laplace( 0 , self._mu_delta, generated_s.shape[ 0 ]) # predict future = np.arange(length) + self._T.max() future_y, _ = self._logistic_trend( T=future, S=generated_s, delta=generated_delta, k=self._k, C=self._C, m=self._gamma[- 1 ] ) # plot y plt.plot(self._T, self._y, c= 'steelblue' , label= 'past' ) plt.plot(future, future_y, c= 'darkorange' , label= 'predict' ) plt.xlabel( "t" , fontsize= 16 ) plt.ylabel( "y" , fontsize= 16 ) # plot change point ymax=np.max([self._y.max(), future_y.max()]) ymin=np.min([self._y.min(), future_y.min()]) plt.vlines( np.hstack([self._S, generated_s]), ymin=ymin, ymax=ymax, linestyle= 'dashed' , color= 'gray' , label= 'change point' ) plt.legend() plt.show() return future_y T = np.arange( 100 ) S = np.array([ 20 , 60 , 80 ]) delta = np.array([- 0.03 , 0.01 , 0.02 ]) estimator = LogisticTrendEstimator() estimator.fit(T=T, S=S, delta=delta, k= 0.01 , m= 0 ) pred = estimator.forecast(length= 100 , seed= 123 ) 2. : 季節変化 次に季節変化を表現する下記の式を理解していきます。 英語の直訳で「季節変化」と書きましたが、意味的には季節を含め、週、月、年といったあらゆる周期性を で扱えます。 季節による変動がある → 周期性がある → 信号処理っぽく表現できる、という発想で は下記のように一般的なフーリエ級数で表現されています。 この式を理解するために、まずフーリエ級数展開の気持ちを簡単に復習します。 フーリエ級数展開について 下記のような曲線をどうにかして関数 で表したいとします。 (これはもちろん私が作ったので事前に知ってるだけですが)調べたら下記の式で表せることがわかりました。 どうやらこの曲線は3つの三角関数の和で表現されているようです。3つの三角関数をバラバラにプロットした図をみると下記のようになります。 このようにマクローリン展開などと違って、sinやcosなどの三角関数の和で関数近似しようというのがフーリエ級数展開の特徴です。 季節性の売上など、周期性をもった波形のデータであれば というように各周波数(ここではt, 2t, 3t...のこと)の成分を追加していけばどのような波形でも表現可能になります。 ここまでの話をまとめるため、少し強引に一般化した式に直すと のようにsin波とcos波の和で表現できます。 三角関数の周波数について Prophetの季節関数を理解するためにあと一点だけ、周波数 の部分について深掘っていきます。 周波数はsin波cos波の振幅数を表しています。 という単位で一周するので、例えば30日のうち1週間ごとに一周するsin波を表現したい場合は という式になり、周波数の分母で周期の単位(ここでは1週間なので7)を指定します。 これをグラフ化すると下記のようになり、30日の間にsin波が4周している(30日とは4週間ちょっとの期間なので)ことが見てとれます。 Prophetの季節関数では、このような週、月、年単位の周期を持つことを柔軟に表現できるよう変数 を用いて という変形をしています。さらに が無限大だと計算量が過多になる & 正の実数のみで十分に季節変化を表現できるため、 を に直し、 に書き換えると と変形でき、最初に示した季節変化の関数 が得られます。 パラメタ推定しやすい形に変形する このとき最適化すべきは ] の部分、合計2N個のパラメタなので扱いやすいベクトルで抜き出して表現します。 残りの三角関数の部分もベクトル化して抜き出します。仮に の粒度までで関数近似し、年単位の季節変化に見るために とした場合には、 というベクトルを作ります。 ※ 閏年を含めると1年の平均日数は365.25 これらを用いて結局 は下記のように変形できます。 論文ではさらに とし、フーリエ級数の各係数に正規分布を仮定しているようです。 ちなみにパラメタ は多いほど周期変動にうまく当てはまるようになりますが、同時にオーバーフィッティングしてしまう問題も抱えています。論文では、年単位の周期なら 、週単位なら くらいで程よくフィッティングすると書いてあります(個人的にN = 10 はやや多いのでは、という気もしますが)。 3. : 休日効果 最後に突発的なイベント効果をモデルに組み込む について見ていきます。 これも英語の直訳で「休日効果」と書きましたが、意味的には休日含むイベント全てを で扱えます。 休日やイベントの多くは事前に予見できるわりに周期といったものはなく、季節変化 では取り入れにくい要素です。そのため自動検知云々は諦め、Prophetでは分析者自身がイベントカレンダーのリストを作ってモデルに組み込めるように設計しています。 この設計をProphetではどのような数式で表しているのかを見ていきます。 あるイベントを とし、それに該当する日付を全て含んだベクトル を作ります。 たとえば なら というように過去と未来全ての12月25日を含むことになります。さらに各時点 が に該当するか否かを表すインディケーターとして というベクトルを定義します。ある時点 が各イベント に該当するかのフラグが 0 or 1で入っています。 各イベント に対する係数パラメタを とし、そのベクトルを で表すと、最終的に休日効果 は となり、季節変化 と同様にパラメタ部分だけをベクトルに分離して表されています。 季節変化のパラメタ と同様に も下記のように正規分布が仮定されています。 まとめ 冒頭のProphetのモデル式を再掲すると下記のような、主に3つのコンポーネントからできていました。 各要素は最終的に下記のように展開できました。 ...本当はこれら未知のパラメタの最適化について書かないとキリが悪いのですが、 もう体力が 記事のボリュームが限界なので簡単に述べます。 諸々のパラメタを定義していく中で、記事中では下記の3つについてわざわざ確率分布を仮定していました。 パラメタごとに分布を仮定しておくと状態空間モデルとして扱うことができます。実際にProphetではこのモデル式がStanで記述され、L-BFGS法などで最適化されているようです。 最後に 私自身が数学があまり得意でないのでかなり噛み砕いて書いてみました。どなたかの理解のとっかかりになれば幸いです。 明日のアドベントカレンダーは Owners Growthチームの大木さんと、PAY株式会社のセキュリティエンジニア、クリスさんです! お楽しみに!