6

I have a list of product price objects, List<ProductPrice>.

@Value
public class ProductPrice {

    Long id;
    LocalDate startDate;
    LocalDate endDate;
    BigDecimal value;
    
}

From the list I need to find prices that have a start date equal to or greater than a given start date AND an end date equal to or before the end date. I do that via:

List<ProductPrice> pricesInDateRange = productPrices.stream()
        .filter(p -> p.getStartDate().equals(startDate) || p.getStartDate().isAfter(startDate))
        .filter(p -> p.getEndDate().equals(endDate) || p.getEndDate().isBefore(endDate))
        .collect(Collectors.toList());

I need to also make sure that one price has a start date equal to the given start date and same of end date.

Finally, I need to perform some calculations on the list of ProductPrice objects using the value field. For example,

pricesInDateRange 
    .stream()
    .map(value -> value.add(BigDecimal.ONE))
    .reduce(BigDecimal.ONE, BigDecimal::multiply);

Should I create a class to wrap List<ProductPrice> for a given date range and encapsulate the calculations? For example,

@Data
public class ProductPrices {

    private List<ProductPrice> productPrices;
    
    public ProductPrices getPricesInDateRange(LocalDate startDate, LocalDate endDate) {
      
          return new ProductPrices(productPrices.stream()
            .filter(p -> p.getStartDate().equals(startDate) || p.getStartDate().isAfter(startDate))
            .filter(p -> p.getEndDate().equals(endDate) || p.getEndDate().isBefore(endDate))
            .collect(Collectors.toList()));
   }
   
   public BigDecimal calculation1() {
        productPrices
            .stream()
            .map(value -> value.add(BigDecimal.ONE))
            .reduce(BigDecimal.ONE, BigDecimal::multiply);
   }

Or should I do these operations in the service layer instead of a domain object? In other words, don't encapsulate in a ProductPrices class.

1 Answer 1

6

If you're aiming for an object oriented design, you should try to co-locatate all behavior with its data. That is pretty much the basic premise behind object-orientation.

Note 1: encapsulate != include. If you put data and methods in one class, then you just included both in one class. It becomes encapsulated only if there are no way around your "capsule". If the same data that is available for the included methods are also available to anyone else, then that is not encapsulation. Everyone is free to use the data however they like. They are free to misuse the data, even if unintentionally. Even worse, they have to possess the same amount of knowledge.

Note 2: If you're doing the co-locating consistently, you'll find that "business logic" always has a place in the "domain", not in the "service layer". I understood the Blue Book to say that the service layer is a technical thing only, so that it agrees with the above. But, regardless of what DDD does or does not say, if you want maintainable code, you'll want to bundle things that change together, like data and its behavior.

3
  • Would love to also hear your thoughts on this unpopular bit: The Anemic Domain Model is no Anti-Pattern, it’s a SOLID design
    – sepehr
    Jun 18, 2021 at 17:48
  • @sepehr I think the author is onto something. I also think that trying to interpret and follow SOLID will lead you to an anemic design. I think since Clean Architecture came out it is clear that Robert Martin intended it that way, and/or has no problems with that. This doesn't mean that ADM is then "good". It means exactly the opposite, that SOLID is probably bad. I also disagree with the example and following assumptions and analysis. Jun 18, 2021 at 20:10
  • Thank you for taking the time to read & your input. Much appreciated.
    – sepehr
    Jun 18, 2021 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.