TECH PLAY

株式会社mediba

株式会社mediba の技術ブログ

167

こんにちは、medibaのエンジニアカルロスです! 昔、暗号(crypto)に強い関心があったにもかかわらず、暗号通貨にはあまり興味が持てません。Bitcoinは始まったころに少し採掘していましたが、大した量は稼げずウォレットのログイン方法も覚えていません。 尚、生活側面の全てを計装化された経済に移行することに対する現代の興奮には共感できません。 厳密に言えば、技術的なレベルだと、私はまだ信者になれていません。 そこで、現在web3について注目すべき点をふまえた上で、いくつかの点を徹底的に勉強して、見逃していることがあるか確認することにしました。 Web 1.0と2.0についての意見 web3はやや曖昧な用語であり、web3の野心を厳密に評価することは困難ですが、一般的な理論では、以下になります。 web1:分散化 web2:すべてをプラットフォームに集中化 web3:すべてを再び分散化 web3はweb2の豊かさを提供しつつ、分散化されています。 まずは、集中型プラットフォームが何故登場した理由をある程度明確にするほうがいいでしょう。私の考えでは、説明は非常に簡単です! 誰も自分のサーバーを管理したくないし、決してしません web1の前提は、ネット上の全ての人がコンテンツの発行者であると同時に消費者であり、インフラストラクチャの発行者と消費者でもあるということでした。 皆が、個人サーバーの上に個人ウェブページをホストし、個人のメアドのための個人のメールサーバーを用意し、個人のステータスメッセージ用の個人のフィンガーサーバーがあるようにします。 しかし、強調してもしすぎることはないだろうと思いますが、一般ユーザーがこれを望んでいることではないでしょう。一般ユーザーは自分のサーバーを管理したくないし、それはオタクでもしたくないことです。ソフトウェアを開発している組織でさえ、現時点ではサーバーを管理したくないです。私たちがこの世界について学んだことを1つ挙げるとしたら、それは人間がサーバーを管理したくないということです。 だから、代わりにそのサービスを提供してくれる会社は成功しました。そして、それらのネットワークの上の可能であることを拡張して新しい機能をイテレーション(※1)した会社はさらに成功しました。 プロトコルはプラットフォームよりもはるかにゆっくりと移動します 30年以上経っても、電子メールはまだ暗号化されていません。一方、WhatsAppは1年で暗号化されていない状態から完全なe2eeに移行しました。 未だにIRCを介してビデオを確実に共有することを標準化しようとしていますが、一方、Slackでは、ユーザーの表情に基づいてカスタムのリアクション絵文字を作成できます。 これは資金調達の問題ではなく、何かが完全に分散化されていると、変更が非常に難しくなり、時の流れの中で取り残されたままになることがよくあります。これはテクノロジーの問題であり、エコシステムの動きが非常に速くて常に並走していなければ置いていかれます。膨大な数の人々のグループを編成して早く移動できるようにする方法を見つけることが非常に重要であるため、アジャイルのような方法論の定義と改善に焦点を当てた並行業界全体が存在するのでしょう。 テクノロジー自体が動きよりも停滞を助長するとなると大問題です。成功の確実な秘訣は、時間に追われていた90年代のプロトコルを採用し、それを集中化して、迅速に反復することだと考えられます。 しかし、web3の意図は違うところにあるので、そのことを確認してみましょう。技術の感じをつかんで将来がどうなるかをよりよく理解するためにいくつかのdAppを利用してNFTを作成することにしました。(今後自分で作成したいみたいと思いますが…) 分散型アプリケーション(dApp)を使ってみました web3を感じ取るために AutonomousArt というdAppを使ってみました。これにより、NFTに視覚的な貢献することで、誰でもNFTのトークンを発行できます。視覚的な貢献をするためのコストは時間の経過とともに増加し、貢献者がミントのために支払う資金は以前のすべての貢献者に分配されます(この財務構造はピラミッド型に似たものになります)。 そして、原資産を追跡する金融デリバティブのように、原資産を追跡するNFTデリバティブを作成、発見、交換させてくれる FirstDerivative というdAppも使ってみました。 どちらもdAppの仕組みを感じさせてくれました。正直にいうと、アプリ自体について特に「分散」されているものはありませんでした。アプリは通常のReactのウェブサイト… その「分散性」とは、ステートとそのステートを更新するためのロジック/権限が存在する場所になります。その場所は「集中型」データベースではなく、ブロックチェーンの上です。 私がいつもおかしいと思っているのは、クライアント/サーバーのインターフェイスへの注意の欠如です。誰かがブロックチェーンについて話すときには、信頼の分散、指導者のない総意、そして仕組みがどのように動いているかについて話しますが、多くの場合最終的にクライアントができないことは何であるかがうやむやになっているように思えます。 どんなネットワーク図にも載っているものはサーバーばかりで、信頼モデルもサーバー間のものであり、すべてはサーバーに関するものです。ブロックチェーンはピアのネットワークとして設計されていますが、モバイルデバイスまたはブラウザがピアになれるようには設計されていません。 たとえば、モバイルでもPCでも、AutonomousArtやFirstDerivativeのようなdAppは、ステートの変更またはレンダリングするため何らかの方法でブロックチェーンとやり取りする必要があります。ただし、ブロックチェーンはモバイルデバイス(もしくはPCブラウザー)上で動作できないため、クライアント側からこれを行うことは実際には不可能でしょう。したがって、唯一の代替手段はどこかのサーバー上でリモートで実行されているノードを介してブロックチェーンと対話することです。 やっぱりサーバーだ!でも、すでに書いたように、人はサーバーを管理したくありません。偶然にも、サービスとしてetheriumノードへのAPIアクセスを販売する企業が出現し、分析、拡張API、および履歴トランザクションへのアクセスを提供しています。なんか聞いたことありますよね…現時点で、ほとんどすべてのdAppは、ブロックチェーンと対話するために2つの会社を使っています。InfuraまたはAlchemy。実際、MetaMaskのようなウォレットをdAppに接続し、dAppがウォレットを介してブロックチェーンと対話する場合でも、MetaMaskはInfuraを呼び出しているだけです。 これらのクライアントAPIは、ブロックチェーンの状態や応答の信頼性を承認しようとしていません。結果は署名すらされていません。AutonomousArtのようなアプリは、「このスマートコントラクトでのこのビュー機能の出力は何ですか」と訊いて、AlchemyまたはInfuraは、「これは出力だよ」というJSON BLOBで応答し、アプリがそれをレンダリングします。 これは驚きでした。信用不要(※2)の分散型コンセンサスメカニズム(※3)の構築には多大な労力、エネルギー、時間が費やされてきたのに、それにアクセスしたいほとんどのクライアントは、検証なしに、この2つのサービスからの出力を単に信頼しています。また、プライバシーの面からも最善ではないでしょう。すべてのwriteトラフィックは、もちろんブロックチェーン上ですでに公開されていますが、この2つのサービスはほぼすべてのdAppのユーザーからの読み取り要求を可視化することが可能となっています。 NFTを作る それから、もう少し一般的なNFTを作成してみたいと思いました。ほとんどの人は、NFTについて考えるときに画像やデジタルアートについて考えますが、普段はNFTがそのデータをチェーン上に保存しているわけではありません。ほとんどの画像のNFTの場合、これは非常に費用のかかるものです。画像のデータをチェーンに保存する代わりに、データを指すURLになっています。この標準について私が驚いたのは、URLにあるデータにハッシュコミットメントがないことです。数十ドル、数百ドル、または数百万ドルで販売されている人気のある市場のNFTを見ると、そのURLは、Apacheを実行しているVPSを指していることがよくあるようです。そのマシンにアクセスできる人、将来そのドメイン名を購入する人、またはそのマシンを侵害する人は、NFTの画像、タイトル、説明などをいつでも好きなように変更できます(そのトークンを「所有」していないにもかかわらず)。NFT仕様には、画像が「こうであるべき」や、「正しい」かどうか確認させてくれるものは何もありません。 インターネットを再現する web1がweb2になった理由を考えると、web3の奇妙なところは、ethereumのような技術がweb1で既に明らかになっているはず多く罠で構築されていることです。これらの技術を使用可能にするために、プラットフォームを中心に統合されています。また同じことが起こっています。あなたの代わりにサーバーを運営し、出現する新しい機能をイテレーションするサービス。それはInfura、OpenSea、Coinbase、Etherscan、などです。 同様に、web3プロトコルの進化は遅いです。 例えば、 First Derivativeは原資産の価値のパーセンテージとしてミントの価格を設定することもできたけれど、そのデータはチェーン上にありません。OpenSeaが提供するAPI内にあります。多くの人がクリエイターに利益をもたらす方法でNFTの特許権使用料に期待していますが、使用料はERC-721で指定されておらず、変更するには遅すぎます。そのため、OpenSeaには、web2スペースにその使用料を構成する独自の方法があります。集中型プラットフォームでの迅速な反復は、すでに分散プロトコルを上回り、制御をプラットフォームに統合しています。この方法だと暗号通貨ウォレットでのNFTのビューがOpenSeaのNFTのビューになっているとしても驚きはないでしょう。OpenSeaは、標準を変更することが不可能/困難で、可能な範囲を超えてプラットフォームを反復することに忙しいため、置き換え可能な純粋な「ビュー」ではないことに驚くことではありません。 これはメールの状況と非常に似ていると思います。自分のメールサーバーを運用することはできますが、プライバシー、検閲への抵抗、または制御に関しては、私が送受信するすべてのメールの裏にGMailがいるから、結局意味がありません。分散型エコシステムが利便性のためにプラットフォームを中心に集中すると、両方の世界の悪い部分だけを抽出した形になってしまいます。集中管理されていますが、時間内に混乱するほど十分に分散されています。自分のNFTマーケットプレイスを構築することはできますが、OpenSeaがユーザーが使用するウォレット内のすべてのNFT(およびエコシステム内の他のすべてのアプリ)のビューを仲介するため特別な制御は何も提供できません。 創造性は十分ではないかもしれません web3を試しに少しやってみただけですが、この小さな試しの視点から見てみると、なぜこれほど多くの人たちがweb3エコシステムをとてもきれいだと思うのかが簡単にわかります。集中化されたプラットフォームから離せてくれる軌道になっていることやテクノロジーとの関係が根本的に変わるとは思いませんし、プライバシーのところもすでにインターネットの水準を下回っていると思いますが、私のような開発者がここで何かを構築することに興奮している理由もわかります。少なくとも新しいものであり、初期のインターネット時代を思い出させる創造性/探索出来るようなスペースになっているからです。皮肉なことに、その創造性の一部は、おそらくweb3を非常に不格好にする制約から生じています。今目にしている創造性と探求が前向きな結果をもたらすことを願っていますが、過去のインターネットと同じ帰結を防ぐために十分かどうかはわかりません。 テクノロジーとの関係を改善したいのなら、意図的にやらなければならないと思うのは: インフラストラクチャを分散させずに信頼を分散できるシステムを設計することで、皆が自分のサーバーを管理しないという前提を受け入れる必要があります。 ソフトウェア構築の負担を軽減するよう努めるべきです。 しかし、正直いうとGameStopとLoopRingが作ろうとしているマーケットプレイスには少し期待があります!! 私からは以上です。最後まで読んでいただきありがとうございました。 参考 ※1 https://it-trend.jp/development_tools/article/32-0022 ※2 https://komugi.jp/?p=859 ※3 https://support.okcoin.jp/hc/ja/articles/4403552067865
アバター
こんにちは、データアナリストの左海です。 最近アサインされたプロジェクトにてアプリ/WebのGoogle Analytics 4(以下GA4)での計測を検討していましたが、私自身アプリもGA4も知見がなかったため、SwiftUIでネイティブアプリを作成しログ検証を行うことにしました。 ※なお、Apple Developer Programを契約していないためiOS Simulatorを使用しています。 実行環境 XcodeのVersion:13.2.1 (13C100) iOS Simulatorの名前:iPhone 13 iOSのversion:iOS 15.2 最終的にできたもの 以下の通り、テストアプリを作成しGA4のDebugViewでscreen_viewイベント、カスタムイベントが意図したタイミングで飛んでいるか検証可能になりました。 作業手順 以下の作業手順の通りに進めていきます。 SwiftUIでネイティブアプリの作成を行う ネイティブアプリへFirebase SDKを追加する GA4のDebugViewでログを検証する それでは、ネイティブアプリ作成の流れ、Firebase SDKの追加、GA4を用いたログ検証について説明していきます。 1.SwiftUIでネイティブアプリの作成を行う 画面遷移のイメージ まずは、XcodeでFirebase SDK追加に必要なアプリの作成を行います。 ここでは、3つの画面を作成します。画面遷移は添付画像のようなイメージです。 ContentView SubView MyWebView ContentViewの作成 ContentViewから作成します。ContentViewでは、SubView、MyWebViewそれぞれに遷移するリンクを作成し、Buttonも画面右上部に配置します。加えて、イベント送信処理も追加します。 画面遷移は以下の通り NavigationLink を用います。 destination には遷移先のView名を、 Text() にはリンクに表示する文字列を記述します。 NavigationLink(destination: SubView()) { Text("Go SubView") } NavigationLink(destination: MySecondWebView(urlString: "https://test-c1632.web.app/")) { Text("Go WebView") } screen_viewイベントはインスタンスメソッド onAppear でContentViewが描画されたタイミングで送信します。 イベントパラメーターとして、 screen_name と screen_class を定義しておきます。 同様の処理をSubView、MyWebViewでも記述していきます。 .onAppear() { Analytics.logEvent(AnalyticsEventScreenView, parameters: [AnalyticsParameterScreenName: "\(ContentView.self)", AnalyticsParameterScreenClass: "\(ContentView.self)"] ) } 次に、画面右上部のButtonをタップした際に送信するカスタムイベント button_tap を追加します。イベントパラメータとして button_name を定義します。今回送信するイベント送信処理の記述は以上です。 Button(action: { let button_name = "hoge" Analytics.logEvent("button_tap", parameters: ["button_name" : button_name as NSObject,] ) } SubViewの作成 ContentViewの遷移先であるSubViewの作成を行います。ここではダミーの遷移先を設定しておきました。 MyWebViewの作成 ContentViewの遷移先であるMyWebView画面の作成を行います。MyWebView画面は私が作成したWebサイトを表示します。 WebサイトはFirebase Hostingで公開しています。 これでアプリの作成は終了です。以下の通り、問題なくビルドすることができました。 2.アプリへFirebase SDKを追加する アプリに接続するFirebase プロジェクトを新規作成する 作成したアプリにFirebase SDKを追加します。まずは、 Firebase コンソール でアプリに接続するための Firebase プロジェクトを新規作成します。 作成したアプリをFirebaseに登録する プロジェクト概要 > iOS+ アイコン > 設定ワークフローに進みます。すると、Apple バンドルIDの入力を求められます。 ナビゲーターエリアの上のプロジェクト名 > TARGETS のプロジェクト名 > General タブでApple バンドルIDを確認できます。 設定ファイルのダウンロード ダウンロードした GoogleService-Info.plist ファイルをXcode プロジェクトのルートに移動し、すべてのターゲットに追加します。 Firebase SDKを追加する Firebase SDKはいくつかインストール方法がありますが、今回はCocoaPodsを利用します。 Podfileを作成する プロジェクトディレクトリのルートから次のコマンドで、Podfileを作成します。 pod init PodfileにFirebaseAnalyticsを追加する アプリで使用する'Firebase/Analytics'をPodfileに追記します。 # Add the Firebase pod for Google Analytics pod 'Firebase/Analytics' # For Analytics without IDFA collection capability, use this pod instead# pod ‘Firebase/AnalyticsWithoutAdIdSupport’ # Add the pods for any other Firebase products you want to use in your app# For example, to use Firebase Authentication and Cloud Firestore # pod 'Firebase/Auth' # pod 'Firebase/Firestore' Podをインストールする pod install を実行します。ただし、M1 Macだと上手くインストールできないことがあるようです。 私の環境下ではターミナルのアーキテクチャに依存していたので以下のページを参考に、ターミナル > 右クリックで「情報を見る」 > 「Rosettaを使用して開く」 に事前にチェックを入れます。そうすることで、問題なくPodがインストールできました。 【参考】M1 Macでpod installを実行する pod install Podインストールが完了すると、 .xcworkspace ファイルが作成されます。以降は .xcworkspace ファイルからXcodeプロジェクトを開きます。 初期化コードを追加する アプリ起動時に Firebase を接続するには、Xcodeプロジェクトの@mainアトリビューションの指定の場所に指定のコードを追加します。 import SwiftUI import Firebase //追記 @main struct testApp: App { var body: some Scene { WindowGroup { ContentView() } } init() { FirebaseApp.configure() //追記 } } これで、Firebase SDKの追加は完了です。 3.GA4のDebugViewでログ検証を行う 最後に、GA4でログ検証を行います。早速ログ検証といきたいところですが、事前にいくつか設定を行う必要があります。 Debug Modeを有効にする Xcodeを開き、メニューバー > Product > Scheme > Edit Scheme > Run > ArgumentsのArguments Passed On LaunchにFIRDebugEnabledを追加することで、Debug Modeを有効にします。 Firebaseの自動ログ取得をオフにして手動ログ取得を設定する 今回SwiftUIとの相性かもしれませんが、自動ログ取得では画面遷移でscreen_viewイベントが飛んだり飛ばなかったりしていたので、手動でログ取得をする実装にしています。 Info.plist ファイルに以下を追加することで自動ログ収集をオフにします。 Key:FirebaseAutomaticScreenReportingEnabled Value:NO(Boolean) デバック時にXcodeのReport Navigatorで以下のようなログが確認できれば、自動ログ収集がオフになっています。 Analytics screen reporting is disabled. UIViewController transitions will not be logged. ログ検証 Debug Viewでscreen_viewイベント、buttun_tapイベントが計測されているのが分かります。問題なくログが飛んでいます、検証完了です。 BigQuery連携 BigQueryに連携してエクスポートされているログを確認しました。添付画像のカスタムイベントbutton_tapのログは実装した通り、button_name に設定した値 hoge が格納されています。 おわりに 今回無事にアプリを完成させてログ検証を行うことができました。私自身プログラミング未経験だったため、取り組む前は分からないことだらけでした。具体的には、プログラミングの基礎がなく、エラーの解決方法が分かりませんでした。他にもターミナルやGitHubで何ができるのかすら知りませんでした。 しかし、Udemyで学習したり、分からないことを一つ一つ検索エンジンで調べることで問題解決することができました。アプリ制作の場面はエラーの連続でしたが、エラーを解決した瞬間が最も楽しかったです。 そして、何を取り組むにしても「やればできる」と自信がつくようになりました。これからもさまざまなことにチャンレンジしていきます。 どなたかの参考になれば幸いです。
アバター
はじめまして、mediba FEエンジニアの楊です。 最近猫パンチ避け上手になっているので、猫を困らせています。 React Native初見 ネットで調べてみて、第一印象は「可愛いかった」です。 その他に感じた印象は下記です。 facebookのcross-platformフレームワークで、1回書いたらAndroid、iOS、Web全部動くだろう。 React Native(以下RN)という名前付けなので、React開発者にとって、使いやすいだろう という浅い認識で、チームメンバーと一緒にRNのプロジェクトを書いてみました。 React Native振り返る ある程度コードを書いてみると、以下の事に気が付きました。 バージョンアップが早い 最近のリリースを見ると1年間3個のメインバージョンがもうリリースされて、RNのバージョン管理が課題になりそう、特にキャッチアップには、互換性の問題が目の前に debugの難しさ Web開発と比べると、RNでよくみる真っ赤な画面のエラー画面あるが、それのメッセージだけで、フロントかネイティブかのエラーの判断は難しい 要素の高さがスクリーンの高さを超えたらスクロールできない Webだと生まれつきでスクロールできるけど、RNの場合一番外側にスクロール可能のエレメントでwrapしてあげないと iOSとAndroidにおいての違い ある程度RNがその違いを埋めてくれてますが、それぞれの環境に依存した処理がある(外部libで済む場合も) リストの選択 RNのリスト、元々listViewとflatList二つもあって、listviewは性能的な問題で放棄されて、flatListが推奨されてます(実際カルーセルを作った際に困ってた) つまり、RNは依存心の強い子でした。 印象的ポイント RNを触ってみて、一番興味深いところというと、native codeで書いたSDKを扱える部分でした、いわゆる Native Module Bridging というものです。 前述の通りある程度RNがiOSとAndroidの違いを埋めてくれてますが、それぞれのOSに依存したAPIはJSでの扱いは不可なものもあり、あるいはより高いパフォーマンス的、マルチスレッド的に作りたい場合、native moduleを介してそれを実現できます。 実際手を動かして、 公式ドキュメントをみながら、自分なりに簡単な「hello-world」を作ってみました。(学校でJavaを学んでたので、個人的に親切な Android を選んだ^^) ステップ: 環境設定 なんとなく動くnative code書く RNを介して、native moduleとして、JS側にに披露(expose)する JS側で呼び出し 環境設定 公式ドキュメント に沿って環境設定できます。JavaScript(以下JS)、TypeScript(以下TS)両方作れますが、 自分はTSのテンプレートを作りました(TS最高) なんとなく動くnative code書く 環境設定終わりましたら、下記のandroidのフォルダで作業をしていきます(自分はJavaの構文あまり自信ないから、Android Studioでandroidフォルダーを開いてコードを書いているw) android/app/src/main/java/com/awesomeproject の配下で HelloModule.java という新規Javaファイルを作ります。 ファイルの中身はこんな感じ: public class HelloModule extends ReactContextBaseJavaModule {    private static ReactApplicationContext reactContext;    HelloModule(ReactApplicationContext context) {        super(context);        reactContext = context;    }    public static void setTimeout(Runnable runnable, int delay){        new Thread(() -> {            try {                Thread.sleep(delay);                runnable.run();            }            catch (Exception e){                System.err.println(e);            }        }).start();    }    @ReactMethod    public void sayHelloToPopUp(String name) {        Toast.makeText(reactContext,"Hello World,"+name,Toast.LENGTH_LONG).show();    }    @ReactMethod    public void sayHelloAfterThreeSecond(String name, Promise promise) {        setTimeout(()->promise.resolve("Hello after 3 seconds,"+name),3000);    }    @NonNull    @Override    public String getName() {        return "HelloModule"; //この命名でNativeModulesに追加    } }; とりあえず試しに、下記二つの関数を作ってみました。 AndroidのToastを使って出力できる sayHelloToPopUp Promiseを返す sayHelloAfterThreeSecond App(JS側)に披露(expose)する init後に自動生成された android/app/src/main/java/com/awesometsproject/MainApplication.java の中身を見てみると protected List getPackages() {          @SuppressWarnings("UnnecessaryLocalVariable")          List packages = new PackageList(this).getPackages();          // Packages that cannot be autolinked yet can be added manually here, for example: ※        // packages.add(new MyReactNativePackage()); ←この行を解放          return packages;        } 既に用意してくれたPackage名でpackageを作ります。上記のファイルと同じフォルダー配下で MyReactNativePackage.java のファイルを作成し、nativePackage作り、NativeModuleに先程作成したHelloModuleを追加すれば完成です。 public class MyReactNativePackage implements ReactPackage {    @NonNull    @Override    public List createViewManagers(@NonNull ReactApplicationContext reactContext) {        return Collections.emptyList();    }    @NonNull    @Override    public List createNativeModules(            @NonNull ReactApplicationContext reactContext) {        List modules = new ArrayList<>();        modules.add(new HelloModule(reactContext));        return modules;    } } JS側で呼び出し 最後の一歩は、App.tsxで使うことですね。   import { NativeModules } from 'react-native';   まずNativeModulesをimport,それから先程作った sayHelloToPopUp と sayHelloAfterThreeSecond を呼び出します!  const { HelloModule } = NativeModules  const sayHello = React.useCallback((words)=>{    HelloModule.sayHelloToPopUp(words)  },[])  const sayHelloThreeSecondsLater = React.useCallback(()=>{    HelloModule.sayHelloAfterThreeSecond('lalalalala').then((res)=>sayHello(res))  },[]) ... <button title="{"> 最後、rootでyarn androidすれば、起動を待ち、シミュレーターが立ち上がってアプリインストール完了、アプリでpressボタンをポチる 3秒後に添付画像のトーストが出てきます 感想 RNのようなクロスプラットフォームのフレームが宣伝したように、「一回書いたらどのプラットフォームでも動ける」という理想があるが、 実際にプロジェクトを作るに当たって、ネイティブアプリもフロントを全部理解し、一人でのiOS,AndroidとWeb全てのコードを書けることはありえない。 しかしRNを架け橋に、ネイティブチームとフロントチームを連携し、斬新なビッグ(B.I.G)フロントチームを生み出せるではないでしょうか。 ことわざの通り「理想なき者に成功なし」^^
アバター
こんにちは。Eng6G バックエンドエンジニアの土屋( @hrktcy )です。もうすぐ社会人2年目ということで月日の流れがとても早く感じます。 はじめに 現在開発中のプロダクトで、API リクエストパラメータに含まれるお客様の個人情報をデータベースに暗号化して格納するためにAWS Key Management Service(KMS) を導入することになりました。導入するにあたり、KMS での暗号化のリクエストパフォーマンスが、開発中のシステムに定めたSLA に耐えうる性能かを検証する必要がありました。本記事ではその結果を共有したいと思います。 検証項目 出力するものは以下の3つです。 平均暗号化処理時間(ms) 各暗号化処理時間を昇順にソートした際の90パーセンタイル値(ms) 最大暗号化処理時間(ms) 環境 ローカル環境でGolang で検証用のサンプルコードを書きます。そしてクロスプラットフォームビルドによって生成されたバイナリファイルをS3 にPUT し、EC2から実行します。 言語 Golang SDK aws-sdk-go-v2 実行場所 EC2(t2.micro/x86) 暗号化する文字列を作成する関数を定義する まず、検証用の文字列[0-9a-zA-Z](32文字)を生成する関数を定義します。生成には math/rand パッケージを用いてランダムな文字列を生成するようにします。生成数はコマンドライン引数から指定できるようにしておきます。 package main import ( "math/rand" ) func randomStr(n int) string { letters := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" lettersLen := len(letters) dst := make([]uint8, n) for i := 0; i < n; i++ { l := rand.Intn(lettersLen - 1) dst[i] = letters[l] } return string(dst) } 暗号化処理を行う関数を定義する 暗号化処理を行うexec 関数を定義します。この関数をgoroutine で並行処理するようにします。sync.waitGroup のDone() をdefer することで、処理の最後に完了を知らせるようにしておきます。 var wg sync.WaitGroup var keyID = "*******" // マスターキーのKeyID var client *kms.Client // data 結果格納用構造体 type data struct { Encrypted []byte ProcessTime time.Duration } func exec(plain string, results *[]data) { defer wg.Done() // 計測開始 start := time.Now() // keyIDと暗号化する文字列を代入 input := &kms.EncryptInput{ KeyId: &keyID, Plaintext: []byte(plain), } // 暗号化リクエスト output, err := client.Encrypt(context.TODO(), input) if err != nil { fmt.Printf("Got error encrypting data:%v\n", err) return } // 暗号化文字列と処理時間をdata型の変数に代入する result := data{ Encrypted: output.CiphertextBlob, ProcessTime: time.Since(start), } *results = append(*results, result) } main 関数を実装する 前述の関数を用いてmain 関数を実装します。main 関数ではrandomStr 関数で生成した文字列を格納する配列を用意し、KMS クライアントを生成してから暗号化処理をgoroutine で並行処理させます。なお、for ループの中でtime.Sleep() させることでTPS を調整できるように設定し、ループ処理が終了したら検証項目を計算して出力するようにします。 package main import ( "context" "flag" "fmt" "log" "math/rand" "sort" "sync" "time" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/kms" ) // data 結果格納用構造体 type data struct { Plain string Encrypted []byte ProcessTime time.Duration } var wg sync.WaitGroup var keyID = "*******" // KeyID var client *kms.Client var num = flag.Int("num", 100, "暗号化実行回数") var interval = flag.Int("interval", 1, "-interval ") // randomStr ランダム文字列を生成する関数 func randomStr(n int) string { letters := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" lettersLen := len(letters) dst := make([]uint8, n) for i := 0; i < n; i++ { l := rand.Intn(lettersLen - 1) dst[i] = letters[l] } return string(dst) } // exec 並列処理する関数 func exec(plain string, results *[]data) { defer wg.Done() // 計測開始 start := time.Now() // keyIDと暗号化する文字列を代入 input := &kms.EncryptInput{ KeyId: &keyID, Plaintext: []byte(plain), } // 暗号化リクエスト output, err := client.Encrypt(context.TODO(), input) if err != nil { fmt.Printf("Got error encrypting data:%v\n", err) return } // 暗号化文字列と処理時間をdata型の変数に代入する result := data{ Encrypted: output.CiphertextBlob, ProcessTime: time.Since(start), } *results = append(*results, result) } func main() { flag.Parse() // 検証用文字列準備 maxStrLen := 32 plainList := make([]string, *num) for i := range plainList { plainList[i] = randomStr(maxStrLen) } // KMSのクライアントを作成 loadOptions := []func(*config.LoadOptions) error{config.WithRegion("ap-northeast-1")} sdkCfg, err := config.LoadDefaultConfig(context.TODO(), loadOptions...) if err != nil { panic("configuration error, " + err.Error()) } client = kms.NewFromConfig(sdkCfg) // 暗号化処理実行 results := make([]data, 0) for _, v := range plainList { wg.Add(1) go exec(v, &results) time.Sleep(time.Millisecond * time.Duration(*interval)) } // 終了を待つ wg.Wait() // 結果判定 success := len(results) if success < 1 { log.Fatal("empty success result") } // あらかじめ昇順でソートしておく sort.Slice(results, func(i, j int) bool { return results[i].ProcessTime < results[j].ProcessTime }) // 平均暗号化処理時間を計算 total := int64(0) for _, v := range results { total = total + v.ProcessTime.Milliseconds() } fmt.Printf("avg:%dms\n", total/int64(success)) // 90パーセンタイル値を計算 percentile := int(float64(success)*0.9 - 1) fmt.Printf("Percentile:%dms\n", results[percentile].ProcessTime.Milliseconds()) // 最大暗号化処理時間を計算 fmt.Printf("max:%dms\n", results[success-1].ProcessTime.Milliseconds()) } 検証結果 500(req/sec)で400個の文字列暗号化を行った場合 avg(ms) 90 percentile(ms) max(ms) 9 9 68 500(req/sec)で1000個の文字列暗号化を行った場合 avg(ms) 90 percentile(ms) max(ms) 7 8 61 暗号化処理を増やした場合でも各暗号化処理時間に大きな差は生まれませんでした。しかし、最大暗号化処理時間は90パーセンタイル値よりも大幅に時間がかかっていることがわかりました。この値は初回暗号化処理時間の値でしたので、2回目以降はKMS クライアントを使い回しており、初回はKMS クライアントを読み込んでいる分時間がかかっているのではないかという推測をしました。 暗号化処理毎にKMSクライアントを読み込んでみたらどうか main 関数内で行っているKMS クライアント作成処理をexec 関数内に移動し、暗号化リクエストの都度クライアントを作成するように変更してリクエストパフォーマンスを検証してみます。 同様の条件で検証を試みたところ、以下のIMDSの認証エラーが生じてしまい暗号化が行えませんでした。 Got error encrypting data:operation error KMS: Encrypt, failed to sign request: failed to retrieve credentials: no EC2 IMDS role found, operation error ec2imds: GetMetadata, http response error StatusCode: 429,request to EC2 IMDS failed [参考]:429 Too Many Requests そこで、暗号化処理数を減らして処理速度を計測してみます。 500(req/sec)で10個の文字列暗号化を行った場合 avg(ms) 90 percentile(ms) max(ms) 52 65 73 500(req/sec)で100個の文字列暗号化を行った場合 avg(ms) 90 percentile(ms) max(ms) 749 2113 2158 処理数を増やすと応答時間が長くなることが分かりました。TPS を下げてみたらどうなるでしょうか。 100(req/sec)で100個の文字列暗号化を行った場合 avg(ms) 90 percentile(ms) max(ms) 28 39 54 TPS を下げると各処理時間もだいぶ落ち着きました。 まとめ KMS クライアントを使い回すパターンでは初回暗号化処理時間に少し時間がかかるものの、暗号化処理数を変えても各処理時間に大きな差は生まれませんでした。一方、都度KMS クライアントを読み込ませるパターンでは、応答時間が長くなる+大量の暗号化処理を行う場合はTPS を下げる必要がありそうです。
アバター
初めに こんにちは。エンジニアの野崎です。最近は現場でプロダクトの開発を行いつつ、 兼務としてCTO準備室に所属しテックリードというロールを持ち、複数のプロダクトにまたがるテクノロジーセンターの課題を 技術的な点を軸にしながら解決に向けて取り組んでいます。 ※参考記事: 2020年度エンジニア組織について | mediba Creator × Engineer Blog 本記事では、テックリードの取り組みとして21年度上期に検討し取り組み始めた 「新規開発時の技術選定の指針」 について その内容と背景をお伝えてしていきます。 特に今後エンジニアとして弊社に入社を検討頂ける方に向けて、 どのように技術と組織について考えているかをお伝えて出来ればと思います。 これまでの技術選定方針 これまでは、新規開発時には各プロダクト毎に利用する技術(ツール・ソフトウェア)を選定して来ました。 各プロダクト毎に利用する技術(ツール・ソフトウェア)を選定することで、 プロダクトやそのチームにあった開発をし、新しいものを積極的に取り入れることを目指していました。 この方針で数年たちましたが、この点は達成できてきました。 ただ、その中で特に運用保守において上手くいかない点・歪みが生まれてきました。 今回「新規開発時の技術選定」の指針にて、部分的に利用する技術(ツール・ソフトウェア)の指針を設けることによって 複数のプロダクトにおいても横断的に運用保守をより効率的に品質高く行うことを目指したものになります。 新規開発時における技術選定の指針 今回取り決めた「新規開発時の技術選定」の指針について、 そのまま公開は出来ませんので、3点にポイントを絞って説明します。 ■指針1. 以下の領域において、基本となるツール・ソフトウェアを指針を定義する パブリッククラウド コンピューティング バックエンドアプリケーション開発のプログラミング言語 データベース CI/CD環境 ここでは全て記載しませんが、 例えば「パブリッククラウド」においては「AWS」を利用すること基本とする、といった内容です。 また、ここに挙げた領域以外のもので検討中のものもあります。 例えば「ネイティブアプリケーション」についてですが、 社内で開発運用実績もありますが担当するチームが一つになっていることもあり、検討している段階になります。 領域やツール・ソフトウェアの選定に当たっては、 プロダクトにとってコアとなる技術領域か、ライフサイクルはどのくらいを見込んでいるかなど鑑み、 既存システムでの採用事例・既存メンバーのスキルセットを重要視し選定を行いました。 ■指針2.「指針1」に記載されていない場合は、センター内で話し合い決定する 今後新規開発時の要求要件によっては、「指針1」で定義したものでは実現出来ない可能性ももちろんあります。 その場合は、新規開発の担当チームだけではなく、他の開発チームも含めセンター内で話し合って決定していきます。その上で、導入事後のフィードバックを行い再評価します。 ■指針3.「指針1」の内容は1年程度のスパンで見直しを行う 「指針1」の内容も、長い年月を経ると継続的なメンテナンスに問題が出たり、 より良い代替のツール・ソフトウェアが出てくる場合もあります。 そういったメリット・デメリットを評価せずに技術を硬直化させることは望んではいないため、 定期的に内容を評価・見直していきましょう、ということです。 これら指針を軸に、 「横断的な検討→試行→評価」のサイクルを目指すことが骨子にあたります。 medibaにおける開発の特徴 ここでは、medibaにおける開発の特徴を取り上げ、 「新規開発時の技術選定」の背景を説明します。 大きな特徴として、 一つのプロダクト(システム)の新規開発から運用(グロースフェーズ)・保守まで、 社内で一気通貫で対応することが挙がります。 また、「新規開発」や「運用」を専門にするチームはなく、 あくまでプロダクト毎でのチームで担当するのも特徴的です。 社内で扱うプロダクト(サービス)数は数十ほどあり、 必然的にチーム組成の機会が多くなります。 下記の図を使って説明します。 例として、プロダクトAとBの時系列で「新規開発」「運用」「保守」のフェーズと その時点で必要とされるエンジニアを図示してみました。 プロダクトBの運用フェーズ開始時について考えてみます。 簡単のためにプロダクトAの運用メンバーが、プロダクトBの運用を担うことになる場合、 プロダクトAとBの採用技術が大きく異なる場合、 その技術的な習得、オンボーディングなどに大きく時間を取られてしまい、 本来運用(グロースフェーズ)に期待されるような積極的な開発やシステムの安定性を高める取り組みが達成できない、 あるいはそもそもチーム組成が難しいといった課題が発生してきました。 このような背景から上述の「新規開発時の技術選定」の指針を作成しました。 最後に ビジョン:「良いもの」を届け続ける テクノロジーセンターのビジョンとして、『「良いもの」を届け続ける』があります。 日々現場での業務を行っていると、 短期的な納期や自チームのメンバーにしか意識が集中することが多く、視点が狭くなりがちです。 時にはこう言ったビジョンに立ち返り、長期的に組織全体や業界全体にも目を向けつつ 「良いもの」を届け続けるために、自分たちが使う技術をより良く変化させていくことは、 組織としても個人としても更なる成長に繋がると信じています。 ビジョンの実現のために、 地道に一歩ずつ一緒に取り組んで頂けるエンジニアの方の応募をmedibaではお待ちしております。
アバター
mediba Advent Calendar 2021 の 18 日目の記事です。 こんにちは。エンジニアの中畑( @yn2011 )です。最近は Switch の月姫で直死の魔眼について学んでいます。 初めに プロダクトを新規に開発する際に、どんな Lint ツールを導入し、どのような設定で利用するかは悩むことが多いです。最終的にはチームメンバーと議論をして決めていく必要がありますが、社内外の他のプロダクトでどのようにしているか参考事例が欲しい場合があります。業務で開発したプロダクトのコードベースは社外に公開されることは少ないため、他社でどのような Lint ツールを導入しているのかを知る機会は少ないです。 そういった背景から、直近のプロダクト開発において ① どんな Lint ツールを導入しているか、② 特に工夫している設定は何か、 という Lint にフォーカスした内容を書いたらどうだろうと考え、今回の記事のテーマとしました。 昨年から引き続き、mediba のプロダクトでは Next.js の採用が増えています。その中のとある Next.js 採用プロダクトを例として今回ご紹介します。 tsc TypeScript です。Next.js 9 以降は、TypeScript 関連の機能はビルトインされていて yarn build 時に型検査を行いますが、型検査だけを任意のタイミングで行う場合は、 tsc -p . --noEmit のようなコマンドを実行します。Lint ツールではありませんが、型検査のためにだけ使用しているので一応記載しました。 ESLint ESLint です。Next.js 11 以降は、ESLint 関連の設定をビルトインするようになり、 next lint で実行と設定ができます。 Next.js 向けのコンフィグとして、 eslint-config-next があります。以下のルールはオフにしています。 "@next/next/no-img-element": "off", "@next/next/no-html-link-for-pages": "off", "@next/next/next-script-for-ga": "off", "@next/next/no-sync-scripts": "off", Next.js の機能を活用するためのルールをなぜオフにしているか疑問に思われるかもしれませんので理由を説明します。 no-img-element は、Next.js が提供する next/image を使用せずに、img 要素を使用している場合に警告を行うルールです。プロダクトでは、SSR ではなく、SSG を使用していて next/image は未使用なのでオフにしています。 no-html-link-for-pages は、Next.js が提供する next/link を使用せずに anchor 要素のみを使ってリンクを実装している場合に警告を行うルールです。next/link を使用することで、CSR でページ遷移を実現することが可能になりますが、mediba で利用している Google Analytics 向けの実装ではページ遷移時の集計に懸念があることが分かっています。そういった理由から意図的に next/link を使用しないようにしているため、ルールをオフにしています。 next-script-for-ga と no-sync-scripts は、 next/script の仕様をきちんと調査できていなかったので暫定的に script 要素を使用していましたが今後対応するかもしれません。 Stylelint Stylelint です。プロダクトでは、styled-components のような JS in CSS ではなく、 .scss ファイルに定義した CSS を CSS Modules から利用しているためプロパティの並び順の auto fix 等、Stylelint の機能を全て活用することができています。 プロパティの並び順定義は、 stylelint-config-recess-order を使用しています。定義されているプロパティの数とメンテナンスの頻度の面で、他のライブラリや自身で作成するよりも良さそうだったため選定しました。 また、 Prettier を使用しているので stylelint-config-prettier で競合を防いでいます。 markuplint markuplint です。HTML Living Standard の仕様に準拠した HTML になっているかを検証できます。React (JSX) にも対応しています。 ルールを独自に追加しています。 { "nodeRules": [ { "selector": "meta[property]", "rules": { "invalid-attr": { "option": { "attrs": { "property": { "type": "String" }, "content": { "type": "String" } } } } } } ] } meta 要素の属性についてです。SNS でページを共有された際の挙動を定義するため、このような meta 要素を実装することが多いと思います。 <meta content="ページタイトル"> この実装は markuplint が 2 つ警告を出します。まず、HTML Living Standard の meta 要素の仕様には、property 属性が定義されていないため警告を出します。 また、name 属性が定義されていない場合に、content 属性を使用することができないため、こちらについても警告を出します。 では一体この属性は何なんだ、という話になりますが、property 属性については RDFa で定義されており、 OGP に従ってこの meta 要素を SNS 側が解釈します。したがってここでは例外として取り扱うようにルールを変更しています。 なお、React 独自の属性( key や dangerouslySetInnerHTML ) は @markuplint/react-spec に含まれているため独自にルールを追加する必要はありません。便利です。 まとめ 直近の Next.js 採用プロダクトを例に、使用している Lint ツールと、その設定についてご紹介しました。これらの Lint ツールは全て GitHub Actions で commit push をトリガーにして実行していますので、ルールに違反しているコードをコミットするとすぐに気づくことができ、Lint ツール運用の形骸化を防ぐことができています。 Lint ツールの導入によって、コードベースをクリーンに保つことはもちろんですが、標準仕様やベストプラクティスを知る良いきっかけにもなっています。 Lint ツール導入時の参考になれば幸いです。最後までお読み頂きありがとうございました。
アバター
はじめまして、mediba 新卒2年目 データアナリストの左海です。 mediba Adventカレンダー 10日目ということで、私からはGTMを使用して自社サイトにMicrosoft Clarity(ヒートマップツール)を設定したお話を書いていきます。 Microsoft Clarityでできること Clarityとは、Microsoft社から2020年10月にリリースされた無料のヒートマップツールです。 Clarityでできることは、主に以下の3つです。 サイト訪問者数、離脱者数、任意のページにおけるスクロールされた割合など基本数値を把握できる 1ユーザーにフォーカスして、サイト上でどんな行動をしたのかセッションレコーディング(録画)できる どこをクリックして、どこまでスクロールしたのかヒートマップで視覚的に可視化できる 導入編 設定手順 GTMを使用する場合は、以下の順に設定していきます。 Microsoft Clarity アカウントを作成する GTMを使用し、Clarity tracking codeを配信する GTMのプレビューで、設定したタグが配信されているか確認後、公開する Clarityの管理画面で、正しく数値が計測されているか確認する Microsoft Clarityのアカウントを作成する Microsoft Clarity にアクセスし、赤枠箇所をクリックしてアカウントを作成します。その後、必要事項を記入します。 GTMを使用し、Clarity tracking codeを配信する Clarity管理画面 > Settings > SetupからClarity tracking codeをコピーし、GTMのタグ設定画面のHTMLに貼り付けます。 GTM側で、タグの作成とトリガーの設定を行います。 タグの作成は、GTM 管理画面 > タグ > 新規 から作成をします。タグとトリガーの設定は以下の通りです。 タグの種類:カスタムHTML トリガーの設定:All Pages 以上で、タグの作成とトリガーの設定は完了です。 GTMのプレビューで、作成したタグが問題なく配信されているか確認する GTMのプレビュー画面のTags FiredにMicrosoft Clarityタグが確認できました。問題なく、タグが発火されているようです。公開ボタンをクリックします。 GTMの設定は以上になります。 Clarityの管理画面で、正しく数値が計測されているか確認する 最後に、Clarityの管理画面で、問題なく数値が計測されているか確認します。タグ公開から2時間ほどで、Clarityのダッシュボード画面に数値が反映されました。 ヒートマップのデータも反映され、活用できるようになっています。 GTM経由で、自社サイトにClarityの設定が完了しました。 Session Recordingsについて 以下の通り、Session Recordingsも活用できるようになっています。ユーザーのカーソルの動きが鮮明に閲覧できます。 活用方法の検討 Clarityはどういった場面で活用することができるのかを検討しました。ここでは、仮設として、任意のページの次のページへの遷移率が減少傾向であるとします。そうすると、以下のような課題発見から意思決定までの流れがが考えられます。 課題発見 まずは、課題を設定します。ここでは、仮の課題として、「任意のページの次のページへの遷移率が減少傾向である」とします。 現状把握・要因調査 GAの定量データを使用し、課題の要因を突き止めようとします。しかし、定量データだけでは、特に要因が分かりません。 次に、Clarityを使用して、任意のページで何が起こっているのかセッションレコーディングとヒートマップで要因を調査します。例えば非活性箇所がクリックされているような場合には「誤クリックで離脱している」と言えるかもしれません。 方策 方策として以下の案が浮かぶのではないでしょうか。 クリックできない箇所をクリックできるように活性させる 以上のように、GAの定量データでは普段発見できないようなユーザーの行動でもClarityを使用すれば、簡単に把握することができるでしょう。その結果、意思決定につながると考えています。 おわりに GTMを使用することで、エンジニアに依頼せずとも、Clarityを設定できるのは便利かと思います。 Clarityは無料でかつ実装に手間がかかりません。そして、ヒートマップやセッションレコーディング機能など多様な機能も魅力的です。ぜひご自身のサイトにClarityを導入してみてはいかがでしょうか。 どなたかのご参考になれば、幸いです。
アバター
こんにちは。エンジニアの中畑( @yn2011 )です。千葉県は最高です。 今回は NewRelic ブラウザモニタリング を使用して JavaScript のエラー収集を行おうとした際に、NewRelic のソースマップアップロード機能の仕様で色々とハマってしまったというお話をします。 ソースマップアップロード API について NewRelic ブラウザモニタリングでは、ソースマップというファイルを利用して発生した JavaScript エラーのスタックトレースを取得することができます。 ソースマップは、以下のような内容のファイルです。 { "version": 3, "file": "static/chunks/254-be419541614271942b8c.js", "mappings": "gFAAoEA,... } NewRelic ブラウザモニタリングにはソースマップのバージョン管理機能があり、ソースマップにユニークな識別子をアップロード時に付与することができます。提供されているソースマップのアップロード API を利用してアップロード時に識別子の付与ができます。 publishSourcemap({ sourcemapPath: './dist/bundle.js.map', javascriptUrl: 'https://example.com/assets/bundle.js', ... releaseName: 'OPTIONAL RELEASE NAME', // 追加 releaseId: 'OPTIONAL RELEASE ID', // 追加 }, function (err) { console.log(err || 'Source map upload done')}) releaseName と releaseId を指定できることに違和感を持った方もいるかもしれません。これは次項の伏線です。 ハマリポイント releaseId と releaseName は両方必須 早速伏線を回収します。NewRelic ブラウザモニタリングでバージョン管理を行う場合は、 releaseName と releaseId の両方が必須になります。 ドキュメントに記載はないように認識していますが、 @newrelic/publish-sourcemap の実装を確認すると、しっかり条件式に含まれていました。 if (releaseName && releaseId) { console.log('Using release name:', releaseName, 'and release id:', releaseId) request = request.field('releaseName', releaseName) request = request.field('releaseId', releaseId) } コミットハッシュを活用する場合など、どちらか 1 つの項目を利用できれば十分なことが多いと思います。なぜこのような仕様になっているのかは分かりませんでした。 コンフリクトエラー ソースマップのバージョン管理を行わない場合は、 releaseName と releaseId を指定する必要はありません。しかし、1 度これらの項目を空でアップロードしてしまうと、次にアップロードする際に releaseId と releaseName を指定してアップロードしてもコンフリクトエラーというレスポンスが返り、アップロードが失敗します。 コンフリクトエラーは、NewRelic に同一のソースマップをアップロードした場合に発生する仕様となっています。 releaseName と releaseId が異なる場合は別のソースマップとして解釈されるためアップロードが可能です。 バージョン管理を行う場合は、1度、 releaseName と releaseId が空のソースマップを削除し、再度アップロードする必要があります。 ブラウザ側で addRelease を呼び出す必要がある ソースマップのバージョン管理を行う場合、ブラウザ側で browser agent API から次のメソッドを呼び出す必要があります。 newrelic.addRelease(string $release_name, string $release_id) このメソッドを呼び出さないと、発生した JavaScript エラーを正しいバージョンのソースマップにマッピングすることができません。メソッドを呼び出さずに JavaScript エラーを発生させた場合、NewRelic の画面上では releaseName と releaseId は空になります。 addRelease の呼び出しの実装としては、スニペットの末尾への追加が可能です。 NewRelic ブラウザモニタリングを利用する場合は、New Relic から提供されるスニペットをブラウザで実行する必要があります。このスニペットの実行後に、browser agent API が利用可能になるため、以下のように末尾に呼び出しを追加します。 // 省略... applicationID:"${config.applicationID}",sa:1}; newrelic.addRelease("foo", "bar"); // 追加 addRelease の呼び出しは、類似サービスである Sentry では不要であったため、検証時に気づかずハマってしまいました。 まとめ NewRelic ブラウザモニタリングのソースマップに関するハマりポイントを 3 つ書きました。 最終的に、プロダクトではソースマップバージョン管理機能の導入は見送りましたが、今後必要に応じて対応を検討する予定です。 最後までお読み頂きありがとうございました。
アバター
はじめに こんにちは、SRE Unitの北浦( @kitta0108 )です。 当ブログは、執筆活動をモブでやったらどうかという新しい試みをしておりまして、 テクノロジーセンター6G Managerの下地さん( @primunu )、 SRE Unitの板谷さん( @mary_tuba )、 そして北浦の3人でお送りしております。 さて、皆さんはアプリケーションの実行基盤としてのコンテナイメージを選定するとき、どのような関心軸を持って望んでますか? 今回はDistrolessイメージについて、何の嬉しみがあるのか、どのような優位性があるのかなどを深掘りっていきたいと思います。 皆さんの引き出しの一つとして、知見に加えていただけるようなことがあったならとても嬉しく思います! 対象者 イメージを選定する人 = DockerFileを書く人 イメージの脆弱性対応を強めたい人 コンテナサイズの軽量化を追い求めたい人 概要 Distrolessとは、Googleが公開しているアプリケーションとそのランタイム依存のみが含まれたDebian10(buster)に基づいて作成されたコンテナイメージです。 https://github.com/GoogleContainerTools/distroless 必要な依存のみが含まれるとは? Distrolessにはアプリケーションの実行に必要な依存しか含まれていません。 なので、Shellはおろか、aptやcdといった機能も有していません。 だが、それが良い。それで良いのです。以下をご覧ください。 この図はAWSのECRにそれぞれ以下のプレーンイメージをプッシュした結果になります。 (Goのアプリケーションを動かすという前提でイメージを選んでます。) alpine:3.13 golang:1.16.5-buster gcr.io/distroless/static-debian10 どうでしょうか。この時点で嬉しみポイントが2つ見えていませんか? 嬉しみポイントその1 軽量であるということ Debianの318MBというのは置いておいてw Alpineの2.81MBと比較してもDistrolessの0.66MBはめちゃくちゃ軽いです。 コンテナを扱う上では、インフラの振る舞いとして、スケーリング性能や可搬性の向上を期待したいですから、この軽量ポイントは嬉しいですよね。 嬉しみポイントその2 セキュアであるということ 実はこっそりとECRにImage Scanning機能をONにしておきました。 ここでもDistrolessイメージは堂々のNoneの文字が…! アプリケーションを実行する上で余分な依存が含まれないということは、それだけ脆弱性が露呈する可能性が低くなることにも繋がります。 コンテナイメージのレイヤーでここを抑えられるのは嬉しいポイントですよね。 イメージのバージョン上げ作業の頻度も少なくなりそうです。 まとめ 今回はアプリケーションコンテナのイメージとしてDistrolessを紹介してみましたがいかがでしたでしょうか。 利用している言語がDistrolessに対応しているのであれば、積極的に利用を検討してもいいと個人的には思います。 ただし、Distrolessを利用したイメージの作成にはShellが含まれていないという点でちょっとしたコツが必要になります。 時間があったら、イメージの作成方法などを具体的にしたものを書いてみようかな…!!
アバター
はじめまして、2021年度新卒の木村です。 フロントエンドエンジニアとして テクノロジーセンター5G に所属しています。 今回は、Nuxt.js ならびに TypeScript を利用して、簡単な抽選ツールを作成してみた内容についてご紹介します。 Nuxt.js と TypeScript Nuxt.js は、Vue.js ベースの JavaScript のフレームワークです。Webページ構築に有用な UI 以外の機能( Ajax やサーバーサイドレンダリングなど)をまとめて利用できる環境を提供してくれます。 Nuxt.js 公式サイト TypeScript は、省略可能な静的型付けとクラスベースオブジェクト指向を加えた JavaScript のスーパーセット(上位互換)です。一言で言うと「型定義できる JavaScript 」。 TypeScript 公式サイト なぜ抽選ツールを作ったのか 主な理由は、以下の2点です。 私の所属するチームで扱っている Nuxt.js および Typescript の概要を掴むため チーム内でMTGのファシリテータが偏ってしまう課題を解決するため 完成物 さっそくですが、完成した物がこちら。 ランダムにユーザーを抽選できるシンプルなツールです。 デザインは近年流行ったニューモーフィズムを取り入れてみました。 少ない配色でも凹凸によって奥行きができるので、シンプルなツールでも見栄えが良くなります。 使った技術 Nuxt.js 2.15.7 TypeScript 4.2.4 Vue.js 2.6.14 Vuex 3.6.2 firebase 8.7.1 tailwindcss 2.2.4 sass 1.35.1 環境 M1 Mac VSCode yarn 機能 今回の抽選ツールの機能は以下とおりです。 ユーザーを登録・削除する ユーザー情報をDBで保持する 登録したユーザーからランダムに抽選する 完了した人・休みの人にチェックし、抽選対象から外す 抽選終了後、選ばれた人は自動で完了チェックされる 全員チェックしたら一括リセットできる 作成手順 全体的な流れは以下のとおりです。 環境構築 各機能作成 Vuex導入 firebase導入 1. 環境構築 create-nuxt-appを利用して環境を構築しました。 create-nuxt-app 公式サイト と Nuxt.js 入門の記事 を参考にしました。 2. 各機能作成 前述した各種機能を実装していきます。 欠席者の対応 完了した人だけでなく、休みの人など抽選対象から除外したい場合を想定して、完了チェックとは別に除外したいユーザーを指定できるようフラグを持たせています。 コンポーネントについて 個人的に苦労したのはコンポーネント周りです。 まず、どのくらいの単位で区切れば良いのかがわかリませんでした。 そこで今回は Atomic Design を参考に切り出しました。 また、他の箇所でも使えるように汎用的に作ることです。 クリックなどのイベントは全て親へ渡し、スタイルや表示の変更は親から値を渡すようにすることで、atoms単位のコンポーネントがどこでも使えるよう心がけました。 例えばボタンコンポーネントは、予めいくつかのスタイルを定義しておき、親コンポーネントで使うときに下記のように props で必要なスタイルを渡すとともに、$emit でクリックイベントを親に渡しています。 //Button.vue <div> <button :class="[ colorStyle[buttonColor], sizeStyle[buttonSize], fontStyle[buttonFont], ]" @click="$emit('button-click')" > <slot></slot> </button> </div> //Input.vue <Button button-color="gray" button-size="short" button-font="small"> ADD </Button> そしてコンポーネント間のデータの受け渡しです。 例えばユーザー情報は、子から emit で親コンポーネントに値を引き渡し、さらに別の子コンポーネントに props で渡して表示しています。 今回はシンプルなツールなのでコンポーネントも少ないですが、こうした受け渡しが重なってくるとコードが煩雑になりメンテナンスがしづらくなります。そこで、Vuex を導入し状態管理をすることにしました。 3. Vuex 導入 Vuex のストアでユーザー情報の状態を管理します。 今回はDBも使う予定なので、actions でDBに接続した後、値を state に反映させています。 Nuxt.js は store ディレクトリにファイルを作成するだけでストアを有効化してくれるので便利ですね。今回は管理するのがユーザー情報だけなので、index.ts を作らず users.ts を直下に作りました。 Vuex ストアでデータを一元管理できるので、本当にわかりやすくなりました。コンポーネントを超えて共有される情報を管理するには、とても強力なツールだと思います。 一方で、Vuex を使用する際には、注意点もあります。 例えば、小さいレベルのコンポーネントからは Vuex を使用しない方が良いでしょう。 コンポーネントの使い回しが難しくなるのと、1画面の中で複数の箇所から Vuex のストアへの参照と変更が入り組んだ場合、処理が追いにくくなるためです。 参考: Vuexはなるべく避ける     Vuexで何をするか、何をしないか Vuex を利用する際は、 Atom や Molecule レベルのコンポーネントの中では使わない 、 state を直接参照しない 、などのルールを設けて運用したいと思います。 4. firebase 導入 DBとデプロイは firebase を利用します。 firebase は以前にも使ったことがありましたが、やはり firestore を使えば面倒な手間をかけずにDBが実装できますね。万歳。 デプロイも firebase の Hosting を使って行いました。 さらに、今回は社内向けツールなので、デプロイにあたり外部からのアクセスを制限するため、 Authentication を使用してログイン認証を追加します。管理者アカウントとしてユーザーを一人だけ登録し、簡単なPWを打てば誰でも使えるようにしました。 ログインしていない状態では認証後のページへ飛べないようにするため、強制的にindexへリダイレクトするmiddlewareも追加しています。 const auth = firebase.auth() const middleware: Middleware = ({ route, redirect }) => { auth.onAuthStateChanged((user) => { if (!user && route.name !== 'index') redirect('/') }) } また、Authentication の認証状態は、デフォルトではユーザーがブラウザを閉じた後でも永続的に維持されるようになっています。 今回は管理者アカウント一つだけの使用になるため、ログアウトせずにウィンドウを閉じてしまうと、他の人がアクセスした時でも前のセッションが続くことになります。 そこで、 firebase.auth().setPersistence メソッドで永続性タイプを SESSION に変更することで、ウィンドウやタブを閉じるたびにログイン状態がクリアされるようにしました。 ちなみに、Nuxt.js には nuxt/firebase というモジュールがあります。firebase をより簡単に利用できるので、興味があればご覧ください。 詰まったところ sass の導入 sass の導入で沼りました。どうやら node のバージョン16 かつ M1チップ だと node-sass が動かないようです(2021/09 執筆時点)。node のバージョンを下げることで解決しました。 型定義 Vuex での型定義、props での型定義、firebase で扱う日時の型定義… 型推論が効かず、気づけばanyになっている型に苦しめられました。 特に Vuex は、ストアへアクセスするための便利な型がなく、今回使用した this.$store は Nuxt.js で型指定されていません。 自前の型を使うか、 nuxt-typed-vuex を利用することで改善するしかないようです。 また、 TypeScript と Vuex の相性は良くなく、コンポーネントから store を呼び出したときに型安全が守られない、インテリセンスが効かないといった問題があります。 次に TypeScript で Vuex を扱う際は、Nuxt.js 公式で推奨されている vuex-module-decorators を使用したいと思います。 振り返り さて、ここまで長々と書いてきましたが、今回のアウトプットを通して Nuxt.js と TypeScript の概要は大体掴めたかな、という感じです。 結論、Nuxt.js 便利! 日本語ドキュメントが充実していてほぼ誰でも簡単に始めることができる ルーティングを自分で作成する必要がない SSRなどモードを選べるて柔軟なサイト設計ができる …など、Nuxt.js を使うことで直感的にDOMの内容を操作でき、より簡単に抽選ツールを作ることができました。 また、TypeScript についても、型定義のおかげで、コンポーネント間のデータの受け渡しでもどういう型か判断でき、コードが見やすくなります。 さらに、力補完のおかげでコードを書く時間を短縮できる、エラーチェックを機械に任せることができる、など多くのメリットがありました。 一方で、Vuexとの連携は公式で推奨されているパッケージを使うなど、改善の余地があります。 最後に 以上、Nuxt.js を利用して抽選ツールを作成してみた内容の紹介でした。 最後まで読んでいただき、ありがとうございました。 どなたかの参考になれば幸いです。
アバター
こんにちは。エンジニアの中畑( @yn2011 )です。8 月から千葉県民になります。 先日、新チーム発足にあたり、チームビルディングのワークショップとして有名なインセプションデッキの作成にチャレンジしました。その際に改めてインセプションデッキについて勉強し直したのですが、 実際にチームがどういった経緯で、どのようにインセプションデッキを作成したか 、という具体的な事例の公開が少ないなと感じました。 そこで今回は、私達が ① なぜインセプションデッキを作成し、② どのようにワークショップを進めたのか、③ そして何が得られたのか について書きたいと思います。 なぜインセプションデッキを作成したか 新チーム発足 新プロダクトの開発が決まり、ビジネスチームが先行して企画と要求の洗い出しを行っていました。ある程度の整理が完了したため、デザイナーとエンジニアが合流し、本格的に開発を始めていくぞ、という経緯で新しいチームが形成されました。 したがって、ここで言う「チーム」とは、特定の職種のみで構成されたチームではなく、ビジネスのメンバーやデザイナー等プロダクト開発に必要な全ての職種を含むチームです。 ちなみに、エンジニア同士、ビジネスのメンバー同士はこれまでも同じチームで仕事をしていましたが、エンジニアとビジネスのメンバー同士はほぼ一緒に仕事をしたことがありませんでした。 何が課題だったか デザイナーとエンジニアは途中からビジネスチームに合流したわけなので、これから開発するプロダクトの目的やプロジェクトとして何を重要視しているのか等をしっかり理解できていませんでした。したがって、 プロダクトの要求事項が書かれたドキュメントを読んでも、それが適切なのか、なぜ必要なのかという部分の理解や納得度が低い 状態でした。 また、プロダクトの開発を始めるためには、要求を要件に落とし込んだ上で初回にリリースをする範囲(MVP) を決める必要もありました。 どうするか この 2 つの課題について、チームメンバーと相談し、インセプションデッキを作成することと、ユーザーストーリーマッピングを行うことで課題を解決することになりました。(なお、この記事ではインセプションデッキについてのみ取り扱います) しかし、やろうというのは簡単ですが、実際に私がインセプションデッキ作成のワークショップを企画するにあたり、いくつもの課題に直面することとなりました。とても現場感溢れるものです。 例えば時間の面では以下の課題がありました。 参加して欲しい関係者は 15 人以上で、全員が参加可能な日程の調整が難しい 既に多くの予定が組まれていて、1 時間以上の時間の確保が難しい プロダクトのリリース日は大まかに決まっていたことから、できるだけ早く完了させなければいけない これらの制約から、インセプションデッキは 10 個の項目で構成されていますが、重要だと思ういくつかの項目を選択し、それ以外の項目は検討対象から外しました。そして、ワークショップは 1 回 1 時間で、複数回に分割することとして、何とか全員参加のワークショップ実施の見通しを立てることができました。ちなみに、参加可能なメンバーだけで実施して後で結果を共有する、といったやり方はせず極力全員参加に拘りました。インセプションデッキは結果と同じぐらい構築過程の議論が大事だと考えていたためです。 また、内容の面では以下の課題がありました。 インセプションデッキのワークショップを実際にファシリテートした経験のあるメンバーがいない そもそもインセプションデッキを知らないメンバーも多い これらについては、私が書籍や Web から情報を得つつ創意工夫して実施していくことになりました。 どのようにインセプションデッキを作成したか 検討の結果、ワークショップのアジェンダは以下になりました。かなり厳選し、最低限必要だと思う 3 つの項目のみを選びました。 自己紹介 インセプションデッキの説明 我々はなぜここにいるのか エレベーターピッチ トレードオフスライダー 分割してワークショップを行うことにしていたので、初回は「我々はなぜここにいるのか」までで、次回に残りのアジェンダを消化しました。 では、それぞれどのように進めていったのか、当日の様子と合わせてお伝えしていきます。(「自己紹介」、「インセプションデッキの説明」については一般的なものなので省略します) なお、付箋を使いたいことと、原則在宅勤務のため Miro を使用してオンラインで実施しました。 我々はなぜここにいるのか 「我々はなぜここにいるのか」は、プロジェクトの目的について認識を合わせるためのワークショップです。 最初に個人ワークの時間を取って、1 人ずつ自分の考えを付箋に書いてもらいました。その後に共有の時間を取り、ファシリテーターが補足や質問と、似ている意見のグルーピングをしていきました。 こちらが実際のワークショップの結果です。 グルーピングは難しいですが、大きくは定性的なものと定量的なもので 2 つに分けました。上の図では 3 つに分かれていますが、定性のものを更に 2 つに分割しています。 例えば、定性的な目的は「ユーザに〜という価値を提供する」等で、定量的な目的は「利用率 x % 向上」等です。 基本的にどの意見も間違っているということはないのですが、どうしても定性的なものほど達成できたかどうかを判断しにくく解釈にもブレがあります。定量的で、 達成できなければプロジェクトが解散になるものはどれか と問い直すことで、明確な目的を探していきました。ここは前提となっているインプットの差もあるので、ビジネスチームの方々にリードして決めて頂きました。ただし、今回はこの時点では具体的な数値までは盛り込めませんでした。ひとまずはプロジェクトが向かっていく方向の認識を合わせることが大事なので、計測可能な指標であるならば良いのかなと思います。 エレベーターピッチ エレベーターピッチは、事前に用意されたテンプレートを埋めることで、プロダクトの骨格を明らかにするワークショップです。 ちなみに、通常 エレベーターピッチは 1 プロダクトに対して作成しますが、今回は関連するプロダクトが複数存在していたので同じ時間に複数のエレベーターピッチを作成しました。複数プロダクトを同時に開発する必要があったのです(現実は教科書通りにはいかないということが実感できます) エレベーターピッチの進め方としては、テンプレートの項目を 1 つずつ埋めていく方法を取りました。1 つの項目毎に、個人ワーク → 共有 → グルーピング → 最終的な答えという手順を踏みました。(初めは、一気に全ての項目を埋めてもらい 1 人ずつ共有してもらったのですが、付箋の整理がしにくいのと、最初の項目の認識が合っていないと後半の内容も異なってくるのでやり方を変えました) こちらが実際のワークショップの結果です。右の付箋が個人ワークで書いて頂いたもので、左の赤い付箋が整理した結果です。 エレベーターピッチで意見が分かれやすかったのは「ユーザは誰なのか」と「差別化要素は何なのか」でした。定義が難しいからなのか、 経験上この部分が曖昧なままプロダクトの開発が進行することも多いので最初に確認できる のはこのワークショップの利点だと感じました。 トレードオフスライダー トレードオフスライダーはプロジェクト(プロダクト)で何を大切にするかの優先順位を決めるワークショップです。 今回はプロダクトというよりは初回のリリースまでに時間を区切ったプロジェクトとしての優先順位として検討しました。 けっこう悩みましたが、進め方は以下にしました。(付箋をドットシールとして利用するイメージです) 個人ではなく職種ごとに色分けした付箋を利用する(エンジニアなら黄色等) 個人毎に自分の職種に該当する付箋を利用して、1 番優先順位が高いと思うものに投票 次に 1 番優先順位が低いと思うものに投票 投票結果を元にプロジェクトとしての合意を形成する こちらが実際のワークショップの結果です。今回は都合により予算は検討から外しました(予算の増減が実質的に不可) スライダーの各項目は、スコープ、時間、品質等の言葉が使われますが、必要に応じて言い換えを行うことによって参加者の認識齟齬を防いでいます。 個別の要素の評価値が 2 なのか 3 なのかといった部分はあまり議論せず、重要なものについてざっくりと認識を合わせました。優先順位が中間のものについては、正直あまりプロジェクトの進行中に意識されることは少ないと思ったためです。また、個人毎ではなく、職種毎にしたのは参加人数が多いためです。職種毎の傾向を大まかに把握した方が効率良く議論できると考えました。 トレードオフスライダーによって、 リリースの期日に対するステークホルダーの温度感等、プロジェクトの制約について全員の認識を揃えることができる のは利点だと感じました。また、プロジェクト中盤での機能追加や方針変更に対して、トレードオフスライダーの優先順位に従って意思決定や議論を行える点も魅力的です。 インセプションデッキをやってみてどうだったか インセプションデッキのワークショップの終了後に、参加者の方々から感想を頂きました。 インセプションデッキの作成がどういったものか知ることができた ワークショップ形式で議論ができたことによって、プロダクトについて理解が深まったり認識齟齬があることに気づけた といったポジティブな感想を頂きました。インセプションデッキによって、 当初の課題であった「プロジェクトの目的や何を重要視しているのかの理解不足」を補うことに貢献できたと言えそうです 。 また、プロダクトについてもエレベーターピッチによって複数観点から議論することで、以前より理解が深まりました。 テンプレートが決まっていることによって、観点漏れが少ないのと、質問しにくいことも全て議題に乗せることができる のも良かったです。次の工程であるユーザーストーリーマッピングを行うための最低限の共通認識の形成にも役立ちました。 一方で 発言に偏りがある ツールの使い方には少し戸惑った 最終的なまとめをもう少ししっかりやりたい といった意見も頂けました。 発言の偏りについては、私のファシリテーション能力が足りていないのと、ワークショップの時間にゆとりを持たせることができなかったことも一因だと思います。時間に追われていて、進行を優先しすぎていました。 ファシリテーションの際には、 インセプションデッキを完成させることだけが目的ではなく、全員でフラットな議論をすること・共通の認識を作ることも意識することが大事 という学びがありました。 また、最初に Miro の使い方に慣れる時間や、最後にインセプションデッキ全体を振り返る時間も考慮したアジェンダを作成するとより良いものになりそうです。 まとめ 新チームの発足にあたり、インセプションデッキの作成を行うに至った経緯と、ワークショップの実施方法、その結果得られた成果や知見について書きました。 新しいプロジェクトを始める際や既存のプロジェクトの方向性が曖昧になってしまっていると感じられている方は、インセプションデッキの作成に挑戦してみると何らかの手がかりになるかもしれません。 最後までお読み頂きありがとうございました。
アバター
はじめに はじめまして、2021年4月新卒入社の土屋( @hrktcy )です。バックエンドエンジニアとして6月にテクノロジーセンター Eng6G に所属しました。 背景 Eng6G では スマプレチャレンジ と呼ばれるサービスを、サーバレスなシステムを構築して開発・保守・運用しています。またバックエンドには Go を採用しており、比較的モダンな技術スタックによるプロダクト開発が行われています。 [参考]mediba に入社したらアジャイル志向のチームで最高だった件〜モブワーク無双でテレワークを超越します〜 Go は基本的に暗黙的型変換が認められないため、開発関連のタスクを全てモブで行う弊チームにおいて、ナビゲーターがレビューしやすいというメリットがあります。またモダンな技術を扱うことで、エンジニアとしての市場価値も高まると考えています。 そこで Go 未経験の私がバックエンドエンジニアとしてジョインするにあたり、学習とアウトプットを兼ねて、本稿では  Go でスクレイピングした  mediba+  の記事を Slack に投稿するバッチを作った話 を備忘録として残したいと思います。 開発環境 M1 Mac Docker 20.10.7 Golang 1.14 Terraform 1.0.1 AWS provider 3.49.0 事前準備 Slack API トークンを生成しておく Slack に投稿する App を作成し、予め API トークンを取得しておきます。   Slack API  へアクセス 「  Create an app  」をクリック 「  Create New App  」をクリック 「 名前 」と「 ワークスペース 」を指定する アプリを作成した後に表示されたページで「  Permissions  」をクリック 「  Scopes  」の項目で  chat:write  権限を設定 「  Install App to Workspace  」をクリックし API トークンを取得する バッチの仕様を固める Go でスクレイピングした mediba+ の記事を Slack に投稿するにあたり、まずはバッチの仕様を固めることにしました。 スクレイピング方法 スクレイピング系のパッケージ(  goquery  , etc. )を用いることもできますが、今回は mediba+ の RSS フィードをパースすることでスクレイピングを行います。当たり前ですがスクレイピング先の情報が更新される度にフィードの内容は変更されます。バッチ実行時点の日時と、スクレイピング先の記事日時を参照する必要があります。そこで今回は、バッチ実行時点の日時に更新された記事のみを Slack に投稿するようにします。 同一記事を重複して投稿しない バッチを実行するたびに同一記事が投稿されるのは避けたいです。これについてはテーブルにスクレイピングした記事情報を格納しておき、 Slack に投稿する文章を生成する前段階で、記事が投稿されたものなのかを判定する必要があります。 バッチの運用方法 バッチプログラムを任意の日時に実行されるような環境を整えたいです。これについては ECS Fargate + CloudWatch Events で任意の日時にバッチが実行されるような環境を構築することで解決します。本稿では以下のシステムを構築しました。本システムは Terraform によって一元管理しています。 ソースコード 1. DB へ接続する Go の OR マッパーとして提供されている  gorm  を用いて、 env ファイルに記載した DB の情報から接続を行います。 DBTYPE := "mysql" USER := os.Getenv("USER") // ユーザ名 PASS := os.Getenv("PASS") // パスワード ENDPOINT := os.Getenv("ENDPOINT") //エンドポイント DBNAME := os.Getenv("DBNAME") //データベース名 CONNECT := USER+":"+PASS+"@tcp("+ENDPOINT+":3306)/"+DBNAME+"?charset=utf8&parseTime=True&loc=Local" db, err := gorm.Open(DBTYPE, CONNECT) if err != nil { fmt.Println("DB接続失敗") panic(err) } fmt.Println("DB接続成功") 今回接続先のテーブル情報は下図の通りです。 記事タイトル(title)と URL (link)、投稿済み判定(status)の3つのカラムを定義してあります。 2. mediba+ の記事をスクレイピングする RSS フィードからバッチ実行時の日時に投稿された記事をスクレイピングしテーブルへ insert します。フィードの取得には  gofeed  を用います。 # mediba+をスクレイピングする fp := gofeed.NewParser() feed, _ := fp.ParseURL("https://koho.mediba.jp/feed/") 変数 feed にはパースされたフィードが格納されており、 feed.Items で投稿記事全てを取得することができます。今回はバッチ実行時点の日時に更新された記事のみを取得するために、要素1つ1つを for 文で回し、各記事の投稿日時とバッチ実行時の日時を比較します。 比較するにあたり、パースされた PubDate(UTC) は string 型なので time 型と比較することができず、スクレイピング先によってはフォーマットも違う可能性があるため合わせてあげる必要があります。また PubDate を JST にする必要もあります。 こちらについては RFC1123Z フォーマットで統一しました。まず PubDate を time.Parse で変換します。さらにそれを RFC1123Z のフォーマットに変換し、タイムゾーンを JST にすることで、投稿日時とバッチ実行時の日時を比較します。公式フォーマットは こちら から参照できます。 // RSS構造体 type Article struct { link string published string } // RSSのPubDateを文字列→日時の型に変換、それをRFC1123ZのFormatに変換し、タイムゾーンをJSTにする for _, item := range feed.Items { m := make(map[string]Article) if item == nil { break } var timeParse = time.Time{} timeParse, _ = time.Parse(time.RFC1123Z, item.Published) pubDateJST := timeParse.In(time.FixedZone("Asia/Tokyo", 9*60*60)).Format(time.RFC1123Z) // 以下Insert処理 ... } 投稿日時とバッチ実行時の日時が同じ時、構造体 m に記事の情報を格納し、 gorm を用いてテーブルに insert すれば OK です。このとき、 status カラムにはデフォルト値として false を入れておきます。 3. Slack に投稿する Slack に投稿する処理は以下の通りです。 // envファイルに記載したAPIトークンからクライアントを生成する tkn := os.Getenv("TOKEN") c := slack.New(tkn) _, _, err := c.PostMessage("#チャンネル名", slack.MsgOptionText( " テキスト " , true)) if err != nil { panic(err) } else { fmt.Println("投稿完了") } テーブルに格納されている、投稿日時がバッチ実行時の日時と同じ且つ status が false の記事のタイトルとリンクを取得し、 slack.MsgOptionText の第一引数に代入します。記事1つ1つを連投すると Slack の通知が多くなり煩わしく感じるので、 string 配列の中に取得した記事とタイトルを append していき、 strings パッケージの strings.Join を用いて、各要素を結合してから投稿処理を行うようにしました。 投稿処理が完了したら gorm を用いて status カラムの値を true に update することで、バッチを実行し直しても同じ記事を再度 Slack に投稿しないようにします。また全てのクエリ操作はトランザクション内で行うようにし、返ってきた err が nil かどうかを見てロールバック、コミットを判断するようにします。 実行結果 無事バッチが動作することを確認できました。 バッチの実行時間は大体1秒でした。 つまづいたところ Dockerfile の設計 バッチプログラムでは  Go Modules  という外部パッケージ管理システムを用いています( Go Modules を使用する場合、 Go のバージョンは1.11以上である必要があります)が、 Docker イメージをビルドする際に毎回 Go Modules のダウンロードが走るため、バッチの実行時間が長くなってしまうという問題がありました。また M1 mac でビルドした Go イメージを ECR に Push すると、自動的に Go イメージが linux/arm 版になってしまうようでした(記事執筆時点)。こちらについては、  Docker Buildx  で linux/amd64 でビルドし ECR に Push することで対応できましたが、実行時間問題は解決していません。 そこで  Multistage Build  を採用することにしました。開発環境用のイメージの中でビルドを行い、生成されたシングルバイナリを本番環境用の Alpine イメージに移すことで、大幅なメモリ削減ができるだけでなく実行時間も短縮することが可能です。 FROM amd64/golang:1.15-alpine AS builder WORKDIR /go/src/tsuchiya COPY . /go/src/tsuchiya ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux go build -o api main.go FROM alpine:latest WORKDIR /root/ COPY --from=builder /go/src/tsuchiya/api . CMD ["./api"] コンソールから ECR のプライベートリポジトリを覗いてみます。 イメージサイズを確認すると100 MB 以上削減されていることが分かりました。 おわりに スクレイピングした mediba+ の最新記事を Slack に投稿するバッチを作りました。静的型付け言語に苦手意識を持っていた私ですが、 Go は構文もシンプルなため、とても楽しく実装まで取り組むことができました。 なおご存知だとは思いますが、スクレイピングは相手先のサーバにアクセスするため短時間で大量のアクセスを行うのは NG です。迷惑がかからないよう十分配慮をしましょう。 最後に、 mediba では一緒にモノづくりができるエンジニアを募集しています。 少しでもご興味がありましたら こちら からどうぞ。
アバター
こんにちは。テクノロジーセンター6GでManagerをしている下地( @primunu )です。 メンバーとの雑談でたまに話題になるエンジニアのキャリアパス。 どのキャリアを選択するかはもちろん各々が決定しますが、昨今のソフトウェアエンジニアのキャリアパスは多様かつ、組織によって若干役割が異なる事があるので、目指す方向がわからず迷子になる事があると相談を受けました。 そこで一度立ち戻り、medibaではどんなキャリアパスがあるのか?また、どのような役割なのか?を整理を含め図解してみました。 キャリアパスの概要 役割を図解した所、23のキャリアがありました。 その中でもイメージが付きにくい、または他社と若干領域に乖離があるキャリアを重点的に説明していきます。 ※ マネージャー/PjMはこちらを参照下さい SRE 主な業務はAWS全体管理や、インフラ管理や構築、SLO/SLA/SLIの定義、セキュリティ、バジェット管理、トイルの洗い出しやトラッキング等が主な業務になります。なお、IaCやアーキテクチャ設計はリードバックエンドエンジニアやテックリードが実施する事が多いです。 インフラ組織から名前がSREに変わった背景があるので、SREという職能のコミットメント領域がまだ不明確という課題があり、組織の納得度を高める為にも定義したいと考えています。 ※ SRE奮闘記 に今後記載していきます。 リードエンジニア(セキュリティエンジニア含) 豊富な知識・経験から最適なアーキテクチャを選定し、技術選定を行いながらPoCを回し、技術で牽引してくキャリアになります。後述するテックリードと似た振る舞いしますが 大きな違いは影響範囲 になります。リードエンジニアの影響範囲はチームになりますが、テックリードは組織全般に及びます。 ここまで記述するとエンジニアリングしかしないと思われがちですが、ステークホルダーとの会話も行いも各メンバーへの教示も行うので、 ただコードを書くキャリア ではありません。 テックリード予備軍のキャリアなのでテックリードと連携をしプロダクトの技術的意思決定を行います。 システムディレクター medibaでは完全自社開発の会社ではなく、プロダクトによっては受託開発もあります。 現時点だと受託開発プロダクトのみのキャリア になり、そこでステークホルダーと技術ナレッジを活かしながらディレクションを行います。ディレクターという事もあり提案や、要件定義はもちろんの事、ファシリテータも行います。ステークホルダーや協力会社・外部会社と技術的な会話ができ、意思決定ができ必要があるので〇〇エンジニアの上級職になると感じています。 プロジェクトリーダー プロダクトによってPjMと責務が被る事がありますが、開発チームオンリーのチームや、8人未満の小規模チームのリーダーがmedibaで言うプロジェクトリーダーになります。 サーバントリーダーではなく前に出て引っ張っていくリーダー となります。 ステークホルダーとの会話はもちろんの事、スケジュール管理や、チームビルディングも行います。チームによってはスクラムマスターがプロジェクトリーダーを兼任する事が多いと感じています。ハードスキルとソフトスキルの両面を求められるので、マネージャーを目指す方が通るキャリアになります。プレマネバランスですが感覚値としてプレーヤー7、マネージャー3です(個人の感想です)。 そんなプロジェクトリーダーですが、 マネージャーとの大きな違いはピープルマネージャーと組織へのコミットメントの有無 になります。チームへのコミットメントは当然ですが、組織への干渉は低く、ピープルマネージメントは直属マネージャーの責務になります。 品質管理リーダー 品質管理はスポットで入る事が多く、協力会社と連携する事が多いです。そこで試験項目作成やスケジュール確認、SLA的に問題ないか実施、確認すると平行し、協力会社との連携や契約、統率を行うのが品質管理リーダーになります。品質管理の知識以外にも他社を巻込み組織しなければらないので、 メンバーをまとめ組織するスキル が求められます。E2Eテストのシナリオを記述したり品質管理におけるPDCAを回す所までは介入できておらず今後の課題となっています。 なお、品質管理とテスターの大きな違いは要件から試験項目作成ができるの有無です。 テックリード テックリードとは完結に言うと、リードエンジニアの上位職になり、リードエンジニアの箇所でも触れましたが、影響範囲が組織全般となります。厳密にこれを実施する等はなく、PjMを実施したり、PoCを回したりと技術的な意思決定を主導し推進します。コードの品質管理はもちろんの事、チーム全体の生産性、アーキテクチャ・設計も行うので広さと深さの両面を求められます。 なお、 medibaのテックリードはマネージャーではなく 、成長支援等は行やメンタリングは適宜行うもののコミット領域ではなく、あくまでも技術でリードしプロダクトの成功に寄与するキャリアになります。 Unitマネージャー、マネージャー、シニアマネージャー。何が違うの? 細かい違いはあるものの、 大きな違いは管轄するチームの種別とコミット領域、比率 になります。 Unitマネージャーはプロダクト横断チーム、マネージャーとシニアマネージャーはプロダクトに強く関わるマネージャーになります。マネージャーとシニアマネージャーの役割は大きく変わりませんが、 シニアマネージャーの方が組織へのコミットメント が求めれます。 PO(プロダクトオーナー)や、PM(プロダクトマネージャー)のキャリアパスは? Biz職からのPO/PMのキャリアパスはあるものの、POの業務範囲にエンジニアに馴染みが薄いPL管理が含まれているのが起因しているせいか、 エンジニアとしてのPO/PMのキャリアを描けていません 。 【翻訳】プロダクトマネジメントトライアングル にあるように、プロダクトは開発者、ユーザー、ビジネスの 3 つで構成されており、エンジニア視点は必要不可欠だと認識しています。 エンジニアリング、データ分析、エンジニアとのコミュニケーションと言った領域をカバーできるのはエンジニア出身のPO/PMの強みだと感じており、 プロダクトの意思決定を素早く判断できる と感じています。 先述の通りまだエンジニアのキャリアとして描けていないのもの、現場ではマネージャーや、テックリードが中心となりエンジニア領域をカバーしています。マルチタスクになってしまうので、キャリアパスにない事を組織課題と捉え、PO/PMのコミット領域を明確にし、キャリアパスとして描きたいと考えています。 まとめ 如何だったでしょうか。複雑に見えるキャリアパスですが、目指す方向の咀嚼ができれば、日々の行動が変わり、目標、引いては組織へのコミットメントが高くなると考えています。 これを気にキャリアプランを再考してみるのは如何でしょうか。少しでも手助けになれば幸いです
アバター
はじめに こんにちは、SRE Unitの北浦です。 私達がSRE活動を推進していく中で、行き詰まった点や逆にうまくいった方法などの知見を共有していこうと思い、筆を取りました。 “アプリケーション"のシステムとは違い、"人"のシステムを改善・介入していくためには、 関係各者へたくさんの説明や知見を共有していかないといけないのは当然のこと。 重要なポイントを割愛してしまうと、要らぬ誤解を生んでしまい、物事がうまく推進できないという自体に陥ります。 今回は、我々の体験談としてエラーバジェットというものがネガティブに誤解されてしまった過程と、その軌道修正に必要だった説明をまとめてみました。 エラーバジェットって何よ まずはエラーバジェットそのものについて、足並みを揃えていきましょう。 Googleで検索をかけると以下のような説明が出てきます。 エラーバジェット(Error Budgets)とはエラーに対する予算であり、SLOに基づき算出される損失可能な信頼性である。 サービスの計測された稼働時間がSLOを超えている、換言すればエラーバジェットがまだ残っている状態であれば、チームは新しいリリースをプッシュ(デプロイ)できる。 例えば、正常な稼働時間という切り口のSLIを設定し、そのSLOを99.9%と定義したとしましょう。 そうした場合、残りの0.1%がエラーバジェットになります。 100% - SLO(99.9%) = 0.1% 1ヶ月を30日間とした場合、残りの0.1%は43.2分です。 1ヶ月(43,200分)/1000 = 43.2分 この場合、仮にダウンタイムが1ヶ月のうちに43.2分未満であれば新規開発を続行し、 43.2分を超過する自体に陥っていた場合は、新規開発を中断し、この値が回復するまでの間はダウンタイムの時間を減らす活動を行う。 このように使われるのがエラーバジェットです。 生まれる誤解 エラーバジェット自体はDevとOpsの課題を絶妙なバランスで解決した良い手法なのですが、 これを良いものと認識するためには、いくつかの前提知識が必要であり、 加えて、何を解決しているものなのか、課題を捉える必要があります。 前提知識と課題、ここの相互理解がないまま導入を提案しても、 「うちは予算取って動いているプロダクトなので、なかなか柔軟に舵切ることは難しい」 「納期があるから、新規開発中断は困る」 「うちは組織形態として大きいから・・・SREってスタートアップやテックリードのような企業で導入するやつでしょ?」 …などの反応が返ってきそうです。 上記の反応は、見方によれば至極真っ当で、 そもそも開発プロダクトとして、開発への投資を信頼性に向けるか新規開発に向けるかといった意思決定は、プロダクトの命運を左右する重要な要素でしょう。 そんな重要な要素の一つをエラーバジェットなるものに背を預けて良いものでしょうか。 こう考えると、なかなか心理的にもハードルが高そうです。 ということで、エラーバジェットを理解するための知見、そして何が解決されて嬉しいのかというポイントをふかぼっていきたいと思います。 暗黙のエラーバジェット ところで、システム開発に関わりのある読者の方には質問なのですが、 「あなたが担当しているシステムのエラーバジェットは最適化されていますか?」 ・ ・ ・ 「いや、エラーバジェット導入してねーよ」 と言われてしまいそうなのですが、 実はそうとも言い切れないケースが多いのではないかと思っています。 例えば、 システムに致命的な欠陥が見つかり、予定していた開発を中断、 回復作業に注力した。 という状況はイメージできないでしょうか。 この場合は、エラーバジェットを大幅に超過しているということが、 関係者全員の共通認識となった時に起こる状態です。 ・・・ふむ。こう考えると、どうやらエラーバジェットというものは、 暗黙的には存在しているもののようです。 暗黙のエラーバジェットが引き起こすもの エラーバジェットが暗黙的であるものの場合、言い換えれば、 ”人”それぞれの感覚値に依存していることになります。 Aさんは0.1%の感覚かもしれませんし、Bさんは0.01%の感覚かもしれません。 プロダクトとしての意思決定を行う際、この感覚の乖離を埋める手段はコミュニケーションになることと思いますが、 この感覚値は各役割にも影響されることが多く、 そして、正解が存在しないところが実に厄介なところで手をこまねいているプロダクトも多くありそうです。 「あなたが担当しているシステムのエラーバジェットは最適化されていますか?そしてその感覚は関係者各位で共通しているものですか?」 もしかしたら、あなたのチームはこの暗黙のエラーバジェットのコミュニケーションに多大な工数と労力を強いられてるかもしれません。 この質問にYesと答えられず、且つ、コミュニケーションや実態に課題を感じているのであれば、 プロダクトとして指針を決めることに一度注力し、エラーバジェットの運用をしてみるのも良い選択肢かもしれませんね。 機能性と信頼性、ユーザーはどっちが欲しい? 一つ、エラーバジェットの嬉しみポイントが見えたところですが、 以下に、極端ですがToDo管理アプリケーションの例を二つあげてみます。 ケースA ユーザーからも評価の高い機能性のあるアプリケーションだが、 3日に1日くらいのペースで利用ができなくなる。 ケースB 24時間365日正常に稼働するが、ToDoの登録と削除の機能しかない 上記、どちらかのアプリケーションを使いたいと思いますか? ・・・どちらも使いたくないと思ったことでしょう。 実際に市場に出してもユーザーから選んでもらえることは無さそうです。 つまり、ビジネスとしてプロダクトが成功するには、 新機能の開発と信頼性の向上、両方のタスクをバランスよくおこなっていかなければならなそうです。 しかし、ここで二つ立ち塞がる障壁があります。 新機能開発を行うとバグが発生する可能性があるため、信頼性が低下する。 SLOに9を加えるには、その前の9を実装するのにかかったコストの10倍かかる。99%から99.9%にするためには99%にするためにかかったコストの10倍かかる。99.99%にするためには、それのさらに10倍のコストがかかると言われています。 どうやら新機能開発と信頼性の向上は、お互いにトレードオフの関係性にあるようです。 <参考> https://cloud.google.com/architecture/defining-SLOs?hl=ja#why_slos 高すぎるSLOが引き起こすもの 例えば、暗黙的にSLO99.99999…%のように高い水準のSLOを設定してしまい、 エラーバジェットがとても少ない状況下にあったとしましょう。 その場合、信頼性の担保に対して必要なコストがかなり高い水準で必要とされ、 結果的に新規開発への着手が困難という自体に陥るわけです。 ただし、そんな状況下だったとしても、プロダクトとして新機能開発がゼロということは考えづらいですから、 信頼性が担保できていない状態なのに新機能も開発しなくちゃいけないという大変な状況になるかもしれません。 思い当たる節があるようであれば、まずは今のシステムがどれほどの水準で信頼性があるのか、 SLIを定義し、計測するところから始めると良いでしょう。 SLOをあげてしまうと新規開発に投資できるだけのコストが失われてしまうという前提のもと、 実現可能性が高い値を定義できると、少しづつ新規開発にも投資できるようになるかもしれませんし、それでも今の現状で信頼性が担保できていないと考えるのであれば、定量的な指標によって、現在行っている信頼性向上の施策が効果があるのかないのか。判断することができるでしょう。 まとめ ネガティブな解釈をされがちなエラーバジェットに関して、 今回は課題の背景や解決アプローチを説明させていただきましたが、誤解は解けましたでしょうか。 エラーバジェットを定義するのは良いかも。とか、 改めて自分の担当しているプロダクトには、緊急的には必要なさそう。など、諸々考えてくれたら嬉しいです。 さいごに medibaでは、現在、各プロダクトの価値を向上すべく、 日々SRE活動を積極的に行っております。 次回は、エラーバジェットをより有効なものにするために、信頼性を上がると何が嬉しいのか、下がってしまうとどんなことが起こりうるのかという点を深堀し、より効果の高いSLI定義についてのナレッジを共有しようかと考えてます。お楽しみに・・・!
アバター
はじめに こんにちは、フロントエンドエンジニアの中畑 ( @yn2011 ) です。 au Webポータル 無料ゲーム では様々なフロントエンドのパフォーマンス最適化に取り組んでいます。今回は、既に実施した最適化の中から 対応コストが小さく、効果が分かりやすかったもの を中心に、対応事例のご紹介をします。 なお、 au Webポータル 無料ゲーム は Jamstack 構成となっており、Next.js (SSG) と ヘッドレスCMS を利用しております。詳細な技術スタックについては ヘッドレス CMS 運用におけるデプロイと環境差分について に記載がありますので、ぜひこちらもご覧ください。 Lighthouse による測定結果 パフォーマンスの最適化を行う前の状態で、Lighthouse の測定結果は以下のようになっていました。 指標 指標の意味 結果 First Contentful Paint テキストまたは画像が初めてペイントされるまでにかかった時間 2.3 秒 Speed Index ページのコンテンツが取り込まれて表示される速さ 3.7 秒 Largest Contentful Paint 最も大きなテキストまたは画像が描画されるまでにかかった時間 9.1 秒 Time to Interactive ページが完全に操作可能になるのに要する時間 8.9 秒 Total Blocking Time タスクの処理時間が 50 ミリ秒を上回った場合の、コンテンツの初回描画から操作可能になるまでの合計時間 1,720 ミリ秒 Cumulative Layout Shift ビューポート内の視覚要素がどのくらい移動しているか 0 測定結果を見ると、Largest Contentful Paint、Total Blocking Time は特に改善の余地がありそうでした。今回は、この測定結果を元に、実装コストが小さく・既存の機能に対する影響も小さいと判断した以下の 3 つの最適化についてご紹介します。 ピックアップバナーの preload au Webポータル 無料ゲーム のトップページは以下のようになっており、画面の大部分をゲームタイトルのバナー(ピックアップバナー)が占めています。 トップページにアクセスした際に、ピックアップバナーの画像を優先的に読み込むことで Largest Contentful Paint の数値を改善しユーザが体験する画面表示までの速度を高めることができます。 実装としては、以下のような要素を HTML に追加するだけで実現可能です。 <link rel="preload" href="https://url-to-image" as="image" /> 外部ドメインに対するリクエストの preconnect au Webポータル 無料ゲーム では、広告の表示やデータ分析のために外部ドメインに対してリクエストを行います。 リクエスト先の外部ドメインが予め分かっている場合には、事前に DNS Lookup、Initial Connection、SSL を済ませておくことでリクエストが要求されてからレスポンスを得るまでの時間を短縮することができます。 実装としては、以下のような要素を追加するだけで実現可能です。 <link rel="preconnect" href="//other.domain.com" /> Chrome DevTools の Network タブを利用して確認すると、preconnect 指定前は以下であったのに対し preconnect 指定後に確認すると、各フェーズに使用される時間が短くなりました。 なお、preconnect の他に、dns-prefetch を属性値として指定する方法もあります。両者の特徴を比較すると、preconnect の方が多くの処理を事前に行うことができる反面、ブラウザのサポート範囲は dns-prefetch よりは狭いです。 dns-prefetch preconncet ブラウザサポート ○ △ DNS Lookup ○ ○ Initial Connection、SSL ☓ ○ 以下のようにフォールバックとして両方を指定することも可能なそうですが、 WebKit では動作しないという報告 がありました。 <link rel="dns-prefetch preconnect" href="//other.domain.com" /> preconnect は、あくまでパフォーマンス改善のための nice to have な対応であり一部ブラウザが対応していなくても動作自体に影響はないため preconncet のみを利用する判断をしました。 Google オプティマイズのスクリプトを常に読み込まない au Webポータル 無料ゲーム では、A/B テストを実施するために、Google オプティマイズを利用しています。そのため、Google オプティマイズのスクリプトを必ず読み込んでいました。 <script src="https://www.googleoptimize.com/optimize.js?id=OPT_CONTAINER_ID" /> しかし、A/B テストは常に実施しているわけではないため A/B テストを実施していない期間については不要なリソースの読み込みとスクリプトの実行が発生してしまっていました。 au Webポータル 無料ゲーム は、Next.js の SSG を利用しているので、 A/B テストを実施していなければビルド時に Google オプティマイズのスクリプトタグを含めないように変更しました。 A/B テストを実施しているかの判定は、利用しているヘッドレスCMS に特定のレコードが存在するかどうかで行っています。 実装としては以下のように、 _app.tsx の getInitialProps で 判定を行い Props にフラグを追加しました。 static async getInitialProps() { ... return { pageProps: {}, isOptimizeRunning: !!config.experiments && config.experiments.length > 0 } } この isOptimizeRunning を利用し、レンダー時にスクリプトタグを含めるかどうかを決定します。 {isOptimizeRunning && ( <script src="https://www.googleoptimize.com/optimize.js?id=%24%7BOPT_CONTAINER_ID" /> )} 改善の結果 以上の最適化を行い、Lighthouse を再実行すると測定結果は以下になりました。 指標 前 後 改善率(%) First Contentful Paint 2.3 秒 1.3 秒 43.48 Speed Index 3.7 秒 3.0 秒 18.92 Largest Contentful Paint 9.1 秒 6.2 秒 31.87 Time to Interactive 8.9 秒 8.6 秒 3.37 Total Blocking Time 1,720 ミリ秒 1,550 ミリ秒 9.88 Cumulative Layout Shift 0 0 0.00 画面描画に関する指標(First Contentful Paint、Speed Index、Largest Contentful Paint)を中心に大きく数値が改善しました。トータルのスコア自体も 38 → 44 と上昇しました。 おわりに au Webポータル 無料ゲーム で実施したフロントエンドパフォーマンスの最適化についてご紹介しました。 個人的には、パフォーマンスの最適化というと難しそう、調査や実装に多くの時間が必要になりそう、といった先入観がありましたが、意外と簡単に実装できて成果に繋げられるものもあるんだな、という学びがありました。 今回ご紹介したものは、 実際に取り組んだパフォーマンスの最適化の中の一部であり、実際にはもっと実装の変更が大きく、既存機能への影響を考慮しなけばならないものも多くありました。 それらについては、また別の機会にお伝えできればと思います。 最後までお読み頂きありがとうございました。
アバター
SrManager の尾野です。 2021/03/17(水)にKDDIグループ6社合同でテックカンファレンスを開催しました。 今回、コミュニティの立ち上げから運営まで携わりましたので、雑感と今後について触れたいと思います。 (このアイキャッチは コネヒト社 の方がデザインしてくださいました。) KGDC is 何 KGDCは「 K DDI G roup D eveloper C ommunity」の略です。 ”KDDI グループ企業各社の Developer が集い、「エンジニアが楽しめる」イベントを提供するコミュニティ” をコンセプトに立ち上がりました。 connpass グループページ connpass イベントページ(現在は終了しております) 弊社 お知らせ 弊社からは VPoE 新井:パネルディスカッション CTO準備室室長補佐 曽根:セッション カルロス(フロントエンドエンジニア): LT にも登壇していただきました。 なぜやることになったのか? 思いつき です。 個人的に繋がりのあった コネヒト社の CTO と Supership 社の CTO に「一緒にイベントやったら楽しそうじゃないですかね?」と雑にお声掛けさせてもらったのがきっかけです。 KDDIグループ、実は多彩なんです。 そんな事が多くの方に伝われば良いなと思って企てました。 どうやって進めていったのか? 招集 時間軸で言うと 2020年12月初頭です。 弊社社長の江幡にも協力を頂き、 共催企業 にお声掛けしました。 共催企業は弊社含め全 6 社、快く共催について賛同を頂きました。 運営準備 各社運営にご協力いただける方をアサイン頂き、実行に移ったのが年明けすぐ 2021年01月初頭でした。(運営メンバーは総勢20人程) 私自身もゼロイチでコミュニティを立ち上げる経験が無く、各社知見のある方と相談しながらTODOやロードマップを立てていきました。 ざっくりと以下の分担で各社割り振り、進めて行きました。 配信チーム オンライン配信におけるツールやコミュニケーションに意思決定を持つチーム コンテンツチーム コンテンツやタイムテーブルに意思決定を持つチーム プロモーションチーム 拡散/集客方法に意思決定を持つチーム 司会チーム 当日の司会進行など また、週次で集まり各チームのTODOや進捗/課題などを吸い上げて進行していきました。 主に scrapbox 上でログを残しながら、専用の Slack workspace 上で連携を取りながら進めていくスタイルを取りました。 当日に至るまで、開催日の変更や緊急事態宣言の延長など、大きな予定変更がありながらも運営メンバーの皆様が臨機応変に対応してくれました。 開催前日のお気持ち Slack 投稿です。 (共同司会の KDDI Web Communications 葛さんのなんと上手い事か。圧巻でした。) 当日 connpass 上での参加申し込み: 233 名 当日 Zoom 参加も常時 100 人以上のイベント規模となりました。 当日司会進行しながら聞いていて、非常にバラエティに富んだラインナップだったと実感しました。 KDDIグループ、実は多彩なんです。 全登壇者が時間超過する事なく進行出来たのは、登壇者の皆様がすごかった(語彙力)というのもありますが、裏でタイムキープをしてくれた 弊社 下地/五月女 のきめ細やかさのお陰というのもあります。 全量ではありませんが、当日セッション/LT資料は こちら に上がっております。 さいごに 執筆時点、次回開催に向けて検討を進めております。 次回は #1 として、もっと大々的に打ち出しながら開催していければと考えております。 KDDIグループで協力しながら、もっとグループ全体を盛り上げて行ければと思っております。 また、快く共催に賛同してくださり、運営に携わってくださったグループ企業の皆様には厚く御礼申し上げます。 共催企業(敬称略) iret https://www.iret.co.jp/ au コマース&ライフ https://www.au-cl.co.jp/ KDDI Web Communications https://www.kddi-webcommunications.co.jp/ コネヒト https://connehito.com/ Supership https://supership.jp/ KDDIグループ、実は多彩なんです。 その中でも ユーザー中心な “ものづくりCompany” を目指す mediba で一緒にものづくりしてくれるメンバーを随時募集しております。
アバター
こんにちは。下地と申します。 私のロールはエンジニアリングマネージャー(以下、EM)なのですが、EMと言っても組織によってコミットする領域や振る舞い等が違うと思います。そこでmedibaにおけるEMとしての業務内容をまとめ、どの領域にコミットしているのかを整理したいと思います。 ※他EMとやり方を同期しているわけではないのであくまで私がEMとして実施している範囲の解説になります。予めご了承下さい。 主な業務 medibaにおけるEMはピープルマネージメントを軸にしておりプロダクトに1人のEMを配置しチームを運営しています。主な業務としてピープルマネージメント、組織運営、プロダクト開発の3領域にわける事ができます。 ピープルマネージメント メンバー(ヒト)に向き合い、成長や育成にコミットすることで、組織の成果を最大化する事を目指します。ただ、成長や育成はあくまで個人に依存していると考えているので主に場を整えたり、作る事を意識し支援する事を重点に置いています。 1on1の実施 毎週30分メンバーと話しています。話す内容はメンバーによってバラバラで雑談や、趣味、業務のお困りごと、目標等についてお話します。リモートワーク中心の業務なのでメンタルチェックも兼ねて実施しています。 各メンバーの成長支援の創造 コンフリクトの解消や場を整備しパフォーマンスが出せる環境を提供するよう心がけ支援します。私の考える理想のEM像は部活動におけるマネージャーなので、雑務やパフォーマンスに影響がある箇所は1on1を通し情報収集を行い対応します。 組織運営 ミドルマネージャーの主な業務が詰まっている領域です。medibaのEMも例外ではありません。詳細は割愛しますが、評価は良いところにフォーカスし価値を高めていく事を意識しています。 評価 採用面接 組織KPIのコミット 広報活動 プロダクト開発 書籍、ティール組織にある”助言プロセス”を取り入れフォローしています。 助言プロセスとは 原則として、組織内のだれかがどんな決定も下してもかまわない。 ただしその前にすべての関係者とその問題の専門家に助言を求めなければならない。 決定を下そうという人には、一つ一つの助言をすべて取り入れる義務はない ※書籍"ティール組織 ― マネジメントの常識を覆す次世代型組織の出現"から抜粋 プロダクト、メンバーのボトルネックの解消 開発プロセスや、プロダクトの課題解決は助言プロセス主体で進めるものの、プロダクト全体のボトルネックや、他職能マネージャーとの調整やメンバー間のトラブル解消等は全体を俯瞰して見ているEMが対応します。このボトルネックの解消は、EMの多岐に渡る業務の中で1番優先度が高い業務としています。 意思決定が必要なMTGへの参加 施策出しやリリース等々のMTGはあまり参加しないものの意思決定が必要なMTGには積極的に参加しています。テキストで対応するより会話した方が意思も伝えやすいと考えています。 障害時の取りまとめ EMが指揮を取り影響範囲等を調査報告します。他マネージャーとの連携が必要なのと最終的にEMが報告するので積極的に対応します。 プロジェクトマネージャー業務 メンバーが実施する事もありますが他マネージャーや他部署との連携が必要な場合EMが対応します。 技術選定 組織としての技術選定の観点として売上/利益、今後の展望、成長のバランスを意識し事業内容でどこにBETするかを判断しています。売上/利益重視なら得意なスタック、今後の展望を重視するなら既存スタックにとらわれず最適なスタックを選定といった具合です。 まとめ medibaの主なEM業をまとめて見ましたが如何でしょうか?私自身はコードを書く業務には携われていないのでハードスキルが止まっているものの、1on1等で視座があがっていくメンバーや、仕事が楽しいと言ってくれるメンバー、業務の幅を広げ活躍できる領域を広げるメンバー等の成長を近くで実感できるのは非常に気持ちがよくEMのやりがいを感じます。 今回は組織運営パートの詳細は割愛していますが機会があれば言語化したいと思います。 最後になりますが、現在medibaでは EMを募集 をしています。 少しでも興味を持って頂けたら応募してもらえると幸いです。 余談 EMトライアングル とはEMの役割をグラフィックモデル化したものです。以下の図はEMトライアングルに主な作業領域に色をつけ可視化したものになります。medibaではTechnology - Team領域を主軸に活動している事もあり全領域を網羅する形となっています。こちらも参考にして頂けると幸いです
アバター
はじめに medibaの野崎です。 バックエンドエンジニアとしてバックエンドアプリケーションやインフラストラクチャーの運用を行っています。 また,兼務でテクノロジーセンターにてTechLeadチームにも所属しています。 昨年よりau Webポータルの運用チームに担当となりました。 au Webポータルの運用チームは、複数のシステムを運用しています。 大半のシステムが初期開発から数年経過しインフラストラクチャー(AWSリソース)を手動で変更するような業務となっており、後述するような課題が多く見受けられました。 本記事では、その改善のために行った 既存システムのインフラストラクチャーのコード化 - Infrastructure as Code(IaC)を進めたプロセスを紹介したい と思います。 インフラストラクチャーを手動で変更する場合の課題 所属するチームではこれまで以下のような業務の流れになっておりインフラストラクチャー(AWSリソース)を手動で変更していました。 ※弊社ではバックエンドエンジニアとインフラエンジニアにロールが分かれています。 変更内容をバックエンドエンジニアが作成し、インフラエンジニアに変更依頼を行う その変更内容を元にインフラエンジニアがAWSマネージメントコンソールを介して変更作業を行う このような業務の大きな課題として以下のような点が挙がっていました。 AWSリソースの管理がインフラエンジニアに属人的にとなる バックエンドエンジニアからの変更依頼が多い際には、変更作業にリードタイムが発生する 変更履歴が残らず、組織変更に柔軟に対応できない こういった課題の解決のため、 まず既存システムのインフラストラクチャーのコード化 - Infrastructure as Code(IaC) を検討しました。 バックエンドエンジニアもコードを介して、AWSリソースの変更管理を行って貰うためです。 どのように既存システムリソースをインフラコード化するか 弊社では新規開発時にInfrastructure as Code(IaC)ツールとしてTerraformを利用することが多いため、Terraform利用を前提にしました。 既存のリソースをTerraformのコード化するには、 terraform import コマンドを使うことになります。 しかし、このコマンドはAWSのリソースを1点ずつ指定する必要があります。 参考: https://www.terraform.io/docs/cli/import/index.html 今回の対象システムのリソース数は 数100 あり、この方法でコード化するのは現実的ではありませんでした。 そこで今回はTerraformerというツールを使い、既存システムのTeraformコードの生成を行いました。 以下では、その実施手順を記載します。 参考:  https://github.com/GoogleCloudPlatform/terraformer Terraformerは、既存のシステムからTerraformコードとそのstateファイル(tfstate)を生成するCLIツールです。 Terrformerによる既存システムのコード化の実施 今回のシステムは以下のような前提として記載していきます。 システムの環境は、開発環境(dev環境)と商用環境(prd環境)に分かれている 各環境ごとに、AWSアカウントが1つずつある 東京リージョンを利用している ローカルPCとして、macOSを使う 1. コード化対象のシステムの確認 Terraformerの README.md にAWSに対応しているサービスが記載されています。これらから対象システムのうちコード化したいサービスについてリージョンごとにリスト化しておきます。 (例: 東京リージョンにてacm,alb,ec2_instanceなど) また、AWSのグローバルサービスについても同様にリスト化しておきます。 (例: iam,route53,wafなど) 2. Terrformerコマンド実行し、コードを生成する 以下のツールをローカルPCにインストールします tfenv Terraformのバージョンを管理するツール https://github.com/tfutils/tfenv 利用したバージョン: 2.0.0 Terraformer 既存システムからTerraformのコードとstateファイルを生成するツール * https://github.com/GoogleCloudPlatform/terraformer 利用したバージョン: 0.8.11 次に適当なディレクトリを作成し、以下のファイルを作成します。 init.tf .terraform-version ファイルには以下のように記載します。 $ echo 'provider "aws" {}' > init.tf $ echo '0.11.14' > .terraform-version 次に terraformer import コマンドでコードの生成を行います。 このコマンドのフラグはいくつかありますが、今回はシステムに合わせて以下のようにしました。 フラグ regions は、生成対象のサービスのリージョンを指定する。 今回は東京リージョンとグローバルサービスにそれぞれ分けて指定する。 フラグ resources には、「1. コード化対象のシステムの確認」で確認したサービスをリージョンごとに記載する。 フラグ path-pattern は、生成するコードの配置ディレクトリを指定する。 ※tfenvにて 0.11.14 としている理由 TerraformerのAWSプロバイダーの生成するコードが 0.11(HCL1)系のものになるためです。 README.md  などを見ると_0.13 対応などの記述もありましたが、コードを確認したり実際にコマンドを実行しましたが、現在のところ 0.11系 でしか生成出来ないようでした。 #対象が東京リージョンにあるサービス $ terraformer import aws \ --resources=acm,alb,ec2_instance,eip,elasticache,igw,nat,nacl,rds,route_table,s3,sg,sns,sqs,subnet,waf_regional,vpc,vpc_peering \ --path-pattern aws-service/dev/ap-northeast-1 \ --regions=ap-northeast-1 #対象がグローバルサービス $ terraformer import aws \ --resources=cloudfront,route53,waf,iam \ --path-pattern aws-service/dev/global/ \ --regions=global このコマンドを実行すると、以下のようにtfファイルとtfstateファイルが生成されます。 ※スクリーンショットでは、フォルダを閉じていますが_globalディレクトリの配下にもtfファイルとtfstateファイルが作成されています。 3. コードの修正とapply 生成したコードをチーム開発で使えるようにコードの修正をいくつか行いました。以下に今回実施したことを簡単にリストします。 これらは開発チームの状況や対象システムによって異なるため、参考までに留めてください。 Teraformのバージョンアップ 前述したように、生成したコードは 0.11 のものとして作成されています 運用するバージョンに合わせて、Terraformの terraform 0.12upgrade コマンド等を使い、コード修正を行います 参考: https://www.terraform.io/upgrade-guides/0-12.html 管理不要なAWSリソースをTeraform管理外とする 例えば、defaultのvpcなどは運用上管理が不要なので、この時点で管理外としておきます tfstateをローカルからS3に移動 最後に生成・修正したコードを元に実際の環境に terraform apply を実行します。 一見差分が出ないように思われますが、今回実行した場合いくつか差分が出ました。 一例として、Route53があります。providerにて comment のdefault値が Managed by Terraform となっているため、これ以外の文字列が実リソースに設定されていれば、差分となります。 参考: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone#argument-reference このような差分は1点ずつ確認しながら、コードの修正(差分を ignore_changes に設定する)または apply を行い、 最終的にコードとの差分を無くします。 チーム開発を始める ここまでで既存システムをコード管理下に置くことが出来ました。 今回はさらにバックエンドエンジニアを対象に Terraformの使い方やAWSリソースそのものについて勉強会などを行い、IaCでの開発に必要な知識を理解して貰いました。 その上で簡単な変更作業を切り出しながら、Infrastructure as Code(IaC)での開発に慣れて貰っています。 最後に 弊社のようなシステムの安定性と変更容易性を高いレベルで両立しなければいけない組織では、Infrastructure as Code(IaC)は非常に大切なプロセスだと考えています。 今回の取り組みは改善の第一歩であり、 継続的に今回のプロセスを維持・改善しつつ、組織的にもスキルの向上を高めていけるように、今後も取り組みを続けていきたいと考えています。 medibaでは、システムを深く理解し関わる組織の理解も鑑みながら、よりよいサービス運用をともに検討していただけるようなエンジニアのご応募をお待ちしています。 以上になります。
アバター
こんにちは。武田( @tkdn )です。 納得度の高いプロダクト開発とは何だったのか 前編 では、プロダクト開発に際して直接的な関与ができていない原因は、自分の中の無知・無関心にあったというところで終えました。 後編ではプロダクトマネジメントを意識しながら、どうやってものを作ろうとしているかについて書いていきます。前編よりは実践的になっているはずです。取り組みのご参考にされてください。 プロダクトマネジメントを意識する 昨年 11 月にキリの良い大きなリリースがあったため 12 月から 3 つプロダクトを抱えたチームは二手に分かれ、私は au Web ポータル無料ゲームというプロダクトの担当となりました( ヘッドレス CMS による運用についてこのブログでも触れたプロダクトです )。 まず取り掛かったことはプロダクトオーナー(以降 PO)に張り付いた業務を剥がすことと、プロダクトマネジメントの理解でした。 PO はステークホルダーからは期限を迫られ、開発からはできるか分からんと言われ、事業部全体では数字が足りないと言われる。それだけならまだしも、ミーティングや調整が多く、肝心なプロダクトのことやプロダクトが提供するユーザー価値や収益バランスについて深く考える時間の余裕があまりなさそうでした。 同時にプロダクトマネジメントについて理解を深めることで、PO の負担を減らすだけでなく、プロダクトチームを正しいものづくりに持っていく必要があると考えたのです。前編でも一部引用したように プロダクトマネジメント は非常に強力な書籍となり、ここまでの活動を支えています。 PO やプロダクトマネージャーに委任することなく、現場で正しいと思ったことはすべて現場で取り組もうと考えたのです。 関心を持つための根幹を定義する プロダクトチームが施策の優先度や意思決定の場面で立ち戻るための指針を定義する PRD(プロダクト要求定義書)からまずは着手しました。これは太い指針がなければ必ず折れる、立ち戻る場所がなければ離散すると考えたからです。 プロダクトの目標 プロダクト立ち上がりの背景 ターゲットユーザー、ペルソナ なぜ作っているのか 何を作っているのか どういった機能を提供しているのか … 参考にしたリソースはいくつかありますが、 Tably 及川さんの資料 にある項目を一番参考にしています。 幸いにも 2020 年前半に UX 部門のメンバーを中心にプロダクトリブランディングのためのワーク(スクリーンショット下部の Miro)を行っており、ビジネス提供方針、NPS 調査結果などを踏まえたユーザーの根本的要求の定義やペルソナやターゲットセグメントなど多くの考察をすでに行っていました。 技術畑からするとむずがゆいような内容に触れなくてはいけない場面もありますが、ここを定義しないと納得感を得られる自信がなかったため歯を食いしばりました。現状「なぜ開発しているのか」に立ち戻る原点にはなっているかなと感じています。 最適なプロダクトのカタを見つける 開発に際してはモブプロを中心とした組織学習をベースにしており、ほとんど息を吸うように実践していましたが、ことプロダクトマネジメントとなると何から手を付ければ良いかわからない状態です。現状十分に回せているかと言われると怪しいところですし、プロダクトのカタをうまく回せる工夫を今も実践中といったところです(以下の画像は書籍、プロダクトマネジメントからのものです)。 1 では企業活動の戦略的意図を実現するため、プロダクトが扱う課題へブレイクダウンさせる必要があります。収益とプロダクトをつなげる接点となるので本来は PdM と強く持つべき接点ですがうまく実践できていないところです。 2 では 1 における共通目標達成のための現状把握です。3 はその共通目標に対するオプション目標(目標達成のためのエピックと言えるでしょう)を立て実行に移していきます。4 で結果を振り返り継続し最適化するか別のソリューションを考えるかなど実績ベースでカタを繰り返すことになります。 これらカタ実践の前に、 ソリューション先行による施策をやめるようにしています。具体的には共通目標に紐付かない・現状分析のない機能追加やツール導入の撤廃です。 プロダクトが目指す目標を意識したエピックを切り、数値ベースの試算をしたうえでリリース後の効果検証を繰り返しています。 幸運にも KR とした数値がプロダクトの達成度と成功を測るために最適なメトリクスであったため、基本的にはその数値を追い続けることになりました。これまで PO やアナリストに任せていたところにも関心を寄せ、構想と実行が分離しない形でなぜ我々は手を動かしているかの納得を得ようとしています。 ただしくツールを使う、たとえば JIRA プロダクトチームはスクラムを実践してきており、ワークフローの可視化や日々のイベントで JIRA を利用していましたが、JIRA にあるエピックはいつの間にか緩いカテゴリやラベリングといった使い方で本来の意味を失っていました。 カタでも触れたように共通目標のためのオプションはすべてエピックされるべきで、それぞれが何を達成するのか明文化しておく必要があります。JIRA のエピックを機能させるべく使い方も改めました。 エピックにはテンプレートを用意しなぜこれを推進するか明記する エピックはロードマップで可視化しリファインメントしやすくしておく (エピックを色分けせず色気が出てないのはご勘弁ください) プロダクトを世に放つのは開発者である これはポジショントークととらえられる可能性も多分にありそうですが、最終的にプロダクトを動かしリリースしているのはまごうことなく開発者です。 プロダクトマネジメントトライアングル にある、プロダクト・ソフトウェアを起点とした根源的要素として開発者、ユーザー、ビジネス(収益)の 3 点で構成されていることからもよく分かります。 コードをアップデートする人たちこそが唯一厳密に欠くことのできないチーム要員だ。開発者は会社のすべての責務を担うことができる(常に効果的にとは行かないが)。 どこまでの責務を追うか・プロダクトマネジメントにあたるような業務まで手を伸ばすかは開発者それぞれですが、介入していったほうが圧倒的に納得感が高いはずですし、コミットできる深さも違うはずだと今は感じます。 課題はないのか 模索しながらなのでまだつかみきれていない部分や課題もたくさんあります。 誰と何をどこまでやるべきかは整理しきれていないので、開発者が踏み込んでよいか微妙なラインで取り組んでいます。前編にもあるとおり私たちは職能のプロが集まってひとつの事業もしくはプロダクトを支えていくことになるわけですが、別の職能のプロに任せるべきことにまで手を伸ばしていないかは気を遣っています。判断を誤ると気持ちの良いチームワークが果たせません。 また開発者の担当するタスクがいわゆる「開発」だけではなく、ミーティングのアジェンダ作成やファシリテート、施策効果のざっくりした試算も行うようになった反面、開発タスクが薄くなりすぎていることも懸念のひとつです。私自身もコードを書かないスプリントが 2 回以上続いたら、ちょっと気が触れるでしょう。 先日発表されていた プロダクトマネジメントクライテリア ですが、自分たちではチェック項目がまったく埋められず旅の遠さに愕然としているところです。 まとめ 前編とは違って後編は取り組みを雑多に列挙していくような形になりました。 12 月から取り組んで結果どうなっているのかについて触れていませんでしたが、プロダクトの成果としての KR としていた数値目標は徐々に増加傾向にあり、始める前よりはまずまずの納得度を得られているんじゃないかなといったところです。 これまで実践してきたモブプロといった組織学習の中で得られた成功体験は間違いなく効いていて、重要なのは積み重ねられたチームでの学習の厚みであり、チームでの学習こそが不確実性を減らしていく のだとあらためて実感させられています。 前編後編と少し技術から離れたことを書きました。納得あるプロダクト開発を考えたい開発者にとって、少しでも役に立てられれば幸いです。後続のポストはがっつり技術的な投稿を期待して今日はここまでとしましょう。 最後まで読んでいただきありがとうございます。
アバター
こんにちは。武田( @tkdn )です。今日は「納得度の高いプロダクト開発とは何だったのか」というお題で開発チームが現場でどう変わろうとしているかという話をします。 簡潔に申し上げれば、技術的な取り組みを推進するものの直接事業目標にコミットできていなかった開発チームが、プロダクトマネージャー・プロダクトオーナーの役割を部分的に剥がして目標となる KR に対してコミットできる開発チームへと変わろうとしている話になります。 「納得度の高いプロダクト開発」という言葉自体かなり主語が大きく、人それぞれの主観でとらえ方が変わりそうですので、あくまで 我々の開発チームにとって 、という冠はつけさせてください。 また私個人が所属する開発チームにおける現時点の取り組みに至るまでの変遷ですので、mediba 全プロダクトチームに該当しない点はあらかじめお断りしておきます。 内容が膨れてきてしまったため前編と後編に分けています。この前編についてはだいぶ観念的な部分が多く実践を知りたいという人は後編だけ読まれるのをお勧めします。 前編は組織変遷と組織形態に対する一般的な解釈に並行し、分業・標準化について考え、今ある課題への取り組みについてお話します。そのあとに個人が勝手に抱えたチームの課題感について触れていきます。 後編は前編で明らかとなった開発チームの課題を解決するべく、今チームはどう取り組みどういった変化が起きているか、具体的な事例について話します。 ものづくり再解釈 弊社 VPoE 新井の記事 にもあるとおり、組織構造はここ数年で大きく変わりました。2018 年中期に機能別組織としての開発本部はなくなり縦割りの事業部制組織へ変更されたあと、2020 年にビジネス、テクノロジー、クリエイティブ部門レイヤとなる横串の組織体ができていわゆるマトリクス型組織に変化しています。 テクノロジー部門では、 定期の活動報告会が開催される(オンライン・ニ◯◯コ動◯風なコメント付きで) 、 数年眠っていた勉強会が再開される(クソゲーを大量に作って品評する) 、などマトリクス型となった開発横組織の活動の一部はこのブログでもお伝えしたとおりです。 先日の Agile Tech EXPO での報告にもある シニアマネージャー森實のセッションでも取り組みの一部が垣間見えるのでぜひ御覧ください。 組織体の変遷、一エンジニアからの雑感 ここ数年思いつきで組織が変わっているわけではありません(私の知る限りにおいては)。変遷の中でそれぞれの組織体で求められたこと、その変遷をざっくり振り返ってみます。 組織体種別/特徴 分業のスタイル アウトプット 標準化対象 機能別組織 直列的 統合的 スループットコントロール(プロセス) 事業部制組織 並列的 加算的 アウトプットコントロール 機能別組織 事業部制組織 技術基盤が固まった、機能別組織 機能別組織では各作業工程が直列でチェインしており、ビジネスや企画やディレクション、デザインそして開発から品質管理といった工程の分割が行われます。直列分業により前工程のアウトプットが次工程のインプットとなり、前工程に依存したコンテキストを持つ、リレー的協働が多くなるでしょう。全工程のアウトプットが最終的な成果として統合され、組織の経営成果となるのが特徴です。 機能別組織を成す目的は予測された目標をクリアするためです。 作るものがはっきりしている場合に有効で、最終的な成果統合のために、機能別組織であった開発本部でも予測可能な目標設定を持つことになります。 たとえば事業スキームの形成とそのシステム化が目的であった場合、予測されたゴールはシステムのリリースになるでしょう。リリース後一定の経済効果を生み出すために安定したシステムを提供することも、この組織体の目標になるかもしれません。 直列した工程の最終的なアウトプットであるシステムリリースや安定した運用を目標として、開発組織はものを作るためのプロセス、マニュアル、そしてインフラを整備し標準化してきました。具体的に言えば全社通してのインフラ AWS 移管、それらを利用するにあたっての知見集約やマニュアル化、技術的プラクティスの伝播などが標準化にあたるでしょう。これらは作業工程のプロセスコントロールであり、標準化の対象はスループットになります。 当時の CTO を中心に、機能別組織の中で技術的躍進や文化形成などに十分投資できたおかげで、AWS をはじめとしたクラウドを当然のように利用できているという現状があります。 作り物のゴールや成すべき作業目標が明確かつ予測可能である一方で、機能追加やリリースだけを前提としているため、作ったあとの価値提供や事業成長について考える観点がどうしても抜けてしまいます。ものを作ること自体・作る数を達成目標にするということは、プロダクトマネジメントでも「ビルドトラップ」として言及されていますね(機能別組織だから頻出するわけではありません、事業部制でも・だからこそ生じる問題ではあります)。 書籍: O'Reilly Japan - プロダクトマネジメント 参考: KPIが生むビルドトラップ|Aki|note 成長のための事業部制組織 予測可能な目標ではなく変化する市場やニーズをかんがみて、ユーザー体験・価値提供とビジネス価値のバランスに重点を置き、mediba は CXO を中心としてユーザー体験にフォーカスしていくと同時に事業部制組織へと組織体を変えていきます。 機能別組織と違い異なる職能がひとつのチームを形成し、その事業部ごとの複数のプロダクトチームは並列で開発しプロダクトを成長させます。 事業部制組織は複数チームがそれぞれ並行してアウトプットし、それらを加算したものが経営成果となります。分割された経営資源の合算が最終目的ですので、数値目標のようなアウトプットコントロールによる標準化が特徴で、プロセスをコントロールしない(できない)のが機能別組織との違いです。 事業部制組織の目的・狙いは絶え間なく変化する外的環境の不確実性へ立ち向かうため、事業・プロダクトチームはものを作る方法を委ねられたのだと理解しています。 それぞれの事業において、不確実性が高く例外となる事象が発生しやすいケースでは、専門性をもったチームでの組織学習によって得られた思考能力・応用力・判断力に任せて対応を進めるほうが効率的です。事業やビジネススキームに合う形でワークフローは整備され、求められるシステムもそれぞれの事業にあった形で成熟していくことになるでしょう。 事業部制組織がうまくいくケースでは、事業ごと組織内での市場的競争により健全な競争意識が醸成され、事業・プロダクトチームの達成感やエンゲージメントを高めるだけでなく組織と個人の成長を望めそうではあります。 書籍 から引用しますが、書けば書くほど甘美な組織体ですね。 “各チームを並列化、競争を生むことで組織内部の切磋琢磨を望むことができる” “アウトプット・コントロールはプロセス・コントロールよりも不確実性に対して頑健” “管理する側に負担がかからない” 課題に対する現状のマトリクス組織 しかしどの組織も一定の課題を抱えるように mediba の開発組織も例外ではありませんでした。 事業部制により、技術指針や設計レビュー、Ops を含んだシステム保守のあり方など、機能別組織の中で標準化されていた・されようとしていたことはプロダクト開発チームに閉じていくようになりました。いわゆるサイロ化というやつです。 サイロ化が悪いわけではありません。機能別組織が敷いた旧来の標準化されたプロセスにブロックされチャレンジできなかった領域へ取り組める、事業とのバランスを考え余剰を活かして新しい技術要素を広げていける、などのメリットもあります。 ただしその一方で、ほかの事業部の開発には無関心といった状況もないとは限りませんし、マネージャーからすればガバナンスの効力範囲が小さいうえ観察できることも散漫になるでしょう。 そういった課題解決にマトリクス型組織が求められるのは当然で、その一環として前段で申し上げた取り組みがあるというわけです。まだ始まったばかりの組織体ですのでテクノロジー部門では課題への取り組みを一層強化していくでしょう。 一エンジニアの課題感 事業部制になってからチームが持つ目標に対してダイレクトなコミットができない(と思い込んでいた)というのが自分の大きな課題となっていました。 機能別組織であれば開発組織自身が掲げる目標を意識しながら個人の技術的な取り組みや日々の活動のテーマを昇華させやすいと感じていましたが、 事業部制となると自分ができることは何なんだと立ち止まるようになったのです。 こう感じている開発者は私だけではなかったはずです。 プロダクトチームの開発者として取り組めることを苦悩しながらさまざまな実践をしてきました。 モブプログラミングの実践により属人化を排除する リリースの回数を増やしていく(ためのプロセスを構築する) 運用コストを下げるために X を導入する、Y からリプレイスする パフォーマンスを計測しメトリクスから異常値を検知する チームで合意しブレないものづくりに取り組む 以上はいずれも取り組みとしてポジティブに見えます。ただ事業部制といった組織軸においては取るに足らない改善業務のように感じてしまうことが多くなってきたのです。 もちろん実際には取るに足らない改善業務ではけっしてありません、技術組織が強くあるためには必要な取り組みです。 ※ 上記のような技術的な取り組みを評価すべくテクノロジー部門ではどういった評価が適切かといった議論や実評価も試験的に進んでおり、組織的な課題解決に取り組んでいる最中です。 開発者の中にある不確実性 より良いプロダクトチームの組成といった部分においては現マネージャーと協働しながら良いメンバーに恵まれチーム自身も成長しているものの、さてプロダクトに対してどうかと言われると自信がまったくありません。 チームのデータアナリストにはプロダクトが追うべき数値を可視化してもらっていましたが、伸び悩む数字をデイリーイベントで見ては無力感を日々感じ、事業に対してコミットできたかどうかの手応えのない状態がしばらく続きました。若干それに麻痺しかけていたことも危機感の 1 つです。 そうなると人間はどうしても環境や外部に要因を見出そうとして、市場やユーザー、プロダクトの特性に不確実性を探してしまいます。 しかし、不確実性は市場や外的環境の変わりやすさだけではなく、人間の推測する能力が低いことも影響します。それと同時に能力の低さの根本には、無知・無関心があるのではないでしょうか。 プロダクトについての理解不足からコミットできる領域を自分自身が狭くしている可能性について考える必要があるでしょう。 前編はここまでです。 開発者としてプロダクトへダイレクトに関与できなさというのは、自らの能力の低さと関心の無さにある起因するのではと思うにいたり、今どうやってプロダクト開発に取り組んでいるか・納得をえるためにどうしているかといったことを 後編 で書いていきます。
アバター