検索APIは汎用的なら良いってわけじゃない
伝えたいこと
検索APIは汎用的なら良いってことではない。
汎用的であればあるほど使う側が頑張らないといけない。
要約
- 案件マイクロサービスに汎用的な案件検索機能を作った
- 様々な参照クエリに応えられる柔軟性を獲得したが、使いやすさを失った
- 組織設計を含んだマイクロサービス化をしないと良いAPIにならない
前提
レバテックには案件という概念があります。
レバテックフリーランスなどのオウンドメディアやレバテックプラットフォーム[1]といった複数のサービスから参照されます。
そんな案件を共通リソースとして使えるよう、案件マイクロサービスが作成されました。[2]
どんなユースケースも叶える汎用的な検索エンドポイントを作った
検索にはElasticsearchを利用しました。[3]
どんな条件でも検索できるように、Elasticsearchのクエリで実現できることはほとんどそのままリクエストできるようなインターフェースを設計しました。
具体的なインターフェース
ここは今回書く内容の本質ではないので参考までに。
gRPCなので、protoファイルでインターフェースが定義されています。
SearchProjectRequest
が検索エンドポイントを表す型です。
Condition
という条件を表す型が肝で、Condition
の中にCondition
が定義されている入れ子構造になっています。
SQLのサブクエリなんかもそうですが、Elasticsearchも条件の入れ子が存在するので、これを実現する為にそうなっています。
で、詳細は省くのですが、上記のような形で受け取ったリクエストをElasticsearchに渡す為、内部ではCompositeパターンとVisitorパターンを活用してパースしています。(若気の至りで理解容易性が低い。良くない。)
様々な参照クエリに応えられる柔軟性を獲得したが、使いやすさを失った
案件検索エンドポイントとして公開された後、案件マイクロサービスを利用するサービスも増え検索条件も多様になりましたが、柔軟に応えることが出来ました。
一方で、案件マイクロサービスにリクエストするクエリの構築には苦労することになりました。
リクエスト構造の複雑さも原因の一つですが、各サービスで「どのような状態の案件であればウチのサービスで表示して良いのか」を判断し検索条件に含める必要があり、使いにくさに拍車をかけました。
なぜ、汎用的に作ったのか?
「マイクロサービスはクライアントのことを知る必要がない」という教えがあります。[4]
使う側のことを意識しない、ということですね。
これは、マイクロサービスの条件である「独立してデプロイ可能」を実現するためだと捉えています。
マイクロサービスがクライアントのことを知っているとどうなるでしょうか。
例えば、案件マイクロサービスに「レバテックフリーランスで表示して良い案件だけを返すエンドポイント」があったとします。
これは案件マイクロサービスがレバテックフリーランスで表示する条件を知っている状態であり、内部で良い感じにフィルタしてレスポンスしてくれます。
レバテックフリーランスの開発者はそのエンドポイントを呼び出せば良いだけなので楽です。複雑な条件を指定する必要はありません。
一方で、レバテックフリーランスに案件を表示する条件が変わったとしましょう。
この時、条件の変更は案件マイクロサービスに対して行う必要が出てきます。するとレバテックフリーランスチームは案件マイクロサービスチームに依頼することになります。
この状態は独立してデプロイが出来ないというほどではありませんが、依存度が上がっていると言えます。
このような状態を避け、独立してデプロイ出来るよう汎用的に作ったわけです。
今思う、「どうすればよかったか」
マイクロサービスとして独立してデプロイ可能で、チームを跨いだ依存が無い。
なお且つ使いやすいAPIはどうすれば作れたのでしょうか。
根本原因は、案件マイクロサービスチームが案件のオーナーシップを持っておらず、クライアントのチームから変更依頼を受ける形になっているからだと考えています。
案件マイクロサービスチームに案件ドメインエキスパートが存在し、各サービスで表示する案件の条件を決められるのであれば、先の例に挙げたようなチーム間の依頼は発生しません。[5]
まとめ
良かれと思って汎用的にAPIを作ったけど、利用者からすると使いづらいものになったよ。
使いやすいAPIを作りつつ、マイクロサービスとして独立させるには組織体制も関わってくるよ。
というお話でした。
今となっては、マイクロサービスを作るならそれを運用する組織も設計しないといけないことは皆様ご承知の通りですね。
Discussion