dddSample 所感 (Draft)

パッケージ構造


├─com
│  ├─aggregator
│  └─pathfinder
│      ├─api
│      └─internal
└─se
    └─citerus
        └─dddsample
            ├─application
            │  ├─impl
            │  └─util
            ├─domain
            │  ├─model
            │  │  ├─cargo
            │  │  ├─handling
            │  │  ├─location
            │  │  └─voyage
            │  ├─service
            │  └─shared
            │      └─experimental
            ├─infrastructure
            │  ├─messaging
            │  │  └─jms
            │  ├─persistence
            │  │  └─hibernate
            │  └─routing
            └─interfaces
                ├─booking
                │  ├─facade
                │  │  ├─dto
                │  │  └─internal
                │  │      └─assembler
                │  └─web
                ├─handling
                │  ├─file
                │  └─ws
                └─tracking

  • se のパッケージが主になるパッケージ
  • com は別コンテキストの位置付け?

Entity


  • 継承元は Entity インターフェイス

package se.citerus.dddsample.domain.shared;

public interface Entity<T> {
  boolean sameIdentityAs(T other);
}

package se.citerus.dddsample.domain.shared.experimental;

public interface Entity<T,ID> {
  boolean sameIdentityAs(T other);
  ID identity();
}

  • 派生クラス Cago, Location, Voyage の3種類
  • 派生クラス EntitySupport の1種類
  • sameIdentityAs() の実装例

 @Override
 public boolean sameIdentityAs(final Cargo other) {
   return other != null && trackingId.sameValueAs(other.trackingId);
 }

ValueObject


  • 継承元は ValueObject インターフェイス
package se.citerus.dddsample.domain.shared.experimental;

public interface ValueObject<T> {
  boolean sameValueAs(T other);
  T copy();
}

  • se.citerus.dddsample.domain.model 配下に基本的に置かれる。
  • 単純なJavaBeanではない。ビジネスロジックあり。
  • enum の ValueObject あり。
  • HandlingEvent のインナークラス enum Type はなぜインナークラス?

Service


  • BookingService, CargoInspectionService, HandlingEventService, RoutingService の4つ。

public interface BookingService {
  TrackingId bookNewCargo(UnLocode origin, UnLocode destination, Date arrivalDeadline);
  List<Itinerary> requestPossibleRoutesForCargo(TrackingId trackingId);
  void assignCargoToRoute(Itinerary itinerary, TrackingId trackingId);
  void changeDestination(TrackingId trackingId, UnLocode unLocode);
}

public interface CargoInspectionService {
  void inspectCargo(TrackingId trackingId);
}

public interface HandlingEventService {
 void registerHandlingEvent(Date completionTime,
                            TrackingId trackingId,
                            VoyageNumber voyageNumber,
                            UnLocode unLocode,
                            HandlingEvent.Type type) throws CannotCreateHandlingEventException;
}

public interface RoutingService {
 List<Itinerary> fetchRoutesForSpecification(RouteSpecification routeSpecification);
}

  • RoutingService はdomain パッケージの Service 。この実装は infrastructure パッケージに配置されている。GraphTraversalService という外部コンテキストのサービスを使っているからだろう。
  • ValueObject, Entity 関係なく触っている。
  • interface はテスト為に抽出していると思われる。
  • ApplicationEvents は Service と分類してもいいのだろうか?

Aggregate

  • これはちょっと保留。aggregator パッケージって Aggregate の事を言っているの?

Repository

  • 各 Entity に相当する Repository がある。
public interface CargoRepository {
  Cargo find(TrackingId trackingId);
  List<Cargo> findAll();
  void store(Cargo cargo);
  TrackingId nextTrackingId();
}

public interface VoyageRepository {
  Voyage find(VoyageNumber voyageNumber);
}

public interface LocationRepository {
  Location find(UnLocode unLocode);
  List<Location> findAll();
}
  • DomainEvent に対するRerpository も存在した。
public interface HandlingEventRepository {
  void store(HandlingEvent event);
  HandlingHistory lookupHandlingHistoryOfCargo(TrackingId trackingId);
}
  • Repository の実体はすべて Hibernate に依存したクラス。4つの実体クラスともに HibernateRepository を親としており、このクラスで HibernateSession をコントロールしている。
  • 実質問題として、各テストケース毎に Repository の実体を作るのはかなり面倒くさそう。

Specification

  • 親インターフェイス の Specification と抽象クラス AbstractSpecification
public interface Specification<T> {
  boolean isSatisfiedBy(T t);
  Specification<T> and(Specification<T> specification);
  Specification<T> or(Specification<T> specification);
  Specification<T> not(Specification<T> specification);
}

public abstract class AbstractSpecification<T> implements Specification<T> {
  public abstract boolean isSatisfiedBy(T t);
  public Specification<T> and(final Specification<T> specification) {
    return new AndSpecification<T>(this, specification);
  }
  public Specification<T> or(final Specification<T> specification) {
    return new OrSpecification<T>(this, specification);
  }
  public Specification<T> not(final Specification<T> specification) {
    return new NotSpecification<T>(specification);
  }
}

  • AbstractSpecification は DIP に違反している。Wikiでもそうだった。
  • and(), or(), not() の ディスパッチ先は、AndSpecification, OrSpecification, NotSpecification。isSatisfiedBy() の前にそれぞれの演算子がついている。
 public boolean isSatisfiedBy(final T t) {
   return spec1.isSatisfiedBy(t) && spec2.isSatisfiedBy(t);
 }

  • 必ず true, false を返す AlwaysTrueSpec, AlwaysFalseSpec
  public boolean isSatisfiedBy(Object o) {
    return true;
  }

  • そして重要なビジネスルールが実装されているのが RouteSpecification 。なお、ジェネリクス指定されているのは Itinerary【旅程】(ValueObject)。おっと ValueObject も implements している。
public class RouteSpecification extends AbstractSpecification<Itinerary> implements ValueObject<RouteSpecification> {
  private Location origin;
  private Location destination;
  private Date arrivalDeadline;
  public RouteSpecification(final Location origin, final Location destination, final Date arrivalDeadline) {
    Validate.notNull(origin, "Origin is required");
    Validate.notNull(destination, "Destination is required");
    Validate.notNull(arrivalDeadline, "Arrival deadline is required");
    Validate.isTrue(!origin.sameIdentityAs(destination), "Origin and destination can't be the same: " + origin);
    this.origin = origin;
    this.destination = destination;
    this.arrivalDeadline = (Date) arrivalDeadline.clone();
  }
  @Override
  public boolean isSatisfiedBy(final Itinerary itinerary) {
    return itinerary != null &&
           origin().sameIdentityAs(itinerary.initialDepartureLocation()) &&
           destination().sameIdentityAs(itinerary.finalArrivalLocation()) &&
           arrivalDeadline().after(itinerary.finalArrivalDate());
  }
  @Override
  public boolean sameValueAs(final RouteSpecification other) {
    return other != null && new EqualsBuilder().
      append(this.origin, other.origin).
      append(this.destination, other.destination).
      append(this.arrivalDeadline, other.arrivalDeadline).
      isEquals();
  }
  ...
}

  • RouteSpecification.isSatisfiedBy() を Delivery のcalculateRoutingStatus()で呼んでいる。配達のルートが正しいかどうかを判定している?calculateRoutingStatus()呼出し元はなぜか private コンストラクタ。
public class Delivery implements ValueObject<Delivery> {
  private Delivery(HandlingEvent lastEvent, Itinerary itinerary, RouteSpecification routeSpecification) {
    ...
    this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
    ...
  }
  ...
  private RoutingStatus calculateRoutingStatus(Itinerary itinerary, RouteSpecification routeSpecification) {
    if (itinerary == null) {
      return NOT_ROUTED;
    } else {
      if (routeSpecification.isSatisfiedBy(itinerary)) {
        return ROUTED;
      } else {
        return MISROUTED;
      }
    }
  }

DomainEvent

  • やっぱ本質をわかっていない。HandlingEventFactory と合わせて再確認の必要あり。

その他

  • CargoAdminController は Spring のMultiActionControllerのサブクラス
  • ApplicationEvents は、Service と同じパッケージの配下にいるが、実装は infrastructure パッケージに配置。
  • interfaces のパッケージは別に時間をとってみた方がいいかも。Assembler クラスが domain クラスと Dto (JSPで使う JavaBean )の依存関係をぶった切っている。これでいいのか悪いのか話をする必要がありそう。
  • パッケージ構造もどこかで話をするべき。単純に Entity とか ValueObject とかのパッケージがあるわけでないので、Why を考えるとクラス構造が見えてきそう。
  • 動かしていないのでなんだが、DDL はどこにあるのだろう?
  • Negative発言だがこれを見ても積み荷に関する業務に詳しくなれた気がしない...


名前:
コメント:

タグ:

+ タグ編集
  • タグ:
最終更新:2009年08月27日 11:15
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。