I have an application that follows Domain Driven Design rules. One of the rules of DDD is that the domain layer should not depend on the infrastructure layer. I have a tool that checks if there are any dependencies (imports) from domain to infrastructure. In my infrastructure layer, I have a class hierarchy like this:
In my domain layer, like this:
Now, I need to map my domain classes to the infrastructure entities in order to save them in my database.
I have an out port in my domain which is an interface:
// infra/ports/out/RuleRepository.java
public interface RuleRepository {
Rule save(Rule rule);
}
This interface in implemented in the infrastructure layer:
// domain/RuleRepositoryJpaAdapter.java
public class RuleRepositoryJpaAdapter implements RuleRepository {
// extends CrudRepository<RuleEntity, Long>
RuleCrudRepository jpaRepository;
RuleMapper mapper;
@Override
public Rule save(Rule rule) {
return jpaRepository.save(mapper.mapToEntity(rule))
.mapToDomain();
}
}
I'm looking for a way to implement mapToEntity
without having to check the type of the rule. The best way would be to add a mapToEntity
method to Rule
, IpRule
, BlackListRule
but this would break the unit tests that check if there is any imports between the domain and infrastructure layer. If there any other way?
What I have right now:
public class RuleMapper {
public RuleEntity mapToEntity(Rule rule) {
if (rule instanceof IpRule) {
return new IpRuleEntity().mapFromDomain(rule);
} else if (rule instanceof BlackListRule) {
return new BlackListRuleEntity().mapFromDomain(rule);
} else {
throw new IllegalArgumentException("Unexpected argument of type " (rule == null ? "null" : rule.getClass().getName()));
}
}
}
CodePudding user response:
You could use something like the Visitor Pattern to implement double dispatch.
In this example, it could look something like this:
public abstract class Rule {
// Maps a rule to a result type T
public interface Mapper<T> {
T mapIpRule(IpRule rule);
T mapBlackListRule(BlackListRule rule);
}
public abstract <T> T map(Mapper<T> mapper);
// ...
}
public class IpRule extends Rule {
@Override
public <T> T map(Mapper<T> mapper) {
return mapper.mapIpRule(this);
}
// ...
}
public class BlackListRule extends Rule {
@Override
public <T> T map(Mapper<T> mapper) {
return mapper.mapBlackListRule(this);
}
// ...
}
public class RuleMapper implements Rule.Mapper<RuleEntity> {
public RuleEntity mapToEntity(Rule rule) {
return rule.map(this);
}
@Override
public RuleEntity mapIpRule(IpRule rule) {
return new IpRuleEntity();
}
@Override
public RuleEntity mapBlackListRule(BlackListRule rule) {
return new BlackListRuleEntity();
}
}
It has a nice property that there is compile-time check that all subtypes are handled correctly. If a new subtype of Rule
is added later, it will need to implement the map
method, which will require adding a method to the Rule.Mapper
interface, which in turn will require the RuleMapper
implementation to implement that method. In the example given in the question that uses runtime type checks with instanceof
, it's possible to miss a case, resulting in an IllegalArgumentException
at runtime.
However, you will need to judge whether the extra complexity is worthwhile for your specific situation. It might be that your existing RuleMapper
is just fine.