私は、従業員の転勤のためのサービスを実行するベンダーを見つける会社のプロジェクトに取り組んでいます。これらのサービスは、ピアノの準備や輸送用、貴重品用の箱の作成など、引っ越し業者が行う専門知識がないものです。
このドメインでは、注文には1:多くの場所があります。
引っ越し業界では、ベンダーが要求されたサービスを実行するまで、注文は頻繁に流動的です。したがって、このモデルには、注文と場所に適用されるいくつかのステータス(送信済み、キャンセル済み、保留中など)があります。
ここに適用される非常に単純なビジネスルールがいくつかあります。サンプリングは次のとおりです。
- 注文が保留になると、すべての場所が保留になります。
- 親の注文が保留中の場合、ロケーションを保留から外すことはできません。
等々。これらの規則から、これが集合的なルート境界を形成することは私には明らかであるように思われます。したがって、私は、コンテキスト/サービス/あなたがそれを呼びたいものの名前である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();
}
}