0

I am in the process of developing some software using CQRS, ES and DDD. A part of the system is writing services for controlling devices and collecting data from them.

Consider a "Start" command coming from a user interface. The success of this command depends on the execution of the start() command on the device API itself. So is it acceptable to inject the device API into the domain entity as follows:

const (
  On int = iota
  Off
)

// The device entity and aggregate root
type Device struct {
  ID string
  Status int
}

// Infrastructure service for starting a device
type Starter interface {
  Start() error
}

// Entity behaviour for starting the device
func (device *Device) Start(
  starter Starter,
  annotation string,
) error {
  if device.Status == On {
    return fmt.Errorf("device already started")
  }

  err := starter.Start()
  if err != nil {
    return err
  }

  // This will handle the event and change the entity state.
  // I left it out of the example for the sake of simplicity.
  device.raiseEvent(
    &DeviceStartedEvent{
      Time: time.Now(),
      Annotation: annotation,
    },
  )

  return nil
}

Additionally, Stop(), Configure(), etc. commands could also be implemented for the device.

My understanding is that events need to be raised in the domain, so in my mind it wouldn't make sense to handle this behaviour in the application layer. Some examples of why this wouldn't work:

  • What would happen if the device starts successfully, but the start() command on the aggregate fails?
  • If we do it the other way around, the start() command on the aggregate might succeed, but starting the device via its API might fail.

Would this "Starter" service be considered a domain service or an infrastructure service? I don't know why, but something feels uncomfortable about this code.

2

is it acceptable to inject the device API into the domain entity as follows

The DDD police are not going to come after you. Evans, in the blue book, documents a number of common patterns when implementing domain models, but that's not a closed set: you are allowed to use other patterns if they are appropriate.

In his chapter on modeling a domain, Evans describes three common patterns: entities, value objects, and "stateless" domain services. It's fairly common to have your domain entity talk to something on the "outside" via a domain service, and passing the domain service as an argument is the usual way to make that possible.

Starter, here, looks like a pretty reasonable domain service.


This isn't the only possible answer. Domain models are largely about bookkeeping; they aren't "do-ers", they are "keep-track-of-ers". Domain entities are how we write down what happens. The doing of things (including storing copies of the entities in our database) is often located in the application code.

In cases where that separation is important (in particular, in cases where you don't want failure modes polluting your domain logic), it may make sense to do the work a level up, and then pass information to the domain entities to compute what to write down.

I recommend taking a look at Cory Benfield's talk on building protocol libraries, which uses a similar pattern.

What would happen if the device starts successfully, but the start() command on the aggregate fails?

Failure modes in distributed systems are hard. What if the device starts, but the message announcing that the device started gets lost on the network? What if the entity computes what to write down, but the app crashes trying to write the data?

If you are trying to distribute two or more side effects, and you don't have the guarantees about being able to commit/rollback both effects together, then you are pretty much guaranteed at least one failure mode where the system ends up in an inconsistent state.

1
  • This is a great answer, thank you. Side effects and failure modes seem to be two of the concepts I've been struggling with - the terminology and meaning you provided will help me a lot going forward as well. Trying to solve a problem without the correct terminology is like trying to slap a ghost. – fanie42 Apr 5 at 14:12

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.