大量のサービスをささえる社内ライブラリ開発で学んだこと

こんにちは、横断アーキテクトG所属の朏島です。 横断アーキテクトGの仕事は色々ありますが、私は主に社内の共通ライブラリ開発に携わっております。

今日は地味な話ですが、共通ライブラリ開発で学んだこと・気をつけていることについて書いてみたいと思います。

なぜ共通ライブラリが必要か

そもそも、なぜ共通ライブラリが必要になるのか?それは、シンプルに社内にたくさんサービスがあるからです。 (現在リクルートライフスタイルにはサービスが30個もあり、更にここからサービス横断機能やマイクロサービスなど内部的に別れているものもあります!)

ボイラープレートや本質的ではないが煩雑な処理などを共通ライブラリで巻き取ることにより、各開発者が自身のサービス開発に集中できることを目指しています。

ライブラリの価値

サービスでとある機能の実装が必要になった場合、それを共通ライブラリで開発するかどうかは「汎用的な機能か」「実装難易度が高いか」の2点で判断しています。

簡単な図で表すと共通ライブラリでの開発対象は下のようになります。 対象範囲

このような領域でライブラリでの機能提供をしている理由は下記になります。

対象機能/技術の汎用性が高い場合 サービス横断での開発工数削減を図る
  • パブリックではない社内ルールを担保する
  • サービス間での車輪の再発明を避ける
  • 新規サービス・機能の立ち上げにかかるコストを下げる
対象機能/技術の実装難易度が高い場合 高難易度実装部分の品質の担保
  • 実装難度の高い部分をサービス側で作りこまずに、共通部品として提供することで品質を担保する

また、これらをおこなうことで、間接的に設計・実装指針の提示にもつながっています。

これらを実現するための前提として、

  1. 同技術(本ライブラリで言うとJavaやSpring Frameworkなど)を用いたサービスが複数あること
  2. ライブラリ開発チームに対象技術に関する知見が集約しやすい、ということ

の2点があります。

私達のチームがこれらの前提を満たしているかというと、
1は先述の通りです。2についても、鶏と卵のような関係ではありますが、ライブラリ開発を通じて常に対象技術を深く触っているため、プロダクト開発側より自然と知見が多く集まってきていると感じます。

また、弊社のシニアアーキテクトが設計から実装までレビューをするという体制をとることで品質の担保もなされています。

ライブラリ開発チームの組織としての価値

また、サービスから独立した共通ライブラリを開発するチームの組織的価値もあるのではと考えています。

  • 関連情報の集約
    • Spring Frameworkなどの共通ライブラリに関連する情報が集約されるため、ハブとなって上手く発信することで情報が整理され共有資産となる
  • サービスの開発へスポットで遊軍的に参戦することが可能となる。
    • 特定のサービス開発に属していないので、スケジュールとかに引っ張られなかったり、流動的な人員として比較的フレキシブルに動ける
    • 純技術に関する部分だけでなく、プロダクト特性や案件把握などは必要となるためとても大変ではある
  • 技術力の高いエンジニアに囲まれて(少数精鋭で)仕事ができる環境のため、若手エンジニアの修行の場となる
    • まさに私自身が絶賛修行中

実際にどんなものを作っているのか

ライブラリ開発についてつらつらとお話しさせていただきましたが、実際に最近開発したものを少し紹介させていただきます。

1つ目は、VariablesControllerというControllerです。これはSpringのControllerとして提供されており、Componentとして読み込まれると、あらかじめ定義されたパスで

  • DBコネクションの状態
  • 環境変数
  • 対象のwarや依存ライブラリのビルドバージョン
  • 死活監視結果

といった様々な情報が閲覧できるようになります。

SpringのDI機構を利用することで、サービス側での対応は不要のまま利用することができるようになっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
@RequestMapping("/some/path")
public final class VariablesController {
    @Autowired
    private VariableService service;
    @RequestMapping(value = "", produces = MediaType.TEXT_PLAIN_VALUE, method = RequestMethod.GET)
    @ResponseBody
    protected String getVariables() {
        final String content = service.getSomeData();
        return new ResponseEntity<>(content, HttpStatus.OK);
    }
    // ... and more
}

「共通部分をラップして工数削減を図る」というと、どうしても受け身な印象を受けますが、このように便利な機能の開発なども積極的におこなっています。

2つ目の例としては、OGNLのキャッシュ機構のウォームアップなどがあります。 これは、MyBatis(O/R Mapper)が内包しているOGNLキャッシュ機構でレースコンディションが発生するケースがあるというバグに対応した機能となります。

実際に、ごく稀にマルチスレッド処理をしている時にorg.apache.ibatis.ognl.NoSuchPropertyExceptionが発生したため、検知に至りました。

MyBatis本家ではこのバグについては既に検知されているのですが、OGNL側では対応されておらず、OGNLを直接jarファイルで持っているMyBatis側でも対応が進んでいないため、こちら側で対処する方針とし、事前にキャッシュ機構のウォームアップするUtilityクラスを開発しました。

実装自体は込み入っているのでコードをここに載せることはしませんが、クラスパス内の関連クラスを舐めて、OgnlRuntimeのキャッシュへ格納するメソッドを直接叩き事前に全てキャッシュ済みの状態にするというような実装をしています。

それによって下のように書くだけで事前にキャッシュ書き込み処理が走るので、レースコンディションが発生しないようになっています。 (そして1行書き加えているだけなので、本家に修正が取り込まれた場合も簡単に抜くことができます)

1
OgnlWarmer.warmup();

これはまさに、サービスの本質とはあまり関係がないが、原因特定から対応までが煩雑かつ実装難易度も高い分類の機能提供でした。

ライブラリ開発を通じて得られたもの

共通ライブラリ開発を通じて、通常のサービス開発とはまた少し違った力がついたように思います。

優しさ

私たちのチームの直接の顧客は各サービスの開発者なので、使いやすい、運用しやすい、バグを埋め込みにくい、また拡張性の高い実装をするように常に心がけています。

なので、実際に手を動かすよりも思考し設計している時間の方が多いことが殆どです。

また、その先にディレクターなどの案件推進者やサービスの実際の利用者などがいるため、サービス利用者の行動を想像したり、将来の案件を予測して事前に機能提供の準備をすることなどをおこなう必要があります。 これにより大いに想像力が鍛えられました。

このように、相手のことを想像して使いやすいように機能提供することを、私たちのチームでは「優しさ」と呼んでいます。 初期はよく「優しさが足りない」とPRで指摘をもらっていました。

バランス感覚

使いやすい、運用しやすい、バグを埋め込みにくい、また拡張性の高い実装

と書きましたが、この中でも「バグを埋め込みにくい」ことと「拡張性が高い」ことは両立が難しく、どういった実装にするかはバランス感覚が問われます。

ある程度自分の実装に自信とこだわりを持っていないとポジショニングを定められない面もあるので、初期はとても苦労しました。

常に「なぜこういう実装にしたのか」というのを問われ続けたことで、少しずつ実装方針にこだわりを持ちバランス感について議論・主張ができるようになりました。

トラブルシューティング

ライブラリ開発に限りませんが、不具合が起きた時の原因調査から対応までがとても大変で、かつ一番勉強になりました。

共通ライブラリの開発をしていると、先述のものを始めとした広範な相談や火消し依頼が来るので必然的に深く多く経験させてもらうことができました。

共通ライブラリ開発チームからサービス開発へ移ったメンバーもこういった場面でとても活躍していると聞きました。

その他

  • パッケージ・クラス構成
  • バージョニングポリシー
  • テスト
  • 命名規則

などにとても神経を使うようになりました。 言葉にすると当たり前のことばかりですが、やはりライブラリは多くの開発者に使われるので、色々なことに手を抜かず気をつかうようになりました。

チャレンジしたいこと・していること

開発者のエンゲージメントをあげる

最近は、もっと開発者の共通ライブラリに対するエンゲージメントを高めたいと思っています。 ここでいう共通ライブラリへのエンゲージメントとは

  • 共通ライブラリの機能を知ってもらう
  • 共通ライブラリの機能を使ってもらう
  • 共通ライブラリに関する質問を積極的にしてもらう
  • 共通ライブラリへ機能要望を上げてもらう

というようなことを指しています。

共通ライブラリの中でもニッチな機能だと認知されていなかったり、使い方や効果がわからないから使われなかったり、 ということは残念ながら現状発生しています。

こういった事態を少しでも減らすため、実装以外のところでドキュメント整備はもちろん、リリース広報の仕方から勉強会などまで含め考えて発信・巻き込みをしていきたいと考えています。

サービス開発チームと共通ライブラリチームの連携が上手くハマることで、理想の開発体制に近づけると信じているので、今後もチャレンジしていきたいと思っております。

そしてゆくゆくは、知見の共有などを通じて社外へも貢献できたらいいなと考えております。

まとめ

学んだものはどれも社内に限らずライブラリ開発を通じて得られるものかと思いますが、 一言で言うと「いつもより想像力を使ってコードを書く」というのがライブラリ開発で得られる力だと思っています。

どちらかというと裏方作業で、「最新技術を使って爆速でサービスを提供!!」といったような派手さはありませんが、経験する事で得られるものは大きいと思っております。

今後もより良いサービスを届けられるように、共通ライブラリという形で陰ながら支えられるよう精進したいと思います。
文字の多い投稿でしたが、最後まで読んでいただきありがとうございました。