Skip to content

DDD practice scene with the theme "Railway fare calculation"

Notifications You must be signed in to change notification settings

blue-monk/study-DDD-JR-Pricing-Kotlin

Repository files navigation

jr-pricing in Kotlin

Kotlin Version

Table of Contents


🌱 What is This ?

masuda220/jr-pricing 「JR 新幹線 料金ルールを実装してみよう」
を Kotlin で実装してみたものです。

🌱 実行方法

実行結果を確認するために、便宜的に Swagger UI を使用しています。

  • bootRun で実行
  • localhost:50222 で Swagger UI 画面が表示されます
  • 画面の案内に従って実行してください

🌱 設計の過程

大まかな運賃計算の流れを下図のとおり整理してみました。

右側に運賃を決定づける因子があり、
左の方へ計算が流れていくイメージ。


モデルA

🍃 整理できたところ

  • 運賃が定まる元になる 運賃因子 との関連
  • 料金には、加算系と減算系 がある
    → のちに、accumulateパッケージへと発展
  • 団体総合割引では「n人無料扱い」となるので、運賃計算上の乗客数 というのが必要になりそう

🍃 難しそうなところ

  • 団体割引は下記の2通りあり、適用先が異なる
    • 普通運賃から割り引くもの
    • 乗客のうち何人かが無料扱いになる

🍃 悩みそうなところ

  • 往復割引きは、Route自身も使うけど Routeから導出された普通運賃も使うところ

🌱 モデルの説明

このセクションのモデル画像は dddjava/jig により生成されたものです。

出力に必要な環境は こちら を参照ください。

IntelliJ IDEA であれば Gradle ツールウィンドウで jigReportsタスクを実行するか、
あるいは、下記コマンドで生成できます。

$ ./gradlew clean build jig

build/jig/ 配下に出力されます。

JIG の効用

JIG を使うと、
モジュール(DDDの文脈では C# の名前空間や Java のパッケージのこと)間の依存関係を把握することができます。
モジュール分割と依存関係を意識した設計を強制(矯正)されるような気がします。

このセクションで提示しているダイアグラム以外にも様々なダイアグラムや表などが出力されます。
ここにサンプルあり
より詳しい説明は Wiki へ


🍃 パッケージ関連図

🌴 domain/model/ の階層

パッケージ関連図 A

🌴 domain/model/ の階層 + 1

パッケージ関連図 B

🌴 domain/model/ の階層 + 2

パッケージ関連図 C

大きく、

  • faresystem (運賃システム)パッケージ
  • calculation (運賃計算)パッケージ

に分けて、運賃システムを使って運賃計算をするかたちにしています。

🌿 faresystem (運賃システム)パッケージ

faresystem (運賃システム)パッケージの構成は次の表の通り。

パッケージ 内容
_foundation 金額、日付、距離などの土台となる型
factor 運賃計算のファクターとなるもの
pricing 価格設定
rule ファクターと価格設定を用いた運賃計算ルール

🌿 calculation (運賃計算)パッケージ

calculation (運賃計算)パッケージで運賃計算をとりまとめている中心は次の2つです。

  • BasicFareCalculator

    • ファクターから普通運賃計算に必要な料金を導出
    • それらを累算して普通運賃を算出
  • ExpressFareCalculator

    • ファクターから特急料金計算に必要な料金を導出
    • それらを累算して特急料金を算出

calculation (運賃計算)パッケージからは、
faresystem (運賃システム)の pricingパッケージを直接は使っていないところはポイントかもしれません。

🌴 運賃計算のからくり

BasicFareCalculatorExpressFareCalculator で導出といっているところは、
実際には faresystem (運賃システム)の ruleパッケージの運賃型を生成しています。
ruleパッケージの運賃型は AccumulatableAmount に準拠していて、
AccumulatableAmountは、accumulateAmount関数を使って累算ができるようになっています。
料金には「加算系」と「減算系」があり、
どちらに準拠している料金かにより、加算/減算 されるしくみにしています。

詳しくは、
accumulationパッケージにあるクラスの説明を参照してください。

🍃 相互参照が...

モデルB

「累算」パッケージと「金額」パッケージ間で相互参照が発生しています。
ただ、ここは特に問題ないと思っています。
(何か問題がありそうなら教えてください)

  • つぶやき
    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)

🌱 おことわり

要求仕様として解釈が間違っているところもあるかもしれませんが、
ご容赦ください。


🌱 探求課題

🍃 1. (リファクタリング) ルート別価格テーブルを1つに統一してみる

com/example/rail/domain/model/faresystem/pricing/byRouteパッケージをみると、
普通運賃、特急指定席料金、のぞみプレミアムチャージ料金の3つの価格設定テーブルが定義されていて、
新しいルートを登録するときに登録漏れが心配になります。
という理由で、ルート別価格テーブルを1つに統一してみます。

対応版は、quest/refactor-pricing-by-route ブランチ です。

🍃 2. (機能追加) 割引き適用した内容を表示してみる

実行結果を確認するために、便宜的に 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 ブランチ です。

About

DDD practice scene with the theme "Railway fare calculation"

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published