The rationale for DDD is that Domain Objects are abstractions which should fulfil your functional domain requirements - if Domain Objects are unable to easily fulfil those requirements, it suggests you might be using the wrong abstraction.
Naming Domain Objects using Entity Nouns can lead toward those objects becoming tightly-coupled with each other and becoming bloated "god" objects, and they can throw up issues such as the one in this question such as "Where is the right place to put the CreateOrder method?".
To make it easier to identify the 'right' Aggregate Root, consider a different approach where Domain Objects are based on the functional high-level business requirements - i.e. choose nouns which allude to functional requirements and/or behaviours that users of the system need to perform.
DDD is an approach to OO Design which is intended to result in a graph of Domain Objects in the Business Layer of your system - Domain objects are responsible for satisfying your High-level Business requirements, and ideally should be able to rely on the Data Layer for things like the performance and integrity of the underlying persistent data store.
Another way to look at it could be the bullet points in this list
- Entity Nouns typically suggest data attributes.
- Domain Nouns should suggest behaviour
- DDD and OO Modelling are concerned with abstractions based on functional requirements and core domain/business logic.
- The Business Logic Layer is responsible for satisfying high-level domain requirements
One of the common misconceptions regarding DDD is that Domain Objects should be based on some physical real-world "thing" (i.e. some noun which you can point to in the real world, attributed with all kinds of data/properties), however the data/attributes of those real world things do not necessarily make a good starting point when trying to nail down functional requirements.
Of course, Business Logic should use this data, but Domain Objects themselves should ultimately be abstractions which represent functional Domain requirements and behaviours.
For example; nouns such as
Customer do not imply any behaviour, and therefore are generally unhelpful abstractions for representing business logic and Domain Objects.
When looking for the kinds of abstractions which might be useful for representing Business Logic, consider typical requirements which you might expect a system to fulfil:
- As a Salesperson, I want to Create an Order for a New Customer so that I can generate an invoice for the Products to be sold with their Prices and Quantity.
- As a Customer Service Advisor I want to Cancel a Pending Order so that the Order is not fulfilled by a Warehouse Operator.
- As a Customer Service Advisor I want to Return an Order Line so that the Product can be adjusted into the Inventory and Payment is Refunded back via the Customer's original Payment method.
- As a Warehouse Operator I want to view all Products on a Pending Order and the Shipping information so that I can Pick the products and ship them via the Courier.
Modelling Domain Requirements with a DDD Approach
Based on the above list, consider some potential Domain Objects for such an Orders system:
As domain objects, these represent abstractions which take ownership of various behavioural domain requirements; indeed their nouns hint strongly at the specific functional requirement(s) they fulfil.
(There may be be additional infrastructure in there too such as an
EventMediator to pass notifications for observers wanting to know when a new order has been created, or when an order has been shipped, etc).
SalesOrderCheckout probably needs to handle data about Customers, Shipping and Products, however isn't concerned with anything to do with the behaviour for shipping orders, sorting pending orders or issuing refunds.
SalesOrderCheckout to fulfil its domain requirements includes enforcing those business rules such as preventing customers ordering too many items, possibly running some validation, and perhaps raising notifications for other parts of the system - it can do all of those things without necessarily needing to depend on any of the other objects.
DDD using Entity Nouns to represent Domain Objects
There are a number of potential dangers when treating simple nouns such as
Product as Domain Objects; among those problems are those you allude to in the question:
- If a method handles an
Customer and a
Product, which Domain Object does it belong to?
- Where is the Aggregate Root for those 3 Objects?
If you choose Entity Nouns to represent Domain Objects, a number of things may happen:
Product are at risk of growing into "god" objects
- Risk of ending up with a single
Manager god-object to tie everything together.
- Those objects risk becoming tightly coupled to each other - it may be hard to fulfil domain requirements without passing
- A risk of developing "leaky" abstractions - i.e. domain objects being expected to expose dozens of
set methods which weaken encapsulation (or, if you don't, then some other programmer probably will later on..).
- A risk of Domain Objects becoming bloated with a complex mixture of business data (e.g. user data input via a UI) and transitory state (e.g. a 'history' of user actions when the order has been modified).
DDD, OO Design and Plain Models
A common misconception regarding DDD and OO Design is that "plain" models are somehow 'bad' or an 'anti-pattern'. Martin Fowler wrote an article describing the Anaemic Domain Model - but as he makes it clear in the article, DDD itself should not 'contradict' the approach of clean separation between layers
"It's also worth emphasizing that putting behavior into the domain objects should not contradict the solid approach of using layering to separate domain logic from such things as persistence and presentation responsibilities. The logic that should be in a domain object is domain logic - validations, calculations, business rules - whatever you like to call it."
In other words, using plain Models for holding business data transferred between other layers (e.g. an Order model passed in by a user application when the user wants to create a new order) is not the same thing as an "Anaemic Domain Model". 'plain' data models are often the best way to track data and transfer data between layers (such as a REST web service, a persistence store, An application or UI, etc).
Business logic may process the data in those models and may track them as part of the business state - but won't necessarily take ownership of those models.
The Aggregate Root
Looking again at the example Domain Objects -
OrderRefundProcessor there's still no obvious Aggregate Root; but that doesn't actually matter because the these Domain Objects have wildly separate responsibilities which don't seem to overlap.
Functionally, there's no need for the
SalesOrderCheckout to talk to the
PendingOrdersStream because the job of the former is complete when it has added a new order to the Database; on the other hand, the
PendingOrdersStream can retrieve new orders from the Database. These objects don't actually need to interact with each other directly (Perhaps an Event Mediator might provide notifications between the two, but I would expect any coupling between these objects to be very loose)
Perhaps the Aggregate Root will be an IoC Container which injects one or more of those Domain Objects into a UI Controller, also supplying other infrastructure like
Repository. Or perhaps it will be some kind of lightweight Orchestrator Service sitting on top of the Business Layer.
The Aggregate root doesn't necessarily need to be a Domain Object. For the sake of keeping Separation of Concerns between Domain objects, it's generally a good thing when the aggregate root is a separate object with no business logic.