DDD - 如何使用基本示例编写好的,健壮的代码

时间:2014-11-11 09:58:40

标签: java design-patterns architecture domain-driven-design

对于新应用程序,我正在使用图层:

RestRessource - > ApplicationService - >域

我有一个基本用例,用户可以创建一个“bduget”。如果当前年份没有其他预算,则状态为“初始”。在可以创建新预算并将其状态“纠正”与另一个属性“索引”相关联之后:纠正#1,纠正#2,......

现在有些编码。 ApplicationService有一个名为“createBudget”的方法

@Autowired
private BudgetRepository budgetRepo;

@Autowired
private BudgetService budgetService;

public Budget createBudget(int year) {
    Budget newBudget;
    if (budgetService.existsBudgetInitialFor(year)) {
        newBudget = new Budget(year, "INITIAL");
    } else {
        newBudget = new Budget(year, "CORRECTIVE", budgetService.nextIndex());
    }
    budgetRepo.insert(newBudget);
    return newBudget;
}

BudgetService正在使用BudgetRepository来计算数据库。 existsBudgetInitialFor返回一个布尔值,如果count> 0:)

现在的问题是:

  1. 对于这个例子,我发现budgetServcie没用。在BudgetRepository内部移动existsBudgetInitialFor是否更好?
  2. 您是否认为“if ... else ...”声明处于最佳位置:是AppService层责任还是您,它是域规则并强制执行此操作,您是否在BudgetService中移动此代码? ?
  3. 要封装budgetService.nextIndex调用,是否最好起诉工厂来创建预算? (代替构造函数)。你认为现在是计算nextIndex的最佳时机吗?
  4. 应用层是子单元测试还是仅用于集成测试?为什么要问这个?当我试图对这个方法进行单元测试时,我需要至少模拟BudgetService,BudgetRepository,我在想:这是代码味道的标志吗?另一个简化单元测试的解决方案是将budgetService.existsBudgetInitialFor(year)作为私有方法提取,模拟它以我想要的方式返回TRUE / FALSE等。
  5. 我在编写此代码时并不认为创建是实体的责任。我发现awkard将“if ... else ...”逻辑放在Budget实体中。我对吗 ? (请告诉我,我至少是对的一次:D)
  6. 我问自己的最后一个问题:也许我从一开始就错了。如果UI发送createInitialBudget命令然后发送createCorrectiveBudget命令,会更好吗?在这种情况下,我们需要检查命令的有效性(以避免多次初始创建:单一验证)。
  7. 这只是一个非常简单的代码(我放弃了权限管理,验证等)。所以我尝试在编写更复杂的用例之前制作这种代码。主要目的是验证图层,向同事解释,表明可以测试此代码等。

    非常感谢!

    弗朗索瓦

3 个答案:

答案 0 :(得分:0)

  1. 您的应用程序服务似乎有业务逻辑。如果是这种情况,它属于域服务。然后应用程序服务就像你现在一样调用domainBudgetService.CreateBudget(int year)。如果需要,可以将existsBudgetInitialFor移动到存储库。

  2. 参见#1

  3. 是的工厂是首选。

  4. 如果您将预算服务方法移至存储库,那么我不认为这是一个问题。我通常只对我的域名服务和实体进行单元测试。

  5. 工厂应该消除这种尴尬

  6. 我只需要一个createBudget命令,并在内部保留逻辑就像你拥有它一样。否则,UI现在需要具有业务逻辑来确定要发送的命令类型。

  7. 希望这有帮助。

答案 1 :(得分:0)

这是一项实施问题,属于预算本身及其有关如何命名某种状态的知识:

a)域名服务:

numberOfBudgets = budgetRepository.numberOfBudgetsIn(anYear);
budget = Budget.newFrom(anYear, numberOfBudgets + 1);
budgetRepository.add(budget);

b)预算类中的公共工厂方法:

Budget.newFrom(anYear, budgetNumber) {
  status = 'INITIAL';
  if (numberOfBudgets > 0) {
    status = 'CORRECTIVE' + budgetNumber
  }
  return new Budget(anYear, status);
}

这样,您只能在a点中提到一个域名服务。

干杯, 塞巴斯蒂安。

答案 2 :(得分:0)

  

对于这个例子,我发现budgetServcie没用。搬家更好吗?   existsBudgetInitialFor在BudgetRepository内部?

好主意我说,我个人不喜欢xxxService(xxx是实体名称),名称本身并没有多大意义。

  

你认为“if ...... else ......”的声明是在好的地方:是的   它对AppService层负责或对你来说,它是一个域规则和   要强制执行此操作,您是否在BudgetService中移动此代码?

在这个用例中,我认为将语句留在应用程序服务中是可以接受的。由于单元测试仍然很简单,有两条备用路径(初始和预算存在)。如果用例变得更复杂或者其他一些用例涉及预算创建,那么您可能需要一个BudgetFactory来保存该语句。

  

要封装budgetService.nextIndex调用,最好起诉a   工厂创建预算? (代替构造函数)。并做   你认为现在是计算nextIndex的最佳时机。

是的,我认为现在是时候了。如果没有在这里计算,它很可能会在持久层中计算,这更难以测试,因为它们通常需要更多的努力来编写和维护,而且更脆弱。

  

应用程序层是subjet到单元测试或者可能只是   整合测试?为什么要问这个?当我试图进行单元测试时   这个方法我需要至少模拟BudgetService,   BudgetRepository,我在想:它是代码味道的标志吗?   另一种简化单元测试的解决方案是提取   budgetService.existsBudgetInitialFor(year)作为私有方法,mock   它按我的意愿返回TRUE / FALSE等。

考虑到此用例的简单性,如果将if-else语句留在应用程序层中,请进行单元测试。如果引入BudgetFactory来封装if-else和nextIndex,则应用程序层将变得很薄。如果你采用tdd,你仍然可以编写单元测试,如果没有,也可以通过功能测试保留它。

  

我没有想到在编写此代码时创建的是   实体的责任。我找到了“如果......别的话”的尴尬   ......“预算实体内部的逻辑。我是对的吗?(请告诉我,我是   至少一次:D)   我同意。单个预算无法判断是否存在其他预算。

     

我问自己的最后一个问题:也许我错了   开始。如果UI发送createInitialBudget命令并且更好   然后是createCorrectiveBudget命令?在这种情况下,我们需要检查   命令有效性(避免多次初始创建:独特   验证)。

当UI显示带有createCorrectiveBudget和createInitialBudget的两个按钮并让用户选择他/她应该单击的按钮时,我认为这不是一个好主意。后端的验证很好,但知识泄漏,它迫使人们做出不必要的选择。