Suppose we have Promotion as an Aggregate Root and Rules to satisfy a promotion as an Aggregate. Rules are a collection of Different Rule Elements that extends Abstract class Rule.
As I learned I can use factory method, example:
class Promotion (
PromotionIdentity $identity,
string $name,
){
$this->identity = $identity;
$this->name = $name;
$this->rules = new RuleCollection();
}
public function addRule(
RuleIdentity $ruleIdentity,
RuleType $ruleType,
array $configuration
) {
if (RuleType::EMAIL_LIST === $ruleType->value()) {
$makeRule = new EmailListRule($ruleIdentity, $configuration);
$this->rules->add($makeRule);
}
if (RuleType::MIN_ARTICLES === $ruleType->value()) {
$makeRule = new MinimumArticlesRule($ruleIdentity, $configuration);
$this->rules->add($makeRule);
}
... and so on, for example 15 rules
}
I think that this can grow a lot, I see a code smell here.
It's ok to keep this logic of rule creation inside aggregate root?, can we move this responsability of rule creation to the application service within a factory and pass the builded Rule to the addRule Method? Other Options? Thanks Friends!
Rules to satisfy a promotion as an Aggregate
Well, an AR cannot hold instances of other ARs, so Rule
may not be an AR here. Furthermore, at first glance and without knowing your domain, I see very little reason for Rule
to even be an entity from the domain's perspective (it could from the storage perspective). Couldn't Rule
just be a value object, entirely replaced if needed to be changed?
//Application service
changePromotionRules(String promotionId, Set<Map<String, String>> rulesConfig) {
Set<Rule> rules = ruleFactory.rulesFromConfig(rulesConfig);
Promotion promotion = promotionRepository.promotionOfId(new PromotionIdentity(promotionId));
promotion.changeRules(rules);
}
//Promotion AR
changeRules(Set<Rule> rules) {
validateRules(rules);
this.rules = rules;
}
The above example assumes a CRUD-like behavior where all rules are replaced at once, but you could do the same with more granular addRule
/removeRule
operations which may be more suited.
If you have a disconnect between the UI behavior (e.g. all rules saved at once) and the domain (e.g. addRule/removeRule) then you could adapt this in the application service layer.
E.g.
//Application service
changePromotionRules(String promotionId, Set<Map<String, String>> rulesConfig) {
Set<Rule> rules = ruleFactory.rulesFromConfig(rulesConfig);
Promotion promotion = promotionRepository.promotionOfId(new PromotionIdentity(promotionId));
withSetDiffBetween(rules, promotion.rules(), new SetDiffHandler<Rule>() {
withAdd(Rule rule) { promotion.addRule(rule); }
withRemove(Rule rule) { promotion.removeRule(rule); }
});
}