I like the solution provided by @blaž-mrak, as it really simple, and allows the front-end to decide what they really want.
However, if you where to actually allow requesting several calculations at once, here is an idea on how to do so:
By the way your code looks, you probably have some logic in your application that looks kind of like (let's call the class containing that logic Controller
):
PriceMetricsDto dto = new PriceMetricsDto();
if (startDate != null) {
dto.setCustomAnnualAverage(calculateAnnualAverage(startDate, endDate, productDetails))
dto.setCustomTotal(calculateTotal(startDate, endDate, productDetails))
} else {
dto.setThreeYearAnnualAverage(calculateThreeYearAnnualAverage(endDate, productDetails))
dto.setFiveYearAnnualAverage(calculateFiveYearAnnualAverage(endDate, productDetails))
dto.setTenYearAnnualAverage(calculateTenYearAnnualAverage(endDate, productDetails))
dto.setFifteenYearAnnualAverage(calculateFifteenYearAnnualAverage(endDate, productDetails))
dto.setTwentyYearAnnualAverage(calculateTwentyYearAnnualAverage(endDate, productDetails))
// Same with totals
}
Now, the idea to remove this similar and duplicated code is as follows:
Your DTO could have to collections of BigDecimal
s, one for averages and another for totals. And those values would be calculated implementing the strategy pattern, and allowing the front-end to decide which values it actually wants.
So, your DTO would be something like:
@Data
public class PriceMetricsDto {
private String productCode;
private String productClass;
private String productFee;
private String currency;
private EnumMap<PeriodAverageEnum, BigDecimal> periodAverages = new EnumMap<>(PeriodAverageEnum.class);
private EnumMap<PeriodTotalEnum, BigDecimal> periodTotals = new EnumMap<>(PeriodTotalEnum.class);
}
Where PeriodAverageEnum
and PeriodTotalEnum
would be as such:
public enum PeriodAverageEnum {
CUSTON_PERIOD (() -> new CustomAverage()),
THREE_YEARS (() -> new ThreeYearsAverage()),
FIVE_YEARS (() -> new FiveYearsAverage()),
TEN_YEARS (() -> new TenYearsAverage());
// ...
private final Supplier<IPeriodAverageStrategy> supplier;
private PeriodAverageEnum(Supplier<IPeriodAverageStrategy> supplier) {
this.supplier = supplier;
}
public IPeriodAverageStrategy getStrategy() {
return supplier.get();
}
}
(PeriodTotalEnum
would be really similar, so I'm going to omit it)
And you would have two interfaces IPeriodAverageStrategy
and IPeriodTotalStrategy
such as:
public interface IPeriodAverageStrategy {
BigDecimal calculateAverage(Date startDate, Date endDate, ProductDetails productDetails);
}
This way, you would have different strategies for each of the requested values. For example:
public class ThreeYearsAverage implements IPeriodAverageStrategy {
@Override
public BigDecimal calculateAverage(Date startDate, Date endDate, ProductDetails producDetails) {
// do your stuff here
return calculateAnnualAverage(startDate, endDate, productDetails);
}
}
And last but not least, Controller
would now have a method such as:
public PriceMetricsDto getPriceMetrics(Date startDate, Date endDate,
Set<PeriodAverageEnum> periodAveragesDesired, Set<PeriodTotalEnum> periodTotalsDesired) {
// Get you product details somewhere...
ProductDetails productDetails = new ProductDetails();
// Build your dto with the needed product details...
PriceMetricsDto dto = new PriceMetricsDto();
EnumMap<PeriodAverageEnum, BigDecimal> periodAvgs = dto.getPeriodAverages();
for (PeriodAverageEnum periodAvgDesired : periodAveragesDesired) {
periodAvgs.put(periodAvgDesired, periodAvgDesired.getStrategy().calculateAverage(startDate, endDate, productDetails));
}
// Note this code is kind of the same as the above. It could be extracted using generics
EnumMap<PeriodTotalEnum, BigDecimal> periodTotals = dto.getPeriodTotals();
for (PeriodTotalEnum periodTotalDesired : periodTotalsDesired) {
periodTotals.put(periodTotalDesired, periodTotalDesired.getStrategy().calculateTotal(startDate, endDate, productDetails));
}
return dto;
}
All this being said, the front-end could now make requests such as:
SOME_END_POINT?startDate=2020-01-01&endDate=2021-02-28&periodAveragesDesired=CUSTON_PERIOD&periodAveragesDesired=THREE_YEARS
SOME_END_POINT?endDate=2021-02-28&periodTotalsDesired=SIX_MONTHS
...
in which the front-end would, with just one request, retrieve all data needed.
If you wanted to further improve this, you could add default values to periodAveragesDesired
and periodTotalsDesired
, so the front-end does not need to bother about them unless it really needs something specific.
All this being said, unless this type of solution is absolutely necesary for your use case, the KISS principle rules. So I'd follow @blaž-mrak's advice