Beanの実装デザイン

LastaFluteからの提案です。

このページは、Actionの実装デザインを読んでいることが前提です。

ここで言う Bean とは、Form や JsonResult などの入れ物クラスのことです。

いきなりまとめ

実装デザインの提案

役割のわかるクラス名Suffix
Body, Result, Param, Return など
ネストした構造の表現
インナークラス で定義、クラス名は ...Part で終わる
JavaDocでe.g.デフォルト値
e.g. でデフォルト値 (いーじーでふぉるとち) を書く (for Swagger)
気軽なtoString()
Lato で気軽にtoString()をオーバーライド

役割のわかるクラス名Suffix

規約と慣習による統一化

LastaFluteでは、Beanのクラス名に Suffix を付けることで役割を明確にしたいと考えています。

規約のものもあれば、慣習のものもあります。

規約
そういうsuffixじゃないと動かない
慣習
動かないわけじゃないけど、みんなでそうしよう

慣習に関しては、単なる提案になりますので、必ずしもそうでないといけないわけではありませんが、大勢でブレるくらいであれば、LastaFlute の提案する慣習に則った方が多くの人が理解できるものになります。(現場で変更する場合は、しっかり統一性を保つためのファシリテーションをしましょう)

外部やり取りのBean

リクエストパラメーターや、JSONデータの変換、HTMLテンプレートへ渡す値など、外部の世界とのデータの受け渡しに関しては、このようになっています。

...Form (規約)
リクエストパラメーターを受け取るクラス
...Body (規約)
リクエストされたJSON Bodyを受け取るクラス
...Bean (慣習)
Thymeleafに渡すデータのクラス、その他の用途でも使うかも (HtmlBean)
...Result (慣習)
レスポンスで戻すJSONを表現するクラス (JsonResult)
...Part (慣習)
BodyやResultなどの入れ物クラスの一部データをまとめたインナークラス

Thymeleafに渡すデータのクラスだけ、Beanのままです。ここは良い名前が浮かびませんでした。 そもそも、サーバーサイドHTML方式のユーザーも少ないので、検討も先送りしてBeanのままですが、現場で統一ができるのであれば、別のsuffixにしても良いでしょう。

内部やり取りだけのBean

Logicなど、アプリの内部レイヤ間でのデータの受け渡しに関しては、このようになっています。

...Param (慣習)
Logicなどで項目が多い場合にまとめる引数クラス
...Return (慣習)
Logicなどで項目が多い場合にまとめる戻り値クラス

本来、ここはフレームワークが一切関わらないクラスたちなので、現場でケースバイケースでカスタマイズやアドリブがあっても全然良いところですが、 最低限区別しようという提案です。例えば、付ける名前が困り、なんとなくすべて区別せずに ...Bean とか ...Dto とかにするくらいであれば、Param, Return を使いましょうというニュアンスです。

引数でも戻り値でも両方で使うようなケースであれば、業務的な意味の深い名前を付けられるクラスなので、その場合は Bean (or その他) でも良いでしょう。 (ただし、他の規約や慣習で利用されている名前は避けたほうが良いかなと)

※一方で、RemoteApiの送受信のクラスでも利用されたりします。Param, Return自体は汎用的な活用のされ方をします。

ネストした構造の表現

インナークラスのPart

特にJSONのクラスなどは、ネストした構造を気軽に持つことが多いです。 それをつどつど独立したクラスにするのもなかなか手間ですし、逆に切り出すとJSONの前提像がクラスから読み取りづらくなります。 なので、特に再利用しないようなネスト構造の部分のクラスは、その場で インナークラスで表現してしまったほうが良いだろう と考えました。

そして、クラス名の suffix には Part を付けます(ただし変数名はJSONに載る名前なので付けません)。 外側のメインのクラスと区別することで、コード上の可読性が上がりやすいだろうと考えました。

e.g. JSON Body that has nested classes @Java
/*
{
    title : resort
    , sea : {
        docksideStage : {
            showName : over
            , showType : MSC
        }
        , hangarStage : {
            showName : mystic
            , showType : PFM
        }
    }
    , hotels : [{
        hotelName : amba
        , hotelType : DIN
    }, {
        hotelName : baymai
        , hotelType : OFC
    }]
}
*/
public class MaihamaBody {

    @Required
    public String title;

    @Required
    @Valid
    public SeaPart sea;

    public static class SeaPart {

        @Required
        @Valid
        public ShowPart docksideStage;

        @Required
        @Valid
        public ShowPart hangarStage;

        public static class ShowPart {

            @Required
            public String showName;

            @Required
            public CDef.ShowType showType;
        }
    }

    @NotNull
    public List<HotelPart> hotels;

    public static class HotelPart {
        
        @Required
        public String hotelName;

        @Required
        public CDef.HotelType hotelType;
    }
}

Partを再利用するときは?

もちろん再利用するものは、この限りではありませんが、あまりネスト構造の一部を再利用することも多くはないはずです。 もしそういうケースがあった場合、以下のような感じでやると良いでしょう。(ここ厳密ではなくてもOKですが)

片方はメイン利用
そのときは、もちろんメインに合わせたクラス名
どちらもネスト利用
Partという名前のまま独立クラスに

JavaDocでe.g.デフォルト値

JavaDocには、e.g. 形式でデフォルト値を書くことをオススメします。

以下のようなメリットを享受することができます。 特に、JSON API では、Swaggerの機能が不可欠ですが、このe.g.デフォルト値があるとスムーズな運用ができるようになります。

Swaggerでのメリット
swagger-uiですぐにリクエストを送ることができます
LastaDocでのメリット
具体的な値がLastaDocでわかると理解が早まります
コード上でのメリット
具体的な値がコード上でわかると理解が早まります
e.g. javadoc has e.g.default values @Java
public class MaihamaBody {

    /** そのエリアのテーマ e.g. resort */
    @Required
    public String title;

    /** 海パーク */
    @Required
    @Valid
    public SeaPart sea;

    public static class SeaPart {

        /** ドックサイドステージ */
        @Required
        @Valid
        public ShowPart docksideStage;

        /** ハンガーステージ */
        @Required
        @Valid
        public ShowPart hangarStage;

        public static class ShowPart {

            /** そのステージ行われているショーの名前 e.g. over */
            @Required
            public String showName;

            /** ショーの種別 e.g. MSC */
            @Required
            public CDef.ShowType showType;
        }
    }

    /** 存在するホテルの一覧 */
    @NotNull
    public List<HotelPart> hotels;

    public static class HotelPart {
        
        /** ホテルの名前 e.g. amba */
        @Required
        public String hotelName;

        /** ホテルの種別 e.g. DIN */
        @Required
        public CDef.HotelType hotelType;
    }
}

気軽なtoString()

Beanクラスは、何かとデバッグログで出力される機会があります。中のデータを出力しておくことで、デバッグに役立つことが多々あります。

そこで、toString() をオーバーライドして、Lato.string(this) という風に実装すれば、リフレクションで Bean の中身の情報を出力してくれます。 一番外側のクラスにだけ付けておけば、ネストしたクラスの中身も出力してくれます。

LastaFluteの補完テンプレートを入れていれば、_lato でサクッと補完できます。

e.g. toString() of body class @Java
public class MaihamaBody {

    @Required
    public String title;

    @Required
    @Valid
    public SeaPart sea;

    public static class SeaPart {

        @Required
        @Valid
        public ShowPart docksideStage;

        @Required
        @Valid
        public ShowPart hangarStage;

        public static class ShowPart {

            @Required
            public String showName;

            @Required
            public CDef.ShowType showType;
        }
    }

    @NotNull
    public List<HotelPart> hotels;

    public static class HotelPart {

        @Required
        public String hotelName;

        @Required
        public CDef.HotelType hotelType;
    }

    @Override // _lato => ctrl+space でこれら全部補完
    public String toString() { // 一番外側のクラスだけオーバーライドでOK
        return Lato.string(this);
    }
}