1

「ドメインイベントはドメイン内で起こったことの表現である」という定義に基づいて、ドメインイベントのフィールドとしてドメインオブジェクトを使用するのは自然なことです。

イベント ソーシングを使用する場合、ドメイン イベントは永続的です。そのため、フィールドとしてドメイン オブジェクトを使用する場合、ドメイン オブジェクトも永続的です。これにより、CQRS とイベント ソーシングを採用することで得られる利点が希薄になり、ドメイン オブジェクトの変更と進化がより困難になります。

Eric Evans の dddsample の CQRS バージョンを考えてみましょう。ユーザー ストーリーは次のとおりです。

Given a cargo has been registered
And I request possible routes for the cargo
And some routes are shown
When I pick up a candidate
Then the cargo is assigned to the route


public class Cargo { // This is an aggregate
    private TrackingId trackingId;
    private RouteSpecification routeSpecification;

    public void assignToRoute(final Itinerary itinerary) {
        Delivery delivery = Delivery.derivedFrom(routeSpecification, itinerary);
        apply(new CargoAssignedEvent(this.trackingId, 
            itinerary, delivery.routingStatus()));//sending the domain event
    }
}

public class Itinerary { //This is a value object
    private List<Leg> legs;
}

public class Leg { //Another value object
    private VoyageNumber voyageNumber;
    private UnLocode loadLocation;
    private UnLocode unloadLocation;
    private Date loadTime;
    private Date unloadTime;
}

public class CargoAssignedEvent { // This is a domain event
    private final String trackingId;
    private final RouteCandidateDto route; //DTO form of itinerary containing a List of LegDto s
    private final String routingStatus;

    public CargoAssignedEvent(TrackingId trackingId, Itinerary itinerary,
        RoutingStatus routingStatus) {
        this.trackingId = trackingId.getValue(); //transform to primitive
        this.route = toRoute(itinerary); ////transform to DTO
        this.routingStatus = routingStatus.getCode(); //transform to primitive
    }
    ......
}

ご覧のとおり、DTO を DomainEvent のフィールドとして使用して、ドメイン モデル (Itinerary、RoutingStatus) をイベントの永続性の問題から分離しています。ただし、これはイベント ハンドラ側で不便や問題を引き起こす可能性があります。CargoAssignedEvent の一部のサブスクライバーが決定を下すために旅程の派生を必要とする場合はどうなりますか? 次に、RouteCandidateDto を Itinerary にマップする必要があります。

考えられる解決策は、ドメイン オブジェクトをフィールドとして使用することですが、イベント ストアにいくつかのアダプターを導入します。イベントをロードまたは保存するときに、アダプタを使用してドメイン オブジェクトと dto をマップします。

私はそれを正しくやっていますか?どんなアイデアでも大歓迎です。

アップデート

旅程はおそらく特殊なケースです。これは値全体と見なされるため、この値オブジェクトを CargoLegEvent(TrackingId, Leg) のような小さなドメイン イベントのグループに分割することはできません。配送のケースを考えてみましょう。配送は貨物ドメインのもう 1 つの重要な値オブジェクトであり、旅程よりもはるかに豊富です。

/**
 * The actual transportation of the cargo, as opposed to
 * the customer requirement (RouteSpecification) and the plan (Itinerary). 
 *
 */
public class Delivery {//value object

  private TransportStatus transportStatus;
  private Location lastKnownLocation;
  private Voyage currentVoyage;
  private boolean misdirected;
  private Date eta;
  private HandlingActivity nextExpectedActivity;
  private boolean isUnloadedAtDestination;
  private RoutingStatus routingStatus;
  private Date calculatedAt;
  private HandlingEvent lastEvent;
  .....rich behavior omitted
}

配達は貨物の現在の状態を示し、貨物の新しい取り扱いイベントが登録されるか、ルート仕様が変更されると再計算されます。

//non-cqrs style of cargo
public void specifyNewRoute(final RouteSpecification routeSpecification) {
     this.routeSpecification = routeSpecification;
     // Handling consistency within the Cargo aggregate synchronously
     this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
}

/**
  * Updates all aspects of the cargo aggregate status based on the current
  * route specification, itinerary and handling of the cargo. <p/> When
  * either of those three changes, i.e. when a new route is specified for the
  * cargo, the cargo is assigned to a route or when the cargo is handled, the
  * status must be re-calculated. <p/> {@link RouteSpecification} and
  * {@link Itinerary} are both inside the Cargo aggregate, so changes to them
  * cause the status to be updated <b>synchronously</b>, but changes to the
  * delivery history (when a cargo is handled) cause the status update to
  * happen <b>asynchronously</b> since {@link HandlingEvent} is in a
  * different aggregate.
  */
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
    this.delivery = Delivery.derivedFrom(routeSpecification(), itinerary(),
            handlingHistory);
}

次のように、最初に CargoDeliveryUpdatedEvent が必要だと思いました。

//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
     apply(new CargoDeliveryUpdatedEvent(
         this.trackingId, delivery.derivedFrom(routeSpecification(), 
         itinerary(), handlingHistory);
}

class CargoDeliveryUpdatedEvent {
    private String trackingId;
    private DeliveryDto delivery;//DTO ?
}

しかし、最終的に、次のように、意図をよりよく明らかにできる小さなイベントを使用できることがわかりました。

//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
     final Delivery delivery = Delivery.derivedFrom(
         routeSpecification(), itinerary(), handlingHistory);
     apply(new CargoRoutingStatusRecalculatedEvent(this.trackingId, 
         delivery.routingStatus());
     apply(new CargoTransportStatusRecalculatedEvent(this.trackingId, 
         delivery.routingStatus());
     ....sends events telling other aspects of the cargo
}

イベントはより小さく、より具体的であるため、DeliveryDto とそれに伴うマッパー (ドメイン オブジェクト <--> DTO) は不要になりました。

class CargoRoutingStatusRecalculatedEvent{
    private String trackingId;
    private String routingStatus;
}

class CargoTransportStatusRecalculatedEvent{
    private String trackingId;
    private String transportStatus;
}
4

1 に答える 1

5

イベント定義は正しいです。イベントは有線で送信できるため、シリアル化され、大きくてリッチなオブジェクトを送信したくありません。ドメイン イベントが他の境界付けられたコンテキストによって使用されるという事実を追加します。これらのコンテキストは、Itinerary が意味するものとは非常に異なる定義を持っているか、他のドメイン オブジェクトについて知らない可能性があります。ポイントは、境界付けられたコンテキスト (BC) を結合することではなく、可能な限り「中立的な」情報を使用して何が起こったかを伝えることです。

プリミティブは優れており、その後に値オブジェクトが続きます。特に、それらがドメイン全体で同じことを意味する場合はそうです。

CargoAssignedEvent の一部のサブスクライバーが決定を下すために旅程の派生を必要とする場合はどうなりますか?

ドメイン オブジェクトが、サブスクライバーのニーズを推測することは不可能です。ドメイン オブジェクトの役割は、何が起こったかを示すイベントを生成することだけです。イベントを定義する方法についてのレシピはないと思います。ドメインの過去のアクションを表す必要がありますが、コードでどのように表すかは、開発者とアプリの複雑さに依存します。関連する集約ルートまたはリッチ エンティティを直接使用することは、技術的な手間がかかります。私が好んで使用するのは、ドメイン オブジェクトの初期化データとして機能するプリミティブ (可能であれば) または memento (DTO) です。ただし、イベントでドメイン オブジェクトを含めることが本当に必要であると考える場合にのみ。私は常に、リッチ オブジェクトを使用せずにイベントを定義しようとしています。

この特定の例では、イベント ハンドラーはリポジトリ/サービスを使用して必要なオブジェクトを取得できると思います。

EventStore アダプターは物事を複雑にするため、イベントのバージョン管理にのみ使用する必要があると思います。ドメイン オブジェクトがリファクタリングされたという事実は、イベント定義を変更しませんでした。

イベントにドメイン オブジェクト memento のみが含まれている場合、そのバージョン管理を処理するのはドメイン オブジェクトです。基本的には、MementoV2 を引数として新しいコンストラクターを追加するだけです。

于 2013-11-06T09:35:49.363 に答える