何がしたいか?
私は数多の業務システム開発に携わってきましたが、どの案件でも考えないと行けないことは同じです。
例えば、ControllerからServiceに何を渡すべきか?です。Contollerは何となくHTTPリクエストを受け付ける役割があり、Serviceは業務ロジックの実行という役割があります。しかし、Contoller-Service-DAO/Repositoryというめジャーな構造を取ると、層ごとに独立しているべきであり、各層でのパラメータ(引数)の受け渡しが計4回発生することになります。ちゃんと受け渡す度DTOを詰め替えるべきなのでしょうか?そんなルールは破ってもいいのでしょうか?
他の例では、サーバサイドのバリデーションもそうです。なぜそうなるかと言うと、Spring標準のバリデータが種類が少ないこと、全角文字チェックなど日本語に対応していないことが原因です。その結果、どの案件でも案件ごとにバリデータを独自実装することになります。しかし、その実装も面倒なため、後回しになることが多く、そのことによるプロジェクトへの副作用が大きいです。
私はこのようなどのプロジェクトでも考えないといけないこと、作らないといけないことを部品として提供することで、考える・作ることをスキップさせてあげたいと思っています。そうすることで、迅速に高品質な量産体制を整えることができ、コーディングするエンジニアが幸せになるはずです。
この記事ではまず、どのプロジェクトでも考えていること、作っていることをリストアップしていきます。
1.バリデーション
上の例でも挙げましたが、バリデータは日本全体で1つちゃんとしたバリデータがあれば良いはずです。どのプロジェクトでもチェックしたい内容はほとんど同じです。そのため、プロジェクトごとに同じような正規表現を使った独自バリデータを作るのは日本全体として非効率です。
私がそうなのですが、各画面の担当者はバリデータは共通部品として提供されるものだから、実装を待っておこうと考えます。そうすると、いつまで経っても画面の実装が完了になりません。
また、バリデーションを未実装のまま結合テストを迎えると、不正なデータがDBに登録されます。それによって次の画面が期待通りに動かず、原因は「データ不良」になって、余計なNGケースを作ってしまいます。自分が実装した画面ならわざわざ不良データなんて入れないでしょうけど、他の人が作った画面なら全然ありますね。
バリデータが後回しにされると、顧客が打鍵する環境にまでバリデーション未実装のままデプロイされてしまいます。そうすると、顧客から「こんなデータ表示されてますけど?」「ここは半角でしか入力できないようにしてください」と余計な指摘が入ることになります。顧客の心証も悪くしてしまい、バリデーションを後回しにして良いことはありません。
2.日付周りのユーティリティークラス
Java8からDate APIが提供されているので、もうDateクラスは使わないで、LocalDateクラス or LocalDateTimeクラスを使いましょう。
日付の場合はLocalDate、日付時刻(日時)の場合はLocalDateTimeを使いましょう。
流石に2021年以降のスクラッチ案件でDateクラスを使っている案件はないと思いますが、内部ではStringで日付を受け渡している案件はあるのではないでしょうか。
私がの考えるベストプラクティスは、Requestの時点でLocalDate/LocalDateTimeで受け取ってしまうことです。内部でも全LocalDate/LocalDateTimeで受け渡しをします。そして、必要になったタイミングで、DateTimeFomatterを使ってyyyy/MM/ddなど任意の形式に変換します。内部では常にLocalDate/LocalDateTimeで受け渡すのがポイントです。
また、日付のフォーマットもどの案件でも必要になるのは大体一緒ですから、日本全体で1つ、日付フォーマットを定義したクラスがあれば良いはずです。
yyyy/MM/dd,yyyy-MM-dd,yyyy/MM/dd HH:mm:ss, yyyy-MM-dd HH:mm:ss とあと何種類かあれば足りるでしょう。定数管理をちゃんとできている案件であれば、そうしているでしょう。しかし実態はそれすらやらない案件がほとんどで、画面ごとに
private final String DATE_FORMAT = “yyyy/MM/dd”;
を書いていたりします。あるいわ一部は定数クラス、定数クラスを知らない後から入った人は画面ごと・・・というのはよくありますね。
また、現在時刻を取得するというのもよくありますが、クラス内で、
LocalDateTime nowDateTime = LocalDateTime.now();
とするのではなくて、日付ユーティリティクラスに、
/** 現在時刻を取得する */
public static LocalDateTime now() {
return LocalDateTime.now();
}
を作って、みんなそのメソッドを使うことで、現在時刻の取得ロジックを統一することができます。
おそらくエンジニアは一度は「そんなユーティリティないかな?」って探すと思います。探した結果ないから、それぞれ独自実装しちゃうんです。最初っからユーティリティにそういうものが用意されていれば、画面担当者は喜んで使ってくれると思います。
3.Controller-Service-DAO/Repository 3層構造
Contoller
↑↓
Service
↑↓
Dao/Repository
Daoで最もオススメなのはDoma2です。逆にオススメできないのは、JPAです。JPAは超簡単なSQLだけの業務システムであればSQLを一切書かずにできますが、ほとんどの業務システムはSQLを書きます。JPAでは動的なWhere句やサブクエリを駆使しまくる業務システムを構成するのは難しい。
Doma2の2-way-SQLは可読性が高く保守しやすいと感じます。Doma2が日本のSI界の標準になれば良いと思います。
Serviceの下にもう1つ層を挟むという考えもありますが、なくても良いと思っています。作るとまた受け渡すDTOどうするの?という問題が発生するし、ある程度ロジックの重複が出るのは仕方ないと思うからです。
4.メッセージ管理(メッセージID)
メッセージ管理も最後はうやむやになるケースが稀にあります。しかし、どの案件でも同じような体形を作っています。メッセージ定義書、ログ出力システムも日本全体で統一された方がエンジニアはハッピーでしょう。
メッセージをコード内にハードコーディンしても問題はないです。が、メッセージは一元管理されているべきでしょう。
メッセージとしてよくあるのはこんな形式です。
I_MN_0101_1 = “○○をスキップします。”
W_
E_
構成要素としては、
- ログレベル(INFO/WARN/ERROR)
- 機能名
- 画面ID
- 画面内枝番
ですね。
ログとしてあって欲しいのは、ログが発生した機能と、そのときの発生状況(引数の値など)です。
ユーザに見せるメッセージの場合は「私はじゃあどうすればいいの?」を明示してあげるべきです。「〇〇が存在しません」ではなく、「〇〇が存在しません。画面をリロードしてください。」のように、次に取るべき行動がわかるのが
5.Exception/例外処理
例外はどこの案件も2つ作っています。業務例外クラスとシステム例外クラスです。Xxx案件なら、XxxBusinessException と XxxSystemException です。見たことありませんか?
業務例外の区別が難しいところなのですが、BusinessExceptionは、画面をリロードしたり、値を直したら発生しない例外です。
例えば更新ボタンを押すときに業務エラーが発生するとします。排他チェックエラーは、画面をリロードしてもう一度更新ボタンを押してもらえば、次は更新できるはずです。存在チェックエラーは、画面をリロードするか、違う値に変更して更新ボタンを押してもらえば、次は更新できるはずです。そういうのは業務エラーです。
SystemExceptionは、画面をリロードしたり、値を直してもまた発生することが予想される例外です。代表的なのは通信エラーです。システムとDB、システムとクラウド、システムと外部APIなどで発生しうるのですが、画面をリロードしたら直ってるものではありません。
で、日本全体で共通化しておきたいのは、両Exceptionをnewするときの引数ですね。必要な引数は、catchしたException e(ない場合もある)、メッセージID、メッセージ引数です。これらが整備されていれば良いだけです。
なぜ、こんなことをするのか?(メリットは?)
SpringBootを使って、お決まりの構成を作っておくことで、どの案件でも一定の品質が保てます。また、エンジニアのアサインも容易になります。次の案件の立ち上げも非常に容易になります。
プロジェクトごとに作り直すのではなくて、Springフレームワークを使った、業務システムの雛形があれば、関わる人全員がハッピーになると考えています。
- 顧客は高品質で保守しやすいシステムを手に入れることができる。
- エンジニアは快適な開発体験ができる。
- リーダーは残業を命じなくて済む。レビューしやすい、管理しやすい。
- SI企業は人集めやすい、ロースキルの人でも作れる、納期守れる。
誰か一緒にやりませんか?
誰か、日本全体を統一するようなSpringフレームワークを使った業務システムの雛形を一緒に作りませんか?
マネタイズの部分もちゃんと考えてますよ〜〜〜