3

Java では、不変の POJO の階層を使用してドメイン モデルを表現したいと考えています。

例えば

final ServiceId id = new ServiceId(ServiceType.Foo, "my-foo-service")
final ServiceConfig cfg = new ServiceConfig("localhost", 8080, "abc", JvmConfig.DEFAULT)
final ServiceInfo info = new ServiceInfo(id, cfg)

これらのすべての POJO には、getter または setter のない public final フィールドがあります。(ゲッターのファンの場合は、フィールドがゲッターで非公開であるふりをしてください。)

また、 MessagePackライブラリを使用してこれらのオブジェクトをシリアル化し、ネットワーク経由で渡したり、ZooKeeper ノードに格納したりしたいと考えています。

問題は、MessagePack が public の非 final フィールドのシリアル化のみをサポートしているため、ビジネス オブジェクトをそのままシリアル化できないことです。また、MessagePack は をサポートしていないためenum、enum 値をシリアル化するために変換するint必要があります。String(はい、あなたの s に注釈を追加すればそうですenum。以下の私のコメントを参照してください。)

これに対処するために、「メッセージ」オブジェクトの対応する階層を手書きで作成し、各ビジネス オブジェクトとそれに対応するメッセージ オブジェクトとの間で変換を行います。明らかに、これは理想的ではありません。大量のコードの重複が発生し、人的ミスによってフィールドが欠落するなどの可能性があるからです。

この問題に対するより良い解決策はありますか?

  • コンパイル時のコード生成?
  • 実行時に適切なシリアライズ可能なクラスを生成する方法はありますか?
  • MessagePack をあきらめますか?
  • enumビジネス オブジェクトの不変性と s をあきらめますか?
  • 可変オブジェクト (メッセージ オブジェクト) を不変オブジェクト (ビジネス オブジェクト) にラップできる汎用ラッパー ライブラリはありますか?

MessagePack は (@MessagePackBeans アノテーションを使用して) Java Bean のシリアル化もサポートしているため、不変オブジェクトを Java Bean との間で自動的に変換できれば、解決に近づく可能性があります。

4

2 に答える 2

1

偶然にも、私は最近、あなたが説明していることをほぼ正確に実行するプロジェクトを作成しました。不変のデータ モデルを使用すると大きなメリットが得られますが、多くのシリアライゼーション テクノロジは後から考えて不変性にアプローチしているようです。これを解決する何かが欲しかった。

私のプロジェクトGrainsでは、コード生成を使用して、ドメイン モデルの不変の実装を作成しています。この実装は、さまざまなシリアライゼーション フレームワークに適応できるほど汎用的です。これまでのところ、MessagePack、Jackson、Kryo、および標準の Java シリアライゼーションがサポートされています。

ドメイン モデルを説明する一連のインターフェイスを作成するだけです。例えば:

public interface ServiceId {
    enum ServiceType {Foo, Bar}

    String getName();
    ServiceType getType();
}

public interface ServiceConfig {
    enum JvmConfig {DEFAULT, SPECIAL}

    String getHost();
    int getPort();
    String getUser();
    JvmConfig getType();
}

public interface ServiceInfo {
    ServiceId getId();
    ServiceConfig getConfig();
}

Grains Maven プラグインは、コンパイル時にこれらのインターフェースの不変の実装を生成します。(それが生成するソースは、人間が読むように設計されています。) 次に、オブジェクトのインスタンスを作成します。この例は、2 つの構築パターンを示しています。

ServiceIdGrain id = ServiceIdFactory.defaultValue()
    .withType(ServiceType.Foo)
    .withName("my-foo-service");

ServiceConfigBuilder cfg = ServiceConfigFactory.newBuilder()
    .setHost("localhost")
    .setPort(8080)
    .setUser("abc")
    .setType(JvmConfig.DEFAULT);

ServiceInfoGrain info = ServiceInfoFactory.defaultValue()
    .withId(id)
    .withConfig(cfg.build());

あなたのpublic finalフィールドほど単純ではないことはわかっていますが、ゲッターとセッターなしでは継承と合成は不可能です。そして、これらのオブジェクトは MessagePack で簡単に読み書きできます。

MessagePack msgpack = MessagePackTools.newGrainsMessagePack();

byte[] data = msgpack.write(info);
ServiceInfoGrain unpacked = msgpack.read(data, ServiceInfoGrain.class);

Grainsフレームワークが機能しない場合は、気軽にMessagePack テンプレートを調べてください。TemplateBuilderリフレクションを使用して、手書きのドメイン モデルの最終フィールドを設定するジェネリックを作成できます。秘訣はTemplateRegistry、カスタム ビルダーの登録を許可するカスタムを作成することです。

于 2013-06-25T04:08:06.240 に答える
0

アプリケーションの読み取りと書き込みの問題を分離するのではなく、マージしたようです。この時点でおそらく CQRS を検討する必要があります。

私の経験では、不変のドメイン オブジェクトはほとんどの場合、監査ストーリー (要件) に関連付けられているか、ルックアップ データ (列挙型) に関連付けられています。

ほとんどの場合、ドメインは変更可能である必要がありますが、それでもゲッターとセッターは必要ありません。代わりに、変更されたドメイン モデルを生成し、ドメインで何か興味深いことが発生したときにイベントを発生させる動詞をオブジェクトに設定する必要があります (ビジネスにとって興味深い -- ビジネス == 誰かがあなたの時間にお金を払っている)。おそらく、ドメイン オブジェクトではなく、ネットワーク経由で渡すことに関心があるのはイベントです。おそらくそれはコマンドです (これらはイベントに似ていますが、ソースは、ドメインが存在する境界付けられたコンテキストの外部にあるエージェントです。イベントは、モデルの境界付けられたコンテキストの内部にあります)。

イベントを永続化するサービス (およびコマンドを永続化する別のサービス) を持つことができます。これは、監査ログでもあります (監査ストーリーを実現します)。

イベントをバスにプッシュするイベント ハンドラーを使用できます。これらのイベントには、単純な情報またはエンティティ ID が含まれている必要があります。これらのイベントに応答するサービスは、提供された情報を使用して義務を実行するか、指定された ID を使用して必要な情報を照会する必要があります。

ドメイン モデルの内部状態を公開するべきではありません。そうすることでカプセル化を破っていますが、それは本当に望ましいことではありません。私があなたなら、Axon Frameworkを見てみたいと思います。MessagePack だけよりも先に進む可能性があります。

于 2013-06-05T06:59:30.917 に答える