0

Assume we're building a fleet management system that is monitoring telemetry on a fleet of cars. Important telemetry include like location, speed and engine temperature. Metrics are mostly dynamic and depend on what sensors they want to install (e.g. vibration, tyre pressure, weight sensors).

Metrics are viewed as potentially different individual streams of data with different periods of updates. e.g. speed might be updated twice a minute, whereas tyre pressure might be updated once an hour.

The are two primary use cases - viewing the current state of a vehicle and being able to dig into specific historical metrics (e.g. graph engine temperature over the past 48 hours).

My domain looks something like:

Vehicle (aggregate root) 
  |--> Metrics (value object)
     |--> Metric (value object)

This gives you the latest state of the Vehicle.

However, as mentioned, a valid use case might be to graph engine temperature and speed over the past month, which, in this case could equate to thousands (or tens of thousands) of data points.

One approach is to do something like the following:

class Vehicle {
  Metrics metrics;
  @Transient List<Metric> historicalMetrics; //Don't save this as part of Vehicle

  updateMetrics(Metrics newMetrics) {
    this.metrics = this.metrics.merge(newMetrics);

    this.historicalMetrics.add(newMetrics.asChanges());
  }
}

class VehicleUpdater {
  updateVehicle(UpdateVehicleCommand command) {
    vehicle.updateMetrics(command.getNewMetrics());
    this.repository.save(vehicle);
  }
}

class VehicleRepository {
  save(Vehicle vehicle) {
    this.vehicleStore.save(vehicle);

    if (this.vehicleStore.hasHistoricalMetrics()) {
      this.historicalMetricsStore.saveAll(this.vehicleStore.getHistorialMetrics());
    }
  }
}

There isn't really much use for historical metrics within the Vehicle domain itself. There is use in showing it to the user for graphing and diagnostics purposes as well as potentially using it as part of a Rules Engine domain (not sure if this falls under the Vehicle domain or is separate) to raise events (e.g. preventative maintenance).

If the data should indeed be part of the domain, what approaches should I take to store all the historical metrics. Would they live within the Vehicle?

However I'm not actually sure how I would query for historical metrics. I obviously wouldn't want to hydrate historical metrics for the Vehicle when loading it, since, in most cases, they aren't relevant.

What I could do is something like:

class Vehicle {
  List<Metric> getMetricsFor(String metricName) {
    return this.metricRetriever.loadMetrics(this, metricName);
  }
}

class MetricRetriever extends DomainService {
  loadMetrics(Vehicle vehicle, String metricName) {
    this.repository.loadMetrics(vehicle, metricName);
  }
}

However, in most cases you'd want to filter the metrics (e.g. within a time range or numbers within / outside a range, etc), which is leading me to consider whether I should store this data within the domain or just consider the historical metrics to be a query model and use CQRS to persist it separately?

If I take that approach, however, the query / read model will have historical data that isn't part of the domain model. It won't simply be a more efficient way of storing the data. Not sure if this takes it beyond the intent of CQRS?

  • 1
    Real time. The system you are describing above is not a good fit for DDD. The purpose of DDD is to simplify systems by way of using its behavior as a means to inform the design. Your system has no behavior (i.e. rules). It's axiomatic CRUD. You are simply collecting data for the purpose of aggregation/display (the 2 use-cases you outline). There is nothing to be gained by employing DDD, and in fact, it has lead to this very thread. Simply save each data stream to persistent storage and create views of the data. Don't over-complicate this. – king-side-slide Jun 12 at 16:00
3

whether I should store this data within the domain or just consider the historical metrics to be a query model and use CQRS to persist it separately?

One thing to keep in mind is that your domain model is not the authority for your vehicle metrics -- the real world is. The real world is a firehose of telemetry pointing at you. So typically that data is going to go straight to the database, taking care that you don't overwrite any previous information by mistake, and possibly screening out duplicate metrics.

Your model for "current state of the vehicle" is likely to be derived from the values in the database. So there will be two parts to that -- the domain values in the report that you are calculating from the stored telemetry, and additional bookkeeping to track how far you've gotten into the telemetry stream(s).

It's possible that your "commands" are all going to look like "process the next batch of events".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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