HRMOS Coreの技術選定について

大谷弘喜氏:ということで、製品の説明は以上です。ここからはHRMOS Coreについてどのように技術選定していったのか、簡単にお話ししていきます。

基本方針は、プロジェクトが始まったばっかりだったこともあり、クラウドのマネージドな構成管理で、なるべく自分たちでは手をかけずに作業を進めたかったということ。あとはシンプルな構成、シンプルなアーキテクチャを求めていました。シンプルと言うと聞こえはいいですが、ちょっと古い技術、アーキテクチャで作ろうとしています。

それにはいろいろな理由はありますが、製品を作る上で高度なアーキテクチャ、流行のアーキテクチャ採用するかはそれほど重要な話ではありません。目の前の課題をいかに速やかに解決するかになるべくフォーカスするための選択でした。

初期にいきなり完璧なアーキテクチャで設計できるわけではないのです。そのため、開発のフィードバックをより速め、開発スピードを極限まで上げたかったので、新しめの技術よりもちょっと古めの技術。自分がよくわかっている技術で作り上げることを心がけてます。そこはできる・できないではなく、あえてそうしています。あとは変化に強いこと、ですね。

他には、かつての現場を見ていて思ったことですが、エンジニアの属性を考えずにアーキテクチャを考えると、結局エンジニアがついてこれなくて、システム全体が破綻するという状況を見てきました。ということで、自分たちが抱えているエンジニアがどのような人たちなのか、もしくはこの作業をできる人たちがどのような人たちなのか、ということを考えてアーキテクチャを考えました。

また、初期は依存関係が見えないので、なるべくフラットに、シンプルに作っていくことを心がけました。あと、これはアリエルという会社で感じていたことなんですが、10年以上メンテナンスするためには、いろいろと気を付けなければいけないことがあります。とくにプロジェクトの最初の段階には、今後の10年を左右するものも盛り込まれたりするので、そのあたりはかなり気を付けています。

10年メンテナンスするために、あまり技術トレンドに左右されないということを心がけているのですが、唯一の例外がフロントエンドです。フロントエンドはしょっちゅう変わりすぎるので、ある程度トレンドに追随しなければついていけない部分があります。

そして、枯れた技術から少しずつ構成を変えていけるように、最初はなるべくベタな構成で作っていき、そのあとに少しずつ構成を変えていきたいと思っています。

クラウドの候補と構成

では、今作ろうとしているものがどこで動くのかということで、結局はAWSかGoogle Cloud Platformの2つで検討していました。

プロジェクトの最初の段階で、一旦プロトタイプを作った際に使っていたのがGoogle Cloud Platformです。ちょうどSpannerが出たタイミングだったこともあり、Google App Engine標準版とSpannerでプロトタイプを開発していました。

そのままプロトタイプから製品に進化させていければよかったのですが、当時はSpannerのパフォーマンスがあまりよくありませんでした。最近はわかりませんが、当時はそれほどよくなかったので、やっぱりAWSがいいよね、ということでクラウドの環境はAWSで作っています。

基本的な構成は、ふつうのWebアプリケーションの感じです。

ブラウザがあって、APIを叩く人がいて、その中間にいろいろなデータをフィードするサーバがいて、バックエンドにデータベースがあるという。ふつうの構成ですね。

これらの構成の中で、すべてマネージドなシステムで作るのは最初に決まっていたんですが、サーバをマイクロサービスというかサーバレスにするのか、モノリシックなAppサーバにするのか。データベースはRDBのままでいいのか、KVSにするのかなど。そういったことを考えていました。

人事DBの特性

人事データベースはアクセスはそれほど多くありません。普通の社員が毎日人事データベースを見るかと言うとそんなことはありませんし、人事の人でもそれほど頻繁に見ているわけではありません。

アクセスされる時期には偏りがあって、例えば入社の1週間前、入社者数が多い4月1日、毎月1日などですね。従業員が入社してくる1週間前から急に忙しくなって、その1週間後くらいまで忙しさが続きます。そういった形で、アクセスされる時期はかなり偏りがあります。

データ量についてですが、人事データベースのデータ量は最初は多そうだと思っていたんですが、実は想像するほど多くありませんでした。ただ、データ量自体は多くありませんが、データの種類が豊富です。それに伴って、検索すべきデータも、データの種類が増えるに従って複雑になっていきます。

データ量についてざっくりこのようになっています。

勤怠のデータの場合、1人1日1レコードできるとして、会社に来て退社するまでの時間ということで、それが1年で1人あたり365レコード。休みの日も「休んだ」という状態を持たせるので、1レコードとカウントしています。10年でだいたい3,600レコード、1,000人の会社だと10年で360万レコードです。ということで、300万、400万であればそれほど大きなデータではないということです。

勤怠のデータは1日1日の生のレコードなんですが、実際に分析で使われるデータは1日1日の生のデータではなく、1ヶ月ごとに集約したデータになるので、実際はこの1/10のデータがあれば十分に分析可能になっています。ということで、データ量自体はそれほど大きくならず、メモリ上でそこそこ扱える量かなと思います。

KVSにするか、RDBにするか

そんな状況なので、KVSにするかRDBにするかという点です。

前職で少しKVSを使ってたので、正直KVSを使ってみたいという気持ちもありましたが、トランザクション管理が面倒です。スケーラビリティはKVSのほうが大きいと言われています。RDBも使い方次第なんですが、それほど劣るとも思えませんでした。とくにAWS Auroraが出てきたおかげで、スケーラビリティは本当にRDBのほうが劣るのかな? というところはあります。

結局採用した理由は、エンジニアの熟練度でした。ビズリーチの組織であれば、RDBにある程度詳しい人は集められます。あとは事前にどのような集計が必要なのか、KVSの場合はある程度予測しておく必要がありますが、作ろうとしているシステムは、事前にどのような集計が必要なのか予測できなかったので、RDBを採用しました。

RDBは何を使っているのかというと、AuroraのPostgreSQLを使っています。社内のシステムの多くはMySQLで動いているので、MySQLを使おうかとも考えましたが、MySQLの場合、複雑な分析を行うには不安なところもあったので、PostgrSQLにしています。Auroraを使っている理由は、分析用に巨大なRead Replicaを作れるあたりが大きなところです。

モノリシックなサーバにした理由

次に、サーバについてです。モノリシックなサーバなのか、マイクロサービス・サーバレスのようなことをやるのか。

マイクロサービスの場合、依存関係をある程度低くしておかなければ作れませんが、そもそも自分たちが作ろうとしているものの依存関係がどれくらいあるのかがまったく見えない状況で始めています。

また、RDBと同様にエンジニアの熟練度というところで、モノリシックなアプリケーションサーバであれば、エンジニアは何をやればいいのかがよくわかっているので、モノリシックなアプリケーションサーバを採用しています。

モチベーションとしては実はマイクロサービスは非常にやりたかったです。ですが、開発スピードがあまり上がらなそうだったので、結局はモノリシックなAppサーバにしています。

現在はBeans Talkで動作していますが、近い将来Docker で動くようになります

気を付けていることとしては、サーバに状態を持たせない。できればキャッシュなどを一切持たせない状況で動かそうとしてます。あとはローカルのストレージにはアクセスしない、というのはお約束だと思います。

ここまでを簡単にまとめると、アプリケーションサーバのほうにBeans Talkがいて、バックエンドはデータベースとしてAurora PostgreSQLがいるという状況です。

ユーザー認証

次に、ユーザー認証についてです。アプリケーションの大まかな構成が決まったら、次はユーザー認証の話になると思います。ユーザ認証には、AWS Cognitoを使っています。利用した理由は、まず、そもそも自前でパスワードを管理したくなかったところが大きなポイントです。

最近ではハッシュ関数は何使おうかとか、BCryptが主流であるとか。そういうものを今までは自分は作るんですが、何度もユーザー認証を作っていると、ほかの人に任せたくなって。でも、ほかの人に任せると、なぜかハッシュ関数を使わずに、共通鍵で暗号化したものをデータベースに保存しているとか、そういったなこともたまに起きると聞いています。

あと、リスクべースの認証などをなるべくマネージドなサービスで、プラグイン的に利用できるようにしておきたかったです。また、できれば簡単に作りたいというところもありました。GCPのプロトタイプはFirebaseを使ってユーザー認証をやっていたので、Cognitoのユーザープールを使うようにしてます。

実際にCognitoのユーザープールを使ってどうなったのかというと、ある程度簡単にできるのは事実ですが、ちょっとそこから外れたことをやろうとするとけっこう面倒くさくなります。

例えばユーザーを招待した時にメールの文面をカスタマイズしようと思っても、Cognitoのメール送信機能だけではあまり複雑なカスタマイズができません。結局そのあたりは自前で作ることになるとか。あとは認証の状態管理が、自分たちのデータベースとCognitoで若干食い違うところがあります。このあたりは、がんばればどうにでもなると思います。

Cognitoを使うことで、認証についてはかなりブラックボックス化しました。アプリケーション開発者がやるよりはるかに高い認証基盤ができます。さらにアドバンスドセキュリティやCognitoの機能を使えば、セキュリティはどんどん高められますが、実際にお客様に提案する時に「セキュリティはどうやっているんですか?」と聞かれると「Cognitoに任せています」としか答えられないので、そこはつらいところではあります。

Cognitoをユーザー認証に採用したことで、Beans Talkの上のほうにCognitoがやってきて、ブラウザとCognitoの間、もしくはBeans TalkとCognitoの間で、認証やCognitoの管理を行うようになっています。ここまでが大きな構成ですね。

RPA(自動化)した部分

その次にRPA、自動化についてです。現状のHRMOS Coreでは、データベースの変更や定期的なもの、例えば従業員の入社日や最終出社日といったイベントに応じてRPAを実行できるようにしています。

RPAの実態はイベントごとにエンドユーザーがJavaScriptで記述した処理です。今のところ、エンドユーザーに直接JavaScriptで書かせる、という漢らしさを徹底しています。RPAはLambdaで実行しています。Lambdaで実行するようにしたのは、ほかに選択肢がなかったからですね。

AWSの場合、AWS Batchなどで実行させることもできますが、それだと遅延が発生しすぎるのと、スケーラビリティで若干問題があります。ほかに選択肢がないのでLambda上で実行させています。

このようにBeans TalkからLambdaを起動するようにしたんですが、タイミングによってはLambdaを実行できないケースもあるので、一旦Queueを挟んでLambdaを実行するようにしてます。

言語の選定

次に言語選定についてです。会社の特性としては、ビズリーチはJavaがメインの会社で、最近のプロダクトではScalaも多くなってきています。それ以外の言語はマイナーでほとんど使われていない状況ですね。そんな中で開発に採用する言語は、Javaか、もしくはJavaに近しい言語ということで、このような選択肢がありました。

Javaはずっとやってきたんですが冗長すぎてnull安全性もないし、あまり好きではありませんでした。JavaにLombokを組み合わせればかなりいいところまでいくんですが、たまにLombokが言うことを聞いてくれないことがあります。そこで苦労するくらいなら別の言語がいいなと思いました。

ビズリーチにはScalaを使っている人も多いんですが、やはりScalaはふつうのJavaエンジニアにとっては難易度が高すぎます。Better Java的に使うには規約で縛る必要があるんですが、規約で縛るだけではそれを逸脱する人が出てきてしまいます。ということで、残りの選択肢としてKotlinが残りました。KotlinはJavaとScalaの中間的な位置づけの言語だと僕は認識しているので、Kotlinを選択しました。

他には、KotlinであればJavaから簡単に学習ができること。ふつうのJavaプログラマーであれば、1週間~2週間Kotlinを触っていればそこそこ書けるようになる、という点がありました。

また、冗長性を排除していることと、null安全性。簡単に言ってしまえば、Javaの不満をいろいろ解消してくれていますが、Scalaのような新規性はありません。そこは良いところだと思います。ふつうにJavaの資産をそのまま利用できるのと、GoogleがAndroidを作るのにKotlinを採用しているので、言語自体の安定性も担保されている。そんな理由でKotlinを選んでいます。

実際にKotlinで書いてる部分は、アプリケーションサーバとLambdaの2つの部分です。Lambdaもアプリケーションサーバも、基本的に開発言語は統一させる形で開発しています。結局は学習コストを下げることと、2つの環境でロジックをなるべく再利用したいという点です。こんな感じで、Beans TalkとLambdaのコードはKotlinで書いています。

HRMOS Coreが弊社で初めてKotlinで開発を始めたプロダクトなんですが、社内でもほかのプロダクトでKotlinが採用され始めてます。どれくらいのプロダクトが採用しているのかは覚えていないのですが、いくつかの新規のプロダクトでは採用されています。

Webフレームワークの選定基準

次にWebフレームワークについてです。

特性としてはリアルタイム性はそれほど高くなくてもいいし、画面やデータの種類はわりと多いほうです。データの入力パターンはふつうです。

データの種類が豊富なので、なるべく簡単にページを量産できるような仕組みを作りたかった。フロントエンドのエンジニアはわりと集めにくく、サーバサイドのエンジニアだけでなるべく完結できるような仕組みにさせたいという要望がありました。SPA的なUIではキャッチアップが大変で、なかなかスケールは大変です。

方針としては、HTMLをサーバサイドでレンダリングして、今までどおりHTMLをフィードするような仕組みを作っていきました。JSは基本的には使いません。共通の処理で使う部分はJSを使っていますが、基本的にJavaScriptは使わない、つかわせない方針で開発しています。

とはいえ、例えば組織図で組織の並び順を変更したりするのは、マウスでドラッグ&ドロップでいきたいですよね。そういった動きが必要なページはスポットでVue.jsを採用しています。

Webフレームワークの候補としてはこういった物がありました。

KtorというのはKotlinで書かれたWebフレームワークで、最初は使いたかったんですが、開発を始めた頃はまだ安定していなかったので、採用を見送っています。

JavaEEはGCPでプロトタイプを作った時に使っていました。DIがSpringほど便利ではなく、すこし面倒なところがあったので、結局Springを使っています。

実際にSpringを使って、スポット的にVueを使って、基本的にテンプレートエンジンにFreemarkerで描画をしていましたが、その結果がどうだったのかということをお話します。

とくに入力フォームの話なんですが、動きのないページを作っていたつもりでしたが、実際に作っていくとやはりフィールド間の依存関係が若干は発生してしまうところはあります。

なるべく直接的な、昔ながらのjQuery的なDOM操作は避けたかったので、フィールド間の依存関係が必要なものは、入力フォームの中でもスポット的にVueを使い始めています。その結果、画面の種類ごとにVueで作られたUIとFreemarkerで作られたUIと2つ存在していて、それぞれのコンポーネントが使い回せないので、メンテナンス箇所が単純に2倍に増えてしまったという状況になっています。

サーバサイドもVueですべて書いておけば、すべて一元管理できて問題なかったと思うので、これが唯一の後悔ですね。

リポジトリ層については、基本的にORMを使っています。

処理の80パーセントは簡易的に記述ができるので、そのあたりは便利に使っています。使ってみたかったという気持ちがあったので、ExposedというKotlin純正のORMを使っています。開発初期はそこそこバグが多かったんですが、最近は安定してうまく動いています。

簡単な処理のものが80パーセントくらいあってそこそこ有用に使えるんですが、残り20パーセントに複雑な処理があります。そういったところは無理にORMを使わずに、JDBC Templateで直接SQLを手で書いています。

あとは、ResultSetをObjectにマッピングするあたりを便利にして、かなりうまく開発できるような環境になっています。

全体の構成

全体の構成なんですが、結局ふつうのWebアプリケーションと同じようにリポジトリ層があって、サービス層があってコントローラー層があってビューがある、ごく当たり前の構成になっています。

いろいろなデータをフェッチする時に、複雑なjoinなどをすべての開発者に平等にやらせていると、セキュリティの誤記的なところが出てきたので、サービス層とリポジトリ層の間に薄いドメイン層を乗せています。そして、複雑なjoinなどはドメイン層以下で一部のエンジニアだけが操作するようにしています。

大きな構成はこんなところです。これに対してセキュリティをどうコントロールしているかというと、コントローラー層で基本的なアクセス制御を行っています。「基本的な」というのは、そのURLにそもそもアクセスしていいのかダメなのか、ということを判定しています。

講演時間が終わりに近づいてきたので、詳細までのご説明できないのですが、機能単位でサービス層のレイヤーでその機能にそのユーザーがアクセスしていいのかどうかを制御したり、リポジトリ層の部分では、データを書き出す時に、権限がない人が絶対にデータにアクセスできないような仕組みを提供したりしようとしています。

基本的な方針としては、全体はマネージドな構成をとりつつ、シンプルな構成でシンプルなアーキテクチャを心がけております。

ありがとうございました。

(会場拍手)