30代を目の前に、エンジニアのキャリアパスを再考

これなに

次の年度が20代最後の年度となるので経験の棚卸しとこれからエンジニアとして大事にしていきたいこと・やりたいことなど文字起こししておく。

...

もう20代終わりなの?早くない?

何をしてきたか、大事にしてきたか

2018年に新卒としてマネーフォワードに入社し、約3年間B2B事業のマネーフォワード クラウドの開発に携わりました。マネーフォワード クラウドは20前後のサービスを提供していて、自分はそういった環境で生まれる横断的な技術課題を解決するためにアプリケーションレイヤーの共通基盤の開発に取り組んでいました。一部具体例をあげるとマネジメントとして採用やチーム組成、PdMとしてプロダクトを0から作ったり、PjMとして複数チームとのプロジェクトの推進、エンジニアとしては共有データベースパターンのアーキテクチャーをマイクロサービスパターンに移行させるために必要な基盤の開発運用や移行計画の作成・一部実行、SRE文化の導入としてObservabilityやレジリエンスの向上・DevOpsなど色んな活動の推進に関わらせてもらい、入社時の「共通基盤の開発をしたい」という自分の希望を大きく叶えてもらいました。

その後の2021年春にグループ会社のマネーフォワードケッサイに出向し、マルチクラウド・NoOpsなどマネーフォワードとは違った技術や文化環境でデータ領域のエンジニアとして新規事業やデータ基盤、パイプライン開発に関わり、21年末にチームの立て直しをミッションとしてデータ領域のEMになりました。データ領域は技術に強いメンバーに恵まれてるのでHowに関する意思決定は任せ、優先順位の洗い出し・決定やPJ管理などといったWhy/Whatに関することやドキュメント文化の醸成などコミュニケーション設計の改善を行い、生産性と満足度の向上に取り組んでいました。またプロダクトビジョンの策定や開発組織全体の課題抽出、サービスのクローズなどデータ領域に閉じない活動もさせてもらってました。

様々な課題に異なる役割や権限で向きあってきましたが自分の中で大事にしてきたのは課題に対してやるべきことをやる(経験のない領域だったとしても)・ある状態の発生原因・課題を定義or特定して人を巻き込み解決できる状態を作る、ということでした。

同じグループの会社ではあるけど、マネーフォワード・マネーフォワードケッサイという異なる会社で同じことを大事にして行動したこと、そして結果が出たことは大事にしてきたことが間違っていなかったという証明だと思っているし、自信にも繋がりました(もちろんやりきれたこと、途中で止まってしまったもの、うまくいかなかったものなど色んなものがありますが)。

次にやりたいこと

4年間こういう動き方をした結果、ありがたいことに今後のキャリアとして選べる方向性が多い状態になっていることに気づきました(PdM, EM, Full stack, Platform Engineer, SRE etc)。そしてどれを取るか決めるために次の数年を何に投資したいか、自分のエンジニアリングテーマはなにか、を先に定める必要性を感じました。

結果的に Developer Productivity Engineering (通称DPE。Productivity Engineering/Developer Productivityなどとも言われていたりする) という自動化技術やツール、文化面のアプローチから開発者の開発体験を改善し、ソフトウェア開発チームの生産性を向上させ、その結果プロダクト価値が高まっていく、というアプローチを次のテーマにしてみようと思っています。自分の馬力でプロダクトを作りユーザーに直接価値を届けるよりも、プロダクトを作るチームやプロセスが生み出す馬力を高めることにフォーカスすることで結果的にユーザーへの価値提供に貢献する、みたいなイメージです。

DPEは3, 4年ほど前からアメリカの有名なTech企業で取り組みが始まっているテーマで日本だとメルカリさんやサイバーエージェントさんなども取り組まれているようです。DPEをざっくり理解するにはこれがちょうど良いです: https://www.linkedin.com/pulse/developer-productivity-engineering-next-big-thing-software-caccamo/

このテーマにしたのは次の数年を投資していくならワクワクすること、努力と思わず続けられる範囲が大きいことにしたく、それをこの4年間の中で特に嬉しかったこと・ワクワクしたことをベースに考えてみた結果でした。

4年間の中で特に嬉しかったことを思い出してみると、開発体験上の自分のペインを解決した時にそれが一緒に働く開発メンバーの役にも立ったり、チームのアウトプットが上がったと感じた瞬間でした。ユーザーの課題が解決されたという反響や売上が上がったということよりも自分自身と自分に近い人が喜んでいたりワクワクしている状態が実現されていること、その状態に対して自分が何かしらテクノロジーや仕組みで貢献してることが僕にとって大きく重要なものだと気づきました。これに該当する過去の自分の取り組みを抽象化すると数年前に流行ったDX(Developer Experienceの方!)がキーワードになりそうと思い、そこから色々調べていった時にDPEにたどり着いて、しっくり来る感覚を覚えました。

テスラがプロダクトである自動車を製造するプロセスに強くこだわりを持ち最適化を続けていることは有名ですが、彼らと同じようにソフトウェアを提供する会社でソフトウェア開発のプロセスにこだわりをもってみることにチャレンジしてみたいなと思ってます。

DPEについてだらだら

キャッチアップを初めたばかりなので間違ったことを言ってる部分がありそうですが、DPEは継続的に長く続けていくことが一番効果を発揮するのではないかなと思っています。もちろん規模が小さければDPE専門の人は必要なケースは少なく、アプリケーションエンジニアでそういう動きもする人がいる、ぐらいが適切かもしれませんが。一方事業が複数あるとかコンテキストが複雑・多いなどが発生するフェーズに入ってくるとDPEを専門にやっていく人が必要になってくるのかなと感じています。またDPEという範囲をDeveloperに絞らず、会社全体にフォーカスを当てたProductivity Engineeringという観点も重要かなと思っています。

このあたりは今後継続的にインプットアウトプットしていくつもりです

最後まで読んでいただいた方、途中まで読んでいただいた方、ありがとうございました!

アジャイルとは何であり、何でないか

この記事に書くこと・書かないこと

書くこと:アジャイルとはなんなのかをわかるようにする

書かないこと:なぜアジャイルを使うのか、アジャイルの具体的なプラクティスなど

アジャイルとは何であり、何でないか

Principles behind the Agile Manifestoの第1項にあるように

顧客が満足する価値あるソフトウェアを「素早く」・「継続的に」デリバリーする

ために必要な手法やフレームワークをまとめた「概念」という理解をしている。

概念であり具体的な方法を示すものではないため、アジャイルだからスプリントやカンバンをしなければいけないということはない(決められたHowは存在しない)。

スクラムエクストリームプログラミングなどいくつかのフレームワーク・アプローチが提供されているが、何を使うかは我々に選ぶ責任があり、Spotifyのように自分たちの経験を体系化しSpotify Modelという新しいアプローチを構築した組織もある。

自由度が高いため使い手の思考と行動による継続的な改善を必要とする難易度の高い概念だと思う。

アジャイルの実現のために大切なこと

この記事では最後にアジャイルの目的である

顧客が満足する価値あるソフトウェアを「素早く」・「継続的に」デリバリーする

を実現するために大切だと思っている「自己組織化されたチーム」について紹介したい。

最良のアーキテクチャ・要求・設計は,自己組織的なチームから生み出されるというアジャイルマニフェストの第11項から持ってきている概念

自己組織化されたチームとは

自己組織化されたチームは与えられた目的を達成するために必要なコト(ex: 開発プロセスアーキテクチャ、技術要件など)を自ら見つけ出し、行動し、目的の達成を目指す文化が根付いた組織のことを指していると思っています。このチームは自分たちで決めたコトを実行し、それらを振り返って改善点を見つけることでコトを最適化し続け、外部環境の変化に対応していくサイクルを作り出していくことが期待されています。この文脈だと目的は与えられるものであり与えるのは経営やマネジメントレイヤーなのかなと思いますが、組織によってこの形も異なりそうと感じました。

自己組織化されたチームを作ることは簡単ではなく、例えば外部からのコーチングが必要だったりこともありますし、自己組織化が実現してもそれが永遠にキープされるわけではないので文化の醸成と維持・アップデートが求められると思いました。

※ 参考:自己組織化されたアジャイルチームを確立する

タスク管理ツールを揃える

会社で使うタスク管理ツールを揃えておくことが重要だなーと感じている。チームや部署ごとに違うものを導入していると例えば

  • そもそもタスクが見れない(アカウントがない or inviteされてない)
  • タスクについて質問が気軽にできない
  • 依頼したタスクの進捗や状態がわからない

みたいなことが起こる。

タスク管理ツールの殆どは組織にinviteしてもらわないとタスクが見れない仕様なのでSlackやGitHubに貼ってあるリンクを踏んでも見れないことが多い。招待をお願いしてから完了するまで待ち時間は発生するし、招待作業は誰かがやる必要がある、招待できる権限を絞ったりすることもあって複雑。別チームのタスク管理ツールに招待してもらった時に1回ログインした後ずっとログインしてなくて数ヶ月後に棚卸ししていい?って聞かれたこともあった(不要な課金)。

気軽に質問できない・頼んだタスクの進捗・状態がわからないなんてこともめちゃくちゃあって、前者はSlackがあるからなんとかなるけど、大きい組織や頻繁に構造が変わる場合はどのチャンネルで誰に聞いたらいいかわからないなんてことも起こると思う。進捗・状態がわからないとSlackであのタスクってどうなってます?って質問する必要も出てくるけど、3つ以上のチームと共同で何かを進める時にそれぞれのチームが使ってるタスク管理ツールを見ないといけないことがあって、これはけっこう大変だった。

今所属してる会社ではタスク管理ツールとしてAsanaが全社導入されている。今はチームトポロジで言うプラットフォームチーム色が強いところで仕事をしているので、色んなチームから依頼や質問を受けたり依頼することが多いが、セールスやCSがユーザーさんとどういうステータスでやり取りしているかが一発でわかるのがすごい体験だなと思っている。

それを味わってしまったのでタスク管理ツールはある程度大きな単位(日常業務で関わりが生まれる部との間くらい)で統一してあったほうがいいと思っている。

個別最適目線に立つと1つのチームや要望を満たしてくれるエッジの聞いたサービス使えるって見方もできてそれは正解なのだけど、広い遠い視点で運用目線を持った時に辛さを生まないようにツールを選んでいきたい。

. . . . . . .

だいぶ久しぶりのブログで緊張した

Rails6 + Springな開発環境で環境変数をたくさん設定したらrails consoleがハングしたw

以下の環境でrails consoleを使おうと思ったらハングしました。

  • Mac OS X 10.14.6
  • Ruby 2.7
  • Rails.envは development
  • Rails 6.0.3.1 or 6.0.3.2
    • Rails 5以下のバージョンではこの現象は起こらないのは確認済
    • Rails 6.0.0 ~ 6.0.3で再現するかは試してない
  • Spring 2.0.1(Springのバージョンは関係ない気がするが念の為)

原因はSpringの内部処理でsocketに環境変数を書き込む下記の処理の際に

# https://github.com/rails/spring/blob/v2.0.1/lib/spring/client/run.rb#L144
def run_command(client, application)
  log "sending command"

  application.send_io STDOUT
  application.send_io STDERR
  application.send_io STDIN

  send_json application, "args" => args, "env" => ENV.to_hash

  ...
ensure
  application.close
end

# https://github.com/rails/spring/blob/v2.0.1/lib/spring/client/run.rb#L206
def send_json(socket, data)
  data = JSON.dump(data)

  socket.puts  data.bytesize
  # 環境変数を書き込む処理
  # dataには `JSON.dump(ENV.to_hash)` が入っている
  socket.write data
end

書き込む環境変数のバイト数が大体128.4KBを超えると書き込みバッファが溢れてるっぽく、ここでハングしてしまってました。環境変数の容量を128.3KB以下に減らしたところハングせずに書き込めたため環境変数の容量が悪いと判断し、使っていなかった変数を消す作業を行い事なきを得ました。

溢れてるっぽく と書いたのは socket.write data 以降の処理にbinding.pryのstepで入れなかったためです。socket.writeの実態はIO#writeで、このメソッドはCで実装されているためこれ以上pryでデバッグできないんじゃないかと思ったのが諦めた背景です(ruby/rubyのコードもデバッグできるようになりたい...)。

後々社内の別の開発者がVM上のubuntu環境で同じ環境変数を用意してrails consoleを実行したところErrno::E2BIGが出ていました。E2BIGはLinuxによって定義されたシステムエラーの1つで、単一の引数や環境変数の文字列が128KBより大きい場合に返されるエラーなので、やはり環境変数の容量の問題だったねーということになりました。

マイクロサービスの分割境界線

これ何

マイクロサービスの分割点の通説をまとめてみようと思う。

通例

How to Partition Your Data Between Microservices

2つの競合するルールが存在する

  1. データの所有権
    • データの更新、保持に責任を持つこと
  2. ローカリティ
    • マイクロサービスが必要とするデータは多くのサービスの近くにあるべき。可能ならサービス自体に含まれていること

※この2つは対立する場合があり、両方を満たすにはデータを複数の場所に保存しなれけばならなくなる。それはOKだが、重要なのはそれら複数のストレージの内、1つのストレージだけが権威あるソースとして全体が成り立っていることだ。

データの所有権

マイクロサービスの責務を決める第一の要因はサービスが持つビジネス能力(Business Capabilities)となるべき。ビジネス能力に属するものはすべてマイクロサービスに実装されるべき(ここにはデータの保存も含まれる)。特定のデータをどのマイクロサービスが担当しているかを決めるには、どのビジネスプロセスがそのデータを最新の状態に保つのかを把握することが必要になる。

ローカリティ

ローカリティは各データの所有権の次に重要。

サービスが自分のDBに問い合わせのと別のサービスに問い合わせるのでは、速度と信頼性において大きな違いがある(自分のDBに問い合わせる方が速度・信頼性共に高くなる)。マイクロサービス間では各サービスが保持しているデータを参照する必要が出てくるが、1つのサービスがダウンするとそれに伴って別のサービスがダウンしたり、速度低下が起こる等の問題が起こる。この結合状態を緩めるには受け取り側はデータをキャッシュすること、データの所有者はキャッシュヘッダなどを使いデータのライフサイクルを管理すること(キャッシュ扱うのは受け取り側なので、結局受け取り側の責務な気もするが)。

また別の方法としてデータのミラーを持っておくことも考えられる。他のサービスが所有しているデータを参照するためにそのサービスへのクエリ(API call etc)ではなく、そのサービスが所有しているデータを読み取り専用として自分のDBに保持し、他サービスへのクエリを自分のDBへのクエリに置き換えることだ。この場合、データを所有しているサービスから取得したデータをコピーとして格納し、必要に応じ権威データを元に更新することが求められる。書き込み時の複雑性が増すが、読み取り時の複雑性は下がる。このような構成はイベントモデルに基づいていることが多い。

martinfowler.comのMicroservices

マイクロサービスアーキテクチャを9個の特性に分解したドキュメント。その中から分割点に関わる部分だけ抜粋する

Organized around Business Capabilities

テクノロジーに焦点を当てサービスを分割すると単純な変更でも時間を必要とすることになってしまう。マイクロサービスの分割アプローチはビジネス能力(Business Capabilities)を中心に組織化されていく。

Decentralized Data Management

データ管理の分散化を行うとUserモデルがサービスAとBで違う呼ばれ方をしたり、表示される属性が異なったりすることが起こる。この問題への有用な考え方としてDDDのバウンデッドコンテキストがある。DDDでは、複雑なドメインを複数のバウンデッドコンテキストに分割し、それらの間の関係をマッピングします。このプロセスはモノリシック・アーキテクチャとマイクロサービス・アーキテクチャの両方に有効ですが、サービスとコンテキストの境界には自然な相関関係があり、それが明確化を助け、ビジネス能力(Business Capabilities)のセクションで説明するように、分離を強化します。

ドメイン分析を使用したマイクロサービスのモデル化

Microservices should be designed around business capabilities, not horizontal layers such as data access or messaging.(マイクロサービスは、データ アクセスやメッセージングなどの水平レイヤーではなく、ビジネス機能に基づいて設計する必要があります)

マイクロサービスには疎結合と機能の高い凝集度が必要。個々のサービスの境界定義がうまくできないと、サービス間の非表示の依存関係、密結合、不完全なデザインのインターフェイスなどの好ましくない特徴を持った設計になる可能性がある。

他のサービスを同時に更新せずにサービスを変更できる場合、マイクロサービス同士はゆるやかに結び付いています。

ここまでをまとめる

Business capabilitiesに沿ってサービスを分割し、チームを配置することが取るべきアプローチとして紹介されている。

Business capabilitiesをどう定義するか

マーティンファウラーの記事でも記述があったが、マイクロソフトからもDDDを使ってバウンデッドコンテキストを作成するのがオススメされていた。この記事では、例に挙げられたサービスをDDDを使ってどのように分割し認識するか、までがチュートリアルぽくまとめられていたので、これを見ると良さそう。

その他参考資料

FastFedのドラフト仕様を読んでみた

OpenID FoundationのFast Federation (FastFed) WGからFastFedというSSOやSCIMプロビジョニングをより簡単に扱うための標準仕様のドラフトが出たので読んでみました。大まかな内容はAbstractとIntroductionで掴むことができるのでその部分だけ日本語訳にしたので興味を持ってもらえると嬉しいです。

FastFed Core 1.0 draft02

Abstract

FastFedの目的はIdPとホストされたアプリケーション間のIDフェデレーションを設定する管理作業を簡素化することです。

メタデータドキュメント、API、フローを定義することで、管理者が素早く(OpenID Connect, SAML, SCIMをサポートする)IdPとホストされたアプリケーションを連携すること、また接続設定の変更をIdPとホストされたアプリケーション間で直接、かつ継続的に行えるようにします。

1. Introduction

IDフェデレーションに関してはいくつかの標準仕様が存在するにも関わらず、設定やそれの維持することは依然として難しく、管理者にとっては以下の3つの理由からIDフェデレーション技術を正しく扱うことはコストが高いものであると感じられます。

  1. 既存の標準仕様を一つ一つ見ると例えばOpenID Connectは認証、SCIMスキーマ定義というように、IDフェデレーションの一部のみしか解決せず、またIDフェデレーションの全体像を組み立てるための推奨仕様がありません。そのため各アプリケーションは標準仕様を様々な方法で組み立てていたり、標準仕様を守っていないこともあり、管理者はホストされたアプリケーションでフェデレーションの設定を行う前に仕様の調査とその理解を行う必要があります。
  2. 標準仕様で必要とされているメタデータをIdPとアプリケーションが直接交換するための方法がないため、管理者がコピーアンドペーストによってメタデータを入力する必要があります。コピーアンドペーストを前提にした手順はデバックが困難なエラーを引き起こす原因となります。
  3. 例えば証明書が期限切れになったときなど、フェデレーション設定が古くなった時にフェデレーションが動かなくなることがあります。これはIdPとアプリケーションが設定に関して直接通信する方法がないためです。そのため管理者は定期的に設定の更新・見直しを行わなければなりません。

FastFedの目的は管理者のエクスペリエンス向上であり、これをWebベースのワークフローを数回クリックするだけで、新しいフェデレーション関係をインスタンス化することができるUXによって実現しようとしています(この時管理者がFastFedやその基となる技術を理解している必要はありません)。

FastFedでは追加のメタデータドキュメント、API、フローを定義し、管理者が共通のID標準仕様をサポートする2つのプロバイダーを接続できるようにします。さらに、FastFedは相互運用性プロファイルを定義しており、プロバイダがFastFedに互換性があることを示すために実装されなければならない既存の標準のサブセットを記述しています。

FastFed CoreはFastFedのコアメタデータフォーマットとハンドシェイクフローを定義し、SAMLSCIMプロビジョニングに関する追加のプロファイルはFastFed Coreを拡張します。

Rubyで定数をfreezeしても高速化はしない

定数をfrozenにしておくと高速化するという記事が公開されていました。 techracho.bpsinc.jp

定数をfreezeしておけばメモリ使用量も少し節約できるので完璧です。

記事には上記のようにあったので、圧縮などを行い確保量を少なくしているのかなと思ったのですが、

[1] pry(main)> ObjectSpace.memsize_of([1, 2, 3])
=> 40
[2] pry(main)> ARRAY = [1, 2, 3].freeze
=> [1, 2, 3]
[3] pry(main)> ObjectSpace.memsize_of(ARRAY)
=> 40

ObjectSpaceで検証してみるとメモリサイズは同じだと言われました🤔 freezeするとメモリアロケーションが削減される というのはどういう意味なのか社内のslackで聞いたみたところ

Rubyコミッターの卜部さんから下記の回答をいただきました。 f:id:kamillle:20200224212919p:plain

ObjectSpaceで検証した通り、freezeではメモリアロケーションは削減できないようですね。 ちなみに写真で紹介されているfreezeでメモリ圧縮する機能はまだ本線にはマージされていないようです。中々メモリアロケーションまで気にしてコードを書くことは少ないですが、早くマージされるといいですね!

github.com

また

frozen_string_literal: true は効果があります(生まれた時からfrozenなら適用できる最適化の箇所がやや広がります)

と書かれいているのは、同じ文字列であればすべて同じobject_idを返すようにして都度都度オブジェクトを生成しないからみたいです。

# frozen_string_literal: true

hoge = 'aaa'
fuga = 'aaa'

# 全部同じobject_id
puts 'aaa'.object_id # => 70240713691900
puts hoge.object_id  # => 70240713691900
puts fuga.object_id  # => 70240713691900