创建方法来计算域或服务中的字段

Create method to calculate a field in the Domain or in the Service

如果我们有一本 Class 本书,我们想要按照一些规则计算一本书的分数,例如“如果页数低于 X,那么我们需要从分数中减去 Y”和使用六边形架构。我们是否应该将此方法 calculateScore() 放在单独的服务中,以防将来使用不同字段更改此逻辑,或者此责任应该在域本身中?

第一种方法

package com.xxx.domain;

[...]
public class Book {

    [...]

    public Double score() {
        [...]
    }

    [...]

}

第二种方法

package com.xxx.application;

[...]
public interface ScoreService {

    [...]

    void calculateScore(Book book);

    [...]

}

如果分数的计算仅取决于书籍状态,我会在书籍实体中创建一个方法来计算它。

否则,如果它也依赖于其他领域对象,我会创建一个领域服务来计算它。

关于坚持得分。如果计算过程非常复杂并且需要很多时间,我会坚持下去。不然我也不会坚持,需要的时候再计算。

如果你坚持 jt,你必须考虑到你必须重新计算它并在每次它所依赖的其他值发生变化时坚持新值。

Should we place this method calculateScore() in a separate Service in case this logic changes in the future using different fields or this reponsibility should be in the Domain itself?

首先,在谈到“业务逻辑应该放在哪里?”这个问题时,干净的架构非常清晰。

  • 实体层中的应用程序不可知业务逻辑。
  • 用例层中特定于应用程序的业务逻辑。

但我认为您的问题有点不同,是关于 anemic or rich domain models。我不能在这里把我的每一个想法都告诉你,但是我已经把大部分的想法写在我之前链接的博客里了。

我的简明陈述是

rich domain models combine data and logic while anemic models separate them.

想想贫血的方法吧...

如果将逻辑放在单独的服务中,通常意味着您必须公开图书的所有属性。如果服务在同一个包中,您将使它们成为 public 或至少包范围。

你的问题也集中在变化上。您声明如果将逻辑放在单独的服务中,可以更好地处理逻辑更改。没错,但这是有代价的。

的确,贫血模型可以让您更轻松地添加逻辑,但每个逻辑(每个服务)都必须对贫血模型有相同的解释。我的意思是每个服务都必须知道如何正确修改数据结构以保持其一致性,并且当服务数量增加时将很难维护。

但是实施服务也可以是一个很好的中间步骤,因为它会给您关于凝聚力的暗示。内聚通常会告诉你在哪里放置一个方法。例如

public class ScoreService {

    public BookScore calculateScore(Book book, BookRentals rentals){
        int pageCount = book.getPageCount();
        Author author = book.getAuthor();

        // calculate a new value based on pageCount and the author
        // ...

        OtherValue ov = book.getSomeOtherValue();
        // do something with ov

        int rentalCount = rentals.getCountSince(someDate);
        // ... and so on
    }
}

当您查看上面的 calculateScore 时,您会发现在 Book 上有很多 get 调用,而在 BookRentals 上调用较少。这暗示 calculateScore 需要的大部分数据都放在 Book 中。因此 calculateScore 的内聚性比 Book 更高,并且该方法可能会放在 Bookclass 中。例如

public class Book {

    public BookScore getScore(BookRentals rentals){
        int pageCount = this.getPageCount();
        Author author = this.getAuthor();

        // calculate a new value based on pageCount and the author
        // ...

        OtherValue ov = this.getSomeOtherValue();
        // do something with ov

        int rentalCount = rentals.getCountSince(someDate);
        // ... and so on
    }
}

区别很明显:

  1. 方法参数数量减少。也许你应用了 DDD 并且 Book 是一个聚合根并且也可以访问 BookRentals。然后你的参数可能会减少到零。
  2. 由于 getScore 需要的大部分属性都位于 Book class 中,您可能希望将它们的可见性降低为私有。因此不允许不受控制的访问。

当开发人员将逻辑放入实体中时,经常出现的一个问题是:“实体如何从数据存储中访问数据?”

我的解决方案是将存储库传递给需要它的方法。例如

public class Book {

    public BookScore getScore(BookRentalRepository repo){
        // ...
        int rentalCount = repo.getRentalCountSince(this, someDate);
    }
}

无论您想走什么路,贫血还是富有,都将逻辑保留在 POJO 中。还要记住 POJO can be more than a simple data structure.

希望我的回答能帮助您针对您的具体应用做出决定。