masuda220/jr-pricing 「JR 新幹線 料金ルールを実装してみよう」
を Kotlin で実装してみたものです。
実行結果を確認するために、便宜的に Swagger UI を使用しています。
- bootRun で実行
- localhost:50222 で Swagger UI 画面が表示されます
- 画面の案内に従って実行してください
大まかな運賃計算の流れを下図のとおり整理してみました。
右側に運賃を決定づける因子があり、
左の方へ計算が流れていくイメージ。
- 運賃が定まる元になる 運賃因子 との関連
- 料金には、加算系と減算系 がある
→ のちに、accumulate
パッケージへと発展 - 団体総合割引では「n人無料扱い」となるので、運賃計算上の乗客数 というのが必要になりそう
- 団体割引は下記の2通りあり、適用先が異なる
- 普通運賃から割り引くもの
- 乗客のうち何人かが無料扱いになる
- 往復割引きは、Route自身も使うけど Routeから導出された普通運賃も使うところ
このセクションのモデル画像は dddjava/jig により生成されたものです。
出力に必要な環境は こちら を参照ください。
IntelliJ IDEA であれば Gradle ツールウィンドウで jigReports
タスクを実行するか、
あるいは、下記コマンドで生成できます。
$ ./gradlew clean build jig
build/jig/
配下に出力されます。
JIG を使うと、
モジュール(DDDの文脈では C# の名前空間や Java のパッケージのこと)間の依存関係を把握することができます。
モジュール分割と依存関係を意識した設計を強制(矯正)されるような気がします。
このセクションで提示しているダイアグラム以外にも様々なダイアグラムや表などが出力されます。
ここにサンプルあり
より詳しい説明は Wiki へ
大きく、
- faresystem (運賃システム)パッケージ
- calculation (運賃計算)パッケージ
に分けて、運賃システムを使って運賃計算をするかたちにしています。
faresystem
(運賃システム)パッケージの構成は次の表の通り。
パッケージ | 内容 |
---|---|
_foundation | 金額、日付、距離などの土台となる型 |
factor | 運賃計算のファクターとなるもの |
pricing | 価格設定 |
rule | ファクターと価格設定を用いた運賃計算ルール |
calculation
(運賃計算)パッケージで運賃計算をとりまとめている中心は次の2つです。
-
BasicFareCalculator
- ファクターから普通運賃計算に必要な料金を導出
- それらを累算して普通運賃を算出
-
ExpressFareCalculator
- ファクターから特急料金計算に必要な料金を導出
- それらを累算して特急料金を算出
calculation
(運賃計算)パッケージからは、
faresystem
(運賃システム)の pricing
パッケージを直接は使っていないところはポイントかもしれません。
BasicFareCalculator
や ExpressFareCalculator
で導出といっているところは、
実際には faresystem (運賃システム)の rule
パッケージの運賃型を生成しています。
rule
パッケージの運賃型は AccumulatableAmount
に準拠していて、
AccumulatableAmount
は、accumulateAmount
関数を使って累算ができるようになっています。
料金には「加算系」と「減算系」があり、
どちらに準拠している料金かにより、加算/減算 されるしくみにしています。
詳しくは、
accumulation
パッケージにあるクラスの説明を参照してください。
「累算」パッケージと「金額」パッケージ間で相互参照が発生しています。
ただ、ここは特に問題ないと思っています。
(何か問題がありそうなら教えてください)
- つぶやき
accumulation
パッケージとamount
パッケージを1つのパッケージにまとめると解消できるし、
そうすると相互参照NGというのはある程度の目安という感じになるのかなと...
パッケージを分けるほどにパッケージ間相互参照は発生する可能性高くなりそう...
まぁ、パッケージ1つだとパッケージ相互参照しないのでそういうことか...
ふと思い立って、
ビジネスルールを実装している箇所に BIZ-RULE:
でマークしてみました。
IntelliJ IDEA であれば、⌘ + shift + F
で検索してみてください。
ほとんどは、rule
パッケージに書かれているようです。
pricing
パッケージなどとも協調していますけれども。
ほぼほぼ、
「ビジネスルールを確認したい場合は、rule
パッケージまわりをみて、
具体的な価格設定については、pricing
パッケージをみていけばいい」
という構成になっていそうです。
ただ、rule
パッケージ以外のところに実装しているビジネスルールもあり、
置き場所として適切なのかは検証の必要がありそうですかね。
プロダクトではないのでいまのところほぼ書いていません。
が、一応ある程度の品質は保ちたいので、
アプリケーション層に対してのテスト(FareCalculationServiceTest
)を書いています。
microsoft/pict でケースを出した上で、
さらに間引いたケースをテストしています。
ある程度実装できた段階でテストを用意し、
それを拠り所にリファクタリングしていった感じです。
それと、ImmutableMap をプライシングテーブルとして利用していますが、
キーとして期間を使用しているものについては、
月日のオーバーラップチェックだけ書いています。
GroupIndividualDiscountPricingTest
SeasonDefTest
-
入口から辿りたい場合は、
FareApi
クラスのfareFor
メソッドから辿れます
(com.example.rail.presentation.api.fare.FareApi) -
ビジネスルールの実装から探りたい場合は、
rule
パッケージあたりから探るのがいいように思います
(com/example/rail/domain/model/faresystem/rule)
要求仕様として解釈が間違っているところもあるかもしれませんが、
ご容赦ください。
com/example/rail/domain/model/faresystem/pricing/byRoute
パッケージをみると、
普通運賃、特急指定席料金、のぞみプレミアムチャージ料金の3つの価格設定テーブルが定義されていて、
新しいルートを登録するときに登録漏れが心配になります。
という理由で、ルート別価格テーブルを1つに統一してみます。
対応版は、quest/refactor-pricing-by-route ブランチ です。
実行結果を確認するために、便宜的に Swagger UI を使用していて、
結果をプレーンテキストで表示していますが、
割引きが発生してもその情報がありません。
経路 Tokyo − SinOsaka
座席タイプ Reserved
列車タイプ Hikari
出発日 2021-02-15
行程タイプ OneWay
人数 大人 3 名, 小人 160 名
---------------------------------------------------------------------
大人片道普通運賃 8,910 円
大人片道特急料金 5,490 円
大人運賃合計 14,400 円
大人適用人数 0 名
大人運賃総合計 0 円
---------------------------------------------------------------------
小人片道普通運賃 4,450 円
小人片道特急料金 2,740 円
小人運賃合計 7,190 円
小人適用人数 159 名
小人運賃総合計 1,143,210 円
---------------------------------------------------------------------
お支払い金額合計 1,143,210 円
どれだけ割引きが発生しているのかは情報として欲しい気がするので、
対応してみます。
こんな感じ。
経路 Tokyo − Himeji
座席タイプ Reserved
列車タイプ Nozomi
出発日 2021-01-16
行程タイプ RoundTrip
人数 大人 3 名, 小人 150 名
---------------------------------------------------------------------
大人往復普通運賃 18,020 円 往復割引 10 % 適用済み
大人往復特急料金 12,500 円 閑散期 -200 円 適用済み
大人運賃合計 30,520 円
大人適用人数 0 名 団体割引 大人 3 名様 無料扱い
大人運賃総合計 0 円
---------------------------------------------------------------------
小人往復普通運賃 9,000 円 往復割引 10 % 適用済み
小人往復特急料金 6,240 円 閑散期 -200 円 適用済み
小人運賃合計 15,240 円
小人適用人数 149 名 団体割引 小人 1 名様 無料扱い
小人運賃総合計 2,270,760 円
---------------------------------------------------------------------
お支払い金額合計 2,270,760 円
対応版は、quest/discount-trail ブランチ です。