DDDによって設計されました。 パート1:ドメインとアプリケーション

xxx:あるライブラリをダウンロードしている間、別のライブラリを0.5メートルのxml構成で接着している間、休止状態のマッピングを構成する間、ベースを描画する間、Webサービスを上げる間

xxx:みなさんこんにちは世界を書いているようですが、2週間が過ぎており、これは小規模ビジネス向けの会計システムであるように皆に思われます

ibash.org.ru




一連のいくつかの記事では、シンプルでありながらニュアンスを持ちたいと思います。2つの異なる人気のあるテクノロジー-Entity Frameworkを使用して永続化のためのインフラストラクチャを実装するための既製のドメインおよびアプリケーションレイヤーの例コードファーストと流entなNHibernate 。 DDDの3文字についても聞いたことがあるのに、3文字も送信したくない場合でも、他の文字は猫の下に置いてください。







実際、まさにDDD(ドメイン駆動型開発)という名前は、開発がインフラストラクチャや他の何かによってではなく、サブジェクトエリアによって「実行」されることを示しています。 設計時の理想的な状況では、どのデータストレージのフレームワークを使用するかを考えてはならず、何らかの形でドメインをその機能に変更する必要があります-これが私たちがやることです:最初の記事では、インフラストラクチャを実際に実装する例を説明します。



ドメイン層



時間を整理する方法の1つは、TODOシート(特定の日のタスク計画)を作成することです。 これには多くのプログラムがあります(はい、同じOutlook)-これらのプログラムの1つを取り上げます。 このサブジェクト領域を分析すると、いくつかのエンティティを一度に区別できます。



これらすべてを、仕様とリポジトリコントラクトを追加したコードで組み立てると、このようなグラフが得られます(CodeMapのおかげです)。



名前が最も成功しているわけではないことに同意します(特に「MultipleDailyTask」ですが、残念ながら他の人には決して起こりません)。 このグラフは、オブジェクトの属性構成に関する情報を提供するものではありませんが、サブジェクト領域全体を示しています。 次のコードリストは、この見落としを修正します。



リスト


集計
タスクベース
public abstract class TaskBase : Entity { protected TaskBase(string summary, string desc) { ChangeSummary(summary); ChangeDescription(desc); } protected TaskBase() { } public string Summary { get; private set; } public string Description { get; private set; } public bool IsComplete { get; protected set; } public virtual void Complete() { if (IsComplete) throw new InvalidOperationException("Task is already completed"); IsComplete = true; } public void ChangeSummary(string summary) { Summary = summary; } public void ChangeDescription(string description) { Description = description; } }
      
      









IndeterminateTask
  public class IndeterminateTask : TaskBase { public IndeterminateTask(string summary, string desc, Category category) : base(summary, desc) { Category = category; } protected IndeterminateTask() { } public Category Category { get; private set; } public void ChangeCategory(Category category) { Category = category; } }
      
      









DailyTask
 public abstract class DailyTask : TaskBase { protected DailyTask(string summary, string desc, DateTime setupDay) : base(summary, desc) { DueToDate = setupDay; } protected DailyTask() { } public DateTime DueToDate { get; private set; } public void ChangeDueDate(DateTime dueToDate) { DueToDate = dueToDate; } }
      
      









SingleDailyTask
  public class SingleDailyTask : DailyTask { public SingleDailyTask(string summary, string desc, DateTime setupDay, bool isWholeDay) : base(summary, desc, setupDay) { IsWholeDay = isWholeDay; } protected SingleDailyTask() { } public bool IsWholeDay { get; private set; } public void ChangeIsWholeDay(bool isWholeDay) { IsWholeDay = isWholeDay; if (IsWholeDay) { ChangeDueDate(DueToDate.Date); } } }
      
      









MultipleDailyTask
  public class MultipleDailyTask : DailyTask { public MultipleDailyTask(string summary, string desc, DateTime setupDay, IEnumerable<DateTime> dueToDates) : base(summary, desc, setupDay) { ChangeSubtasks(dueToDates.ToList()); } protected MultipleDailyTask() { } public virtual ICollection<Subtask> Subtasks { get; set; } public override void Complete() { throw new NotSupportedException(); } public void CompleteSubtask(DateTime subtaskDueDate) { if (Subtasks == null) throw new InvalidOperationException(); var subtask = Subtasks.FirstOrDefault(i => i.DueTime == subtaskDueDate); if (subtask == null) throw new InvalidOperationException(); subtask.Complete(DateTime.Now); var hasUncompleted = Subtasks.Any(i => i.CompletedAt == null); if (!hasUncompleted) { base.Complete(); } } public bool HasUncompletedSubtasks { get { return Subtasks != null && Subtasks.Any(i => i.CompletedAt == null); } } public int CompletionPercentage { get { var totalSubtasks = Subtasks.Count; var completedSubtasks = Subtasks.Count(i => i.CompletedAt.HasValue); if (totalSubtasks == 0 || totalSubtasks == completedSubtasks) return 100; return (int) Math.Round(completedSubtasks * 100.0 / totalSubtasks, 0); } } public void ChangeSubtasks(ICollection<DateTime> subtasksDueToDates) { var times = subtasksDueToDates.Select(i => i.ToTime()); if (Subtasks == null) { Subtasks = times.Select(i => new Subtask(i)).ToList(); return; } var oldSubtasks = Subtasks.ToList(); var newSubtasks = times.ToList(); //removing no longer exist items foreach (var oldSubtask in oldSubtasks) { if (!newSubtasks.Contains(oldSubtask.DueTime)) { Subtasks.Remove(oldSubtask); } } //adding new foreach (var newSubtask in newSubtasks) { if (Subtasks.All(i => i.DueTime != newSubtask)) { Subtasks.Add(new Subtask(newSubtask)); } } } }
      
      









サブタスク
  public class Subtask : Entity { public DateTime DueTime { get; private set; } public DateTime? CompletedAt { get; private set; } public Subtask(DateTime dueTime) { DueTime = dueTime; } public void Complete(DateTime completedAt) { CompletedAt = completedAt; } protected Subtask() { } }
      
      









カテゴリー
  public class Category : Entity { public Category(string name, Category parentCategory) { Name = name; ParentCategory = parentCategory; } protected Category() { } public string Name { get; private set; } public virtual ICollection<IndeterminateTask> Tasks { get; set; } public virtual ICollection<Category> ChildrenCategories { get; set; } public virtual Category ParentCategory { get; private set; } public void ChangeName(string name) { Name = name; } public void ChangeParentCategory(Category category) { ParentCategory = category; } }
      
      









リポジトリ
  public interface IRepository { } public interface ITaskRepository : IRepository { IEnumerable<TaskBase> AllMatching(Specification<TaskBase> specification); void Add(TaskBase taskBase); void Remove(TaskBase taskBase); TaskBase Get(Guid taskId); } public interface ICategoryRepository : IRepository { IEnumerable<Category> All(); void Add(Category category); void Remove(Category category); Category Get(Guid id); }
      
      







仕様書
  public static class CategorySpecifications { public static Specification<Category> Name(string name) { return new DirectSpecification<Category>(category => category.Name == name); } } public static class TaskSpecifications { public static Specification<TaskBase> CompletedTask() { return new DirectSpecification<TaskBase>(task => task.IsComplete); } public static Specification<TaskBase> DueToDateRange(DateTime startDateIncl, DateTime endDateIncl) { var spec = IsDailyTask(); spec &= new DirectSpecification<TaskBase>(task => ((DailyTask)task).DueToDate >= startDateIncl && ((DailyTask)task).DueToDate <= endDateIncl); return spec; } public static Specification<TaskBase> IsIndeterminatedTask() { return new DirectSpecification<TaskBase>(task => task is IndeterminateTask); } public static Specification<TaskBase> IsDailyTask() { return new DirectSpecification<TaskBase>(task => task is DailyTask); } }
      
      









仕様とリポジトリについては、私の別の記事で読むことができます。

ご覧のとおり、例は単純ですが、ニュアンスがあります-ビジネスオブジェクトの継承(相続人の一部は他のオブジェクトへの独自の接続を持っています)、相互関係、カテゴリの階層(接続自体)-これはすべて、いくつかのかゆみを引き起こす可能性がありますインフラストラクチャを実装するときの1つの場所。 IRepositoryが空であり、AllMatching、GetById、Delete、Addなどのすべての種類のメソッドでいっぱいではないことも注目に値します。 -実際にはより良い:特定のリポジトリごとに、必要な方法のみを決定します。 汎用リポジトリが悪である理由は、 ここで読むことができます



アプリケーションおよび分散サービスレイヤー



この例のアプリケーションレイヤーは、DTOオブジェクトを定義するためのアセンブリと、サービスおよびDTO <->エンティティアダプター用の2つのアセンブリによって表されます。分散サービスレイヤーは、単にサービスメソッドを転送する1つのWCFサービス(ファサード)を持つWeb空のアプリケーションです同じDTOを使用するアプリケーションレベル(WCF DataContractSerializerの利点は属性を必要とせず、クラス階層を操作できます)

例として、タスクを削除することと、今月のすべてのタスクを受信することの2つのサービスメソッドを検討します。



アプリケーション層:



 public void RemoveTask(Guid taskId) { using (var unitOfWork = UnitOfWorkFactory.Create()) { var task = _tasksRepository.Get(taskId); if (task == null) throw new Exception(); _tasksRepository.Remove(task); unitOfWork.Commit(); } } public IEnumerable<DailyTaskDTO> GetMonthTasks() { var nowDate = DateTime.Now; var monthStartDate = new DateTime(nowDate.Year, nowDate.Month, 1); var monthEndDate = new DateTime(nowDate.Year, nowDate.Month, DateTime.DaysInMonth(nowDate.Year, nowDate.Month)); var specification = TaskSpecifications.DueToInDateRange(monthStartDate, monthEndDate); var tasks = _tasksRepository.AllMatching(specification).ToList(); return tasks.OfType<DailyTask>().ProjectedAsCollection<DailyTaskDTO>().ToArray(); }
      
      





そして、分散サービスに潜入



 public void RemoveTask(Guid taskId) { using (ILifetimeScope container = BeginLifetimeScope()) { container.Resolve<TaskService>().RemoveTask(taskId); } } public List<DailyTaskDTO> GetMonthTasks() { using (ILifetimeScope container = BeginLifetimeScope()) { return container.Resolve<TaskService>().GetMonthTasks().ToList(); } }
      
      





ご覧のとおり、アプリケーション層はすべての汚い作業を行います。リポジトリの呼び出し、トランザクションのラップ、エンティティのDTOへの変換(素晴らしいツールAutoMapperを使用)。 Distributed ServicesのLifetimeScopeでサービスメソッドをラップすると、単一のUnitOfWork(ライフタイムスコープごとに1つのインスタンス)オブジェクトでリポジトリを初期化できます。

実際には退屈なテキストと水-これらはソースです。



インフラ



この記事の目的は、DDDが何であるかを説明することではなく、これに関する多くの書籍があり、これは記事の形式に適合しません-インフラストラクチャの実装に注意を払うことを望みました。具体的には、この記事は単なる紹介です。



All Articles