了解域实体中的域服务与应用程序服务与行为(方法)

时间:2012-05-31 03:12:23

标签: asp.net-mvc architecture domain-driven-design service-layer

我们一直致力于为分层应用程序开发架构。我们计划建立一个ASP.NET MVC表示层,同时为移动浏览器和“普通”浏览器提供服务。也就是说,如果用户在移动设备上导航到该站点,我们需要相同的ASP.NET MVC表示层来提供针对移动浏览器优化的视图,同时提供针对经过优化的“常规”视图桌面/笔记本电脑浏览器,如果用户使用他们的台式机/笔记本电脑目前,我们还没有计划支持原生移动应用,但我们可能会在未来添加该功能。此外,该体系结构将具有域模型的层 - 域实体,域服务等,应用程序服务和使用EF代码优先POCO以及存储库和工作单元模式的数据层 - 所有标准域模型类型设计。

但是,我一直试图找出域实体中的域服务,应用程序服务和行为(方法)之间的区别,因为它适用于我们正在开发的系统。以下是我们系统中的高级用例。

我们有Reports和ReportGroups。这两个实体之间存在多对多关系。数据库表格是:Reports< - > Jct_Reports_ReportGroups< - > ReportGroups,域实体看起来像:

Report class :
    public string ReportName { get; set; }
    ...
    public virtual ICollection<ReportGroup> ReportGroups { get; set; }

ReportGroup class :
    public string GroupName { get; set; }
    ...
    public virtual ICollection<Report> Reports { get; set; }

当然,用户可以创建报告(无需创建报告组),他们可以创建包含一个或多个报告的报告组。此外,用户可以删除报告。当发生这种情况时,会发生许多事情。首先,我们检查报告是否在任何报告组中(单个报告可以在许多不同的报告组中)。如果在任何报告组中找到报告,我们将遍历每个报告组并从报告组中删除报告(通过删除Jct_Reports_ReportGroups表中的一行)。然后,对于包含要删除的报告的每个报告组,我们会检查报告组中是否还有其他报告。如果报告组中没有其他报告,我们还会删除报告组(通过删除ReportGroups表中的行)。如果报告组中还有其他报告,我们不会删除报告组。如果所有这些操作都成功,我们将删除用户选择的要删除的报告(通过删除Reports表中的行)。最后,用户会显示一条消息,说明报告删除是否成功。

我希望这是一个足够的用例来帮助理解我们的整体应用程序中的一小部分。

我在某处读过域服务封装了自然不适合域对象的业务逻辑,并且不是典型的CRUD操作,而外部消费者使用应用程序服务与您的系统通信 - 如果消费者需要访问对于CRUD操作,它们将暴露在这里。但是,我只是不了解POCO域实体中将采用哪些方法(业务逻辑),哪些业务逻辑将被视为域服务并包含在业务层中,以及哪些业务逻辑将被视为包含在应用服务层。所以,我的主要问题是:鉴于上面的用例,关于如何在域实体中突破域服务与应用服务与行为(方法)的建议是什么?

1 个答案:

答案 0 :(得分:4)

这是一个很大的问题。

我更喜欢以下非严格的指导原则:

应用程序服务:想象一下,我们有一组描述用户与系统交互的用例。假设我们有LoginCash transfer个具有特定输入和输出的案例;他们还描述了成功和失败的情景。有了这个,我们通常都有一个应用程序服务规范:我公开了LoginResul Login(name, pass)TransferResult CashTrasnfer(from, to, amount)方法,在out和out以及成功/失败行为中或多或少相同。

很明显,为了实现用例,应用程序服务调用BL(但增加了安全性和其他特定于应用程序的检查)。

域名服务:就像您描述的那样:'自然不适合域对象'。

拥有CashTrasnfer用例,我们必须:

  • 从'
  • 加载帐户'
  • 将帐户'加载'到'
  • 检查'来自'余额'//可能会返回错误
  • 验证帐户访问权限(已锁定等)//可能会返回错误
  • 从'from'中撤回'金额'
  • 将金额计入'to'
  • (理想情况下,这应该是单个“商家”交易)。

很明显,这个函数不适合Account实体 - 所以最好有一个特殊的TransferService


特别是关于Report / ReportGroup功能 - 它可以是实体范围,因为没有非逻辑引用(所有项都可以从报告中访问):

Report.Remove() {
    foreach(group in Groups) {
        group.RemoveReport(this);
    }
    repository.Remove(this);
} 
Group.RemoveReport(report) {
    reports.Remove(report);
    if(reports.Count == 0)
        repository.Remove(this);
}

或在报表服务和实体之间拆分(引用存储库可能会有问题):

Report.RemoveFromAllGroups() {
   foreach(group in Groups) {
        group.RemoveReport(this);
        if(group.IsEmpty)
            //add to collection to return
    }
}
Service.Remove(report) 
{
    var emptyGroups = report.RemoveFromAllGroups();
    reportRepo.Remove(report);
    groupRepo.Remove(emptyGroups);
}