4

私は、従業員の転勤のためのサービスを実行するベンダーを見つける会社のプロジェクトに取り組んでいます。これらのサービスは、ピアノの準備や輸送用、貴重品用の箱の作成など、引っ越し業者が行う専門知識がないものです。

このドメインでは、注文には1:多くの場所があります。

引っ越し業界では、ベンダーが要求されたサービスを実行するまで、注文は頻繁に流動的です。したがって、このモデルには、注文と場所に適用されるいくつかのステータス(送信済み、キャンセル済み、保留中など)があります。

ここに適用される非常に単純なビジネスルールがいくつかあります。サンプリングは次のとおりです。

  1. 注文が保留になると、すべての場所が保留になります。
  2. 親の注文が保留中の場合、ロケーションを保留から外すことはできません。

等々。これらの規則から、これが集合的なルート境界を形成することは私には明らかであるように思われます。したがって、私は、コンテキスト/サービス/あなたがそれを呼びたいものの名前であるMyClient.Statuses.Order集合体を持っています:Statuses

public class Order {
    private Guid _id;
    private OrderStatus _status;

    public void PlaceOnHold() {
        if (_status == OrderStatus.Cancelled)
            // throw exception

        _status = OrderStatus.OnHold;
        Locations.ForEach(loc => loc.PlaceOnHold());
    } 

    public void PlaceLocationOnHold(Guid id) {
        if (_status == OrderStatus.Cancelled)
            // throw exception
        Locations.Single(loc => loc.Id == id).PlaceOnHold();
    }

    // etc...

        private Location[] Locations;
}

internal class Location { 
    public Guid Id;
    public LocationStatus Status;

    public void PlaceOnHold() {
        // It's ok for a cancelled location on a non-cancelled order,
        // but a Location cannot be placed On Hold if it's Cancelled so 
        // just ignore it
        if (Status == LocationStatus.Cancelled)
            return; 

        Status = LocationStatus.OnHold;
    }
}

これらのオブジェクト(Order、Location)は両方とも、他のコンテキストではGUID IDを持っています(たとえば、状態遷移を持たないCRUDベースの属性の場合)。だから今、私たちはついに私の質問に行き着きます:

場所を保留にするコマンドとハンドラーを作成するにはどうすればよいですか?

結合を最小限に抑えるために、これをDRYおよびサービス指向に保ちたいのですが、2つのエンティティ間の親子関係を1か所だけに保つことは非常に困難です。

オプション1-単一のロケーションID:

public class PlaceLocationOnHold_V1 {
    public readonly Guid Id;
}

public class PlaceLocationOnHold_V1Handler {
    public void Handle(PlaceLocationOnHold_V1 command) {
        // This is typically a no-no.  Should only fetch by OrderId:
        var aggregate = _repository.GetByLocationId(command.Id);  

        aggregate.PlaceLocationOnHold(command.Id);
        _repository.Save();
    }
}

オプション2-注文IDと場所ID:

public class PlaceLocationOnHold_V2 {
    public readonly Guid OrderId; // This feels redundant
    public readonly Guid LocationId;
}

public class PlaceLocationOnHold_V2Handler {
    public void Handle(PlaceLocationOnHold_V2 command) {
        var aggregate = _repository.GetById(command.OrderId);
        aggregate.PlaceLocationOnHold(command.LocationId);
        _repository.Save();
    }
}

オプション3-「注文に属する場所」をカプセル化するクラスを持つ単一のパラメーター

public class LocationIdentity {
    public Guid Id;
    public Guid OrderId;
}

public class PlaceLocationOnHold_V3 {
    public readonly LocationIdentity Location;
}

public class PlaceLocationOnHold_V3Handler {
    public void Handle(PlaceLocationOnHold_V3 command) {
        var aggregate = _repository.GetById(command.Location.OrderId);  
        aggregate.PlaceLocationOnHold(command.Location.Id);
        _repository.Save();
    }
}
4

1 に答える 1

8

効果的な骨材設計に関するVaughnVernonの記事をご覧ください。具体的には、パート2-相互に通信する骨材のモデリングに関するいくつかの優れた情報があります。

設計に欠けている主な点は、すでに述べたように、これらは両方ともARであり、グローバルに識別可能であるということです。したがって、IDで相互に参照している必要があります。Orderは、ロケーションの子コレクションを保持するべきではありません。

したがって、OrderクラスにはLocationIdのコレクションがあり、LocationにはOrderIdがあります。

public class Order
{
    private Guid _id;
    private OrderStatus _status;
    private Guid[] _locationIds;
    //...
}

public class Location
{
    private Guid _id;
    private Guid _orderId;
    //...
}

それを正しく理解すれば、オプション#1は理にかなっています。Locationはそれ自体がARであるため、Order ARを経由せずに、それをインスタンス化してPlaceOnHoldを直接呼び出すことができます。

1つのARの変更が他のARに浸透する状況(つまり、注文を保留にすると、すべての場所も保留になります)については、ドメインイベントまたは結果整合性を使用できます。

public class Order
{
    //... private instance variables

    public void PlaceOnHold()
    {
        if (_status == OrderStatus.Cancelled)
          // throw exception

        _status == Orderstatus.OnHold;

        DomainEvents.Handle(new OrderPlacedOnHold(_id)); // handle this, look up the related locations and call PlaceOnHold on each of them)
    }
}

また、Locationの保留を解除しようとしているが、Orderが保留中であり、アクションが不正である場合は、コマンドハンドラーでOrderオブジェクトをインスタンス化し、LocationのRemoveFromHoldメソッドに渡すことができます。Vernonはこれに言及し、トランザクションごとに1つのARしか変更できないからといって、トランザクションで複数のARをインスタンス化できないことを意味しないという点を繰り返し述べています。

public class RemoveHoldFromLocation : IHandler<RemoveHoldFromLocationCommand>
{
    public void Execute(RemoveHoldFromLocationCommand cmd)
    {
        var location = locationRepo.Get(cmd.LocationId);
        var order = orderRepo.Get(location.GetOrderId());

        location.RemoveHold(order.GetStatus());
    }
}

public class Location
{
    //... private instance variables, etc.

    public void RemoveHold(OrderStatus orderStatus)
    {
        if (orderStatus == OrderStatus.OnHold)
            // throw Exception

        _status == LocationStatus.OnHold;
    }
}

これは単なる擬似コードなので、タイプミスなどはご容赦ください。同様のコードサンプルがVernonPDFにあります。

于 2012-08-07T18:48:05.310 に答える