Consider the following fictitious toy example:
There is an aggregate root DayChecklist that holds a list of Tasks planned for the day:
class DayChecklist {
String id;
DateTime today;
...
List<Task> tasks;
// lots of other business logic
}
Task itself is modeled as:
abstract class Task {
DateTime startTime;
DateTime endTime;
// lots of other common business logic
}
Let subtypes of Task be:
- TemperatureTask
class TemperatureTask extends Task {
double temperatureAtStart;
double temperatureAtEnd;
public recordStartingTemperature(double temperature) {
// some business logic
temperatureAtStart = temperature;
}
public recordEndingTemperature(double temperature) {
// some business logic
temperatureAtEnd = temperature;
}
...
}
- PressureTask
class PressureTask extends Task {
double pressureAtStart;
double pressureAtEnd;
public recordStartingPressure(double pressure) {
// some business logic
pressureAtStart = pressure;
}
public recordEndingPressure(double pressure) {
// some business logic
pressureAtEnd = pressure;
}
...
}
Please assume that the Task subtypes have a lot of common business logic like state transitions (i.e. is-a relationship makes sense) and the AR and Tasks are behavior-rich models (i.e. DDD makes sense). What differs is some specific properties (like the temperature and pressure readings above).
Now, when a Task needs to be updated (say recordStartingPressure() for a PressureTask), the update should ideally pass through the aggregate root.
- Does this mean that the aggregate root has a specific method defined for each subtask? In the example above, it is possible to have polymorphic recordStartReading and recordEndReading on the subtasks and the aggregate root could delegate such behavior. However, I am asking about the more general case when such polymorphism cannot easily be extracted.
- Also, what if it is certain that more Task types will be added in the future. Does that mean that corresponding methods also get added to the AR? This will violate OCP. Should some pattern be used for such a situation to allow extensibility?
Thanks!
Edit
To highlight the crux of the problem, consider the following Task:
PressureIntervalTask
class PressureIntervalTask extends Task {
List<Pair<DateTime, Double>> instantPressurePairs;
public recordPressureAtInstant(double pressure, DateTime instant) {
// some business rules
instantPressurePairs.add(new Pair(instant, pressure));
}
...
}
Now, PressureIntervalTask has a method recordPressureAtInstant() that has a completely different signature. So, should the AR have a corresponding recordPressureAtInstant() method as well?
Essentially, the Tasks can have metadata that can differ in terms of number of fields and their type. One extreme solution could be to model metadata as a JSON Object that the specific Task knows how to parse and apply. Then the AR could have a boolean applyMetadataUpdate(JSONObject metadata)
and each Task can implement a polymorphic version, where the target Task may use a type identifier (for example) and return True if the update was handled/applied.